mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
fix: android audio service and notification and fallback for lyrics when anonymous
This commit is contained in:
parent
f7b12924db
commit
f160ec767d
@ -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),
|
||||||
|
@ -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,8 +106,10 @@ 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
|
||||||
|
.headlineSmall
|
||||||
|
?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: paletteColor.titleTextColor,
|
color: paletteColor.titleTextColor,
|
||||||
),
|
),
|
||||||
@ -117,8 +121,10 @@ 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
|
||||||
|
.titleLarge!
|
||||||
|
.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: paletteColor.bodyTextColor,
|
color: paletteColor.bodyTextColor,
|
||||||
),
|
),
|
||||||
@ -126,8 +132,10 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
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
|
||||||
|
.titleLarge!
|
||||||
|
.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: paletteColor.bodyTextColor,
|
color: paletteColor.bodyTextColor,
|
||||||
),
|
),
|
||||||
@ -193,6 +201,7 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
floatingQueue: false,
|
floatingQueue: false,
|
||||||
extraActions: [
|
extraActions: [
|
||||||
|
if (auth != null)
|
||||||
PlatformIconButton(
|
PlatformIconButton(
|
||||||
tooltip: "Open Lyrics",
|
tooltip: "Open Lyrics",
|
||||||
icon: const Icon(SpotubeIcons.music),
|
icon: const Icon(SpotubeIcons.music),
|
||||||
|
@ -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(() {
|
||||||
|
@ -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',
|
||||||
|
@ -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) {
|
||||||
|
case AudioInterruptionType.duck:
|
||||||
|
await volumeNotifier.setVolume(event.begin ? 0.5 : 1.0);
|
||||||
|
break;
|
||||||
|
case AudioInterruptionType.pause:
|
||||||
|
case AudioInterruptionType.unknown:
|
||||||
await playlistNotifier.pause();
|
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user