feat(app): 完善播放协议搜索与空状态
This commit is contained in:
1 parent
671e0ce143
commit
ef29fc5f15
85 files changed
+2976
-768
No files matched your search
@@ -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 (_) {
|
||||
// 续看位置取不到不影响播放:从头播。
|
||||
// 续看或清晰度定位失败时仍用已初始化的播放器,不把切换判失败。
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user