fix(player): 收紧图片与快速切集资源占用
This commit is contained in:
1 parent
2fef7470e0
commit
80c962a95c
10 files changed
+281
-30
No files matched your search
@@ -93,6 +93,9 @@ class PlayerLogic extends ChangeNotifier {
|
||||
String selectedQuality = 'AUTO';
|
||||
Duration? _qualitySeek;
|
||||
int pageRequest = 0;
|
||||
int? _pendingSelectionIndex;
|
||||
int? _pendingSelectionRevision;
|
||||
Future<void>? _selectionDrain;
|
||||
bool muted = false;
|
||||
double playbackSpeed = 1.0;
|
||||
bool stoppedAtFeedEnd = false;
|
||||
@@ -957,6 +960,37 @@ class PlayerLogic extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
/// 翻页入口使用 single-flight:执行中的切集不被重复排队,只保留最新目标。
|
||||
Future<void> requestEpisodeSelection(int index, {int? revision}) {
|
||||
if (_disposed || index < 0 || index >= sessionEpisodes.length) {
|
||||
return Future<void>.value();
|
||||
}
|
||||
_pendingSelectionIndex = index;
|
||||
_pendingSelectionRevision = revision;
|
||||
return _selectionDrain ??= _drainEpisodeSelections();
|
||||
}
|
||||
|
||||
Future<void> _drainEpisodeSelections() async {
|
||||
try {
|
||||
while (!_disposed) {
|
||||
final index = _pendingSelectionIndex;
|
||||
if (index == null) break;
|
||||
final revision = _pendingSelectionRevision;
|
||||
_pendingSelectionIndex = null;
|
||||
_pendingSelectionRevision = null;
|
||||
await selectEpisode(index, revision: revision);
|
||||
}
|
||||
} finally {
|
||||
_selectionDrain = null;
|
||||
final index = _pendingSelectionIndex;
|
||||
if (!_disposed && index != null) {
|
||||
unawaited(
|
||||
requestEpisodeSelection(index, revision: _pendingSelectionRevision),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> selectEpisode(int index, {int? revision}) async {
|
||||
final feedRevisionAtStart = revision ?? feedRevision;
|
||||
final request = ++pageRequest;
|
||||
|
||||
@@ -353,6 +353,28 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _selectPage(int index) async {
|
||||
await logic.requestEpisodeSelection(index);
|
||||
if (!mounted || logic.session.currentIndex != index) return;
|
||||
await logic.session.preloadSettled;
|
||||
if (!mounted || logic.session.currentIndex != index) return;
|
||||
_logResourceWatermark();
|
||||
}
|
||||
|
||||
void _logResourceWatermark() {
|
||||
if (!kDebugMode) return;
|
||||
final imageCache = PaintingBinding.instance.imageCache;
|
||||
debugPrint(
|
||||
'[player-resources] '
|
||||
'slots=${logic.session.livePlayerCount} '
|
||||
'warmNext=${logic.session.preloadedIndex ?? '-'} '
|
||||
'warmPrevious=${logic.session.previousPreloadedIndex ?? '-'} '
|
||||
'sprites=${_spriteAxisService.spriteCacheSize} '
|
||||
'imageCacheBytes=${imageCache.currentSizeBytes} '
|
||||
'imageCacheCount=${imageCache.currentSize}',
|
||||
);
|
||||
}
|
||||
|
||||
void _setScrubbing(bool value) {
|
||||
if (!mounted) return;
|
||||
if (_scrubbing == value) return;
|
||||
@@ -507,7 +529,7 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
onPageChanged: (position) {
|
||||
final index = indices[position];
|
||||
widget.onDramaChanged?.call(logic.queue.entryAt(index).drama);
|
||||
unawaited(logic.selectEpisode(index));
|
||||
unawaited(_selectPage(index));
|
||||
if (position >= indices.length - 3) {
|
||||
final loadMore = widget.onLoadMore;
|
||||
if (loadMore != null) unawaited(loadMore());
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
||||
import '../../../core/config/app_config.dart';
|
||||
import '../../../core/data/services/sprite_thumbnail_axis.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/widgets/remote_image.dart';
|
||||
import 'scrub_preview_card.dart';
|
||||
|
||||
enum PlayerOpeningFallback { handoff, poster, thumbnails, black }
|
||||
@@ -149,12 +150,25 @@ class _PlayerOpeningFrameState extends State<PlayerOpeningFrame> {
|
||||
children: [
|
||||
const ColoredBox(color: AppColors.mediaBlack),
|
||||
if (_hasPoster)
|
||||
Image.network(
|
||||
AppConfig.mediaUrl(widget.posterUrl!.trim()),
|
||||
key: const Key('player-opening-poster'),
|
||||
fit: widget.fit,
|
||||
gaplessPlayback: true,
|
||||
errorBuilder: (_, _, _) => _handlePosterError(),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final pixelRatio = MediaQuery.devicePixelRatioOf(context);
|
||||
return Image.network(
|
||||
AppConfig.mediaUrl(widget.posterUrl!.trim()),
|
||||
key: const Key('player-opening-poster'),
|
||||
fit: widget.fit,
|
||||
cacheWidth: resolveImageDecodePixels(
|
||||
constraints.maxWidth,
|
||||
pixelRatio,
|
||||
),
|
||||
cacheHeight: resolveImageDecodePixels(
|
||||
constraints.maxHeight,
|
||||
pixelRatio,
|
||||
),
|
||||
gaplessPlayback: true,
|
||||
errorBuilder: (_, _, _) => _handlePosterError(),
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
_spriteOrBlack(),
|
||||
|
||||
Reference in new issue
Block a user