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

+83 -3
View File
@@ -3,6 +3,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
@@ -88,6 +89,9 @@ class PlayerLogic extends ChangeNotifier {
String? blockedEpisodeId;
List<Episode> sessionEpisodes = const [];
List<String> qualities = const [];
List<PlayQualityTrack> qualityTracks = const [];
String selectedQuality = 'AUTO';
Duration? _qualitySeek;
int pageRequest = 0;
bool muted = false;
double playbackSpeed = 1.0;
@@ -426,6 +430,7 @@ class PlayerLogic extends ChangeNotifier {
blockedTicket = null;
blockedEpisodeId = null;
qualities = const [];
qualityTracks = const [];
sessionEpisodes[index] = episode.withVideoUrl(_fileUrl(localPath));
return true;
}
@@ -437,10 +442,18 @@ class PlayerLogic extends ChangeNotifier {
blockedTicket = null;
blockedEpisodeId = null;
qualities = ticket.qualities;
if (ticket.accessible && ticket.playUrl.isNotEmpty) {
qualityTracks = ticket.tracks;
final preferred = qualityPlayUrl(
quality: selectedQuality,
tracks: qualityTracks,
currentUrl: ticket.playUrl,
);
final playUrl = preferred ?? ticket.playUrl;
if (preferred == null) selectedQuality = 'AUTO';
if (ticket.accessible && playUrl.isNotEmpty) {
// 签名地址原样写回,不做任何拼接/改写(AppConfig.mediaUrl 只补 host)
sessionEpisodes[index] = episode.withVideoUrl(
ticket.playUrl,
playUrl,
likeCount: ticket.likeCount,
commentCount: ticket.commentCount,
thumbnailsUrl: ticket.thumbnailsUrl,
@@ -470,6 +483,64 @@ class PlayerLogic extends ChangeNotifier {
String _fileUrl(String path) =>
path.startsWith('file:') ? path : Uri.file(path).toString();
/// 切到指定清晰度。新片源出画后再替换,失败则继续播原来的。
Future<bool> selectQuality(String quality) async {
final index = session.currentIndex;
if (index < 0 || index >= sessionEpisodes.length) return false;
final current = sessionEpisodes[index];
final url = qualityPlayUrl(
quality: quality,
tracks: qualityTracks,
currentUrl: current.videoUrl,
);
if (url == null || url.isEmpty) return false;
if (selectedQuality.toUpperCase() == quality.toUpperCase() &&
current.videoUrl == url) {
return true;
}
final previousQuality = selectedQuality;
final active = session.active;
if (active is VideoPlayerPort && active.controller.value.isInitialized) {
_qualitySeek = active.controller.value.position;
}
selectedQuality = quality.toUpperCase();
sessionEpisodes[index] = current.withVideoUrl(url);
final switched = await session.replaceCurrent();
if (switched) {
if (isMounted()) notify();
return true;
}
_qualitySeek = null;
selectedQuality = previousQuality;
if (index < sessionEpisodes.length) {
sessionEpisodes[index] = current;
}
if (isMounted()) notify();
return false;
}
Future<void> _waitUntilFrameReady(VideoPlayerController controller) async {
if (controller.value.hasError) {
throw StateError('quality source failed');
}
if (!controller.value.isBuffering) return;
final done = Completer<void>();
void onChange() {
final value = controller.value;
if (done.isCompleted) return;
if (value.hasError || !value.isBuffering) done.complete();
}
controller.addListener(onChange);
try {
await done.future.timeout(const Duration(seconds: 4));
} finally {
controller.removeListener(onChange);
}
if (controller.value.hasError) {
throw StateError('quality source failed');
}
}
/// 重试当前集的票据(失败态的"重试"按钮)。
Future<void> retryTicket() async {
ticketError = null;
@@ -667,6 +738,15 @@ class PlayerLogic extends ChangeNotifier {
if (player is! VideoPlayerPort) return;
try {
await player.controller.setPlaybackSpeed(playbackSpeed);
final override = _qualitySeek;
_qualitySeek = null;
if (override != null && override > Duration.zero) {
await player.seekTo(
clampResumePosition(override, player.controller.value.duration),
);
await _waitUntilFrameReady(player.controller);
return;
}
await pendingWrite;
// 服务端每剧只记一条:只有"记录里的那一集"才定位,其它集从头播
if (resumePoint == null) await loadResumePoint();
@@ -689,7 +769,7 @@ class PlayerLogic extends ChangeNotifier {
);
}
} catch (_) {
// 续看位置取不到不影响播放:从头播。
// 续看或清晰度定位失败时仍用已初始化的播放器,不把切换判失败。
}
}