audio: Give the BassPlayer a position property
I use the state change callbacks to send a position-changed signal on a regular interval when playback is going. Seeking is handled by setting the position property to a new value. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
a4595eab93
commit
fa2cbcc261
|
@ -1,5 +1,6 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
from gi.repository import GObject
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gst
|
||||
|
||||
class BassPlayer(GObject.GObject):
|
||||
|
@ -13,6 +14,9 @@ class BassPlayer(GObject.GObject):
|
|||
self.bus.add_signal_watch()
|
||||
self.bus.connect("message::state-changed", self.state_changed)
|
||||
self.bus.connect("message::stream-start", self.stream_start)
|
||||
self.bus.connect("message::state-changed", self.state_changed)
|
||||
|
||||
self.timeout = None
|
||||
|
||||
@GObject.Property
|
||||
def bus(self):
|
||||
|
@ -33,6 +37,17 @@ class BassPlayer(GObject.GObject):
|
|||
state = Gst.State.PLAYING if playing else Gst.State.PAUSED
|
||||
self.playbin.set_state(state)
|
||||
|
||||
@GObject.Property
|
||||
def position(self):
|
||||
(res, pos) = self.playbin.query_position(Gst.Format.TIME)
|
||||
return pos if res == True else 0
|
||||
|
||||
@position.setter
|
||||
def position(self, pos):
|
||||
self.playbin.seek_simple(Gst.Format.TIME,
|
||||
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
|
||||
pos)
|
||||
|
||||
@GObject.Property
|
||||
def uri(self):
|
||||
return self.playbin.get_property("uri")
|
||||
|
@ -55,14 +70,25 @@ class BassPlayer(GObject.GObject):
|
|||
def stream_start(self, bus, message):
|
||||
self.emit("duration-changed")
|
||||
|
||||
def timeout_function(self):
|
||||
self.emit("position-changed")
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
@GObject.Signal
|
||||
def duration_changed(self):
|
||||
pass
|
||||
|
||||
@GObject.Signal
|
||||
def playback_start(self):
|
||||
pass
|
||||
if not self.timeout:
|
||||
self.timeout = GLib.timeout_add(200, self.timeout_function)
|
||||
|
||||
@GObject.Signal
|
||||
def playback_paused(self):
|
||||
if self.timeout:
|
||||
GLib.source_remove(self.timeout)
|
||||
self.timeout = None
|
||||
|
||||
@GObject.Signal
|
||||
def position_changed(self):
|
||||
pass
|
||||
|
|
|
@ -57,9 +57,6 @@ class Player(bass.BassPlayer):
|
|||
state = Gst.State.PLAYING if cont else Gst.State.PAUSED
|
||||
self.load_set_state(track, state)
|
||||
|
||||
def on_state_changed(self, bus, message):
|
||||
(old, new, pending) = message.parse_state_changed()
|
||||
|
||||
def on_tag(self, bus, message):
|
||||
taglist = message.parse_tag()
|
||||
(res, sample) = taglist.get_sample("image")
|
||||
|
@ -78,10 +75,6 @@ class Player(bass.BassPlayer):
|
|||
self.load_set_state(track, Gst.State.PLAYING)
|
||||
return True
|
||||
|
||||
def position(self):
|
||||
(res, pos) = self.playbin.query_position(Gst.Format.TIME)
|
||||
return pos if res == True else 0
|
||||
|
||||
def previous(self, *args):
|
||||
self.play_track(tagdb.Stack.previous())
|
||||
|
||||
|
@ -90,9 +83,6 @@ class Player(bass.BassPlayer):
|
|||
return 0
|
||||
return self.playbin.clock.get_time() - self.playbin.base_time
|
||||
|
||||
def seek(self, value):
|
||||
self.playbin.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, value)
|
||||
|
||||
def set_volume(self, volume):
|
||||
self.playbin.set_property("volume", volume)
|
||||
|
||||
|
|
|
@ -43,14 +43,17 @@ class SeekScale(ScalePlus):
|
|||
self.set_size_request(200, -1)
|
||||
self.player = player
|
||||
self.player.connect("duration-changed", self.duration_changed)
|
||||
GLib.timeout_add(200, self.update)
|
||||
self.player.connect("position-changed", self.position_changed)
|
||||
|
||||
def do_change_value(self, scroll, value):
|
||||
self.player.seek(value)
|
||||
self.player.position = value
|
||||
|
||||
def duration_changed(self, player):
|
||||
self.set_range(0, player.duration)
|
||||
|
||||
def position_changed(self, player):
|
||||
self.set_value(player.position)
|
||||
|
||||
def format_value(self, scale, value):
|
||||
position = int(value / Gst.SECOND)
|
||||
duration = int(self.get_adjustment().get_upper() / Gst.SECOND)
|
||||
|
@ -58,10 +61,6 @@ class SeekScale(ScalePlus):
|
|||
(r_m, r_s) = divmod(duration - position, 60)
|
||||
return f"{p_m:02}:{p_s:02} / {r_m:02}:{r_s:02}"
|
||||
|
||||
def update(self):
|
||||
self.set_value(self.player.position())
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
|
||||
class AutoPauseScale(ScalePlus):
|
||||
def __init__(self):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright 2021 (c) Anna Schumaker.
|
||||
import pathlib
|
||||
import time
|
||||
import unittest
|
||||
from gi.repository import GObject
|
||||
from gi.repository import GLib
|
||||
|
@ -27,6 +28,7 @@ class TestBassPlayer(unittest.TestCase):
|
|||
self.assertEqual(base.playbin.get_property("video-sink"), base.video)
|
||||
self.assertEqual(base.playbin.get_state(Gst.CLOCK_TIME_NONE)[1],
|
||||
Gst.State.READY)
|
||||
self.assertIsNone(base.timeout, 0)
|
||||
|
||||
def test_bass_player_bus(self):
|
||||
base = bass.BassPlayer()
|
||||
|
@ -60,11 +62,29 @@ class TestBassPlayer(unittest.TestCase):
|
|||
(ret, state, pending) = base.playbin.get_state(Gst.CLOCK_TIME_NONE)
|
||||
self.assertEqual(state, Gst.State.PLAYING)
|
||||
self.assertTrue(base.get_property("playing"))
|
||||
while main_context.iteration(may_block=False): pass
|
||||
self.assertIsNotNone(base.timeout)
|
||||
|
||||
base.set_property("playing", False)
|
||||
(ret, state, pending) = base.playbin.get_state(Gst.CLOCK_TIME_NONE)
|
||||
self.assertEqual(state, Gst.State.PAUSED)
|
||||
self.assertFalse(base.get_property("playing"))
|
||||
while main_context.iteration(may_block=False): pass
|
||||
self.assertIsNone(base.timeout)
|
||||
|
||||
def test_basic_player_position(self):
|
||||
base = bass.BassPlayer()
|
||||
self.assertEqual(base.get_property("position"), 0)
|
||||
|
||||
base.set_property("uri", test_uri)
|
||||
base.set_property("playing", False)
|
||||
time.sleep(0.1)
|
||||
while main_context.iteration(may_block=False): time.sleep(0.005)
|
||||
|
||||
base.set_property("position", 5 * Gst.SECOND)
|
||||
time.sleep(0.2)
|
||||
while main_context.iteration(may_block=False): time.sleep(0.005)
|
||||
self.assertGreater(base.get_property("position"), 0)
|
||||
|
||||
def test_bass_player_uri(self):
|
||||
base = bass.BassPlayer()
|
||||
|
|
|
@ -29,7 +29,6 @@ class TestPlayer(unittest.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.changed = None
|
||||
self.state_changed = None
|
||||
settings.reset()
|
||||
self.library = tagdb.Library.store[test_album]
|
||||
self.track = [ t for t in self.library.tracks if t.tracknumber == 1 ][0]
|
||||
|
|
|
@ -12,7 +12,15 @@ class FakePlayer(GObject.GObject):
|
|||
self.dur = duration
|
||||
self.vol = volume
|
||||
|
||||
@GObject.Property
|
||||
def position(self): return self.pos
|
||||
@position.setter
|
||||
def position(self, newval):
|
||||
self.pos = newval
|
||||
self.emit("position-changed")
|
||||
|
||||
@GObject.Signal
|
||||
def position_changed(self): pass
|
||||
|
||||
@GObject.Property
|
||||
def duration(self): return self.dur
|
||||
|
@ -99,12 +107,12 @@ class TestSeekScale(unittest.TestCase):
|
|||
fake.emit("duration-changed")
|
||||
self.assertEqual(adj.get_upper(), 3 * Gst.SECOND)
|
||||
|
||||
def test_seek_scale_update(self):
|
||||
fake = FakePlayer(3 * Gst.SECOND, 15 * Gst.SECOND, 1)
|
||||
def test_seek_scale_position(self):
|
||||
fake = FakePlayer(0, 15 * Gst.SECOND, 1)
|
||||
seek = scale.SeekScale(fake)
|
||||
adj = seek.get_adjustment()
|
||||
|
||||
seek.update()
|
||||
fake.position = 3 * Gst.SECOND
|
||||
self.assertEqual(seek.player, fake)
|
||||
self.assertEqual(adj.get_value(), 3 * Gst.SECOND)
|
||||
self.assertEqual(adj.get_lower(), 0)
|
||||
|
@ -113,14 +121,13 @@ class TestSeekScale(unittest.TestCase):
|
|||
fake = FakePlayer(0, 15 * Gst.SECOND, 1)
|
||||
seek = scale.SeekScale(fake)
|
||||
|
||||
seek.update()
|
||||
seek.increment()
|
||||
self.assertEqual(fake.seek_val, 5 * Gst.SECOND)
|
||||
self.assertEqual(fake.pos, 5 * Gst.SECOND)
|
||||
self.assertEqual(seek.format_value(seek, 5 * Gst.SECOND),
|
||||
"00:05 / 00:10")
|
||||
|
||||
seek.decrement()
|
||||
self.assertEqual(fake.seek_val, 0)
|
||||
self.assertEqual(fake.pos, 0)
|
||||
|
||||
|
||||
class TestAutoPauseScale(unittest.TestCase):
|
||||
|
|
Loading…
Reference in New Issue