fix(home): 修复频道快速切换卡 loading
This commit is contained in:
1 parent
c8392b2f2a
commit
3c4d65a9b2
5 files changed
+408
-110
No files matched your search
@@ -102,6 +102,13 @@ class PlayerLogic extends ChangeNotifier {
|
||||
bool _disposed = false;
|
||||
PlayerFactory? _sessionFactory;
|
||||
PlaybackCoordinator? _sessionCoordinator;
|
||||
int feedRevision = 0;
|
||||
List<Drama> _latestFeed = const [];
|
||||
String? _latestKeepDramaId;
|
||||
bool _latestResetToFirst = false;
|
||||
bool _feedUpdateRunning = false;
|
||||
bool _hasPendingFeed = false;
|
||||
Future<void> _feedUpdateFuture = Future<void>.value();
|
||||
|
||||
/// [start] 是否已经跑过:没跑过时 session/pages 还是 late 未初始化,
|
||||
/// dispose 不能直接碰它们(例如页面构建失败或纯逻辑测试)。
|
||||
@@ -111,6 +118,9 @@ class PlayerLogic extends ChangeNotifier {
|
||||
|
||||
bool get chromeHidden => immersive;
|
||||
|
||||
/// 最近一次 feed 更新流程完成;测试和页面退出可等待它结束。
|
||||
Future<void> get feedUpdateSettled => _feedUpdateFuture;
|
||||
|
||||
/// 当前 PageView 对应用户正在观看的短剧 id,尚未起播时为 null。
|
||||
String? get currentDramaId {
|
||||
if (!_started || visibleIndices.isEmpty) return null;
|
||||
@@ -173,38 +183,26 @@ class PlayerLogic extends ChangeNotifier {
|
||||
unawaited(selectEpisode(initialIndex));
|
||||
}
|
||||
|
||||
/// 原地替换首页 feed 队列;频道变化时重置到第 1 个,刷新时尽量保留当前剧。
|
||||
void updateFeed(List<Drama> dramas, {String? keepDramaId}) {
|
||||
final currentId = currentDramaId;
|
||||
final nextQueue = PlaybackQueue.home(List<Drama>.unmodifiable(dramas));
|
||||
final nextVisible = List<int>.generate(nextQueue.length, (index) => index);
|
||||
final nextEpisodes = [for (final entry in nextQueue.entries) entry.episode];
|
||||
feedDramas = List<Drama>.unmodifiable(dramas);
|
||||
queue = nextQueue;
|
||||
visibleIndices = nextVisible;
|
||||
if (dramas.isNotEmpty) pageDrama = dramas.first;
|
||||
if (!_started) return;
|
||||
if (nextQueue.length == 0) {
|
||||
session.setRouteVisible(false);
|
||||
notify();
|
||||
/// 串行替换首页 feed 队列;频道变化时重置到第 1 个,刷新时尽量保留当前剧。
|
||||
void updateFeed(
|
||||
List<Drama> dramas, {
|
||||
String? keepDramaId,
|
||||
bool resetToFirst = false,
|
||||
}) {
|
||||
feedRevision++;
|
||||
pageRequest++;
|
||||
_latestFeed = List<Drama>.unmodifiable(dramas);
|
||||
_latestKeepDramaId = keepDramaId;
|
||||
_latestResetToFirst = resetToFirst;
|
||||
_hasPendingFeed = true;
|
||||
if (!_started) {
|
||||
feedDramas = _latestFeed;
|
||||
return;
|
||||
}
|
||||
final keepIndex = keepDramaId != null && keepDramaId == currentId
|
||||
? nextQueue.entries.indexWhere((entry) => entry.drama.id == keepDramaId)
|
||||
: -1;
|
||||
if (keepIndex >= 0 && session.active != null) {
|
||||
sessionEpisodes
|
||||
..clear()
|
||||
..addAll(nextEpisodes);
|
||||
session.remapCurrentIndex(
|
||||
keepIndex,
|
||||
preloadIndex: nextVisibleIndex(keepIndex),
|
||||
);
|
||||
_jumpToFeedPage(keepIndex);
|
||||
notify();
|
||||
return;
|
||||
if (!_feedUpdateRunning) {
|
||||
_feedUpdateFuture = _drainFeedUpdates();
|
||||
unawaited(_feedUpdateFuture);
|
||||
}
|
||||
_replaceSession(nextEpisodes, keepIndex >= 0 ? keepIndex : 0);
|
||||
}
|
||||
|
||||
PlaybackSession _createSession(
|
||||
@@ -222,7 +220,77 @@ class PlayerLogic extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
void _replaceSession(List<Episode> nextEpisodes, int targetIndex) {
|
||||
Future<void> _drainFeedUpdates() async {
|
||||
_feedUpdateRunning = true;
|
||||
while (true) {
|
||||
if (!_hasPendingFeed) {
|
||||
_feedUpdateRunning = false;
|
||||
return;
|
||||
}
|
||||
_hasPendingFeed = false;
|
||||
final revision = feedRevision;
|
||||
final dramas = _latestFeed;
|
||||
final keepDramaId = _latestKeepDramaId;
|
||||
final resetToFirst = _latestResetToFirst;
|
||||
if (revision != feedRevision) continue;
|
||||
await _applyFeedUpdate(
|
||||
dramas,
|
||||
keepDramaId: keepDramaId,
|
||||
resetToFirst: resetToFirst,
|
||||
revision: revision,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _applyFeedUpdate(
|
||||
List<Drama> dramas, {
|
||||
required String? keepDramaId,
|
||||
required bool resetToFirst,
|
||||
required int revision,
|
||||
}) async {
|
||||
if (!_started || revision != feedRevision) return;
|
||||
final currentId = currentDramaId;
|
||||
final nextQueue = PlaybackQueue.home(dramas);
|
||||
final nextVisible = List<int>.generate(nextQueue.length, (index) => index);
|
||||
final nextEpisodes = [for (final entry in nextQueue.entries) entry.episode];
|
||||
feedDramas = dramas;
|
||||
queue = nextQueue;
|
||||
visibleIndices = nextVisible;
|
||||
if (dramas.isNotEmpty) pageDrama = dramas.first;
|
||||
if (nextQueue.length == 0) {
|
||||
session.setRouteVisible(false);
|
||||
notify();
|
||||
return;
|
||||
}
|
||||
final keepIndex =
|
||||
!resetToFirst && keepDramaId != null && keepDramaId == currentId
|
||||
? nextQueue.entries.indexWhere((entry) => entry.drama.id == keepDramaId)
|
||||
: -1;
|
||||
if (keepIndex >= 0 && session.active != null) {
|
||||
sessionEpisodes
|
||||
..clear()
|
||||
..addAll(nextEpisodes);
|
||||
session.remapCurrentIndex(
|
||||
keepIndex,
|
||||
preloadIndex: nextVisibleIndex(keepIndex),
|
||||
);
|
||||
_jumpToFeedPage(keepIndex);
|
||||
notify();
|
||||
return;
|
||||
}
|
||||
await _replaceSession(
|
||||
nextEpisodes,
|
||||
keepIndex >= 0 ? keepIndex : 0,
|
||||
revision: revision,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _replaceSession(
|
||||
List<Episode> nextEpisodes,
|
||||
int targetIndex, {
|
||||
required int revision,
|
||||
}) async {
|
||||
if (revision != feedRevision) return;
|
||||
final factory = _sessionFactory;
|
||||
final coordinator = _sessionCoordinator;
|
||||
if (factory == null || coordinator == null) return;
|
||||
@@ -232,12 +300,14 @@ class PlayerLogic extends ChangeNotifier {
|
||||
trackedPlayer = null;
|
||||
trackedEpisode = null;
|
||||
trackedDrama = null;
|
||||
session = _createSession(factory, coordinator);
|
||||
session.setRouteVisible(active && visibleIndices.isNotEmpty);
|
||||
unawaited(previous.close());
|
||||
_jumpToFeedPage(targetIndex);
|
||||
unawaited(selectEpisode(targetIndex));
|
||||
final nextSession = _createSession(factory, coordinator);
|
||||
session = nextSession;
|
||||
nextSession.setRouteVisible(active && visibleIndices.isNotEmpty);
|
||||
notify();
|
||||
await previous.close();
|
||||
if (revision != feedRevision || !identical(session, nextSession)) return;
|
||||
_jumpToFeedPage(targetIndex);
|
||||
await selectEpisode(targetIndex, revision: revision);
|
||||
}
|
||||
|
||||
void _jumpToFeedPage(int index) {
|
||||
@@ -625,15 +695,24 @@ class PlayerLogic extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> selectEpisode(int index) async {
|
||||
Future<void> selectEpisode(int index, {int? revision}) async {
|
||||
final feedRevisionAtStart = revision ?? feedRevision;
|
||||
final request = ++pageRequest;
|
||||
cancelScrub();
|
||||
// Capture the old position synchronously, persist without blocking a swipe.
|
||||
unawaited(saveCurrentProgress());
|
||||
if (request != pageRequest || !isMounted()) return;
|
||||
if (request != pageRequest ||
|
||||
feedRevisionAtStart != feedRevision ||
|
||||
!isMounted()) {
|
||||
return;
|
||||
}
|
||||
// 起播前先要票据:拿不到就不起播,交给三态 UI 提示(失败可重试)
|
||||
final ready = await ensureTicket(index);
|
||||
if (request != pageRequest || !isMounted()) return;
|
||||
if (request != pageRequest ||
|
||||
feedRevisionAtStart != feedRevision ||
|
||||
!isMounted()) {
|
||||
return;
|
||||
}
|
||||
if (!ready && ticketError != null) return;
|
||||
await session.select(index, preloadIndex: nextVisibleIndex(index));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -131,20 +130,25 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
void didUpdateWidget(covariant PlayerPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
logic.attach(widget, isMounted: () => mounted);
|
||||
final channelChanged = oldWidget.feedChannel != widget.feedChannel;
|
||||
final feedChanged =
|
||||
_feedSignature(oldWidget.feedDramas) !=
|
||||
_feedSignature(widget.feedDramas);
|
||||
if (widget.homeMode &&
|
||||
widget.feedDramas != null &&
|
||||
(!listEquals(oldWidget.feedDramas, widget.feedDramas) ||
|
||||
oldWidget.feedChannel != widget.feedChannel)) {
|
||||
(feedChanged || channelChanged)) {
|
||||
logic.updateFeed(
|
||||
widget.feedDramas!,
|
||||
keepDramaId: oldWidget.feedChannel == widget.feedChannel
|
||||
? logic.currentDramaId
|
||||
: null,
|
||||
keepDramaId: channelChanged ? null : logic.currentDramaId,
|
||||
resetToFirst: channelChanged,
|
||||
);
|
||||
}
|
||||
logic.onPageUpdated(oldWidget);
|
||||
}
|
||||
|
||||
String _feedSignature(List<Drama>? dramas) =>
|
||||
dramas == null ? '' : dramas.map((drama) => drama.id).join('|');
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
Reference in new issue
Block a user