diff --git a/lib/services/music_player_service.dart b/lib/services/music_player_service.dart index aca4f6a0..f8c595a5 100644 --- a/lib/services/music_player_service.dart +++ b/lib/services/music_player_service.dart @@ -1273,7 +1273,6 @@ class MusicPlayerHandler extends BaseAudioHandler await _player.stop(); if (!_isCurrentPlayRequest(generation, media)) return; await _loadIndex( - index, generation, media, recordHistory: recordHistory, @@ -1312,7 +1311,6 @@ class MusicPlayerHandler extends BaseAudioHandler } Future _loadIndex( - int index, int generation, PlayableMedia media, { required bool recordHistory, @@ -1322,7 +1320,7 @@ class MusicPlayerHandler extends BaseAudioHandler _interruptionActive = false; _userPaused = false; - if (recordHistory) _recordPlayHistory(index); + if (recordHistory) _recordPlayHistory(_index); final effectiveStartPosition = normalizedPlaybackResumePosition( startPosition, @@ -1454,7 +1452,7 @@ class MusicPlayerHandler extends BaseAudioHandler unawaited(_persistSession(position: effectiveStartPosition)); // Some files do not emit onDurationChanged reliably (stuck at 0:00); // poll the engine for the real duration as a fallback. - unawaited(_ensureDurationKnown(index, generation)); + unawaited(_ensureDurationKnown(media, generation)); } catch (e) { if (!_isCurrentPlayRequest(generation, media)) return; _sourceReady = false; @@ -1473,16 +1471,16 @@ class MusicPlayerHandler extends BaseAudioHandler /// 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 /// do not get stuck at 0:00. - Future _ensureDurationKnown(int index, int generation) async { + Future _ensureDurationKnown(PlayableMedia media, int generation) async { for (var attempt = 0; attempt < 15; attempt++) { - if (_index != index || generation != _playRequestGeneration) return; + if (!_isCurrentPlayRequest(generation, media)) return; final current = mediaItem.value; final existing = current?.duration; if (existing != null && existing > Duration.zero) return; try { final d = await _player.getDuration(); - if (_index != index || generation != _playRequestGeneration) return; + if (!_isCurrentPlayRequest(generation, media)) return; if (d != null && d > Duration.zero) { final item = mediaItem.value; if (item != null) { diff --git a/test/music_player_automix_test.dart b/test/music_player_automix_test.dart index 3e4f4226..c7484891 100644 --- a/test/music_player_automix_test.dart +++ b/test/music_player_automix_test.dart @@ -41,6 +41,7 @@ class _AudioNative { final sources = {}; final sourceGates = >{}; final resumedSources = []; + bool emitsDuration = true; void install() { for (final name in [ @@ -71,7 +72,7 @@ class _AudioNative { await sourceGates[source]?.future; sources[id] = source; unawaited(event(id, 'audio.onPrepared', true)); - unawaited(event(id, 'audio.onDuration', 60000)); + if (emitsDuration) unawaited(event(id, 'audio.onDuration', 60000)); case 'seek': positions[id] = args['position']! as int; 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(); + 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 { await handler.restoreSession( items: [_tracks[1], _tracks[2], _tracks[0]],