createPositionStream static method

Stream<Duration> createPositionStream({
  1. int steps = 800,
  2. Duration minPeriod = const Duration(milliseconds: 200),
  3. Duration maxPeriod = const Duration(milliseconds: 200),
})

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;
}