[banshee/gapless] Patch 147845 from bgo#440952
- From: Alexander Kojevnikov <alexk src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [banshee/gapless] Patch 147845 from bgo#440952
- Date: Wed, 18 Nov 2009 00:31:59 +0000 (UTC)
commit a1164904a0c1e13efd1e3306f8eeaea7dd71532c
Author: Christopher Halse Rogers <chalserogers gmail com>
Date: Wed Nov 18 11:30:55 2009 +1100
Patch 147845 from bgo#440952
Signed-off-by: Alexander Kojevnikov <alexander kojevnikov com>
libbanshee/banshee-player-pipeline.c | 107 ++++++++++++++++----
libbanshee/banshee-player-private.h | 14 +++
libbanshee/banshee-player-video.c | 16 ++--
libbanshee/banshee-player.c | 40 +++++++
.../Banshee.GStreamer/PlayerEngine.cs | 104 +++++++++++++++++++
.../Banshee.MediaEngine/PlayerEngine.cs | 50 +++++++++
.../Banshee.MediaEngine/PlayerEngineService.cs | 85 ++++++++++++----
.../Banshee.MediaEngine/PlayerEvent.cs | 3 +-
.../IBasicPlaybackController.cs | 2 +-
.../PlaybackControllerService.cs | 84 +++++++++-------
.../Banshee.InternetRadio/InternetRadioSource.cs | 14 +++-
.../Banshee.Lastfm.Radio/StationSource.cs | 21 +++--
.../Banshee.PlayQueue/PlayQueueSource.cs | 15 ++-
13 files changed, 460 insertions(+), 95 deletions(-)
---
diff --git a/libbanshee/banshee-player-pipeline.c b/libbanshee/banshee-player-pipeline.c
index d246c92..9014a55 100644
--- a/libbanshee/banshee-player-pipeline.c
+++ b/libbanshee/banshee-player-pipeline.c
@@ -62,6 +62,38 @@ bp_pipeline_process_tag (const GstTagList *tag_list, const gchar *tag_name, Bans
}
}
+void bp_try_set_audiosink_buffer (GstElement *element, guint64 *buffer_len)
+{
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "buffer-time")) {
+ g_object_set (G_OBJECT (element), "buffer-time", *buffer_len, NULL);
+ }
+ gst_object_unref (G_OBJECT (element));
+}
+
+static void
+bp_set_audiosink_buffer_length (BansheePlayer *player, guint64 buffer_len)
+{
+ GstIterator *sink_iterator;
+ GstIteratorResult iter_result;
+ GstElement *audiosink;
+
+ g_return_if_fail (IS_BANSHEE_PLAYER (player));
+ audiosink = player->audiosink;
+ // If we've directly selected alsasink or directsoundsink then we'll have a
+ // buffer-time property on audiosink. If so, just set this and return.
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (audiosink), "buffer-time")) {
+ g_object_set (G_OBJECT (audiosink), "buffer-time", buffer_len, NULL);
+ return;
+ }
+ // If we've selected an auto audio sink, then the real audio sink is a child of the bin.
+ sink_iterator = gst_bin_iterate_recurse (GST_BIN (audiosink));
+ iter_result = gst_iterator_foreach (sink_iterator, (GFunc)&bp_try_set_audiosink_buffer, &buffer_len);
+ if (iter_result == GST_ITERATOR_ERROR) {
+ bp_debug ("Failed to set audio buffer length: failed to find sink");
+ }
+ gst_iterator_free (sink_iterator);
+}
+
static gboolean
bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
{
@@ -81,7 +113,16 @@ bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
case GST_MESSAGE_STATE_CHANGED: {
GstState old, new, pending;
gst_message_parse_state_changed (message, &old, &new, &pending);
-
+
+ if (new == GST_STATE_READY) {
+ // Explicitly set the buffer length.
+ // We need to know the buffer length so that we can emit a fake track-changed signal at something
+ // approximating the right time, at least until playbin2 can do this for us.
+ // The easiest way to do this is to set the buffer to a known length.
+ //
+ // gconfaudiosink only has a real audiosink in it once it's made a transition to READY.
+ bp_set_audiosink_buffer_length (player, BP_BUFFER_LEN_MICROSECONDS);
+ }
_bp_missing_elements_handle_state_changed (player, old, new);
_bp_replaygain_handle_state_changed (player, old, new, pending);
@@ -171,6 +212,33 @@ 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)->next_track_starting_timer_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 timer with length equal to the hardware buffer.
+ player->next_track_starting_timer_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
// ---------------------------------------------------------------------------
@@ -180,7 +248,6 @@ _bp_pipeline_construct (BansheePlayer *player)
{
GstBus *bus;
GstPad *teepad;
- GstElement *audiosink;
GstElement *audiosinkqueue;
GstElement *eq_audioconvert = NULL;
GstElement *eq_audioconvert2 = NULL;
@@ -189,31 +256,35 @@ _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
- audiosink = gst_element_factory_make ("gconfaudiosink", "audiosink");
- if (audiosink == NULL) {
- audiosink = gst_element_factory_make ("directsoundsink", "audiosink");
- if (audiosink != NULL) {
- g_object_set (G_OBJECT (audiosink), "volume", 1.0, NULL);
+ player->audiosink = gst_element_factory_make ("gconfaudiosink", "audiosink");
+ if (player->audiosink == NULL) {
+ player->audiosink = gst_element_factory_make ("directsoundsink", "audiosink");
+ if (player->audiosink != NULL) {
+ g_object_set (G_OBJECT (player->audiosink), "volume", 1.0, NULL);
} else {
- audiosink = gst_element_factory_make ("autoaudiosink", "audiosink");
- if (audiosink == NULL) {
- audiosink = gst_element_factory_make ("alsasink", "audiosink");
+ player->audiosink = gst_element_factory_make ("autoaudiosink", "audiosink");
+ if (player->audiosink == NULL) {
+ player->audiosink = gst_element_factory_make ("alsasink", "audiosink");
}
}
}
- g_return_val_if_fail (audiosink != NULL, FALSE);
+ g_return_val_if_fail (player->audiosink != NULL, FALSE);
// Set the profile to "music and movies" (gst-plugins-good 0.10.3)
- if (g_object_class_find_property (G_OBJECT_GET_CLASS (audiosink), "profile")) {
- g_object_set (G_OBJECT (audiosink), "profile", 1, NULL);
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (player->audiosink), "profile")) {
+ g_object_set (G_OBJECT (player->audiosink), "profile", 1, NULL);
}
-
+
// Create a custom audio sink bin that will hold the real primary sink
player->audiobin = gst_bin_new ("audiobin");
g_return_val_if_fail (player->audiobin != NULL, FALSE);
@@ -244,7 +315,7 @@ _bp_pipeline_construct (BansheePlayer *player)
}
gst_bin_add (GST_BIN (player->audiobin), audiosinkqueue);
- gst_bin_add (GST_BIN (player->audiobin), audiosink);
+ gst_bin_add (GST_BIN (player->audiobin), player->audiosink);
// Ghost pad the audio bin so audio is passed from the bin into the tee
teepad = gst_element_get_pad (player->audiotee, "sink");
@@ -255,10 +326,10 @@ _bp_pipeline_construct (BansheePlayer *player)
if (player->equalizer != NULL) {
// link in equalizer, preamp and audioconvert.
gst_element_link_many (audiosinkqueue, eq_audioconvert, player->preamp,
- player->equalizer, eq_audioconvert2, audiosink, NULL);
+ player->equalizer, eq_audioconvert2, player->audiosink, NULL);
} else {
// link the queue with the real audio sink
- gst_element_link (audiosinkqueue, audiosink);
+ gst_element_link (audiosinkqueue, player->audiosink);
}
_bp_vis_pipeline_setup (player);
diff --git a/libbanshee/banshee-player-private.h b/libbanshee/banshee-player-private.h
index 5cbaa40..58cdb8d 100644
--- a/libbanshee/banshee-player-private.h
+++ b/libbanshee/banshee-player-private.h
@@ -68,6 +68,8 @@
#define bp_debug(x...) banshee_log_debug ("player", x)
#endif
+#define BP_BUFFER_LEN_MICROSECONDS 1000000
+
typedef struct BansheePlayer BansheePlayer;
typedef void (* BansheePlayerEosCallback) (BansheePlayer *player);
@@ -79,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 {
@@ -96,14 +100,18 @@ 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
GstElement *playbin;
+ GstElement *audiosink;
GstElement *audiotee;
GstElement *audiobin;
GstElement *equalizer;
GstElement *preamp;
+ GstElement *videosink;
gint equalizer_status;
gdouble current_volume;
@@ -156,6 +164,12 @@ struct BansheePlayer {
gdouble album_peak;
gdouble track_gain;
gdouble track_peak;
+
+ // Work around playbin2 not giving any notification about when a
+ // track changes. We know how long playbin's buffer is, so we know
+ // how long after about-to-finish is raised the track will end.
+ // Use this timer to fire a signal when that happens.
+ guint next_track_starting_timer_id;
};
#endif /* _BANSHEE_PLAYER_PRIVATE_H */
diff --git a/libbanshee/banshee-player-video.c b/libbanshee/banshee-player-video.c
index 21bc0ce..31a4b4d 100644
--- a/libbanshee/banshee-player-video.c
+++ b/libbanshee/banshee-player-video.c
@@ -37,15 +37,14 @@
static gboolean
bp_video_find_xoverlay (BansheePlayer *player)
{
- GstElement *video_sink = NULL;
GstElement *xoverlay;
GstXOverlay *previous_xoverlay;
+ g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
+
previous_xoverlay = player->xoverlay;
- g_object_get (player->playbin, "video-sink", &video_sink, NULL);
-
- if (video_sink == NULL) {
+ if (player->videosink == NULL) {
player->xoverlay = NULL;
if (previous_xoverlay != NULL) {
gst_object_unref (previous_xoverlay);
@@ -54,9 +53,9 @@ bp_video_find_xoverlay (BansheePlayer *player)
return FALSE;
}
- xoverlay = GST_IS_BIN (video_sink)
- ? gst_bin_get_by_interface (GST_BIN (video_sink), GST_TYPE_X_OVERLAY)
- : video_sink;
+ xoverlay = GST_IS_BIN (player->videosink)
+ ? gst_bin_get_by_interface (GST_BIN (player->videosink), GST_TYPE_X_OVERLAY)
+ : player->videosink;
player->xoverlay = GST_IS_X_OVERLAY (xoverlay) ? GST_X_OVERLAY (xoverlay) : NULL;
@@ -74,8 +73,6 @@ bp_video_find_xoverlay (BansheePlayer *player)
g_object_set (G_OBJECT (player->xoverlay), "handle-events", FALSE, NULL);
}
- gst_object_unref (video_sink);
-
return player->xoverlay != NULL;
}
@@ -178,6 +175,7 @@ _bp_video_pipeline_setup (BansheePlayer *player, GstBus *bus)
#endif
#endif
+ player->videosink = videosink;
}
P_INVOKE void
diff --git a/libbanshee/banshee-player.c b/libbanshee/banshee-player.c
index 4052d06..f2253ba 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->next_track_starting_timer_id != 0) {
+ g_source_remove (player->next_track_starting_timer_id);
+ player->next_track_starting_timer_id = 0;
+ }
+
bp_debug ("bp_stop: setting state to %s",
state == GST_STATE_NULL ? "GST_STATE_NULL" : "GST_STATE_PAUSED");
@@ -195,10 +200,33 @@ bp_pause (BansheePlayer *player)
P_INVOKE void
bp_play (BansheePlayer *player)
{
+ g_return_if_fail (IS_BANSHEE_PLAYER (player));
+ if (player->next_track_starting_timer_id != 0) {
+ g_source_remove (player->next_track_starting_timer_id);
+ player->next_track_starting_timer_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);
+ if (uri == NULL && player->next_track_starting_timer_id != 0) {
+ // URI == NULL indicates that there is not a next track to play.
+ // This means that there will not be a next track *starting*, so
+ // we have to disable the timer so that we don't fire a spurious
+ // next-track-starting signal.
+ g_source_remove (player->next_track_starting_timer_id);
+ player->next_track_starting_timer_id = 0;
+ return TRUE;
+ }
+ 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 +376,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 3193187..6350405 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,11 +82,16 @@ 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 gapless_enabled;
+ private EventWaitHandle next_track_set;
+
private event VisualizationDataHandler data_available = null;
public event VisualizationDataHandler DataAvailable {
add {
@@ -135,6 +143,8 @@ 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);
@@ -142,7 +152,10 @@ namespace Banshee.GStreamer
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,7 @@ namespace Banshee.GStreamer
InstallPreferences ();
ReplayGainEnabled = ReplayGainEnabledSchema.Get ();
+ GaplessEnabled = GaplessEnabledSchema.Get ();
}
public override void Dispose ()
@@ -210,6 +224,23 @@ 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 ();
+ bp_set_next_track (handle, IntPtr.Zero);
+ 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)
{
bp_video_window_expose (handle, window, direct);
@@ -230,6 +261,37 @@ namespace Banshee.GStreamer
{
Close (false);
OnEventChanged (PlayerEvent.EndOfStream);
+ if (!GaplessEnabled) {
+ OnEventChanged (PlayerEvent.RequestNextTrack);
+ }
+ }
+
+ private void OnNextTrackStarting (IntPtr player)
+ {
+ if (GaplessEnabled) {
+ OnEventChanged (PlayerEvent.EndOfStream);
+ OnEventChanged (PlayerEvent.StartOfStream);
+ }
+ }
+
+ private void OnAboutToFinish (IntPtr player)
+ {
+ // This is needed to make Shuffle-by-* work.
+ // Shuffle-by-* uses the LastPlayed field to determine what track in the grouping to play next.
+ // Therefore, we need to update this before requesting the next track.
+ //
+ // This will be overridden by IncrementLastPlayed () called by
+ // PlaybackControllerService's EndOfStream handler.
+ CurrentTrack.LastPlayed = DateTime.Now;
+ CurrentTrack.Save ();
+
+ 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)
@@ -489,6 +551,20 @@ namespace Banshee.GStreamer
set { bp_replaygain_set_enabled (handle, value); }
}
+ private bool GaplessEnabled {
+ get { return gapless_enabled; }
+ set
+ {
+ gapless_enabled = value;
+ if (value) {
+ bp_set_about_to_finish_callback (handle, about_to_finish_callback);
+ } else {
+ bp_set_about_to_finish_callback (handle, null);
+ }
+ }
+ }
+
+
#region ISupportClutter
private IntPtr clutter_video_sink;
@@ -515,6 +591,7 @@ namespace Banshee.GStreamer
}
}
+
private IntPtr OnVideoPipelineSetup (IntPtr player, IntPtr bus)
{
try {
@@ -543,6 +620,7 @@ namespace Banshee.GStreamer
#region Preferences
private PreferenceBase replaygain_preference;
+ private PreferenceBase gapless_preference;
private void InstallPreferences ()
{
@@ -556,6 +634,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"),
+ Catalog.GetString ("Eliminate the small playback gap on track change. Useful for concept albums and classical music."),
+ delegate { GaplessEnabled = GaplessEnabledSchema.Get (); }
+ ));
}
private void UninstallPreferences ()
@@ -566,7 +649,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> (
@@ -576,6 +661,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",
+ true,
+ "Enable gapless playback",
+ "Eliminate the small playback gap on track change. Useful for concept albums & classical music. EXPERIMENTAL"
+ );
+
+
#endregion
[DllImport ("libbanshee.dll")]
@@ -617,6 +710,14 @@ namespace Banshee.GStreamer
GstTaggerTagFoundCallback cb);
[DllImport ("libbanshee.dll")]
+ 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);
[DllImport ("libbanshee.dll")]
@@ -629,6 +730,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")]
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
index b074d2b..e5f31b0 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
@@ -47,6 +47,8 @@ namespace Banshee.MediaEngine
private TrackInfo current_track;
private SafeUri current_uri;
+ private TrackInfo pending_track;
+ private SafeUri pending_uri;
private PlayerState current_state = PlayerState.NotReady;
private PlayerState last_state = PlayerState.NotReady;
@@ -98,6 +100,40 @@ namespace Banshee.MediaEngine
HandleOpen (uri);
}
+ public void SetNextTrack (TrackInfo track)
+ {
+ pending_track = track;
+ pending_uri = track != null ? track.Uri : null;
+
+ HandleNextTrack (pending_uri);
+ }
+
+ public void SetNextTrack (SafeUri uri)
+ {
+ pending_uri = uri;
+ pending_track = new UnknownTrackInfo (uri);
+
+ HandleNextTrack (uri);
+ }
+
+ private void HandleNextTrack (SafeUri uri)
+ {
+ if (current_state != PlayerState.Playing) {
+ // Pre-buffering the next track only makes sense when we're currently playing
+ // Instead, just open.
+ HandleOpen (uri);
+ Play ();
+ return;
+ }
+
+ try {
+ // Setting the next track doesn't change the player state.
+ SetNextTrackUri (uri);
+ } catch (Exception e) {
+ Log.Exception ("Failed to pre-buffer next track", e);
+ }
+ }
+
private void HandleOpen (SafeUri uri)
{
if (current_state != PlayerState.Idle && current_state != PlayerState.NotReady && current_state != PlayerState.Contacting) {
@@ -117,6 +153,13 @@ namespace Banshee.MediaEngine
public abstract void Pause ();
+ public virtual void SetNextTrackUri (SafeUri uri)
+ {
+ // Opening files on SetNextTrack is a sane default behaviour.
+ // This only wants to be overridden if the PlayerEngine sends out RequestNextTrack signals before EoS
+ OpenUri (uri);
+ }
+
public virtual void VideoExpose (IntPtr displayContext, bool direct)
{
throw new NotImplementedException ("Engine must implement VideoExpose since this method only gets called when SupportsVideo is true");
@@ -163,6 +206,13 @@ namespace Banshee.MediaEngine
protected virtual void OnEventChanged (PlayerEventArgs args)
{
+ if (args.Event == PlayerEvent.StartOfStream && pending_track != null) {
+ Log.DebugFormat ("OnEventChanged called with StartOfStream. Replacing current_track: \"{0}\" with pending_track: \"{1}\"", current_track.DisplayTrackTitle, pending_track.DisplayTrackTitle);
+ current_track = pending_track;
+ current_uri = pending_uri;
+ pending_track = null;
+ pending_uri = null;
+ }
if (ThreadAssist.InMainThread) {
RaiseEventChanged (args);
} else {
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
index 2ad3f74..94c70fb 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
@@ -246,6 +246,10 @@ namespace Banshee.MediaEngine
}
}
+ if (args.Event == PlayerEvent.StartOfStream) {
+ incremented_last_played = false;
+ }
+
RaiseEvent (args);
// Do not raise iterate across DBus to avoid so many calls;
@@ -303,6 +307,46 @@ namespace Banshee.MediaEngine
OpenCheck (new SafeUri (uri));
}
+ public void SetNextTrack (TrackInfo track)
+ {
+ if (track != null && EnsureActiveEngineCanPlay (track.Uri)) {
+ active_engine.SetNextTrack (track);
+ } else {
+ active_engine.SetNextTrack ((TrackInfo) null);
+ }
+ }
+
+ public void SetNextTrack (SafeUri uri)
+ {
+ if (EnsureActiveEngineCanPlay (uri)) {
+ active_engine.SetNextTrack (uri);
+ } else {
+ active_engine.SetNextTrack ((SafeUri) null);
+ }
+ }
+
+ private bool EnsureActiveEngineCanPlay (SafeUri uri)
+ {
+ if (uri == null) {
+ // No engine can play the null URI.
+ return false;
+ }
+ if (active_engine != FindSupportingEngine (uri)) {
+ if (active_engine.CurrentState == PlayerState.Playing) {
+ // If we're currently playing then we can't switch engines now.
+ // We can't ensure the active engine can play this URI.
+ return false;
+ } else {
+ // If we're not playing, we can switch the active engine to
+ // something that will play this URI.
+ SwitchToEngine (FindSupportingEngine (uri));
+ CheckPending ();
+ return true;
+ }
+ }
+ return true;
+ }
+
public void OpenPlay (TrackInfo track)
{
OpenPlay (track, true);
@@ -349,17 +393,14 @@ namespace Banshee.MediaEngine
return;
}
- IncrementLastPlayed ();
-
- FindSupportingEngine (uri);
+ PlayerEngine supportingEngine = FindSupportingEngine (uri);
+ SwitchToEngine (supportingEngine);
CheckPending ();
if (track != null) {
active_engine.Open (track);
- incremented_last_played = false;
} else if (uri != null) {
active_engine.Open (uri);
- incremented_last_played = false;
}
if (play) {
@@ -381,34 +422,39 @@ namespace Banshee.MediaEngine
}
}
- private void FindSupportingEngine (SafeUri uri)
+ private PlayerEngine FindSupportingEngine (SafeUri uri)
{
foreach (PlayerEngine engine in engines) {
foreach (string extension in engine.ExplicitDecoderCapabilities) {
if (!uri.AbsoluteUri.EndsWith (extension)) {
continue;
- } else if (active_engine != engine) {
- Close ();
- pending_engine = engine;
- Log.DebugFormat ("Switching engine to: {0}", engine.GetType ());
}
- return;
+ return engine;
}
}
foreach (PlayerEngine engine in engines) {
foreach (string scheme in engine.SourceCapabilities) {
bool supported = scheme == uri.Scheme;
- if (supported && active_engine != engine) {
- Close ();
- pending_engine = engine;
- Log.DebugFormat ("Switching engine to: {0}", engine.GetType ());
- return;
- } else if (supported) {
- return;
+ if (supported) {
+ return engine;
}
}
}
+ // If none of our engines support this URI, return the currently active one.
+ // There doesn't seem to be anything better to do.
+ return active_engine;
+ }
+
+ private bool SwitchToEngine (PlayerEngine switchTo)
+ {
+ if (active_engine != switchTo) {
+ Close ();
+ pending_engine = switchTo;
+ Log.DebugFormat ("Switching engine to: {0}", switchTo.GetType ());
+ return true;
+ }
+ return false;
}
public void Close ()
@@ -644,7 +690,8 @@ namespace Banshee.MediaEngine
| PlayerEvent.Error
| PlayerEvent.Volume
| PlayerEvent.Metadata
- | PlayerEvent.TrackInfoUpdated;
+ | PlayerEvent.TrackInfoUpdated
+ | PlayerEvent.RequestNextTrack;
private const PlayerEvent event_default_mask = event_all_mask & ~PlayerEvent.Iterate;
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs
index 96b1b79..a5c714f 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs
@@ -108,7 +108,8 @@ namespace Banshee.MediaEngine
Error = 64,
Volume = 128,
Metadata = 256,
- TrackInfoUpdated = 512
+ TrackInfoUpdated = 512,
+ RequestNextTrack = 1024
}
public enum PlayerState
diff --git a/src/Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs b/src/Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs
index 4d793c7..9825f8a 100644
--- a/src/Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs
+++ b/src/Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs
@@ -31,7 +31,7 @@ namespace Banshee.PlaybackController
public interface IBasicPlaybackController
{
bool First ();
- bool Next (bool restart);
+ bool Next (bool restart, bool userRequested);
bool Previous (bool restart);
}
}
diff --git a/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs b/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
index 6625cde..fa30d5a 100644
--- a/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
+++ b/src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs
@@ -90,6 +90,7 @@ namespace Banshee.PlaybackController
player_engine = ServiceManager.PlayerEngine;
player_engine.PlayWhenIdleRequest += OnPlayerEnginePlayWhenIdleRequest;
player_engine.ConnectEvent (OnPlayerEvent,
+ PlayerEvent.RequestNextTrack |
PlayerEvent.EndOfStream |
PlayerEvent.StartOfStream |
PlayerEvent.StateChange |
@@ -164,6 +165,9 @@ namespace Banshee.PlaybackController
transition_track_started = true;
}
break;
+ case PlayerEvent.RequestNextTrack:
+ RequestTrackHandler ();
+ break;
}
}
@@ -177,12 +181,18 @@ namespace Banshee.PlaybackController
private bool EosTransition ()
{
+ player_engine.IncrementLastPlayed ();
+ return true;
+ }
+
+ private bool RequestTrackHandler ()
+ {
if (!StopWhenFinished) {
if (RepeatMode == PlaybackRepeatMode.RepeatSingle) {
QueuePlayTrack ();
} else {
last_was_skipped = false;
- Next ();
+ Next (RepeatMode == PlaybackRepeatMode.RepeatAll, false);
}
} else {
OnStopped ();
@@ -211,21 +221,28 @@ namespace Banshee.PlaybackController
public void Next ()
{
- Next (RepeatMode == PlaybackRepeatMode.RepeatAll);
+ Next (RepeatMode == PlaybackRepeatMode.RepeatAll, true);
}
public void Next (bool restart)
{
+ Next (restart, true);
+ }
+
+ public void Next (bool restart, bool userRequested)
+ {
CancelErrorTransition ();
Source = NextSource;
raise_started_after_transition = true;
- player_engine.IncrementLastPlayed ();
+ if (userRequested) {
+ player_engine.IncrementLastPlayed ();
+ }
- if (Source is IBasicPlaybackController && ((IBasicPlaybackController)Source).Next (restart)) {
+ if (Source is IBasicPlaybackController && ((IBasicPlaybackController)Source).Next (restart, userRequested)) {
} else {
- ((IBasicPlaybackController)this).Next (restart);
+ ((IBasicPlaybackController)this).Next (restart, userRequested);
}
OnTransition ();
@@ -261,29 +278,19 @@ namespace Banshee.PlaybackController
return true;
}
- bool IBasicPlaybackController.Next (bool restart)
+ bool IBasicPlaybackController.Next (bool restart, bool userRequested)
{
- TrackInfo tmp_track = CurrentTrack;
-
- if (next_stack.Count > 0) {
- CurrentTrack = next_stack.Pop ();
- if (tmp_track != null) {
- previous_stack.Push (tmp_track);
- }
- } else {
- TrackInfo next_track = QueryTrack (Direction.Next, restart);
- if (next_track != null) {
- if (tmp_track != null) {
- previous_stack.Push (tmp_track);
- }
- } else {
- return true;
- }
-
- CurrentTrack = next_track;
+ if (CurrentTrack != null) {
+ previous_stack.Push (CurrentTrack);
}
- QueuePlayTrack ();
+ CurrentTrack = CalcNextTrack (Direction.Next, restart);
+ if (!userRequested) {
+ // A RequestNextTrack event should always result in SetNextTrack being called. null is acceptable.
+ player_engine.SetNextTrack (CurrentTrack);
+ } else if (CurrentTrack != null) {
+ QueuePlayTrack ();
+ }
return true;
}
@@ -293,21 +300,28 @@ namespace Banshee.PlaybackController
next_stack.Push (current_track);
}
- if (previous_stack.Count > 0) {
- CurrentTrack = previous_stack.Pop ();
- } else {
- TrackInfo track = CurrentTrack = QueryTrack (Direction.Previous, restart);
- if (track != null) {
- CurrentTrack = track;
- } else {
- return true;
- }
+ CurrentTrack = CalcNextTrack (Direction.Previous, restart);
+ if (CurrentTrack != null) {
+ QueuePlayTrack ();
}
- QueuePlayTrack ();
return true;
}
+ private TrackInfo CalcNextTrack (Direction direction, bool restart)
+ {
+ if (direction == Direction.Previous) {
+ if (previous_stack.Count > 0) {
+ return previous_stack.Pop ();
+ }
+ } else if (direction == Direction.Next) {
+ if (next_stack.Count > 0) {
+ return next_stack.Pop ();
+ }
+ }
+ return QueryTrack (direction, restart);
+ }
+
private TrackInfo QueryTrack (Direction direction, bool restart)
{
Log.DebugFormat ("Querying model for track to play in {0}:{1} mode", ShuffleMode, direction);
diff --git a/src/Extensions/Banshee.InternetRadio/Banshee.InternetRadio/InternetRadioSource.cs b/src/Extensions/Banshee.InternetRadio/Banshee.InternetRadio/InternetRadioSource.cs
index 7447515..5d700b7 100644
--- a/src/Extensions/Banshee.InternetRadio/Banshee.InternetRadio/InternetRadioSource.cs
+++ b/src/Extensions/Banshee.InternetRadio/Banshee.InternetRadio/InternetRadioSource.cs
@@ -279,8 +279,20 @@ namespace Banshee.InternetRadio
return false;
}
- public bool Next (bool restart)
+ public bool Next (bool restart, bool userRequested)
{
+ /*
+ * TODO: It should be technically possible to handle userRequested=False
+ * correctly here, but the current implementation is quite hostile.
+ * For the moment, just SetNextTrack (null), and go on to OpenPlay if
+ * the engine isn't currently playing.
+ */
+ if (!userRequested) {
+ ServiceManager.PlayerEngine.SetNextTrack ((SafeUri)null);
+ if (ServiceManager.PlayerEngine.IsPlaying ()) {
+ return true;
+ }
+ }
RadioTrackInfo radio_track = ServiceManager.PlaybackController.CurrentTrack as RadioTrackInfo;
if (radio_track != null && radio_track.PlayNextStream ()) {
return true;
diff --git a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs
index 2aeffce..746349b 100644
--- a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs
+++ b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs
@@ -294,17 +294,24 @@ namespace Banshee.Lastfm.Radio
bool IBasicPlaybackController.First ()
{
- return ((IBasicPlaybackController)this).Next (false);
+ return ((IBasicPlaybackController)this).Next (false, true);
}
private bool playback_requested;
- bool IBasicPlaybackController.Next (bool restart)
+ bool IBasicPlaybackController.Next (bool restart, bool userRequested)
{
TrackInfo next = NextTrack;
- if (next != null) {
- ServiceManager.PlayerEngine.OpenPlay (next);
- } else {
- playback_requested = true;
+ if (userRequested) {
+ if (next != null) {
+ ServiceManager.PlayerEngine.OpenPlay (next);
+ } else {
+ playback_requested = true;
+ }
+ } else {
+ // We want to unconditionally SetNextTrack.
+ // Passing null is OK.
+ ServiceManager.PlayerEngine.SetNextTrack (next);
+ playback_requested = next == null;
}
return true;
}
@@ -372,7 +379,7 @@ namespace Banshee.Lastfm.Radio
if (playback_requested) {
if (this == ServiceManager.PlaybackController.Source ) {
- ((IBasicPlaybackController)this).Next (false);
+ ((IBasicPlaybackController)this).Next (false, true);
}
playback_requested = false;
}
diff --git a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
index 85013fe..22b21fe 100644
--- a/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
+++ b/src/Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs
@@ -519,10 +519,10 @@ namespace Banshee.PlayQueue
bool IBasicPlaybackController.First ()
{
- return ((IBasicPlaybackController)this).Next (false);
+ return ((IBasicPlaybackController)this).Next (false, true);
}
- bool IBasicPlaybackController.Next (bool restart)
+ bool IBasicPlaybackController.Next (bool restart, bool userRequested)
{
if (current_track != null && ServiceManager.PlayerEngine.CurrentTrack == current_track) {
int index = TrackModel.IndexOf (current_track) + 1;
@@ -533,14 +533,21 @@ namespace Banshee.PlayQueue
ServiceManager.PlaybackController.Source = PriorSource;
if (was_playing) {
ServiceManager.PlaybackController.PriorTrack = prior_playback_track;
- ServiceManager.PlaybackController.Next (restart);
+ ServiceManager.PlaybackController.Next (restart, userRequested);
} else {
+ if (!userRequested) {
+ ServiceManager.PlayerEngine.SetNextTrack ((TrackInfo)null);
+ }
ServiceManager.PlayerEngine.Close ();
}
return true;
}
- ServiceManager.PlayerEngine.OpenPlay (current_track);
+ if (userRequested) {
+ ServiceManager.PlayerEngine.OpenPlay (current_track);
+ } else {
+ ServiceManager.PlayerEngine.SetNextTrack (current_track);
+ }
return true;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]