fix(player): 解锁状态登录期内共享,重进播放页不再丢解锁

解锁记录原本是播放页实例的私有集合,退出再进入会先闪一次锁定态,还会
被 selectLocked 提前释放播放器。改为进程内共享的 UnlockedEpisodeRegistry,
由全局注入并在登录态变化时清空;播放器三处渲染分支统一走同一判定,
会话锁态只约束当前页,避免相邻预加载页被误判为锁定。
This commit is contained in:
Zhou Weidong authored and trae-solo-agent committed 2026-10-29 10:44:44 +08:00
1 parent a4d08e7c1e
commit 57f84c45bd
6 files changed
+103 -18

No files matched your search

+22 -6
View File
@@ -27,6 +27,7 @@ import '../../core/data/services/platform_share_service.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/unlocked_episode_registry.dart';
import '../../core/data/services/video_player_port.dart';
import '../../core/data/services/wallet_controller.dart';
import '../../core/formatting/localized_content.dart';
@@ -64,11 +65,13 @@ class PlayerLogic extends ChangeNotifier {
required this.pictureInPicture,
this.wallet,
this.downloads,
UnlockedEpisodeRegistry? unlockedEpisodes,
PlaybackPositionCache? positionCache,
Future<void> Function()? waitForFrame,
DateTime Function()? clock,
bool Function()? isSignedIn,
}) : positionCache = positionCache ?? PlaybackPositionCache(),
}) : unlockedEpisodes = unlockedEpisodes ?? UnlockedEpisodeRegistry(),
positionCache = positionCache ?? PlaybackPositionCache(),
_waitForFrame =
waitForFrame ?? (() => WidgetsBinding.instance.endOfFrame),
_clock = clock ?? DateTime.now,
@@ -79,6 +82,9 @@ class PlayerLogic extends ChangeNotifier {
final AndroidPictureInPicture pictureInPicture;
final WalletController? wallet;
final DownloadController? downloads;
/// 已确认可播的收费集(登录期内共享):退出播放页再进来不必重新挡一次。
final UnlockedEpisodeRegistry unlockedEpisodes;
final PlaybackPositionCache positionCache;
final Future<void> Function() _waitForFrame;
final DateTime Function() _clock;
@@ -169,14 +175,15 @@ class PlayerLogic extends ChangeNotifier {
/// 当前设备是否为 iOS 真机环境(面板据此显示自动小窗开关)。
bool get iosDevice => !kIsWeb && Platform.isIOS;
/// PiP 状态是全局单通道回调,须广播给所有存活的 PlayerLogic:
/// 推荐页每页一个 logic,若只保留最后一次注册的 handler,
/// 相邻预加载页会盖掉正在播放页的注册,导致小窗 UI 不隐藏且无声音。
static final Set<PlayerLogic> _pipSubscribers = <PlayerLogic>{};
/// 「记住上次选集」:dramaId → 集号。只在用户从选集面板主动挑集时写入
/// (见 [_recordPickedEpisode]),feed 里同一部剧据此显示上次选中的集。
static final Map<String, int> pickedEpisode = {};
final Set<String> _unlockedEpisodeIds = {};
final Map<String, EpisodeStats> _episodeStats = {};
final Map<String, DateTime> _episodeStatsFetchedAt = {};
final Map<String, Future<void>> _episodeStatsInFlight = {};
@@ -562,7 +569,7 @@ class PlayerLogic extends ChangeNotifier {
final localPath = await downloads?.findLocalPath(episode.id);
if (request != pageRequest) return false;
if (localPath != null) {
_unlockedEpisodeIds.add(episode.id);
unlockedEpisodes.markUnlocked(episode.id);
blockedTicket = null;
blockedEpisodeId = null;
qualities = const [];
@@ -587,7 +594,7 @@ class PlayerLogic extends ChangeNotifier {
final playUrl = preferred ?? ticket.playUrl;
if (preferred == null) selectedQuality = 'AUTO';
if (ticket.accessible && playUrl.isNotEmpty) {
_unlockedEpisodeIds.add(episode.id);
unlockedEpisodes.markUnlocked(episode.id);
// 签名地址原样写回,不做任何拼接/改写(AppConfig.mediaUrl 只补 host)
sessionEpisodes[index] = episode.withVideoUrl(
playUrl,
@@ -860,7 +867,7 @@ class PlayerLogic extends ChangeNotifier {
unlockError = service.unlockError;
return false;
}
_unlockedEpisodeIds.add(episode.id);
unlockedEpisodes.markUnlocked(episode.id);
final ready = await ensureTicket(index, forceRefresh: true);
if (ready) {
await _selectVisible(index);
@@ -1688,7 +1695,16 @@ class PlayerLogic extends ChangeNotifier {
/// 收费集只有在本会话确认可播或解锁成功后才解除门禁。
bool isEpisodeLocked(Episode episode) {
return (episode.locked || !episode.isFree) &&
!_unlockedEpisodeIds.contains(episode.id);
!unlockedEpisodes.contains(episode.id);
}
/// 单页锁定判定:剧集自身锁定 + 会话锁态(只约束当前页)+ 后端拒绝。
///
/// 播放页的多个渲染分支都必须走这里,否则相邻预加载页会被会话锁态误判成锁定。
bool isEpisodeBlockedFor(Episode episode, int index) {
return isEpisodeLocked(episode) ||
(index == session.currentIndex && session.isLocked) ||
blockedEpisodeId == episode.id;
}
/// 按可见队列选择一集,并预热上一集与下一集。