mpris2: Add a Player object
This begins to implement the MediaPlayer2.Player interface. The properties and signals are there, and I expect to fully implement them as Emmental development goes on. Implements: #7 ("Add MPRIS2 Support") Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
93dc476706
commit
d105b15e02
5
Makefile
5
Makefile
|
@ -26,8 +26,11 @@ mpris-spec/Makefile:
|
|||
emmental/mpris2/MediaPlayer2.xml: mpris-spec/Makefile
|
||||
cp mpris-spec/spec/org.mpris.MediaPlayer2.xml emmental/mpris2/MediaPlayer2.xml
|
||||
|
||||
emmental/mpris2/Player.xml: mpris-spec/Makefile
|
||||
cp mpris-spec/spec/org.mpris.MediaPlayer2.Player.xml emmental/mpris2/Player.xml
|
||||
|
||||
.PHONY: mpris2
|
||||
mpris2: emmental/mpris2/MediaPlayer2.xml
|
||||
mpris2: emmental/mpris2/MediaPlayer2.xml emmental/mpris2/Player.xml
|
||||
|
||||
.PHONY: emmental.gresource.xml
|
||||
emmental.gresource.xml:
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from . import application
|
||||
from . import player
|
||||
|
||||
MPRIS2_ID = f"org.mpris.MediaPlayer2.emmental{'-debug' if __debug__ else ''}"
|
||||
|
||||
|
@ -16,8 +17,10 @@ class Connection(GObject.GObject):
|
|||
"""Initialize Mpris2."""
|
||||
super().__init__()
|
||||
self.app = application.Application()
|
||||
self.player = player.Player()
|
||||
|
||||
self.bind_property("dbus", self.app, "dbus")
|
||||
self.bind_property("dbus", self.player, "dbus")
|
||||
|
||||
self._busid = Gio.bus_own_name(Gio.BusType.SESSION, MPRIS2_ID,
|
||||
Gio.BusNameOwnerFlags.NONE,
|
||||
|
@ -31,14 +34,17 @@ class Connection(GObject.GObject):
|
|||
def __on_bus_acquired(self, dbus: Gio.DBusConnection, name: str) -> None:
|
||||
self.dbus = dbus
|
||||
self.app.register(dbus)
|
||||
self.player.register(dbus)
|
||||
|
||||
def __on_name_lost(self, dbus: Gio.DBusConnection, name: str) -> None:
|
||||
self.app.unregister(dbus)
|
||||
self.player.unregister(dbus)
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnect from dbus."""
|
||||
if self.dbus:
|
||||
self.app.unregister(self.dbus)
|
||||
self.player.unregister(self.dbus)
|
||||
self.dbus = None
|
||||
|
||||
if self._busid:
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""Our Mpris2 Player dbus Object."""
|
||||
import pathlib
|
||||
from gi.repository import GObject
|
||||
from . import dbus
|
||||
|
||||
PLAYER_XML = pathlib.Path(__file__).parent / "Player.xml"
|
||||
|
||||
|
||||
class Player(dbus.Object):
|
||||
"""The mpris2 Player dbus object."""
|
||||
|
||||
PlaybackStatus = GObject.Property(type=str, default="Stopped")
|
||||
LoopStatus = GObject.Property(type=str, default="None")
|
||||
Rate = GObject.Property(type=float, default=1.0)
|
||||
Shuffle = GObject.Property(type=bool, default=False)
|
||||
Volume = GObject.Property(type=float, default=1.0)
|
||||
Position = GObject.Property(type=float, default=0)
|
||||
MinimumRate = GObject.Property(type=float, default=1.0)
|
||||
MaximumRate = GObject.Property(type=float, default=1.0)
|
||||
CanGoNext = GObject.Property(type=bool, default=False)
|
||||
CanGoPrevious = GObject.Property(type=bool, default=False)
|
||||
CanPlay = GObject.Property(type=bool, default=False)
|
||||
CanPause = GObject.Property(type=bool, default=False)
|
||||
CanSeek = GObject.Property(type=bool, default=False)
|
||||
CanControl = GObject.Property(type=bool, default=True)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the mpris2 application object."""
|
||||
super().__init__(xml=PLAYER_XML)
|
||||
|
||||
@GObject.Property
|
||||
def Metadata(self) -> dict:
|
||||
"""Metadata for the current Track."""
|
||||
return {}
|
||||
|
||||
@GObject.Signal
|
||||
def Next(self) -> None:
|
||||
"""Skip to the next track."""
|
||||
|
||||
@GObject.Signal
|
||||
def Previous(self) -> None:
|
||||
"""Skip to the previous track."""
|
||||
|
||||
@GObject.Signal
|
||||
def Pause(self) -> None:
|
||||
"""Pause playback."""
|
||||
|
||||
@GObject.Signal
|
||||
def PlayPause(self) -> None:
|
||||
"""Toggle playback status."""
|
||||
|
||||
@GObject.Signal
|
||||
def Stop(self) -> None:
|
||||
"""Stop playback."""
|
||||
|
||||
@GObject.Signal
|
||||
def Play(self) -> None:
|
||||
"""Start or resume playback."""
|
||||
|
||||
@GObject.Signal(arg_types=(float,))
|
||||
def Seek(self, offset: float) -> None:
|
||||
"""Seek forward or backward by the given offset."""
|
||||
|
||||
@GObject.Signal(arg_types=(str, float))
|
||||
def SetPosition(self, trackid: str, position: float) -> None:
|
||||
"""Set the current track position in microseconds."""
|
||||
|
||||
@GObject.Signal(arg_types=(str,))
|
||||
def OpenUri(self, uri: str) -> None:
|
||||
"""Open the given uri."""
|
|
@ -20,6 +20,8 @@ class TestMpris2(unittest.TestCase):
|
|||
self.assertIsInstance(self.mpris2, emmental.mpris2.GObject.GObject)
|
||||
self.assertIsInstance(self.mpris2.app,
|
||||
emmental.mpris2.application.Application)
|
||||
self.assertIsInstance(self.mpris2.player,
|
||||
emmental.mpris2.player.Player)
|
||||
self.assertIsNone(self.mpris2.dbus)
|
||||
self.assertGreater(self.mpris2._busid, 0)
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# Copyright 2022 (c) Anna Schumaker.
|
||||
"""Tests our Mpris Player object."""
|
||||
import pathlib
|
||||
import unittest
|
||||
import emmental.mpris2.player
|
||||
from gi.repository import Gio
|
||||
|
||||
|
||||
class TestPlayer(unittest.TestCase):
|
||||
"""Test the mpris2 player object."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.player = emmental.mpris2.player.Player()
|
||||
|
||||
def test_xml_files(self):
|
||||
"""Test that the interface definition files exist."""
|
||||
mpris2 = pathlib.Path(emmental.mpris2.__file__).parent
|
||||
self.assertEqual(emmental.mpris2.player.PLAYER_XML,
|
||||
mpris2 / "Player.xml")
|
||||
self.assertTrue(emmental.mpris2.player.PLAYER_XML.is_file())
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the application object is configured correctly."""
|
||||
self.assertIsInstance(self.player, emmental.mpris2.dbus.Object)
|
||||
self.assertIsInstance(self.player.nodeinfo, Gio.DBusNodeInfo)
|
||||
self.assertIsInstance(self.player.interface, Gio.DBusInterfaceInfo)
|
||||
|
||||
def test_properties(self):
|
||||
"""Test Player properties."""
|
||||
self.assertEqual(self.player.PlaybackStatus, "Stopped")
|
||||
self.assertEqual(self.player.LoopStatus, "None")
|
||||
self.assertEqual(self.player.Rate, 1.0)
|
||||
self.assertFalse(self.player.Shuffle)
|
||||
self.assertEqual(self.player.Volume, 1.0)
|
||||
self.assertEqual(self.player.Position, 0)
|
||||
self.assertDictEqual(self.player.Metadata, {})
|
||||
self.assertEqual(self.player.MinimumRate, 1.0)
|
||||
self.assertEqual(self.player.MaximumRate, 1.0)
|
||||
self.assertFalse(self.player.CanGoNext)
|
||||
self.assertFalse(self.player.CanGoPrevious)
|
||||
self.assertFalse(self.player.CanPlay)
|
||||
self.assertFalse(self.player.CanPause)
|
||||
self.assertFalse(self.player.CanSeek)
|
||||
self.assertTrue(self.player.CanControl)
|
||||
|
||||
def test_signals(self):
|
||||
"""Test that Player signals exist."""
|
||||
self.player.emit("Next")
|
||||
self.player.emit("Previous")
|
||||
self.player.emit("Pause")
|
||||
self.player.emit("PlayPause")
|
||||
self.player.emit("Stop")
|
||||
self.player.emit("Play")
|
||||
self.player.emit("Seek", 12345)
|
||||
self.player.emit("SetPosition",
|
||||
"/com/nowheycreamery/emmental/123", 12345)
|
||||
self.player.emit("OpenUri", "/a/b/c.ogg")
|
Loading…
Reference in New Issue