dbus MPRIS implemented, OS media control support added

Hotkey support removed due to too much key_display_binding issue caused by window resize & re-renders
This commit is contained in:
Kingkor Roy Tirtho 2022-06-26 13:27:38 +06:00
parent 580d7eea60
commit bea7fd14b7
21 changed files with 750 additions and 300 deletions

View File

@ -18,7 +18,6 @@ import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/components/Player/Player.dart'; import 'package:spotube/components/Player/Player.dart';
import 'package:spotube/components/Library/UserLibrary.dart'; import 'package:spotube/components/Library/UserLibrary.dart';
import 'package:spotube/hooks/useBreakpointValue.dart'; import 'package:spotube/hooks/useBreakpointValue.dart';
import 'package:spotube/hooks/useHotKeys.dart';
import 'package:spotube/hooks/usePaginatedFutureProvider.dart'; import 'package:spotube/hooks/usePaginatedFutureProvider.dart';
import 'package:spotube/hooks/useUpdateChecker.dart'; import 'package:spotube/hooks/useUpdateChecker.dart';
import 'package:spotube/models/Logger.dart'; import 'package:spotube/models/Logger.dart';
@ -54,8 +53,6 @@ class Home extends HookConsumerWidget {
final _selectedIndex = useState(0); final _selectedIndex = useState(0);
_onSelectedIndexChanged(int index) => _selectedIndex.value = index; _onSelectedIndexChanged(int index) => _selectedIndex.value = index;
// initializing global hot keys
useHotKeys(ref);
// checks for latest version of the application // checks for latest version of the application
useUpdateChecker(ref); useUpdateChecker(ref);

View File

@ -1,19 +1,15 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/components/Settings/About.dart'; import 'package:spotube/components/Settings/About.dart';
import 'package:spotube/components/Settings/ColorSchemePickerDialog.dart'; import 'package:spotube/components/Settings/ColorSchemePickerDialog.dart';
import 'package:spotube/components/Settings/SettingsHotkeyTile.dart';
import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/helpers/search-youtube.dart'; import 'package:spotube/helpers/search-youtube.dart';
import 'package:spotube/models/SpotifyMarkets.dart'; import 'package:spotube/models/SpotifyMarkets.dart';
import 'package:spotube/models/SpotubeTrack.dart'; import 'package:spotube/models/SpotubeTrack.dart';
import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Auth.dart';
import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/UserPreferences.dart';
import 'package:spotube/utils/platform.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
class Settings extends HookConsumerWidget { class Settings extends HookConsumerWidget {
@ -57,29 +53,6 @@ class Settings extends HookConsumerWidget {
constraints: const BoxConstraints(maxWidth: 1366), constraints: const BoxConstraints(maxWidth: 1366),
child: ListView( child: ListView(
children: [ children: [
if (!kIsMobile) ...[
SettingsHotKeyTile(
title: "Next track global shortcut",
currentHotKey: preferences.nextTrackHotKey,
onHotKeyRecorded: (value) {
preferences.setNextTrackHotKey(value);
},
),
SettingsHotKeyTile(
title: "Prev track global shortcut",
currentHotKey: preferences.prevTrackHotKey,
onHotKeyRecorded: (value) {
preferences.setPrevTrackHotKey(value);
},
),
SettingsHotKeyTile(
title: "Play/Pause global shortcut",
currentHotKey: preferences.playPauseHotKey,
onHotKeyRecorded: (value) {
preferences.setPlayPauseHotKey(value);
},
),
],
ListTile( ListTile(
title: const Text("Theme"), title: const Text("Theme"),
horizontalTitleGap: 10, horizontalTitleGap: 10,

View File

@ -1,42 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:spotube/components/Shared/RecordHotKeyDialog.dart';
class SettingsHotKeyTile extends StatelessWidget {
final String title;
final HotKey? currentHotKey;
final ValueChanged<HotKey> onHotKeyRecorded;
const SettingsHotKeyTile({
required this.onHotKeyRecorded,
required this.title,
Key? key,
this.currentHotKey,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (currentHotKey != null) HotKeyVirtualView(hotKey: currentHotKey!),
const SizedBox(width: 10),
ElevatedButton(
child: const Text("Set Shortcut"),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return RecordHotKeyDialog(
onHotKeyRecorded: onHotKeyRecorded,
);
},
);
},
),
],
),
);
}
}

View File

@ -115,6 +115,24 @@ class DownloadTrackButton extends HookConsumerWidget {
if (!await outputFile.exists()) await outputFile.create(recursive: true); if (!await outputFile.exists()) await outputFile.create(recursive: true);
IOSink outputFileStream = outputFile.openWrite();
await audioStream.pipe(outputFileStream);
await outputFileStream.flush();
await outputFileStream.close().then((value) async {
if (status.value == TrackStatus.downloading) {
status.value = TrackStatus.done;
await Future.delayed(
const Duration(seconds: 3),
() {
if (status.value == TrackStatus.done) {
status.value = TrackStatus.idle;
}
},
);
}
return statusCb.cancel();
});
if (preferences.saveTrackLyrics && playback.currentTrack != null) { if (preferences.saveTrackLyrics && playback.currentTrack != null) {
if (!await outputLyricsFile.exists()) { if (!await outputLyricsFile.exists()) {
await outputLyricsFile.create(recursive: true); await outputLyricsFile.create(recursive: true);
@ -136,24 +154,6 @@ class DownloadTrackButton extends HookConsumerWidget {
); );
} }
} }
IOSink outputFileStream = outputFile.openWrite();
await audioStream.pipe(outputFileStream);
await outputFileStream.flush();
await outputFileStream.close().then((value) async {
if (status.value == TrackStatus.downloading) {
status.value = TrackStatus.done;
await Future.delayed(
const Duration(seconds: 3),
() {
if (status.value == TrackStatus.done) {
status.value = TrackStatus.idle;
}
},
);
}
return statusCb.cancel();
});
}, [ }, [
track, track,
status, status,

View File

@ -1,85 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
class RecordHotKeyDialog extends HookWidget {
final ValueChanged<HotKey> onHotKeyRecorded;
const RecordHotKeyDialog({
Key? key,
required this.onHotKeyRecorded,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _hotKey = useState<HotKey?>(null);
return AlertDialog(
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(
'Press the keys you want to use',
style: Theme.of(context).textTheme.headline5,
),
const SizedBox(height: 10),
const Text(
"DO NOT Use only letters (e.g. k, g etc..)\nUse in combination with these"),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
KeyCode.control,
KeyCode.shift,
KeyCode.alt,
KeyCode.superKey,
KeyCode.meta,
]
.map((key) => HotKeyVirtualView(
hotKey: HotKey(key),
))
.toList(),
),
Container(
width: 100,
height: 60,
margin: const EdgeInsets.only(top: 20),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).primaryColor,
),
),
child: Stack(
alignment: Alignment.center,
children: [
HotKeyRecorder(
onHotKeyRecorded: (hotKey) {
_hotKey.value = hotKey;
},
),
],
),
),
],
),
),
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('OK'),
onPressed: _hotKey.value == null
? null
: () {
onHotKeyRecorded(_hotKey.value!);
GoRouter.of(context).pop();
},
),
],
);
}
}

