diff --git a/emmental/audio/__init__.py b/emmental/audio/__init__.py index 92e9ceb..650d16f 100644 --- a/emmental/audio/__init__.py +++ b/emmental/audio/__init__.py @@ -31,6 +31,8 @@ class Player(GObject.GObject): playing = GObject.Property(type=bool, default=False) status = GObject.Property(type=str, default="Stopped") have_track = GObject.Property(type=bool, default=False) + playtime = GObject.Property(type=float) + savedtime = GObject.Property(type=float) def __init__(self): """Initialize the audio Player.""" @@ -56,6 +58,12 @@ class Player(GObject.GObject): self.connect("notify::file", self.__notify_file) + def __get_current_playtime(self) -> float: + if not self._playbin.clock: + return 0.0 + time = self._playbin.clock.get_time() - self._playbin.base_time + return time / Gst.SECOND + def __msg_async_done(self, bus: Gst.Bus, message: Gst.Message) -> None: self.__update_position() @@ -121,7 +129,8 @@ class Player(GObject.GObject): artwork: pathlib.Path | None = None) -> None: for tag in ["artist", "album-artist", "album", "title"]: self.set_property(tag, "") - for tag in ["album-disc-number", "track-number", "position"]: + for tag in ["album-disc-number", "track-number", + "position", "playtime", "savedtime"]: self.set_property(tag, 0) self.artwork = artwork self.duration = duration @@ -129,6 +138,7 @@ class Player(GObject.GObject): def __update_position(self) -> bool: (res, pos) = self._playbin.query_position(Gst.Format.TIME) self.position = pos / Gst.USECOND if res else 0 + self.playtime = self.__get_current_playtime() + self.savedtime return GLib.SOURCE_CONTINUE def __update_timeout(self) -> None: @@ -163,6 +173,7 @@ class Player(GObject.GObject): def seek(self, newpos: float, *args) -> None: """Seek to a different point in the stream.""" + self.savedtime += self.__get_current_playtime() self._playbin.seek_simple(Gst.Format.TIME, SEEK_FLAGS, newpos * Gst.USECOND) diff --git a/tests/audio/test_audio.py b/tests/audio/test_audio.py index 49db631..ed14afc 100644 --- a/tests/audio/test_audio.py +++ b/tests/audio/test_audio.py @@ -59,6 +59,8 @@ class TestAudio(unittest.TestCase): self.player.file = tests.util.TRACK_OGG self.player.duration = 10 self.player.position = 8 + self.player.playtime = 6 + self.player.savedtime = 4 self.player.artwork = pathlib.Path("/a/b/c.jpg") eos = Gst.Message.new_eos(self.player._playbin) @@ -70,7 +72,7 @@ class TestAudio(unittest.TestCase): for prop in ["artist", "album-artist", "album", "title"]: self.assertEqual(self.player.get_property(prop), "") for prop in ["album-disc-number", "track-number", - "position", "duration"]: + "position", "duration", "playtime", "savedtime"]: self.assertEqual(self.player.get_property(prop), 0) self.assertIsNone(self.player.artwork) @@ -100,10 +102,13 @@ class TestAudio(unittest.TestCase): "audio: file loaded\n" "audio: state changed to 'paused'\n") + self.player.playtime = 6 + self.player.savedtime = 4 self.player.emit("file-loaded", tests.util.TRACK_OGG) for prop in ["artist", "album-artist", "album", "title"]: self.assertEqual(self.player.get_property(prop), "") - for prop in ["album-disc-number", "track-number"]: + for prop in ["album-disc-number", "track-number", + "playtime", "savedtime"]: self.assertEqual(self.player.get_property(prop), 0) @unittest.mock.patch("sys.stdout", new_callable=io.StringIO) @@ -226,6 +231,34 @@ class TestAudio(unittest.TestCase): emmental.audio.SEEK_FLAGS, 5 * Gst.SECOND) + @unittest.mock.patch("sys.stdout", new_callable=io.StringIO) + def test_playtime(self, mock_stdout: io.StringIO): + """Test the play time property.""" + self.assertEqual(self.player.playtime, 0.0) + self.assertEqual(self.player.savedtime, 0.0) + self.assertEqual(self.player._Player__get_current_playtime(), 0.0) + + self.player.file = tests.util.TRACK_OGG + self.player.play() + self.main_loop() + self.assertIsNotNone(self.player._playbin.clock) + + base_time = unittest.mock.PropertyMock(return_value=Gst.SECOND) + type(self.player._playbin).base_time = base_time + get_time = unittest.mock.Mock(return_value=3 * Gst.SECOND) + self.player._playbin.clock.get_time = get_time + + self.assertEqual(self.player._Player__get_current_playtime(), 2.0) + self.player._Player__update_position() + self.assertEqual(self.player.playtime, 2) + + with unittest.mock.patch.object(self.player._playbin, "seek_simple"): + self.player.seek(5) + self.assertEqual(self.player.savedtime, 2) + + self.player._Player__update_position() + self.assertEqual(self.player.playtime, 4) + def test_volume(self): """Test that the volume property works as expected.""" self.assertEqual(self.player.volume, 1.0)