audio: Calculate the play time for the current track
I am going to use this to determine if a track has been played or not. Gstreamer resets the clock when seeking, so I do some extra work to save the play time just before seeking and add it back later. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
1c0712e673
commit
4be92c7326
|
@ -31,6 +31,8 @@ class Player(GObject.GObject):
|
||||||
playing = GObject.Property(type=bool, default=False)
|
playing = GObject.Property(type=bool, default=False)
|
||||||
status = GObject.Property(type=str, default="Stopped")
|
status = GObject.Property(type=str, default="Stopped")
|
||||||
have_track = GObject.Property(type=bool, default=False)
|
have_track = GObject.Property(type=bool, default=False)
|
||||||
|
playtime = GObject.Property(type=float)
|
||||||
|
savedtime = GObject.Property(type=float)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the audio Player."""
|
"""Initialize the audio Player."""
|
||||||
|
@ -56,6 +58,12 @@ class Player(GObject.GObject):
|
||||||
|
|
||||||
self.connect("notify::file", self.__notify_file)
|
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:
|
def __msg_async_done(self, bus: Gst.Bus, message: Gst.Message) -> None:
|
||||||
self.__update_position()
|
self.__update_position()
|
||||||
|
|
||||||
|
@ -121,7 +129,8 @@ class Player(GObject.GObject):
|
||||||
artwork: pathlib.Path | None = None) -> None:
|
artwork: pathlib.Path | None = None) -> None:
|
||||||
for tag in ["artist", "album-artist", "album", "title"]:
|
for tag in ["artist", "album-artist", "album", "title"]:
|
||||||
self.set_property(tag, "")
|
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.set_property(tag, 0)
|
||||||
self.artwork = artwork
|
self.artwork = artwork
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
@ -129,6 +138,7 @@ class Player(GObject.GObject):
|
||||||
def __update_position(self) -> bool:
|
def __update_position(self) -> bool:
|
||||||
(res, pos) = self._playbin.query_position(Gst.Format.TIME)
|
(res, pos) = self._playbin.query_position(Gst.Format.TIME)
|
||||||
self.position = pos / Gst.USECOND if res else 0
|
self.position = pos / Gst.USECOND if res else 0
|
||||||
|
self.playtime = self.__get_current_playtime() + self.savedtime
|
||||||
return GLib.SOURCE_CONTINUE
|
return GLib.SOURCE_CONTINUE
|
||||||
|
|
||||||
def __update_timeout(self) -> None:
|
def __update_timeout(self) -> None:
|
||||||
|
@ -163,6 +173,7 @@ class Player(GObject.GObject):
|
||||||
|
|
||||||
def seek(self, newpos: float, *args) -> None:
|
def seek(self, newpos: float, *args) -> None:
|
||||||
"""Seek to a different point in the stream."""
|
"""Seek to a different point in the stream."""
|
||||||
|
self.savedtime += self.__get_current_playtime()
|
||||||
self._playbin.seek_simple(Gst.Format.TIME, SEEK_FLAGS,
|
self._playbin.seek_simple(Gst.Format.TIME, SEEK_FLAGS,
|
||||||
newpos * Gst.USECOND)
|
newpos * Gst.USECOND)
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,8 @@ class TestAudio(unittest.TestCase):
|
||||||
self.player.file = tests.util.TRACK_OGG
|
self.player.file = tests.util.TRACK_OGG
|
||||||
self.player.duration = 10
|
self.player.duration = 10
|
||||||
self.player.position = 8
|
self.player.position = 8
|
||||||
|
self.player.playtime = 6
|
||||||
|
self.player.savedtime = 4
|
||||||
self.player.artwork = pathlib.Path("/a/b/c.jpg")
|
self.player.artwork = pathlib.Path("/a/b/c.jpg")
|
||||||
|
|
||||||
eos = Gst.Message.new_eos(self.player._playbin)
|
eos = Gst.Message.new_eos(self.player._playbin)
|
||||||
|
@ -70,7 +72,7 @@ class TestAudio(unittest.TestCase):
|
||||||
for prop in ["artist", "album-artist", "album", "title"]:
|
for prop in ["artist", "album-artist", "album", "title"]:
|
||||||
self.assertEqual(self.player.get_property(prop), "")
|
self.assertEqual(self.player.get_property(prop), "")
|
||||||
for prop in ["album-disc-number", "track-number",
|
for prop in ["album-disc-number", "track-number",
|
||||||
"position", "duration"]:
|
"position", "duration", "playtime", "savedtime"]:
|
||||||
self.assertEqual(self.player.get_property(prop), 0)
|
self.assertEqual(self.player.get_property(prop), 0)
|
||||||
self.assertIsNone(self.player.artwork)
|
self.assertIsNone(self.player.artwork)
|
||||||
|
|
||||||
|
@ -100,10 +102,13 @@ class TestAudio(unittest.TestCase):
|
||||||
"audio: file loaded\n"
|
"audio: file loaded\n"
|
||||||
"audio: state changed to 'paused'\n")
|
"audio: state changed to 'paused'\n")
|
||||||
|
|
||||||
|
self.player.playtime = 6
|
||||||
|
self.player.savedtime = 4
|
||||||
self.player.emit("file-loaded", tests.util.TRACK_OGG)
|
self.player.emit("file-loaded", tests.util.TRACK_OGG)
|
||||||
for prop in ["artist", "album-artist", "album", "title"]:
|
for prop in ["artist", "album-artist", "album", "title"]:
|
||||||
self.assertEqual(self.player.get_property(prop), "")
|
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)
|
self.assertEqual(self.player.get_property(prop), 0)
|
||||||
|
|
||||||
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
|
||||||
|
@ -226,6 +231,34 @@ class TestAudio(unittest.TestCase):
|
||||||
emmental.audio.SEEK_FLAGS,
|
emmental.audio.SEEK_FLAGS,
|
||||||
5 * Gst.SECOND)
|
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):
|
def test_volume(self):
|
||||||
"""Test that the volume property works as expected."""
|
"""Test that the volume property works as expected."""
|
||||||
self.assertEqual(self.player.volume, 1.0)
|
self.assertEqual(self.player.volume, 1.0)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user