emmental: Wire up the Next and Previous buttons

And connect to the Player EOS and about-to-finish signals so we can
select the next track when the current one finishes (or slightly before
for gapless playback).

Implements: #7 (Add MPRIS2 Support)
Implements: #48 (Implement Intelligent ReplayGain)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2023-04-23 09:21:03 -04:00
parent a687b564a9
commit 0d27a09233
2 changed files with 65 additions and 9 deletions

View File

@ -40,20 +40,34 @@ class Application(Adw.Application):
flags=Gio.ApplicationFlags.HANDLES_OPEN)
self.add_main_option_entries([options.Version])
def __load_file(self, file: pathlib.Path) -> None:
def __load_file(self, file: pathlib.Path,
*, gapless: bool = False) -> None:
self.__stop_current_track()
self.player.stop()
self.player.file = file
self.player.play()
if gapless:
self.player.file = file
else:
self.player.stop()
self.player.file = file
self.player.play()
def __load_path(self, src: GObject.GObject, path: pathlib.Path) -> None:
if (track := self.db.tracks.lookup(path=path)) is not None:
self.__load_track(track)
self.__load_file(path)
def __load_track(self, track: GObject.GObject) -> None:
self.__load_file(track.path)
track.start()
def __load_track(self, track: GObject.GObject, *, gapless: bool = False,
rg_auto: str = "track", restart: bool = False) -> None:
self.__load_file(track.path, gapless=gapless)
if restart:
track.restart()
else:
track.start()
self.__set_replaygain(rg_auto=rg_auto)
def __pick_next_track(self, *, user: bool, gapless: bool = False) -> None:
(track, rg_auto, restart) = self.factory.next_track(user=user)
self.__load_track(track, gapless=gapless,
rg_auto=rg_auto, restart=restart)
def __on_seek(self, nowplay: nowplaying.Card, newpos: float) -> None:
"""Handle a seek event."""
@ -67,16 +81,29 @@ class Application(Adw.Application):
trackid: str, position: float) -> None:
self.player.seek(position)
def __set_replaygain(self, *args) -> None:
def __set_replaygain(self, *args, rg_auto="track") -> None:
enabled = self.db.settings["audio.replaygain.enabled"]
mode = self.db.settings["audio.replaygain.mode"]
mode = "track" if mode == "auto" else mode
mode = rg_auto if mode == "auto" else mode
self.player.set_replaygain(enabled, mode)
def __stop_current_track(self) -> None:
if self.db.tracks.current_track is not None:
self.db.tracks.current_track.stop(self.player.playtime)
def __system_next(self, player: audio.Player, gapless: bool) -> None:
self.player.pause_on_load = self.autopause == 0
if self.autopause >= 0:
self.autopause -= 1
self.__pick_next_track(user=False, gapless=gapless)
def __user_next(self, *args) -> None:
self.__pick_next_track(user=True)
def __user_previous(self, *args) -> None:
self.__load_track(self.factory.previous_track(),
rg_auto="track", restart=True)
def __tracks_table_loaded(self, track_table, param) -> None:
if track_table.current_track is not None:
self.player.file = track_table.current_track.path
@ -110,12 +137,16 @@ class Application(Adw.Application):
playing, "have-db-track")
self.db.tracks.bind_property("current-favorite", playing, "favorite",
GObject.BindingFlags.BIDIRECTIONAL)
self.factory.bind_property("can-go-next", playing, "have-next")
self.factory.bind_property("can-go-previous", playing, "have-previous")
self.db.settings.bind_setting("now-playing.prefer-artist",
playing, "prefer-artist")
playing.connect("play", self.player.play)
playing.connect("pause", self.player.pause)
playing.connect("seek", self.__on_seek)
playing.connect("next", self.__user_next)
playing.connect("previous", self.__user_previous)
return playing
def build_sidebar(self) -> sidebar.Card:
@ -159,8 +190,14 @@ class Application(Adw.Application):
("active-loop", "LoopStatus")]:
self.factory.bind_property(prop, self.mpris.player, mpris_prop,
GObject.BindingFlags.BIDIRECTIONAL)
for (prop, mpris_prop) in [("can-go-next", "CanGoNext"),
("can-go-previous", "CanGoPrevious")]:
self.factory.bind_property(prop, self.mpris.player, mpris_prop)
self.mpris.player.link_property("Volume", self.win.header, "volume")
self.mpris.player.connect("OpenPath", self.__load_path)
self.mpris.player.connect("Next", self.__user_next)
self.mpris.player.connect("Previous", self.__user_previous)
self.mpris.player.connect("Pause", self.player.pause)
self.mpris.player.connect("Play", self.player.play)
self.mpris.player.connect("PlayPause", self.player.play_pause)
@ -175,6 +212,11 @@ class Application(Adw.Application):
self.win.sidebar.bind_property("selected-playlist",
self.factory, "db-visible")
def connect_player(self) -> None:
"""Connect the audio.Player."""
self.player.connect("about-to-finish", self.__system_next, True)
self.player.connect("eos", self.__system_next, False)
def do_handle_local_options(self, opts: GLib.VariantDict) -> int:
"""Handle any command line options."""
if opts.contains("version"):
@ -201,6 +243,7 @@ class Application(Adw.Application):
self.add_window(self.win)
self.connect_mpris2()
self.connect_playlist_factory()
self.connect_player()
def do_activate(self) -> None:
"""Handle the Adw.Application::activate signal."""

View File

@ -100,6 +100,8 @@ class TestEmmental(unittest.TestCase):
def test_window_widgets(self, mock_stdout: io.StringIO):
"""Check that the window widgets are added properly."""
self.application.db = emmental.db.Connection()
self.application.factory = emmental.playlist.Factory(
self.application.db)
self.application.player = emmental.audio.Player()
win = self.application.build_window()
@ -118,6 +120,8 @@ class TestEmmental(unittest.TestCase):
pause_func: unittest.mock.Mock):
"""Check that the nowplaying widget is wired up properly."""
self.application.db = emmental.db.Connection()
self.application.factory = emmental.playlist.Factory(
self.application.db)
self.application.player = emmental.audio.Player()
win = self.application.build_window()
@ -131,6 +135,11 @@ class TestEmmental(unittest.TestCase):
self.application.db.tracks.have_current_track = True
self.assertTrue(win.now_playing.have_db_track)
self.application.factory.can_go_next = True
self.assertTrue(win.now_playing.have_next)
self.application.factory.can_go_previous = True
self.assertTrue(win.now_playing.have_previous)
win.now_playing.emit("play")
play_func.assert_called()
win.now_playing.emit("pause")
@ -152,6 +161,8 @@ class TestEmmental(unittest.TestCase):
def test_sidebar(self, mock_stdout: io.StringIO):
"""Check that the sidebar widget is wired up properly."""
self.application.db = emmental.db.Connection()
self.application.factory = emmental.playlist.Factory(
self.application.db)
self.application.player = emmental.audio.Player()
win = self.application.build_window()
@ -180,6 +191,8 @@ class TestEmmental(unittest.TestCase):
def test_replaygain(self, mock_stdout: io.StringIO):
"""Test setting replaygain modes."""
self.application.db = emmental.db.Connection()
self.application.factory = emmental.playlist.Factory(
self.application.db)
self.application.player = emmental.audio.Player()
win = self.application.build_window()
player = self.application.player