From f160ec767d9941d33f83aba1752b28df629d0e10 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 24 Mar 2023 19:55:16 +0600 Subject: [PATCH] fix: android audio service and notification and fallback for lyrics when anonymous --- lib/pages/lyrics/lyrics.dart | 13 ++++ lib/pages/player/player.dart | 89 +++++++++++++---------- lib/pages/root/root_app.dart | 16 ++-- lib/provider/playlist_queue_provider.dart | 5 +- lib/services/mobile_audio_service.dart | 57 ++++++++++++--- 5 files changed, 120 insertions(+), 60 deletions(-) diff --git a/lib/pages/lyrics/lyrics.dart b/lib/pages/lyrics/lyrics.dart index ee61deef..3f1167a0 100644 --- a/lib/pages/lyrics/lyrics.dart +++ b/lib/pages/lyrics/lyrics.dart @@ -5,12 +5,14 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:platform_ui/platform_ui.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/hooks/use_custom_status_bar_color.dart'; import 'package:spotube/hooks/use_palette_color.dart'; import 'package:spotube/pages/lyrics/plain_lyrics.dart'; import 'package:spotube/pages/lyrics/synced_lyrics.dart'; +import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/playlist_queue_provider.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -39,6 +41,17 @@ class LyricsPage extends HookConsumerWidget { noSetBGColor: true, ); + final auth = ref.watch(AuthenticationNotifier.provider); + + if (auth == null) { + return PlatformScaffold( + appBar: !kIsMacOS && platform != TargetPlatform.windows && !isModal + ? PageWindowTitleBar() + : null, + body: const AnonymousFallback(), + ); + } + Widget body = [ SyncedLyrics(palette: palette, isModal: isModal), PlainLyrics(palette: palette, isModal: isModal), diff --git a/lib/pages/player/player.dart b/lib/pages/player/player.dart index 202eb256..6ebdb99e 100644 --- a/lib/pages/player/player.dart +++ b/lib/pages/player/player.dart @@ -18,6 +18,7 @@ import 'package:spotube/hooks/use_custom_status_bar_color.dart'; import 'package:spotube/hooks/use_palette_color.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/pages/lyrics/lyrics.dart'; +import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/playlist_queue_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -29,6 +30,7 @@ class PlayerView extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final auth = ref.watch(AuthenticationNotifier.provider); final currentTrack = ref.watch(PlaylistQueueNotifier.provider.select( (value) => value?.activeTrack, )); @@ -104,11 +106,13 @@ class PlayerView extends HookConsumerWidget { height: 30, child: SpotubeMarqueeText( text: currentTrack?.name ?? "Not playing", - style: - Theme.of(context).textTheme.headline5?.copyWith( - fontWeight: FontWeight.bold, - color: paletteColor.titleTextColor, - ), + style: Theme.of(context) + .textTheme + .headlineSmall + ?.copyWith( + fontWeight: FontWeight.bold, + color: paletteColor.titleTextColor, + ), isHovering: true, ), ), @@ -117,20 +121,24 @@ class PlayerView extends HookConsumerWidget { TypeConversionUtils.artists_X_String( currentTrack?.artists ?? [], ), - style: - Theme.of(context).textTheme.headline6!.copyWith( - fontWeight: FontWeight.bold, - color: paletteColor.bodyTextColor, - ), + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + fontWeight: FontWeight.bold, + color: paletteColor.bodyTextColor, + ), ) else TypeConversionUtils.artists_X_ClickableArtists( currentTrack?.artists ?? [], - textStyle: - Theme.of(context).textTheme.headline6!.copyWith( - fontWeight: FontWeight.bold, - color: paletteColor.bodyTextColor, - ), + textStyle: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + fontWeight: FontWeight.bold, + color: paletteColor.bodyTextColor, + ), onRouteChange: (route) { GoRouter.of(context).pop(); GoRouter.of(context).push(route); @@ -193,32 +201,33 @@ class PlayerView extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, floatingQueue: false, extraActions: [ - PlatformIconButton( - tooltip: "Open Lyrics", - icon: const Icon(SpotubeIcons.music), - onPressed: () { - showModalBottomSheet( - context: context, - isDismissible: true, - enableDrag: true, - isScrollControlled: true, - backgroundColor: Colors.black38, - barrierColor: Colors.black12, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), + if (auth != null) + PlatformIconButton( + tooltip: "Open Lyrics", + icon: const Icon(SpotubeIcons.music), + onPressed: () { + showModalBottomSheet( + context: context, + isDismissible: true, + enableDrag: true, + isScrollControlled: true, + backgroundColor: Colors.black38, + barrierColor: Colors.black12, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), ), - ), - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context).size.height * 0.8, - ), - builder: (context) => - const LyricsPage(isModal: true), - ); - }, - ) + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context).size.height * 0.8, + ), + builder: (context) => + const LyricsPage(isModal: true), + ); + }, + ) ], ), PlayerControls(iconColor: paletteColor.bodyTextColor), diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index b696a5f3..f5b06672 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -10,15 +10,9 @@ import 'package:spotube/components/root/sidebar.dart'; import 'package:spotube/components/root/spotube_navigation_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/hooks/use_update_checker.dart'; +import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/provider/downloader_provider.dart'; -const rootPaths = { - 0: "/", - 1: "/search", - 2: "/library", - 3: "/lyrics", -}; - class RootApp extends HookConsumerWidget { final Widget child; const RootApp({ @@ -30,6 +24,14 @@ class RootApp extends HookConsumerWidget { Widget build(BuildContext context, ref) { final index = useState(0); final isMounted = useIsMounted(); + final auth = ref.watch(AuthenticationNotifier.provider); + + final rootPaths = [ + "/", + if (auth != null) "/search", + "/library", + if (auth != null) "/lyrics", + ].asMap(); final downloader = ref.watch(downloaderProvider); useEffect(() { diff --git a/lib/provider/playlist_queue_provider.dart b/lib/provider/playlist_queue_provider.dart index f63ff6d6..013406f9 100644 --- a/lib/provider/playlist_queue_provider.dart +++ b/lib/provider/playlist_queue_provider.dart @@ -152,7 +152,10 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { void configure() async { if (kIsMobile || kIsMacOS) { mobileService = await AudioService.init( - builder: () => MobileAudioService(this), + builder: () => MobileAudioService( + this, + ref.read(VolumeProvider.provider.notifier), + ), config: const AudioServiceConfig( androidNotificationChannelId: 'com.krtirtho.Spotube', androidNotificationChannelName: 'Spotube', diff --git a/lib/services/mobile_audio_service.dart b/lib/services/mobile_audio_service.dart index c21f0c0b..ccc1d3d6 100644 --- a/lib/services/mobile_audio_service.dart +++ b/lib/services/mobile_audio_service.dart @@ -9,22 +9,28 @@ import 'package:spotube/services/audio_player.dart'; class MobileAudioService extends BaseAudioHandler { AudioSession? session; final PlaylistQueueNotifier playlistNotifier; + final VolumeProvider volumeNotifier; PlaylistQueue? get playlist => playlistNotifier.state; - MobileAudioService(this.playlistNotifier) { + MobileAudioService(this.playlistNotifier, this.volumeNotifier) { AudioSession.instance.then((s) { session = s; + session?.configure(const AudioSessionConfiguration.music()); s.interruptionEventStream.listen((event) async { - if (event.type != AudioInterruptionType.duck) { - await playlistNotifier.pause(); + switch (event.type) { + case AudioInterruptionType.duck: + await volumeNotifier.setVolume(event.begin ? 0.5 : 1.0); + break; + case AudioInterruptionType.pause: + case AudioInterruptionType.unknown: + await playlistNotifier.pause(); + break; } }); }); audioPlayer.onPlayerStateChanged.listen((state) async { - if (state != PlayerState.completed) { - playbackState.add(await _transformEvent()); - } + playbackState.add(await _transformEvent()); }); audioPlayer.onPositionChanged.listen((pos) async { @@ -46,6 +52,27 @@ class MobileAudioService extends BaseAudioHandler { @override Future seek(Duration position) => playlistNotifier.seek(position); + @override + Future setShuffleMode(AudioServiceShuffleMode shuffleMode) async { + await super.setShuffleMode(shuffleMode); + + if (shuffleMode == AudioServiceShuffleMode.all) { + playlistNotifier.shuffle(); + } else { + playlistNotifier.unshuffle(); + } + } + + @override + Future setRepeatMode(AudioServiceRepeatMode repeatMode) async { + super.setRepeatMode(repeatMode); + if (repeatMode == AudioServiceRepeatMode.all) { + playlistNotifier.loop(); + } else { + playlistNotifier.unloop(); + } + } + @override Future stop() async { await playlistNotifier.stop(); @@ -71,6 +98,7 @@ class MobileAudioService extends BaseAudioHandler { } Future _transformEvent() async { + final position = (await audioPlayer.getCurrentPosition()) ?? Duration.zero; return PlaybackState( controls: [ MediaControl.skipToPrevious, @@ -85,12 +113,17 @@ class MobileAudioService extends BaseAudioHandler { }, androidCompactActionIndices: const [0, 1, 2], playing: audioPlayer.state == PlayerState.playing, - updatePosition: (await audioPlayer.getCurrentPosition()) ?? Duration.zero, - processingState: audioPlayer.state == PlayerState.paused - ? AudioProcessingState.buffering - : audioPlayer.state == PlayerState.playing - ? AudioProcessingState.ready - : AudioProcessingState.idle, + updatePosition: position, + bufferedPosition: position, + shuffleMode: playlist?.isShuffled == true + ? AudioServiceShuffleMode.all + : AudioServiceShuffleMode.none, + repeatMode: playlist?.isLooping == true + ? AudioServiceRepeatMode.one + : AudioServiceRepeatMode.all, + processingState: playlist?.isLoading == true + ? AudioProcessingState.loading + : AudioProcessingState.ready, ); } }