[gnome-music/wip/mschraal/gapless-v3: 1/5] gstplayer: Enable gapless playback
- From: Marinus Schraal <mschraal src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music/wip/mschraal/gapless-v3: 1/5] gstplayer: Enable gapless playback
- Date: Sat, 4 May 2019 15:30:45 +0000 (UTC)
commit 8f3f147ef17a1f5c9bef6d2b1b3441be9e3557c2
Author: Marinus Schraal <mschraal gnome org>
Date: Sat May 4 12:18:15 2019 +0200
gstplayer: Enable gapless playback
Enable gapless playback in GstPlayer by listening to the 'about-to-finish'
signal of playbin and handling it in Player. The active playlist is now
played as one continous stream, so start listening to 'stream-start' on the
playbin bus as well as an indicator of a new song starting.
A few workarounds are in place to ensure a smooth experience.
1. The 'eos' signal is in theory only sent at the end of the stream
(playlist). In practice it is possible to trigger both 'about-to-finish'
and 'eos' if seeking to the end of a song, this creates the need for some
workarounds to correctly play the next song.
2. On 'stream-start' the new duration is not yet set. Introduce a slight
delay before processing further to have GStreamer update the new duration.
3. The clock tick is reset on 'stream-start', as the clock is now ticking
for the full duration of the pipeline.
gnomemusic/gstplayer.py | 29 +++++++++++++++++++++++++----
gnomemusic/player.py | 37 ++++++++++++++++++++++++++++++++++---
2 files changed, 59 insertions(+), 7 deletions(-)
---
diff --git a/gnomemusic/gstplayer.py b/gnomemusic/gstplayer.py
index 9f56510f..b60e84e9 100644
--- a/gnomemusic/gstplayer.py
+++ b/gnomemusic/gstplayer.py
@@ -30,7 +30,7 @@ import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstAudio', '1.0')
gi.require_version('GstPbutils', '1.0')
-from gi.repository import Gtk, Gio, GObject, Gst, GstAudio, GstPbutils
+from gi.repository import GLib, Gtk, Gio, GObject, Gst, GstAudio, GstPbutils
from gnomemusic import log
@@ -51,8 +51,10 @@ class GstPlayer(GObject.GObject):
Handles GStreamer interaction for Player and SmoothScale.
"""
__gsignals__ = {
+ "about-to-finish": (GObject.SignalFlags.RUN_FIRST, None, ()),
'eos': (GObject.SignalFlags.RUN_FIRST, None, ()),
- 'clock-tick': (GObject.SignalFlags.RUN_FIRST, None, (int, ))
+ "clock-tick": (GObject.SignalFlags.RUN_FIRST, None, (int, )),
+ "stream-start": (GObject.SignalFlags.RUN_FIRST, None, ())
}
def __repr__(self):
@@ -70,6 +72,7 @@ class GstPlayer(GObject.GObject):
self._application = application
self._duration = -1.
+ self._tick = 0
self._missing_plugin_messages = []
self._settings = application.props.settings
@@ -89,6 +92,9 @@ class GstPlayer(GObject.GObject):
self._bus.connect('message::element', self._on_bus_element)
self._bus.connect('message::eos', self._on_bus_eos)
self._bus.connect('message::new-clock', self._on_new_clock)
+ self._bus.connect("message::stream-start", self._on_bus_stream_start)
+
+ self._player.connect("about-to-finish", self._on_about_to_finish)
self.props.state = Playback.STOPPED
@@ -124,6 +130,10 @@ class GstPlayer(GObject.GObject):
else:
self._player.set_property("audio-filter", None)
+ @log
+ def _on_about_to_finish(self, klass):
+ self.emit("about-to-finish")
+
@log
def _on_async_done(self, bus, message):
success, duration = self._player.query_duration(
@@ -144,14 +154,25 @@ class GstPlayer(GObject.GObject):
@log
def _on_clock_tick(self, clock, time, id, data):
- tick = time / Gst.SECOND
- self.emit('clock-tick', tick)
+ self.emit('clock-tick', self._tick)
+ self._tick += 1
@log
def _on_bus_element(self, bus, message):
if GstPbutils.is_missing_plugin_message(message):
self._missing_plugin_messages.append(message)
+ @log
+ def _on_bus_stream_start(self, bus, message):
+ def delayed_query():
+ self._on_async_done(None, None)
+ self._tick = 0
+ self.emit("stream-start")
+
+ # Delay the signalling slightly or the new duration will not
+ # have been set yet.
+ GLib.timeout_add(1, delayed_query)
+
@log
def _on_bus_error(self, bus, message):
if self._is_missing_plugin_message(message):
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index f60eed0a..03e9a5df 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -562,6 +562,14 @@ class Player(GObject.GObject):
"""
super().__init__()
+ # In the case of gapless playback, both 'about-to-finish'
+ # and 'eos' can occur during the same stream. 'about-to-finish'
+ # already sets self._playlist to the next song, so doing it
+ # again on eos would skip a song.
+ # TODO: Improve playlist handling so this hack is no longer
+ # needed.
+ self._gapless_set = False
+
self._playlist = PlayerPlaylist()
self._playlist.connect('song-validated', self._on_song_validated)
@@ -577,8 +585,10 @@ class Player(GObject.GObject):
self._new_clock = True
self._gst_player = GstPlayer(application)
+ self._gst_player.connect("about-to-finish", self._on_about_to_finish)
self._gst_player.connect('clock-tick', self._on_clock_tick)
self._gst_player.connect('eos', self._on_eos)
+ self._gst_player.connect("stream-start", self._on_stream_start)
self._gst_player.bind_property(
'duration', self, 'duration', GObject.BindingFlags.SYNC_CREATE)
self._gst_player.bind_property(
@@ -626,12 +636,32 @@ class Player(GObject.GObject):
self.emit('song-changed', self._playlist.get_current_index())
@log
- def _on_eos(self, klass):
+ def _on_about_to_finish(self, klass):
if self.props.has_next:
- self.next()
+ self._playlist.next()
+
+ new_url = self._playlist.props.current_song.get_url()
+ self._gst_player.props.url = new_url
+ self._gapless_set = True
+
+ @log
+ def _on_eos(self, klass):
+ if self._gapless_set:
+ # After 'eos' in the gapless case, the pipeline needs to be
+ # hard reset.
+ self.stop()
+ self.play()
else:
self.stop()
+ self._gapless_set = False
+
+ @log
+ def _on_stream_start(self, klass):
+ self._gapless_set = False
+ self._time_stamp = int(time.time())
+ self.emit('song-changed', self._playlist.get_current_index())
+
@log
def play(self, song_offset=None):
"""Play a song.
@@ -651,7 +681,8 @@ class Player(GObject.GObject):
url = self._playlist.props.current_song.get_url()
loop_modes = [RepeatMode.SONG, RepeatMode.ALL]
if (url != self._gst_player.props.url
- or self.props.repeat_mode in loop_modes):
+ or self.props.repeat_mode in loop_modes
+ or self._gapless_set):
self._load(self._playlist.props.current_song)
self._gst_player.props.state = Playback.PLAYING
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]