brim-borium/spotify_sdk

PlayerState playback position doesn't update continuously

Closed this issue ยท 6 comments

When using SpotifySdk.subscribePlayerState() in a StreamBuilder, the member PlayerState.playbackPosition only updates when another member of PlayerState changes. You can see this in the example app. You have to click on pause/resume to get the value to change.

I've been trying to see if there is a bug in this SDK but haven't been able to find anything.

I want this to work as I'm trying to make a track progress bar for a project I'm working on.

This is not a problem with this plugin but rather a limitation of the Spotify SDK itself. The updates are only sent by Spotify when the player parameters change or randomly during the playback.
If you want to implement a track progress bar, you need to cache the last position of the player received from this plugin and then have a timer which will increase that position over time. A good plugin for this could be timer_builder

Oh okay, that makes sense. And thanks for the quick response!

I'll implement something this evening and post it here for reference and then close this issue.

I am using a variant of this snippet I made earlier:

    // Run every 100 milliseconds (10 times a second)
    Timer.periodic(Duration(milliseconds: 100), (_) {
      // Only increase progress if state is not null and is not paused
      if(playerState?.track?.duration != null && !playerState.isPaused) {
        // Increase the progress of the playback by 100 milliseconds
        this._progress += 100;
        // If the progress is larger than the duration of the track,
        // Set it to the track duration, so any errors with progress bars
        // etc. get avoided
        if(this._progress > playerState.track.duration) {
          this._progress = playerState.track.duration;
        }
      }
    });

Thanks Luca, very useful! Only change I did was also incorporate playback speed and wrap it all in a widget ๐Ÿ˜„

class PlaybackIndicator extends StatefulWidget {
    PlaybackIndicator({Key key, this.initialPosition, this.trackDuration,
        this.playbackSpeed, this.isPaused}) : super(key: key);

    final int initialPosition;
    final int trackDuration;
    final double playbackSpeed;
    final bool isPaused;

    @override
    _PlaybackIndicatorState createState() => _PlaybackIndicatorState();
}

class _PlaybackIndicatorState extends State<PlaybackIndicator> {
    int _position;
    Timer _timer;

    @override
    void initState() {
        super.initState();
        _position = widget.initialPosition;
        if (!widget.isPaused) {
            _timer = Timer.periodic(Duration(milliseconds: 100), (_) {
                setState(() {
                    _position = min(widget.trackDuration, _position + (100*widget.playbackSpeed).toInt());
                });
            });
        }
    }

    @override
    void didUpdateWidget(Widget oldWidget){
        super.didUpdateWidget(oldWidget);
        _position = widget.initialPosition;
    }

    @override
    void dispose() {
        _timer?.cancel();
        super.dispose();
    }

    @override
    Widget build(BuildContext context) {
        return LinearProgressIndicator(
            value: _position / widget.trackDuration
        );
    }
}

Null safe version. I used this successfully in Flutter 2.10.5:

class PlaybackIndicator extends StatefulWidget {
  const PlaybackIndicator(
      {Key? key,
      required this.initialPosition,
      required this.trackDuration,
      required this.playbackSpeed,
      required this.isPaused})
      : super(key: key);

  final int initialPosition;
  final int trackDuration;
  final double playbackSpeed;
  final bool isPaused;

  @override
  _PlaybackIndicatorState createState() => _PlaybackIndicatorState();
}

class _PlaybackIndicatorState extends State<PlaybackIndicator> {
  late int position;
  late Timer timer;

  @override
  void initState() {
    super.initState();
    position = widget.initialPosition;
    if (!widget.isPaused) {
      timer = Timer.periodic(Duration(milliseconds: 100), (_) {
        setState(() {
          position = min(widget.trackDuration,
              position + (100 * widget.playbackSpeed).toInt());
        });
      });
    }
  }

  @override
  void didUpdateWidget(PlaybackIndicator oldWidget) {
    super.didUpdateWidget(oldWidget);
    position = widget.initialPosition;
  }

  @override
  void dispose() {
    timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LinearProgressIndicator(value: position / widget.trackDuration);
  }
}

@banool CAN U elaborate how did u used it cause mine is not updating.