fix(player): 重构单层过渡并阻止锁定集串播

This commit is contained in:
Zhou Weidong committed 2026-10-26 14:41:18 +08:00
1 parent 70d8e0f982
commit 0cb0ab0613
8 files changed
+657 -335

No files matched your search

+36 -31
View File
@@ -146,9 +146,8 @@ class _PlayerPageState extends State<PlayerPage>
_thumbnailService = PlaybackThumbnailService(
sourceResolver: _resolveThumbnailSource,
);
_spriteAxisService = SpriteThumbnailAxisService(
spriteCacheCapacity: 8,
)..addListener(_handleSpriteFrameChanged);
_spriteAxisService = SpriteThumbnailAxisService(spriteCacheCapacity: 8)
..addListener(_handleSpriteFrameChanged);
WidgetsBinding.instance.addObserver(this);
logic.start(
factory: context.read<PlayerFactory>(),
@@ -389,10 +388,9 @@ class _PlayerPageState extends State<PlayerPage>
continue;
}
final thumbnailsUrl = logic.sessionEpisodes[index].thumbnailsUrl;
if (thumbnailsUrl != null) {
unawaited(
_spriteAxisService.prepareAt(thumbnailsUrl, Duration.zero),
);
if (thumbnailsUrl != null &&
!logic.isEpisodeLocked(logic.sessionEpisodes[index])) {
unawaited(_spriteAxisService.prepareAt(thumbnailsUrl, Duration.zero));
}
}
}
@@ -603,33 +601,36 @@ class _PlayerPageState extends State<PlayerPage>
)
: displayed;
final selected = index == logic.session.currentIndex;
final locked =
logic.isEpisodeLocked(episode) ||
logic.session.isLocked ||
logic.blockedEpisodeId == episode.id;
// Selection changes before the prepared slot is promoted. Use its controller
// throughout the handoff instead of briefly falling back to a placeholder.
final port =
(selected ? logic.session.active : null) ??
logic.session.preparedPlayerAt(index);
final port = locked
? null
: (selected ? logic.session.active : null) ??
logic.session.preparedPlayerAt(index);
final videoPort = port is VideoPlayerPort ? port : null;
final handoff = selected ? logic.session.handoffAt(index) : null;
final handoff = selected && !locked ? logic.session.handoffAt(index) : null;
final handoffPort = handoff is VideoPlayerPort ? handoff : null;
// 测试播放器没有纹理。有播放器但不是视频控制器时视为已就绪,避免加载圈一直转。
final videoReady = videoPort?.controller.value.isInitialized ?? false;
if (selected && videoReady) _logSwipeReveal();
final ready = videoReady || port != null;
final ready = !locked && (videoReady || port != null);
final theater = logic.pip || logic.pipPreparing;
final videoFit = theater ? BoxFit.contain : BoxFit.cover;
final preparedFrameAvailable =
selected && logic.session.reusedWarmAt(index);
selected && !locked && logic.session.reusedWarmAt(index);
final thumbnailsUrl = episode.thumbnailsUrl;
final openingFrameReady =
thumbnailsUrl != null &&
_spriteAxisService.cachedFrameAt(thumbnailsUrl, Duration.zero) != null;
if (thumbnailsUrl != null) {
unawaited(
_spriteAxisService.prepareAt(thumbnailsUrl, Duration.zero),
);
unawaited(_spriteAxisService.prepareAt(thumbnailsUrl, Duration.zero));
}
final locked = logic.session.isLocked || episode.locked;
if (selected &&
!locked &&
videoPort != null &&
videoPort.controller.value.isInitialized) {
unawaited(
@@ -649,20 +650,27 @@ class _PlayerPageState extends State<PlayerPage>
color: AppColors.mediaBlack,
),
PlayerVideoSurface(
controller: videoPort?.controller,
handoff: handoffPort?.controller,
controller: locked ? null : videoPort?.controller,
videoUrl: videoPort?.controller.dataSource ?? episode.videoUrl,
theater: theater,
holdOpeningUntilFirstFrame: logic.session.userWantsPlay,
preparedFrameAvailable: preparedFrameAvailable,
openingFrameReady: openingFrameReady,
openingFrame: playerOpeningFrame(
thumbnailsUrl,
spriteSource: _spriteAxisService,
fit: videoFit,
),
warmFrameAvailable: preparedFrameAvailable,
thumbnailReady: openingFrameReady,
waitForPlaybackFrame: logic.session.userWantsPlay,
placeholder: locked
? null
: handoffPort != null
? ControllerFramePlaceholder(
controller: handoffPort.controller,
theater: theater,
)
: playerOpeningFrame(
thumbnailsUrl,
spriteSource: _spriteAxisService,
fit: videoFit,
),
),
if (selected &&
!locked &&
!theater &&
!ready &&
!logic.ticketLoading &&
@@ -884,10 +892,7 @@ class _PlayerPageState extends State<PlayerPage>
controlsVisible: _chromeVisible,
),
// 票据三态之三:后端判定不可播放 —— 用 accessType 驱动文案,不在前端猜
if (selected &&
!theater &&
!ready &&
logic.blockedEpisodeId == episode.id)
if (selected && !theater && locked)
PlayerBlockedNotice(
l10n: l10n,
episode: episode,