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