Anna Schumaker
4cdf4c528a
The playbin's about-to-finish signal triggers in a different thread, which Gtk is very much not happy about, and often results in both the about-to-finish and eos handlers getting called (and therefore multiple tracks getting picked from the queue). Fix this by checking how much time is left during the regular position changed timeout function and triggering about-to-finish manually Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
164 lines
4.7 KiB
Python
164 lines
4.7 KiB
Python
# Copyright 2021 (c) Anna Schumaker.
|
|
import gi
|
|
gi.require_version("Gst", "1.0")
|
|
|
|
import lib
|
|
import sys
|
|
from gi.repository import GObject
|
|
from gi.repository import GLib
|
|
from gi.repository import Gst
|
|
Gst.init(sys.argv)
|
|
|
|
from . import replaygain
|
|
|
|
TIMEOUT = 100
|
|
|
|
|
|
class BassPlayer(GObject.GObject):
|
|
def __init__(self):
|
|
GObject.GObject.__init__(self)
|
|
lib.settings.initialize("audio.replaygain", "disabled")
|
|
lib.settings.initialize("audio.volume", 1.0)
|
|
|
|
self.audio = replaygain.ReplayGainSink()
|
|
self.video = Gst.ElementFactory.make("fakesink")
|
|
|
|
self.playbin = Gst.ElementFactory.make("playbin")
|
|
self.playbin.set_property("audio-sink", self.audio)
|
|
self.playbin.set_property("video-sink", self.video)
|
|
self.playbin.set_property("volume", lib.settings.get_float("audio.volume"))
|
|
self.playbin.set_state(Gst.State.READY)
|
|
self.set_property("replaygain", lib.settings.get("audio.replaygain"))
|
|
|
|
self.bus.add_signal_watch()
|
|
self.bus.connect("message::eos", self.__eos__)
|
|
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.bus.connect("message::tag", self.tag)
|
|
|
|
self.timeout = None
|
|
|
|
def __eos__(self, bus, message):
|
|
self.emit("eos")
|
|
|
|
@GObject.Property
|
|
def bus(self):
|
|
return self.playbin.get_bus()
|
|
|
|
@GObject.Property
|
|
def duration(self):
|
|
(res, dur) = self.playbin.query_duration(Gst.Format.TIME)
|
|
return dur if res == True else 0
|
|
|
|
@GObject.Property
|
|
def playing(self):
|
|
(ret, state, pending) = self.playbin.get_state(Gst.CLOCK_TIME_NONE)
|
|
return state == Gst.State.PLAYING
|
|
|
|
@playing.setter
|
|
def playing(self, playing):
|
|
state = Gst.State.PLAYING if playing else Gst.State.PAUSED
|
|
self.playbin.set_state(state)
|
|
|
|
@GObject.Property
|
|
def play_percent(self):
|
|
if self.playbin.clock == None or self.duration == 0:
|
|
return 0
|
|
runtime = self.playbin.clock.get_time() - self.playbin.base_time
|
|
return runtime / self.duration
|
|
|
|
@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 replaygain(self):
|
|
return self.audio.get_property("mode")
|
|
|
|
@replaygain.setter
|
|
def replaygain(self, mode):
|
|
lib.settings.set("audio.replaygain", mode)
|
|
self.audio.set_property("mode", mode)
|
|
|
|
@GObject.Property
|
|
def uri(self):
|
|
return self.playbin.get_property("uri")
|
|
|
|
@uri.setter
|
|
def uri(self, uri):
|
|
if uri:
|
|
self.playbin.set_property("uri", uri)
|
|
else:
|
|
self.playbin.set_state(Gst.State.READY)
|
|
|
|
@GObject.Property
|
|
def volume(self):
|
|
return self.playbin.get_property("volume")
|
|
|
|
@volume.setter
|
|
def volume(self, vol):
|
|
self.playbin.set_property("volume", vol)
|
|
lib.settings.set("audio.volume", vol)
|
|
|
|
def state_changed(self, bus, message):
|
|
if message.src == self.playbin:
|
|
(old, new, pending) = message.parse_state_changed()
|
|
if new == Gst.State.PLAYING:
|
|
self.emit("playback-start")
|
|
else:
|
|
self.emit("playback-paused")
|
|
|
|
def stream_start(self, bus, message):
|
|
self.emit("duration-changed")
|
|
|
|
def tag(self, bus, message):
|
|
(res, sample) = message.parse_tag().get_sample("image")
|
|
if res:
|
|
self.emit("artwork", sample)
|
|
|
|
def timeout_function(self):
|
|
self.emit("position-changed")
|
|
return GLib.SOURCE_CONTINUE
|
|
|
|
@GObject.Signal
|
|
def about_to_finish(self):
|
|
pass
|
|
|
|
@GObject.Signal(arg_types=(Gst.Sample,))
|
|
def artwork(self, sample):
|
|
pass
|
|
|
|
@GObject.Signal
|
|
def duration_changed(self):
|
|
pass
|
|
|
|
@GObject.Signal
|
|
def eos(self):
|
|
pass
|
|
|
|
@GObject.Signal
|
|
def playback_start(self):
|
|
if not self.timeout:
|
|
self.timeout = GLib.timeout_add(TIMEOUT, 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):
|
|
remaining = self.duration - self.position
|
|
if remaining < 2 * Gst.SECOND:
|
|
if remaining + (TIMEOUT * Gst.MSECOND) >= 2 * Gst.SECOND:
|
|
self.emit("about-to-finish")
|