Trying to get DBus PropertiesChanged propagation working
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import functools
|
||||||
import os
|
import os
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
@@ -13,12 +14,28 @@ from .ui.main import MainWindow
|
|||||||
from .ui.configure_servers import ConfigureServersDialog
|
from .ui.configure_servers import ConfigureServersDialog
|
||||||
from .ui.settings import SettingsDialog
|
from .ui.settings import SettingsDialog
|
||||||
|
|
||||||
|
from .dbus_manager import DBusManager
|
||||||
from .state_manager import ApplicationState, RepeatType
|
from .state_manager import ApplicationState, RepeatType
|
||||||
from .cache_manager import CacheManager
|
from .cache_manager import CacheManager
|
||||||
from .server.api_objects import Child
|
from .server.api_objects import Child
|
||||||
from .ui.common.players import PlayerEvent, MPVPlayer, ChromecastPlayer
|
from .ui.common.players import PlayerEvent, MPVPlayer, ChromecastPlayer
|
||||||
|
|
||||||
|
|
||||||
|
def dbus_propagate(param_self=None):
|
||||||
|
"""
|
||||||
|
Wraps a function which causes changes to DBus properties.
|
||||||
|
"""
|
||||||
|
def decorator(function):
|
||||||
|
@functools.wraps(function)
|
||||||
|
def wrapper(*args):
|
||||||
|
function(*args)
|
||||||
|
(param_self or args[0]).dbus_manager.property_diff()
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
class LibremsonicApp(Gtk.Application):
|
class LibremsonicApp(Gtk.Application):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -205,6 +222,8 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
)
|
)
|
||||||
self.player = self.mpv_player
|
self.player = self.mpv_player
|
||||||
|
|
||||||
|
self.player.volume = self.state.volume
|
||||||
|
|
||||||
if self.state.current_device != 'this device':
|
if self.state.current_device != 'this device':
|
||||||
# TODO figure out how to activate the chromecast if possible
|
# TODO figure out how to activate the chromecast if possible
|
||||||
# without blocking the main thread. Also, need to make it obvious
|
# without blocking the main thread. Also, need to make it obvious
|
||||||
@@ -220,42 +239,14 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
|
|
||||||
# ########## DBUS MANAGMENT ########## #
|
# ########## DBUS MANAGMENT ########## #
|
||||||
def do_dbus_register(self, connection, path):
|
def do_dbus_register(self, connection, path):
|
||||||
Gio.bus_own_name_on_connection(
|
self.dbus_manager = DBusManager(
|
||||||
connection,
|
connection,
|
||||||
'org.mpris.MediaPlayer2.libremsonic',
|
self.on_dbus_method_call,
|
||||||
Gio.BusNameOwnerFlags.NONE,
|
self.on_dbus_set_property,
|
||||||
self.dbus_name_acquired,
|
lambda: (self.state, self.player),
|
||||||
self.dbus_name_lost,
|
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def dbus_name_acquired(self, connection, name):
|
|
||||||
specs = [
|
|
||||||
'org.mpris.MediaPlayer2.xml',
|
|
||||||
'org.mpris.MediaPlayer2.Player.xml',
|
|
||||||
'org.mpris.MediaPlayer2.Playlists.xml',
|
|
||||||
'org.mpris.MediaPlayer2.TrackList.xml',
|
|
||||||
]
|
|
||||||
for spec in specs:
|
|
||||||
spec_path = os.path.join(
|
|
||||||
os.path.dirname(__file__),
|
|
||||||
f'ui/mpris_specs/{spec}',
|
|
||||||
)
|
|
||||||
with open(spec_path) as f:
|
|
||||||
node_info = Gio.DBusNodeInfo.new_for_xml(f.read())
|
|
||||||
|
|
||||||
connection.register_object(
|
|
||||||
'/org/mpris/MediaPlayer2',
|
|
||||||
node_info.interfaces[0],
|
|
||||||
self.on_dbus_method_call,
|
|
||||||
self.on_dbus_get_property,
|
|
||||||
self.on_dbus_set_property,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: I have no idea what to do here.
|
|
||||||
def dbus_name_lost(self, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_dbus_method_call(
|
def on_dbus_method_call(
|
||||||
self,
|
self,
|
||||||
connection,
|
connection,
|
||||||
@@ -303,129 +294,6 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
print('Unknown method:', method)
|
print('Unknown method:', method)
|
||||||
invocation.return_value(method(*params) if callable(method) else None)
|
invocation.return_value(method(*params) if callable(method) else None)
|
||||||
|
|
||||||
def on_dbus_get_property(
|
|
||||||
self,
|
|
||||||
connection,
|
|
||||||
sender,
|
|
||||||
path,
|
|
||||||
interface,
|
|
||||||
property_name,
|
|
||||||
):
|
|
||||||
second_microsecond_conversion = 1000000
|
|
||||||
has_current_song = self.state.current_song is not None
|
|
||||||
has_next_song = False
|
|
||||||
if self.state.repeat_type in (RepeatType.REPEAT_QUEUE,
|
|
||||||
RepeatType.REPEAT_SONG):
|
|
||||||
has_next_song = True
|
|
||||||
elif has_current_song and self.state.current_song.id in self.state.play_queue:
|
|
||||||
current = self.state.play_queue.index(self.state.current_song.id)
|
|
||||||
has_next_song = current < len(self.state.play_queue) - 1
|
|
||||||
|
|
||||||
response_map = {
|
|
||||||
'org.mpris.MediaPlayer2': {
|
|
||||||
'CanQuit': True,
|
|
||||||
'CanRaise': True,
|
|
||||||
'HasTrackList': True,
|
|
||||||
'Identity': 'Libremsonic',
|
|
||||||
# TODO should implement in #29
|
|
||||||
'DesktopEntry': 'foo',
|
|
||||||
'SupportedUriSchemes': [],
|
|
||||||
'SupportedMimeTypes': [],
|
|
||||||
},
|
|
||||||
'org.mpris.MediaPlayer2.Player': {
|
|
||||||
'PlaybackStatus': {
|
|
||||||
(False, False): 'Stopped',
|
|
||||||
(False, True): 'Stopped',
|
|
||||||
(True, False): 'Paused',
|
|
||||||
(True, True): 'Playing',
|
|
||||||
}[self.player.song_loaded, self.state.playing],
|
|
||||||
'LoopStatus':
|
|
||||||
self.state.repeat_type.as_mpris_loop_status(),
|
|
||||||
'Rate':
|
|
||||||
1.0,
|
|
||||||
'Shuffle':
|
|
||||||
self.state.shuffle_on,
|
|
||||||
'Metadata': {
|
|
||||||
'mpris:trackid':
|
|
||||||
self.state.current_song.id,
|
|
||||||
'mpris:length':
|
|
||||||
GLib.Variant(
|
|
||||||
'i',
|
|
||||||
self.state.current_song.duration
|
|
||||||
* second_microsecond_conversion,
|
|
||||||
),
|
|
||||||
# TODO this won't work. Need to get the cached version or
|
|
||||||
# give a URL which downloads from the server.
|
|
||||||
'mpris:artUrl':
|
|
||||||
self.state.current_song.coverArt,
|
|
||||||
'xesam:album':
|
|
||||||
self.state.current_song.album,
|
|
||||||
'xesam:albumArtist': [self.state.current_song.artist],
|
|
||||||
'xesam:artist': [self.state.current_song.artist],
|
|
||||||
'xesam:title':
|
|
||||||
self.state.current_song.title,
|
|
||||||
} if self.state.current_song else {},
|
|
||||||
'Volume':
|
|
||||||
self.state.volume,
|
|
||||||
'Position':
|
|
||||||
GLib.Variant(
|
|
||||||
'x',
|
|
||||||
int(
|
|
||||||
self.state.song_progress
|
|
||||||
* second_microsecond_conversion),
|
|
||||||
),
|
|
||||||
'MinimumRate':
|
|
||||||
1.0,
|
|
||||||
'MaximumRate':
|
|
||||||
1.0,
|
|
||||||
'CanGoNext':
|
|
||||||
has_current_song and has_next_song,
|
|
||||||
'CanGoPrevious':
|
|
||||||
has_current_song,
|
|
||||||
'CanPlay':
|
|
||||||
True,
|
|
||||||
'CanPause':
|
|
||||||
True,
|
|
||||||
'CanSeek':
|
|
||||||
True,
|
|
||||||
'CanControl':
|
|
||||||
True,
|
|
||||||
},
|
|
||||||
'org.mpris.MediaPlayer2.TrackList': {
|
|
||||||
'Tracks': self.state.play_queue,
|
|
||||||
'CanEditTracks': self.state.play_queue,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
response = response_map.get(interface, {}).get(property_name)
|
|
||||||
if response is None:
|
|
||||||
print('get FAILED', interface, property_name)
|
|
||||||
# TODO finish implementing all of this
|
|
||||||
if callable(response):
|
|
||||||
response = response()
|
|
||||||
|
|
||||||
if type(response) == dict:
|
|
||||||
return GLib.Variant(
|
|
||||||
'a{sv}',
|
|
||||||
{
|
|
||||||
k:
|
|
||||||
v if isinstance(v, GLib.Variant) else GLib.Variant('s', v)
|
|
||||||
for k, v in response.items()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
elif type(response) == list:
|
|
||||||
return GLib.Variant('as', response)
|
|
||||||
elif type(response) == str:
|
|
||||||
return GLib.Variant('s', response)
|
|
||||||
elif type(response) == int:
|
|
||||||
return GLib.Variant('i', response)
|
|
||||||
elif type(response) == float:
|
|
||||||
return GLib.Variant('d', response)
|
|
||||||
elif type(response) == bool:
|
|
||||||
return GLib.Variant('b', response)
|
|
||||||
else:
|
|
||||||
return response
|
|
||||||
|
|
||||||
def on_dbus_set_property(
|
def on_dbus_set_property(
|
||||||
self,
|
self,
|
||||||
connection,
|
connection,
|
||||||
@@ -462,6 +330,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
setter(value)
|
setter(value)
|
||||||
|
|
||||||
# ########## ACTION HANDLERS ########## #
|
# ########## ACTION HANDLERS ########## #
|
||||||
|
@dbus_propagate()
|
||||||
def on_refresh_window(self, _, state_updates, force=False):
|
def on_refresh_window(self, _, state_updates, force=False):
|
||||||
for k, v in state_updates.items():
|
for k, v in state_updates.items():
|
||||||
setattr(self.state, k, v)
|
setattr(self.state, k, v)
|
||||||
@@ -491,6 +360,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
self.reset_cache_manager()
|
self.reset_cache_manager()
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_play_pause(self, *args):
|
def on_play_pause(self, *args):
|
||||||
if self.state.current_song is None:
|
if self.state.current_song is None:
|
||||||
return
|
return
|
||||||
@@ -505,6 +375,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
self.state.playing = not self.state.playing
|
self.state.playing = not self.state.playing
|
||||||
self.update_window()
|
self.update_window()
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_next_track(self, *args):
|
def on_next_track(self, *args):
|
||||||
current_idx = self.state.play_queue.index(self.state.current_song.id)
|
current_idx = self.state.play_queue.index(self.state.current_song.id)
|
||||||
|
|
||||||
@@ -517,6 +388,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
|
|
||||||
self.play_song(self.state.play_queue[current_idx + 1], reset=True)
|
self.play_song(self.state.play_queue[current_idx + 1], reset=True)
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_prev_track(self, *args):
|
def on_prev_track(self, *args):
|
||||||
# TODO there is a bug where you can't go back multiple songs fast
|
# TODO there is a bug where you can't go back multiple songs fast
|
||||||
current_idx = self.state.play_queue.index(self.state.current_song.id)
|
current_idx = self.state.play_queue.index(self.state.current_song.id)
|
||||||
@@ -535,12 +407,14 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
|
|
||||||
self.play_song(self.state.play_queue[song_to_play], reset=True)
|
self.play_song(self.state.play_queue[song_to_play], reset=True)
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_repeat_press(self, action, params):
|
def on_repeat_press(self, action, params):
|
||||||
# Cycle through the repeat types.
|
# Cycle through the repeat types.
|
||||||
new_repeat_type = RepeatType((self.state.repeat_type.value + 1) % 3)
|
new_repeat_type = RepeatType((self.state.repeat_type.value + 1) % 3)
|
||||||
self.state.repeat_type = new_repeat_type
|
self.state.repeat_type = new_repeat_type
|
||||||
self.update_window()
|
self.update_window()
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_shuffle_press(self, action, params):
|
def on_shuffle_press(self, action, params):
|
||||||
if self.state.shuffle_on:
|
if self.state.shuffle_on:
|
||||||
# Revert to the old play queue.
|
# Revert to the old play queue.
|
||||||
@@ -560,6 +434,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
def on_play_queue_click(self, action, song_id):
|
def on_play_queue_click(self, action, song_id):
|
||||||
self.play_song(song_id.get_string(), reset=True)
|
self.play_song(song_id.get_string(), reset=True)
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_play_next(self, action, song_ids):
|
def on_play_next(self, action, song_ids):
|
||||||
if self.state.current_song is None:
|
if self.state.current_song is None:
|
||||||
insert_at = 0
|
insert_at = 0
|
||||||
@@ -573,6 +448,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
self.state.old_play_queue.extend(song_ids)
|
self.state.old_play_queue.extend(song_ids)
|
||||||
self.update_window()
|
self.update_window()
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_add_to_queue(self, action, song_ids):
|
def on_add_to_queue(self, action, song_ids):
|
||||||
self.state.play_queue.extend(song_ids)
|
self.state.play_queue.extend(song_ids)
|
||||||
self.state.old_play_queue.extend(song_ids)
|
self.state.old_play_queue.extend(song_ids)
|
||||||
@@ -639,6 +515,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
play_queue=song_queue,
|
play_queue=song_queue,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_song_scrub(self, _, scrub_value):
|
def on_song_scrub(self, _, scrub_value):
|
||||||
if not hasattr(self.state, 'current_song'):
|
if not hasattr(self.state, 'current_song'):
|
||||||
return
|
return
|
||||||
@@ -675,11 +552,13 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
if was_playing:
|
if was_playing:
|
||||||
self.on_play_pause()
|
self.on_play_pause()
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_mute_toggle(self, action, _):
|
def on_mute_toggle(self, action, _):
|
||||||
self.state.is_muted = not self.state.is_muted
|
self.state.is_muted = not self.state.is_muted
|
||||||
self.player.is_muted = self.state.is_muted
|
self.player.is_muted = self.state.is_muted
|
||||||
self.update_window()
|
self.update_window()
|
||||||
|
|
||||||
|
@dbus_propagate()
|
||||||
def on_volume_change(self, _, value):
|
def on_volume_change(self, _, value):
|
||||||
self.state.volume = value
|
self.state.volume = value
|
||||||
self.player.volume = self.state.volume
|
self.player.volume = self.state.volume
|
||||||
@@ -705,6 +584,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
|
|
||||||
self.state.save()
|
self.state.save()
|
||||||
self.save_play_queue()
|
self.save_play_queue()
|
||||||
|
self.dbus_manager.shutdown()
|
||||||
CacheManager.shutdown()
|
CacheManager.shutdown()
|
||||||
|
|
||||||
# ########## PROPERTIES ########## #
|
# ########## PROPERTIES ########## #
|
||||||
@@ -793,6 +673,7 @@ class LibremsonicApp(Gtk.Application):
|
|||||||
):
|
):
|
||||||
# Do this the old fashioned way so that we can have access to ``reset``
|
# Do this the old fashioned way so that we can have access to ``reset``
|
||||||
# in the callback.
|
# in the callback.
|
||||||
|
@dbus_propagate(self)
|
||||||
def do_play_song(song: Child):
|
def do_play_song(song: Child):
|
||||||
uri, stream = CacheManager.get_song_filename_or_stream(
|
uri, stream = CacheManager.get_song_filename_or_stream(
|
||||||
song,
|
song,
|
||||||
|
285
libremsonic/dbus_manager.py
Normal file
285
libremsonic/dbus_manager.py
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from deepdiff import DeepDiff
|
||||||
|
from gi.repository import Gio, GLib
|
||||||
|
|
||||||
|
from .state_manager import RepeatType
|
||||||
|
|
||||||
|
|
||||||
|
class DBusManager:
|
||||||
|
second_microsecond_conversion = 1000000
|
||||||
|
|
||||||
|
current_state = {}
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
connection,
|
||||||
|
do_on_method_call,
|
||||||
|
on_set_property,
|
||||||
|
get_state_and_player,
|
||||||
|
):
|
||||||
|
self.get_state_and_player = get_state_and_player
|
||||||
|
self.do_on_method_call = do_on_method_call
|
||||||
|
self.on_set_property = on_set_property
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
def dbus_name_acquired(connection, name):
|
||||||
|
specs = [
|
||||||
|
'org.mpris.MediaPlayer2.xml',
|
||||||
|
'org.mpris.MediaPlayer2.Player.xml',
|
||||||
|
'org.mpris.MediaPlayer2.Playlists.xml',
|
||||||
|
'org.mpris.MediaPlayer2.TrackList.xml',
|
||||||
|
]
|
||||||
|
for spec in specs:
|
||||||
|
spec_path = os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
f'ui/mpris_specs/{spec}',
|
||||||
|
)
|
||||||
|
with open(spec_path) as f:
|
||||||
|
node_info = Gio.DBusNodeInfo.new_for_xml(f.read())
|
||||||
|
|
||||||
|
connection.register_object(
|
||||||
|
'/org/mpris/MediaPlayer2',
|
||||||
|
node_info.interfaces[0],
|
||||||
|
self.on_method_call,
|
||||||
|
self.on_get_property,
|
||||||
|
self.on_set_property,
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: I have no idea what to do here.
|
||||||
|
def dbus_name_lost(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.bus_number = Gio.bus_own_name_on_connection(
|
||||||
|
connection,
|
||||||
|
'org.mpris.MediaPlayer2.libremsonic',
|
||||||
|
Gio.BusNameOwnerFlags.NONE,
|
||||||
|
dbus_name_acquired,
|
||||||
|
dbus_name_lost,
|
||||||
|
)
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
Gio.bus_unown_name(self.bus_number)
|
||||||
|
|
||||||
|
def on_get_property(
|
||||||
|
self,
|
||||||
|
connection,
|
||||||
|
sender,
|
||||||
|
path,
|
||||||
|
interface,
|
||||||
|
property_name,
|
||||||
|
):
|
||||||
|
return self.to_variant(
|
||||||
|
self.property_dict().get(
|
||||||
|
interface,
|
||||||
|
{},
|
||||||
|
).get(property_name))
|
||||||
|
|
||||||
|
def on_method_call(
|
||||||
|
self,
|
||||||
|
connection,
|
||||||
|
sender,
|
||||||
|
path,
|
||||||
|
interface,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
invocation,
|
||||||
|
):
|
||||||
|
if interface == 'org.freedesktop.DBus.Properties':
|
||||||
|
if method == 'Get':
|
||||||
|
invocation.return_value(
|
||||||
|
self.on_get_property(
|
||||||
|
connection, sender, path, interface, *params))
|
||||||
|
elif method == 'Set':
|
||||||
|
self.on_set_property(
|
||||||
|
connection, sender, path, interface, *params)
|
||||||
|
elif method == 'GetAll':
|
||||||
|
all_properties = {
|
||||||
|
k: self.to_variant(v)
|
||||||
|
for k, v in self.property_dict()[interface].items()
|
||||||
|
}
|
||||||
|
invocation.return_value(
|
||||||
|
GLib.Variant('(a{sv})', (all_properties, )))
|
||||||
|
|
||||||
|
return
|
||||||
|
self.do_on_method_call(
|
||||||
|
connection,
|
||||||
|
sender,
|
||||||
|
path,
|
||||||
|
interface,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
invocation,
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_variant(self, value):
|
||||||
|
if callable(value):
|
||||||
|
return self.to_variant(value())
|
||||||
|
|
||||||
|
if isinstance(value, GLib.Variant):
|
||||||
|
return value
|
||||||
|
|
||||||
|
if type(value) == tuple:
|
||||||
|
return GLib.Variant(*value)
|
||||||
|
|
||||||
|
if type(value) == dict:
|
||||||
|
return GLib.Variant(
|
||||||
|
'a{sv}',
|
||||||
|
{k: self.to_variant(v)
|
||||||
|
for k, v in value.items()},
|
||||||
|
)
|
||||||
|
|
||||||
|
variant_type = {
|
||||||
|
list: 'as',
|
||||||
|
str: 's',
|
||||||
|
int: 'i',
|
||||||
|
float: 'd',
|
||||||
|
bool: 'b',
|
||||||
|
}.get(type(value))
|
||||||
|
if not variant_type:
|
||||||
|
return value
|
||||||
|
return GLib.Variant(variant_type, value)
|
||||||
|
|
||||||
|
def property_dict(self):
|
||||||
|
state, player = self.get_state_and_player()
|
||||||
|
has_current_song = state.current_song is not None
|
||||||
|
has_next_song = False
|
||||||
|
if state.repeat_type in (RepeatType.REPEAT_QUEUE,
|
||||||
|
RepeatType.REPEAT_SONG):
|
||||||
|
has_next_song = True
|
||||||
|
elif has_current_song and state.current_song.id in state.play_queue:
|
||||||
|
current = state.play_queue.index(state.current_song.id)
|
||||||
|
has_next_song = current < len(state.play_queue) - 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
'org.mpris.MediaPlayer2': {
|
||||||
|
'CanQuit': True,
|
||||||
|
'CanRaise': True,
|
||||||
|
'HasTrackList': True,
|
||||||
|
'Identity': 'Libremsonic',
|
||||||
|
# TODO should implement in #29
|
||||||
|
'DesktopEntry': 'foo',
|
||||||
|
'SupportedUriSchemes': [],
|
||||||
|
'SupportedMimeTypes': [],
|
||||||
|
},
|
||||||
|
'org.mpris.MediaPlayer2.Player': {
|
||||||
|
'PlaybackStatus': {
|
||||||
|
(False, False): 'Stopped',
|
||||||
|
(False, True): 'Stopped',
|
||||||
|
(True, False): 'Paused',
|
||||||
|
(True, True): 'Playing',
|
||||||
|
}[player.song_loaded, state.playing],
|
||||||
|
'LoopStatus':
|
||||||
|
state.repeat_type.as_mpris_loop_status(),
|
||||||
|
'Rate':
|
||||||
|
1.0,
|
||||||
|
'Shuffle':
|
||||||
|
state.shuffle_on,
|
||||||
|
'Metadata': {
|
||||||
|
'mpris:trackid':
|
||||||
|
state.current_song.id,
|
||||||
|
'mpris:length': (
|
||||||
|
'i',
|
||||||
|
(state.current_song.duration or 0)
|
||||||
|
* self.second_microsecond_conversion,
|
||||||
|
),
|
||||||
|
# TODO this won't work. Need to get the cached version or
|
||||||
|
# give a URL which downloads from the server.
|
||||||
|
'mpris:artUrl':
|
||||||
|
state.current_song.coverArt,
|
||||||
|
'xesam:album':
|
||||||
|
state.current_song.album,
|
||||||
|
'xesam:albumArtist': [state.current_song.artist],
|
||||||
|
'xesam:artist': [state.current_song.artist],
|
||||||
|
'xesam:title':
|
||||||
|
state.current_song.title,
|
||||||
|
} if state.current_song else {},
|
||||||
|
'Volume':
|
||||||
|
0.0 if state.is_muted else state.volume,
|
||||||
|
'Position': (
|
||||||
|
'x',
|
||||||
|
int(
|
||||||
|
(state.song_progress or 0)
|
||||||
|
* self.second_microsecond_conversion),
|
||||||
|
),
|
||||||
|
'MinimumRate':
|
||||||
|
1.0,
|
||||||
|
'MaximumRate':
|
||||||
|
1.0,
|
||||||
|
'CanGoNext':
|
||||||
|
has_current_song and has_next_song,
|
||||||
|
'CanGoPrevious':
|
||||||
|
has_current_song,
|
||||||
|
'CanPlay':
|
||||||
|
True,
|
||||||
|
'CanPause':
|
||||||
|
True,
|
||||||
|
'CanSeek':
|
||||||
|
True,
|
||||||
|
'CanControl':
|
||||||
|
True,
|
||||||
|
},
|
||||||
|
'org.mpris.MediaPlayer2.TrackList': {
|
||||||
|
'Tracks': state.play_queue,
|
||||||
|
'CanEditTracks': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
diff_parse_re = re.compile(r"root\['(.*?)'\]\['(.*?)'\](?:\[.*\])?")
|
||||||
|
|
||||||
|
def property_diff(self):
|
||||||
|
new_property_dict = self.property_dict()
|
||||||
|
diff = DeepDiff(self.current_state, new_property_dict)
|
||||||
|
|
||||||
|
if 'dictionary_item_added' in diff.keys():
|
||||||
|
for interface, property_dict in new_property_dict.items():
|
||||||
|
self.connection.emit_signal(
|
||||||
|
None,
|
||||||
|
'/org/mpris/MediaPlayer2',
|
||||||
|
'org.freedesktop.DBus.Properties',
|
||||||
|
'PropertiesChanged',
|
||||||
|
GLib.Variant(
|
||||||
|
'(sa{sv}as)', (
|
||||||
|
interface,
|
||||||
|
self.to_variant(property_dict),
|
||||||
|
self.to_variant([]),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
self.current_state = new_property_dict
|
||||||
|
return
|
||||||
|
|
||||||
|
changes = defaultdict(dict)
|
||||||
|
|
||||||
|
for path, change in diff.get('values_changed', {}).items():
|
||||||
|
interface, property_name = self.diff_parse_re.match(path).groups()
|
||||||
|
changes[interface][property_name] = change['new_value']
|
||||||
|
|
||||||
|
for interface, changed_props in changes.items():
|
||||||
|
# If the metadata has changed, just make the entire Metadata object
|
||||||
|
# part of the update.
|
||||||
|
if 'Metadata' in changed_props.keys():
|
||||||
|
changed_props['Metadata'] = new_property_dict[interface][
|
||||||
|
'Metadata']
|
||||||
|
|
||||||
|
if 'Position' in changed_props.keys():
|
||||||
|
del changed_props['Position']
|
||||||
|
|
||||||
|
print(interface, changed_props)
|
||||||
|
print(self.to_variant(changed_props))
|
||||||
|
self.connection.emit_signal(
|
||||||
|
None,
|
||||||
|
'/org/mpris/MediaPlayer2',
|
||||||
|
'org.freedesktop.DBus.Properties',
|
||||||
|
'PropertiesChanged',
|
||||||
|
GLib.Variant(
|
||||||
|
"(sa{sv}as)", (
|
||||||
|
interface,
|
||||||
|
self.to_variant(changed_props),
|
||||||
|
self.to_variant([]),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.current_state = new_property_dict
|
Reference in New Issue
Block a user