diff --git a/Banshee.sln b/Banshee.sln
index ff76e1d..2ebd4af 100644
--- a/Banshee.sln
+++ b/Banshee.sln
@@ -98,7 +98,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.Wikipedia", "src\Ex
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.NowPlaying", "src\Extensions\Banshee.NowPlaying\Banshee.NowPlaying.csproj", "{16FB0D3A-53FA-4B8E-B02B-4AF66E87829A}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.AudioCd", "src\Extensions\Banshee.AudioCd\Banshee.AudioCd.csproj", "{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.Discs", "src\Extensions\Banshee.Discs\Banshee.Discs.csproj", "{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.LibraryWatcher", "src\Extensions\Banshee.LibraryWatcher\Banshee.LibraryWatcher.csproj", "{49CA3F27-0BB6-428d-8B3A-20232493652E}"
 EndProject
diff --git a/build/build.environment.mk b/build/build.environment.mk
index 5dd05ae..1f4a474 100644
--- a/build/build.environment.mk
+++ b/build/build.environment.mk
@@ -127,11 +127,11 @@ REF_DAP_KARMA = $(LINK_DAP_DEPS) $(LINK_KARMA_DEPS)
 LINK_EXTENSION_AMAZONMP3 = -r:$(DIR_BIN)/Banshee.AmazonMp3.exe
 REF_EXTENSION_AMAZONMP3 = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_AMAZONMP3_STORE = $(LINK_BANSHEE_WEBBROWSER_DEPS) $(LINK_EXTENSION_AMAZONMP3)
-REF_EXTENSION_AUDIOCD = $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_MUSICBRAINZ_DEPS)
 REF_EXTENSION_BOOSCRIPT = $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_BOO)
 REF_EXTENSION_BPM = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_COVERART = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_DAAP = $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_ICSHARP_ZIP_LIB) $(LINK_MONO_ZEROCONF)
+REF_EXTENSION_DISCS = $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_MUSICBRAINZ_DEPS)
 REF_EXTENSION_EMUSIC = $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_MIGO_DEPS)
 REF_EXTENSION_FILESYSTEMQUEUE = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_INTERNETRADIO = $(LINK_BANSHEE_THICKCLIENT_DEPS)
diff --git a/configure.ac b/configure.ac
index f87f0a6..1c7dfed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -340,11 +340,11 @@ src/Extensions/Makefile
 src/Extensions/Banshee.AmazonMp3/Makefile
 src/Extensions/Banshee.AmazonMp3.Store/Makefile
 src/Extensions/Banshee.Audiobook/Makefile
-src/Extensions/Banshee.AudioCd/Makefile
 src/Extensions/Banshee.BooScript/Makefile
 src/Extensions/Banshee.Bpm/Makefile
 src/Extensions/Banshee.CoverArt/Makefile
 src/Extensions/Banshee.Daap/Makefile
+src/Extensions/Banshee.Discs/Makefile
 src/Extensions/Banshee.Emusic/Makefile
 src/Extensions/Banshee.FileSystemQueue/Makefile
 src/Extensions/Banshee.InternetArchive/Makefile
diff --git a/data/addin-xml-strings.cs b/data/addin-xml-strings.cs
index 4fde14f..9e7053a 100644
--- a/data/addin-xml-strings.cs
+++ b/data/addin-xml-strings.cs
@@ -81,11 +81,6 @@ internal static class AddinXmlStringCatalog
         Catalog.GetString (@"Organize audiobooks, lectures, etc.");
         Catalog.GetString (@"Core");
 
-        // ../src/Extensions/Banshee.AudioCd/Banshee.AudioCd.addin.xml
-        Catalog.GetString (@"Audio CD Support");
-        Catalog.GetString (@"Listen to and rip Audio CDs.");
-        Catalog.GetString (@"Core");
-
         // ../src/Extensions/Banshee.BooScript/Banshee.BooScript.addin.xml
         Catalog.GetString (@"Boo Scripting");
         Catalog.GetString (@"Customize and extend Banshee with Boo-language scripts.");
@@ -106,6 +101,11 @@ internal static class AddinXmlStringCatalog
         Catalog.GetString (@"Browse and listen to music shared over your local network.");
         Catalog.GetString (@"Utilities");
 
+        // ../src/Extensions/Banshee.Discs/Banshee.Discs.addin.xml
+        Catalog.GetString (@"DVD and Audio CD Support");
+        Catalog.GetString (@"Watch DVDs; listen to and rip Audio CDs.");
+        Catalog.GetString (@"Core");
+
         // ../src/Extensions/Banshee.Emusic/Banshee.Emusic.addin.xml
         Catalog.GetString (@"eMusic Import");
         Catalog.GetString (@"Download and import music purchased from eMusic");
diff --git a/libbanshee/Makefile.am b/libbanshee/Makefile.am
index 415b329..6ed369a 100644
--- a/libbanshee/Makefile.am
+++ b/libbanshee/Makefile.am
@@ -19,6 +19,7 @@ libbanshee_la_SOURCES =  \
 	banshee-gst.c \
 	banshee-player.c \
 	banshee-player-cdda.c \
+	banshee-player-dvd.c \
 	banshee-player-equalizer.c \
 	banshee-player-missing-elements.c \
 	banshee-player-pipeline.c \
@@ -39,6 +40,7 @@ endif
 noinst_HEADERS =  \
 	banshee-gst.h \
 	banshee-player-cdda.h \
+	banshee-player-dvd.h \
 	banshee-player-equalizer.h \
 	banshee-player-missing-elements.h \
 	banshee-player-pipeline.h \
diff --git a/libbanshee/banshee-player-dvd.c b/libbanshee/banshee-player-dvd.c
new file mode 100644
index 0000000..ad96fe8
--- /dev/null
+++ b/libbanshee/banshee-player-dvd.c
@@ -0,0 +1,328 @@
+//
+// banshee-player-dvd.c
+//
+// Author:
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright (C) 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include "banshee-player-dvd.h"
+
+// ---------------------------------------------------------------------------
+// Private Functions
+// ---------------------------------------------------------------------------
+
+static void
+bp_dvd_on_notify_source (GstElement *playbin, gpointer unknown, BansheePlayer *player)
+{
+    GstElement *dvd_src = NULL;
+
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+
+    if (player->dvd_device == NULL) {
+        return;
+    }
+
+    g_object_get (playbin, "source", &dvd_src, NULL);
+    if (dvd_src == NULL) {
+        return;
+    }
+
+    if (G_LIKELY (g_object_class_find_property (G_OBJECT_GET_CLASS (dvd_src), "device"))) {
+      bp_debug2 ("bp_dvd: setting device property on source (%s)", player->dvd_device);
+      g_object_set (dvd_src, "device", player->dvd_device, NULL);
+    }
+
+    g_object_unref (dvd_src);
+}
+
+/*
+static gboolean
+bp_dvd_source_seek_to_track (GstElement *playbin, guint track)
+{
+    static GstFormat format = GST_FORMAT_UNDEFINED;
+    GstElement *dvd_src = NULL;
+    GstState state;
+
+    format = gst_format_get_by_nick ("track");
+    if (G_UNLIKELY (format == GST_FORMAT_UNDEFINED)) {
+        return FALSE;
+    }
+
+    gst_element_get_state (playbin, &state, NULL, 0);
+    if (state < GST_STATE_PAUSED) {
+        // We can only seek if the pipeline is playing or paused, otherwise
+        // we just allow playbin to do its thing, which will re-start the
+        // device and start at the desired track
+        return FALSE;
+    }
+
+    cdda_src = bp_cdda_get_cdda_source (playbin);
+    if (G_UNLIKELY (cdda_src == NULL)) {
+        return FALSE;
+    }
+
+    if (gst_element_seek (playbin, 1.0, format, GST_SEEK_FLAG_FLUSH, 
+        GST_SEEK_TYPE_SET, track - 1, GST_SEEK_TYPE_NONE, -1)) {
+        bp_debug2 ("bp_cdda: seeking to track %d, avoiding playbin", track);
+        g_object_unref (cdda_src);
+        return TRUE;
+    }
+
+    g_object_unref (cdda_src);
+    return FALSE;
+}
+*/
+
+// ---------------------------------------------------------------------------
+// Internal Functions
+// ---------------------------------------------------------------------------
+
+void
+_bp_dvd_pipeline_setup (BansheePlayer *player)
+{
+    if (player != NULL && player->playbin != NULL) {
+        g_signal_connect (player->playbin, "notify::source", G_CALLBACK (bp_dvd_on_notify_source), player);
+    }
+}
+
+gboolean
+_bp_dvd_handle_uri (BansheePlayer *player, const gchar *uri)
+{
+    // Processes URIs like dvd://<track-number>#<device-node> and overrides
+    // track transitioning through playbin if playback was already happening
+    // from the device node by seeking directly to the track since the disc
+    // is already spinning; playbin doesn't handle DVD URIs with device nodes
+    // so we have to handle setting the device property through the
+    // notify::source signal on playbin
+
+    const gchar *new_dvd_device;
+
+    if (player == NULL || uri == NULL || !g_str_has_prefix (uri, "dvd://")) {
+        // Something is hosed or the URI isn't actually DVD
+        if (player->dvd_device != NULL) {
+            bp_debug2 ("bp_dvd: finished using device (%s)", player->dvd_device);
+            g_free (player->dvd_device);
+            player->dvd_device = NULL;
+        }
+
+        return FALSE;
+    }
+
+    GError *error = NULL;
+    new_dvd_device = uri + 6;
+    if (error != NULL) {
+        bp_debug2 ("bp_dvd: error converting uri to filename: %s", error->message);
+        g_error_free (error);
+        return FALSE;
+    }
+
+    if (player->dvd_device == NULL) {
+        // If we weren't already playing from a DVD, cache the
+        // device and allow playbin to begin playing it
+        player->dvd_device = g_strdup (new_dvd_device);
+        bp_debug2 ("bp_dvd: storing device node for fast seeks (%s)", player->dvd_device);
+        return FALSE;
+    }
+
+    if (strcmp (new_dvd_device, player->dvd_device) == 0) {
+        // Parse the track number from the URI and seek directly to it
+        // since we are already playing from the device; prevent playbin
+        // from stopping/starting the DVD, which can take many many seconds
+        gchar *chapter_str = g_strndup (uri + 7, strlen (uri) - strlen (new_dvd_device) - 8);
+        //gint chapter_num = atoi (chapter_str);
+        g_free (chapter_str);
+        bp_debug2 ("bp_dvd: fast seeking to track on already playing device (%s)", player->dvd_device);
+
+        //return bp_dvd_source_seek_to_track (player->playbin, track_num);
+        return FALSE;
+    }
+
+    // We were already playing some DVD, but switched to a different device node, 
+    // so unset and re-cache the new device node and allow playbin to do its thing
+    bp_debug3 ("bp_dvd: switching devices for DVD playback (from %s, to %s)", player->dvd_device, new_dvd_device);
+    g_free (player->dvd_device);
+    player->dvd_device = g_strdup (new_dvd_device);
+
+    return FALSE;
+}
+
+//get navigation when videosink changed as done on video
+void _bp_dvd_find_navigation (BansheePlayer *player)
+{
+    GstElement *video_sink = NULL;
+    GstElement *navigation = NULL;
+    GstNavigation *previous_navigation;
+
+    previous_navigation = player->navigation;
+    g_object_get (player->playbin, "video-sink", &video_sink, NULL);
+
+    if (video_sink == NULL) {
+        player->navigation = NULL;
+        if (previous_navigation != NULL) {
+            gst_object_unref (previous_navigation);
+        }
+    }
+
+    navigation = GST_IS_BIN (video_sink)
+        ? gst_bin_get_by_interface (GST_BIN (video_sink), GST_TYPE_NAVIGATION)
+        : video_sink;
+
+    player->navigation = GST_IS_NAVIGATION (navigation) ? GST_NAVIGATION (navigation) : NULL;
+
+    if (previous_navigation != NULL) {
+        gst_object_unref (previous_navigation);
+    }
+    gst_object_unref (video_sink);
+}
+
+P_INVOKE gboolean
+bp_dvd_is_menu (BansheePlayer *player)
+{
+    return player->is_menu;
+}
+
+//mouse event
+
+P_INVOKE void
+bp_dvd_mouse_move_notify (BansheePlayer *player, double x, double y)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_mouse_event (player->navigation, "mouse-move", 0, x, y);
+    }
+}
+
+P_INVOKE void
+bp_dvd_mouse_button_pressed_notify (BansheePlayer *player, int button, double x, double y)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_mouse_event (player->navigation, "mouse-button-press", button, x, y);
+    }
+}
+
+P_INVOKE void
+bp_dvd_mouse_button_released_notify (BansheePlayer *player, int button, double x, double y)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_mouse_event (player->navigation, "mouse-button-release", button, x, y);
+    }
+}
+
+//keyboard event
+
+P_INVOKE void
+bp_dvd_left_notify (BansheePlayer *player)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_command (player->navigation, GST_NAVIGATION_COMMAND_LEFT);
+    }
+}
+
+P_INVOKE void
+bp_dvd_right_notify (BansheePlayer *player)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_command (player->navigation, GST_NAVIGATION_COMMAND_RIGHT);
+    }
+}
+
+P_INVOKE void
+bp_dvd_up_notify (BansheePlayer *player)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_command (player->navigation, GST_NAVIGATION_COMMAND_UP);
+    }
+}
+
+P_INVOKE void
+bp_dvd_down_notify (BansheePlayer *player)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_command (player->navigation, GST_NAVIGATION_COMMAND_DOWN);
+    }
+}
+
+P_INVOKE void
+bp_dvd_activate_notify (BansheePlayer *player)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_command (player->navigation, GST_NAVIGATION_COMMAND_ACTIVATE);
+    }
+}
+
+//command
+
+P_INVOKE void
+bp_dvd_go_to_menu (BansheePlayer *player)
+{
+    if (!player->navigation) {
+        _bp_dvd_find_navigation (player);
+    }
+    if (player->navigation) {
+        gst_navigation_send_command (player->navigation, GST_NAVIGATION_COMMAND_DVD_MENU);
+    }
+}
+
+P_INVOKE void
+bp_dvd_go_to_next_chapter (BansheePlayer *player)
+{
+    gint64 index;
+    GstFormat format = gst_format_get_by_nick ("chapter");
+    gst_element_query_position (player->playbin, &format, &index);
+    gst_element_seek (player->playbin, 1.0, format, GST_SEEK_FLAG_FLUSH,
+        GST_SEEK_TYPE_SET, index + 1, GST_SEEK_TYPE_NONE, 0);
+}
+
+P_INVOKE void
+bp_dvd_go_to_previous_chapter (BansheePlayer *player)
+{
+    gint64 index;
+    GstFormat format = gst_format_get_by_nick ("chapter");
+    gst_element_query_position (player->playbin, &format, &index);
+    gst_element_seek (player->playbin, 1.0, format, GST_SEEK_FLAG_FLUSH,
+        GST_SEEK_TYPE_SET, index - 1, GST_SEEK_TYPE_NONE, 0);
+}
diff --git a/libbanshee/banshee-player-dvd.h b/libbanshee/banshee-player-dvd.h
new file mode 100644
index 0000000..74bba6b
--- /dev/null
+++ b/libbanshee/banshee-player-dvd.h
@@ -0,0 +1,39 @@
+//
+// banshee-player-dvd.h
+//
+// Author:
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright (C) 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#ifndef _BANSHEE_PLAYER_DVD_H
+#define _BANSHEE_PLAYER_DVD_H
+
+#include "banshee-player-private.h"
+
+void      _bp_dvd_pipeline_setup    (BansheePlayer *player);
+gboolean  _bp_dvd_handle_uri        (BansheePlayer *player, const gchar *uri);
+void      _bp_dvd_find_navigation   (BansheePlayer *player);
+
+
+#endif /* _BANSHEE_PLAYER_DVD_H */
diff --git a/libbanshee/banshee-player-pipeline.c b/libbanshee/banshee-player-pipeline.c
index e0b3759..39eef19 100644
--- a/libbanshee/banshee-player-pipeline.c
+++ b/libbanshee/banshee-player-pipeline.c
@@ -30,6 +30,7 @@
 
 #include "banshee-player-pipeline.h"
 #include "banshee-player-cdda.h"
+#include "banshee-player-dvd.h"
 #include "banshee-player-video.h"
 #include "banshee-player-equalizer.h"
 #include "banshee-player-missing-elements.h"
@@ -209,9 +210,39 @@ bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
         case GST_MESSAGE_ELEMENT: {
             const GstStructure *messageStruct;
             messageStruct = gst_message_get_structure (message);
-            if (GST_MESSAGE_SRC (message) == GST_OBJECT (player->playbin) && gst_structure_has_name (messageStruct, "playbin2-stream-changed")) {
+            if (GST_MESSAGE_SRC (message) == GST_OBJECT (player->playbin)
+                    && gst_structure_has_name (messageStruct, "playbin2-stream-changed")) {
                 bp_next_track_starting (player);
             }
+            if ( gst_navigation_message_get_type (message) == GST_NAVIGATION_MESSAGE_COMMANDS_CHANGED) {
+                player->is_menu = FALSE;
+                // Get available command to know if player is in menu
+                GstQuery *query = gst_navigation_query_new_commands();
+
+                //execute query over playbin or navigation ?
+                if (gst_element_query (player->playbin, query)) {
+                    guint n_cmds, i;
+                    if (gst_navigation_query_parse_commands_length (query, &n_cmds)) {
+                        for (i = 0; i < n_cmds; i++) {
+                            GstNavigationCommand cmd;
+                            if (gst_navigation_query_parse_commands_nth (query, i, &cmd)) {
+                                switch (cmd) {
+                                    case GST_NAVIGATION_COMMAND_ACTIVATE:
+                                    case GST_NAVIGATION_COMMAND_LEFT:
+                                    case GST_NAVIGATION_COMMAND_RIGHT:
+                                    case GST_NAVIGATION_COMMAND_UP:
+                                    case GST_NAVIGATION_COMMAND_DOWN:
+                                        player->is_menu = TRUE;
+                                        break;
+                                    default:
+                                        break;
+                                }
+                            }
+                        }
+                    }
+                }
+                gst_query_unref (query);
+            }
             _bp_missing_elements_process_message (player, message);
             break;
         }
@@ -416,8 +447,9 @@ _bp_pipeline_construct (BansheePlayer *player)
     gst_pad_link (gst_element_get_request_pad (player->audiotee, "src0"), sinkpad);
     // Now allow specialized pipeline setups
     _bp_cdda_pipeline_setup (player);
+    _bp_dvd_pipeline_setup (player);
     _bp_video_pipeline_setup (player, bus);
-
+    _bp_dvd_find_navigation (player);
     return TRUE;
 }
 
diff --git a/libbanshee/banshee-player-private.h b/libbanshee/banshee-player-private.h
index 4d9bf44..26ed900 100644
--- a/libbanshee/banshee-player-private.h
+++ b/libbanshee/banshee-player-private.h
@@ -42,6 +42,7 @@
 #include <gst/fft/gstfftf32.h>
 #include <gst/pbutils/pbutils.h>
 #include <gst/tag/tag.h>
+#include <gst/interfaces/navigation.h>
 
 #if defined(GDK_WINDOWING_X11)
 #  include <gdk/gdkx.h>
