diff --git a/lib/provider/playlist_queue_provider.dart b/lib/provider/playlist_queue_provider.dart index aee2c13bc..f4a3b4914 100644 --- a/lib/provider/playlist_queue_provider.dart +++ b/lib/provider/playlist_queue_provider.dart @@ -11,6 +11,7 @@ import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/services/audio_player.dart'; import 'package:spotube/services/linux_audio_service.dart'; import 'package:spotube/services/mobile_audio_service.dart'; +import 'package:spotube/services/windows_audio_service.dart'; import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -137,6 +138,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { final Ref ref; MobileAudioService? mobileService; LinuxAudioService? linuxService; + WindowsAudioService? windowsService; static final provider = StateNotifierProvider( @@ -166,6 +168,9 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { if (kIsLinux) { linuxService = LinuxAudioService(ref, this); } + if (kIsWindows) { + windowsService = WindowsAudioService(ref, this); + } addListener((state) { linuxService?.player.updateProperties(); }); @@ -363,6 +368,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { duration: state!.activeTrack.duration, ); mobileService?.addItem(mediaItem); + windowsService?.addTrack(state!.activeTrack); if (state!.activeTrack is LocalTrack) { await audioPlayer.play( DeviceFileSource((state!.activeTrack as LocalTrack).path), @@ -512,6 +518,12 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { Map toJson() { return state?.toJson() ?? {}; } + + @override + void dispose() { + windowsService?.dispose(); + super.dispose(); + } } class VolumeProvider extends PersistedStateNotifier { diff --git a/lib/services/windows_audio_service.dart b/lib/services/windows_audio_service.dart new file mode 100644 index 000000000..305eb19d6 --- /dev/null +++ b/lib/services/windows_audio_service.dart @@ -0,0 +1,99 @@ +import 'dart:async'; + +import 'package:audioplayers/audioplayers.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:smtc_windows/smtc_windows.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/provider/playlist_queue_provider.dart'; +import 'package:spotube/services/audio_player.dart'; +import 'package:spotube/utils/type_conversion_utils.dart'; + +class WindowsAudioService { + final SMTCWindows smtc; + final Ref ref; + final PlaylistQueueNotifier playlistNotifier; + + final subscriptions = []; + + WindowsAudioService(this.ref, this.playlistNotifier) : smtc = SMTCWindows() { + smtc.setPlaybackStatus(PlaybackStatus.Stopped); + final buttonStream = smtc.buttonPressStream.listen((event) { + switch (event) { + case PressedButton.play: + playlistNotifier.resume(); + break; + case PressedButton.pause: + playlistNotifier.pause(); + break; + case PressedButton.next: + playlistNotifier.next(); + break; + case PressedButton.previous: + playlistNotifier.previous(); + break; + case PressedButton.stop: + playlistNotifier.stop(); + break; + default: + break; + } + }); + + final playerStateStream = + audioPlayer.onPlayerStateChanged.listen((state) async { + switch (state) { + case PlayerState.playing: + await smtc.setPlaybackStatus(PlaybackStatus.Playing); + break; + case PlayerState.paused: + await smtc.setPlaybackStatus(PlaybackStatus.Paused); + break; + case PlayerState.stopped: + await smtc.setPlaybackStatus(PlaybackStatus.Stopped); + break; + case PlayerState.completed: + await smtc.setPlaybackStatus(PlaybackStatus.Changing); + break; + default: + break; + } + }); + + final positionStream = audioPlayer.onPositionChanged.listen((pos) async { + await smtc.setPosition(pos); + }); + + final durationStream = + audioPlayer.onDurationChanged.listen((duration) async { + await smtc.setEndTime(duration); + }); + + subscriptions.addAll([ + buttonStream, + playerStateStream, + positionStream, + durationStream, + ]); + } + + Future addTrack(Track track) async { + await smtc.updateMetadata(MusicMetadata( + title: track.name!, + albumArtist: track.artists?.first.name ?? "Unknown", + artist: TypeConversionUtils.artists_X_String(track.artists ?? []), + album: track.album?.name ?? "Unknown", + trackNumber: track.trackNumber ?? 0, + thumbnail: TypeConversionUtils.image_X_UrlString( + track.album?.images ?? [], + placeholder: ImagePlaceholder.albumArt, + ), + )); + } + + void dispose() { + smtc.dispose(); + for (var element in subscriptions) { + element.cancel(); + } + } +} diff --git a/pubspec.lock b/pubspec.lock index a53560a01..f7b157731 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1452,6 +1452,13 @@ packages: description: flutter source: sdk version: "0.0.99" + smtc_windows: + dependency: "direct main" + description: + path: "../smtc_windows/packages/smtc_windows" + relative: true + source: path + version: "0.0.1" source_gen: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e6d1ae8f8..97fe1daae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -84,6 +84,8 @@ dependencies: youtube_explode_dart: ^1.12.1 flutter_desktop_tools: path: ../flutter_desktop_tools + smtc_windows: + path: ../smtc_windows/packages/smtc_windows dev_dependencies: build_runner: ^2.3.2 diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index bfe4236b0..bc353537d 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -18,6 +18,7 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST metadata_god + smtc_windows ) set(PLUGIN_BUNDLED_LIBRARIES)