fix(player): 持久化竖滑播放画面层
This commit is contained in:
1 parent
5dafc68775
commit
2a32dec183
5 files changed
+593
-101
No files matched your search
@@ -40,6 +40,7 @@ import 'widgets/player_center_toggle.dart';
|
||||
import 'widgets/player_more_sheet.dart';
|
||||
import 'widgets/player_overlay.dart';
|
||||
import 'widgets/player_overlay_models.dart';
|
||||
import 'widgets/player_render_stack.dart';
|
||||
import 'widgets/player_speed_sheet.dart';
|
||||
import 'widgets/player_unlock_sheet.dart';
|
||||
import 'widgets/player_opening_frame.dart';
|
||||
@@ -528,6 +529,14 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
itemCount: indices.length,
|
||||
onPageChanged: (position) {
|
||||
final index = indices[position];
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-page] '
|
||||
'ts=${DateTime.now().millisecondsSinceEpoch} '
|
||||
'phase=session-index '
|
||||
'previous=${logic.session.currentIndex} next=$index',
|
||||
);
|
||||
}
|
||||
widget.onDramaChanged?.call(logic.queue.entryAt(index).drama);
|
||||
unawaited(_selectPage(index));
|
||||
if (position >= indices.length - 3) {
|
||||
@@ -535,16 +544,37 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
if (loadMore != null) unawaited(loadMore());
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, position) =>
|
||||
_buildEpisode(context, indices[position], interactions),
|
||||
itemBuilder: (context, position) {
|
||||
final index = indices[position];
|
||||
if (kDebugMode) {
|
||||
debugPrint(
|
||||
'[player-page] '
|
||||
'ts=${DateTime.now().millisecondsSinceEpoch} '
|
||||
'phase=item-build page=$position index=$index',
|
||||
);
|
||||
}
|
||||
return _buildEpisode(context, index, interactions);
|
||||
},
|
||||
),
|
||||
);
|
||||
if (widget.onRefreshFeed == null) return pages;
|
||||
return FeedPullRefresh(
|
||||
pageController: logic.pages,
|
||||
topInset: MediaQuery.paddingOf(context).top + _feedRefreshTopGap,
|
||||
onRefresh: _refreshHomeFeed,
|
||||
child: pages,
|
||||
final pageLayer = widget.onRefreshFeed == null
|
||||
? pages
|
||||
: FeedPullRefresh(
|
||||
pageController: logic.pages,
|
||||
topInset:
|
||||
MediaQuery.paddingOf(context).top + _feedRefreshTopGap,
|
||||
onRefresh: _refreshHomeFeed,
|
||||
child: pages,
|
||||
);
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
PlayerRenderStack(
|
||||
pageController: logic.pages,
|
||||
slots: _renderSlots(indices),
|
||||
),
|
||||
pageLayer,
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -555,7 +585,6 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
if (widget.homeMode &&
|
||||
widget.showHomeFeedHeader &&
|
||||
!_scrubbing &&
|
||||
_chromeVisible &&
|
||||
!_boosting &&
|
||||
!logic.pip &&
|
||||
!logic.pipPreparing)
|
||||
@@ -616,6 +645,145 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
_openMore(context, entry.episode);
|
||||
}
|
||||
|
||||
Episode _resolvedEpisode(int index) {
|
||||
final entry = logic.queue.entryAt(index);
|
||||
final sessionEpisode = index >= 0 && index < logic.sessionEpisodes.length
|
||||
? logic.sessionEpisodes[index]
|
||||
: entry.episode;
|
||||
final displayed = widget.homeMode
|
||||
? logic.displayEpisode(entry.drama, sessionEpisode)
|
||||
: sessionEpisode;
|
||||
final needsSessionThumbnails =
|
||||
(displayed.thumbnailsUrl == null || displayed.thumbnailsUrl!.isEmpty) &&
|
||||
sessionEpisode.thumbnailsUrl != null;
|
||||
final needsSessionPoster =
|
||||
(displayed.posterUrl == null || displayed.posterUrl!.isEmpty) &&
|
||||
sessionEpisode.posterUrl != null;
|
||||
if (!needsSessionThumbnails && !needsSessionPoster) return displayed;
|
||||
return displayed.withVideoUrl(
|
||||
displayed.videoUrl,
|
||||
thumbnailsUrl: sessionEpisode.thumbnailsUrl,
|
||||
posterUrl: sessionEpisode.posterUrl,
|
||||
);
|
||||
}
|
||||
|
||||
Map<PlayerRenderSlotRole, PlayerRenderSlotData> _renderSlots(
|
||||
List<int> indices,
|
||||
) {
|
||||
final current = logic.session.currentIndex;
|
||||
final previous =
|
||||
logic.previousVisibleIndex(current) ??
|
||||
logic.session.previousPreloadedIndex;
|
||||
final next =
|
||||
logic.nextVisibleIndex(current) ?? logic.session.preloadedIndex;
|
||||
return {
|
||||
PlayerRenderSlotRole.previous: _renderSlot(
|
||||
PlayerRenderSlotRole.previous,
|
||||
previous,
|
||||
indices,
|
||||
currentIndex: current,
|
||||
),
|
||||
PlayerRenderSlotRole.current: _renderSlot(
|
||||
PlayerRenderSlotRole.current,
|
||||
current,
|
||||
indices,
|
||||
currentIndex: current,
|
||||
),
|
||||
PlayerRenderSlotRole.next: _renderSlot(
|
||||
PlayerRenderSlotRole.next,
|
||||
next,
|
||||
indices,
|
||||
currentIndex: current,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
PlayerRenderSlotData _renderSlot(
|
||||
PlayerRenderSlotRole role,
|
||||
int? index,
|
||||
List<int> indices, {
|
||||
required int currentIndex,
|
||||
}) {
|
||||
if (index == null || index < 0 || index >= logic.sessionEpisodes.length) {
|
||||
return PlayerRenderSlotData.empty(role);
|
||||
}
|
||||
final episode = _resolvedEpisode(index);
|
||||
final selected = index == currentIndex;
|
||||
final locked =
|
||||
logic.isEpisodeLocked(episode) ||
|
||||
(selected && logic.session.isLocked) ||
|
||||
logic.blockedEpisodeId == episode.id;
|
||||
final port = locked
|
||||
? null
|
||||
: (selected ? logic.session.active : null) ??
|
||||
logic.session.preparedPlayerAt(index);
|
||||
final videoPort = port is VideoPlayerPort ? port : null;
|
||||
final handoff = selected && !locked ? logic.session.handoffAt(index) : null;
|
||||
final handoffPort = handoff is VideoPlayerPort ? handoff : null;
|
||||
final videoReady = videoPort?.controller.value.isInitialized ?? false;
|
||||
final theater = logic.pip || logic.pipPreparing;
|
||||
final videoFit = theater ? BoxFit.contain : BoxFit.cover;
|
||||
final preparedFrameAvailable =
|
||||
!locked &&
|
||||
videoReady &&
|
||||
(selected
|
||||
? logic.session.reusedWarmAt(index)
|
||||
: logic.session.preparedPlayerAt(index) != null);
|
||||
final thumbnailsUrl = episode.thumbnailsUrl;
|
||||
final posterUrl = episode.posterUrl;
|
||||
final posterReady = posterUrl?.trim().isNotEmpty ?? false;
|
||||
final thumbnailReady =
|
||||
thumbnailsUrl != null &&
|
||||
_spriteAxisService.cachedFrameAt(thumbnailsUrl, Duration.zero) != null;
|
||||
if (!locked && !posterReady && thumbnailsUrl != null) {
|
||||
unawaited(_spriteAxisService.prepareAt(thumbnailsUrl, Duration.zero));
|
||||
}
|
||||
if (selected &&
|
||||
!locked &&
|
||||
videoPort != null &&
|
||||
videoPort.controller.value.isInitialized) {
|
||||
unawaited(
|
||||
_spriteAxisService.prepareAt(
|
||||
thumbnailsUrl,
|
||||
videoPort.controller.value.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
final pagePosition = indices.indexOf(index);
|
||||
return PlayerRenderSlotData(
|
||||
role: role,
|
||||
index: index,
|
||||
pagePosition: pagePosition >= 0 ? pagePosition.toDouble() : null,
|
||||
controller: locked ? null : videoPort?.controller,
|
||||
videoUrl: videoPort?.controller.dataSource ?? episode.videoUrl,
|
||||
theater: theater,
|
||||
warmFrameAvailable: preparedFrameAvailable,
|
||||
thumbnailReady: thumbnailReady,
|
||||
posterReady: posterReady,
|
||||
fallback: handoffPort != null
|
||||
? PlayerOpeningFallback.handoff
|
||||
: playerOpeningFallback(
|
||||
posterUrl: posterUrl,
|
||||
thumbnailsUrl: thumbnailsUrl,
|
||||
),
|
||||
warmSlotReused: logic.session.reusedWarmAt(index),
|
||||
waitForPlaybackFrame: logic.session.userWantsPlay,
|
||||
placeholder: locked
|
||||
? null
|
||||
: handoffPort != null
|
||||
? ControllerFramePlaceholder(
|
||||
controller: handoffPort.controller,
|
||||
theater: theater,
|
||||
)
|
||||
: playerOpeningFrame(
|
||||
thumbnailsUrl,
|
||||
posterUrl: posterUrl,
|
||||
spriteSource: _spriteAxisService,
|
||||
fit: videoFit,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEpisode(
|
||||
BuildContext context,
|
||||
int index,
|
||||
@@ -624,25 +792,7 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final entry = logic.queue.entryAt(index);
|
||||
final drama = entry.drama;
|
||||
final sessionEpisode = index >= 0 && index < logic.sessionEpisodes.length
|
||||
? logic.sessionEpisodes[index]
|
||||
: entry.episode;
|
||||
final displayed = widget.homeMode
|
||||
? logic.displayEpisode(drama, sessionEpisode)
|
||||
: sessionEpisode;
|
||||
final needsSessionThumbnails =
|
||||
(displayed.thumbnailsUrl == null || displayed.thumbnailsUrl!.isEmpty) &&
|
||||
sessionEpisode.thumbnailsUrl != null;
|
||||
final needsSessionPoster =
|
||||
(displayed.posterUrl == null || displayed.posterUrl!.isEmpty) &&
|
||||
sessionEpisode.posterUrl != null;
|
||||
final episode = needsSessionThumbnails || needsSessionPoster
|
||||
? displayed.withVideoUrl(
|
||||
displayed.videoUrl,
|
||||
thumbnailsUrl: sessionEpisode.thumbnailsUrl,
|
||||
posterUrl: sessionEpisode.posterUrl,
|
||||
)
|
||||
: displayed;
|
||||
final episode = _resolvedEpisode(index);
|
||||
final selected = index == logic.session.currentIndex;
|
||||
final locked =
|
||||
logic.isEpisodeLocked(episode) ||
|
||||
@@ -655,79 +805,15 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
: (selected ? logic.session.active : null) ??
|
||||
logic.session.preparedPlayerAt(index);
|
||||
final videoPort = port is VideoPlayerPort ? port : 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 = !locked && (videoReady || port != null);
|
||||
final theater = logic.pip || logic.pipPreparing;
|
||||
final videoFit = theater ? BoxFit.contain : BoxFit.cover;
|
||||
final preparedFrameAvailable =
|
||||
!locked &&
|
||||
videoReady &&
|
||||
(selected
|
||||
? logic.session.reusedWarmAt(index)
|
||||
: logic.session.preparedPlayerAt(index) != null);
|
||||
final warmSlotReused = logic.session.reusedWarmAt(index);
|
||||
final thumbnailsUrl = episode.thumbnailsUrl;
|
||||
final posterUrl = episode.posterUrl;
|
||||
final posterReady = posterUrl?.trim().isNotEmpty ?? false;
|
||||
final openingFrameReady =
|
||||
thumbnailsUrl != null &&
|
||||
_spriteAxisService.cachedFrameAt(thumbnailsUrl, Duration.zero) != null;
|
||||
if (!posterReady && thumbnailsUrl != null) {
|
||||
unawaited(_spriteAxisService.prepareAt(thumbnailsUrl, Duration.zero));
|
||||
}
|
||||
if (selected &&
|
||||
!locked &&
|
||||
videoPort != null &&
|
||||
videoPort.controller.value.isInitialized) {
|
||||
unawaited(
|
||||
_spriteAxisService.prepareAt(
|
||||
episode.thumbnailsUrl,
|
||||
videoPort.controller.value.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
if (widget.homeMode)
|
||||
const ColoredBox(
|
||||
key: Key('home-feed-black-background'),
|
||||
color: AppColors.mediaBlack,
|
||||
),
|
||||
PlayerVideoSurface(
|
||||
controller: locked ? null : videoPort?.controller,
|
||||
videoUrl: videoPort?.controller.dataSource ?? episode.videoUrl,
|
||||
theater: theater,
|
||||
warmFrameAvailable: preparedFrameAvailable,
|
||||
warmSlotReused: warmSlotReused,
|
||||
thumbnailReady: openingFrameReady,
|
||||
posterReady: posterReady,
|
||||
fallback: handoffPort != null
|
||||
? PlayerOpeningFallback.handoff
|
||||
: playerOpeningFallback(
|
||||
posterUrl: posterUrl,
|
||||
thumbnailsUrl: thumbnailsUrl,
|
||||
),
|
||||
waitForPlaybackFrame: logic.session.userWantsPlay,
|
||||
placeholder: locked
|
||||
? null
|
||||
: handoffPort != null
|
||||
? ControllerFramePlaceholder(
|
||||
controller: handoffPort.controller,
|
||||
theater: theater,
|
||||
)
|
||||
: playerOpeningFrame(
|
||||
thumbnailsUrl,
|
||||
posterUrl: posterUrl,
|
||||
spriteSource: _spriteAxisService,
|
||||
fit: videoFit,
|
||||
),
|
||||
),
|
||||
if (selected &&
|
||||
!locked &&
|
||||
!theater &&
|
||||
|
||||
Reference in new issue
Block a user