@@ -150,6 +151,7 @@ struct BansheePlayer {
     GstState target_state;
     gboolean buffering;
     gchar *cdda_device;
+    gchar *dvd_device;
     gboolean in_gapless_transition;
     gboolean supports_stream_volume;
     gboolean audiosink_has_volume;
@@ -199,6 +201,10 @@ struct BansheePlayer {
     // http://replaygain.hydrogenaudio.org/player_scale.html
     gdouble rg_gain_history[10];
     gint history_size;
+
+    //dvd navigation
+    GstNavigation *navigation;
+    gboolean is_menu;
 };
 
 #endif /* _BANSHEE_PLAYER_PRIVATE_H */
diff --git a/libbanshee/banshee-player-vis.c b/libbanshee/banshee-player-vis.c
index e3c3ebb..1cb867b 100644
--- a/libbanshee/banshee-player-vis.c
+++ b/libbanshee/banshee-player-vis.c
@@ -168,6 +168,7 @@ _bp_vis_pipeline_event_probe (GstPad *pad, GstEvent *event, gpointer data)
         case GST_EVENT_FLUSH_STOP:
         case GST_EVENT_SEEK:
         case GST_EVENT_NEWSEGMENT:
+        case GST_EVENT_CUSTOM_DOWNSTREAM:
             player->vis_thawing = TRUE;
 
         default: break;
@@ -178,9 +179,10 @@ _bp_vis_pipeline_event_probe (GstPad *pad, GstEvent *event, gpointer data)
 
     switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_EOS:
+    case GST_EVENT_CUSTOM_DOWNSTREAM_OOB:
         _bp_vis_pipeline_set_blocked (player, FALSE);
         break;
-
+    case GST_EVENT_CUSTOM_DOWNSTREAM:
     case GST_EVENT_NEWSEGMENT:
         _bp_vis_pipeline_set_blocked (player, TRUE);
         break;
diff --git a/libbanshee/banshee-player.c b/libbanshee/banshee-player.c
index 56f8319..38c3645 100644
--- a/libbanshee/banshee-player.c
+++ b/libbanshee/banshee-player.c
@@ -110,6 +110,10 @@ bp_destroy (BansheePlayer *player)
     if (player->cdda_device != NULL) {
         g_free (player->cdda_device);
     }
+
+    if (player->dvd_device != NULL) {
+        g_free (player->dvd_device);
+    }
     
     _bp_pipeline_destroy (player);
     _bp_missing_elements_destroy (player);
@@ -155,6 +159,8 @@ bp_open (BansheePlayer *player, const gchar *uri)
     // in case it is able to perform a fast seek to a track
     if (_bp_cdda_handle_uri (player, uri)) {
         return TRUE;
+    } else if (_bp_dvd_handle_uri (player, uri)) {
+        return TRUE;
     } else if (player->playbin == NULL) {
         return FALSE;
     }
diff --git a/libbanshee/libbanshee.cproj b/libbanshee/libbanshee.cproj
index a98b090..e594807 100644
--- a/libbanshee/libbanshee.cproj
+++ b/libbanshee/libbanshee.cproj
@@ -11,7 +11,6 @@
     <Language>C</Language>
     <Target>Bin</Target>
     <SchemaVersion>2.0</SchemaVersion>
-    <ReleaseVersion>1.3</ReleaseVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -43,6 +42,7 @@
     <Compile Include="banshee-player-replaygain.c" />
     <Compile Include="banshee-player-vis.c" />
     <Compile Include="banshee-bpmdetector.c" />
+    <Compile Include="banshee-player-dvd.c" />
   </ItemGroup>
   <ItemGroup>
     <None Include="banshee-player-private.h" />
@@ -55,6 +55,7 @@
     <None Include="banshee-player-equalizer.h" />
     <None Include="banshee-player-replaygain.h" />
     <None Include="banshee-player-vis.h" />
+    <None Include="banshee-player-dvd.h" />
   </ItemGroup>
   <ProjectExtensions>
     <MonoDevelop>
diff --git a/libbanshee/libbanshee.sln b/libbanshee/libbanshee.sln
index 3a03817..ace1939 100644
--- a/libbanshee/libbanshee.sln
+++ b/libbanshee/libbanshee.sln
@@ -20,6 +20,18 @@ Global
 	EndGlobalSection
 	GlobalSection(MonoDevelopProperties) = preSolution
 		StartupItem = libbanshee.cproj
+		Policies = $0
+		$0.TextStylePolicy = $1
+		$1.FileWidth = 120
+		$1.inheritsSet = VisualStudio
+		$1.inheritsScope = text/plain
+		$1.scope = text/x-csrc
+		$0.TextStylePolicy = $2
+		$2.FileWidth = 120
+		$2.TabWidth = 4
+		$2.inheritsSet = Mono
+		$2.inheritsScope = text/plain
+		$2.scope = text/plain
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
index 426bbfc..f8c8f79 100644
--- a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
@@ -298,6 +298,61 @@ namespace Banshee.GStreamer
             return null;
         }
 
+        public override void NotifyMouseMove (double x, double y)
+        {
+            bp_dvd_mouse_move_notify (handle, x, y);
+        }
+
+        public override void NotifyMouseButtonPressed (int button, double x, double y)
+        {
+            bp_dvd_mouse_button_pressed_notify (handle, button, x, y);
+        }
+
+        public override void NotifyMouseButtonReleased (int button, double x, double y)
+        {
+            bp_dvd_mouse_button_released_notify (handle, button, x, y);
+        }
+
+        public override void NavigateToLeftMenu ()
+        {
+            bp_dvd_left_notify (handle);
+        }
+
+        public override void NavigateToRightMenu ()
+        {
+            bp_dvd_right_notify (handle);
+        }
+
+        public override void NavigateToUpMenu ()
+        {
+            bp_dvd_up_notify (handle);
+        }
+
+        public override void NavigateToDownMenu ()
+        {
+            bp_dvd_down_notify (handle);
+        }
+
+        public override void NavigateToMenu ()
+        {
+            bp_dvd_go_to_menu (handle);
+        }
+
+        public override void ActivateCurrentMenu ()
+        {
+            bp_dvd_activate_notify (handle);
+        }
+
+        public override void GoToNextChapter ()
+        {
+            bp_dvd_go_to_next_chapter (handle);
+        }
+
+        public override void GoToPreviousChapter ()
+        {
+            bp_dvd_go_to_previous_chapter (handle);
+        }
+
         private void OnEos (IntPtr player)
         {
             Close (false);
@@ -659,7 +714,7 @@ namespace Banshee.GStreamer
             bp_equalizer_set_gain (handle, band, gain);
         }
 
-        private static string [] source_capabilities = { "file", "http", "cdda" };
+        private static string [] source_capabilities = { "file", "http", "cdda", "dvd", "vcd" };
         public override IEnumerable SourceCapabilities {
             get { return source_capabilities; }
         }
@@ -733,6 +788,10 @@ namespace Banshee.GStreamer
             }
         }
 
+        public override bool IsMenu {
+            get { return bp_dvd_is_menu (handle); }
+        }
+
 #region ISupportClutter
 
         private IntPtr clutter_video_sink;
