fix(player): 修复切集返回推荐页黑屏
This commit is contained in:
1 parent
9d2d3b97a1
commit
440188f261
5 files changed
+297
-37
No files matched your search
@@ -4,6 +4,9 @@ import '../models/drama.dart';
|
||||
|
||||
/// A player session has one navigation meaning: browse dramas or browse episodes.
|
||||
class PlaybackQueue {
|
||||
PlaybackQueue.fromEntries(List<PlaybackEntry> entries)
|
||||
: entries = List.unmodifiable(entries);
|
||||
|
||||
PlaybackQueue.home(List<Drama> dramas)
|
||||
: entries = List.unmodifiable(
|
||||
dramas
|
||||
|
||||
@@ -137,6 +137,47 @@ class PlaybackSession extends ChangeNotifier {
|
||||
if (_visible && !_overlayOpen) _warmNeighbors(this);
|
||||
}
|
||||
|
||||
/// 在剧维度和集维度之间切换队列,但保留当前 active controller 与播放状态。
|
||||
void remapQueuePreservingActive(
|
||||
List<Episode> nextEpisodes, {
|
||||
required int currentIndex,
|
||||
int? preloadIndex,
|
||||
int? previousIndex,
|
||||
bool notify = true,
|
||||
}) {
|
||||
if (_closed ||
|
||||
currentIndex < 0 ||
|
||||
currentIndex >= nextEpisodes.length ||
|
||||
_active == null) {
|
||||
return;
|
||||
}
|
||||
_generation++;
|
||||
episodes
|
||||
..clear()
|
||||
..addAll(nextEpisodes);
|
||||
_active!.index = currentIndex;
|
||||
_currentIndex = currentIndex;
|
||||
_selected = true;
|
||||
_loading = false;
|
||||
_error = null;
|
||||
_rememberNeighbors(preloadIndex, previousIndex);
|
||||
final staleNext = _next;
|
||||
final stalePrevious = _previous;
|
||||
final staleHandoff = _handoff;
|
||||
_next = null;
|
||||
_previous = null;
|
||||
_handoff = null;
|
||||
if (notify) _notify();
|
||||
unawaited(
|
||||
_enqueue(() async {
|
||||
await _retire(staleNext);
|
||||
await _retire(stalePrevious);
|
||||
await _retire(staleHandoff);
|
||||
}),
|
||||
);
|
||||
if (_visible && !_overlayOpen) _warmNeighbors(this);
|
||||
}
|
||||
|
||||
void _rememberNeighbors(int? preloadIndex, int? previousIndex) {
|
||||
_nextIndex = preloadIndex;
|
||||
_previousIndex = previousIndex;
|
||||
|
||||
@@ -212,6 +212,68 @@ class PlayerLogic extends ChangeNotifier {
|
||||
unawaited(selectEpisode(initialIndex));
|
||||
}
|
||||
|
||||
/// 独立播放页把 feed 的“剧索引”切换成 series 的“集索引”,保留 active player。
|
||||
void adoptSeriesHandoff({
|
||||
required Drama drama,
|
||||
required List<Episode> seriesEpisodes,
|
||||
required int episodeIndex,
|
||||
}) {
|
||||
if (episodeIndex < 0 || episodeIndex >= seriesEpisodes.length) return;
|
||||
homeMode = false;
|
||||
embedded = false;
|
||||
feedDramas = null;
|
||||
pageDrama = drama;
|
||||
initialIndex = episodeIndex;
|
||||
queue = PlaybackQueue.series(drama);
|
||||
visibleIndices = List<int>.generate(queue.length, (i) => i);
|
||||
sessionEpisodes
|
||||
..clear()
|
||||
..addAll(seriesEpisodes);
|
||||
session.remapQueuePreservingActive(
|
||||
List<Episode>.of(seriesEpisodes),
|
||||
currentIndex: episodeIndex,
|
||||
notify: false,
|
||||
preloadIndex: episodeIndex + 1 < seriesEpisodes.length
|
||||
? episodeIndex + 1
|
||||
: null,
|
||||
previousIndex: episodeIndex > 0 ? episodeIndex - 1 : null,
|
||||
);
|
||||
}
|
||||
|
||||
/// 返回推荐页时恢复剧维度 queue,并把当前集写回该剧的 feed 条目。
|
||||
void restoreFeedHandoff({
|
||||
required PlayerPage page,
|
||||
required PlaybackQueue sourceQueue,
|
||||
required List<Episode> sourceEpisodes,
|
||||
required int sourceIndex,
|
||||
}) {
|
||||
if (sourceIndex < 0 || sourceIndex >= sourceQueue.length) return;
|
||||
final currentEpisode = session.currentEpisode;
|
||||
final feedEpisodes = List<Episode>.of(sourceEpisodes);
|
||||
feedEpisodes[sourceIndex] = currentEpisode;
|
||||
final entries = List<PlaybackEntry>.of(sourceQueue.entries);
|
||||
entries[sourceIndex] = PlaybackEntry(
|
||||
entries[sourceIndex].drama,
|
||||
currentEpisode,
|
||||
);
|
||||
feedDramas = page.feedDramas;
|
||||
pageDrama = page.drama;
|
||||
homeMode = page.homeMode;
|
||||
embedded = page.embedded;
|
||||
initialIndex = sourceIndex;
|
||||
queue = PlaybackQueue.fromEntries(entries);
|
||||
visibleIndices = List<int>.generate(queue.length, (i) => i);
|
||||
sessionEpisodes
|
||||
..clear()
|
||||
..addAll(feedEpisodes);
|
||||
session.remapQueuePreservingActive(
|
||||
List<Episode>.of(feedEpisodes),
|
||||
currentIndex: sourceIndex,
|
||||
preloadIndex: nextVisibleIndex(sourceIndex),
|
||||
previousIndex: previousVisibleIndex(sourceIndex),
|
||||
);
|
||||
}
|
||||
|
||||
/// 串行替换首页 feed 队列;频道变化时重置到第 1 个,刷新时尽量保留当前剧。
|
||||
void updateFeed(
|
||||
List<Drama> dramas, {
|
||||
|
||||
@@ -10,10 +10,12 @@ import 'package:provider/provider.dart';
|
||||
import '../../core/config/app_config.dart';
|
||||
import '../../core/data/models/drama.dart';
|
||||
import '../../core/data/repositories/comment_repository.dart';
|
||||
import '../../core/data/services/catalog_controller.dart';
|
||||
import '../../core/data/services/favorite_controller.dart';
|
||||
import '../../core/data/services/download_controller.dart';
|
||||
import '../../core/data/services/interaction_controller.dart';
|
||||
import '../../core/data/services/playback_position_cache.dart';
|
||||
import '../../core/data/services/playback_queue.dart';
|
||||
import '../../core/data/services/playback_session.dart';
|
||||
import '../../core/data/services/sprite_thumbnail_axis.dart';
|
||||
import '../../core/data/services/playback_thumbnail_service.dart';
|
||||
@@ -58,12 +60,22 @@ const double _feedRefreshTopGap = 56;
|
||||
class PlayerPageHandoff {
|
||||
PlayerPageHandoff({
|
||||
required this.logic,
|
||||
required this.initialIndex,
|
||||
required this.sourceQueue,
|
||||
required this.sourceEpisodes,
|
||||
required this.sourceIndex,
|
||||
required this.seriesDrama,
|
||||
required this.seriesEpisodes,
|
||||
required this.episodeIndex,
|
||||
required this.prepareSource,
|
||||
});
|
||||
|
||||
final PlayerLogic logic;
|
||||
final int initialIndex;
|
||||
final PlaybackQueue sourceQueue;
|
||||
final List<Episode> sourceEpisodes;
|
||||
final int sourceIndex;
|
||||
final Drama seriesDrama;
|
||||
final List<Episode> seriesEpisodes;
|
||||
final int episodeIndex;
|
||||
final Future<void> Function() prepareSource;
|
||||
PageController? destinationController;
|
||||
VoidCallback? activateDestination;
|
||||
@@ -150,6 +162,8 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
bool _presentationSuspended = false;
|
||||
bool _presentationReturning = false;
|
||||
bool _leavingHandoff = false;
|
||||
bool _preparingFullPlayer = false;
|
||||
PlayerPageHandoff? _activeHandoff;
|
||||
PageController? _returnPageController;
|
||||
final Set<String> _precachedPosterUrls = {};
|
||||
|
||||
@@ -185,10 +199,12 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
);
|
||||
_pageController = logic.pages;
|
||||
} else {
|
||||
final position = logic.visibleIndices.indexOf(handoff.initialIndex);
|
||||
_pageController = PageController(
|
||||
initialPage: position < 0 ? 0 : position,
|
||||
logic.adoptSeriesHandoff(
|
||||
drama: handoff.seriesDrama,
|
||||
seriesEpisodes: handoff.seriesEpisodes,
|
||||
episodeIndex: handoff.episodeIndex,
|
||||
);
|
||||
_pageController = PageController(initialPage: handoff.episodeIndex);
|
||||
handoff.destinationController = _pageController;
|
||||
logic.session.setRouteVisible(widget.active);
|
||||
}
|
||||
@@ -1163,13 +1179,56 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
}
|
||||
|
||||
Future<void> _presentFullPlayer(BuildContext context, int index) async {
|
||||
if (_presentationActive || !_pageController.hasClients) return;
|
||||
if (_presentationActive ||
|
||||
_preparingFullPlayer ||
|
||||
!_pageController.hasClients) {
|
||||
return;
|
||||
}
|
||||
_preparingFullPlayer = true;
|
||||
final sourceIndex = logic.session.currentIndex;
|
||||
final sourceEpisode = logic.session.currentEpisode;
|
||||
final sourceQueue = logic.queue;
|
||||
final sourceEpisodes = List<Episode>.of(logic.sessionEpisodes);
|
||||
var seriesDrama = sourceQueue.entryAt(sourceIndex).drama;
|
||||
try {
|
||||
seriesDrama = await context.read<CatalogController>().requireDetail(
|
||||
seriesDrama,
|
||||
);
|
||||
} catch (_) {
|
||||
// 详情不可用时仍使用当前剧已知的集列表。
|
||||
}
|
||||
_preparingFullPlayer = false;
|
||||
if (!mounted ||
|
||||
!context.mounted ||
|
||||
logic.session.currentIndex != sourceIndex ||
|
||||
logic.session.currentEpisode.id != sourceEpisode.id) {
|
||||
return;
|
||||
}
|
||||
final seriesEpisodes = List<Episode>.of(seriesDrama.episodes);
|
||||
var episodeIndex = seriesEpisodes.indexWhere(
|
||||
(episode) => episode.id == sourceEpisode.id,
|
||||
);
|
||||
if (episodeIndex < 0) {
|
||||
episodeIndex = seriesEpisodes.indexWhere(
|
||||
(episode) => episode.number == sourceEpisode.number,
|
||||
);
|
||||
}
|
||||
if (episodeIndex < 0) {
|
||||
seriesEpisodes.insert(0, sourceEpisode);
|
||||
episodeIndex = 0;
|
||||
}
|
||||
final sourcePageController = _pageController;
|
||||
final handoff = PlayerPageHandoff(
|
||||
logic: logic,
|
||||
initialIndex: index,
|
||||
sourceQueue: sourceQueue,
|
||||
sourceEpisodes: sourceEpisodes,
|
||||
sourceIndex: sourceIndex,
|
||||
seriesDrama: seriesDrama,
|
||||
seriesEpisodes: seriesEpisodes,
|
||||
episodeIndex: episodeIndex,
|
||||
prepareSource: _prepareSourceAfterFullPlayer,
|
||||
);
|
||||
_activeHandoff = handoff;
|
||||
handoff.activateDestination = () {
|
||||
final destination = handoff.destinationController;
|
||||
if (destination != null) logic.pages = destination;
|
||||
@@ -1179,8 +1238,8 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
final route = Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => PlayerPage(
|
||||
drama: logic.queue.entryAt(index).drama,
|
||||
initialIndex: index,
|
||||
drama: seriesDrama,
|
||||
initialIndex: episodeIndex,
|
||||
handoff: handoff,
|
||||
wallet: logic.wallet,
|
||||
downloads: logic.downloads,
|
||||
@@ -1210,17 +1269,23 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
_presentationSuspended = false;
|
||||
_presentationReturning = false;
|
||||
});
|
||||
_activeHandoff = null;
|
||||
logic.session.setRouteVisible(widget.active);
|
||||
_logFullPlayerRoute('restored');
|
||||
}
|
||||
|
||||
Future<void> _prepareSourceAfterFullPlayer() async {
|
||||
if (!mounted) return;
|
||||
final page = logic.visibleIndices.indexOf(logic.session.currentIndex);
|
||||
_returnPageController = PageController(
|
||||
initialPage: page < 0 ? 0 : page,
|
||||
);
|
||||
final handoff = _activeHandoff;
|
||||
if (handoff == null) return;
|
||||
logic.attach(widget, isMounted: () => mounted);
|
||||
logic.restoreFeedHandoff(
|
||||
page: widget,
|
||||
sourceQueue: handoff.sourceQueue,
|
||||
sourceEpisodes: handoff.sourceEpisodes,
|
||||
sourceIndex: handoff.sourceIndex,
|
||||
);
|
||||
_returnPageController = PageController(initialPage: handoff.sourceIndex);
|
||||
setState(() {
|
||||
_presentationSuspended = false;
|
||||
_presentationReturning = true;
|
||||
@@ -1247,5 +1312,4 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
'position=${video?.controller.value.position.inMilliseconds ?? '-'}',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user