import 'dart:async'; import 'package:flutter_discord_rpc/flutter_discord_rpc.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.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)); var lastPosition = audioPlayer.position; 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; if (diff > 500 || diff < -500) { await updatePresence(ref.read(audioPlayerProvider).activeTrack!); } } lastPosition = position; } catch (e, stack) { AppLogger.reportError(e, stack); } }) ]; ref.onDispose(() async { for (final subscription in subscriptions) { subscription.cancel(); } await clear(); await close(); await FlutterDiscordRPC.instance.dispose(); }); if (!enabled && FlutterDiscordRPC.instance.isConnected) { await clear(); await close(); } else if (enabled) { await FlutterDiscordRPC.instance.connect(autoRetry: true); } } Future updatePresence(SpotubeTrackObject track) async { if (!kIsDesktop) return; if (FlutterDiscordRPC.instance.isConnected == false) return; final artistNames = track.artists.asString(); final isPlaying = audioPlayer.isPlaying; final position = audioPlayer.position; await FlutterDiscordRPC.instance.setActivity( activity: RPCActivity( details: track.name, state: artistNames, assets: RPCAssets( largeImage: track.album?.images.first.url ?? "spotube-logo-foreground", largeText: track.album?.name ?? "Unknown album", smallImage: "spotube-logo-foreground", smallText: "Spotube", ), buttons: [ RPCButton( label: "Listen on Spotify", url: track.externalUri, ), ], timestamps: RPCTimestamps( start: isPlaying ? DateTime.now().millisecondsSinceEpoch - position.inMilliseconds : null, ), activityType: ActivityType.listening, ), ); } Future clear() async { if (!kIsDesktop) return; await FlutterDiscordRPC.instance.clearActivity(); } Future close() async { if (!kIsDesktop) return; await FlutterDiscordRPC.instance.disconnect(); } } final discordProvider = AsyncNotifierProvider(() => DiscordNotifier());