@@ -1005,5 +1064,41 @@ namespace Banshee.GStreamer
 
         [DllImport ("libbanshee.dll")]
         private static extern IntPtr bp_get_subtitle_description (HandleRef player, int index);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern bool bp_dvd_is_menu (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_mouse_move_notify (HandleRef player, double x, double y);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_mouse_button_pressed_notify (HandleRef player, int button, double x, double y);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_mouse_button_released_notify (HandleRef player, int button, double x, double y);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_left_notify (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_right_notify (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_up_notify (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_down_notify (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_activate_notify (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_go_to_menu (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_go_to_next_chapter (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_dvd_go_to_previous_chapter (HandleRef player);
    }
 }
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
index e60320f..e232065 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
@@ -249,6 +249,10 @@ namespace Banshee.GStreamerSharp
             }
         }
 
+        public override bool IsMenu {
+            get { return false; }
+        }
+
         public override SafeUri SubtitleUri {
             set { playbin.Suburi = value.AbsoluteUri; }
             get { return playbin.Suburi; }
diff --git a/src/Backends/Banshee.Gio/Banshee.Gio.csproj b/src/Backends/Banshee.Gio/Banshee.Gio.csproj
index 36d1bf5..63a7efd 100644
--- a/src/Backends/Banshee.Gio/Banshee.Gio.csproj
+++ b/src/Backends/Banshee.Gio/Banshee.Gio.csproj
@@ -58,13 +58,16 @@
       <Package>glib-sharp-2.0</Package>
     </Reference>
     <Reference Include="gio-sharp, Version=2.14.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
+      <Package>gio-sharp-2.0</Package>
     </Reference>
     <Reference Include="gtk-sharp-beans">
       <SpecificVersion>False</SpecificVersion>
+      <Package>gtk-sharp-beans-2.0</Package>
     </Reference>
     <Reference Include="Mono.Posix" />
     <Reference Include="gkeyfile-sharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e886e4e52ee77480">
       <SpecificVersion>False</SpecificVersion>
+      <Package>gkeyfile-sharp</Package>
     </Reference>
     <Reference Include="libgudev, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3565e728cafab25e">
       <SpecificVersion>False</SpecificVersion>
@@ -81,7 +84,6 @@
     <Compile Include="Banshee.Hardware.Gio\Device.cs" />
     <Compile Include="Banshee.Hardware.Gio\DeviceMediaCapabilities.cs" />
     <Compile Include="Banshee.Hardware.Gio\DiscVolume.cs" />
-    <Compile Include="Banshee.Hardware.Gio\DiskDevice.cs" />
     <Compile Include="Banshee.Hardware.Gio\HardwareManager.cs" />
     <Compile Include="Banshee.Hardware.Gio\LowLevel\GioDriveMetadetaSource.cs" />
     <Compile Include="Banshee.Hardware.Gio\LowLevel\GioMetadataSource.cs" />
diff --git a/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/DiscVolume.cs b/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/DiscVolume.cs
index 96a2767..e5b3921 100644
--- a/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/DiscVolume.cs
+++ b/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/DiscVolume.cs
@@ -26,6 +26,8 @@
 
 #if ENABLE_GIO_HARDWARE
 using System;
+using System.Linq;
+using GLib;
 
 using Banshee.Hardware;
 
@@ -33,6 +35,8 @@ namespace Banshee.Hardware.Gio
 {
     class DiscVolume : Volume, IDiscVolume
     {
+        private static string[] video_mime_types;
+
         public static new IDiscVolume Resolve (IDevice device)
         {
             var raw = device as IRawDevice;
@@ -43,6 +47,14 @@ namespace Banshee.Hardware.Gio
             return null;
         }
 
+        static DiscVolume ()
+        {
+            video_mime_types = new string[] {"x-content/video-dvd",
+                "x-content/video-vcd",
+                "x-content/video-svcd"
+            };
+        }
+
         public bool HasAudio {
             get {
                 return PropertyExists ("ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
@@ -55,6 +67,14 @@ namespace Banshee.Hardware.Gio
             }
         }
 
+        public bool HasVideo {
+            get {
+                return ((GioVolumeMetadataSource) this.device.GioMetadata).MediaContentTypes
+                    .Intersect (video_mime_types)
+                    .Any ();
+            }
+        }
+
         public bool IsBlank {
             get {
                 return GetPropertyString ("ID_CDROM_MEDIA_STATE") == "blank";
diff --git a/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/LowLevel/GioVolumeMetadataSource.cs b/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/LowLevel/GioVolumeMetadataSource.cs
index 9a861fc..459a97f 100644
--- a/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/LowLevel/GioVolumeMetadataSource.cs
+++ b/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/LowLevel/GioVolumeMetadataSource.cs
@@ -26,6 +26,7 @@
 
 #if ENABLE_GIO_HARDWARE
 using System;
+using System.Linq;
 
 namespace Banshee.Hardware.Gio
 {
@@ -74,6 +75,19 @@ namespace Banshee.Hardware.Gio
         {
             throw new NotImplementedException ();
         }
+
+        private string[] content_types;
+        public string[] MediaContentTypes {
+            get {
+                if (Volume.MountInstance == null) {
+                    content_types = new string[] {};
+                } else if (content_types == null) {
+                    content_types = Volume.MountInstance.GuessContentTypeSync (false, null);
+                }
+
+                return content_types;
+            }
+        }
     }
 }
 
diff --git a/src/Backends/Banshee.Hal/Banshee.HalBackend/DiscVolume.cs b/src/Backends/Banshee.Hal/Banshee.HalBackend/DiscVolume.cs
index 1798984..e8cfbae 100644
--- a/src/Backends/Banshee.Hal/Banshee.HalBackend/DiscVolume.cs
+++ b/src/Backends/Banshee.Hal/Banshee.HalBackend/DiscVolume.cs
@@ -51,6 +51,10 @@ namespace Banshee.HalBackend
             get { return HalDevice.GetPropertyBoolean ("volume.disc.has_data"); }
         }
 
+        public bool HasVideo {
+            get { return HalDevice.GetPropertyBoolean ("volume.disc.is_videodvd"); }
+        }
+
         public bool IsBlank {
             get { return HalDevice.GetPropertyBoolean ("volume.disc.is_blank"); }
         }
diff --git a/src/Core/Banshee.Services/Banshee.Hardware/IDiscVolume.cs b/src/Core/Banshee.Services/Banshee.Hardware/IDiscVolume.cs
index d5fb215..c9e86f3 100644
--- a/src/Core/Banshee.Services/Banshee.Hardware/IDiscVolume.cs
+++ b/src/Core/Banshee.Services/Banshee.Hardware/IDiscVolume.cs
@@ -34,6 +34,7 @@ namespace Banshee.Hardware
     {
         bool HasAudio { get; }
         bool HasData { get; }
+        bool HasVideo { get; }
         bool IsRewritable { get; }
         bool IsBlank { get; }
         ulong MediaCapacity { get; }
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs
index 5dd3a6a..34092ad 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs
@@ -106,9 +106,58 @@ namespace Banshee.MediaEngine
             get { return null; }
         }
 
+        public override bool IsMenu {
+            get { return false; }
+        }
+
         public override string GetSubtitleDescription (int index)
         {
             return string.Empty;
         }
+
+        public override void NotifyMouseMove (double x, double y)
+        {
+        }
+
+        public override void NotifyMouseButtonPressed (int button, double x, double y)
+        {
+        }
+
+        public override void NotifyMouseButtonReleased (int button, double x, double y)
+        {
+        }
+
+        public override void NavigateToLeftMenu ()
+        {
+        }
+
+        public override void NavigateToRightMenu ()
+        {
+        }
+
+        public override void NavigateToUpMenu ()
+        {
+        }
+
+        public override void NavigateToDownMenu ()
+        {
+        }
+
+        public override void NavigateToMenu ()
+        {
+        }
+
+        public override void ActivateCurrentMenu ()
+        {
+        }
+
+        public override void GoToNextChapter ()
+        {
+        }
+
+        public override void GoToPreviousChapter ()
+        {
+        }
+
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
index 5365747..b0aa5f6 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
@@ -333,5 +333,25 @@ namespace Banshee.MediaEngine
             set;
             get;
         }
+
+        public abstract bool IsMenu {
+            get;
+        }
+
+        public abstract void NotifyMouseMove (double x, double y);
+        public abstract void NotifyMouseButtonPressed (int button, double x, double y);
+        public abstract void NotifyMouseButtonReleased (int button, double x, double y);
+
+        public abstract void NavigateToLeftMenu ();
+        public abstract void NavigateToRightMenu ();
+        public abstract void NavigateToUpMenu ();
+        public abstract void NavigateToDownMenu ();
+        public abstract void NavigateToMenu ();
+
+        public abstract void ActivateCurrentMenu ();
+
+        public abstract void GoToNextChapter ();
+        public abstract void GoToPreviousChapter ();
+
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
index 8d30711..d36e74c 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
@@ -539,6 +539,61 @@ namespace Banshee.MediaEngine
             return active_engine.GetSubtitleDescription (index);
         }
 
+        public void NotifyMouseMove (double x, double y)
+        {
+            active_engine.NotifyMouseMove (x, y);
+        }
+
+        public void NotifyMouseButtonPressed (int button, double x, double y)
+        {
+            active_engine.NotifyMouseButtonPressed (button, x, y);
+        }
+
+        public void NotifyMouseButtonReleased (int button, double x, double y)
+        {
+            active_engine.NotifyMouseButtonReleased (button, x, y);
+        }
+
+        public void NavigateToLeftMenu ()
+        {
+            active_engine.NavigateToLeftMenu ();
+        }
+
+        public void NavigateToRightMenu ()
+        {
+            active_engine.NavigateToRightMenu ();
+        }
+
+        public void NavigateToUpMenu ()
+        {
+            active_engine.NavigateToUpMenu ();
+        }
+
+        public void NavigateToDownMenu ()
+        {
+            active_engine.NavigateToDownMenu ();
+        }
+
+        public void NavigateToMenu ()
+        {
+            active_engine.NavigateToMenu ();
+        }
+
+        public void ActivateCurrentMenu ()
+        {
+            active_engine.ActivateCurrentMenu ();
+        }
+
+        public void GoToNextChapter ()
+        {
+            active_engine.GoToNextChapter ();
+        }
+
+        public void GoToPreviousChapter ()
+        {
+            active_engine.GoToPreviousChapter ();
+        }
+
         private void CheckPending ()
         {
             if (pending_engine != null && pending_engine != active_engine) {
@@ -644,6 +699,10 @@ namespace Banshee.MediaEngine
             get { return active_engine.SubtitleUri; }
         }
 
+        public bool IsMenu {
+            get { return active_engine.IsMenu; }
+        }
+
         public VideoDisplayContextType VideoDisplayContextType {
             get { return active_engine.VideoDisplayContextType; }
         }
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.addin.xml b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.addin.xml
deleted file mode 100644
index f7351a8..0000000
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.addin.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Addin 
-    id="Banshee.AudioCd"
-    version="1.0"
-    compatVersion="1.0"
-    copyright="© 2008 Novell Inc. Licensed under the MIT X11 license."
-    name="Audio CD Support"
-    category="Core"
-    description="Listen to and rip Audio CDs."
-    author="Aaron Bockover"
-    url="http://banshee.fm/"
-    defaultEnabled="true">
-
-  <Dependencies>
-    <Addin id="Banshee.Services" version="1.0"/>
-  </Dependencies>
-
-  <Extension path="/Banshee/ServiceManager/Service">
-    <Service class="Banshee.AudioCd.AudioCdService"/>
-  </Extension>
-
-</Addin>
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.csproj b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.csproj
deleted file mode 100644
index 1dd0ef7..0000000
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.csproj
+++ /dev/null
@@ -1,129 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.50727</ProductVersion>
-    <ProjectGuid>{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}</ProjectGuid>
-    <OutputType>Library</OutputType>
-    <UseParentDirectoryAsNamespace>true</UseParentDirectoryAsNamespace>
-    <AssemblyName>Banshee.AudioCd</AssemblyName>
-    <SchemaVersion>2.0</SchemaVersion>
-    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
-    <Optimize>true</Optimize>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ReleaseVersion>1.3</ReleaseVersion>
-    <RootNamespace>Banshee.AudioCd</RootNamespace>
-    <AssemblyOriginatorKeyFile>.</AssemblyOriginatorKeyFile>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <WarningLevel>4</WarningLevel>
-    <Optimize>false</Optimize>
-    <OutputPath>..\..\..\bin</OutputPath>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <PlatformTarget>x86</PlatformTarget>
-    <WarningLevel>4</WarningLevel>
-    <Optimize>false</Optimize>
-    <OutputPath>..\..\..\bin</OutputPath>
-  </PropertyGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\..\Core\Banshee.Core\Banshee.Core.csproj">
-      <Project>{2ADB831A-A050-47D0-B6B9-9C19D60233BB}</Project>
-      <Name>Banshee.Core</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Core\Banshee.Services\Banshee.Services.csproj">
-      <Project>{B28354F0-BA87-44E8-989F-B864A3C7C09F}</Project>
-      <Name>Banshee.Services</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Libraries\MusicBrainz\MusicBrainz.csproj">
-      <Project>{C5DD02DE-4F0E-4DC0-ABC7-035A8217CD50}</Project>
-      <Name>MusicBrainz</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Core\Banshee.ThickClient\Banshee.ThickClient.csproj">
-      <Project>{AC839523-7BDF-4AB6-8115-E17921B96EC6}</Project>
-      <Name>Banshee.ThickClient</Name>
-    </ProjectReference>
-    <Reference Include="atk-sharp">
-      <SpecificVersion>False</SpecificVersion>
-      <Package>gtk-sharp-2.0</Package>
-    </Reference>
-    <Reference Include="glib-sharp">
-      <SpecificVersion>False</SpecificVersion>
-      <Package>glib-sharp-2.0</Package>
-    </Reference>
-    <Reference Include="taglib-sharp">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\..\..\bin\taglib-sharp.dll</HintPath>
-      <Package>taglib-sharp</Package>
-    </Reference>
-    <Reference Include="Mono.Addins">
-      <HintPath>..\..\..\bin\Mono.Addins.dll</HintPath>
-      <Package>mono-addins</Package>
-    </Reference>
-    <Reference Include="Mono.Posix">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\..\..\bin\Mono.Posix.dll</HintPath>
-    </Reference>
-    <ProjectReference Include="..\..\Hyena\Hyena.Data.Sqlite\Hyena.Data.Sqlite.csproj">
-      <Project>{95374549-9553-4C1E-9D89-667755F90E13}</Project>
-      <Name>Hyena.Data.Sqlite</Name>
-      <Private>False</Private>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Hyena\Hyena\Hyena.csproj">
-      <Project>{95374549-9553-4C1E-9D89-667755F90E12}</Project>
-      <Name>Hyena</Name>
-    </ProjectReference>
-    <ProjectReference Include="..\..\Hyena\Hyena.Gui\Hyena.Gui.csproj">
-      <Project>{C856EFD8-E812-4E61-8B76-E3583D94C233}</Project>
-      <Name>Hyena.Gui</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="gtk-sharp">
-      <SpecificVersion>False</SpecificVersion>
-      <Package>gtk-sharp-2.0</Package>
-    </Reference>
-  </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Banshee.AudioCd.addin.xml">
-      <LogicalName>Banshee.AudioCd.addin.xml</LogicalName>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Resources\ActiveSourceUI.xml">
-      <LogicalName>ActiveSourceUI.xml</LogicalName>
-    </EmbeddedResource>
-    <EmbeddedResource Include="Resources\GlobalUI.xml">
-      <LogicalName>GlobalUI.xml</LogicalName>
-    </EmbeddedResource>
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="Banshee.AudioCd\AudioCdService.cs" />
-    <Compile Include="Banshee.AudioCd\AudioCdSource.cs" />
-    <Compile Include="Banshee.AudioCd\AudioCdDiscModel.cs" />
-    <Compile Include="Banshee.AudioCd\AudioCdTrackInfo.cs" />
-    <Compile Include="Banshee.AudioCd\AudioCdRipper.cs" />
-    <Compile Include="Banshee.AudioCd\AudioCdDuplicator.cs" />
-  </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <ProjectExtensions>
-    <MonoDevelop>
-      <Properties>
-        <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="true" RelativeMakefileName="./Makefile.am">
-          <BuildFilesVar Sync="true" Name="SOURCES" />
-          <DeployFilesVar />
-          <ResourcesVar Sync="true" Name="RESOURCES" />
-          <OthersVar />
-          <GacRefVar />
-          <AsmRefVar />
-          <ProjectRefVar />
-        </MonoDevelop.Autotools.MakefileInfo>
-      </Properties>
-    </MonoDevelop>
-  </ProjectExtensions>
-</Project>
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDiscModel.cs b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDiscModel.cs
deleted file mode 100644
index 24b6972..0000000
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDiscModel.cs
+++ /dev/null
@@ -1,299 +0,0 @@
-//
-// AudioCdDisc.cs
-//
-// Author:
-//   Aaron Bockover <abockover@novell.com>
-//
-// Copyright (C) 2008 Novell, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Threading;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-
-using Mono.Unix;
-using MusicBrainz;
-
-using Hyena;
-using Banshee.Base;
-using Banshee.Hardware;
-using Banshee.Collection;
-using Banshee.Collection.Database;
-
-namespace Banshee.AudioCd
-{
-    public class AudioCdDiscModel : MemoryTrackListModel
-    {
-        // 44.1 kHz sample rate * 16 bit channel resolution * 2 channels (stereo)
-        private const long PCM_FACTOR = 176400;
-
-        private IDiscVolume volume;
-
-        public event EventHandler MetadataQueryStarted;
-        public event EventHandler MetadataQueryFinished;
-        public event EventHandler EnabledCountChanged;
-
-        private bool metadata_query_success;
-        private DateTime metadata_query_start_time;
-
-        public bool MetadataQuerySuccess {
-            get { return metadata_query_success; }
-        }
-
-        private TimeSpan duration;
-        public TimeSpan Duration {
-            get { return duration; }
-        }
-
-        private long file_size;
-        public long FileSize {
-            get { return file_size; }
-        }
-
-        public AudioCdDiscModel (IDiscVolume volume)
-        {
-            this.volume = volume;
-            disc_title = Catalog.GetString ("Audio CD");
-        }
-
-        public void NotifyUpdated ()
-        {
-            OnReloaded ();
-        }
-
-        public void LoadModelFromDisc ()
-        {
-            Clear ();
-
-            LocalDisc mb_disc = LocalDisc.GetFromDevice (volume.DeviceNode);
-            if (mb_disc == null) {
-                throw new ApplicationException ("Could not read contents of the disc. Platform may not be supported.");
-            }
-
-            TimeSpan[] durations = mb_disc.GetTrackDurations ();
-            for (int i = 0, n = durations.Length; i < n; i++) {
-                AudioCdTrackInfo track = new AudioCdTrackInfo (this, volume.DeviceNode, i);
-                track.TrackNumber = i + 1;
-                track.TrackCount = n;
-                track.DiscNumber = 1;
-                track.Duration = durations[i];
-                track.ArtistName = Catalog.GetString ("Unknown Artist");
-                track.AlbumTitle = Catalog.GetString ("Unknown Album");
-                track.TrackTitle = String.Format (Catalog.GetString ("Track {0}"), track.TrackNumber);
-                track.FileSize = PCM_FACTOR * (uint)track.Duration.TotalSeconds;
-                Add (track);
-
-                duration += track.Duration;
-                file_size += track.FileSize;
-            }
-
-            EnabledCount = Count;
-
-            Reload ();
-
-            ThreadPool.QueueUserWorkItem (LoadDiscMetadata, mb_disc);
-        }
-
-        private void LoadDiscMetadata (object state)
-        {
-            try {
-                LocalDisc mb_disc = (LocalDisc)state;
-
-                OnMetadataQueryStarted (mb_disc);
-
-                Release release = Release.Query (mb_disc).PerfectMatch ();
-
-                var tracks = release.GetTracks ();
-                if (release == null || tracks.Count != Count) {
-                    OnMetadataQueryFinished (false);
-                    return;
-                }
-
-                disc_title = release.GetTitle ();
-
-                int disc_number = 1;
-                int i = 0;
-
-                foreach (Disc disc in release.GetDiscs ()) {
-                    i++;
-                    if (disc.Id == mb_disc.Id) {
-                        disc_number = i;
-                    }
-                }
-
-                DateTime release_date = DateTime.MaxValue;
-
-                foreach (Event release_event in release.GetEvents ()) {
-                    if (release_event.Date != null) {
-                        try {
-                            // Handle "YYYY" dates
-                            var date_str = release_event.Date;
-                            DateTime date = DateTime.Parse (
-                                date_str.Length > 4 ? date_str : date_str + "-01",
-                                ApplicationContext.InternalCultureInfo
-                            );
-
-                            if (date < release_date) {
-                                release_date = date;
-                            }
-                        } catch {
-                        }
-                    }
-                }
-
-                DatabaseArtistInfo artist = new DatabaseArtistInfo ();
-                var mb_artist = release.GetArtist ();
-                artist.Name = mb_artist.GetName ();
-                artist.NameSort = mb_artist.GetSortName ();
-                artist.MusicBrainzId = mb_artist.Id;
-                bool is_compilation = false;
-
-                DatabaseAlbumInfo album = new DatabaseAlbumInfo ();
-                album.Title = disc_title;
-                album.ArtistName = artist.Name;
-                album.MusicBrainzId = release.Id;
-                album.ReleaseDate = release_date == DateTime.MaxValue ? DateTime.MinValue : release_date;
-
-                i = 0;
-                foreach (Track track in tracks) {
-                    AudioCdTrackInfo model_track = (AudioCdTrackInfo)this[i++];
-                    var mb_track_artist = track.GetArtist ();
-
-                    model_track.MusicBrainzId = track.Id;
-                    model_track.TrackTitle = track.GetTitle ();
-                    model_track.ArtistName = mb_track_artist.GetName ();
-                    model_track.AlbumTitle = disc_title;
-                    model_track.DiscNumber = disc_number;
-                    model_track.Album = album;
-
-                    model_track.Artist = new DatabaseArtistInfo ();
-                    model_track.Artist.Name = model_track.ArtistName;
-                    model_track.Artist.NameSort = mb_track_artist.GetSortName ();
-                    model_track.Artist.MusicBrainzId = mb_track_artist.Id;
-
-                    if (release_date != DateTime.MinValue) {
-                        model_track.Year = release_date.Year;
-                    }
-
-                    if (!is_compilation && mb_track_artist.Id != artist.MusicBrainzId) {
-                        is_compilation = true;
-                    }
-                }
-
-                if (is_compilation) {
-                    album.IsCompilation = true;
-                    for (i = 0; i < tracks.Count; i++) {
-                        AudioCdTrackInfo model_track = (AudioCdTrackInfo)this[i];
-                        model_track.IsCompilation = true;
-                        model_track.AlbumArtist = artist.Name;
-                        model_track.AlbumArtistSort = artist.NameSort;
-                    }
-                }
-
-                OnMetadataQueryFinished (true);
-            } catch (Exception ex) {
-                Log.DebugException (ex);
-                OnMetadataQueryFinished (false);
-            }
-        }
-
-        private void OnMetadataQueryStarted (LocalDisc mb_disc)
-        {
-            metadata_query_success = false;
-            metadata_query_start_time = DateTime.Now;
-            Log.InformationFormat ("Querying MusicBrainz for Disc Release ({0})", mb_disc.Id);
-
-            ThreadAssist.ProxyToMain (delegate {
-                EventHandler handler = MetadataQueryStarted;
-                if (handler != null) {
-                    handler (this, EventArgs.Empty);
-                }
-            });
-        }
-
-        private void OnMetadataQueryFinished (bool success)
-        {
-            metadata_query_success = success;
-            Log.InformationFormat ("Query finished (success: {0}, {1} seconds)",
-                success, (DateTime.Now - metadata_query_start_time).TotalSeconds);
-
-            ThreadAssist.ProxyToMain (delegate {
-                Reload ();
-
-                EventHandler handler = MetadataQueryFinished;
-                if (handler != null) {
-                    handler (this, EventArgs.Empty);
-                }
-            });
-        }
-
-        private void OnEnabledCountChanged ()
-        {
-            EventHandler handler = EnabledCountChanged;
-            if (handler != null) {
-                handler (this, EventArgs.Empty);
-            }
-        }
-
-        private ICdromDevice Drive {
-            get { return Volume == null ? null : (Volume.Parent as ICdromDevice); }
-        }
-
-        public bool LockDoor ()
-        {
-            ICdromDevice drive = Drive;
-            return drive != null ? drive.LockDoor () : false;
-        }
-
-        public bool UnlockDoor ()
-        {
-            ICdromDevice drive = Drive;
-            return drive != null ? drive.UnlockDoor () : false;
-        }
-
-        public bool IsDoorLocked {
-            get {
-                ICdromDevice drive = Drive;
-                return drive != null ? drive.IsDoorLocked : false;
-            }
-        }
-
-        public IDiscVolume Volume {
-            get { return volume; }
-        }
-
-        private string disc_title;
-        public string Title {
-            get { return disc_title; }
-        }
-
-        private int enabled_count;
-        public int EnabledCount {
-            get { return enabled_count; }
-            internal set {
-                enabled_count = value;
-                OnEnabledCountChanged ();
-            }
-        }
-    }
-}
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDuplicator.cs b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDuplicator.cs
deleted file mode 100644
index 6cc8dd9..0000000
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDuplicator.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-// AudioCdDuplicator.cs
-//
-// Author:
-//   Aaron Bockover <abockover@novell.com>
-//
-// Copyright (C) 2008 Novell, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using Mono.Addins;
-
-using Banshee.Hardware;
-
-namespace Banshee.AudioCd
-{
-    public static class AudioCdDuplicator
-    {
-        private static bool duplicator_extension_queried = false;
-        private static TypeExtensionNode duplicator_extension_node = null;
-
-        public static bool Supported {
-            get {
-                if (duplicator_extension_queried) {
-                    return duplicator_extension_node != null;
-                }
-
-                duplicator_extension_queried = true;
-
-                foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes (
-                    "/Banshee/Platform/DiscDuplicator")) {
-                    duplicator_extension_node = node;
-                    break;
-                }
-
-                return duplicator_extension_node != null;
-            }
-        }
-
-        public static void Duplicate (AudioCdDiscModel model)
-        {
-            if (Supported && model != null && model.Volume != null) {
-                ((IDiscDuplicator)duplicator_extension_node.CreateInstance ()).Duplicate (model.Volume);
-            }
-        }
-    }
-}
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
deleted file mode 100644
index 0f748c1..0000000
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
+++ /dev/null
@@ -1,288 +0,0 @@
-//
-// AudioCdRipper.cs
-//
-// Author:
-//   Aaron Bockover <abockover@novell.com>
-//
-// Copyright (C) 2008 Novell, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-
-using Mono.Unix;
-using Mono.Addins;
-
-using Hyena;
-using Hyena.Jobs;
-
-using Banshee.Library;
-using Banshee.ServiceStack;
-using Banshee.Streaming;
-using Banshee.Collection;
-using Banshee.Collection.Database;
-using Banshee.MediaEngine;
-
-namespace Banshee.AudioCd
-{
-    public class AudioCdRipper : IDisposable
-    {
-        private static bool ripper_extension_queried = false;
-        private static TypeExtensionNode ripper_extension_node = null;
-
-        public static bool Supported {
-            get {
-                if (ripper_extension_queried) {
-                    return ripper_extension_node != null;
-                }
-
-                ripper_extension_queried = true;
-
-                foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes (
-                    "/Banshee/MediaEngine/AudioCdRipper")) {
-                    ripper_extension_node = node;
-                    break;
-                }
-
-                return ripper_extension_node != null;
-            }
-        }
-
-        public event EventHandler Finished;
-
-        // State that does real work
-        private IAudioCdRipper ripper;
-        private AudioCdSource source;
-        private UserJob user_job;
-
-        // State to process the rip operation
-        private Queue<AudioCdTrackInfo> queue = new Queue<AudioCdTrackInfo> ();
-
-        private TimeSpan ripped_duration;
-        private TimeSpan total_duration;
-        private int track_index;
-
-        // State to compute/display the rip speed (i.e. 24x)
-        private TimeSpan last_speed_poll_duration;
-        private DateTime last_speed_poll_time;
-        private double last_speed_poll_factor;
-        private string status;
-
-        public AudioCdRipper (AudioCdSource source)
-        {
-            if (ripper_extension_node != null) {
-                ripper = (IAudioCdRipper)ripper_extension_node.CreateInstance ();
-                ripper.TrackFinished += OnTrackFinished;
-                ripper.Progress += OnProgress;
-                ripper.Error += OnError;
-            } else {
-                throw new ApplicationException ("No AudioCdRipper extension is installed");
-            }
-
-            this.source = source;
-        }
-
-        public void Start ()
-        {
-            ResetState ();
-
-            foreach (AudioCdTrackInfo track in source.DiscModel) {
-                if (track.RipEnabled) {
-                    total_duration += track.Duration;
-                    queue.Enqueue (track);
-                }
-            }
-
-            if (queue.Count == 0) {
-                return;
-            }
-
-            source.LockAllTracks ();
-
-            user_job = new UserJob (Catalog.GetString ("Importing Audio CD"),
-                Catalog.GetString ("Initializing Drive"), "media-import-audio-cd");
-            user_job.CancelMessage = String.Format (Catalog.GetString (
-                "<i>{0}</i> is still being imported into the music library. Would you like to stop it?"
-                ), GLib.Markup.EscapeText (source.DiscModel.Title));
-            user_job.SetResources (Resource.Cpu);
-            user_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
-            user_job.CanCancel = true;
-            user_job.CancelRequested += OnCancelRequested;
-            user_job.Finished += OnFinished;
-            user_job.Register ();
-
-            if (source != null && source.DiscModel != null) {
-                if (!source.DiscModel.LockDoor ()) {
-                    Hyena.Log.Warning ("Could not lock CD-ROM door", false);
-                }
-            }
-
-            ripper.Begin (source.DiscModel.Volume.DeviceNode, AudioCdService.ErrorCorrection.Get ());
-
-            RipNextTrack ();
-        }
-
-        public void Dispose ()
-        {
-            ResetState ();
-
-            if (source != null && source.DiscModel != null) {
-                source.DiscModel.UnlockDoor ();
-            }
-
-            if (ripper != null) {
-                ripper.Finish ();
-                ripper = null;
-            }
-
-            if (user_job != null) {
-                user_job.Finish ();
-                user_job = null;
-            }
-        }
-
-        private void ResetState ()
-        {
-            track_index = 0;
-            ripped_duration = TimeSpan.Zero;
-            total_duration = TimeSpan.Zero;
-            last_speed_poll_duration = TimeSpan.Zero;
-            last_speed_poll_time = DateTime.MinValue;
-            last_speed_poll_factor = 0;
-            status = null;
-            queue.Clear ();
-        }
-
-        private void RipNextTrack ()
-        {
-            if (queue.Count == 0) {
-                OnFinished ();
-                Dispose ();
-                return;
-            }
-
-            AudioCdTrackInfo track = queue.Dequeue ();
-
-            user_job.Title = String.Format (Catalog.GetString ("Importing {0} of {1}"),
-                ++track_index, source.DiscModel.EnabledCount);
-            status = String.Format("{0} - {1}", track.ArtistName, track.TrackTitle);
-            user_job.Status = status;
-
-            SafeUri uri = new SafeUri (MusicLibrarySource.MusicFileNamePattern.BuildFull (
-                ServiceManager.SourceManager.MusicLibrary.BaseDirectory, track, null));
-            bool tagging_supported;
-            ripper.RipTrack (track.IndexOnDisc, track, uri, out tagging_supported);
-        }
-
-#region Ripper Event Handlers
-
-        private void OnTrackFinished (object o, AudioCdRipperTrackFinishedArgs args)
-        {
-            if (user_job == null || ripper == null) {
-                return;
-            }
-
-            AudioCdTrackInfo track = (AudioCdTrackInfo)args.Track;
-
-            ripped_duration += track.Duration;
-            track.PrimarySource = ServiceManager.SourceManager.MusicLibrary;
-            track.Uri = args.Uri;
-
-            track.FileSize = Banshee.IO.File.GetSize (track.Uri);
-            track.FileModifiedStamp = Banshee.IO.File.GetModifiedTime (track.Uri);
-            track.LastSyncedStamp = DateTime.Now;
-
-            using (var file = StreamTagger.ProcessUri (track.Uri)) {
-                StreamTagger.TrackInfoMerge (track, file, true);
-            }
-
-            track.Save ();
-
-            source.UnlockTrack (track);
-            RipNextTrack ();
-        }
-
-        private void OnProgress (object o, AudioCdRipperProgressArgs args)
-        {
-            if (user_job == null) {
-                return;
-            }
-
-            TimeSpan total_ripped_duration = ripped_duration + args.EncodedTime;
-            user_job.Progress = total_ripped_duration.TotalMilliseconds / total_duration.TotalMilliseconds;
-
-            TimeSpan poll_diff = DateTime.Now - last_speed_poll_time;
-            double factor = 0;
-
-            if (poll_diff.TotalMilliseconds >= 1000) {
-                factor = ((total_ripped_duration - last_speed_poll_duration).TotalMilliseconds
-                    * (poll_diff.TotalMilliseconds / 1000.0)) / 1000.0;
-
-                last_speed_poll_duration = total_ripped_duration;
-                last_speed_poll_time = DateTime.Now;
-                last_speed_poll_factor = factor > 1 ? factor : 0;
-            }
-
-            // Make sure the speed factor is between 1 and 200 to allow it to ramp and settle
-            user_job.Status = last_speed_poll_factor > 1 && last_speed_poll_factor <= 200
-                ? String.Format ("{0} ({1:0.0}x)", status, last_speed_poll_factor)
-                : status;
-        }
-
-        private void OnError (object o, AudioCdRipperErrorArgs args)
-        {
-            Dispose ();
-            Hyena.Log.Error (Catalog.GetString ("Cannot Import CD"), args.Message, true);
-        }
-
-#endregion
-
-        private void OnFinished ()
-        {
-            EventHandler handler = Finished;
-            if (handler != null) {
-                handler (this, EventArgs.Empty);
-            }
-        }
-
-#region User Job Event Handlers
-
-        private void OnCancelRequested (object o, EventArgs args)
-        {
-            Dispose ();
-        }
-
-        private void OnFinished (object o, EventArgs args)
-        {
-            if (user_job != null) {
-                user_job.CancelRequested -= OnCancelRequested;
-                user_job.Finished -= OnFinished;
-                user_job = null;
-            }
-
-            source.UnlockAllTracks ();
-        }
-
-#endregion
-
-    }
-}
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdService.cs b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdService.cs
deleted file mode 100644
index d8eb5cc..0000000
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdService.cs
+++ /dev/null
@@ -1,380 +0,0 @@
-//
-// AudioCdService.cs
-//
-// Author:
-//   Aaron Bockover <abockover@novell.com>
-//
-// Copyright (C) 2008 Novell, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-using Mono.Unix;
-
-using Hyena;
-
-using Banshee.ServiceStack;
-using Banshee.Configuration;
-using Banshee.Preferences;
-using Banshee.Hardware;
-using Banshee.Gui;
-
-namespace Banshee.AudioCd
-{
-    public class AudioCdService : IExtensionService, IDisposable
-    {
-        private Dictionary<string, AudioCdSource> sources;
-        private List<DeviceCommand> unhandled_device_commands;
-        private SourcePage pref_page;
-        private Section pref_section;
-        private uint global_interface_id;
-
-        public AudioCdService ()
-        {
-        }
-
-        public void Initialize ()
-        {
-            lock (this) {
-                InstallPreferences ();
-
-                sources = new Dictionary<string, AudioCdSource> ();
-
-                foreach (ICdromDevice device in ServiceManager.HardwareManager.GetAllCdromDevices ()) {
-                    MapCdromDevice (device);
-                }
-
-                ServiceManager.HardwareManager.DeviceAdded += OnHardwareDeviceAdded;
-                ServiceManager.HardwareManager.DeviceRemoved += OnHardwareDeviceRemoved;
-                ServiceManager.HardwareManager.DeviceCommand += OnDeviceCommand;
-
-                SetupActions ();
-            }
-        }
-
-        public void Dispose ()
-        {
-            lock (this) {
-                UninstallPreferences ();
-
-                ServiceManager.HardwareManager.DeviceAdded -= OnHardwareDeviceAdded;
-                ServiceManager.HardwareManager.DeviceRemoved -= OnHardwareDeviceRemoved;
-                ServiceManager.HardwareManager.DeviceCommand -= OnDeviceCommand;
-
-                foreach (AudioCdSource source in sources.Values) {
-                    source.Dispose ();
-                    ServiceManager.SourceManager.RemoveSource (source);
-                }
-
-                sources.Clear ();
-                sources = null;
-
-                DisposeActions ();
-            }
-        }
-
-        private void MapCdromDevice (ICdromDevice device)
-        {
-            lock (this) {
-                foreach (IVolume volume in device) {
-                    if (volume is IDiscVolume) {
-                        MapDiscVolume ((IDiscVolume)volume);
-                    }
-                }
-            }
-        }
-
-        private void MapDiscVolume (IDiscVolume volume)
-        {
-            lock (this) {
-                if (!sources.ContainsKey (volume.Uuid) && volume.HasAudio) {
-                    AudioCdSource source = new AudioCdSource (this, new AudioCdDiscModel (volume));
-                    sources.Add (volume.Uuid, source);
-                    ServiceManager.SourceManager.AddSource (source);
-
-                    // If there are any queued device commands, see if they are to be
-                    // handled by this new volume (e.g. --device-activate-play=cdda://sr0/)
-                    try {
-                        if (unhandled_device_commands != null) {
-                            foreach (DeviceCommand command in unhandled_device_commands) {
-                                if (DeviceCommandMatchesSource (source, command)) {
-                                    HandleDeviceCommand (source, command.Action);
-                                    unhandled_device_commands.Remove (command);
-                                    if (unhandled_device_commands.Count == 0) {
-                                        unhandled_device_commands = null;
-                                    }
-                                    break;
-                                }
-                            }
-                        }
-                    } catch (Exception e) {
-                        Log.Exception (e);
-                    }
-
-                    Log.DebugFormat ("Mapping audio CD ({0})", volume.Uuid);
-                }
-            }
-        }
-
-        internal void UnmapDiscVolume (string uuid)
-        {
-            lock (this) {
-                if (sources.ContainsKey (uuid)) {
-                    AudioCdSource source = sources[uuid];
-                    source.StopPlayingDisc ();
-                    ServiceManager.SourceManager.RemoveSource (source);
-                    sources.Remove (uuid);
-                    Log.DebugFormat ("Unmapping audio CD ({0})", uuid);
-                }
-            }
-        }
-
-        private void OnHardwareDeviceAdded (object o, DeviceAddedArgs args)
-        {
-            lock (this) {
-                if (args.Device is ICdromDevice) {
-                    MapCdromDevice ((ICdromDevice)args.Device);
-                } else if (args.Device is IDiscVolume) {
-                    MapDiscVolume ((IDiscVolume)args.Device);
-                }
-            }
-        }
-
-        private void OnHardwareDeviceRemoved (object o, DeviceRemovedArgs args)
-        {
-            lock (this) {
-                UnmapDiscVolume (args.DeviceUuid);
-            }
-        }
-
-#region DeviceCommand Handling
-
-        private bool DeviceCommandMatchesSource (AudioCdSource source, DeviceCommand command)
-        {
-            if (command.DeviceId.StartsWith ("cdda:")) {
-                try {
-                    Uri uri = new Uri (command.DeviceId);
-                    string match_device_node = String.Format ("{0}{1}", uri.Host,
-                        uri.AbsolutePath).TrimEnd ('/', '\\');
-                    string device_node = source.DiscModel.Volume.DeviceNode;
-                    return device_node.EndsWith (match_device_node);
-                } catch {
-                }
-            }
-
-            return false;
-        }
-
-        private void HandleDeviceCommand (AudioCdSource source, DeviceCommandAction action)
-        {
-            if ((action & DeviceCommandAction.Activate) != 0) {
-                ServiceManager.SourceManager.SetActiveSource (source);
-            }
-
-            if ((action & DeviceCommandAction.Play) != 0) {
-                ServiceManager.PlaybackController.NextSource = source;
-                if (!ServiceManager.PlayerEngine.IsPlaying ()) {
-                    ServiceManager.PlaybackController.Next ();
-                }
-            }
-        }
-
-        private void OnDeviceCommand (object o, DeviceCommand command)
-        {
-            lock (this) {
-                // Check to see if we have an already mapped disc volume that should
-                // handle this incoming command; if not, queue it for later discs
-                foreach (AudioCdSource source in sources.Values) {
-                    if (DeviceCommandMatchesSource (source, command)) {
-                        HandleDeviceCommand (source, command.Action);
-                        return;
-                    }
-                }
-
-                if (unhandled_device_commands == null) {
-                    unhandled_device_commands = new List<DeviceCommand> ();
-                }
-                unhandled_device_commands.Add (command);
-            }
-        }
-
-#endregion
-
-#region Preferences
-
-        private void InstallPreferences ()
-        {
-            PreferenceService service = ServiceManager.Get<PreferenceService> ();
-            if (service == null) {
-                return;
-            }
-
-            service.InstallWidgetAdapters += OnPreferencesServiceInstallWidgetAdapters;
-
-            pref_page = new Banshee.Preferences.SourcePage ("audio-cd", Catalog.GetString ("Audio CDs"), "media-cdrom", 400);
-
-            pref_section = pref_page.Add (new Section ("audio-cd", Catalog.GetString ("Audio CD Importing"), 20));
-            pref_section.ShowLabel = false;
-
-            pref_section.Add (new VoidPreference ("import-profile",  Catalog.GetString ("_Import format")));
-            pref_section.Add (new VoidPreference ("import-profile-desc"));
-
-            pref_section.Add (new SchemaPreference<bool> (AutoRip,
-                Catalog.GetString ("_Automatically import audio CDs when inserted"),
-                Catalog.GetString ("When an audio CD is inserted, automatically begin importing it " +
-                    "if metadata can be found and it is not already in the library.")));
-
-            pref_section.Add (new SchemaPreference<bool> (EjectAfterRipped,
-                Catalog.GetString ("_Eject when done importing"),
-                Catalog.GetString ("When an audio CD has been imported, automatically eject it.")));
-
-            pref_section.Add (new SchemaPreference<bool> (ErrorCorrection,
-                Catalog.GetString ("Use error correction when importing"),
-                Catalog.GetString ("Error correction tries to work around problem areas on a disc, such " +
-                    "as surface scratches, but will slow down importing substantially.")));
-        }
-
-        private void UninstallPreferences ()
-        {
-            PreferenceService service = ServiceManager.Get<PreferenceService> ();
-            if (service == null || pref_page == null) {
-                return;
-            }
-
-            service.InstallWidgetAdapters -= OnPreferencesServiceInstallWidgetAdapters;
-
-            pref_page.Dispose ();
-            pref_page = null;
-            pref_section = null;
-        }
-
-        private void OnPreferencesServiceInstallWidgetAdapters (object o, EventArgs args)
-        {
-            if (pref_section == null) {
-                return;
-            }
-
-            Gtk.HBox description_box = new Gtk.HBox ();
-            Banshee.MediaProfiles.Gui.ProfileComboBoxConfigurable chooser
-                = new Banshee.MediaProfiles.Gui.ProfileComboBoxConfigurable (ServiceManager.MediaProfileManager,
-                    "cd-importing", description_box);
-
-            pref_section["import-profile"].DisplayWidget = chooser;
-            pref_section["import-profile"].MnemonicWidget = chooser.Combo;
-            pref_section["import-profile-desc"].DisplayWidget = description_box;
-        }
-
-        public static readonly SchemaEntry<bool> ErrorCorrection = new SchemaEntry<bool> (
-            "import", "audio_cd_error_correction",
-            false,
-            "Enable error correction",
-            "When importing an audio CD, enable error correction (paranoia mode)"
-        );
-
-        public static readonly SchemaEntry<bool> AutoRip = new SchemaEntry<bool> (
-            "import", "auto_rip_cds",
-            false,
-            "Enable audio CD auto ripping",
-            "When an audio CD is inserted, automatically begin ripping it."
-        );
-
-        public static readonly SchemaEntry<bool> EjectAfterRipped = new SchemaEntry<bool> (
-            "import", "eject_after_ripped",
-            false,
-            "Eject audio CD after ripped",
-            "After an audio CD has been ripped, automatically eject it."
-        );
-
-#endregion
-
-#region UI Actions
-
-        private void SetupActions ()
-        {
-            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
-            if (uia_service == null) {
-                return;
-            }
-
-            uia_service.GlobalActions.AddImportant (new Gtk.ActionEntry [] {
-                new Gtk.ActionEntry ("RipDiscAction", null,
-                    Catalog.GetString ("Import CD"), null,
-                    Catalog.GetString ("Import this audio CD to the library"),
-                    OnImportDisc)
-            });
-
-            uia_service.GlobalActions.AddImportant (
-                new Gtk.ActionEntry ("DuplicateDiscAction", null,
-                    Catalog.GetString ("Duplicate CD"), null,
-                    Catalog.GetString ("Duplicate this audio CD"),
-                    OnDuplicateDisc)
-            );
-
-            global_interface_id = uia_service.UIManager.AddUiFromResource ("GlobalUI.xml");
-        }
-
-        private void DisposeActions ()
-        {
-            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
-            if (uia_service == null) {
-                return;
-            }
-
-            uia_service.GlobalActions.Remove ("RipDiscAction");
-            uia_service.GlobalActions.Remove ("DuplicateDiscAction");
-            uia_service.UIManager.RemoveUi (global_interface_id);
-        }
-
-        private void OnImportDisc (object o, EventArgs args)
-        {
-            ImportOrDuplicateDisc (true);
-        }
-
-        private void OnDuplicateDisc (object o, EventArgs args)
-        {
-            ImportOrDuplicateDisc (false);
-        }
-
-        private void ImportOrDuplicateDisc (bool import)
-        {
-            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
-            if (uia_service == null) {
-                return;
-            }
-
-            AudioCdSource source = uia_service.SourceActions.ActionSource as AudioCdSource;
-            if (source != null) {
-                if (import) {
-                    source.ImportDisc ();
-                } else {
-                    source.DuplicateDisc ();
-                }
-            }
-        }
-
-#endregion
-
-        string IService.ServiceName {
-            get { return "AudioCdService"; }
-        }
-    }
-}
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs
deleted file mode 100644
index 5b99ba8..0000000
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs
+++ /dev/null
@@ -1,490 +0,0 @@
-//
-// AudioCdSource.cs
-//
-// Author:
-//   Aaron Bockover <abockover@novell.com>
-//
-// Copyright (C) 2008 Novell, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using Mono.Unix;
-
-using Hyena;
-using Banshee.Base;
-using Banshee.ServiceStack;
-using Banshee.Sources;
-using Banshee.Library;
-using Banshee.Collection;
-using Banshee.Collection.Database;
-
-using Gtk;
-using Banshee.Gui;
-using Selection = Hyena.Collections.Selection;
-
-namespace Banshee.AudioCd
-{
-    public class AudioCdSource : Source, ITrackModelSource, IUnmapableSource,
-        IImportSource, IDurationAggregator, IFileSizeAggregator, IDisposable
-    {
-        private AudioCdService service;
-        private AudioCdDiscModel disc_model;
-        private SourceMessage query_message;
-
-        public AudioCdSource (AudioCdService service, AudioCdDiscModel discModel)
-            : base (Catalog.GetString ("Audio CD"), discModel.Title, 400)
-        {
-            this.service = service;
-            this.disc_model = discModel;
-
-            TypeUniqueId = "";
-
-            Properties.SetString ("TrackView.ColumnControllerXml", String.Format (@"
-                <column-controller>
-                  <column>
-                    <renderer type=""Hyena.Data.Gui.ColumnCellCheckBox"" property=""RipEnabled""/>
-                  </column>
-                  <add-all-defaults />
-                </column-controller>
-            "));
-
-            disc_model.MetadataQueryStarted += OnMetadataQueryStarted;
-            disc_model.MetadataQueryFinished += OnMetadataQueryFinished;
-            disc_model.EnabledCountChanged += OnEnabledCountChanged;
-            disc_model.LoadModelFromDisc ();
-
-            SetupGui ();
-        }
-
-        public TimeSpan Duration {
-            get { return disc_model.Duration; }
-        }
-
-        public long FileSize {
-            get { return disc_model.FileSize; }
-        }
-
-        public bool DiscIsPlaying {
-            get {
-                AudioCdTrackInfo playing_track = ServiceManager.PlayerEngine.CurrentTrack as AudioCdTrackInfo;
-                return playing_track != null && playing_track.Model == disc_model;
-            }
-        }
-
-        public void StopPlayingDisc ()
-        {
-            if (DiscIsPlaying) {
-                ServiceManager.PlayerEngine.Close (true);
-            }
-        }
-
-        public void Dispose ()
-        {
-            ClearMessages ();
-            disc_model.MetadataQueryStarted -= OnMetadataQueryStarted;
-            disc_model.MetadataQueryFinished -= OnMetadataQueryFinished;
-            disc_model.EnabledCountChanged -= OnEnabledCountChanged;
-            service = null;
-            disc_model = null;
-        }
-
-        public AudioCdDiscModel DiscModel {
-            get { return disc_model; }
-        }
-
-        private void OnEnabledCountChanged (object o, EventArgs args)
-        {
-            UpdateActions ();
-        }
-
-        private void OnMetadataQueryStarted (object o, EventArgs args)
-        {
-            if (query_message != null) {
-                DestroyQueryMessage ();
-            }
-
-            query_message = new SourceMessage (this);
-            query_message.FreezeNotify ();
-            query_message.CanClose = false;
-            query_message.IsSpinning = true;
-            query_message.Text = Catalog.GetString ("Searching for track information...");
-            query_message.ThawNotify ();
-
-            PushMessage (query_message);
-        }
-
-        private void OnMetadataQueryFinished (object o, EventArgs args)
-        {
-            if (disc_model.Title != Name) {
-                Name = disc_model.Title;
-                OnUpdated ();
-            }
-
-            if (disc_model.MetadataQuerySuccess) {
-                DestroyQueryMessage ();
-                if (DiscIsPlaying) {
-                    ServiceManager.PlayerEngine.TrackInfoUpdated ();
-                }
-
-                if (AudioCdService.AutoRip.Get ()) {
-                    BeginAutoRip ();
-                }
-
-                return;
-            }
-
-            if (query_message == null) {
-                return;
-            }
-
-            query_message.FreezeNotify ();
-            query_message.IsSpinning = false;
-            query_message.SetIconName ("dialog-error");
-            query_message.Text = Catalog.GetString ("Could not fetch track information");
-            query_message.CanClose = true;
-            query_message.ThawNotify ();
-        }
-
-        private void DestroyQueryMessage ()
-        {
-            if (query_message != null) {
-                RemoveMessage (query_message);
-                query_message = null;
-            }
-        }
-
-        private void BeginAutoRip ()
-        {
-            // Make sure the album isn't already in the Library
-            TrackInfo track = disc_model[0];
-            int count = ServiceManager.DbConnection.Query<int> (
-                @"SELECT Count(*) FROM CoreTracks, CoreArtists, CoreAlbums WHERE
-                    CoreTracks.PrimarySourceID = ? AND
-                    CoreTracks.ArtistID = CoreArtists.ArtistID AND
-                    CoreTracks.AlbumID = CoreAlbums.AlbumID AND
-                    CoreArtists.Name = ? AND CoreAlbums.Title = ? AND (CoreTracks.Disc = ? OR CoreTracks.Disc = 0)",
-                    ServiceManager.SourceManager.MusicLibrary.DbId,
-                    track.ArtistName, track.AlbumTitle, track.DiscNumber
-            );
-
-            if (count > 0) {
-                SetStatus (Catalog.GetString ("Automatic import off since this album is already in the Music Library."), true, false, null);
-                return;
-            }
-
-            Log.DebugFormat ("Beginning auto rip of {0}", Name);
-            ImportDisc ();
-        }
-
-        internal void ImportDisc ()
-        {
-            AudioCdRipper ripper = null;
-
-            try {
-                if (AudioCdRipper.Supported) {
-                    ripper = new AudioCdRipper (this);
-                    ripper.Finished += OnRipperFinished;
-                    ripper.Start ();
-                }
-            } catch (Exception e) {
-                if (ripper != null) {
-                    ripper.Dispose ();
-                }
-
-                Log.Error (Catalog.GetString ("Could not import CD"), e.Message, true);
-                Log.Exception (e);
-            }
-        }
-
-        private void OnRipperFinished (object o, EventArgs args)
-        {
-            if (AudioCdService.EjectAfterRipped.Get ()) {
-                Unmap ();
-            }
-        }
-
-        internal void DuplicateDisc ()
-        {
-            try {
-                AudioCdDuplicator.Duplicate (disc_model);
-            } catch (Exception e) {
-                Hyena.Log.Error (Catalog.GetString ("Could not duplicate audio CD"), e.Message, true);
-                Hyena.Log.Exception (e);
-            }
-        }
-
-        internal void LockAllTracks ()
-        {
-            StopPlayingDisc ();
-
-            foreach (AudioCdTrackInfo track in disc_model) {
-                track.CanPlay = false;
-            }
-
-            disc_model.NotifyUpdated ();
-        }
-
-        internal void UnlockAllTracks ()
-        {
-            foreach (AudioCdTrackInfo track in disc_model) {
-                track.CanPlay = true;
-            }
-
-            disc_model.NotifyUpdated ();
-        }
-
-        internal void UnlockTrack (AudioCdTrackInfo track)
-        {
-            track.CanPlay = true;
-            disc_model.NotifyUpdated ();
-        }
-
-#region Source Overrides
-
-        public override int Count {
-            get { return disc_model.Count; }
-        }
-
-        public override string PreferencesPageId {
-            get { return "audio-cd"; }
-        }
-
-        public override bool HasEditableTrackProperties {
-            get { return true; }
-        }
-
-        public override bool HasViewableTrackProperties {
-            get { return true; }
-        }
-
-#endregion
-
-#region ITrackModelSource Implementation
-
-        public TrackListModel TrackModel {
-            get { return disc_model; }
-        }
-
-        public AlbumListModel AlbumModel {
-            get { return null; }
-        }
-
-        public ArtistListModel ArtistModel {
-            get { return null; }
-        }
-
-        public void Reload ()
-        {
-            disc_model.Reload ();
-        }
-
-        public void RemoveTracks (Selection selection)
-        {
-        }
-
-        public void DeleteTracks (Selection selection)
-        {
-        }
-
-        public bool CanAddTracks {
-            get { return false; }
-        }
-
-        public bool CanRemoveTracks {
-            get { return false; }
-        }
-
-        public bool CanDeleteTracks {
-            get { return false; }
-        }
-
-        public bool ConfirmRemoveTracks {
-            get { return false; }
-        }
-
-        public virtual bool CanRepeat {
-            get { return true; }
-        }
-
-        public virtual bool CanShuffle {
-            get { return true; }
-        }
-
-        public bool ShowBrowser {
-            get { return false; }
-        }
-
-        public bool HasDependencies {
-            get { return false; }
-        }
-
-        public bool Indexable {
-            get { return false; }
-        }
-
-#endregion
-
-#region IUnmapableSource Implementation
-
-        public bool Unmap ()
-        {
-            StopPlayingDisc ();
-
-            foreach (TrackInfo track in disc_model) {
-                track.CanPlay = false;
-            }
-
-            OnUpdated ();
-
-            SourceMessage eject_message = new SourceMessage (this);
-            eject_message.FreezeNotify ();
-            eject_message.IsSpinning = true;
-            eject_message.CanClose = false;
-            eject_message.Text = Catalog.GetString ("Ejecting audio CD...");
-            eject_message.ThawNotify ();
-            PushMessage (eject_message);
-
-            ThreadPool.QueueUserWorkItem (delegate {
-                try {
-                    disc_model.Volume.Unmount ();
-                    disc_model.Volume.Eject ();
-
-                    ThreadAssist.ProxyToMain (delegate {
-                        service.UnmapDiscVolume (disc_model.Volume.Uuid);
-                        Dispose ();
-                    });
-                } catch (Exception e) {
-                    ThreadAssist.ProxyToMain (delegate {
-                        ClearMessages ();
-                        eject_message.IsSpinning = false;
-                        eject_message.SetIconName ("dialog-error");
-                        eject_message.Text = String.Format (Catalog.GetString ("Could not eject audio CD: {0}"), e.Message);
-                        PushMessage (eject_message);
-
-                        foreach (TrackInfo track in disc_model) {
-                            track.CanPlay = true;
-                        }
-                        OnUpdated ();
-                    });
-
-                    Log.Exception (e);
-                }
-            });
-
-            return true;
-        }
-
-        public bool CanUnmap {
-            get { return DiscModel != null ? !DiscModel.IsDoorLocked : true; }
-        }
-
-        public bool ConfirmBeforeUnmap {
-            get { return false; }
-        }
-
-#endregion
-
-#region GUI/ThickClient
-
-        private bool actions_loaded = false;
-
-        private void SetupGui ()
-        {
-            Properties.SetStringList ("Icon.Name", "media-cdrom", "gnome-dev-cdrom-audio", "source-cd-audio");
-            Properties.SetString ("SourcePreferencesActionLabel", Catalog.GetString ("Audio CD Preferences"));
-            Properties.SetString ("UnmapSourceActionLabel", Catalog.GetString ("Eject Disc"));
-            Properties.SetString ("UnmapSourceActionIconName", "media-eject");
-            Properties.SetString ("ActiveSourceUIResource", "ActiveSourceUI.xml");
-            Properties.SetString ("GtkActionPath", "/AudioCdContextMenu");
-
-            actions_loaded = true;
-
-            UpdateActions ();
-        }
-
-        private void UpdateActions ()
-        {
-            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
-            if (uia_service == null) {
-                return;
-            }
-
-            Gtk.Action rip_action = uia_service.GlobalActions["RipDiscAction"];
-            if (rip_action != null) {
-                string title = disc_model.Title;
-                int max_title_length = 20;
-                title = title.Length > max_title_length
-                    ? String.Format ("{0}\u2026", title.Substring (0, max_title_length).Trim ())
-                    : title;
-                rip_action.Label = String.Format (Catalog.GetString ("Import \u201f{0}\u201d"), title);
-                rip_action.ShortLabel = Catalog.GetString ("Import CD");
-                rip_action.IconName = "media-import-audio-cd";
-                rip_action.Sensitive = AudioCdRipper.Supported && disc_model.EnabledCount > 0;
-            }
-
-            Gtk.Action duplicate_action = uia_service.GlobalActions["DuplicateDiscAction"];
-            if (duplicate_action != null) {
-                duplicate_action.IconName = "media-cdrom";
-                duplicate_action.Visible = AudioCdDuplicator.Supported;
-            }
-        }
-
-        protected override void OnUpdated ()
-        {
-            if (actions_loaded) {
-                UpdateActions ();
-            }
-
-            base.OnUpdated ();
-        }
-
-#endregion
-
-#region IImportSource
-
-        void IImportSource.Import ()
-        {
-            ImportDisc ();
-        }
-
-        string [] IImportSource.IconNames {
-            get { return Properties.GetStringList ("Icon.Name"); }
-        }
-
-        bool IImportSource.CanImport {
-            get { return true; }
-        }
-
-        int IImportSource.SortOrder {
-            get { return -10; }
-        }
-
-        string IImportSource.ImportLabel {
-            get { return null; }
-        }
-
-#endregion
-
-    }
-}
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdTrackInfo.cs b/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdTrackInfo.cs
deleted file mode 100644
index 68e6a26..0000000
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdTrackInfo.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-//
-// AudioCdTrackInfo.cs
-//
-// Author:
-//   Aaron Bockover <abockover@novell.com>
-//
-// Copyright (C) 2008 Novell, Inc.
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-
-using Hyena;
-
-using Banshee.Collection;
-using Banshee.Collection.Database;
-
-namespace Banshee.AudioCd
-{
-    public class AudioCdTrackInfo : DatabaseTrackInfo
-    {
-        public AudioCdTrackInfo (AudioCdDiscModel model, string deviceNode, int index)
-        {
-            this.model = model;
-            this.index_on_disc = index;
-
-            Uri = new SafeUri (String.Format ("cdda://{0}#{1}", index_on_disc + 1, deviceNode));
-        }
-
-        public override bool TrackEqual (TrackInfo track)
-        {
-            AudioCdTrackInfo cd_track = track as AudioCdTrackInfo;
-            return cd_track == null ? false : (cd_track.Model == Model && cd_track.IndexOnDisc == IndexOnDisc);
-        }
-
-        private AudioCdDiscModel model;
-        public AudioCdDiscModel Model {
-            get { return model; }
-        }
-
-        private int index_on_disc;
-        public int IndexOnDisc {
-            get { return index_on_disc; }
-        }
-
-        private DatabaseAlbumInfo album_info;
-        public new DatabaseAlbumInfo Album {
-            get { return album_info; }
-            set { album_info = value; }
-        }
-
-        public DatabaseArtistInfo artist_info;
-        public new DatabaseArtistInfo Artist {
-            get { return artist_info; }
-            set { artist_info = value; }
-        }
-
-        public override string AlbumMusicBrainzId {
-            get { return Album == null ? null : Album.MusicBrainzId; }
-        }
-
-        public override string ArtistMusicBrainzId {
-            get { return Artist == null ? null : Artist.MusicBrainzId; }
-        }
-
-        public override DateTime ReleaseDate {
-            get { return Album == null ? base.ReleaseDate : Album.ReleaseDate; }
-            set {
-                if (Album == null) {
-                    base.ReleaseDate = value;
-                } else {
-                    Album.ReleaseDate = value;
-                }
-            }
-        }
-
-        private bool rip_enabled = true;
-        public bool RipEnabled {
-            get { return rip_enabled; }
-            set {
-                if (rip_enabled == value) {
-                    return;
-                }
-
-                rip_enabled = value;
-                model.EnabledCount += rip_enabled ? 1 : -1;
-            }
-        }
-    }
-}
diff --git a/src/Extensions/Banshee.AudioCd/Makefile.am b/src/Extensions/Banshee.AudioCd/Makefile.am
deleted file mode 100644
index 7ad475a..0000000
--- a/src/Extensions/Banshee.AudioCd/Makefile.am
+++ /dev/null
@@ -1,20 +0,0 @@
-ASSEMBLY = Banshee.AudioCd
-TARGET = library
-LINK = $(REF_EXTENSION_AUDIOCD)
-INSTALL_DIR = $(EXTENSIONS_INSTALL_DIR)
-
-SOURCES =  \
-	Banshee.AudioCd/AudioCdDiscModel.cs \
-	Banshee.AudioCd/AudioCdDuplicator.cs \
-	Banshee.AudioCd/AudioCdRipper.cs \
-	Banshee.AudioCd/AudioCdService.cs \
-	Banshee.AudioCd/AudioCdSource.cs \
-	Banshee.AudioCd/AudioCdTrackInfo.cs
-
-RESOURCES =  \
-	Banshee.AudioCd.addin.xml \
-	Resources/ActiveSourceUI.xml \
-	Resources/GlobalUI.xml
-
-include $(top_srcdir)/build/build.mk
-
diff --git a/src/Extensions/Banshee.AudioCd/Resources/ActiveSourceUI.xml b/src/Extensions/Banshee.AudioCd/Resources/ActiveSourceUI.xml
deleted file mode 100644
index 038665e..0000000
--- a/src/Extensions/Banshee.AudioCd/Resources/ActiveSourceUI.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<ui>
-    <toolbar name="HeaderToolbar">
-        <placeholder name="SourceActions">
-            <toolitem action="DuplicateDiscAction"/>
-            <toolitem action="RipDiscAction"/>
-        </placeholder>
-    </toolbar>
-</ui>
diff --git a/src/Extensions/Banshee.AudioCd/Resources/GlobalUI.xml b/src/Extensions/Banshee.AudioCd/Resources/GlobalUI.xml
deleted file mode 100644
index eec6c5f..0000000
--- a/src/Extensions/Banshee.AudioCd/Resources/GlobalUI.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<ui>
-    <popup name="AudioCdContextMenu">
-        <menuitem name="RipDisc" action="RipDiscAction"/>
-        <menuitem name="DuplicateDisc" action="DuplicateDiscAction"/>
-        <menuitem name="SourcePreferences" action="SourcePreferencesAction"/>
-        <separator/>
-        <menuitem name="UnmapSource" action="UnmapSourceAction"/>
-    </popup>
-</ui>
diff --git a/src/Extensions/Banshee.AudioCd/ThemeIcons/16x16/actions/media-import-audio-cd.png b/src/Extensions/Banshee.AudioCd/ThemeIcons/16x16/actions/media-import-audio-cd.png
deleted file mode 100644
index e637fea..0000000
Binary files a/src/Extensions/Banshee.AudioCd/ThemeIcons/16x16/actions/media-import-audio-cd.png and /dev/null differ
diff --git a/src/Extensions/Banshee.AudioCd/ThemeIcons/22x22/actions/media-import-audio-cd.png b/src/Extensions/Banshee.AudioCd/ThemeIcons/22x22/actions/media-import-audio-cd.png
deleted file mode 100644
index ee0d170..0000000
Binary files a/src/Extensions/Banshee.AudioCd/ThemeIcons/22x22/actions/media-import-audio-cd.png and /dev/null differ
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdDiscModel.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdDiscModel.cs
new file mode 100644
index 0000000..5e8b6a8
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdDiscModel.cs
@@ -0,0 +1,271 @@
+//
+// AudioCdDisc.cs
+//
+// Author:
+//   Aaron Bockover <abockover@novell.com>
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Threading;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+using Mono.Unix;
+using MusicBrainz;
+
+using Hyena;
+using Banshee.Base;
+using Banshee.Hardware;
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+namespace Banshee.Discs.AudioCd
+{
+    public class AudioCdDiscModel : DiscModel
+    {
+        // 44.1 kHz sample rate * 16 bit channel resolution * 2 channels (stereo)
+        private const long PCM_FACTOR = 176400;
+
+        public event EventHandler MetadataQueryStarted;
+        public event EventHandler MetadataQueryFinished;
+        public event EventHandler EnabledCountChanged;
+
+        private bool metadata_query_success;
+        private DateTime metadata_query_start_time;
+
+        public bool MetadataQuerySuccess {
+            get { return metadata_query_success; }
+        }
+
+        private TimeSpan duration;
+        public TimeSpan Duration {
+            get { return duration; }
+        }
+
+        private long file_size;
+        public long FileSize {
+            get { return file_size; }
+        }
+
+        public AudioCdDiscModel (IDiscVolume volume)
+            : base (volume)
+        {
+            disc_title = Catalog.GetString ("Audio CD");
+        }
+
+        public void NotifyUpdated ()
+        {
+            OnReloaded ();
+        }
+
+        public override void LoadModelFromDisc ()
+        {
+            Clear ();
+
+            LocalDisc mb_disc = LocalDisc.GetFromDevice (Volume.DeviceNode);
+            if (mb_disc == null) {
+                throw new ApplicationException ("Could not read contents of the disc. Platform may not be supported.");
+            }
+
+            TimeSpan[] durations = mb_disc.GetTrackDurations ();
+            for (int i = 0, n = durations.Length; i < n; i++) {
+                AudioCdTrackInfo track = new AudioCdTrackInfo (this, Volume.DeviceNode, i);
+                track.TrackNumber = i + 1;
+                track.TrackCount = n;
+                track.DiscNumber = 1;
+                track.Duration = durations[i];
+                track.ArtistName = Catalog.GetString ("Unknown Artist");
+                track.AlbumTitle = Catalog.GetString ("Unknown Album");
+                track.TrackTitle = String.Format (Catalog.GetString ("Track {0}"), track.TrackNumber);
+                track.FileSize = PCM_FACTOR * (uint)track.Duration.TotalSeconds;
+                Add (track);
+
+                duration += track.Duration;
+                file_size += track.FileSize;
+            }
+
+            EnabledCount = Count;
+
+            Reload ();
+
+            ThreadPool.QueueUserWorkItem (LoadDiscMetadata, mb_disc);
+        }
+
+        private void LoadDiscMetadata (object state)
+        {
+            try {
+                LocalDisc mb_disc = (LocalDisc)state;
+
+                OnMetadataQueryStarted (mb_disc);
+
+                Release release = Release.Query (mb_disc).PerfectMatch ();
+
+                var tracks = release.GetTracks ();
+                if (release == null || tracks.Count != Count) {
+                    OnMetadataQueryFinished (false);
+                    return;
+                }
+
+                disc_title = release.GetTitle ();
+
+                int disc_number = 1;
+                int i = 0;
+
+                foreach (Disc disc in release.GetDiscs ()) {
+                    i++;
+                    if (disc.Id == mb_disc.Id) {
+                        disc_number = i;
+                    }
+                }
+
+                DateTime release_date = DateTime.MaxValue;
+
+                foreach (Event release_event in release.GetEvents ()) {
+                    if (release_event.Date != null) {
+                        try {
+                            // Handle "YYYY" dates
+                            var date_str = release_event.Date;
+                            DateTime date = DateTime.Parse (
+                                date_str.Length > 4 ? date_str : date_str + "-01",
+                                ApplicationContext.InternalCultureInfo
+                            );
+
+                            if (date < release_date) {
+                                release_date = date;
+                            }
+                        } catch {
+                        }
+                    }
+                }
+
+                DatabaseArtistInfo artist = new DatabaseArtistInfo ();
+                var mb_artist = release.GetArtist ();
+                artist.Name = mb_artist.GetName ();
+                artist.NameSort = mb_artist.GetSortName ();
+                artist.MusicBrainzId = mb_artist.Id;
+                bool is_compilation = false;
+
+                DatabaseAlbumInfo album = new DatabaseAlbumInfo ();
+                album.Title = disc_title;
+                album.ArtistName = artist.Name;
+                album.MusicBrainzId = release.Id;
+                album.ReleaseDate = release_date == DateTime.MaxValue ? DateTime.MinValue : release_date;
+
+                i = 0;
+                foreach (Track track in tracks) {
+                    AudioCdTrackInfo model_track = (AudioCdTrackInfo)this[i++];
+                    var mb_track_artist = track.GetArtist ();
+
+                    model_track.MusicBrainzId = track.Id;
+                    model_track.TrackTitle = track.GetTitle ();
+                    model_track.ArtistName = mb_track_artist.GetName ();
+                    model_track.AlbumTitle = disc_title;
+                    model_track.DiscNumber = disc_number;
+                    model_track.Album = album;
+
+                    model_track.Artist = new DatabaseArtistInfo ();
+                    model_track.Artist.Name = model_track.ArtistName;
+                    model_track.Artist.NameSort = mb_track_artist.GetSortName ();
+                    model_track.Artist.MusicBrainzId = mb_track_artist.Id;
+
+                    if (release_date != DateTime.MinValue) {
+                        model_track.Year = release_date.Year;
+                    }
+
+                    if (!is_compilation && mb_track_artist.Id != artist.MusicBrainzId) {
+                        is_compilation = true;
+                    }
+                }
+
+                if (is_compilation) {
+                    album.IsCompilation = true;
+                    for (i = 0; i < tracks.Count; i++) {
+                        AudioCdTrackInfo model_track = (AudioCdTrackInfo)this[i];
+                        model_track.IsCompilation = true;
+                        model_track.AlbumArtist = artist.Name;
+                        model_track.AlbumArtistSort = artist.NameSort;
+                    }
+                }
+
+                OnMetadataQueryFinished (true);
+            } catch (Exception ex) {
+                Log.DebugException (ex);
+                OnMetadataQueryFinished (false);
+            }
+        }
+
+        private void OnMetadataQueryStarted (LocalDisc mb_disc)
+        {
+            metadata_query_success = false;
+            metadata_query_start_time = DateTime.Now;
+            Log.InformationFormat ("Querying MusicBrainz for Disc Release ({0})", mb_disc.Id);
+
+            ThreadAssist.ProxyToMain (delegate {
+                EventHandler handler = MetadataQueryStarted;
+                if (handler != null) {
+                    handler (this, EventArgs.Empty);
+                }
+            });
+        }
+
+        private void OnMetadataQueryFinished (bool success)
+        {
+            metadata_query_success = success;
+            Log.InformationFormat ("Query finished (success: {0}, {1} seconds)",
+                success, (DateTime.Now - metadata_query_start_time).TotalSeconds);
+
+            ThreadAssist.ProxyToMain (delegate {
+                Reload ();
+
+                EventHandler handler = MetadataQueryFinished;
+                if (handler != null) {
+                    handler (this, EventArgs.Empty);
+                }
+            });
+        }
+
+        private void OnEnabledCountChanged ()
+        {
+            EventHandler handler = EnabledCountChanged;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+
+        private string disc_title;
+        public override string Title {
+            get { return disc_title; }
+        }
+
+        private int enabled_count;
+        public int EnabledCount {
+            get { return enabled_count; }
+            internal set {
+                enabled_count = value;
+                OnEnabledCountChanged ();
+            }
+        }
+    }
+}
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdDuplicator.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdDuplicator.cs
new file mode 100644
index 0000000..60db596
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdDuplicator.cs
@@ -0,0 +1,66 @@
+//
+// AudioCdDuplicator.cs
+//
+// Author:
+//   Aaron Bockover <abockover@novell.com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mono.Addins;
+
+using Banshee.Hardware;
+
+namespace Banshee.Discs.AudioCd
+{
+    public static class AudioCdDuplicator
+    {
+        private static bool duplicator_extension_queried = false;
+        private static TypeExtensionNode duplicator_extension_node = null;
+
+        public static bool Supported {
+            get {
+                if (duplicator_extension_queried) {
+                    return duplicator_extension_node != null;
+                }
+
+                duplicator_extension_queried = true;
+
+                foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes (
+                    "/Banshee/Platform/DiscDuplicator")) {
+                    duplicator_extension_node = node;
+                    break;
+                }
+
+                return duplicator_extension_node != null;
+            }
+        }
+
+        public static void Duplicate (AudioCdDiscModel model)
+        {
+            if (Supported && model != null && model.Volume != null) {
+                ((IDiscDuplicator)duplicator_extension_node.CreateInstance ()).Duplicate (model.Volume);
+            }
+        }
+    }
+}
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdRipper.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdRipper.cs
new file mode 100644
index 0000000..73718a6
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdRipper.cs
@@ -0,0 +1,288 @@
+//
+// AudioCdRipper.cs
+//
+// Author:
+//   Aaron Bockover <abockover@novell.com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+
+using Mono.Unix;
+using Mono.Addins;
+
+using Hyena;
+using Hyena.Jobs;
+
+using Banshee.Library;
+using Banshee.ServiceStack;
+using Banshee.Streaming;
+using Banshee.Collection;
+using Banshee.Collection.Database;
+using Banshee.MediaEngine;
+
+namespace Banshee.Discs.AudioCd
+{
+    public class AudioCdRipper : IDisposable
+    {
+        private static bool ripper_extension_queried = false;
+        private static TypeExtensionNode ripper_extension_node = null;
+
+        public static bool Supported {
+            get {
+                if (ripper_extension_queried) {
+                    return ripper_extension_node != null;
+                }
+
+                ripper_extension_queried = true;
+
+                foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes (
+                    "/Banshee/MediaEngine/AudioCdRipper")) {
+                    ripper_extension_node = node;
+                    break;
+                }
+
+                return ripper_extension_node != null;
+            }
+        }
+
+        public event EventHandler Finished;
+
+        // State that does real work
+        private IAudioCdRipper ripper;
+        private AudioCdSource source;
+        private UserJob user_job;
+
+        // State to process the rip operation
+        private Queue<AudioCdTrackInfo> queue = new Queue<AudioCdTrackInfo> ();
+
+        private TimeSpan ripped_duration;
+        private TimeSpan total_duration;
+        private int track_index;
+
+        // State to compute/display the rip speed (i.e. 24x)
+        private TimeSpan last_speed_poll_duration;
+        private DateTime last_speed_poll_time;
+        private double last_speed_poll_factor;
+        private string status;
+
+        public AudioCdRipper (AudioCdSource source)
+        {
+            if (ripper_extension_node != null) {
+                ripper = (IAudioCdRipper)ripper_extension_node.CreateInstance ();
+                ripper.TrackFinished += OnTrackFinished;
+                ripper.Progress += OnProgress;
+                ripper.Error += OnError;
+            } else {
+                throw new ApplicationException ("No AudioCdRipper extension is installed");
+            }
+
+            this.source = source;
+        }
+
+        public void Start ()
+        {
+            ResetState ();
+
+            foreach (AudioCdTrackInfo track in source.Model) {
+                if (track.RipEnabled) {
+                    total_duration += track.Duration;
+                    queue.Enqueue (track);
+                }
+            }
+
+            if (queue.Count == 0) {
+                return;
+            }
+
+            source.LockAllTracks ();
+
+            user_job = new UserJob (Catalog.GetString ("Importing Audio CD"),
+                Catalog.GetString ("Initializing Drive"), "media-import-audio-cd");
+            user_job.CancelMessage = String.Format (Catalog.GetString (
+                "<i>{0}</i> is still being imported into the music library. Would you like to stop it?"
+                ), GLib.Markup.EscapeText (source.Model.Title));
+            user_job.SetResources (Resource.Cpu);
+            user_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
+            user_job.CanCancel = true;
+            user_job.CancelRequested += OnCancelRequested;
+            user_job.Finished += OnFinished;
+            user_job.Register ();
+
+            if (source != null && source.Model != null) {
+                if (!source.Model.LockDoor ()) {
+                    Hyena.Log.Warning ("Could not lock CD-ROM door", false);
+                }
+            }
+
+            ripper.Begin (source.Model.Volume.DeviceNode, AudioCdService.ErrorCorrection.Get ());
+
+            RipNextTrack ();
+        }
+
+        public void Dispose ()
+        {
+            ResetState ();
+
+            if (source != null && source.Model != null) {
+                source.Model.UnlockDoor ();
+            }
+
+            if (ripper != null) {
+                ripper.Finish ();
+                ripper = null;
+            }
+
+            if (user_job != null) {
+                user_job.Finish ();
+                user_job = null;
+            }
+        }
+
+        private void ResetState ()
+        {
+            track_index = 0;
+            ripped_duration = TimeSpan.Zero;
+            total_duration = TimeSpan.Zero;
+            last_speed_poll_duration = TimeSpan.Zero;
+            last_speed_poll_time = DateTime.MinValue;
+            last_speed_poll_factor = 0;
+            status = null;
+            queue.Clear ();
+        }
+
+        private void RipNextTrack ()
+        {
+            if (queue.Count == 0) {
+                OnFinished ();
+                Dispose ();
+                return;
+            }
+
+            AudioCdTrackInfo track = queue.Dequeue ();
+
+            user_job.Title = String.Format (Catalog.GetString ("Importing {0} of {1}"),
+                ++track_index, source.Model.EnabledCount);
+            status = String.Format("{0} - {1}", track.ArtistName, track.TrackTitle);
+            user_job.Status = status;
+
+            SafeUri uri = new SafeUri (MusicLibrarySource.MusicFileNamePattern.BuildFull (
+                ServiceManager.SourceManager.MusicLibrary.BaseDirectory, track, null));
+            bool tagging_supported;
+            ripper.RipTrack (track.IndexOnDisc, track, uri, out tagging_supported);
+        }
+
+#region Ripper Event Handlers
+
+        private void OnTrackFinished (object o, AudioCdRipperTrackFinishedArgs args)
+        {
+            if (user_job == null || ripper == null) {
+                return;
+            }
+
+            AudioCdTrackInfo track = (AudioCdTrackInfo)args.Track;
+
+            ripped_duration += track.Duration;
+            track.PrimarySource = ServiceManager.SourceManager.MusicLibrary;
+            track.Uri = args.Uri;
+
+            track.FileSize = Banshee.IO.File.GetSize (track.Uri);
+            track.FileModifiedStamp = Banshee.IO.File.GetModifiedTime (track.Uri);
+            track.LastSyncedStamp = DateTime.Now;
+
+            using (var file = StreamTagger.ProcessUri (track.Uri)) {
+                StreamTagger.TrackInfoMerge (track, file, true);
+            }
+
+            track.Save ();
+
+            source.UnlockTrack (track);
+            RipNextTrack ();
+        }
+
+        private void OnProgress (object o, AudioCdRipperProgressArgs args)
+        {
+            if (user_job == null) {
+                return;
+            }
+
+            TimeSpan total_ripped_duration = ripped_duration + args.EncodedTime;
+            user_job.Progress = total_ripped_duration.TotalMilliseconds / total_duration.TotalMilliseconds;
+
+            TimeSpan poll_diff = DateTime.Now - last_speed_poll_time;
+            double factor = 0;
+
+            if (poll_diff.TotalMilliseconds >= 1000) {
+                factor = ((total_ripped_duration - last_speed_poll_duration).TotalMilliseconds
+                    * (poll_diff.TotalMilliseconds / 1000.0)) / 1000.0;
+
+                last_speed_poll_duration = total_ripped_duration;
+                last_speed_poll_time = DateTime.Now;
+                last_speed_poll_factor = factor > 1 ? factor : 0;
+            }
+
+            // Make sure the speed factor is between 1 and 200 to allow it to ramp and settle
+            user_job.Status = last_speed_poll_factor > 1 && last_speed_poll_factor <= 200
+                ? String.Format ("{0} ({1:0.0}x)", status, last_speed_poll_factor)
+                : status;
+        }
+
+        private void OnError (object o, AudioCdRipperErrorArgs args)
+        {
+            Dispose ();
+            Hyena.Log.Error (Catalog.GetString ("Cannot Import CD"), args.Message, true);
+        }
+
+#endregion
+
+        private void OnFinished ()
+        {
+            EventHandler handler = Finished;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+
+#region User Job Event Handlers
+
+        private void OnCancelRequested (object o, EventArgs args)
+        {
+            Dispose ();
+        }
+
+        private void OnFinished (object o, EventArgs args)
+        {
+            if (user_job != null) {
+                user_job.CancelRequested -= OnCancelRequested;
+                user_job.Finished -= OnFinished;
+                user_job = null;
+            }
+
+            source.UnlockAllTracks ();
+        }
+
+#endregion
+
+    }
+}
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdService.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdService.cs
new file mode 100644
index 0000000..f8c0ff1
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdService.cs
@@ -0,0 +1,267 @@
+//
+// AudioCdService.cs
+//
+// Author:
+//   Aaron Bockover <abockover@novell.com>
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using Mono.Unix;
+
+using Hyena;
+
+using Banshee.ServiceStack;
+using Banshee.Configuration;
+using Banshee.Preferences;
+using Banshee.Hardware;
+using Banshee.Gui;
+
+namespace Banshee.Discs.AudioCd
+{
+    public class AudioCdService : DiscService, IService
+    {
+        private SourcePage pref_page;
+        private Section pref_section;
+        private uint global_interface_id;
+
+        public AudioCdService ()
+        {
+        }
+
+        public override void Initialize ()
+        {
+            lock (this) {
+                InstallPreferences ();
+                base.Initialize ();
+                SetupActions ();
+            }
+        }
+
+        public override void Dispose ()
+        {
+            lock (this) {
+                UninstallPreferences ();
+                base.Dispose ();
+                DisposeActions ();
+            }
+        }
+
+#region DeviceCommand Handling
+
+        protected override bool DeviceCommandMatchesSource (DiscSource source, DeviceCommand command)
+        {
+            AudioCdSource cdSource = source as AudioCdSource;
+
+            if (cdSource != null && command.DeviceId.StartsWith ("cdda:")) {
+                try {
+                    Uri uri = new Uri (command.DeviceId);
+                    string match_device_node = String.Format ("{0}{1}", uri.Host,
+                        uri.AbsolutePath).TrimEnd ('/', '\\');
+                    string device_node = source.DiscModel.Volume.DeviceNode;
+                    return device_node.EndsWith (match_device_node);
+                } catch {
+                }
+            }
+
+            return false;
+        }
+
+#endregion
+
+#region Preferences
+
+        private void InstallPreferences ()
+        {
+            PreferenceService service = ServiceManager.Get<PreferenceService> ();
+            if (service == null) {
+                return;
+            }
+
+            service.InstallWidgetAdapters += OnPreferencesServiceInstallWidgetAdapters;
+
+            pref_page = new Banshee.Preferences.SourcePage ("audio-cd", Catalog.GetString ("Audio CDs"), "media-cdrom", 400);
+
+            pref_section = pref_page.Add (new Section ("audio-cd", Catalog.GetString ("Audio CD Importing"), 20));
+            pref_section.ShowLabel = false;
+
+            pref_section.Add (new VoidPreference ("import-profile",  Catalog.GetString ("_Import format")));
+            pref_section.Add (new VoidPreference ("import-profile-desc"));
+
+            pref_section.Add (new SchemaPreference<bool> (AutoRip,
+                Catalog.GetString ("_Automatically import audio CDs when inserted"),
+                Catalog.GetString ("When an audio CD is inserted, automatically begin importing it " +
+                    "if metadata can be found and it is not already in the library.")));
+
+            pref_section.Add (new SchemaPreference<bool> (EjectAfterRipped,
+                Catalog.GetString ("_Eject when done importing"),
+                Catalog.GetString ("When an audio CD has been imported, automatically eject it.")));
+
+            pref_section.Add (new SchemaPreference<bool> (ErrorCorrection,
+                Catalog.GetString ("Use error correction when importing"),
+                Catalog.GetString ("Error correction tries to work around problem areas on a disc, such " +
+                    "as surface scratches, but will slow down importing substantially.")));
+        }
+
+        private void UninstallPreferences ()
+        {
+            PreferenceService service = ServiceManager.Get<PreferenceService> ();
+            if (service == null || pref_page == null) {
+                return;
+            }
+
+            service.InstallWidgetAdapters -= OnPreferencesServiceInstallWidgetAdapters;
+
+            pref_page.Dispose ();
+            pref_page = null;
+            pref_section = null;
+        }
+
+        private void OnPreferencesServiceInstallWidgetAdapters (object o, EventArgs args)
+        {
+            if (pref_section == null) {
+                return;
+            }
+
+            Gtk.HBox description_box = new Gtk.HBox ();
+            Banshee.MediaProfiles.Gui.ProfileComboBoxConfigurable chooser
+                = new Banshee.MediaProfiles.Gui.ProfileComboBoxConfigurable (ServiceManager.MediaProfileManager,
+                    "cd-importing", description_box);
+
+            pref_section["import-profile"].DisplayWidget = chooser;
+            pref_section["import-profile"].MnemonicWidget = chooser.Combo;
+            pref_section["import-profile-desc"].DisplayWidget = description_box;
+        }
+
+        public static readonly SchemaEntry<bool> ErrorCorrection = new SchemaEntry<bool> (
+            "import", "audio_cd_error_correction",
+            false,
+            "Enable error correction",
+            "When importing an audio CD, enable error correction (paranoia mode)"
+        );
+
+        public static readonly SchemaEntry<bool> AutoRip = new SchemaEntry<bool> (
+            "import", "auto_rip_cds",
+            false,
+            "Enable audio CD auto ripping",
+            "When an audio CD is inserted, automatically begin ripping it."
+        );
+
+        public static readonly SchemaEntry<bool> EjectAfterRipped = new SchemaEntry<bool> (
+            "import", "eject_after_ripped",
+            false,
+            "Eject audio CD after ripped",
+            "After an audio CD has been ripped, automatically eject it."
+        );
+
+#endregion
+
+#region UI Actions
+
+        private void SetupActions ()
+        {
+            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
+            if (uia_service == null) {
+                return;
+            }
+
+            uia_service.GlobalActions.AddImportant (new Gtk.ActionEntry [] {
+                new Gtk.ActionEntry ("RipDiscAction", null,
+                    Catalog.GetString ("Import CD"), null,
+                    Catalog.GetString ("Import this audio CD to the library"),
+                    OnImportDisc)
+            });
+
+            uia_service.GlobalActions.AddImportant (
+                new Gtk.ActionEntry ("DuplicateDiscAction", null,
+                    Catalog.GetString ("Duplicate CD"), null,
+                    Catalog.GetString ("Duplicate this audio CD"),
+                    OnDuplicateDisc)
+            );
+
+            global_interface_id = uia_service.UIManager.AddUiFromResource ("GlobalUI.xml");
+        }
+
+        private void DisposeActions ()
+        {
+            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
+            if (uia_service == null) {
+                return;
+            }
+
+            uia_service.GlobalActions.Remove ("RipDiscAction");
+            uia_service.GlobalActions.Remove ("DuplicateDiscAction");
+            uia_service.UIManager.RemoveUi (global_interface_id);
+        }
+
+        private void OnImportDisc (object o, EventArgs args)
+        {
+            ImportOrDuplicateDisc (true);
+        }
+
+        private void OnDuplicateDisc (object o, EventArgs args)
+        {
+            ImportOrDuplicateDisc (false);
+        }
+
+        private void ImportOrDuplicateDisc (bool import)
+        {
+            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
+            if (uia_service == null) {
+                return;
+            }
+
+            AudioCdSource source = uia_service.SourceActions.ActionSource as AudioCdSource;
+            if (source != null) {
+                if (import) {
+                    source.ImportDisc ();
+                } else {
+                    source.DuplicateDisc ();
+                }
+            }
+        }
+
+#endregion
+
+#region implemented abstract members of Banshee.Discs.DiscService
+
+        protected override DiscSource GetDiscSource (IDiscVolume volume)
+        {
+            if  (volume.HasAudio) {
+                Log.Debug ("Mapping audio cd");
+                return new AudioCdSource (this, new AudioCdDiscModel (volume));
+            } else {
+                Log.Debug ("Can not map to audio cd source.");
+                return null;
+            }
+        }
+        
+#endregion
+
+        string IService.ServiceName {
+            get { return "AudioCdService"; }
+        }
+    }
+}
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdSource.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdSource.cs
new file mode 100644
index 0000000..7562e13
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdSource.cs
@@ -0,0 +1,359 @@
+//
+// AudioCdSource.cs
+//
+// Author:
+//   Aaron Bockover <abockover@novell.com>
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Mono.Unix;
+
+using Hyena;
+using Banshee.Base;
+using Banshee.ServiceStack;
+using Banshee.Sources;
+using Banshee.Library;
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+using Gtk;
+using Banshee.Gui;
+
+namespace Banshee.Discs.AudioCd
+{
+    public class AudioCdSource : DiscSource, IImportSource,
+        IDurationAggregator, IFileSizeAggregator
+    {
+        private SourceMessage query_message;
+
+        public AudioCdSource (DiscService service, AudioCdDiscModel discModel)
+            : base (service, discModel, Catalog.GetString ("Audio CD"), discModel.Title, 400)
+        {
+            TypeUniqueId = "";
+            Properties.SetString ("TrackView.ColumnControllerXml", String.Format (@"
+                <column-controller>
+                  <column>
+                    <renderer type=""Hyena.Data.Gui.ColumnCellCheckBox"" property=""RipEnabled""/>
+                  </column>
+                  <add-all-defaults />
+                </column-controller>
+            "));
+
+            Model.MetadataQueryStarted += OnMetadataQueryStarted;
+            Model.MetadataQueryFinished += OnMetadataQueryFinished;
+            Model.EnabledCountChanged += OnEnabledCountChanged;
+            Model.LoadModelFromDisc ();
+
+            SetupGui ();
+        }
+
+        public TimeSpan Duration {
+            get { return Model.Duration; }
+        }
+
+        public long FileSize {
+            get { return Model.FileSize; }
+        }
+
+        public new AudioCdDiscModel Model {
+            get { return (AudioCdDiscModel) base.DiscModel; }
+            set { base.DiscModel = value; }
+        }
+
+        public override void Dispose ()
+        {
+            StopPlayingDisc ();
+            ClearMessages ();
+            Model.MetadataQueryStarted -= OnMetadataQueryStarted;
+            Model.MetadataQueryFinished -= OnMetadataQueryFinished;
+            Model.EnabledCountChanged -= OnEnabledCountChanged;
+            Service = null;
+            Model = null;
+        }
+
+        private void OnEnabledCountChanged (object o, EventArgs args)
+        {
+            UpdateActions ();
+        }
+
+        private void OnMetadataQueryStarted (object o, EventArgs args)
+        {
+            if (query_message != null) {
+                DestroyQueryMessage ();
+            }
+
+            query_message = new SourceMessage (this);
+            query_message.FreezeNotify ();
+            query_message.CanClose = false;
+            query_message.IsSpinning = true;
+            query_message.Text = Catalog.GetString ("Searching for track information...");
+            query_message.ThawNotify ();
+
+            PushMessage (query_message);
+        }
+
+        private void OnMetadataQueryFinished (object o, EventArgs args)
+        {
+            if (Model.Title != Name) {
+                Name = Model.Title;
+                OnUpdated ();
+            }
+
+            if (Model.MetadataQuerySuccess) {
+                DestroyQueryMessage ();
+                if (DiscIsPlaying) {
+                    ServiceManager.PlayerEngine.TrackInfoUpdated ();
+                }
+
+                if (AudioCdService.AutoRip.Get ()) {
+                    BeginAutoRip ();
+                }
+
+                return;
+            }
+
+            if (query_message == null) {
+                return;
+            }
+
+            query_message.FreezeNotify ();
+            query_message.IsSpinning = false;
+            query_message.SetIconName ("dialog-error");
+            query_message.Text = Catalog.GetString ("Could not fetch track information");
+            query_message.CanClose = true;
+            query_message.ThawNotify ();
+        }
+
+        private void DestroyQueryMessage ()
+        {
+            if (query_message != null) {
+                RemoveMessage (query_message);
+                query_message = null;
+            }
+        }
+
+        private void BeginAutoRip ()
+        {
+            // Make sure the album isn't already in the Library
+            TrackInfo track = Model[0];
+            int count = ServiceManager.DbConnection.Query<int> (
+                @"SELECT Count(*) FROM CoreTracks, CoreArtists, CoreAlbums WHERE
+                    CoreTracks.PrimarySourceID = ? AND
+                    CoreTracks.ArtistID = CoreArtists.ArtistID AND
+                    CoreTracks.AlbumID = CoreAlbums.AlbumID AND
+                    CoreArtists.Name = ? AND CoreAlbums.Title = ? AND (CoreTracks.Disc = ? OR CoreTracks.Disc = 0)",
+                    ServiceManager.SourceManager.MusicLibrary.DbId,
+                    track.ArtistName, track.AlbumTitle, track.DiscNumber
+            );
+
+            if (count > 0) {
+                SetStatus (Catalog.GetString ("Automatic import off since this album is already in the Music Library."), true, false, null);
+                return;
+            }
+
+            Log.DebugFormat ("Beginning auto rip of {0}", Name);
+            ImportDisc ();
+        }
+
+        internal void ImportDisc ()
+        {
+            AudioCdRipper ripper = null;
+
+            try {
+                if (AudioCdRipper.Supported) {
+                    ripper = new AudioCdRipper (this);
+                    ripper.Finished += OnRipperFinished;
+                    ripper.Start ();
+                }
+            } catch (Exception e) {
+                if (ripper != null) {
+                    ripper.Dispose ();
+                }
+
+                Log.Error (Catalog.GetString ("Could not import CD"), e.Message, true);
+                Log.Exception (e);
+            }
+        }
+
+        private void OnRipperFinished (object o, EventArgs args)
+        {
+            if (AudioCdService.EjectAfterRipped.Get ()) {
+                Unmap ();
+            }
+        }
+
+        internal void DuplicateDisc ()
+        {
+            try {
+                AudioCdDuplicator.Duplicate (Model);
+            } catch (Exception e) {
+                Hyena.Log.Error (Catalog.GetString ("Could not duplicate audio CD"), e.Message, true);
+                Hyena.Log.Exception (e);
+            }
+        }
+
+        internal void LockAllTracks ()
+        {
+            StopPlayingDisc ();
+
+            foreach (AudioCdTrackInfo track in Model) {
+                track.CanPlay = false;
+            }
+
+            Model.NotifyUpdated ();
+        }
+
+        internal void UnlockAllTracks ()
+        {
+            foreach (AudioCdTrackInfo track in Model) {
+                track.CanPlay = true;
+            }
+
+            Model.NotifyUpdated ();
+        }
+
+        internal void UnlockTrack (AudioCdTrackInfo track)
+        {
+            track.CanPlay = true;
+            Model.NotifyUpdated ();
+        }
+
+#region DiscSource
+
+        public override bool CanRepeat {
+            get { return true;}
+        }
+
+        public override bool CanShuffle {
+            get { return true; }
+        }
+
+#endregion
+
+#region Source Overrides
+
+        public override int Count {
+            get { return Model.Count; }
+        }
+
+        public override string PreferencesPageId {
+            get { return "audio-cd"; }
+        }
+
+        public override bool HasEditableTrackProperties {
+            get { return true; }
+        }
+
+        public override bool HasViewableTrackProperties {
+            get { return true; }
+        }
+
+#endregion
+
+#region GUI/ThickClient
+
+        private bool actions_loaded = false;
+
+        private void SetupGui ()
+        {
+            Properties.SetStringList ("Icon.Name", "media-cdrom", "gnome-dev-cdrom-audio", "source-cd-audio");
+            Properties.SetString ("SourcePreferencesActionLabel", Catalog.GetString ("Audio CD Preferences"));
+            Properties.SetString ("UnmapSourceActionLabel", Catalog.GetString ("Eject Disc"));
+            Properties.SetString ("UnmapSourceActionIconName", "media-eject");
+            Properties.SetString ("ActiveSourceUIResource", "ActiveSourceUI.xml");
+            Properties.SetString ("GtkActionPath", "/AudioCdContextMenu");
+
+            actions_loaded = true;
+
+            UpdateActions ();
+        }
+
+        private void UpdateActions ()
+        {
+            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
+            if (uia_service == null) {
+                return;
+            }
+
+            Gtk.Action rip_action = uia_service.GlobalActions["RipDiscAction"];
+            if (rip_action != null) {
+                string title = Model.Title;
+                int max_title_length = 20;
+                title = title.Length > max_title_length
+                    ? String.Format ("{0}\u2026", title.Substring (0, max_title_length).Trim ())
+                    : title;
+                rip_action.Label = String.Format (Catalog.GetString ("Import \u201f{0}\u201d"), title);
+                rip_action.ShortLabel = Catalog.GetString ("Import CD");
+                rip_action.IconName = "media-import-audio-cd";
+                rip_action.Sensitive = AudioCdRipper.Supported && Model.EnabledCount > 0;
+            }
+
+            Gtk.Action duplicate_action = uia_service.GlobalActions["DuplicateDiscAction"];
+            if (duplicate_action != null) {
+                duplicate_action.IconName = "media-cdrom";
+                duplicate_action.Visible = AudioCdDuplicator.Supported;
+            }
+        }
+
+        protected override void OnUpdated ()
+        {
+            if (actions_loaded) {
+                UpdateActions ();
+            }
+
+            base.OnUpdated ();
+        }
+
+#endregion
+
+#region IImportSource
+
+        void IImportSource.Import ()
+        {
+            ImportDisc ();
+        }
+
+        string [] IImportSource.IconNames {
+            get { return Properties.GetStringList ("Icon.Name"); }
+        }
+
+        bool IImportSource.CanImport {
+            get { return true; }
+        }
+
+        int IImportSource.SortOrder {
+            get { return -10; }
+        }
+
+        string IImportSource.ImportLabel {
+            get { return null; }
+        }
+
+#endregion
+
+    }
+}
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdTrackInfo.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdTrackInfo.cs
new file mode 100644
index 0000000..f359cf1
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.AudioCd/AudioCdTrackInfo.cs
@@ -0,0 +1,109 @@
+//
+// AudioCdTrackInfo.cs
+//
+// Author:
+//   Aaron Bockover <abockover@novell.com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Hyena;
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+namespace Banshee.Discs.AudioCd
+{
+    public class AudioCdTrackInfo : DiscTrackInfo
+    {
+        public AudioCdTrackInfo (AudioCdDiscModel model, string deviceNode, int index)
+            : base (model)
+        {
+            this.index_on_disc = index;
+
+            Uri = new SafeUri (String.Format ("cdda://{0}#{1}", index_on_disc + 1, deviceNode));
+        }
+
+        public override bool TrackEqual (TrackInfo track)
+        {
+            AudioCdTrackInfo cd_track = track as AudioCdTrackInfo;
+            return cd_track == null ? false : (cd_track.Model == Model && cd_track.IndexOnDisc == IndexOnDisc);
+        }
+
+        public new AudioCdDiscModel Model {
+            get {
+                return (AudioCdDiscModel) base.Model;
+            }
+        }
+
+        private int index_on_disc;
+        public int IndexOnDisc {
+            get { return index_on_disc; }
+        }
+
+        private DatabaseAlbumInfo album_info;
+        public new DatabaseAlbumInfo Album {
+            get { return album_info; }
+            set { album_info = value; }
+        }
+
+        public DatabaseArtistInfo artist_info;
+        public new DatabaseArtistInfo Artist {
+            get { return artist_info; }
+            set { artist_info = value; }
+        }
+
+        public override string AlbumMusicBrainzId {
+            get { return Album == null ? null : Album.MusicBrainzId; }
+        }
+
+        public override string ArtistMusicBrainzId {
+            get { return Artist == null ? null : Artist.MusicBrainzId; }
+        }
+
+        public override DateTime ReleaseDate {
+            get { return Album == null ? base.ReleaseDate : Album.ReleaseDate; }
+            set {
+                if (Album == null) {
+                    base.ReleaseDate = value;
+                } else {
+                    Album.ReleaseDate = value;
+                }
+            }
+        }
+
+        private bool rip_enabled = true;
+        public bool RipEnabled {
+            get { return rip_enabled; }
+            set {
+                if (rip_enabled == value) {
+                    return;
+                }
+
+                rip_enabled = value;
+                Model.EnabledCount += rip_enabled ? 1 : -1;
+            }
+        }
+    }
+}
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdModel.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdModel.cs
new file mode 100644
index 0000000..b51b6d7
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdModel.cs
@@ -0,0 +1,48 @@
+//
+// DvdModel.cs
+//
+// Author:
+//   Alex Launi <alex.launi@gmail.com>
+//
+// Copyright 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Mono.Unix;
+
+using Banshee.Collection;
+using Banshee.Hardware;
+
+namespace Banshee.Discs.Dvd
+{
+    public class DvdModel : DiscModel
+    {
+        public DvdModel (IDiscVolume volume) : base (volume)
+        {
+        }
+
+        public override void LoadModelFromDisc ()
+        {
+            Add (new DvdTrackInfo (this, Volume.DeviceNode));
+        }
+    }
+}
+
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdService.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdService.cs
new file mode 100644
index 0000000..abc2593
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdService.cs
@@ -0,0 +1,134 @@
+//
+// DvdService.cs
+//
+// Author:
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Banshee.Hardware;
+using Banshee.Gui;
+using Banshee.ServiceStack;
+using Mono.Unix;
+using Hyena;
+
+namespace Banshee.Discs.Dvd
+{
+    public class DvdService : DiscService, IService
+    {
+        private uint global_interface_id;
+        
+        public DvdService ()
+        {
+        }
+
+        protected override bool DeviceCommandMatchesSource (DiscSource source, DeviceCommand command)
+        {
+            DvdSource dvdSource = source as DvdSource;
+
+            if (dvdSource != null && command.DeviceId.StartsWith ("dvd:")) {
+                try {
+                    Uri uri = new Uri (command.DeviceId);
+                    string match_device_node = String.Format ("{0}{1}", uri.Host,
+                        uri.AbsolutePath).TrimEnd ('/', '\\');
+                    string device_node = source.DiscModel.Volume.DeviceNode;
+                    return device_node.EndsWith (match_device_node);
+                } catch {
+                }
+            }
+
+            return false;
+        }
+
+        public override void Initialize()
+        {
+            lock (this) {
+                //InstallPreferences ();
+                base.Initialize ();
+                SetupActions ();
+            }
+
+        }
+
+        public override void Dispose ()
+        {
+            lock (this) {
+                //UninstallPreferences ();
+                base.Dispose ();
+                DisposeActions ();
+            }
+        }
+
+#region UI Actions
+
+        private void SetupActions ()
+        {
+            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
+            if (uia_service == null) {
+                return;
+            }
+
+            uia_service.GlobalActions.AddImportant (new Gtk.ActionEntry [] {
+                new Gtk.ActionEntry ("GoToMenuAction", null,
+                    Catalog.GetString ("Go to Menu"), null,
+                    Catalog.GetString ("Naviguate to menu"),
+                    (object o, EventArgs args) => { ServiceManager.PlayerEngine.NavigateToMenu (); })
+            });
+
+            global_interface_id = uia_service.UIManager.AddUiFromResource ("GlobalUIDvd.xml");
+        }
+
+        private void DisposeActions ()
+        {
+            InterfaceActionService uia_service = ServiceManager.Get<InterfaceActionService> ();
+            if (uia_service == null) {
+                return;
+            }
+
+            uia_service.GlobalActions.Remove ("GoToMenuAction");
+            uia_service.UIManager.RemoveUi (global_interface_id);
+        }
+
+#endregion
+
+#region implemented abstract members of Banshee.Discs.DiscService
+
+        protected override DiscSource GetDiscSource (IDiscVolume volume)
+        {
+            if (volume.HasVideo) {
+                Log.Debug ("Mapping dvd");
+                return new DvdSource (this, new DvdModel (volume));
+            } else {
+                Log.Debug ("Can not map to dvd source.");
+                return null;
+            }
+        }
+        
+#endregion
+
+        string IService.ServiceName {
+            get { return "DvdService"; }
+        }
+    }
+}
+
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdSource.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdSource.cs
new file mode 100644
index 0000000..bd2b662
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdSource.cs
@@ -0,0 +1,117 @@
+//
+// DvdSource.cs
+//
+// Author:
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Mono.Unix;
+using Banshee.PlaybackController;
+using Banshee.ServiceStack;
+
+namespace Banshee.Discs.Dvd
+{
+    public class DvdSource : DiscSource, IBasicPlaybackController
+    {
+        public DvdSource (DiscService service, DvdModel model)
+            : base (service, (DiscModel) model, Catalog.GetString ("Dvd"), model.Title, 400)
+        {
+            TypeUniqueId = "";
+
+            SetupGui ();
+            model.LoadModelFromDisc ();
+        }
+
+#region DiscSource
+
+        public override bool CanRepeat {
+            get { return false;}
+        }
+
+        public override bool CanShuffle {
+            get { return false; }
+        }
+
+#endregion
+
+#region GUI/ThickClient
+
+        private bool actions_loaded = false;
+
+        private void SetupGui ()
+        {
+            Properties.SetStringList ("Icon.Name", "media-dvd", "gnome-dev-dvd");
+            Properties.SetString ("ActiveSourceUIResource", "ActiveSourceUI.xml");
+            Properties.SetString ("GtkActionPath", "/DvdContextMenu");
+            actions_loaded = true;
+
+            UpdateActions ();
+        }
+
+        private void UpdateActions ()
+        {
+            // TODO Update dvd actions
+        }
+
+        protected override void OnUpdated ()
+        {
+            if (actions_loaded) {
+                UpdateActions ();
+            }
+
+            base.OnUpdated ();
+        }
+#endregion
+
+#region IBasicPlaybackController implementation
+
+        public bool First ()
+        {
+            // TODO expected behaviour ?
+            // Go to menu ?
+            ServiceManager.PlayerEngine.NavigateToMenu ();
+            return true;
+        }
+
+        public bool Next (bool restart, bool changeImmediately)
+        {
+            if (!ServiceManager.PlayerEngine.IsMenu) {
+                ServiceManager.PlayerEngine.GoToNextChapter ();
+            }
+            // Do nothing if not in menu
+            return true;
+        }
+
+        public bool Previous (bool restart)
+        {
+            if (!ServiceManager.PlayerEngine.IsMenu) {
+                ServiceManager.PlayerEngine.GoToPreviousChapter ();
+            }
+            // Do nothing if not in menu
+            return true;
+        }
+#endregion
+    }
+}
+
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdTrackInfo.cs b/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdTrackInfo.cs
new file mode 100644
index 0000000..8f34863
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.Dvd/DvdTrackInfo.cs
@@ -0,0 +1,49 @@
+//
+// DvdTrackInfo.cs
+//
+// Author:
+//   Alex Launi <alex.launi@gmail.com>
+//
+// Copyright 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Hyena;
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+namespace Banshee.Discs.Dvd
+{
+    public class DvdTrackInfo : DiscTrackInfo
+    {
+        public DvdTrackInfo (DiscModel model, string deviceNode)
+            : base (model)
+        {
+            Uri = new SafeUri (String.Format ("dvd://{0}", deviceNode));
+        }
+
+        public override TrackMediaAttributes MediaAttributes {
+            get { return TrackMediaAttributes.VideoStream; }
+        }
+    }
+}
+
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.addin.xml b/src/Extensions/Banshee.Discs/Banshee.Discs.addin.xml
new file mode 100644
index 0000000..31901c2
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.addin.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Addin 
+    id="Banshee.Discs"
+    version="1.0"
+    compatVersion="1.0"
+    copyright="© 2008 Novell Inc. Licensed under the MIT X11 license."
+    name="DVD and Audio CD Support"
+    category="Core"
+    description="Watch DVDs; listen to and rip Audio CDs."
+    author="Aaron Bockover"
+    url="http://banshee.fm/"
+    defaultEnabled="true">
+
+  <Dependencies>
+    <Addin id="Banshee.Services" version="1.0"/>
+  </Dependencies>
+
+  <Extension path="/Banshee/ServiceManager/Service">
+    <Service class="Banshee.Discs.AudioCd.AudioCdService"/>
+    <Service class="Banshee.Discs.Dvd.DvdService"/>
+  </Extension>
+
+</Addin>
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs.csproj b/src/Extensions/Banshee.Discs/Banshee.Discs.csproj
new file mode 100644
index 0000000..b4c5d91
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs.csproj
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.50727</ProductVersion>
+    <ProjectGuid>{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <UseParentDirectoryAsNamespace>true</UseParentDirectoryAsNamespace>
+    <AssemblyName>Banshee.Discs</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <Optimize>true</Optimize>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ReleaseVersion>1.3</ReleaseVersion>
+    <RootNamespace>Banshee.Discs</RootNamespace>
+    <AssemblyOriginatorKeyFile>.</AssemblyOriginatorKeyFile>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <WarningLevel>4</WarningLevel>
+    <Optimize>false</Optimize>
+    <OutputPath>..\..\..\bin</OutputPath>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Windows|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <PlatformTarget>x86</PlatformTarget>
+    <WarningLevel>4</WarningLevel>
+    <Optimize>false</Optimize>
+    <OutputPath>..\..\..\bin</OutputPath>
+  </PropertyGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\Core\Banshee.Core\Banshee.Core.csproj">
+      <Project>{2ADB831A-A050-47D0-B6B9-9C19D60233BB}</Project>
+      <Name>Banshee.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\Core\Banshee.Services\Banshee.Services.csproj">
+      <Project>{B28354F0-BA87-44E8-989F-B864A3C7C09F}</Project>
+      <Name>Banshee.Services</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\Libraries\MusicBrainz\MusicBrainz.csproj">
+      <Project>{C5DD02DE-4F0E-4DC0-ABC7-035A8217CD50}</Project>
+      <Name>MusicBrainz</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\Core\Banshee.ThickClient\Banshee.ThickClient.csproj">
+      <Project>{AC839523-7BDF-4AB6-8115-E17921B96EC6}</Project>
+      <Name>Banshee.ThickClient</Name>
+    </ProjectReference>
+    <Reference Include="atk-sharp">
+      <SpecificVersion>False</SpecificVersion>
+      <Package>gtk-sharp-2.0</Package>
+    </Reference>
+    <Reference Include="glib-sharp">
+      <SpecificVersion>False</SpecificVersion>
+      <Package>glib-sharp-2.0</Package>
+    </Reference>
+    <Reference Include="taglib-sharp">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\..\bin\taglib-sharp.dll</HintPath>
+      <Package>taglib-sharp</Package>
+    </Reference>
+    <Reference Include="Mono.Posix">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\..\bin\Mono.Posix.dll</HintPath>
+    </Reference>
+    <ProjectReference Include="..\..\Hyena\Hyena\Hyena.csproj">
+      <Project>{95374549-9553-4C1E-9D89-667755F90E12}</Project>
+      <Name>Hyena</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\Hyena\Hyena.Gui\Hyena.Gui.csproj">
+      <Project>{C856EFD8-E812-4E61-8B76-E3583D94C233}</Project>
+      <Name>Hyena.Gui</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="gtk-sharp">
+      <SpecificVersion>False</SpecificVersion>
+      <Package>gtk-sharp-2.0</Package>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Resources\ActiveSourceUI.xml">
+      <LogicalName>ActiveSourceUI.xml</LogicalName>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Resources\GlobalUI.xml">
+      <LogicalName>GlobalUI.xml</LogicalName>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Banshee.Discs.addin.xml">
+      <LogicalName>Banshee.Discs.addin.xml</LogicalName>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Resources\GlobalUIDvd.xml">
+      <LogicalName>GlobalUIDvd.xml</LogicalName>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Banshee.Discs.AudioCd\AudioCdDiscModel.cs" />
+    <Compile Include="Banshee.Discs.AudioCd\AudioCdDuplicator.cs" />
+    <Compile Include="Banshee.Discs.AudioCd\AudioCdRipper.cs" />
+    <Compile Include="Banshee.Discs.AudioCd\AudioCdService.cs" />
+    <Compile Include="Banshee.Discs.AudioCd\AudioCdSource.cs" />
+    <Compile Include="Banshee.Discs.AudioCd\AudioCdTrackInfo.cs" />
+    <Compile Include="Banshee.Discs.Dvd\DvdSource.cs" />
+    <Compile Include="Banshee.Discs\DiscService.cs" />
+    <Compile Include="Banshee.Discs\DiscSource.cs" />
+    <Compile Include="Banshee.Discs.Dvd\DvdService.cs" />
+    <Compile Include="Banshee.Discs.Dvd\DvdModel.cs" />
+    <Compile Include="Banshee.Discs\DiscModel.cs" />
+    <Compile Include="Banshee.Discs.Dvd\DvdTrackInfo.cs" />
+    <Compile Include="Banshee.Discs\DiscTrackInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ProjectExtensions>
+    <MonoDevelop>
+      <Properties>
+        <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="true" RelativeMakefileName="./Makefile.am">
+          <BuildFilesVar Sync="true" Name="SOURCES" />
+          <DeployFilesVar />
+          <ResourcesVar Sync="true" Name="RESOURCES" />
+          <OthersVar />
+          <GacRefVar />
+          <AsmRefVar />
+          <ProjectRefVar />
+        </MonoDevelop.Autotools.MakefileInfo>
+      </Properties>
+    </MonoDevelop>
+  </ProjectExtensions>
+  <ItemGroup>
+    <Folder Include="Banshee.Discs\" />
+    <Folder Include="Banshee.Discs.Dvd\" />
+  </ItemGroup>
+</Project>
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs/DiscModel.cs b/src/Extensions/Banshee.Discs/Banshee.Discs/DiscModel.cs
new file mode 100644
index 0000000..ee695bd
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs/DiscModel.cs
@@ -0,0 +1,77 @@
+//
+// DiscModel.cs
+//
+// Author:
+//   Alex Launi <alex.launi@gmail.com>
+//
+// Copyright 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Banshee.Collection;
+using Banshee.Hardware;
+
+namespace Banshee.Discs
+{
+    public class DiscModel : MemoryTrackListModel
+    {
+        public DiscModel (IDiscVolume volume)
+        {
+           Volume = volume;
+        }
+
+        public IDiscVolume Volume { get; protected set;}
+
+        public virtual string Title {
+            get {
+                return Volume.Name;
+            }
+        }
+
+        public virtual void LoadModelFromDisc ()
+        {
+        }
+
+        private ICdromDevice Drive {
+            get { return Volume == null ? null : (Volume.Parent as ICdromDevice); }
+        }
+
+        public bool LockDoor ()
+        {
+            ICdromDevice drive = Drive;
+            return drive != null ? drive.LockDoor () : false;
+        }
+
+        public bool UnlockDoor ()
+        {
+            ICdromDevice drive = Drive;
+            return drive != null ? drive.UnlockDoor () : false;
+        }
+
+        public bool IsDoorLocked {
+            get {
+                ICdromDevice drive = Drive;
+                return drive != null ? drive.IsDoorLocked : false;
+            }
+        }
+    }
+}
+
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs/DiscService.cs b/src/Extensions/Banshee.Discs/Banshee.Discs/DiscService.cs
new file mode 100644
index 0000000..c063aab
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs/DiscService.cs
@@ -0,0 +1,214 @@
+//
+// DiscService.cs
+//
+// Author:
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright (C) 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using Mono.Unix;
+
+using Hyena;
+
+using Banshee.ServiceStack;
+using Banshee.Configuration;
+using Banshee.Preferences;
+using Banshee.Hardware;
+using Banshee.Gui;
+
+namespace Banshee.Discs
+{
+    public abstract class DiscService : IExtensionService, IDisposable
+    {
+        private List<DeviceCommand> unhandled_device_commands;
+
+        public DiscService ()
+        {
+        }
+
+        public virtual void Initialize ()
+        {
+            Sources = new Dictionary<string, DiscSource> ();
+
+            lock (this) {
+                // This says Cdrom, but really it means Cdrom in the general Disc device sense.
+                foreach (ICdromDevice device in ServiceManager.HardwareManager.GetAllCdromDevices ()) {
+                    MapDiscDevice (device);
+                }
+
+                ServiceManager.HardwareManager.DeviceAdded += OnHardwareDeviceAdded;
+                ServiceManager.HardwareManager.DeviceRemoved += OnHardwareDeviceRemoved;
+                ServiceManager.HardwareManager.DeviceCommand += OnDeviceCommand;
+            }
+        }
+
+        public virtual void Dispose ()
+        {
+            lock (this) {
+                ServiceManager.HardwareManager.DeviceAdded -= OnHardwareDeviceAdded;
+                ServiceManager.HardwareManager.DeviceRemoved -= OnHardwareDeviceRemoved;
+                ServiceManager.HardwareManager.DeviceCommand -= OnDeviceCommand;
+
+                foreach (DiscSource source in Sources.Values) {
+                    ServiceManager.SourceManager.RemoveSource (source);
+                    source.Dispose ();
+                }
+
+                Sources.Clear ();
+                Sources = null;
+            }
+        }
+
+        protected Dictionary<string, DiscSource> Sources {
+            get; private set;
+        }
+
+        protected virtual void MapDiscDevice (ICdromDevice device)
+        {
+            lock (this) {
+                foreach (IVolume volume in device) {
+                    if (volume is IDiscVolume) {
+                        MapDiscVolume ((IDiscVolume) volume);
+                    }
+                }
+            }
+        }
+
+        protected abstract DiscSource GetDiscSource  (IDiscVolume volume);
+
+        protected virtual void MapDiscVolume (IDiscVolume volume)
+        {
+            DiscSource source = null;
+
+            lock (this) {
+                if (Sources.ContainsKey (volume.Uuid)) {
+                    Log.Debug ("Already mapped");
+                    return;
+                }
+
+                source =  GetDiscSource (volume);
+
+                if (source == null)
+                    return;
+
+                Sources.Add (volume.Uuid, source);
+                ServiceManager.SourceManager.AddSource (source);
+
+                // If there are any queued device commands, see if they are to be
+                // handled by this new volume (e.g. --device-activate-play=cdda://sr0/)
+                try {
+                    if (unhandled_device_commands != null) {
+                        foreach (DeviceCommand command in unhandled_device_commands) {
+                            if (DeviceCommandMatchesSource (source, command)) {
+                                HandleDeviceCommand (source, command.Action);
+                                unhandled_device_commands.Remove (command);
+                                if (unhandled_device_commands.Count == 0) {
+                                    unhandled_device_commands = null;
+                                }
+                                break;
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    Log.Exception (e);
+                }
+
+                Log.DebugFormat ("Mapping disc ({0})", volume.Uuid);
+            }
+        }
+
+        internal void UnmapDiscVolume (string uuid)
+        {
+            lock (this) {
+                if (Sources.ContainsKey (uuid)) {
+                    DiscSource source = Sources[uuid];
+                    source.StopPlayingDisc ();
+                    ServiceManager.SourceManager.RemoveSource (source);
+                    Sources.Remove (uuid);
+                    Log.DebugFormat ("Unmapping disc ({0})", uuid);
+                }
+            }
+        }
+
+        private void OnHardwareDeviceAdded (object o, DeviceAddedArgs args)
+        {
+            lock (this) {
+                if (args.Device is ICdromDevice) {
+                    MapDiscDevice ((ICdromDevice)args.Device);
+                } else if (args.Device is IDiscVolume) {
+                    MapDiscVolume ((IDiscVolume)args.Device);
+                }
+            }
+        }
+
+        private void OnHardwareDeviceRemoved (object o, DeviceRemovedArgs args)
+        {
+            lock (this) {
+                UnmapDiscVolume (args.DeviceUuid);
+            }
+        }
+
+#region DeviceCommand Handling
+
+        protected abstract bool DeviceCommandMatchesSource (DiscSource source, DeviceCommand command);
+
+        protected virtual void OnDeviceCommand (object o, DeviceCommand command)
+        {
+            lock (this) {
+                // Check to see if we have an already mapped disc volume that should
+                // handle this incoming command; if not, queue it for later discs
+                foreach (var source in Sources.Values) {
+                    if (DeviceCommandMatchesSource (source, command)) {
+                        HandleDeviceCommand (source, command.Action);
+                        return;
+                    }
+                }
+
+                if (unhandled_device_commands == null) {
+                    unhandled_device_commands = new List<DeviceCommand> ();
+                }
+                unhandled_device_commands.Add (command);
+            }
+        }
+
+        protected virtual void HandleDeviceCommand (DiscSource source, DeviceCommandAction action)
+        {
+            if ((action & DeviceCommandAction.Activate) != 0) {
+                ServiceManager.SourceManager.SetActiveSource (source);
+            }
+
+            if ((action & DeviceCommandAction.Play) != 0) {
+                ServiceManager.PlaybackController.NextSource = source;
+                if (!ServiceManager.PlayerEngine.IsPlaying ()) {
+                    ServiceManager.PlaybackController.Next ();
+                }
+            }
+        }
+#endregion
+        string IService.ServiceName {
+            get { return "DiscService"; }
+        }
+    }
+}
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs/DiscSource.cs b/src/Extensions/Banshee.Discs/Banshee.Discs/DiscSource.cs
new file mode 100644
index 0000000..1893770
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs/DiscSource.cs
@@ -0,0 +1,200 @@
+//
+// DiscSource.cs
+//
+// Author:
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+
+using Hyena;
+using Mono.Unix;
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+using Banshee.ServiceStack;
+using Banshee.Sources;
+
+using Selection = Hyena.Collections.Selection;
+
+namespace Banshee.Discs
+{
+    public abstract class DiscSource : Source, ITrackModelSource, IUnmapableSource,
+        IDisposable
+    {
+        public DiscSource (DiscService service, DiscModel model, string genericName, string name, int order)
+            : base (genericName, name, order)
+        {
+            Service = service;
+            Model = model;
+        }
+
+        protected DiscService Service { get; set; }
+        protected DiscModel Model { get; set; }
+
+        public DiscModel DiscModel {
+            get { return Model; }
+            protected set { Model = value; }
+        }
+
+        public bool DiscIsPlaying {
+            get {
+                DiscTrackInfo playing_track = ServiceManager.PlayerEngine.CurrentTrack as DiscTrackInfo;
+                return playing_track != null && playing_track.Model == Model;
+            }
+        }
+
+        public virtual void StopPlayingDisc ()
+        {
+            if (DiscIsPlaying) {
+                ServiceManager.PlayerEngine.Close (true);
+            }
+        }
+
+        public virtual void Dispose ()
+        {
+        }
+
+#region ITrackModelSource Implementation
+
+        public TrackListModel TrackModel {
+            get { return Model; }
+        }
+
+        public AlbumListModel AlbumModel {
+            get { return null; }
+        }
+
+        public ArtistListModel ArtistModel {
+            get { return null; }
+        }
+
+        public void Reload ()
+        {
+            Model.Reload ();
+        }
+
+        public void RemoveTracks (Selection selection)
+        {
+        }
+
+        public void DeleteTracks (Selection selection)
+        {
+        }
+
+        public abstract bool CanRepeat { get; }
+
+        public abstract bool CanShuffle { get; }
+
+        public bool CanAddTracks {
+            get { return false; }
+        }
+
+        public bool CanRemoveTracks {
+            get { return false; }
+        }
+
+        public bool CanDeleteTracks {
+            get { return false; }
+        }
+
+        public bool ConfirmRemoveTracks {
+            get { return false; }
+        }
+
+        public bool ShowBrowser {
+            get { return false; }
+        }
+
+        public bool HasDependencies {
+            get { return false; }
+        }
+
+        public bool Indexable {
+            get { return false; }
+        }
+#endregion
+
+#region IUnmapableSource Implementation
+
+        public bool Unmap ()
+        {
+            StopPlayingDisc ();
+
+            foreach (TrackInfo track in DiscModel) {
+                track.CanPlay = false;
+            }
+
+            OnUpdated ();
+
+            SourceMessage eject_message = new SourceMessage (this);
+            eject_message.FreezeNotify ();
+            eject_message.IsSpinning = true;
+            eject_message.CanClose = false;
+            eject_message.Text = Catalog.GetString (String.Format ("Ejecting {0}...", GenericName.ToLower ()));
+            eject_message.ThawNotify ();
+            PushMessage (eject_message);
+
+            ThreadPool.QueueUserWorkItem (delegate {
+                try {
+                    DiscModel.Volume.Unmount ();
+                    DiscModel.Volume.Eject ();
+
+                    ThreadAssist.ProxyToMain (delegate {
+                        Service.UnmapDiscVolume (DiscModel.Volume.Uuid);
+                        Dispose ();
+                    });
+                } catch (Exception e) {
+                    ThreadAssist.ProxyToMain (delegate {
+                        ClearMessages ();
+                        eject_message.IsSpinning = false;
+                        eject_message.SetIconName ("dialog-error");
+                        eject_message.Text = String.Format (Catalog.GetString ("Could not eject {0}: {1}"), GenericName.ToLower (), e.Message);
+                        PushMessage (eject_message);
+
+                        foreach (TrackInfo track in Model) {
+                            track.CanPlay = true;
+                        }
+                        OnUpdated ();
+                    });
+
+                    Log.Exception (e);
+                }
+            });
+
+            return true;
+        }
+
+        public bool CanUnmap {
+            get { return DiscModel != null ? !DiscModel.IsDoorLocked : true; }
+        }
+
+        public bool ConfirmBeforeUnmap {
+            get { return false; }
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Extensions/Banshee.Discs/Banshee.Discs/DiscTrackInfo.cs b/src/Extensions/Banshee.Discs/Banshee.Discs/DiscTrackInfo.cs
new file mode 100644
index 0000000..a15e020
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Banshee.Discs/DiscTrackInfo.cs
@@ -0,0 +1,44 @@
+//
+// DiscTrackInfo.cs
+//
+// Author:
+//   Alex Launi <alex.launi@canonical.com>
+//
+// Copyright 2010 Alex Launi
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+using Banshee.Collection;
+using Banshee.Collection.Database;
+
+namespace Banshee.Discs
+{
+    public abstract class DiscTrackInfo : DatabaseTrackInfo
+    {
+        public DiscTrackInfo (DiscModel model)
+        {
+            Model = model;
+        }
+
+        public DiscModel Model { get; private set; }
+    }
+}
+
diff --git a/src/Extensions/Banshee.Discs/Makefile.am b/src/Extensions/Banshee.Discs/Makefile.am
new file mode 100644
index 0000000..7263b9b
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Makefile.am
@@ -0,0 +1,29 @@
+ASSEMBLY = Banshee.Discs
+TARGET = library
+LINK = $(REF_EXTENSION_DISCS)
+INSTALL_DIR = $(EXTENSIONS_INSTALL_DIR)
+
+SOURCES =  \
+	Banshee.Discs.AudioCd/AudioCdDiscModel.cs \
+	Banshee.Discs.AudioCd/AudioCdDuplicator.cs \
+	Banshee.Discs.AudioCd/AudioCdRipper.cs \
+	Banshee.Discs.AudioCd/AudioCdService.cs \
+	Banshee.Discs.AudioCd/AudioCdSource.cs \
+	Banshee.Discs.AudioCd/AudioCdTrackInfo.cs \
+	Banshee.Discs.Dvd/DvdModel.cs \
+	Banshee.Discs.Dvd/DvdService.cs \
+	Banshee.Discs.Dvd/DvdSource.cs \
+	Banshee.Discs.Dvd/DvdTrackInfo.cs \
+	Banshee.Discs/DiscModel.cs \
+	Banshee.Discs/DiscService.cs \
+	Banshee.Discs/DiscSource.cs \
+	Banshee.Discs/DiscTrackInfo.cs
+
+RESOURCES =  \
+	Banshee.Discs.addin.xml \
+	Resources/ActiveSourceUI.xml \
+	Resources/GlobalUI.xml \
+	Resources/GlobalUIDvd.xml
+
+include $(top_srcdir)/build/build.mk
+
diff --git a/src/Extensions/Banshee.Discs/Resources/ActiveSourceUI.xml b/src/Extensions/Banshee.Discs/Resources/ActiveSourceUI.xml
new file mode 100644
index 0000000..038665e
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Resources/ActiveSourceUI.xml
@@ -0,0 +1,8 @@
+<ui>
+    <toolbar name="HeaderToolbar">
+        <placeholder name="SourceActions">
+            <toolitem action="DuplicateDiscAction"/>
+            <toolitem action="RipDiscAction"/>
+        </placeholder>
+    </toolbar>
+</ui>
diff --git a/src/Extensions/Banshee.Discs/Resources/GlobalUI.xml b/src/Extensions/Banshee.Discs/Resources/GlobalUI.xml
new file mode 100644
index 0000000..eec6c5f
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Resources/GlobalUI.xml
@@ -0,0 +1,9 @@
+<ui>
+    <popup name="AudioCdContextMenu">
+        <menuitem name="RipDisc" action="RipDiscAction"/>
+        <menuitem name="DuplicateDisc" action="DuplicateDiscAction"/>
+        <menuitem name="SourcePreferences" action="SourcePreferencesAction"/>
+        <separator/>
+        <menuitem name="UnmapSource" action="UnmapSourceAction"/>
+    </popup>
+</ui>
diff --git a/src/Extensions/Banshee.Discs/Resources/GlobalUIDvd.xml b/src/Extensions/Banshee.Discs/Resources/GlobalUIDvd.xml
new file mode 100644
index 0000000..f5bb25e
--- /dev/null
+++ b/src/Extensions/Banshee.Discs/Resources/GlobalUIDvd.xml
@@ -0,0 +1,7 @@
+<ui>
+    <popup name="DvdContextMenu">
+        <menuitem name="GoToMenu" action="GoToMenuAction"/>
+        <separator/>
+        <menuitem name="UnmapSource" action="UnmapSourceAction"/>
+    </popup>
+</ui>
\ No newline at end of file
diff --git a/src/Extensions/Banshee.Discs/ThemeIcons/16x16/actions/media-import-audio-cd.png b/src/Extensions/Banshee.Discs/ThemeIcons/16x16/actions/media-import-audio-cd.png
new file mode 100644
index 0000000..e637fea
Binary files /dev/null and b/src/Extensions/Banshee.Discs/ThemeIcons/16x16/actions/media-import-audio-cd.png differ
diff --git a/src/Extensions/Banshee.Discs/ThemeIcons/22x22/actions/media-import-audio-cd.png b/src/Extensions/Banshee.Discs/ThemeIcons/22x22/actions/media-import-audio-cd.png
new file mode 100644
index 0000000..ee0d170
Binary files /dev/null and b/src/Extensions/Banshee.Discs/ThemeIcons/22x22/actions/media-import-audio-cd.png differ
diff --git a/src/Extensions/Banshee.Fixup/Banshee.Fixup.csproj b/src/Extensions/Banshee.Fixup/Banshee.Fixup.csproj
index 608109f..d39a1e1 100644
--- a/src/Extensions/Banshee.Fixup/Banshee.Fixup.csproj
+++ b/src/Extensions/Banshee.Fixup/Banshee.Fixup.csproj
@@ -91,6 +91,7 @@
     <Compile Include="Banshee.Fixup\Solver.cs" />
     <Compile Include="Banshee.Fixup\Tests.cs" />
     <Compile Include="Banshee.Fixup\View.cs" />
+    <Compile Include="Banshee.Fixup\GenreDuplicateSolver.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
diff --git a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/FullscreenWindow.cs b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/FullscreenWindow.cs
index 2a48f4b..e1b661a 100644
--- a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/FullscreenWindow.cs
+++ b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/FullscreenWindow.cs
@@ -83,8 +83,6 @@ namespace Banshee.NowPlaying
                 case Gdk.Key.c:
                 case Gdk.Key.V:
                 case Gdk.Key.v:
-                case Gdk.Key.Return:
-                case Gdk.Key.KP_Enter:
                 case Gdk.Key.Tab:
                     if (controls == null || !controls.Visible) {
                         ShowControls ();
@@ -92,17 +90,51 @@ namespace Banshee.NowPlaying
                         HideControls ();
                     }
                     return true;
+                case Gdk.Key.Return:
+                case Gdk.Key.KP_Enter:
+                    if (ServiceManager.PlayerEngine.IsMenu) {
+                       ServiceManager.PlayerEngine.ActivateCurrentMenu ();
+                    } else if (controls == null || !controls.Visible) {
+                        ShowControls ();
+                    } else {
+                        HideControls ();
+                    }
+                    return true;
                 case Gdk.Key.Right:
                 case Gdk.Key.rightarrow:
-                    player.Position += mod ? fast_seek : slow_seek;
-                    ShowControls ();
+                case Gdk.Key.KP_Right:
+                    if (ServiceManager.PlayerEngine.IsMenu) {
+                       ServiceManager.PlayerEngine.NavigateToRightMenu ();
+                    } else {
+                        player.Position += mod ? fast_seek : slow_seek;
+                        ShowControls ();
+                    }
                     break;
                 case Gdk.Key.Left:
                 case Gdk.Key.leftarrow:
-                    player.Position -= mod ? fast_seek : slow_seek;
-                    ShowControls ();
+                case Gdk.Key.KP_Left:
+                    if (ServiceManager.PlayerEngine.IsMenu) {
+                       ServiceManager.PlayerEngine.NavigateToLeftMenu ();
+                    } else {
+                        player.Position -= mod ? fast_seek : slow_seek;
+                        ShowControls ();
+                    }
                     break;
-            }
+                case Gdk.Key.uparrow:
+                case Gdk.Key.Up:
+                case Gdk.Key.KP_Up:
+                    if (ServiceManager.PlayerEngine.IsMenu) {
+                       ServiceManager.PlayerEngine.NavigateToUpMenu ();
+                    }
+                    break;
+                case Gdk.Key.downarrow:
+                case Gdk.Key.Down:
+                case Gdk.Key.KP_Down:
+                    if (ServiceManager.PlayerEngine.IsMenu) {
+                       ServiceManager.PlayerEngine.NavigateToDownMenu ();
+                    }
+                    break;
+                }
 
             return base.OnKeyPressEvent (evnt);
         }
diff --git a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs
index 04721af..8a0288b 100644
--- a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs
+++ b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingContents.cs
@@ -29,11 +29,14 @@
 using System;
 using Gtk;
 
+using Banshee.Gui;
 using Banshee.Gui.Widgets;
+using Banshee.ServiceStack;
+using Hyena;
 
 namespace Banshee.NowPlaying
 {
-    public class NowPlayingContents : EventBox, IDisposable
+    public class NowPlayingContents : Table, IDisposable
     {
         private static Widget video_display;
 
@@ -44,46 +47,61 @@ namespace Banshee.NowPlaying
             }
         }
 
-        private Table table;
         private Widget substitute_audio_display;
         private bool video_display_initial_shown = false;
+        private EventBox video_event;
 
         private TrackInfoDisplay track_info_display;
 
-        public NowPlayingContents ()
+        public NowPlayingContents () : base (1, 1, false)
         {
-            VisibleWindow = false;
-            Child = table = new Table (1, 1, false) { Visible = true };
-
-            table.NoShowAll = true;
+            NoShowAll = true;
 
             CreateVideoDisplay ();
 
+            video_event = new EventBox ();
+            video_event.VisibleWindow = false;
+            video_event.CanFocus = true;
+            video_event.AboveChild = true;
+            video_event.Add (video_display);
+            video_event.Events |= Gdk.EventMask.PointerMotionMask |
+                    Gdk.EventMask.ButtonPressMask |
+                    Gdk.EventMask.ButtonMotionMask |
+                    Gdk.EventMask.KeyPressMask |
+                    Gdk.EventMask.KeyReleaseMask;
+
+            //TODO stop tracking mouse when no more in menu
+            video_event.ButtonPressEvent += OnButtonPress;
+            video_event.MotionNotifyEvent += OnMouseMove;
+            video_event.KeyPressEvent += OnKeyPress;
+
             IVideoDisplay ivideo_display = video_display as IVideoDisplay;
             if (ivideo_display != null) {
                 ivideo_display.IdleStateChanged += OnVideoDisplayIdleStateChanged;
             }
 
-            table.Attach (video_display, 0, 1, 0, 1,
+            Attach (video_event, 0, 1, 0, 1,
                 AttachOptions.Expand | AttachOptions.Fill,
                 AttachOptions.Expand | AttachOptions.Fill, 0, 0);
 
             track_info_display = new NowPlayingTrackInfoDisplay ();
-            table.Attach (track_info_display, 0, 1, 0, 1,
+            Attach (track_info_display, 0, 1, 0, 1,
                 AttachOptions.Expand | AttachOptions.Fill,
                 AttachOptions.Expand | AttachOptions.Fill, 0, 0);
+
+            video_event.ShowAll ();
         }
         
         internal void SetSubstituteAudioDisplay (Widget widget)
         {
             if (substitute_audio_display != null) {
-                table.Remove (substitute_audio_display);
+                Remove (substitute_audio_display);
             }
             
             substitute_audio_display = widget;
             
             if (widget != null) {
-	            table.Attach (widget, 0, 1, 0, 1,
+	            Attach (widget, 0, 1, 0, 1,
 	                AttachOptions.Expand | AttachOptions.Fill,
 	                AttachOptions.Expand | AttachOptions.Fill, 0, 0);
             }
@@ -93,6 +111,10 @@ namespace Banshee.NowPlaying
 
         public override void Dispose ()
         {
+            video_event.ButtonPressEvent -= OnButtonPress;
+            video_event.MotionNotifyEvent -= OnMouseMove;
+            video_event.KeyPressEvent -= OnKeyPress;
+
             IVideoDisplay ivideo_display = video_display as IVideoDisplay;
             if (ivideo_display != null) {
                 ivideo_display.IdleStateChanged -= OnVideoDisplayIdleStateChanged;
@@ -108,7 +130,7 @@ namespace Banshee.NowPlaying
         protected override void OnShown ()
         {
             base.OnShown ();
-
+            video_event.GrabFocus ();
             // Ugly hack to ensure the video window is mapped/realized
             if (!video_display_initial_shown) {
                 video_display_initial_shown = true;
@@ -148,5 +170,78 @@ namespace Banshee.NowPlaying
         {
             CheckIdle ();
         }
+
+        [GLib.ConnectBefore]
+        void OnMouseMove (object o, MotionNotifyEventArgs args)
+        {
+            if (ServiceManager.PlayerEngine.IsMenu) {
+                ServiceManager.PlayerEngine.NotifyMouseMove (args.Event.X, args.Event.Y);
+            }
+        }
+
+        [GLib.ConnectBefore]
+        void OnButtonPress (object o, ButtonPressEventArgs args)
+        {
+            switch (args.Event.Type) {
+                case Gdk.EventType.TwoButtonPress:
+                    var iaservice = ServiceManager.Get<InterfaceActionService> ();
+                    var action = iaservice.ViewActions["FullScreenAction"] as Gtk.ToggleAction;
+                    if (action != null && action.Sensitive) {
+                        action.Active = !action.Active;
+                    }
+                    break;
+                case Gdk.EventType.ButtonPress:
+                    video_event.GrabFocus ();
+                    if (ServiceManager.PlayerEngine.IsMenu) {
+                        ServiceManager.PlayerEngine.NotifyMouseButtonPressed ((int)args.Event.Button, args.Event.X, args.Event.Y);
+                    }
+                    break;
+                case Gdk.EventType.ButtonRelease:
+                    if (ServiceManager.PlayerEngine.IsMenu) {
+                        ServiceManager.PlayerEngine.NotifyMouseButtonReleased ((int)args.Event.Button, args.Event.X, args.Event.Y);
+                    }
+                    break;
+            }
+        }
+
+        [GLib.ConnectBefore]
+        void OnKeyPress (object o, KeyPressEventArgs args)
+        {
+            if (!ServiceManager.PlayerEngine.IsMenu) {
+                return;
+            }
+            switch (args.Event.Key) {
+                case Gdk.Key.leftarrow:
+                case Gdk.Key.KP_Left:
+                case Gdk.Key.Left:
+                    ServiceManager.PlayerEngine.NavigateToLeftMenu ();
+                    args.RetVal = true;
+                    break;
+                case Gdk.Key.rightarrow:
+                case Gdk.Key.KP_Right:
+                case Gdk.Key.Right:
+                    ServiceManager.PlayerEngine.NavigateToRightMenu ();
+                    args.RetVal = true;
+                    break;
+                case Gdk.Key.uparrow:
+                case Gdk.Key.KP_Up:
+                case Gdk.Key.Up:
+                    ServiceManager.PlayerEngine.NavigateToUpMenu ();
+                    args.RetVal = true;
+                    break;
+                case Gdk.Key.downarrow:
+                case Gdk.Key.KP_Down:
+                case Gdk.Key.Down:
+                    ServiceManager.PlayerEngine.NavigateToDownMenu ();
+                    args.RetVal = true;
+                    break;
+                case Gdk.Key.Break:
+                case Gdk.Key.KP_Enter:
+                case Gdk.Key.Return:
+                    ServiceManager.PlayerEngine.ActivateCurrentMenu ();
+                    args.RetVal = true;
+                    break;
+            }
+        }
     }
 }
diff --git a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingInterface.cs b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingInterface.cs
index b6ce7cb..a3a3a01 100644
--- a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingInterface.cs
+++ b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/NowPlayingInterface.cs
@@ -31,10 +31,10 @@ using System;
 using Mono.Unix;
 using Gtk;
 
-using Banshee.ServiceStack;
+using Banshee.Gui;
 using Banshee.PlatformServices;
+using Banshee.ServiceStack;
 using Banshee.Sources;
-using Banshee.Gui;
 using Banshee.Sources.Gui;
 
 namespace Banshee.NowPlaying
@@ -56,15 +56,7 @@ namespace Banshee.NowPlaying
             primary_window = service.PrimaryWindow;
 
             Contents = new NowPlayingContents ();
-            Contents.ButtonPressEvent += (o, a) => {
-                if (a.Event.Type == Gdk.EventType.TwoButtonPress) {
-                    var iaservice = ServiceManager.Get<InterfaceActionService> ();
-                    var action = iaservice.ViewActions["FullScreenAction"] as Gtk.ToggleAction;
-                    if (action != null && action.Sensitive) {
-                        action.Active = !action.Active;
-                    }
-                }
-            };
+
 
             // This is my really sweet hack - it's where the video widget
             // is sent when the source is not active. This keeps the video
diff --git a/src/Extensions/Makefile.am b/src/Extensions/Makefile.am
index d0c1509..18dc7ad 100644
--- a/src/Extensions/Makefile.am
+++ b/src/Extensions/Makefile.am
@@ -2,11 +2,11 @@ SUBDIRS = \
 	Banshee.AmazonMp3 \
 	Banshee.AmazonMp3.Store \
 	Banshee.Audiobook \
-	Banshee.AudioCd \
 	Banshee.BooScript \
 	Banshee.Bpm \
 	Banshee.CoverArt \
 	Banshee.Daap \
+	Banshee.Discs \
 	Banshee.Emusic \
 	Banshee.FileSystemQueue \
 	Banshee.InternetArchive \
diff --git a/src/Hyena b/src/Hyena
--- a/src/Hyena
+++ b/src/Hyena
@@ -1 +1 @@
-Subproject commit dd5de0988e14e0da67441044dea339eb7ac3a615
+Subproject commit dd5de0988e14e0da67441044dea339eb7ac3a615-dirty
