diff --git a/lib/collections/intents.dart b/lib/collections/intents.dart index eb08adde6..a4e822d27 100644 --- a/lib/collections/intents.dart +++ b/lib/collections/intents.dart @@ -27,8 +27,12 @@ class PlayPauseAction extends Action { final playlistNotifier = intent.ref.read(PlaylistQueueNotifier.notifier); if (playlist == null) { return null; - } else if (!PlaylistQueueNotifier.isPlaying) { - await playlistNotifier.play(); + } else if (!audioPlayer.isPlaying) { + if (audioPlayer.hasSource && !audioPlayer.isCompleted) { + await playlistNotifier.resume(); + } else { + await playlistNotifier.play(); + } } else { await playlistNotifier.pause(); } @@ -103,8 +107,7 @@ class SeekAction extends Action { ); return null; } - final position = - (await audioPlayer.getCurrentPosition() ?? Duration.zero).inSeconds; + final position = (await audioPlayer.position ?? Duration.zero).inSeconds; await playlistNotifier.seek( Duration( seconds: intent.forward ? position + 5 : position - 5, diff --git a/lib/components/album/album_card.dart b/lib/components/album/album_card.dart index 29caf36ce..962673cdf 100644 --- a/lib/components/album/album_card.dart +++ b/lib/components/album/album_card.dart @@ -7,6 +7,7 @@ import 'package:spotube/components/shared/playbutton_card.dart'; import 'package:spotube/hooks/use_breakpoint_value.dart'; import 'package:spotube/provider/playlist_queue_provider.dart'; import 'package:spotube/provider/spotify_provider.dart'; +import 'package:spotube/services/audio_player.dart'; import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -41,8 +42,7 @@ class AlbumCard extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final playlist = ref.watch(PlaylistQueueNotifier.provider); - final playing = useStream(PlaylistQueueNotifier.playing).data ?? - PlaylistQueueNotifier.isPlaying; + final playing = useStream(audioPlayer.playingStream).data ?? false; final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier); final queryClient = useQueryClient(); final query = queryClient diff --git a/lib/components/player/player_controls.dart b/lib/components/player/player_controls.dart index a94e20576..b4eb32e00 100644 --- a/lib/components/player/player_controls.dart +++ b/lib/components/player/player_controls.dart @@ -10,6 +10,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/use_progress.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/provider/playlist_queue_provider.dart'; +import 'package:spotube/services/audio_player.dart'; import 'package:spotube/utils/primitive_utils.dart'; class PlayerControls extends HookConsumerWidget { @@ -43,9 +44,8 @@ class PlayerControls extends HookConsumerWidget { []); final playlist = ref.watch(PlaylistQueueNotifier.provider); final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier); - final playing = useStream(PlaylistQueueNotifier.playing).data ?? - PlaylistQueueNotifier.isPlaying; - final buffering = useStream(playlistNotifier.buffering).data ?? true; + final playing = useStream(audioPlayer.playingStream).data ?? false; + final buffering = useStream(audioPlayer.bufferingStream).data ?? true; final theme = Theme.of(context); final isDominantColorDark = ThemeData.estimateBrightnessForColor( @@ -141,6 +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(), + secondaryTrackValue: progressObj.item4, onChanged: playlist?.isLoading == true || buffering ? null : (v) { @@ -154,6 +155,7 @@ class PlayerControls extends HookConsumerWidget { ); }, activeColor: sliderColor, + secondaryActiveColor: sliderColor.withOpacity(0.2), inactiveColor: sliderColor.withOpacity(0.15), ), ), diff --git a/lib/components/player/player_overlay.dart b/lib/components/player/player_overlay.dart index 4d00bbf5c..9cfef7bed 100644 --- a/lib/components/player/player_overlay.dart +++ b/lib/components/player/player_overlay.dart @@ -10,6 +10,7 @@ import 'package:spotube/components/player/player_track_details.dart'; import 'package:spotube/collections/intents.dart'; import 'package:spotube/hooks/use_progress.dart'; import 'package:spotube/provider/playlist_queue_provider.dart'; +import 'package:spotube/services/audio_player.dart'; import 'package:spotube/utils/service_utils.dart'; class PlayerOverlay extends HookConsumerWidget { @@ -27,8 +28,7 @@ class PlayerOverlay extends HookConsumerWidget { ); final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier); final playlist = ref.watch(PlaylistQueueNotifier.provider); - final playing = useStream(PlaylistQueueNotifier.playing).data ?? - PlaylistQueueNotifier.isPlaying; + final playing = useStream(audioPlayer.playingStream).data ?? false; final theme = Theme.of(context); final textColor = theme.colorScheme.primary; diff --git a/lib/components/playlist/playlist_card.dart b/lib/components/playlist/playlist_card.dart index 50320df36..4e971666a 100644 --- a/lib/components/playlist/playlist_card.dart +++ b/lib/components/playlist/playlist_card.dart @@ -6,6 +6,7 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/playbutton_card.dart'; import 'package:spotube/provider/playlist_queue_provider.dart'; import 'package:spotube/provider/spotify_provider.dart'; +import 'package:spotube/services/audio_player.dart'; import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -20,8 +21,7 @@ class PlaylistCard extends HookConsumerWidget { Widget build(BuildContext context, ref) { final playlistQueue = ref.watch(PlaylistQueueNotifier.provider); final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier); - final playing = useStream(PlaylistQueueNotifier.playing).data ?? - PlaylistQueueNotifier.isPlaying; + final playing = useStream(audioPlayer.playingStream).data ?? false; final queryBowl = QueryClient.of(context); final query = queryBowl.getQuery, dynamic>( "playlist-tracks/${playlist.id}", diff --git a/lib/hooks/use_progress.dart b/lib/hooks/use_progress.dart index 25938de8a..0b1ca309c 100644 --- a/lib/hooks/use_progress.dart +++ b/lib/hooks/use_progress.dart @@ -2,16 +2,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/provider/playlist_queue_provider.dart'; +import 'package:spotube/services/audio_player.dart'; import 'package:tuple/tuple.dart'; -Tuple3 useProgress(WidgetRef ref) { +Tuple4 useProgress(WidgetRef ref) { ref.watch(PlaylistQueueNotifier.provider); + final bufferProgress = + useStream(audioPlayer.bufferedPositionStream).data?.inSeconds ?? 0; final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier); - final duration = - useStream(PlaylistQueueNotifier.duration).data ?? Duration.zero; - final positionSnapshot = useStream(PlaylistQueueNotifier.position); + final duration = useStream(audioPlayer.durationStream).data ?? Duration.zero; + final positionSnapshot = useStream(audioPlayer.positionStream); final position = positionSnapshot.data ?? Duration.zero; @@ -31,9 +33,12 @@ Tuple3 useProgress(WidgetRef ref) { return null; }, [positionSnapshot.hasData, duration]); - return Tuple3( + return Tuple4( sliderMax == 0 || sliderValue > sliderMax ? 0 : sliderValue / sliderMax, position, duration, + sliderMax == 0 || bufferProgress > sliderMax + ? 0 + : bufferProgress / sliderMax, ); } diff --git a/lib/hooks/use_synced_lyrics.dart b/lib/hooks/use_synced_lyrics.dart index 99c4a0f1c..1bdbea65c 100644 --- a/lib/hooks/use_synced_lyrics.dart +++ b/lib/hooks/use_synced_lyrics.dart @@ -1,13 +1,13 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/provider/playlist_queue_provider.dart'; +import 'package:spotube/services/audio_player.dart'; int useSyncedLyrics( WidgetRef ref, Map lyricsMap, int delay, ) { - final stream = PlaylistQueueNotifier.position; + final stream = audioPlayer.positionStream; final currentTime = useState(0); diff --git a/lib/main.dart b/lib/main.dart index 9f7a9e05f..d9d550f3d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -71,6 +71,7 @@ Future main(List rawArgs) async { } WidgetsFlutterBinding.ensureInitialized(); + await DesktopTools.ensureInitialized( DesktopWindowOptions( hideTitleBar: true, diff --git a/lib/provider/playlist_queue_provider.dart b/lib/provider/playlist_queue_provider.dart index 2f800ec29..33b013c40 100644 --- a/lib/provider/playlist_queue_provider.dart +++ b/lib/provider/playlist_queue_provider.dart @@ -1,6 +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'; import 'package:palette_generator/palette_generator.dart'; @@ -140,8 +139,6 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { late AudioServices audioServices; - final StreamController _bufferingController; - static final provider = StateNotifierProvider( (ref) => PlaylistQueueNotifier._(ref), @@ -149,16 +146,14 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { static final notifier = provider.notifier; - PlaylistQueueNotifier._(this.ref) - : _bufferingController = StreamController.broadcast(), - super(null, "playlist") { + PlaylistQueueNotifier._(this.ref) : super(null, "playlist") { configure(); } void configure() async { audioServices = await AudioServices.create(ref, this); - audioPlayer.onPlayerComplete.listen((event) async { + audioPlayer.completedStream.listen((event) async { if (!isLoaded) return; if (state!.isLooping) { await audioPlayer.seek(Duration.zero); @@ -170,10 +165,9 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { bool isPreSearching = false; - audioPlayer.onPositionChanged.listen((pos) async { + audioPlayer.positionStream.listen((pos) async { if (!isLoaded) return; - _bufferingController.add(false); - final currentDuration = await audioPlayer.getDuration() ?? Duration.zero; + final currentDuration = await audioPlayer.duration ?? Duration.zero; // skip all the activeTrack.skipSegments if (state?.isLoading != true && @@ -199,18 +193,16 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { !isPreSearching) { isPreSearching = true; final tracks = state!.tracks.toList(); - tracks[state!.active + 1] = await SpotubeTrack.fetchFromTrack( + final newTrack = await SpotubeTrack.fetchFromTrack( state!.tracks.elementAt(state!.active + 1), preferences, ); + tracks[state!.active + 1] = newTrack; + await audioPlayer.preload(newTrack.ytUri); state = state!.copyWith(tracks: Set.from(tracks)); isPreSearching = false; } }); - - audioPlayer.onSeekComplete.listen((event) { - _bufferingController.add(false); - }); } // properties @@ -222,31 +214,6 @@ 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; - static bool get isStopped => audioPlayer.state == PlayerState.stopped; - - static Stream get duration => - audioPlayer.onDurationChanged.asBroadcastStream(); - static Stream get position => - audioPlayer.onPositionChanged.asBroadcastStream(); - static Stream get playing => audioPlayer.onPlayerStateChanged - .map((event) => event == PlayerState.playing) - .asBroadcastStream(); - List