diff --git a/lib/hooks/configurators/use_deep_linking.dart b/lib/hooks/configurators/use_deep_linking.dart index 90d062dc..0bb27a11 100644 --- a/lib/hooks/configurators/use_deep_linking.dart +++ b/lib/hooks/configurators/use_deep_linking.dart @@ -7,6 +7,7 @@ import 'package:spotube/collections/routes.dart'; import 'package:spotube/provider/spotify_provider.dart'; import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/utils/platform.dart'; final appLinks = AppLinks(); @@ -61,30 +62,34 @@ void useDeepLinking(WidgetRef ref) { } final subscription = linkStream.listen((uri) async { - final startSegment = uri.split(":").take(2).join(":"); - final endSegment = uri.split(":").last; + try { + final startSegment = uri.split(":").take(2).join(":"); + final endSegment = uri.split(":").last; - switch (startSegment) { - case "spotify:album": - await router.push( - "/album/$endSegment", - extra: await spotify.albums.get(endSegment), - ); - break; - case "spotify:artist": - await router.push("/artist/$endSegment"); - break; - case "spotify:track": - await router.push("/track/$endSegment"); - break; - case "spotify:playlist": - await router.push( - "/playlist/$endSegment", - extra: await spotify.playlists.get(endSegment), - ); - break; - default: - break; + switch (startSegment) { + case "spotify:album": + await router.push( + "/album/$endSegment", + extra: await spotify.albums.get(endSegment), + ); + break; + case "spotify:artist": + await router.push("/artist/$endSegment"); + break; + case "spotify:track": + await router.push("/track/$endSegment"); + break; + case "spotify:playlist": + await router.push( + "/playlist/$endSegment", + extra: await spotify.playlists.get(endSegment), + ); + break; + default: + break; + } + } catch (e, stack) { + AppLogger.reportError(e, stack); } }); diff --git a/lib/modules/lyrics/use_synced_lyrics.dart b/lib/modules/lyrics/use_synced_lyrics.dart index 7a171473..cf929226 100644 --- a/lib/modules/lyrics/use_synced_lyrics.dart +++ b/lib/modules/lyrics/use_synced_lyrics.dart @@ -1,6 +1,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/logger/logger.dart'; int useSyncedLyrics( WidgetRef ref, @@ -13,8 +14,12 @@ int useSyncedLyrics( useEffect(() { return stream.listen((pos) { - if (lyricsMap.containsKey(pos.inSeconds + delay)) { - currentTime.value = pos.inSeconds + delay; + try { + if (lyricsMap.containsKey(pos.inSeconds + delay)) { + currentTime.value = pos.inSeconds + delay; + } + } catch (e, stack) { + AppLogger.reportError(e, stack); } }).cancel; }, [lyricsMap, delay]); diff --git a/lib/pages/lyrics/synced_lyrics.dart b/lib/pages/lyrics/synced_lyrics.dart index 643c1064..d7f7685a 100644 --- a/lib/pages/lyrics/synced_lyrics.dart +++ b/lib/pages/lyrics/synced_lyrics.dart @@ -17,6 +17,7 @@ import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:stroke_text/stroke_text.dart'; @@ -80,12 +81,16 @@ class SyncedLyrics extends HookConsumerWidget { StreamSubscription? subscription; WidgetsBinding.instance.addPostFrameCallback((_) { subscription = audioPlayer.positionStream.listen((event) { - if (event > Duration.zero) return; - controller.animateTo( - 0, - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - ); + try { + if (event > Duration.zero) return; + controller.animateTo( + 0, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + } catch (e, stack) { + AppLogger.reportError(e, stack); + } }); }); diff --git a/lib/pages/settings/logs.dart b/lib/pages/settings/logs.dart index 91087b7e..6ccbe32f 100644 --- a/lib/pages/settings/logs.dart +++ b/lib/pages/settings/logs.dart @@ -7,6 +7,7 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/logs/logs_provider.dart'; +import 'package:spotube/services/logger/logger.dart'; class LogsPage extends HookConsumerWidget { static const name = "logs"; @@ -40,6 +41,17 @@ class LogsPage extends HookConsumerWidget { } }, ), + IconButton( + icon: const Icon(SpotubeIcons.trash), + iconSize: 16, + onPressed: () async { + ref.invalidate(logsProvider); + + final logsFile = await AppLogger.getLogsPath(); + + await logsFile.writeAsString(""); + }, + ) ], ), body: SafeArea( diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index c40f683d..50e90dcd 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -13,6 +13,7 @@ import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/discord_provider.dart'; import 'package:spotube/provider/server/sourced_track.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/logger/logger.dart'; class AudioPlayerNotifier extends Notifier { BlackListNotifier get _blacklist => ref.read(blacklistProvider.notifier); @@ -141,36 +142,52 @@ class AudioPlayerNotifier extends Notifier { build() { final subscriptions = [ audioPlayer.playingStream.listen((playing) async { - state = state.copyWith(playing: playing); + try { + state = state.copyWith(playing: playing); - await _updatePlayerState( - AudioPlayerStateTableCompanion( - playing: Value(playing), - ), - ); + await _updatePlayerState( + AudioPlayerStateTableCompanion( + playing: Value(playing), + ), + ); + } catch (e, stack) { + AppLogger.reportError(e, stack); + } }), audioPlayer.loopModeStream.listen((loopMode) async { - state = state.copyWith(loopMode: loopMode); + try { + state = state.copyWith(loopMode: loopMode); - await _updatePlayerState( - AudioPlayerStateTableCompanion( - loopMode: Value(loopMode), - ), - ); + await _updatePlayerState( + AudioPlayerStateTableCompanion( + loopMode: Value(loopMode), + ), + ); + } catch (e, stack) { + AppLogger.reportError(e, stack); + } }), audioPlayer.shuffledStream.listen((shuffled) async { - state = state.copyWith(shuffled: shuffled); + try { + state = state.copyWith(shuffled: shuffled); - await _updatePlayerState( - AudioPlayerStateTableCompanion( - shuffled: Value(shuffled), - ), - ); + await _updatePlayerState( + AudioPlayerStateTableCompanion( + shuffled: Value(shuffled), + ), + ); + } catch (e, stack) { + AppLogger.reportError(e, stack); + } }), audioPlayer.playlistStream.listen((playlist) async { - state = state.copyWith(playlist: playlist); + try { + state = state.copyWith(playlist: playlist); - await _updatePlaylist(playlist); + await _updatePlaylist(playlist); + } catch (e, stack) { + AppLogger.reportError(e, stack); + } }), ]; diff --git a/lib/provider/audio_player/audio_player_streams.dart b/lib/provider/audio_player/audio_player_streams.dart index 845f12ea..08550844 100644 --- a/lib/provider/audio_player/audio_player_streams.dart +++ b/lib/provider/audio_player/audio_player_streams.dart @@ -73,25 +73,33 @@ class AudioPlayerStreamListeners { StreamSubscription subscribeToPlaylist() { return audioPlayer.playlistStream.listen((mpvPlaylist) { - notificationService.addTrack(audioPlayerState.activeTrack!); - discord.updatePresence(audioPlayerState.activeTrack!); - updatePalette(); + try { + notificationService.addTrack(audioPlayerState.activeTrack!); + discord.updatePresence(audioPlayerState.activeTrack!); + updatePalette(); + } catch (e, stack) { + AppLogger.reportError(e, stack); + } }); } StreamSubscription subscribeToSkipSponsor() { return audioPlayer.positionStream.listen((position) async { - final currentSegments = await ref.read(segmentProvider.future); + try { + final currentSegments = await ref.read(segmentProvider.future); - if (currentSegments?.segments.isNotEmpty != true || - position < const Duration(seconds: 3)) return; + if (currentSegments?.segments.isNotEmpty != true || + position < const Duration(seconds: 3)) return; - for (final segment in currentSegments!.segments) { - final seconds = position.inSeconds; + for (final segment in currentSegments!.segments) { + final seconds = position.inSeconds; - if (seconds < segment.start || seconds >= segment.end) continue; + if (seconds < segment.start || seconds >= segment.end) continue; - await audioPlayer.seek(Duration(seconds: segment.end + 1)); + await audioPlayer.seek(Duration(seconds: segment.end + 1)); + } + } catch (e, stack) { + AppLogger.reportError(e, stack); } }); } @@ -122,23 +130,28 @@ class AudioPlayerStreamListeners { StreamSubscription subscribeToPosition() { String lastTrack = ""; // used to prevent multiple calls to the same track return audioPlayer.positionStream.listen((event) async { - if (event < const Duration(seconds: 3) || - audioPlayerState.playlist.index == -1 || - audioPlayerState.playlist.index == - audioPlayerState.tracks.length - 1) { - return; - } - final nextTrack = SpotubeMedia.fromMedia(audioPlayerState.playlist.medias - .elementAt(audioPlayerState.playlist.index + 1)); - - if (lastTrack == nextTrack.track.id || nextTrack.track is LocalTrack) { - return; - } - try { - await ref.read(sourcedTrackProvider(nextTrack).future); - } finally { - lastTrack = nextTrack.track.id!; + if (event < const Duration(seconds: 3) || + audioPlayerState.playlist.index == -1 || + audioPlayerState.playlist.index == + audioPlayerState.tracks.length - 1) { + return; + } + final nextTrack = SpotubeMedia.fromMedia(audioPlayerState + .playlist.medias + .elementAt(audioPlayerState.playlist.index + 1)); + + if (lastTrack == nextTrack.track.id || nextTrack.track is LocalTrack) { + return; + } + + try { + await ref.read(sourcedTrackProvider(nextTrack).future); + } finally { + lastTrack = nextTrack.track.id!; + } + } catch (e, stack) { + AppLogger.reportError(e, stack); } }); } diff --git a/lib/provider/connect/clients.dart b/lib/provider/connect/clients.dart index d92ff8d3..51578a7b 100644 --- a/lib/provider/connect/clients.dart +++ b/lib/provider/connect/clients.dart @@ -1,6 +1,7 @@ import 'package:bonsoir/bonsoir.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/services/device_info/device_info.dart'; +import 'package:spotube/services/logger/logger.dart'; class ConnectClientsState { final List services; @@ -37,42 +38,47 @@ class ConnectClientsNotifier extends AsyncNotifier { final subscription = discovery.eventStream?.listen((event) { // ignore device itself - if (event.service?.attributes["deviceId"] == deviceId) { - return; - } + try { + if (event.service?.attributes["deviceId"] == deviceId) { + return; + } - switch (event.type) { - case BonsoirDiscoveryEventType.discoveryServiceFound: - state = AsyncData(state.value!.copyWith( - services: [ - ...?state.value?.services, - event.service!, - ], - )); - break; - case BonsoirDiscoveryEventType.discoveryServiceResolved: - state = AsyncData( - state.value!.copyWith( - resolvedService: event.service as ResolvedBonsoirService, - ), - ); - break; - case BonsoirDiscoveryEventType.discoveryServiceLost: - state = AsyncData( - ConnectClientsState( - services: state.value!.services - .where((s) => s.name != event.service!.name) - .toList(), - discovery: state.value!.discovery, - resolvedService: state.value?.resolvedService != null && - event.service?.name == state.value?.resolvedService?.name - ? null - : state.value!.resolvedService, - ), - ); - break; - default: - break; + switch (event.type) { + case BonsoirDiscoveryEventType.discoveryServiceFound: + state = AsyncData(state.value!.copyWith( + services: [ + ...?state.value?.services, + event.service!, + ], + )); + break; + case BonsoirDiscoveryEventType.discoveryServiceResolved: + state = AsyncData( + state.value!.copyWith( + resolvedService: event.service as ResolvedBonsoirService, + ), + ); + break; + case BonsoirDiscoveryEventType.discoveryServiceLost: + state = AsyncData( + ConnectClientsState( + services: state.value!.services + .where((s) => s.name != event.service!.name) + .toList(), + discovery: state.value!.discovery, + resolvedService: state.value?.resolvedService != null && + event.service?.name == + state.value?.resolvedService?.name + ? null + : state.value!.resolvedService, + ), + ); + break; + default: + break; + } + } catch (e, stack) { + AppLogger.reportError(e, stack); } }); diff --git a/lib/provider/discord_provider.dart b/lib/provider/discord_provider.dart index 8f8cb375..4db1835f 100644 --- a/lib/provider/discord_provider.dart +++ b/lib/provider/discord_provider.dart @@ -7,11 +7,14 @@ import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/utils/platform.dart'; class DiscordNotifier extends AsyncNotifier { @override FutureOr build() async { + if (!kIsDesktop) return; + final enabled = ref.watch( userPreferencesProvider.select((s) => s.discordPresence && kIsDesktop)); @@ -19,26 +22,38 @@ class DiscordNotifier extends AsyncNotifier { final subscriptions = [ FlutterDiscordRPC.instance.isConnectedStream.listen((connected) async { - final playback = ref.read(audioPlayerProvider); - if (connected && playback.activeTrack != null) { - await updatePresence(playback.activeTrack!); + try { + final playback = ref.read(audioPlayerProvider); + if (connected && playback.activeTrack != null) { + await updatePresence(playback.activeTrack!); + } + } catch (e, stack) { + AppLogger.reportError(e, stack); } }), audioPlayer.playerStateStream.listen((state) async { - final playback = ref.read(audioPlayerProvider); - if (playback.activeTrack == null) return; + try { + final playback = ref.read(audioPlayerProvider); + if (playback.activeTrack == null) return; - await updatePresence(ref.read(audioPlayerProvider).activeTrack!); + await updatePresence(ref.read(audioPlayerProvider).activeTrack!); + } catch (e, stack) { + AppLogger.reportError(e, stack); + } }), audioPlayer.positionStream.listen((position) async { - final playback = ref.read(audioPlayerProvider); - if (playback.activeTrack != null) { - final diff = position.inMilliseconds - lastPosition.inMilliseconds; - if (diff > 500 || diff < -500) { - await updatePresence(ref.read(audioPlayerProvider).activeTrack!); + try { + final playback = ref.read(audioPlayerProvider); + if (playback.activeTrack != null) { + final diff = position.inMilliseconds - lastPosition.inMilliseconds; + if (diff > 500 || diff < -500) { + await updatePresence(ref.read(audioPlayerProvider).activeTrack!); + } } + lastPosition = position; + } catch (e, stack) { + AppLogger.reportError(e, stack); } - lastPosition = position; }) ]; @@ -59,6 +74,7 @@ class DiscordNotifier extends AsyncNotifier { } Future updatePresence(Track track) async { + if (!kIsDesktop) return; final artistNames = track.artists?.asString(); final isPlaying = audioPlayer.isPlaying; final position = audioPlayer.position; @@ -92,10 +108,12 @@ class DiscordNotifier extends AsyncNotifier { } Future clear() async { + if (!kIsDesktop) return; await FlutterDiscordRPC.instance.clearActivity(); } Future close() async { + if (!kIsDesktop) return; await FlutterDiscordRPC.instance.disconnect(); } } diff --git a/lib/provider/download_manager_provider.dart b/lib/provider/download_manager_provider.dart index ec6ffc18..8c9ffadf 100644 --- a/lib/provider/download_manager_provider.dart +++ b/lib/provider/download_manager_provider.dart @@ -23,68 +23,72 @@ class DownloadManagerProvider extends ChangeNotifier { $backHistory = {}, dl = DownloadManager() { dl.statusStream.listen((event) async { - final (:request, :status) = event; + try { + final (:request, :status) = event; - final track = $history.firstWhereOrNull( - (element) => element.getUrlOfCodec(downloadCodec) == request.url, - ); - if (track == null) return; + final track = $history.firstWhereOrNull( + (element) => element.getUrlOfCodec(downloadCodec) == request.url, + ); + if (track == null) return; - final savePath = getTrackFileUrl(track); - // related to onFileExists - final oldFile = File("$savePath.old"); + final savePath = getTrackFileUrl(track); + // related to onFileExists + final oldFile = File("$savePath.old"); - // if download failed and old file exists, rename it back - if ((status == DownloadStatus.failed || - status == DownloadStatus.canceled) && - await oldFile.exists()) { - await oldFile.rename(savePath); + // if download failed and old file exists, rename it back + if ((status == DownloadStatus.failed || + status == DownloadStatus.canceled) && + await oldFile.exists()) { + await oldFile.rename(savePath); + } + if (status != DownloadStatus.completed || + //? WebA audiotagging is not supported yet + //? Although in future by converting weba to opus & then tagging it + //? is possible using vorbis comments + downloadCodec == SourceCodecs.weba) return; + + final file = File(request.path); + + if (await oldFile.exists()) { + await oldFile.delete(); + } + + final imageBytes = await downloadImage( + (track.album?.images).asUrlString( + placeholder: ImagePlaceholder.albumArt, + index: 1, + ), + ); + + final metadata = Metadata( + title: track.name, + artist: track.artists?.map((a) => a.name).join(", "), + album: track.album?.name, + albumArtist: track.artists?.map((a) => a.name).join(", "), + year: track.album?.releaseDate != null + ? int.tryParse(track.album!.releaseDate!.split("-").first) ?? 1969 + : 1969, + trackNumber: track.trackNumber, + discNumber: track.discNumber, + durationMs: track.durationMs?.toDouble() ?? 0.0, + fileSize: BigInt.from(await file.length()), + trackTotal: track.album?.tracks?.length ?? 0, + picture: imageBytes != null + ? Picture( + data: imageBytes, + // Spotify images are always JPEGs + mimeType: 'image/jpeg', + ) + : null, + ); + + await MetadataGod.writeMetadata( + file: file.path, + metadata: metadata, + ); + } catch (e, stack) { + AppLogger.reportError(e, stack); } - if (status != DownloadStatus.completed || - //? WebA audiotagging is not supported yet - //? Although in future by converting weba to opus & then tagging it - //? is possible using vorbis comments - downloadCodec == SourceCodecs.weba) return; - - final file = File(request.path); - - if (await oldFile.exists()) { - await oldFile.delete(); - } - - final imageBytes = await downloadImage( - (track.album?.images).asUrlString( - placeholder: ImagePlaceholder.albumArt, - index: 1, - ), - ); - - final metadata = Metadata( - title: track.name, - artist: track.artists?.map((a) => a.name).join(", "), - album: track.album?.name, - albumArtist: track.artists?.map((a) => a.name).join(", "), - year: track.album?.releaseDate != null - ? int.tryParse(track.album!.releaseDate!.split("-").first) ?? 1969 - : 1969, - trackNumber: track.trackNumber, - discNumber: track.discNumber, - durationMs: track.durationMs?.toDouble() ?? 0.0, - fileSize: BigInt.from(await file.length()), - trackTotal: track.album?.tracks?.length ?? 0, - picture: imageBytes != null - ? Picture( - data: imageBytes, - // Spotify images are always JPEGs - mimeType: 'image/jpeg', - ) - : null, - ); - - await MetadataGod.writeMetadata( - file: file.path, - metadata: metadata, - ); }); } diff --git a/lib/provider/local_tracks/local_tracks_provider.dart b/lib/provider/local_tracks/local_tracks_provider.dart index ca22d841..513fd9b9 100644 --- a/lib/provider/local_tracks/local_tracks_provider.dart +++ b/lib/provider/local_tracks/local_tracks_provider.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -72,39 +73,35 @@ final localTracksProvider = } } - final List> filesWithMetadata = []; + final List> filesWithMetadata = await Future.wait( + entities.map((file) async { + try { + final metadata = await MetadataGod.readMetadata(file: file.path); - for (final file in entities) { - try { - final metadata = await MetadataGod.readMetadata(file: file.path); + final imageFile = File(join( + (await getTemporaryDirectory()).path, + "spotube", + basenameWithoutExtension(file.path) + + imgMimeToExt[metadata.picture?.mimeType ?? "image/jpeg"]!, + )); + if (!await imageFile.exists() && metadata.picture != null) { + await imageFile.create(recursive: true); + await imageFile.writeAsBytes( + metadata.picture?.data ?? [], + mode: FileMode.writeOnly, + ); + } - await Future.delayed(const Duration(milliseconds: 50)); - - final imageFile = File(join( - (await getTemporaryDirectory()).path, - "spotube", - basenameWithoutExtension(file.path) + - imgMimeToExt[metadata.picture?.mimeType ?? "image/jpeg"]!, - )); - if (!await imageFile.exists() && metadata.picture != null) { - await imageFile.create(recursive: true); - await imageFile.writeAsBytes( - metadata.picture?.data ?? [], - mode: FileMode.writeOnly, - ); + return {"metadata": metadata, "file": file, "art": imageFile.path}; + } catch (e, stack) { + if (e case FrbException() || TimeoutException()) { + return {"file": file}; + } + AppLogger.reportError(e, stack); + return null; } - - filesWithMetadata.add( - {"metadata": metadata, "file": file, "art": imageFile.path}, - ); - } catch (e, stack) { - if (e case FrbException() || TimeoutException()) { - filesWithMetadata.add({"file": file}); - } - AppLogger.reportError(e, stack); - continue; - } - } + }), + ).then((value) => value.whereNotNull().toList()); final tracksFromMetadata = filesWithMetadata .map( diff --git a/lib/provider/scrobbler/scrobbler.dart b/lib/provider/scrobbler/scrobbler.dart index 76559d69..8aff0438 100644 --- a/lib/provider/scrobbler/scrobbler.dart +++ b/lib/provider/scrobbler/scrobbler.dart @@ -23,19 +23,23 @@ class ScrobblerNotifier extends AsyncNotifier { final subscription = database.select(database.scrobblerTable).watch().listen((event) async { - if (event.isNotEmpty) { - state = await AsyncValue.guard( - () async => Scrobblenaut( - lastFM: await LastFM.authenticateWithPasswordHash( - apiKey: Env.lastFmApiKey, - apiSecret: Env.lastFmApiSecret, - username: event.first.username, - passwordHash: event.first.passwordHash.value, + try { + if (event.isNotEmpty) { + state = await AsyncValue.guard( + () async => Scrobblenaut( + lastFM: await LastFM.authenticateWithPasswordHash( + apiKey: Env.lastFmApiKey, + apiSecret: Env.lastFmApiSecret, + username: event.first.username, + passwordHash: event.first.passwordHash.value, + ), ), - ), - ); - } else { - state = const AsyncValue.data(null); + ); + } else { + state = const AsyncValue.data(null); + } + } catch (e, stack) { + AppLogger.reportError(e, stack); } }); diff --git a/lib/provider/user_preferences/user_preferences_provider.dart b/lib/provider/user_preferences/user_preferences_provider.dart index a421e7d0..23479b71 100644 --- a/lib/provider/user_preferences/user_preferences_provider.dart +++ b/lib/provider/user_preferences/user_preferences_provider.dart @@ -10,6 +10,7 @@ import 'package:spotube/provider/audio_player/audio_player_streams.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/palette_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; @@ -41,15 +42,21 @@ class UserPreferencesNotifier extends Notifier { ..where((tbl) => tbl.id.equals(0))) .watchSingle() .listen((event) async { - state = event; + try { + state = event; - if (kIsDesktop) { - await windowManager.setTitleBarStyle( - state.systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden, - ); + if (kIsDesktop) { + await windowManager.setTitleBarStyle( + state.systemTitleBar + ? TitleBarStyle.normal + : TitleBarStyle.hidden, + ); + } + + await audioPlayer.setAudioNormalization(state.normalizeAudio); + } catch (e, stack) { + AppLogger.reportError(e, stack); } - - await audioPlayer.setAudioNormalization(state.normalizeAudio); }); ref.onDispose(() { diff --git a/lib/services/audio_services/audio_services.dart b/lib/services/audio_services/audio_services.dart index d1820a00..5d8591ee 100644 --- a/lib/services/audio_services/audio_services.dart +++ b/lib/services/audio_services/audio_services.dart @@ -5,6 +5,7 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_services/mobile_audio_service.dart'; import 'package:spotube/services/audio_services/windows_audio_service.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; @@ -30,8 +31,8 @@ class AudioServices with WidgetsBindingObserver { kIsLinux ? 'spotube' : 'com.krtirtho.Spotube', androidNotificationChannelName: 'Spotube', androidNotificationOngoing: false, - androidNotificationIcon: "drawable/ic_launcher_monochrome", androidStopForegroundOnPause: false, + androidNotificationIcon: "drawable/ic_launcher_monochrome", androidNotificationChannelDescription: "Spotube Media Controls", ), ) diff --git a/lib/services/audio_services/mobile_audio_service.dart b/lib/services/audio_services/mobile_audio_service.dart index cdd16138..84467948 100644 --- a/lib/services/audio_services/mobile_audio_service.dart +++ b/lib/services/audio_services/mobile_audio_service.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; @@ -6,6 +7,8 @@ import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:media_kit/media_kit.dart' hide Track; +import 'package:spotube/services/logger/logger.dart'; +import 'package:spotube/utils/platform.dart'; class MobileAudioService extends BaseAudioHandler { AudioSession? session; @@ -120,35 +123,40 @@ class MobileAudioService extends BaseAudioHandler { @override Future onTaskRemoved() async { await audioPlayerNotifier.stop(); - return super.onTaskRemoved(); + if (kIsAndroid) exit(0); } Future _transformEvent() async { - return PlaybackState( - controls: [ - MediaControl.skipToPrevious, - audioPlayer.isPlaying ? MediaControl.pause : MediaControl.play, - MediaControl.skipToNext, - MediaControl.stop, - ], - systemActions: { - MediaAction.seek, - }, - androidCompactActionIndices: const [0, 1, 2], - playing: audioPlayer.isPlaying, - updatePosition: audioPlayer.position, - bufferedPosition: audioPlayer.bufferedPosition, - shuffleMode: audioPlayer.isShuffled == true - ? AudioServiceShuffleMode.all - : AudioServiceShuffleMode.none, - repeatMode: switch (audioPlayer.loopMode) { - PlaylistMode.loop => AudioServiceRepeatMode.all, - PlaylistMode.single => AudioServiceRepeatMode.one, - _ => AudioServiceRepeatMode.none, - }, - processingState: audioPlayer.isBuffering - ? AudioProcessingState.loading - : AudioProcessingState.ready, - ); + try { + return PlaybackState( + controls: [ + MediaControl.skipToPrevious, + audioPlayer.isPlaying ? MediaControl.pause : MediaControl.play, + MediaControl.skipToNext, + MediaControl.stop, + ], + systemActions: { + MediaAction.seek, + }, + androidCompactActionIndices: const [0, 1, 2], + playing: audioPlayer.isPlaying, + updatePosition: audioPlayer.position, + bufferedPosition: audioPlayer.bufferedPosition, + shuffleMode: audioPlayer.isShuffled == true + ? AudioServiceShuffleMode.all + : AudioServiceShuffleMode.none, + repeatMode: switch (audioPlayer.loopMode) { + PlaylistMode.loop => AudioServiceRepeatMode.all, + PlaylistMode.single => AudioServiceRepeatMode.one, + _ => AudioServiceRepeatMode.none, + }, + processingState: audioPlayer.isBuffering + ? AudioProcessingState.loading + : AudioProcessingState.ready, + ); + } catch (e, stack) { + AppLogger.reportError(e, stack); + rethrow; + } } } diff --git a/lib/services/connectivity_adapter.dart b/lib/services/connectivity_adapter.dart index 1a3835ee..86765671 100644 --- a/lib/services/connectivity_adapter.dart +++ b/lib/services/connectivity_adapter.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/widgets.dart'; +import 'package:spotube/services/logger/logger.dart'; class ConnectionCheckerService with WidgetsBindingObserver { final _connectionStreamController = StreamController.broadcast(); @@ -16,17 +17,21 @@ class ConnectionCheckerService with WidgetsBindingObserver { Timer? timer; onConnectivityChanged.listen((connected) { - if (!connected && timer == null) { - timer = Timer.periodic(const Duration(seconds: 30), (timer) async { - if (WidgetsBinding.instance.lifecycleState == - AppLifecycleState.paused) { - return; - } - await isConnected; - }); - } else { - timer?.cancel(); - timer = null; + try { + if (!connected && timer == null) { + timer = Timer.periodic(const Duration(seconds: 30), (timer) async { + if (WidgetsBinding.instance.lifecycleState == + AppLifecycleState.paused) { + return; + } + await isConnected; + }); + } else { + timer?.cancel(); + timer = null; + } + } catch (e, stack) { + AppLogger.reportError(e, stack); } }); }