98 lines
2.6 KiB
Dart
98 lines
2.6 KiB
Dart
// 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<PlayerOpeningFrame> createState() => _PlayerOpeningFrameState();
|
|
}
|
|
|
|
class _PlayerOpeningFrameState extends State<PlayerOpeningFrame> {
|
|
@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,
|
|
),
|
|
],
|
|
);
|
|
}
|