diff --git a/lib/components/player/player_controls.dart b/lib/components/player/player_controls.dart index 91f336158..a94e20576 100644 --- a/lib/components/player/player_controls.dart +++ b/lib/components/player/player_controls.dart @@ -45,6 +45,7 @@ class PlayerControls extends HookConsumerWidget { final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier); final playing = useStream(PlaylistQueueNotifier.playing).data ?? PlaylistQueueNotifier.isPlaying; + final buffering = useStream(playlistNotifier.buffering).data ?? true; final theme = Theme.of(context); final isDominantColorDark = ThemeData.estimateBrightnessForColor( @@ -140,7 +141,7 @@ class PlayerControls extends HookConsumerWidget { // there's an edge case for value being bigger // than total duration. Keeping it resolved value: progress.value.toDouble(), - onChanged: playlist?.isLoading == true + onChanged: playlist?.isLoading == true || buffering ? null : (v) { progress.value = v; @@ -188,7 +189,7 @@ class PlayerControls extends HookConsumerWidget { style: playlist?.isShuffled == true ? activeButtonStyle : buttonStyle, - onPressed: playlist == null + onPressed: playlist == null || playlist.isLoading ? null : () { if (playlist.isShuffled == true) { @@ -221,10 +222,12 @@ class PlayerControls extends HookConsumerWidget { playing ? SpotubeIcons.pause : SpotubeIcons.play, ), style: resumePauseStyle, - onPressed: Actions.handler( - context, - PlayPauseIntent(ref), - ), + onPressed: playlist?.isLoading == true + ? null + : Actions.handler( + context, + PlayPauseIntent(ref), + ), ), IconButton( tooltip: context.l10n.next_track, diff --git a/lib/provider/playlist_queue_provider.dart b/lib/provider/playlist_queue_provider.dart index 3188fd468..2f800ec29 100644 --- a/lib/provider/playlist_queue_provider.dart +++ b/lib/provider/playlist_queue_provider.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:audioplayers/audioplayers.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -138,6 +140,8 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { late AudioServices audioServices; + final StreamController _bufferingController; + static final provider = StateNotifierProvider( (ref) => PlaylistQueueNotifier._(ref), @@ -145,7 +149,9 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { static final notifier = provider.notifier; - PlaylistQueueNotifier._(this.ref) : super(null, "playlist") { + PlaylistQueueNotifier._(this.ref) + : _bufferingController = StreamController.broadcast(), + super(null, "playlist") { configure(); } @@ -166,6 +172,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { audioPlayer.onPositionChanged.listen((pos) async { if (!isLoaded) return; + _bufferingController.add(false); final currentDuration = await audioPlayer.getDuration() ?? Duration.zero; // skip all the activeTrack.skipSegments @@ -178,7 +185,8 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { in (state!.activeTrack as SpotubeTrack).skipSegments) { if ((pos.inSeconds >= segment["start"]! && pos.inSeconds < segment["end"]!)) { - await audioPlayer.seek(Duration(seconds: segment["end"]!)); + await audioPlayer.pause(); + await seek(Duration(seconds: segment["end"]!)); } } } @@ -199,6 +207,10 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { isPreSearching = false; } }); + + audioPlayer.onSeekComplete.listen((event) { + _bufferingController.add(false); + }); } // properties @@ -210,6 +222,18 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { bool get isLoaded => state != null; + Stream get buffering => + _bufferingController.stream.asyncMap((bufferEvent) async { + final duration = await audioPlayer.getDuration(); + final position = await audioPlayer.getCurrentPosition(); + final isBuffering = state?.activeTrack is! SpotubeTrack && + audioPlayer.state == PlayerState.playing && + (bufferEvent || (duration == null && position == null)); + return isBuffering; + }); + + Future get isBuffering => buffering.first; + // redirectors static bool get isPlaying => audioPlayer.state == PlayerState.playing; static bool get isPaused => audioPlayer.state == PlayerState.paused; @@ -336,6 +360,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { Future play() async { if (!isLoaded) return; + _bufferingController.add(true); await pause(); await audioServices.addTrack(state!.activeTrack); if (state!.activeTrack is LocalTrack) { @@ -362,7 +387,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { ); } - audioServices.addTrack(state!.activeTrack); + await audioServices.addTrack(state!.activeTrack); final cached = await DefaultCacheManager().getFileFromCache(state!.activeTrack.id!); @@ -450,6 +475,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { Future seek(Duration position) async { if (!isLoaded) return; + _bufferingController.add(true); await audioPlayer.seek(position); await resume(); }