[banshee/gapless-ng: 12/836] [libbanshee, Banshee.GStreamer] Implement gapless playback with playbin2



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]