=== modified file 'libbanshee/banshee-player-pipeline.c'
--- libbanshee/banshee-player-pipeline.c	2008-09-03 18:58:15 +0000
+++ libbanshee/banshee-player-pipeline.c	2009-03-17 05:11:27 +0000
@@ -172,6 +172,18 @@
     return TRUE;
 }
 
+static void bp_about_to_finish_callback (GstElement *playbin, BansheePlayer *player)
+{
+    if (player->next_uri != NULL) {
+        bp_debug ("Gapless: Setting uri to: %s", player->next_uri);
+        g_object_set (G_OBJECT (playbin), "uri", player->next_uri, NULL);
+        g_free (player->next_uri);
+        player->next_uri = NULL;
+    } else {
+        bp_debug ("Gapless: about-to-finish called, but player->next_track not set");
+    }
+}
+
 // ---------------------------------------------------------------------------
 // Internal Functions
 // ---------------------------------------------------------------------------
@@ -190,8 +202,14 @@
     
     // 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);
+    
+    // It seems that playbin2 is terribly sensitive about precisely when setting "uri" to queue the next track
+    // will work.  So, handle the callback in native code, using player->next_uri which can be set at leisure from
+    // native code.
+    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

=== modified file 'libbanshee/banshee-player-private.h'
--- libbanshee/banshee-player-private.h	2009-01-24 22:05:00 +0000
+++ libbanshee/banshee-player-private.h	2009-03-17 04:30:51 +0000
@@ -70,6 +70,7 @@
 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, gfloat *spectrum);
+typedef void (* BansheeBufferNextTrackCallback)    (GstElement *playbin, gpointer unused);
 
 struct BansheePlayer {
     // Player Callbacks
@@ -139,6 +140,8 @@
     gdouble album_peak;
     gdouble track_gain;
     gdouble track_peak;
+    
+    gchar *next_uri;
 };
 
 #endif /* _BANSHEE_PLAYER_PRIVATE_H */

=== modified file 'libbanshee/banshee-player.c'
--- libbanshee/banshee-player.c	2009-02-03 16:41:51 +0000
+++ libbanshee/banshee-player.c	2009-03-17 05:11:27 +0000
@@ -198,6 +198,14 @@
 }
 
 P_INVOKE gboolean
+bp_set_next_track (BansheePlayer *player, const gchar *uri)
+{
+	g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
+	player->next_uri = g_strdup(uri);
+	return TRUE;
+}
+
+P_INVOKE gboolean
 bp_set_position (BansheePlayer *player, guint64 time_ms)
 {
     g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);

=== modified file 'src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs'
--- src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs	2009-01-24 22:05:00 +0000
+++ src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs	2009-03-17 06:29:14 +0000
@@ -80,6 +80,8 @@
         private bool buffering_finished;
         private int pending_volume = -1;
         private bool xid_is_set = false;
