feat(app): 完善播放协议搜索与空状态

This commit is contained in:
Zhou Weidong committed 2026-10-23 12:38:47 +08:00
1 parent 671e0ce143
commit ef29fc5f15
85 files changed
+2976 -768

No files matched your search

@@ -10,16 +10,18 @@ const Duration _videoFadeDuration = Duration(milliseconds: 200);
const double _errorHorizontal = 32;
const int _errorMaxLines = 3;
/// 黑色底层与视频层共用同一控制器,视频首帧就绪后再淡入。
/// 黑色底层与视频层共用同一控制器。清晰度切换时用仍在播放的旧画面盖住新纹理的黑帧。
class PlayerVideoSurface extends StatefulWidget {
const PlayerVideoSurface({
super.key,
required this.controller,
required this.videoUrl,
required this.theater,
this.handoff,
});
final VideoPlayerController? controller;
final VideoPlayerController? handoff;
final String videoUrl;
final bool theater;
@@ -48,7 +50,8 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface> {
Widget _frame(VideoPlayerValue? value) {
final controller = widget.controller;
final reveal = _shouldReveal(value);
final handoff = _liveHandoff(controller);
final reveal = _shouldReveal(value, handoff != null);
_logStateChange(value);
return Stack(
fit: StackFit.expand,
@@ -60,7 +63,7 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface> {
AnimatedOpacity(
key: const Key('player-video-opacity'),
opacity: reveal ? 1 : 0,
duration: _videoFadeDuration,
duration: handoff != null ? Duration.zero : _videoFadeDuration,
curve: Curves.easeOut,
child: reveal && controller != null
? _VideoLayer(
@@ -70,6 +73,13 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface> {
)
: const SizedBox.expand(),
),
if (handoff != null)
_VideoLayer(
key: const Key('player-video-handoff'),
controller: handoff,
value: handoff.value,
theater: widget.theater,
),
if (kDebugMode && value?.hasError == true)
Center(
child: Padding(
@@ -88,12 +98,18 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface> {
);
}
bool _shouldReveal(VideoPlayerValue? value) {
return value != null &&
!value.hasError &&
value.isInitialized &&
value.size.width > 0 &&
(value.position > Duration.zero || value.isPlaying);
bool _shouldReveal(VideoPlayerValue? value, bool covered) {
if (value == null || value.hasError || !value.isInitialized) return false;
if (value.size.width <= 0) return false;
if (covered) return true;
return value.position > Duration.zero || value.isPlaying;
}
/// 只画会话仍持有的旧画面,控制器一旦被换掉就不再引用。
VideoPlayerController? _liveHandoff(VideoPlayerController? controller) {
final handoff = widget.handoff;
if (handoff == null || identical(handoff, controller)) return null;
return handoff;
}
void _logStateChange(VideoPlayerValue? value) {
@@ -135,6 +151,7 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface> {
class _VideoLayer extends StatelessWidget {
const _VideoLayer({
super.key,
required this.controller,
required this.value,
required this.theater,