fix: android audio service and notification and fallback for lyrics when anonymous

This commit is contained in:
Kingkor Roy Tirtho 2023-03-24 19:55:16 +06:00
parent f7b12924db
commit f160ec767d
5 changed files with 120 additions and 60 deletions

View File

@ -5,12 +5,14 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart'; import 'package:platform_ui/platform_ui.dart';
import 'package:spotube/collections/spotube_icons.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/page_window_title_bar.dart';
import 'package:spotube/components/shared/image/universal_image.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_custom_status_bar_color.dart';
import 'package:spotube/hooks/use_palette_color.dart'; import 'package:spotube/hooks/use_palette_color.dart';
import 'package:spotube/pages/lyrics/plain_lyrics.dart'; import 'package:spotube/pages/lyrics/plain_lyrics.dart';
import 'package:spotube/pages/lyrics/synced_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/provider/playlist_queue_provider.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
@ -39,6 +41,17 @@ class LyricsPage extends HookConsumerWidget {
noSetBGColor: true, 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 = [ Widget body = [
SyncedLyrics(palette: palette, isModal: isModal), SyncedLyrics(palette: palette, isModal: isModal),
PlainLyrics(palette: palette, isModal: isModal), PlainLyrics(palette: palette, isModal: isModal),

View File

@ -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/hooks/use_palette_color.dart';
import 'package:spotube/models/local_track.dart'; import 'package:spotube/models/local_track.dart';
import 'package:spotube/pages/lyrics/lyrics.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/playlist_queue_provider.dart';
import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
@ -29,6 +30,7 @@ class PlayerView extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final auth = ref.watch(AuthenticationNotifier.provider);
final currentTrack = ref.watch(PlaylistQueueNotifier.provider.select( final currentTrack = ref.watch(PlaylistQueueNotifier.provider.select(
(value) => value?.activeTrack, (value) => value?.activeTrack,
)); ));
@ -104,11 +106,13 @@ class PlayerView extends HookConsumerWidget {
height: 30, height: 30,
child: SpotubeMarqueeText( child: SpotubeMarqueeText(
text: currentTrack?.name ?? "Not playing", text: currentTrack?.name ?? "Not playing",
style: style: Theme.of(context)
Theme.of(context).textTheme.headline5?.copyWith( .textTheme
fontWeight: FontWeight.bold, .headlineSmall
color: paletteColor.titleTextColor, ?.copyWith(
), fontWeight: FontWeight.bold,
color: paletteColor.titleTextColor,
),
isHovering: true, isHovering: true,
), ),
), ),
@ -117,20 +121,24 @@ class PlayerView extends HookConsumerWidget {
TypeConversionUtils.artists_X_String<Artist>( TypeConversionUtils.artists_X_String<Artist>(
currentTrack?.artists ?? [], currentTrack?.artists ?? [],
), ),
style: style: Theme.of(context)
Theme.of(context).textTheme.headline6!.copyWith( .textTheme
fontWeight: FontWeight.bold, .titleLarge!
color: paletteColor.bodyTextColor, .copyWith(
), fontWeight: FontWeight.bold,
color: paletteColor.bodyTextColor,
),
) )
else else
TypeConversionUtils.artists_X_ClickableArtists( TypeConversionUtils.artists_X_ClickableArtists(
currentTrack?.artists ?? [], currentTrack?.artists ?? [],
textStyle: textStyle: Theme.of(context)
Theme.of(context).textTheme.headline6!.copyWith( .textTheme
fontWeight: FontWeight.bold, .titleLarge!
color: paletteColor.bodyTextColor, .copyWith(
), fontWeight: FontWeight.bold,
color: paletteColor.bodyTextColor,
),
onRouteChange: (route) { onRouteChange: (route) {
GoRouter.of(context).pop(); GoRouter.of(context).pop();
GoRouter.of(context).push(route); GoRouter.of(context).push(route);
@ -193,32 +201,33 @@ class PlayerView extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
floatingQueue: false, floatingQueue: false,
extraActions: [ extraActions: [
PlatformIconButton( if (auth != null)
tooltip: "Open Lyrics", PlatformIconButton(
icon: const Icon(SpotubeIcons.music), tooltip: "Open Lyrics",
onPressed: () { icon: const Icon(SpotubeIcons.music),
showModalBottomSheet( onPressed: () {
context: context, showModalBottomSheet(
isDismissible: true, context: context,
enableDrag: true, isDismissible: true,
isScrollControlled: true, enableDrag: true,
backgroundColor: Colors.black38, isScrollControlled: true,
barrierColor: Colors.black12, backgroundColor: Colors.black38,
shape: const RoundedRectangleBorder( barrierColor: Colors.black12,
borderRadius: BorderRadius.only( shape: const RoundedRectangleBorder(
topLeft: Radius.circular(20), borderRadius: BorderRadius.only(
topRight: Radius.circular(20), topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
), ),
), constraints: BoxConstraints(
constraints: BoxConstraints( maxHeight:
maxHeight: MediaQuery.of(context).size.height * 0.8,
MediaQuery.of(context).size.height * 0.8, ),
), builder: (context) =>
builder: (context) => const LyricsPage(isModal: true),
const LyricsPage(isModal: true), );
); },
}, )
)
], ],
), ),
PlayerControls(iconColor: paletteColor.bodyTextColor), PlayerControls(iconColor: paletteColor.bodyTextColor),

View File

@ -10,15 +10,9 @@ import 'package:spotube/components/root/sidebar.dart';
import 'package:spotube/components/root/spotube_navigation_bar.dart'; import 'package:spotube/components/root/spotube_navigation_bar.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/hooks/use_update_checker.dart'; import 'package:spotube/hooks/use_update_checker.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/downloader_provider.dart'; import 'package:spotube/provider/downloader_provider.dart';
const rootPaths = {
0: "/",
1: "/search",
2: "/library",
3: "/lyrics",
};
class RootApp extends HookConsumerWidget { class RootApp extends HookConsumerWidget {
final Widget child; final Widget child;
const RootApp({ const RootApp({
@ -30,6 +24,14 @@ class RootApp extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final index = useState(0); final index = useState(0);
final isMounted = useIsMounted(); 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); final downloader = ref.watch(downloaderProvider);
useEffect(() { useEffect(() {

View File

@ -152,7 +152,10 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
void configure() async { void configure() async {
if (kIsMobile || kIsMacOS) { if (kIsMobile || kIsMacOS) {
mobileService = await AudioService.init( mobileService = await AudioService.init(
builder: () => MobileAudioService(this), builder: () => MobileAudioService(
this,
ref.read(VolumeProvider.provider.notifier),
),
config: const AudioServiceConfig( config: const AudioServiceConfig(
androidNotificationChannelId: 'com.krtirtho.Spotube', androidNotificationChannelId: 'com.krtirtho.Spotube',
androidNotificationChannelName: 'Spotube', androidNotificationChannelName: 'Spotube',

View File

@ -9,22 +9,28 @@ import 'package:spotube/services/audio_player.dart';
class MobileAudioService extends BaseAudioHandler { class MobileAudioService extends BaseAudioHandler {
AudioSession? session; AudioSession? session;
final PlaylistQueueNotifier playlistNotifier; final PlaylistQueueNotifier playlistNotifier;
final VolumeProvider volumeNotifier;
PlaylistQueue? get playlist => playlistNotifier.state; PlaylistQueue? get playlist => playlistNotifier.state;
MobileAudioService(this.playlistNotifier) { MobileAudioService(this.playlistNotifier, this.volumeNotifier) {
AudioSession.instance.then((s) { AudioSession.instance.then((s) {
session = s; session = s;
session?.configure(const AudioSessionConfiguration.music());
s.interruptionEventStream.listen((event) async { s.interruptionEventStream.listen((event) async {
if (event.type != AudioInterruptionType.duck) { switch (event.type) {
await playlistNotifier.pause(); 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 { audioPlayer.onPlayerStateChanged.listen((state) async {
if (state != PlayerState.completed) { playbackState.add(await _transformEvent());
playbackState.add(await _transformEvent());
}
}); });
audioPlayer.onPositionChanged.listen((pos) async { audioPlayer.onPositionChanged.listen((pos) async {
@ -46,6 +52,27 @@ class MobileAudioService extends BaseAudioHandler {
@override @override
Future<void> seek(Duration position) => playlistNotifier.seek(position); Future<void> seek(Duration position) => playlistNotifier.seek(position);
@override
Future<void> setShuffleMode(AudioServiceShuffleMode shuffleMode) async {
await super.setShuffleMode(shuffleMode);
if (shuffleMode == AudioServiceShuffleMode.all) {
playlistNotifier.shuffle();
} else {
playlistNotifier.unshuffle();
}
}
@override
Future<void> setRepeatMode(AudioServiceRepeatMode repeatMode) async {
super.setRepeatMode(repeatMode);
if (repeatMode == AudioServiceRepeatMode.all) {
playlistNotifier.loop();
} else {
playlistNotifier.unloop();
}
}
@override @override
Future<void> stop() async { Future<void> stop() async {
await playlistNotifier.stop(); await playlistNotifier.stop();
@ -71,6 +98,7 @@ class MobileAudioService extends BaseAudioHandler {
} }
Future<PlaybackState> _transformEvent() async { Future<PlaybackState> _transformEvent() async {
final position = (await audioPlayer.getCurrentPosition()) ?? Duration.zero;
return PlaybackState( return PlaybackState(
controls: [ controls: [
MediaControl.skipToPrevious, MediaControl.skipToPrevious,
@ -85,12 +113,17 @@ class MobileAudioService extends BaseAudioHandler {
}, },
androidCompactActionIndices: const [0, 1, 2], androidCompactActionIndices: const [0, 1, 2],
playing: audioPlayer.state == PlayerState.playing, playing: audioPlayer.state == PlayerState.playing,
updatePosition: (await audioPlayer.getCurrentPosition()) ?? Duration.zero, updatePosition: position,
processingState: audioPlayer.state == PlayerState.paused bufferedPosition: position,
? AudioProcessingState.buffering shuffleMode: playlist?.isShuffled == true
: audioPlayer.state == PlayerState.playing ? AudioServiceShuffleMode.all
? AudioProcessingState.ready : AudioServiceShuffleMode.none,
: AudioProcessingState.idle, repeatMode: playlist?.isLooping == true
? AudioServiceRepeatMode.one
: AudioServiceRepeatMode.all,
processingState: playlist?.isLoading == true
? AudioProcessingState.loading
: AudioProcessingState.ready,
); );
} }
} }