createPositionStream static method
Creates a new stream periodically tracking the current position. The
stream will aim to emit steps
position updates at intervals of
current MediaItem.duration / steps
. This interval will be clipped between minPeriod
and maxPeriod
. This stream will not emit values while audio playback is
paused or stalled.
Note: each time this method is called, a new stream is created. If you intend to use this stream multiple times, you should hold a reference to the returned stream.
Implementation
static Stream<Duration> createPositionStream({
int steps = 800,
Duration minPeriod = const Duration(milliseconds: 200),
Duration maxPeriod = const Duration(milliseconds: 200),
}) {
assert(minPeriod <= maxPeriod);
assert(minPeriod > Duration.zero);
Duration? last;
late StreamController<Duration> controller;
late StreamSubscription<MediaItem?> mediaItemSubscription;
late StreamSubscription<PlaybackState> playbackStateSubscription;
Timer? currentTimer;
Duration duration() => _handler.mediaItem.nvalue?.duration ?? Duration.zero;
Duration step() {
var s = duration() ~/ steps;
if (s < minPeriod) s = minPeriod;
if (s > maxPeriod) s = maxPeriod;
return s;
}
void yieldPosition(Timer? timer) {
if (last != _handler.playbackState.nvalue?.position) {
controller.add((last = _handler.playbackState.nvalue?.position)!);
}
}
controller = StreamController.broadcast(
sync: true,
onListen: () {
mediaItemSubscription =
_handler.mediaItem.listen((MediaItem? mediaItem) {
// Potentially a new duration
currentTimer?.cancel();
currentTimer = Timer.periodic(step(), yieldPosition);
});
playbackStateSubscription =
_handler.playbackState.listen((PlaybackState state) {
// Potentially a time discontinuity
yieldPosition(currentTimer);
});
},
onCancel: () {
mediaItemSubscription.cancel();
playbackStateSubscription.cancel();
},
);
return controller.stream;
}