View File

@ -1,49 +0,0 @@
import 'dart:io';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:spotube/hooks/playback.dart';
import 'package:spotube/models/GlobalKeyActions.dart';
import 'package:spotube/provider/Playback.dart';
import 'package:spotube/provider/UserPreferences.dart';
import 'package:spotube/utils/platform.dart';
useHotKeys(WidgetRef ref) {
final playback = ref.watch(playbackProvider);
final preferences = ref.watch(userPreferencesProvider);
List<GlobalKeyActions> _hotKeys = [];
final onNext = useNextTrack(playback);
final onPrevious = usePreviousTrack(playback);
final _playOrPause = useTogglePlayPause(playback);
useEffect(() {
if (kIsMobile) return null;
_hotKeys = [
GlobalKeyActions(
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
_playOrPause,
),
if (preferences.nextTrackHotKey != null)
GlobalKeyActions(preferences.nextTrackHotKey!, (key) => onNext()),
if (preferences.prevTrackHotKey != null)
GlobalKeyActions(preferences.prevTrackHotKey!, (key) => onPrevious()),
if (preferences.playPauseHotKey != null)
GlobalKeyActions(preferences.playPauseHotKey!, _playOrPause)
];
Future.wait(
_hotKeys.map((e) {
return hotKeyManager.register(
e.hotKey,
keyDownHandler: e.onKeyDown,
);
}),
);
return () {
Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey)));
};
});
}

View File

