diff --git a/emmental/__init__.py b/emmental/__init__.py index aa8e587..03e9420 100644 --- a/emmental/__init__.py +++ b/emmental/__init__.py @@ -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.""" diff --git a/tests/test_emmental.py b/tests/test_emmental.py index 53e1c0c..83bb0a2 100644 --- a/tests/test_emmental.py +++ b/tests/test_emmental.py @@ -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