[banshee/gapless-ng: 12/836] [libbanshee, Banshee.GStreamer] Implement gapless playback with playbin2
- From: Christopher James Halse Rogers <chrishr src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [banshee/gapless-ng: 12/836] [libbanshee, Banshee.GStreamer] Implement gapless playback with playbin2
- Date: Thu, 25 Feb 2010 22:42:27 +0000 (UTC)
commit 9692e0b23b2f221fa5607843054e57ce808c46c0
Author: Christopher James Halse Rogers <raof ubuntu com>
Date: Fri Jul 17 18:14:37 2009 +1000
[libbanshee,Banshee.GStreamer] Implement gapless playback with playbin2
libbanshee/banshee-player-pipeline.c | 31 +++++++-
libbanshee/banshee-player-private.h | 6 ++
libbanshee/banshee-player.c | 31 ++++++++
.../Banshee.GStreamer/PlayerEngine.cs | 82 +++++++++++++++++++-
4 files changed, 148 insertions(+), 2 deletions(-)
---
diff --git a/libbanshee/banshee-player-pipeline.c b/libbanshee/banshee-player-pipeline.c
index ca88e20..9694fd8 100644
--- a/libbanshee/banshee-player-pipeline.c
+++ b/libbanshee/banshee-player-pipeline.c
@@ -211,6 +211,31 @@ bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
return TRUE;
}
+static gboolean
+bp_next_track_starting (gpointer player)
+{
+ g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
+
+ bp_debug ("[gapless] Triggering track-change signal");
+ if (((BansheePlayer *)player)->next_track_starting_cb != NULL) {
+ ((BansheePlayer *)player)->next_track_starting_cb (player);
+ }
+ ((BansheePlayer *)player)->timeout_id = 0;
+ return FALSE;
+}
+
+static void bp_about_to_finish_callback (GstElement *playbin, BansheePlayer *player)
+{
+ g_return_if_fail (IS_BANSHEE_PLAYER (player));
+ // Playbin2 doesn't (yet) have any way to notify us when the current track has actually finished playing on the
+ // hardware. Fake this for now by adding a t2imer with length equal to the hardware buffer.
+ player->timeout_id = g_timeout_add (((guint64)BP_BUFFER_LEN_MICROSECONDS) / 1000, &bp_next_track_starting, player);
+
+ if (player->about_to_finish_cb != NULL) {
+ player->about_to_finish_cb (player);
+ }
+}
+
// ---------------------------------------------------------------------------
// Internal Functions
// ---------------------------------------------------------------------------
@@ -229,8 +254,12 @@ _bp_pipeline_construct (BansheePlayer *player)
// Playbin is the core element that handles autoplugging (finding the right
// source and decoder elements) based on source URI and stream content
- player->playbin = gst_element_factory_make ("playbin", "playbin");
+ player->playbin = gst_element_factory_make ("playbin2", "playbin");
g_return_val_if_fail (player->playbin != NULL, FALSE);
+
+ // Connect a proxy about-to-finish callback that will generate a next-track-starting callback.
+ // This can be removed once playbin2 generates its own next-track signal.
+ g_signal_connect (player->playbin, "about-to-finish", G_CALLBACK (bp_about_to_finish_callback), player);
// Try to find an audio sink, prefer gconf, which typically is set to auto these days,
// fall back on auto, which should work on windows, and as a last ditch, try alsa
diff --git a/libbanshee/banshee-player-private.h b/libbanshee/banshee-player-private.h
index 70b10f0..5d12aab 100644
--- a/libbanshee/banshee-player-private.h
+++ b/libbanshee/banshee-player-private.h
@@ -81,6 +81,8 @@ typedef void (* BansheePlayerIterateCallback) (BansheePlayer *player);
typedef void (* BansheePlayerBufferingCallback) (BansheePlayer *player, gint buffering_progress);
typedef void (* BansheePlayerTagFoundCallback) (BansheePlayer *player, const gchar *tag, const GValue *value);
typedef void (* BansheePlayerVisDataCallback) (BansheePlayer *player, gint channels, gint samples, gfloat *data, gint bands, gfloat *spectrum);
+typedef void (* BansheePlayerNextTrackStartingCallback) (BansheePlayer *player);
+typedef void (* BansheePlayerAboutToFinishCallback) (BansheePlayer *player);
typedef GstElement * (* BansheePlayerVideoPipelineSetupCallback) (BansheePlayer *player, GstBus *bus);
typedef enum {
@@ -98,6 +100,8 @@ struct BansheePlayer {
BansheePlayerBufferingCallback buffering_cb;
BansheePlayerTagFoundCallback tag_found_cb;
BansheePlayerVisDataCallback vis_data_cb;
+ BansheePlayerNextTrackStartingCallback next_track_starting_cb;
+ BansheePlayerAboutToFinishCallback about_to_finish_cb;
BansheePlayerVideoPipelineSetupCallback video_pipeline_setup_cb;
// Pipeline Elements
@@ -159,6 +163,8 @@ struct BansheePlayer {
gdouble album_peak;
gdouble track_gain;
gdouble track_peak;
+
+ guint timeout_id;
};
#endif /* _BANSHEE_PLAYER_PRIVATE_H */
diff --git a/libbanshee/banshee-player.c b/libbanshee/banshee-player.c
index 4052d06..019cdb3 100644
--- a/libbanshee/banshee-player.c
+++ b/libbanshee/banshee-player.c
@@ -180,6 +180,11 @@ bp_stop (BansheePlayer *player, gboolean nullstate)
state = GST_STATE_NULL;
}
+ if (player->timeout_id != 0) {
+ g_source_remove (player->timeout_id);
+ player->timeout_id = 0;
+ }
+
bp_debug ("bp_stop: setting state to %s",
state == GST_STATE_NULL ? "GST_STATE_NULL" : "GST_STATE_PAUSED");
@@ -195,10 +200,24 @@ bp_pause (BansheePlayer *player)
P_INVOKE void
bp_play (BansheePlayer *player)
{
+ g_return_if_fail (IS_BANSHEE_PLAYER (player));
+ if (player->timeout_id != 0) {
+ g_source_remove (player->timeout_id);
+ player->timeout_id = 0;
+ }
bp_pipeline_set_state (player, GST_STATE_PLAYING);
}
P_INVOKE gboolean
+bp_set_next_track (BansheePlayer *player, const gchar *uri)
+{
+ g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
+ g_return_val_if_fail (player->playbin != NULL, FALSE);
+ g_object_set (G_OBJECT (player->playbin), "uri", uri, NULL);
+ return TRUE;
+}
+
+P_INVOKE gboolean
bp_set_position (BansheePlayer *player, guint64 time_ms)
{
g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
@@ -348,3 +367,15 @@ bp_get_error_quarks (GQuark *core, GQuark *library, GQuark *resource, GQuark *st
*resource = GST_RESOURCE_ERROR;
*stream = GST_STREAM_ERROR;
}
+
+P_INVOKE void
+bp_set_next_track_starting_callback (BansheePlayer *player, BansheePlayerNextTrackStartingCallback cb)
+{
+ SET_CALLBACK (next_track_starting_cb);
+}
+
+P_INVOKE void
+bp_set_about_to_finish_callback (BansheePlayer *player, BansheePlayerAboutToFinishCallback cb)
+{
+ SET_CALLBACK (about_to_finish_cb);
+}
diff --git a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
index dac3d55..29ef0fd 100644
--- a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
@@ -29,6 +29,7 @@
using System;
using System.Collections;
using System.Runtime.InteropServices;
+using System.Threading;
using Mono.Unix;
using Hyena;
using Hyena.Data;
@@ -57,6 +58,8 @@ namespace Banshee.GStreamer
internal delegate void BansheePlayerIterateCallback (IntPtr player);
internal delegate void BansheePlayerBufferingCallback (IntPtr player, int buffering_progress);
internal delegate void BansheePlayerVisDataCallback (IntPtr player, int channels, int samples, IntPtr data, int bands, IntPtr spectrum);
+ internal delegate void BansheePlayerNextTrackStartingCallback (IntPtr player);
+ internal delegate void BansheePlayerAboutToFinishCallback (IntPtr player);
internal delegate IntPtr VideoPipelineSetupHandler (IntPtr player, IntPtr bus);
internal delegate void GstTaggerTagFoundCallback (IntPtr player, string tagName, ref GLib.Value value);
@@ -79,10 +82,15 @@ namespace Banshee.GStreamer
private BansheePlayerVisDataCallback vis_data_callback;
private VideoPipelineSetupHandler video_pipeline_setup_callback;
private GstTaggerTagFoundCallback tag_found_callback;
+ private BansheePlayerNextTrackStartingCallback next_track_starting_callback;
+ private BansheePlayerAboutToFinishCallback about_to_finish_callback;
private bool buffering_finished;
private int pending_volume = -1;
private bool xid_is_set = false;
+
+ private bool GaplessEnabled;
+ private EventWaitHandle next_track_set;
private event VisualizationDataHandler data_available = null;
public event VisualizationDataHandler DataAvailable {
@@ -135,14 +143,19 @@ namespace Banshee.GStreamer
vis_data_callback = new BansheePlayerVisDataCallback (OnVisualizationData);
video_pipeline_setup_callback = new VideoPipelineSetupHandler (OnVideoPipelineSetup);
tag_found_callback = new GstTaggerTagFoundCallback (OnTagFound);
-
+ next_track_starting_callback = new BansheePlayerNextTrackStartingCallback (OnNextTrackStarting);
+ about_to_finish_callback = new BansheePlayerAboutToFinishCallback (OnAboutToFinish);
+
bp_set_eos_callback (handle, eos_callback);
bp_set_iterate_callback (handle, iterate_callback);
bp_set_error_callback (handle, error_callback);
bp_set_state_changed_callback (handle, state_changed_callback);
bp_set_buffering_callback (handle, buffering_callback);
bp_set_tag_found_callback (handle, tag_found_callback);
+ bp_set_next_track_starting_callback (handle, next_track_starting_callback);
bp_set_video_pipeline_setup_callback (handle, video_pipeline_setup_callback);
+
+ next_track_set = new EventWaitHandle (false, EventResetMode.AutoReset);
}
protected override void Initialize ()
@@ -161,6 +174,11 @@ namespace Banshee.GStreamer
InstallPreferences ();
ReplayGainEnabled = ReplayGainEnabledSchema.Get ();
+ GaplessEnabled = GaplessEnabledSchema.Get ();
+
+ if (GaplessEnabled) {
+ bp_set_about_to_finish_callback (handle, about_to_finish_callback);
+ }
}
public override void Dispose ()
@@ -209,6 +227,22 @@ namespace Banshee.GStreamer
{
bp_pause (handle);
}
+
+ public override void SetNextTrackUri (SafeUri uri)
+ {
+ // If there isn't a next track for us, release the block on the about-to-finish callback.
+ if (uri == null) {
+ next_track_set.Set ();
+ return;
+ }
+ IntPtr uri_ptr = GLib.Marshaller.StringToPtrGStrdup (uri.AbsoluteUri);
+ try {
+ bp_set_next_track (handle, uri_ptr);
+ } finally {
+ GLib.Marshaller.Free (uri_ptr);
+ }
+ next_track_set.Set ();
+ }
public override void VideoExpose (IntPtr window, bool direct)
{
@@ -232,6 +266,25 @@ namespace Banshee.GStreamer
OnEventChanged (PlayerEvent.EndOfStream);
OnEventChanged (PlayerEvent.RequestNexttrack);
}
+
+ private void OnNextTrackStarting (IntPtr player)
+ {
+ if (GaplessEnabled) {
+ OnEventChanged (PlayerEvent.EndOfStream);
+ OnEventChanged (PlayerEvent.StartOfStream);
+ }
+ }
+
+ private void OnAboutToFinish (IntPtr player)
+ {
+ OnEventChanged (PlayerEvent.RequestNexttrack);
+ // Gapless playback with Playbin2 requires that the about-to-finish callback does not return until
+ // the next uri has been set. Block here for a second or until the RequestNextTrack event has
+ // finished triggering.
+ if (!next_track_set.WaitOne (1000, false)) {
+ Log.Debug ("[Gapless] Timed out while waiting for next_track_set to be raised");
+ }
+ }
private void OnIterate (IntPtr player)
{
@@ -544,6 +597,7 @@ namespace Banshee.GStreamer
#region Preferences
private PreferenceBase replaygain_preference;
+ private PreferenceBase gapless_preference;
private void InstallPreferences ()
{
@@ -557,6 +611,11 @@ namespace Banshee.GStreamer
Catalog.GetString ("For tracks that have ReplayGain data, automatically scale (normalize) playback volume."),
delegate { ReplayGainEnabled = ReplayGainEnabledSchema.Get (); }
));
+ gapless_preference = service["general"]["misc"].Add (new SchemaPreference<bool> (GaplessEnabledSchema,
+ Catalog.GetString ("Enable _gapless playback (EXPERIMENTAL)"),
+ Catalog.GetString ("Eliminate the small playback gap on track change. Useful for concept albums & classical music."),
+ delegate { GaplessEnabled = GaplessEnabledSchema.Get (); }
+ ));
}
private void UninstallPreferences ()
@@ -567,7 +626,9 @@ namespace Banshee.GStreamer
}
service["general"]["misc"].Remove (replaygain_preference);
+ service["general"]["misc"].Remove (gapless_preference);
replaygain_preference = null;
+ gapless_preference = null;
}
public static readonly SchemaEntry<bool> ReplayGainEnabledSchema = new SchemaEntry<bool> (
@@ -577,6 +638,14 @@ namespace Banshee.GStreamer
"If ReplayGain data is present on tracks when playing, allow volume scaling"
);
+ public static readonly SchemaEntry<bool> GaplessEnabledSchema = new SchemaEntry<bool> (
+ "player_engine", "gapless_playback_enabled",
+ false,
+ "Enable gapless playback (EXPERIMENTAL)",
+ "Eliminate the small playback gap on track change. Useful for concept albums & classical music. EXPERIMENTAL"
+ );
+
+
#endregion
[DllImport ("libbanshee.dll")]
@@ -616,6 +685,14 @@ namespace Banshee.GStreamer
[DllImport ("libbanshee.dll")]
private static extern void bp_set_tag_found_callback (HandleRef player,
GstTaggerTagFoundCallback cb);
+
+ [DllImport ("libbanshee")]
+ private static extern void bp_set_next_track_starting_callback (HandleRef player,
+ BansheePlayerNextTrackStartingCallback cb);
+
+ [DllImport ("libbanshee.dll")]
+ private static extern void bp_set_about_to_finish_callback (HandleRef player,
+ BansheePlayerAboutToFinishCallback cb);
[DllImport ("libbanshee.dll")]
private static extern bool bp_open (HandleRef player, IntPtr uri);
@@ -630,6 +707,9 @@ namespace Banshee.GStreamer
private static extern void bp_play (HandleRef player);
[DllImport ("libbanshee.dll")]
+ private static extern bool bp_set_next_track (HandleRef player, IntPtr uri);
+
+ [DllImport ("libbanshee.dll")]
private static extern void bp_set_volume (HandleRef player, double volume);
[DllImport("libbanshee.dll")]
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]