// Author: fruit.com.cn import 'dart:async'; import 'package:flutter/material.dart'; import '../../../core/data/services/sprite_thumbnail_axis.dart'; import '../../../core/theme/app_colors.dart'; import 'scrub_preview_card.dart'; /// 当前集雪碧图首帧在视频纹理未就绪时占位;空地址交还纯黑底。 Widget? playerOpeningFrame( String? thumbnailsUrl, { required SpriteThumbnailAxisSource spriteSource, BoxFit fit = BoxFit.cover, }) { final url = thumbnailsUrl?.trim() ?? ''; if (url.isEmpty) return null; return PlayerOpeningFrame( thumbnailsUrl: url, spriteSource: spriteSource, fit: fit, ); } /// 与视频共用全屏父约束和 BoxFit,切换时保持相同可见区域。 class PlayerOpeningFrame extends StatefulWidget { const PlayerOpeningFrame({ super.key, required this.thumbnailsUrl, required this.spriteSource, this.fit = BoxFit.cover, }); /// 当前视频雪碧图 WebVTT 地址。 final String thumbnailsUrl; /// 进度条拖动预览共用的雪碧图时间轴与解码缓存。 final SpriteThumbnailAxisSource spriteSource; /// 必须与同页视频层使用相同适配规则。 final BoxFit fit; @override State createState() => _PlayerOpeningFrameState(); } class _PlayerOpeningFrameState extends State { @override void initState() { super.initState(); widget.spriteSource.addListener(_handleSpriteChanged); unawaited( widget.spriteSource.prepareAt(widget.thumbnailsUrl, Duration.zero), ); } @override void didUpdateWidget(covariant PlayerOpeningFrame oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.spriteSource != widget.spriteSource) { oldWidget.spriteSource.removeListener(_handleSpriteChanged); widget.spriteSource.addListener(_handleSpriteChanged); } if (oldWidget.thumbnailsUrl != widget.thumbnailsUrl) { unawaited( widget.spriteSource.prepareAt(widget.thumbnailsUrl, Duration.zero), ); } } @override void dispose() { widget.spriteSource.removeListener(_handleSpriteChanged); super.dispose(); } void _handleSpriteChanged() { if (mounted) setState(() {}); } @override Widget build(BuildContext context) => Stack( fit: StackFit.expand, children: [ const ColoredBox(color: AppColors.mediaBlack), if (widget.spriteSource.cachedFrameAt(widget.thumbnailsUrl, Duration.zero) case final frame?) SpriteCropPreview( key: const Key('player-opening-sprite'), image: frame.image, sourceRect: frame.sourceRect, fit: widget.fit, ), ], ); }