@ -0,0 +1,207 @@
// This file was generated using the following command and may be overwritten.
// dart-dbus generate-object defs/org.mpris.MediaPlayer2.xml
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:dbus/dbus.dart';
class Media_Player extends DBusObject {
/// Creates a new object to expose on [path].
Media_Player() : super(DBusObjectPath('/org/mpris/MediaPlayer2'));
/// Gets value of property org.mpris.MediaPlayer2.CanQuit
Future<DBusMethodResponse> getCanQuit() async {
return DBusMethodSuccessResponse([const DBusBoolean(true)]);
}
/// Gets value of property org.mpris.MediaPlayer2.Fullscreen
Future<DBusMethodResponse> getFullscreen() async {
return DBusMethodSuccessResponse([const DBusBoolean(false)]);
}
/// Sets property org.mpris.MediaPlayer2.Fullscreen
Future<DBusMethodResponse> setFullscreen(bool value) async {
return DBusMethodSuccessResponse();
}
/// Gets value of property org.mpris.MediaPlayer2.CanSetFullscreen
Future<DBusMethodResponse> getCanSetFullscreen() async {
return DBusMethodSuccessResponse([const DBusBoolean(false)]);
}
/// Gets value of property org.mpris.MediaPlayer2.CanRaise
Future<DBusMethodResponse> getCanRaise() async {
return DBusMethodSuccessResponse([const DBusBoolean(false)]);
}
/// Gets value of property org.mpris.MediaPlayer2.HasTrackList
Future<DBusMethodResponse> getHasTrackList() async {
return DBusMethodSuccessResponse([const DBusBoolean(false)]);
}
/// Gets value of property org.mpris.MediaPlayer2.Identity
Future<DBusMethodResponse> getIdentity() async {
return DBusMethodSuccessResponse([const DBusString("Spotube")]);
}
/// Gets value of property org.mpris.MediaPlayer2.DesktopEntry
Future<DBusMethodResponse> getDesktopEntry() async {
return DBusMethodSuccessResponse([const DBusString("spotube")]);
}
/// Gets value of property org.mpris.MediaPlayer2.SupportedUriSchemes
Future<DBusMethodResponse> getSupportedUriSchemes() async {
return DBusMethodSuccessResponse([
DBusArray.string(["http"])
]);
}
/// Gets value of property org.mpris.MediaPlayer2.SupportedMimeTypes
Future<DBusMethodResponse> getSupportedMimeTypes() async {
return DBusMethodSuccessResponse([
DBusArray.string(["audio/mpeg"])
]);
}
/// Implementation of org.mpris.MediaPlayer2.Raise()
Future<DBusMethodResponse> doRaise() async {
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Quit()
Future<DBusMethodResponse> doQuit() async {
appWindow.close();
return DBusMethodSuccessResponse();
}
@override
List<DBusIntrospectInterface> introspect() {
return [
DBusIntrospectInterface('org.mpris.MediaPlayer2', methods: [
DBusIntrospectMethod('Raise'),
DBusIntrospectMethod('Quit')
], properties: [
DBusIntrospectProperty('CanQuit', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('Fullscreen', DBusSignature('b'),
access: DBusPropertyAccess.readwrite),
DBusIntrospectProperty('CanSetFullscreen', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('CanRaise', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('HasTrackList', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('Identity', DBusSignature('s'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('DesktopEntry', DBusSignature('s'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('SupportedUriSchemes', DBusSignature('as'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('SupportedMimeTypes', DBusSignature('as'),
access: DBusPropertyAccess.read)
])
];
}
@override
Future<DBusMethodResponse> handleMethodCall(DBusMethodCall methodCall) async {
if (methodCall.interface == 'org.mpris.MediaPlayer2') {
if (methodCall.name == 'Raise') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
return doRaise();
} else if (methodCall.name == 'Quit') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
return doQuit();
} else {
return DBusMethodErrorResponse.unknownMethod();
}
} else {
return DBusMethodErrorResponse.unknownInterface();
}
}
@override
Future<DBusMethodResponse> getProperty(String interface, String name) async {
if (interface == 'org.mpris.MediaPlayer2') {
if (name == 'CanQuit') {
return getCanQuit();
} else if (name == 'Fullscreen') {
return getFullscreen();
} else if (name == 'CanSetFullscreen') {
return getCanSetFullscreen();
} else if (name == 'CanRaise') {
return getCanRaise();
} else if (name == 'HasTrackList') {
return getHasTrackList();
} else if (name == 'Identity') {
return getIdentity();
} else if (name == 'DesktopEntry') {
return getDesktopEntry();
} else if (name == 'SupportedUriSchemes') {
return getSupportedUriSchemes();
} else if (name == 'SupportedMimeTypes') {
return getSupportedMimeTypes();
} else {
return DBusMethodErrorResponse.unknownProperty();
}
} else {
return DBusMethodErrorResponse.unknownProperty();
}
}
@override
Future<DBusMethodResponse> setProperty(
String interface, String name, DBusValue value) async {
if (interface == 'org.mpris.MediaPlayer2') {
if (name == 'CanQuit') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'Fullscreen') {
if (value.signature != DBusSignature('b')) {
return DBusMethodErrorResponse.invalidArgs();
}
return setFullscreen((value as DBusBoolean).value);
} else if (name == 'CanSetFullscreen') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'CanRaise') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'HasTrackList') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'Identity') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'DesktopEntry') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'SupportedUriSchemes') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'SupportedMimeTypes') {
return DBusMethodErrorResponse.propertyReadOnly();
} else {
return DBusMethodErrorResponse.unknownProperty();
}
} else {
return DBusMethodErrorResponse.unknownProperty();
}
}
@override
Future<DBusMethodResponse> getAllProperties(String interface) async {
var properties = <String, DBusValue>{};
if (interface == 'org.mpris.MediaPlayer2') {
properties['CanQuit'] = (await getCanQuit()).returnValues[0];
properties['Fullscreen'] = (await getFullscreen()).returnValues[0];
properties['CanSetFullscreen'] =
(await getCanSetFullscreen()).returnValues[0];
properties['CanRaise'] = (await getCanRaise()).returnValues[0];
properties['HasTrackList'] = (await getHasTrackList()).returnValues[0];
properties['Identity'] = (await getIdentity()).returnValues[0];
properties['DesktopEntry'] = (await getDesktopEntry()).returnValues[0];
properties['SupportedUriSchemes'] =
(await getSupportedUriSchemes()).returnValues[0];
properties['SupportedMimeTypes'] =
(await getSupportedMimeTypes()).returnValues[0];
}
return DBusMethodSuccessResponse([DBusDict.stringVariant(properties)]);
}
}

View File

@ -0,0 +1,470 @@
// This file was generated using the following command and may be overwritten.
// dart-dbus generate-object defs/org.mpris.MediaPlayer2.Player.xml
import 'package:dbus/dbus.dart';
import 'package:just_audio/just_audio.dart';
import 'package:spotube/helpers/image-to-url-string.dart';
import 'package:spotube/models/SpotubeTrack.dart';
import 'package:spotube/provider/Playback.dart';
class Player_Interface extends DBusObject {
final AudioPlayer player;
final Playback playback;
/// Creates a new object to expose on [path].
Player_Interface({
required this.player,
required this.playback,
}) : super(DBusObjectPath("/org/mpris/MediaPlayer2"));
/// Gets value of property org.mpris.MediaPlayer2.Player.PlaybackStatus
Future<DBusMethodResponse> getPlaybackStatus() async {
final status = player.playing
? "Playing"
: playback.currentPlaylist == null
? "Stopped"
: "Paused";
return DBusMethodSuccessResponse([DBusString(status)]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.LoopStatus
Future<DBusMethodResponse> getLoopStatus() async {
return DBusMethodSuccessResponse([const DBusString("Playlist")]);
}
/// Sets property org.mpris.MediaPlayer2.Player.LoopStatus
Future<DBusMethodResponse> setLoopStatus(String value) async {
return DBusMethodErrorResponse.failed(
'Set org.mpris.MediaPlayer2.Player.LoopStatus not implemented');
}
/// Gets value of property org.mpris.MediaPlayer2.Player.Rate
Future<DBusMethodResponse> getRate() async {
return DBusMethodSuccessResponse([DBusDouble(player.speed)]);
}
/// Sets property org.mpris.MediaPlayer2.Player.Rate
Future<DBusMethodResponse> setRate(double value) async {
player.setSpeed(value);
return DBusMethodSuccessResponse();
}
/// Gets value of property org.mpris.MediaPlayer2.Player.Shuffle
Future<DBusMethodResponse> getShuffle() async {
return DBusMethodSuccessResponse([DBusBoolean(playback.shuffled)]);
}
/// Sets property org.mpris.MediaPlayer2.Player.Shuffle
Future<DBusMethodResponse> setShuffle(bool value) async {
if (value) {
playback.shuffle();
} else {
playback.unshuffle();
}
return DBusMethodSuccessResponse();
}
/// Gets value of property org.mpris.MediaPlayer2.Player.Metadata
Future<DBusMethodResponse> getMetadata() async {
try {
if (playback.currentTrack == null) {
return DBusMethodSuccessResponse([DBusDict.stringVariant({})]);
}
final id = (playback.currentPlaylist != null
? playback.currentPlaylist!.tracks.indexWhere(
(track) => playback.currentTrack!.id == track.id!,
)
: 0)
.abs();
return DBusMethodSuccessResponse([
DBusDict.stringVariant({
"mpris:trackid": DBusString("${path.value}/Track/$id"),
"mpris:length": DBusInt32(playback.duration?.inMicroseconds ?? 0),
"mpris:artUrl": DBusString(
imageToUrlString(playback.currentTrack?.album?.images)),
"xesam:album": DBusString(playback.currentTrack!.album!.name!),
"xesam:artist": DBusArray.string(
playback.currentTrack!.artists!.map((artist) => artist.name!),
),
"xesam:title": DBusString(playback.currentTrack!.name!),
"xesam:url": DBusString(
playback.currentTrack is SpotubeTrack
? (playback.currentTrack as SpotubeTrack).ytUri
: playback.currentTrack!.previewUrl!,
),
"xesam:genre": const DBusString("Unknown"),
}),
]);
} catch (e) {
print("[DBUS ERROR] $e");
rethrow;
}
}
/// Gets value of property org.mpris.MediaPlayer2.Player.Volume
Future<DBusMethodResponse> getVolume() async {
return DBusMethodSuccessResponse([DBusDouble(player.volume)]);
}
/// Sets property org.mpris.MediaPlayer2.Player.Volume
Future<DBusMethodResponse> setVolume(double value) async {
player.setVolume(value);
return DBusMethodSuccessResponse();
}
/// Gets value of property org.mpris.MediaPlayer2.Player.Position
Future<DBusMethodResponse> getPosition() async {
return DBusMethodSuccessResponse([
DBusInt64(player.position.inMicroseconds),
]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.MinimumRate
Future<DBusMethodResponse> getMinimumRate() async {
return DBusMethodSuccessResponse([const DBusDouble(1)]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.MaximumRate
Future<DBusMethodResponse> getMaximumRate() async {
return DBusMethodSuccessResponse([const DBusDouble(1)]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.CanGoNext
Future<DBusMethodResponse> getCanGoNext() async {
return DBusMethodSuccessResponse([
DBusBoolean(
playback.currentPlaylist?.tracks.isNotEmpty == true,
)
]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.CanGoPrevious
Future<DBusMethodResponse> getCanGoPrevious() async {
return DBusMethodSuccessResponse([
DBusBoolean(
playback.currentPlaylist?.tracks.isNotEmpty == true,
)
]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.CanPlay
Future<DBusMethodResponse> getCanPlay() async {
return DBusMethodSuccessResponse([const DBusBoolean(true)]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.CanPause
Future<DBusMethodResponse> getCanPause() async {
return DBusMethodSuccessResponse([const DBusBoolean(true)]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.CanSeek
Future<DBusMethodResponse> getCanSeek() async {
return DBusMethodSuccessResponse([const DBusBoolean(true)]);
}
/// Gets value of property org.mpris.MediaPlayer2.Player.CanControl
Future<DBusMethodResponse> getCanControl() async {
return DBusMethodSuccessResponse([const DBusBoolean(true)]);
}
/// Implementation of org.mpris.MediaPlayer2.Player.Next()
Future<DBusMethodResponse> doNext() async {
playback.movePlaylistPositionBy(1);
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Player.Previous()
Future<DBusMethodResponse> doPrevious() async {
playback.movePlaylistPositionBy(-1);
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Player.Pause()
Future<DBusMethodResponse> doPause() async {
player.pause();
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Player.PlayPause()
Future<DBusMethodResponse> doPlayPause() async {
player.playing ? player.pause() : player.play();
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Player.Stop()
Future<DBusMethodResponse> doStop() async {
await player.pause();
await player.seek(Duration.zero);
playback.reset();
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Player.Play()
Future<DBusMethodResponse> doPlay() async {
player.play();
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Player.Seek()
Future<DBusMethodResponse> doSeek(int offset) async {
player.seek(Duration(microseconds: offset));
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Player.SetPosition()
Future<DBusMethodResponse> doSetPosition(String TrackId, int Position) async {
return DBusMethodSuccessResponse();
}
/// Implementation of org.mpris.MediaPlayer2.Player.OpenUri()
Future<DBusMethodResponse> doOpenUri(String Uri) async {
return DBusMethodSuccessResponse();
}
/// Emits signal org.mpris.MediaPlayer2.Player.Seeked
Future<void> emitSeeked(int Position) async {
await emitSignal(
'org.mpris.MediaPlayer2.Player', 'Seeked', [DBusInt64(Position)]);
}
@override
List<DBusIntrospectInterface> introspect() {
return [
DBusIntrospectInterface('org.mpris.MediaPlayer2.Player', methods: [
DBusIntrospectMethod('Next'),
DBusIntrospectMethod('Previous'),
DBusIntrospectMethod('Pause'),
DBusIntrospectMethod('PlayPause'),
DBusIntrospectMethod('Stop'),
DBusIntrospectMethod('Play'),
DBusIntrospectMethod('Seek', args: [
DBusIntrospectArgument(DBusSignature('x'), DBusArgumentDirection.in_,
name: 'Offset')
]),
DBusIntrospectMethod('SetPosition', args: [
DBusIntrospectArgument(DBusSignature('o'), DBusArgumentDirection.in_,
name: 'TrackId'),
DBusIntrospectArgument(DBusSignature('x'), DBusArgumentDirection.in_,
name: 'Position')
]),
DBusIntrospectMethod('OpenUri', args: [
DBusIntrospectArgument(DBusSignature('s'), DBusArgumentDirection.in_,
name: 'Uri')
])
], signals: [
DBusIntrospectSignal('Seeked', args: [
DBusIntrospectArgument(DBusSignature('x'), DBusArgumentDirection.out,
name: 'Position')
])
], properties: [
DBusIntrospectProperty('PlaybackStatus', DBusSignature('s'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('LoopStatus', DBusSignature('s'),
access: DBusPropertyAccess.readwrite),
DBusIntrospectProperty('Rate', DBusSignature('d'),
access: DBusPropertyAccess.readwrite),
DBusIntrospectProperty('Shuffle', DBusSignature('b'),
access: DBusPropertyAccess.readwrite),
DBusIntrospectProperty('Metadata', DBusSignature('a{sv}'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('Volume', DBusSignature('d'),
access: DBusPropertyAccess.readwrite),
DBusIntrospectProperty('Position', DBusSignature('x'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('MinimumRate', DBusSignature('d'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('MaximumRate', DBusSignature('d'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('CanGoNext', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('CanGoPrevious', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('CanPlay', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('CanPause', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('CanSeek', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('CanControl', DBusSignature('b'),
access: DBusPropertyAccess.read)
])
];
}
@override
Future<DBusMethodResponse> handleMethodCall(DBusMethodCall methodCall) async {
if (methodCall.interface == 'org.mpris.MediaPlayer2.Player') {
if (methodCall.name == 'Next') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
return doNext();
} else if (methodCall.name == 'Previous') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
return doPrevious();
} else if (methodCall.name == 'Pause') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
return doPause();
} else if (methodCall.name == 'PlayPause') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
return doPlayPause();
} else if (methodCall.name == 'Stop') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
return doStop();
} else if (methodCall.name == 'Play') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
return doPlay();
} else if (methodCall.name == 'Seek') {
if (methodCall.signature != DBusSignature('x')) {
return DBusMethodErrorResponse.invalidArgs();
}
return doSeek((methodCall.values[0] as DBusInt64).value);
} else if (methodCall.name == 'SetPosition') {
if (methodCall.signature != DBusSignature('ox')) {
return DBusMethodErrorResponse.invalidArgs();
}
return doSetPosition((methodCall.values[0] as DBusObjectPath).value,
(methodCall.values[1] as DBusInt64).value);
} else if (methodCall.name == 'OpenUri') {
if (methodCall.signature != DBusSignature('s')) {
return DBusMethodErrorResponse.invalidArgs();
}
return doOpenUri((methodCall.values[0] as DBusString).value);
} else {
return DBusMethodErrorResponse.unknownMethod();
}
} else {
return DBusMethodErrorResponse.unknownInterface();
}
}
@override
Future<DBusMethodResponse> getProperty(String interface, String name) async {
if (interface == 'org.mpris.MediaPlayer2.Player') {
if (name == 'PlaybackStatus') {
return getPlaybackStatus();
} else if (name == 'LoopStatus') {
return getLoopStatus();
} else if (name == 'Rate') {
return getRate();
} else if (name == 'Shuffle') {
return getShuffle();
} else if (name == 'Metadata') {
return getMetadata();
} else if (name == 'Volume') {
return getVolume();
} else if (name == 'Position') {
return getPosition();
} else if (name == 'MinimumRate') {
return getMinimumRate();
} else if (name == 'MaximumRate') {
return getMaximumRate();
} else if (name == 'CanGoNext') {
return getCanGoNext();
} else if (name == 'CanGoPrevious') {
return getCanGoPrevious();
} else if (name == 'CanPlay') {
return getCanPlay();
} else if (name == 'CanPause') {
return getCanPause();
} else if (name == 'CanSeek') {
return getCanSeek();
} else if (name == 'CanControl') {
return getCanControl();
} else {
return DBusMethodErrorResponse.unknownProperty();
}
} else {
return DBusMethodErrorResponse.unknownProperty();
}
}
@override
Future<DBusMethodResponse> setProperty(
String interface, String name, DBusValue value) async {
if (interface == 'org.mpris.MediaPlayer2.Player') {
if (name == 'PlaybackStatus') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'LoopStatus') {
if (value.signature != DBusSignature('s')) {
return DBusMethodErrorResponse.invalidArgs();
}
return setLoopStatus((value as DBusString).value);
} else if (name == 'Rate') {
if (value.signature != DBusSignature('d')) {
return DBusMethodErrorResponse.invalidArgs();
}
return setRate((value as DBusDouble).value);
} else if (name == 'Shuffle') {
if (value.signature != DBusSignature('b')) {
return DBusMethodErrorResponse.invalidArgs();
}
return setShuffle((value as DBusBoolean).value);
} else if (name == 'Metadata') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'Volume') {
if (value.signature != DBusSignature('d')) {
return DBusMethodErrorResponse.invalidArgs();
}
return setVolume((value as DBusDouble).value);
} else if (name == 'Position') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'MinimumRate') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'MaximumRate') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'CanGoNext') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'CanGoPrevious') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'CanPlay') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'CanPause') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'CanSeek') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'CanControl') {
return DBusMethodErrorResponse.propertyReadOnly();
} else {
return DBusMethodErrorResponse.unknownProperty();
}
} else {
return DBusMethodErrorResponse.unknownProperty();
}
}
@override
Future<DBusMethodResponse> getAllProperties(String interface) async {
var properties = <String, DBusValue>{};
if (interface == 'org.mpris.MediaPlayer2.Player') {
properties['PlaybackStatus'] =
(await getPlaybackStatus()).returnValues[0];
properties['LoopStatus'] = (await getLoopStatus()).returnValues[0];
properties['Rate'] = (await getRate()).returnValues[0];
properties['Shuffle'] = (await getShuffle()).returnValues[0];
properties['Metadata'] = (await getMetadata()).returnValues[0];
properties['Volume'] = (await getVolume()).returnValues[0];
properties['Position'] = (await getPosition()).returnValues[0];
properties['MinimumRate'] = (await getMinimumRate()).returnValues[0];
properties['MaximumRate'] = (await getMaximumRate()).returnValues[0];
properties['CanGoNext'] = (await getCanGoNext()).returnValues[0];
properties['CanGoPrevious'] = (await getCanGoPrevious()).returnValues[0];
properties['CanPlay'] = (await getCanPlay()).returnValues[0];
properties['CanPause'] = (await getCanPause()).returnValues[0];
properties['CanSeek'] = (await getCanSeek()).returnValues[0];
properties['CanControl'] = (await getCanControl()).returnValues[0];
}
return DBusMethodSuccessResponse([DBusDict.stringVariant(properties)]);
}
}

View File

@ -1,17 +1,17 @@
import 'dart:io';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:dbus/dbus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:spotube/entities/CacheTrack.dart'; import 'package:spotube/entities/CacheTrack.dart';
import 'package:spotube/interfaces/media_player2.dart';
import 'package:spotube/models/GoRouteDeclarations.dart'; import 'package:spotube/models/GoRouteDeclarations.dart';
import 'package:spotube/models/Logger.dart'; import 'package:spotube/models/Logger.dart';
import 'package:spotube/provider/AudioPlayer.dart'; import 'package:spotube/provider/AudioPlayer.dart';
import 'package:spotube/provider/DBus.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/UserPreferences.dart';
import 'package:spotube/provider/YouTube.dart'; import 'package:spotube/provider/YouTube.dart';
@ -34,7 +34,8 @@ void main() async {
); );
if (kIsDesktop) { if (kIsDesktop) {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await hotKeyManager.unregisterAll(); // final client = DBusClient.session();
// await client.registerObject(Media_Player());
doWhenWindowReady(() { doWhenWindowReady(() {
appWindow.minSize = const Size(359, 700); appWindow.minSize = const Size(359, 700);
appWindow.alignment = Alignment.center; appWindow.alignment = Alignment.center;
@ -49,10 +50,12 @@ void main() async {
playbackProvider.overrideWithProvider(ChangeNotifierProvider( playbackProvider.overrideWithProvider(ChangeNotifierProvider(
(ref) { (ref) {
final youtube = ref.watch(youtubeProvider); final youtube = ref.watch(youtubeProvider);
final dbus = ref.watch(dbusClientProvider);
return Playback( return Playback(
player: audioPlayerHandler, player: audioPlayerHandler,
youtube: youtube, youtube: youtube,
ref: ref, ref: ref,
dbus: dbus,
); );
}, },
)) ))

View File

@ -1,7 +0,0 @@
import 'package:hotkey_manager/hotkey_manager.dart';
class GlobalKeyActions {
late final HotKey hotKey;
late final Function(HotKey hotKey) onKeyDown;
GlobalKeyActions(this.hotKey, this.onKeyDown);
}

View File

@ -24,8 +24,8 @@ GoRouter createGoRouter() => GoRouter(
), ),
GoRoute( GoRoute(
path: "/settings", path: "/settings",
pageBuilder: (context, state) => SpotubePage( pageBuilder: (context, state) => const SpotubePage(
child: const Settings(), child: Settings(),
), ),
), ),
GoRoute( GoRoute(

6
lib/provider/DBus.dart Normal file
View File

@ -0,0 +1,6 @@
import 'package:dbus/dbus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final dbusClientProvider = Provider((ref) {
return DBusClient.session();
});

View File

@ -1,7 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:dbus/dbus.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
@ -10,8 +13,11 @@ import 'package:spotube/entities/CacheTrack.dart';
import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/artist-to-string.dart';
import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/image-to-url-string.dart';
import 'package:spotube/helpers/search-youtube.dart'; import 'package:spotube/helpers/search-youtube.dart';
import 'package:spotube/interfaces/media_player2.dart';
import 'package:spotube/interfaces/media_player2_player.dart';
import 'package:spotube/models/CurrentPlaylist.dart'; import 'package:spotube/models/CurrentPlaylist.dart';
import 'package:spotube/models/Logger.dart'; import 'package:spotube/models/Logger.dart';
import 'package:spotube/provider/DBus.dart';
import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/UserPreferences.dart';
import 'package:spotube/provider/YouTube.dart'; import 'package:spotube/provider/YouTube.dart';
import 'package:spotube/utils/AudioPlayerHandler.dart'; import 'package:spotube/utils/AudioPlayerHandler.dart';
@ -37,15 +43,23 @@ class Playback extends PersistedChangeNotifier {
LazyBox<CacheTrack>? cacheTrackBox; LazyBox<CacheTrack>? cacheTrackBox;
@protected
final DBusClient dbus;
final Media_Player _media_player;
late final Player_Interface _mpris;
Playback({ Playback({
required this.player, required this.player,
required this.youtube, required this.youtube,
required this.ref, required this.ref,
required this.dbus,
CurrentPlaylist? currentPlaylist, CurrentPlaylist? currentPlaylist,
Track? currentTrack, Track? currentTrack,
}) : _currentPlaylist = currentPlaylist, }) : _currentPlaylist = currentPlaylist,
_currentTrack = currentTrack, _currentTrack = currentTrack,
_media_player = Media_Player(),
super() { super() {
_mpris = Player_Interface(player: player.core, playback: this);
player.onNextRequest = () { player.onNextRequest = () {
movePlaylistPositionBy(1); movePlaylistPositionBy(1);
}; };
@ -61,6 +75,19 @@ class Playback extends PersistedChangeNotifier {
StreamSubscription<bool>? _playingStream; StreamSubscription<bool>? _playingStream;
void _init() async { void _init() async {
// dbus m.p.r.i.s stuff
try {
final nameStatus =
await dbus.requestName("org.mpris.MediaPlayer2.spotube");
if (nameStatus == DBusRequestNameReply.exists) {
await dbus.requestName("org.mpris.MediaPlayer2.spotube.instance$pid");
}
await dbus.registerObject(_media_player);
await dbus.registerObject(_mpris);
} catch (e) {
logger.e("[MPRIS initialization error]", e);
}
cacheTrackBox = await Hive.openLazyBox<CacheTrack>("track-cache"); cacheTrackBox = await Hive.openLazyBox<CacheTrack>("track-cache");
_playingStream = player.core.playingStream.listen( _playingStream = player.core.playingStream.listen(
@ -118,6 +145,8 @@ class Playback extends PersistedChangeNotifier {
_playingStream?.cancel(); _playingStream?.cancel();
_durationStream?.cancel(); _durationStream?.cancel();
cacheTrackBox?.close(); cacheTrackBox?.close();
dbus.unregisterObject(_media_player);
dbus.unregisterObject(_mpris);
super.dispose(); super.dispose();
} }
@ -299,9 +328,11 @@ class Playback extends PersistedChangeNotifier {
final playbackProvider = ChangeNotifierProvider<Playback>((ref) { final playbackProvider = ChangeNotifierProvider<Playback>((ref) {
final player = AudioPlayerHandler(); final player = AudioPlayerHandler();
final youtube = ref.watch(youtubeProvider); final youtube = ref.watch(youtubeProvider);
final dbus = ref.watch(dbusClientProvider);
return Playback( return Playback(
player: player, player: player,
youtube: youtube, youtube: youtube,
ref: ref, ref: ref,
dbus: dbus,
); );
}); });

View File

@ -1,9 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:spotube/components/Settings/ColorSchemePickerDialog.dart'; import 'package:spotube/components/Settings/ColorSchemePickerDialog.dart';
import 'package:spotube/helpers/get-random-element.dart'; import 'package:spotube/helpers/get-random-element.dart';
import 'package:spotube/helpers/search-youtube.dart'; import 'package:spotube/helpers/search-youtube.dart';
@ -18,9 +16,6 @@ class UserPreferences extends PersistedChangeNotifier {
String recommendationMarket; String recommendationMarket;
bool saveTrackLyrics; bool saveTrackLyrics;
String geniusAccessToken; String geniusAccessToken;
HotKey? nextTrackHotKey;
HotKey? prevTrackHotKey;
HotKey? playPauseHotKey;
bool checkUpdate; bool checkUpdate;
SpotubeTrackMatchAlgorithm trackMatchAlgorithm; SpotubeTrackMatchAlgorithm trackMatchAlgorithm;
AudioQuality audioQuality; AudioQuality audioQuality;
@ -35,9 +30,6 @@ class UserPreferences extends PersistedChangeNotifier {
this.saveTrackLyrics = false, this.saveTrackLyrics = false,
this.accentColorScheme = Colors.green, this.accentColorScheme = Colors.green,
this.backgroundColorScheme = Colors.grey, this.backgroundColorScheme = Colors.grey,
this.nextTrackHotKey,
this.prevTrackHotKey,
this.playPauseHotKey,
this.checkUpdate = true, this.checkUpdate = true,
this.trackMatchAlgorithm = SpotubeTrackMatchAlgorithm.authenticPopular, this.trackMatchAlgorithm = SpotubeTrackMatchAlgorithm.authenticPopular,
this.audioQuality = AudioQuality.high, this.audioQuality = AudioQuality.high,
@ -67,24 +59,6 @@ class UserPreferences extends PersistedChangeNotifier {
updatePersistence(); updatePersistence();
} }
void setNextTrackHotKey(HotKey? value) {
nextTrackHotKey = value;
notifyListeners();
updatePersistence();
}
void setPrevTrackHotKey(HotKey? value) {
prevTrackHotKey = value;
notifyListeners();
updatePersistence();
}
void setPlayPauseHotKey(HotKey? value) {
playPauseHotKey = value;
notifyListeners();
updatePersistence();
}
void setYtSearchFormat(String format) { void setYtSearchFormat(String format) {
ytSearchFormat = format; ytSearchFormat = format;
notifyListeners(); notifyListeners();
@ -128,15 +102,7 @@ class UserPreferences extends PersistedChangeNotifier {
checkUpdate = map["checkUpdate"] ?? checkUpdate; checkUpdate = map["checkUpdate"] ?? checkUpdate;
geniusAccessToken = geniusAccessToken =
map["geniusAccessToken"] ?? getRandomElement(lyricsSecrets); map["geniusAccessToken"] ?? getRandomElement(lyricsSecrets);
nextTrackHotKey = map["nextTrackHotKey"] != null
? HotKey.fromJson(jsonDecode(map["nextTrackHotKey"]))
: null;
prevTrackHotKey = map["prevTrackHotKey"] != null
? HotKey.fromJson(jsonDecode(map["prevTrackHotKey"]))
: null;
playPauseHotKey = map["playPauseHotKey"] != null
? HotKey.fromJson(jsonDecode(map["playPauseHotKey"]))
: null;
ytSearchFormat = map["ytSearchFormat"] ?? ytSearchFormat; ytSearchFormat = map["ytSearchFormat"] ?? ytSearchFormat;
themeMode = ThemeMode.values[map["themeMode"] ?? 0]; themeMode = ThemeMode.values[map["themeMode"] ?? 0];
backgroundColorScheme = colorsMap.values backgroundColorScheme = colorsMap.values
@ -159,15 +125,6 @@ class UserPreferences extends PersistedChangeNotifier {
"saveTrackLyrics": saveTrackLyrics, "saveTrackLyrics": saveTrackLyrics,
"recommendationMarket": recommendationMarket, "recommendationMarket": recommendationMarket,
"geniusAccessToken": geniusAccessToken, "geniusAccessToken": geniusAccessToken,
"nextTrackHotKey": nextTrackHotKey != null
? jsonEncode(nextTrackHotKey?.toJson())
: null,
"prevTrackHotKey": prevTrackHotKey != null
? jsonEncode(prevTrackHotKey?.toJson())
: null,
"playPauseHotKey": playPauseHotKey != null
? jsonEncode(playPauseHotKey?.toJson())
: null,
"ytSearchFormat": ytSearchFormat, "ytSearchFormat": ytSearchFormat,
"themeMode": themeMode.index, "themeMode": themeMode.index,
"backgroundColorScheme": backgroundColorScheme.value, "backgroundColorScheme": backgroundColorScheme.value,

View File

@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h> #include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
#include <hotkey_manager/hotkey_manager_plugin.h>
#include <libwinmedia/libwinmedia_plugin.h> #include <libwinmedia/libwinmedia_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
@ -15,9 +14,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar = g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar); bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
g_autoptr(FlPluginRegistrar) hotkey_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerPlugin");
hotkey_manager_plugin_register_with_registrar(hotkey_manager_registrar);
g_autoptr(FlPluginRegistrar) libwinmedia_registrar = g_autoptr(FlPluginRegistrar) libwinmedia_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "LibwinmediaPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "LibwinmediaPlugin");
libwinmedia_plugin_register_with_registrar(libwinmedia_registrar); libwinmedia_plugin_register_with_registrar(libwinmedia_registrar);

View File

@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
bitsdojo_window_linux bitsdojo_window_linux
hotkey_manager
libwinmedia libwinmedia
url_launcher_linux url_launcher_linux
) )

View File

@ -8,7 +8,6 @@ import Foundation
import audio_service import audio_service
import audio_session import audio_session
import bitsdojo_window_macos import bitsdojo_window_macos
import hotkey_manager
import just_audio import just_audio
import package_info_plus_macos import package_info_plus_macos
import path_provider_macos import path_provider_macos
@ -20,7 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin")) BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
HotkeyManagerPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerPlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

View File

@ -267,6 +267,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.3" version: "2.2.3"
dbus:
dependency: "direct main"
description:
name: dbus
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.3"
fading_edge_scrollview: fading_edge_scrollview:
dependency: transitive dependency: transitive
description: description:
@ -429,13 +436,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
hotkey_manager:
dependency: "direct main"
description:
name: hotkey_manager
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.7"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -42,7 +42,6 @@ dependencies:
url_launcher: ^6.0.17 url_launcher: ^6.0.17
youtube_explode_dart: ^1.10.8 youtube_explode_dart: ^1.10.8
bitsdojo_window: ^0.1.2 bitsdojo_window: ^0.1.2
hotkey_manager: ^0.1.6
just_audio: ^0.9.18 just_audio: ^0.9.18
just_audio_libwinmedia: ^0.0.4 just_audio_libwinmedia: ^0.0.4
path: ^1.8.0 path: ^1.8.0
@ -64,6 +63,7 @@ dependencies:
skeleton_text: ^3.0.0 skeleton_text: ^3.0.0
hive: ^2.2.2 hive: ^2.2.2
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
dbus: ^0.7.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h> #include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
#include <hotkey_manager/hotkey_manager_plugin.h>
#include <libwinmedia/libwinmedia_plugin.h> #include <libwinmedia/libwinmedia_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
@ -15,8 +14,6 @@
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
BitsdojoWindowPluginRegisterWithRegistrar( BitsdojoWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
HotkeyManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("HotkeyManagerPlugin"));
LibwinmediaPluginRegisterWithRegistrar( LibwinmediaPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LibwinmediaPlugin")); registry->GetRegistrarForPlugin("LibwinmediaPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(

View File

@ -4,7 +4,6 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
bitsdojo_window_windows bitsdojo_window_windows
hotkey_manager
libwinmedia libwinmedia
permission_handler_windows permission_handler_windows
url_launcher_windows url_launcher_windows