feat(player): periodic PlaybackState refresh for smooth external scrubber
_broadcastState only set updatePosition on transitions, so the lock- screen / Wear / Android Auto scrubber jumped in chunks (the in-app bar uses positionStream and was fine). Add _positionBroadcastTimer: a 1s periodic PlaybackState re-broadcast while actively playing so updateTime/updatePosition stay fresh and external surfaces interpolate smoothly. Idempotent (driven from _broadcastState, which the tick itself calls — guarded against pile-up), cancelled when not playing and in stop(). 6a: the notification progress bar now advances since MediaItem.duration was already set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,12 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
/// cancelled the moment playback resumes or a new queue is set.
|
||||
Timer? _idleStopTimer;
|
||||
|
||||
/// Periodic PlaybackState re-broadcast while actively playing so
|
||||
/// external surfaces (lock screen, Wear, Android Auto) interpolate the
|
||||
/// scrubber smoothly. The in-app bar uses positionStream and doesn't
|
||||
/// need this. Active only while playing; cancelled otherwise.
|
||||
Timer? _positionBroadcastTimer;
|
||||
|
||||
/// Player volume captured when an OS duck interruption begins,
|
||||
/// restored when it ends. Null when not currently ducked.
|
||||
double? _volumeBeforeDuck;
|
||||
@@ -544,6 +550,8 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
Future<void> stop() async {
|
||||
_idleStopTimer?.cancel();
|
||||
_idleStopTimer = null;
|
||||
_positionBroadcastTimer?.cancel();
|
||||
_positionBroadcastTimer = null;
|
||||
try {
|
||||
await _player.stop();
|
||||
} catch (_) {}
|
||||
@@ -712,6 +720,7 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
},
|
||||
));
|
||||
_reconcileIdleTimer();
|
||||
_reconcilePositionBroadcast();
|
||||
}
|
||||
|
||||
/// Arms (or cancels) the idle-cleanup timer based on the current
|
||||
@@ -746,6 +755,28 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
unawaited(stop());
|
||||
}
|
||||
|
||||
/// While actively playing, keeps a 1s periodic PlaybackState re-
|
||||
/// broadcast running so updateTime/updatePosition stay fresh and
|
||||
/// external surfaces interpolate the scrubber smoothly. Idempotent and
|
||||
/// driven from _broadcastState — which the periodic tick itself calls,
|
||||
/// so the "already running" guard prevents pile-up. Cancels the moment
|
||||
/// playback is no longer active.
|
||||
void _reconcilePositionBroadcast() {
|
||||
final ps = _player.processingState;
|
||||
final active = _player.playing &&
|
||||
ps != ProcessingState.idle &&
|
||||
ps != ProcessingState.completed;
|
||||
if (!active) {
|
||||
_positionBroadcastTimer?.cancel();
|
||||
_positionBroadcastTimer = null;
|
||||
return;
|
||||
}
|
||||
_positionBroadcastTimer ??= Timer.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(_) => _broadcastState(null),
|
||||
);
|
||||
}
|
||||
|
||||
/// Configures the OS audio session for music playback and wires
|
||||
/// interruption + becoming-noisy handling. just_audio auto-activates /
|
||||
/// deactivates the session around playback once it's configured, but
|
||||
|
||||
Reference in New Issue
Block a user