fix: local tracks takes time to load

This commit is contained in:
Kingkor Roy Tirtho 2024-08-15 22:45:22 +06:00
parent 9294858fb6
commit 470addca83
15 changed files with 377 additions and 270 deletions

View File

@ -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,6 +62,7 @@ void useDeepLinking(WidgetRef ref) {
}
final subscription = linkStream.listen((uri) async {
try {
final startSegment = uri.split(":").take(2).join(":");
final endSegment = uri.split(":").last;
@ -86,6 +88,9 @@ void useDeepLinking(WidgetRef ref) {
default:
break;
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
return () {

View File

@ -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,9 +14,13 @@ int useSyncedLyrics(
useEffect(() {
return stream.listen((pos) {
try {
if (lyricsMap.containsKey(pos.inSeconds + delay)) {
currentTime.value = pos.inSeconds + delay;
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
}).cancel;
}, [lyricsMap, delay]);

View File

@ -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) {
try {
if (event > Duration.zero) return;
controller.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
});

View File

@ -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(

View File

@ -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<AudioPlayerState> {
BlackListNotifier get _blacklist => ref.read(blacklistProvider.notifier);
@ -141,6 +142,7 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
build() {
final subscriptions = [
audioPlayer.playingStream.listen((playing) async {
try {
state = state.copyWith(playing: playing);
await _updatePlayerState(
@ -148,8 +150,12 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
playing: Value(playing),
),
);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
}),
audioPlayer.loopModeStream.listen((loopMode) async {
try {
state = state.copyWith(loopMode: loopMode);
await _updatePlayerState(
@ -157,8 +163,12 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
loopMode: Value(loopMode),
),
);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
}),
audioPlayer.shuffledStream.listen((shuffled) async {
try {
state = state.copyWith(shuffled: shuffled);
await _updatePlayerState(
@ -166,11 +176,18 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
shuffled: Value(shuffled),
),
);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
}),
audioPlayer.playlistStream.listen((playlist) async {
try {
state = state.copyWith(playlist: playlist);
await _updatePlaylist(playlist);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
}),
];

View File

@ -73,14 +73,19 @@ class AudioPlayerStreamListeners {
StreamSubscription subscribeToPlaylist() {
return audioPlayer.playlistStream.listen((mpvPlaylist) {
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 {
try {
final currentSegments = await ref.read(segmentProvider.future);
if (currentSegments?.segments.isNotEmpty != true ||
@ -93,6 +98,9 @@ class AudioPlayerStreamListeners {
await audioPlayer.seek(Duration(seconds: segment.end + 1));
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
}
@ -122,13 +130,15 @@ class AudioPlayerStreamListeners {
StreamSubscription subscribeToPosition() {
String lastTrack = ""; // used to prevent multiple calls to the same track
return audioPlayer.positionStream.listen((event) async {
try {
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
final nextTrack = SpotubeMedia.fromMedia(audioPlayerState
.playlist.medias
.elementAt(audioPlayerState.playlist.index + 1));
if (lastTrack == nextTrack.track.id || nextTrack.track is LocalTrack) {
@ -140,6 +150,9 @@ class AudioPlayerStreamListeners {
} finally {
lastTrack = nextTrack.track.id!;
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
}

View File

@ -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<BonsoirService> services;
@ -37,6 +38,7 @@ class ConnectClientsNotifier extends AsyncNotifier<ConnectClientsState> {
final subscription = discovery.eventStream?.listen((event) {
// ignore device itself
try {
if (event.service?.attributes["deviceId"] == deviceId) {
return;
}
@ -65,7 +67,8 @@ class ConnectClientsNotifier extends AsyncNotifier<ConnectClientsState> {
.toList(),
discovery: state.value!.discovery,
resolvedService: state.value?.resolvedService != null &&
event.service?.name == state.value?.resolvedService?.name
event.service?.name ==
state.value?.resolvedService?.name
? null
: state.value!.resolvedService,
),
@ -74,6 +77,9 @@ class ConnectClientsNotifier extends AsyncNotifier<ConnectClientsState> {
default:
break;
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
ref.onDispose(() {

View File

@ -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<void> {
@override
FutureOr<void> build() async {
if (!kIsDesktop) return;
final enabled = ref.watch(
userPreferencesProvider.select((s) => s.discordPresence && kIsDesktop));
@ -19,18 +22,27 @@ class DiscordNotifier extends AsyncNotifier<void> {
final subscriptions = [
FlutterDiscordRPC.instance.isConnectedStream.listen((connected) async {
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 {
try {
final playback = ref.read(audioPlayerProvider);
if (playback.activeTrack == null) return;
await updatePresence(ref.read(audioPlayerProvider).activeTrack!);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
}),
audioPlayer.positionStream.listen((position) async {
try {
final playback = ref.read(audioPlayerProvider);
if (playback.activeTrack != null) {
final diff = position.inMilliseconds - lastPosition.inMilliseconds;
@ -39,6 +51,9 @@ class DiscordNotifier extends AsyncNotifier<void> {
}
}
lastPosition = position;
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
})
];
@ -59,6 +74,7 @@ class DiscordNotifier extends AsyncNotifier<void> {
}
Future<void> 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<void> {
}
Future<void> clear() async {
if (!kIsDesktop) return;
await FlutterDiscordRPC.instance.clearActivity();
}
Future<void> close() async {
if (!kIsDesktop) return;
await FlutterDiscordRPC.instance.disconnect();
}
}

View File

@ -23,6 +23,7 @@ class DownloadManagerProvider extends ChangeNotifier {
$backHistory = <Track>{},
dl = DownloadManager() {
dl.statusStream.listen((event) async {
try {
final (:request, :status) = event;
final track = $history.firstWhereOrNull(
@ -85,6 +86,9 @@ class DownloadManagerProvider extends ChangeNotifier {
file: file.path,
metadata: metadata,
);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
}

View File

@ -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,14 +73,11 @@ final localTracksProvider =
}
}
final List<Map<dynamic, dynamic>> filesWithMetadata = [];
for (final file in entities) {
final List<Map<dynamic, dynamic>> filesWithMetadata = await Future.wait(
entities.map((file) async {
try {
final metadata = await MetadataGod.readMetadata(file: file.path);
await Future.delayed(const Duration(milliseconds: 50));
final imageFile = File(join(
(await getTemporaryDirectory()).path,
"spotube",
@ -94,17 +92,16 @@ final localTracksProvider =
);
}
filesWithMetadata.add(
{"metadata": metadata, "file": file, "art": imageFile.path},
);
return {"metadata": metadata, "file": file, "art": imageFile.path};
} catch (e, stack) {
if (e case FrbException() || TimeoutException()) {
filesWithMetadata.add({"file": file});
return {"file": file};
}
AppLogger.reportError(e, stack);
continue;
}
return null;
}
}),
).then((value) => value.whereNotNull().toList());
final tracksFromMetadata = filesWithMetadata
.map(

View File

@ -23,6 +23,7 @@ class ScrobblerNotifier extends AsyncNotifier<Scrobblenaut?> {
final subscription =
database.select(database.scrobblerTable).watch().listen((event) async {
try {
if (event.isNotEmpty) {
state = await AsyncValue.guard(
() async => Scrobblenaut(
@ -37,6 +38,9 @@ class ScrobblerNotifier extends AsyncNotifier<Scrobblenaut?> {
} else {
state = const AsyncValue.data(null);
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
final scrobblerSubscription =

View File

@ -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<PreferencesTableData> {
..where((tbl) => tbl.id.equals(0)))
.watchSingle()
.listen((event) async {
try {
state = event;
if (kIsDesktop) {
await windowManager.setTitleBarStyle(
state.systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
state.systemTitleBar
? TitleBarStyle.normal
: TitleBarStyle.hidden,
);
}
await audioPlayer.setAudioNormalization(state.normalizeAudio);
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
ref.onDispose(() {

View File

@ -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",
),
)

View File

@ -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,10 +123,11 @@ class MobileAudioService extends BaseAudioHandler {
@override
Future<void> onTaskRemoved() async {
await audioPlayerNotifier.stop();
return super.onTaskRemoved();
if (kIsAndroid) exit(0);
}
Future<PlaybackState> _transformEvent() async {
try {
return PlaybackState(
controls: [
MediaControl.skipToPrevious,
@ -150,5 +154,9 @@ class MobileAudioService extends BaseAudioHandler {
? AudioProcessingState.loading
: AudioProcessingState.ready,
);
} catch (e, stack) {
AppLogger.reportError(e, stack);
rethrow;
}
}
}

View File

@ -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<bool>.broadcast();
@ -16,6 +17,7 @@ class ConnectionCheckerService with WidgetsBindingObserver {
Timer? timer;
onConnectivityChanged.listen((connected) {
try {
if (!connected && timer == null) {
timer = Timer.periodic(const Duration(seconds: 30), (timer) async {
if (WidgetsBinding.instance.lifecycleState ==
@ -28,6 +30,9 @@ class ConnectionCheckerService with WidgetsBindingObserver {
timer?.cancel();
timer = null;
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
}
});
}