+		private uint last_position;
+		private bool next_track_set = false;
         
         private event VisualizationDataHandler data_available = null;
         public event VisualizationDataHandler DataAvailable {
@@ -143,7 +145,7 @@
             buffering_callback = new BansheePlayerBufferingCallback (OnBuffering);
             vis_data_callback = new BansheePlayerVisDataCallback (OnVisualizationData);
             tag_found_callback = new GstTaggerTagFoundCallback (OnTagFound);
-            
+
             bp_set_eos_callback (handle, eos_callback);
             bp_set_iterate_callback (handle, iterate_callback);
             bp_set_error_callback (handle, error_callback);
@@ -206,6 +208,16 @@
         {
             bp_pause (handle);
         }
+
+		public override void SetNextTrackUri (SafeUri uri)
+		{
+			IntPtr uri_ptr = GLib.Marshaller.StringToPtrGStrdup (uri.AbsoluteUri);
+			try {
+			     bp_set_next_track (handle, uri_ptr);
+			} finally {
+				GLib.Marshaller.Free (uri_ptr);
+			}
+		}
         
         public override void VideoExpose (IntPtr window, bool direct)
         {
@@ -231,6 +243,16 @@
         
         private void OnIterate (IntPtr player)
         {
+			if (((Length - Position) < 1000 * 5) && !next_track_set) {
+				OnEventChanged (PlayerEvent.BufferNextTrack);
+				next_track_set = true;
+			}
+			if (Position < last_position) {
+				next_track_set = false;
+				OnEventChanged (PlayerEvent.StartOfStream);
+			}
+			last_position = Position;
+				
             OnEventChanged (PlayerEvent.Iterate);
         }
         
@@ -568,6 +590,9 @@
         private static extern void bp_play (HandleRef player);
         
         [DllImport ("libbanshee")]
+        private static extern bool bp_set_next_track (HandleRef player, IntPtr uri);
+
+		[DllImport ("libbanshee")]
         private static extern void bp_set_volume (HandleRef player, double volume);
         
         [DllImport("libbanshee")]

=== modified file 'src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs'
--- src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs	2009-01-30 17:45:53 +0000
+++ src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs	2009-03-17 06:58:24 +0000
@@ -47,6 +47,8 @@
         
         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;
         
@@ -86,7 +88,40 @@
             current_track = new UnknownTrackInfo (uri);
             
             HandleOpen (uri);
-        }
+		}
+
+		public bool SetNextTrack (TrackInfo track)
+		{
+			pending_uri = track.Uri;
+			pending_track = track;
+
+			return HandleNextTrack (pending_uri);
+		}
+		
+		public bool SetNextTrack (SafeUri uri)
+		{
+			pending_uri = uri;
+			pending_track = new UnknownTrackInfo (uri);
+
+			return HandleNextTrack (uri);
+		}
+
+		private bool HandleNextTrack (SafeUri uri)
+		{
+			if (current_state != PlayerState.Playing) {
+				// Pre-buffering the next track only makes sense when we're currently playing
+				return false;
+			}
+
+			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);
+				return false;
+			}
+			return true;
+		}
 
         private void HandleOpen (SafeUri uri)
         {
@@ -107,6 +142,11 @@
 
         public abstract void Pause ();
 
+		public virtual void SetNextTrackUri (SafeUri uri)
+		{
+			throw new NotImplementedException ("Engine must implement SetNextTrackUri since this method only gets called when the engine supports gapless playback");
+		}
+
         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");
@@ -148,6 +188,12 @@
         
         protected void OnEventChanged (PlayerEvent evnt)
         {
+			if (evnt == PlayerEvent.StartOfStream && pending_track != null) {
+				current_track = pending_track;
+				current_uri = pending_uri;
+				pending_track = null;
+				pending_uri = null;
+			}
             OnEventChanged (new PlayerEventArgs (evnt));
         }
         

=== modified file 'src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs'
--- src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs	2009-02-02 19:25:23 +0000
+++ src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs	2009-03-17 05:11:27 +0000
@@ -268,7 +268,53 @@
         {
             OpenCheck (new SafeUri (uri));
         }
-        
+
+		/// <summary>
+		/// Give the PlayerEngine the next track, so that it can buffer it for gapless/crossfading playback.
+		/// </summary>
+		/// <param name="track">
+		/// A <see cref="TrackInfo"/>
+		/// </param>
+		/// <returns>
+		/// True iff the engine successfully set <paramref name="track"/> to be pending.  In this case, playback will
+		/// automatically continue to <paramref name="track"/> when the next <see cref="PlayerEvent.EndOfStream"/> is raised.
+		/// </returns>
+		/// <remarks>
+		/// This function must only be called in response to a BufferNextTrack <see cref="PlayerEvent"/>.  Only engines
+		/// which raise this event are guaranteed to handle gapless buffering.
+		/// </remarks>
+		public bool SetNextTrack (TrackInfo track)
+		{
+			if (active_engine != FindSupportingEngine (track.Uri)) {
+				// If the current engine does not support the next track, it will be extremely difficult to make the
+				// transition across playback engines gapless.  Just refuse to try, instead.
+				return false;
+			}
+			return active_engine.SetNextTrack (track);
+		}
+
+		/// <summary>
+		/// Give the PlayerEngine the next track, so that it can buffer it for gapless/crossfading playback.
+		/// </summary>
+		/// <param name="uri">
+		/// A <see cref="SafeUri"/>
+		/// </param>
+		/// <returns>
+		/// True iff the engine successfully set <paramref name="uri"/> to be pending.  In this case, playback will
+		/// automatically continue to <paramref name="uri"/> when the next <see cref="PlayerEvent.EndOfStream"/> is raised.
+		/// </returns>
+		/// <remarks>
+		/// This function must only be called in response to a BufferNextTrack <see cref="PlayerEvent"/>.  Only engines
+		/// which raise this event are guaranteed to handle gapless buffering.
+		/// </remarks>
+		public bool SetNextTrack (SafeUri uri)
+		{
+			if (active_engine != FindSupportingEngine (uri)) {
+				return false;
+			}
+			return active_engine.SetNextTrack (uri);
+		}
+
         public void OpenPlay (TrackInfo track)
         {
             OpenPlay (track, true);
@@ -317,7 +363,8 @@
 
             IncrementLastPlayed ();
             
-            FindSupportingEngine (uri);
+            PlayerEngine supportingEngine = FindSupportingEngine (uri);
+			SwitchToEngine (supportingEngine);
             CheckPending ();
             
             if (track != null) {
@@ -348,35 +395,40 @@
             }
         }
         
-        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 ()
         {
@@ -611,7 +663,8 @@
             | PlayerEvent.Error
             | PlayerEvent.Volume
             | PlayerEvent.Metadata
-            | PlayerEvent.TrackInfoUpdated;
+            | PlayerEvent.TrackInfoUpdated
+            | PlayerEvent.BufferNextTrack;
         
         private const PlayerEvent event_default_mask = event_all_mask & ~PlayerEvent.Iterate;
         

=== modified file 'src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs'
--- src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs	2008-05-04 21:42:09 +0000
+++ src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEvent.cs	2009-03-15 21:03:53 +0000
@@ -108,7 +108,8 @@
         Error = 64,
         Volume = 128,
         Metadata = 256,
-        TrackInfoUpdated = 512
+        TrackInfoUpdated = 512,
+        BufferNextTrack = 1024
     }
     
     public enum PlayerState 

=== modified file 'src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs'
--- src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs	2008-10-09 19:13:48 +0000
+++ src/Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs	2009-03-17 07:16:36 +0000
@@ -90,8 +90,9 @@
                 PlayerEvent.EndOfStream | 
                 PlayerEvent.StartOfStream |
                 PlayerEvent.StateChange |
-                PlayerEvent.Error, 
-                true);
+                PlayerEvent.Error |
+			    PlayerEvent.BufferNextTrack,
+			    true);
 
             ServiceManager.SourceManager.ActiveSourceChanged += delegate {
                 ITrackModelSource active_source = ServiceManager.SourceManager.ActiveSource as ITrackModelSource;
@@ -126,10 +127,17 @@
             switch (args.Event) {
                 case PlayerEvent.StartOfStream:
                     consecutive_errors = 0;
+				Log.DebugFormat ("ControllerService: StartOfStream signalled.  Our current track: {0}, playback_engine.CurrentTrack: {1}", CurrentTrack == null ? "None" : CurrentTrack.Uri.AbsoluteUri,
+				                 player_engine.CurrentTrack == null ? "None" : player_engine.CurrentTrack.Uri.AbsoluteUri);
+				CurrentTrack = player_engine.CurrentTrack;
                     break;
                 case PlayerEvent.EndOfStream:
                     EosTransition ();
                     break;
+			case PlayerEvent.BufferNextTrack:
+				changing_to_track = QueryTrack (Direction.Next, false);
+				player_engine.SetNextTrack (changing_to_track);
+				break;
                 case PlayerEvent.Error:
                     if (++consecutive_errors >= 5) {
                         consecutive_errors = 0;
@@ -143,11 +151,14 @@
                     Application.RunTimeout (250, EosTransition);
                     break;
                 case PlayerEvent.StateChange:
-                    if (((PlayerEventStateChangeArgs)args).Current != PlayerState.Loading) {
+				Log.DebugFormat ("PlaybackController: State change: {0}", ((PlayerEventStateChangeArgs)args).ToString ());
+				if (!(((PlayerEventStateChangeArgs)args).Current == PlayerState.Loading || ((PlayerEventStateChangeArgs)args).Current == PlayerState.Paused)) {
                         break;
                     }
+				Log.Debug ("Continuing");
                        
                     TrackInfo track = player_engine.CurrentTrack;
+				Log.DebugFormat ("Player engine current track is: {0}.  changing_to_track is: {1}", player_engine.CurrentTrack.Uri, changing_to_track.Uri);
                     if (changing_to_track != track && track != null) {
                         CurrentTrack = track;
                     }

