fix(player): queue skip + cut over silence on track tap
Two bugs in the audio handler caused the playback issues seen on the v2026.05.13.0 build: 1. **Queue button on the now-playing screen did nothing.** MinstrelAudio Handler never overrode skipToQueueItem, so taps in QueueScreen fell through to BaseAudioHandler's empty default. The queue UI updated nothing because the handler did nothing. Now overridden: rebuilds the source list via setQueueFromTracks(_lastTracks, initialIndex) so the targeted item plays cleanly even when its source hadn't been built yet by the background fill. 2. **Tapping a song in a playlist let the previous track bleed through until the new source finished building.** setQueueFromTracks awaits _buildAudioSource before swapping the player's source list, and that wait can be a few hundred ms on a cache miss. During the wait the old source kept playing while the mini player UI had already flipped to the new title/artist. Now pause()ing the player at the start of setQueueFromTracks silences the old source the moment the user taps. Also stashes the most recent TrackRef list as _lastTracks so skipToQueueItem can reconstruct sources without having to peek into just_audio's internal source list.
This commit is contained in:
@@ -68,6 +68,14 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
|||||||
/// new queue, leaving the player "locked" to a corrupted state.
|
/// new queue, leaving the player "locked" to a corrupted state.
|
||||||
int _queueGeneration = 0;
|
int _queueGeneration = 0;
|
||||||
|
|
||||||
|
/// Tracks the most recent setQueueFromTracks() input so
|
||||||
|
/// skipToQueueItem can reconstruct the source list. just_audio
|
||||||
|
/// requires every source to be built before it can be a skip
|
||||||
|
/// target, but setQueueFromTracks only builds the initial source
|
||||||
|
/// and fills the rest in the background — so a skip to an index
|
||||||
|
/// past the fill front needs to rebuild from the stored tracks.
|
||||||
|
List<TrackRef> _lastTracks = const [];
|
||||||
|
|
||||||
/// Volume stream for UI subscribers. Mirrors the just_audio player's
|
/// Volume stream for UI subscribers. Mirrors the just_audio player's
|
||||||
/// volume directly; set via setVolume(double).
|
/// volume directly; set via setVolume(double).
|
||||||
Stream<double> get volumeStream => _player.volumeStream;
|
Stream<double> get volumeStream => _player.volumeStream;
|
||||||
@@ -105,6 +113,16 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
|||||||
// Reset suppress flag in case a prior backward-fill bailed on
|
// Reset suppress flag in case a prior backward-fill bailed on
|
||||||
// gen check before reaching its `finally`.
|
// gen check before reaching its `finally`.
|
||||||
_suppressIndexUpdates = false;
|
_suppressIndexUpdates = false;
|
||||||
|
_lastTracks = tracks;
|
||||||
|
|
||||||
|
// Pause the old source immediately so the previous track stops
|
||||||
|
// audibly the moment the user taps, instead of bleeding through
|
||||||
|
// until the new source finishes building. setAudioSources below
|
||||||
|
// will swap the source list cleanly; pause is the simplest way
|
||||||
|
// to silence the player during the (possibly multi-100ms) build.
|
||||||
|
if (_player.playing) {
|
||||||
|
await _player.pause();
|
||||||
|
}
|
||||||
|
|
||||||
// Populate the visible queue + current mediaItem immediately so
|
// Populate the visible queue + current mediaItem immediately so
|
||||||
// the player UI reflects the user's tap before any source has
|
// the player UI reflects the user's tap before any source has
|
||||||
@@ -130,6 +148,23 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
|||||||
unawaited(_fillRemainingSources(tracks, clampedInitial, myGen));
|
unawaited(_fillRemainingSources(tracks, clampedInitial, myGen));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Switches playback to the [index]th queue item. The full
|
||||||
|
/// just_audio source list isn't necessarily built yet
|
||||||
|
/// (_fillRemainingSources runs in the background), so we
|
||||||
|
/// reconstruct from the stored TrackRef list rather than calling
|
||||||
|
/// _player.seek(index: ...) on a possibly-missing source. Calling
|
||||||
|
/// setQueueFromTracks again is the safe path: it bumps the
|
||||||
|
/// generation, cancels any in-flight fill, rebuilds source[0] as
|
||||||
|
/// the target, and re-fills around. play() restarts playback so
|
||||||
|
/// queue taps feel like "jump to this song" rather than "set the
|
||||||
|
/// pointer and wait for me to press play."
|
||||||
|
@override
|
||||||
|
Future<void> skipToQueueItem(int index) async {
|
||||||
|
if (index < 0 || index >= _lastTracks.length) return;
|
||||||
|
await setQueueFromTracks(_lastTracks, initialIndex: index);
|
||||||
|
await play();
|
||||||
|
}
|
||||||
|
|
||||||
/// Background fill of the rest of the just_audio source list after
|
/// Background fill of the rest of the just_audio source list after
|
||||||
/// the initial source is playing. Forward direction first (most
|
/// the initial source is playing. Forward direction first (most
|
||||||
/// common skipNext target). Backward inserts shift the player's
|
/// common skipNext target). Backward inserts shift the player's
|
||||||
|
|||||||
Reference in New Issue
Block a user