fix(player): retain duration probing after queue edits during loading

This commit is contained in:
zarzet committed 2026-09-27 00:42:53 +07:00
1 parent da5a0522ca
commit fa4a03c754
2 files changed
+27 -8

No files matched your search

+5 -7
View File
@@ -1273,7 +1273,6 @@ class MusicPlayerHandler extends BaseAudioHandler
await _player.stop(); await _player.stop();
if (!_isCurrentPlayRequest(generation, media)) return; if (!_isCurrentPlayRequest(generation, media)) return;
await _loadIndex( await _loadIndex(
index,
generation, generation,
media, media,
recordHistory: recordHistory, recordHistory: recordHistory,
@@ -1312,7 +1311,6 @@ class MusicPlayerHandler extends BaseAudioHandler
} }
Future<void> _loadIndex( Future<void> _loadIndex(
int index,
int generation, int generation,
PlayableMedia media, { PlayableMedia media, {
required bool recordHistory, required bool recordHistory,
@@ -1322,7 +1320,7 @@ class MusicPlayerHandler extends BaseAudioHandler
_interruptionActive = false; _interruptionActive = false;
_userPaused = false; _userPaused = false;
if (recordHistory) _recordPlayHistory(index); if (recordHistory) _recordPlayHistory(_index);
final effectiveStartPosition = normalizedPlaybackResumePosition( final effectiveStartPosition = normalizedPlaybackResumePosition(
startPosition, startPosition,
@@ -1454,7 +1452,7 @@ class MusicPlayerHandler extends BaseAudioHandler
unawaited(_persistSession(position: effectiveStartPosition)); unawaited(_persistSession(position: effectiveStartPosition));
// Some files do not emit onDurationChanged reliably (stuck at 0:00); // Some files do not emit onDurationChanged reliably (stuck at 0:00);
// poll the engine for the real duration as a fallback. // poll the engine for the real duration as a fallback.
unawaited(_ensureDurationKnown(index, generation)); unawaited(_ensureDurationKnown(media, generation));
} catch (e) { } catch (e) {
if (!_isCurrentPlayRequest(generation, media)) return; if (!_isCurrentPlayRequest(generation, media)) return;
_sourceReady = false; _sourceReady = false;
@@ -1473,16 +1471,16 @@ class MusicPlayerHandler extends BaseAudioHandler
/// Resolves the real track duration when the initial metadata had none and /// Resolves the real track duration when the initial metadata had none and
/// the duration-changed event did not fire, so the seek bar and total time /// the duration-changed event did not fire, so the seek bar and total time
/// do not get stuck at 0:00. /// do not get stuck at 0:00.
Future<void> _ensureDurationKnown(int index, int generation) async { Future<void> _ensureDurationKnown(PlayableMedia media, int generation) async {
for (var attempt = 0; attempt < 15; attempt++) { for (var attempt = 0; attempt < 15; attempt++) {
if (_index != index || generation != _playRequestGeneration) return; if (!_isCurrentPlayRequest(generation, media)) return;
final current = mediaItem.value; final current = mediaItem.value;
final existing = current?.duration; final existing = current?.duration;
if (existing != null && existing > Duration.zero) return; if (existing != null && existing > Duration.zero) return;
try { try {
final d = await _player.getDuration(); final d = await _player.getDuration();
if (_index != index || generation != _playRequestGeneration) return; if (!_isCurrentPlayRequest(generation, media)) return;
if (d != null && d > Duration.zero) { if (d != null && d > Duration.zero) {
final item = mediaItem.value; final item = mediaItem.value;
if (item != null) { if (item != null) {
+22 -1
View File
@@ -41,6 +41,7 @@ class _AudioNative {
final sources = <String, String>{}; final sources = <String, String>{};
final sourceGates = <String, Completer<void>>{}; final sourceGates = <String, Completer<void>>{};
final resumedSources = <String>[]; final resumedSources = <String>[];
bool emitsDuration = true;
void install() { void install() {
for (final name in [ for (final name in [
@@ -71,7 +72,7 @@ class _AudioNative {
await sourceGates[source]?.future; await sourceGates[source]?.future;
sources[id] = source; sources[id] = source;
unawaited(event(id, 'audio.onPrepared', true)); unawaited(event(id, 'audio.onPrepared', true));
unawaited(event(id, 'audio.onDuration', 60000)); if (emitsDuration) unawaited(event(id, 'audio.onDuration', 60000));
case 'seek': case 'seek':
positions[id] = args['position']! as int; positions[id] = args['position']! as int;
unawaited(event(id, 'audio.onSeekComplete')); unawaited(event(id, 'audio.onSeekComplete'));
@@ -406,6 +407,26 @@ void main() {
}, },
); );
test(
'removing an earlier row during preparation preserves duration probing',
() async {
native.emitsDuration = false;
final gate = Completer<void>();
native.sourceGates['/three.flac'] = gate;
final loading = handler.setQueueAndPlay(_tracks, initialIndex: 2);
await _until(() => native.calls.any((call) => call.$2 == 'setSourceUrl'));
handler.removeQueuedItem(handler.queue.value.first);
gate.complete();
await loading;
await _until(
() => handler.mediaItem.value?.duration == const Duration(seconds: 60),
);
expect(handler.playbackState.value.queueIndex, 1);
expect(handler.mediaItem.value?.id, 'three');
expect(native.resumedSources, ['/three.flac']);
},
);
test('restored shuffle can return to the saved original order', () async { test('restored shuffle can return to the saved original order', () async {
await handler.restoreSession( await handler.restoreSession(
items: [_tracks[1], _tracks[2], _tracks[0]], items: [_tracks[1], _tracks[2], _tracks[0]],