[banshee] Add initial support for DVD playing



commit 598335571a324b4a8a334d154ed30b1a53dfc184
Author: Bertrand Lorentz <bertrand lorentz gmail com>
Date:   Sun Sep 25 12:48:18 2011 +0200

    Add initial support for DVD playing
    
    Add the necessary bits to support DVD playing in both libbanshee and
    GStreamerSharp, including menu navigation.
    
    Rework the AudioCD extension into an OpticalDisc extension that supports
    both audio CDs and video DVDs.
    
    Adapt the NowPlaying extension to forward mouse and keyboard events to
    the player engine when appropriate, to implement menu navigation for
    DVDs.
    
    This is the result of a collaboration between Alex Launi and Olivier
    Dufour, with a few changes and cleanups by me.

 Banshee.sln                                        |   21 +-
 build/build.environment.mk                         |    2 +-
 configure.ac                                       |    2 +-
 data/addin-xml-strings.cs                          |   10 +-
 libbanshee/Makefile.am                             |    2 +
 libbanshee/banshee-player-dvd.c                    |  310 ++++++++++++++++
 libbanshee/banshee-player-dvd.h                    |   39 ++
 libbanshee/banshee-player-pipeline.c               |    4 +
 libbanshee/banshee-player-private.h                |    6 +
 libbanshee/banshee-player-vis.c                    |    4 +-
 libbanshee/banshee-player.c                        |    7 +
 libbanshee/libbanshee.cproj                        |    3 +-
 .../Banshee.GStreamer/PlayerEngine.cs              |   97 +++++-
 .../Banshee.GStreamerSharp.csproj                  |    1 +
 .../Banshee.GStreamerSharp/DvdManager.cs           |  291 +++++++++++++++
 .../Banshee.GStreamerSharp/PlayerEngine.cs         |   76 ++++-
 .../Banshee.GStreamerSharp/Visualization.cs        |   47 ++--
 src/Backends/Banshee.GStreamerSharp/Makefile.am    |    1 +
 .../Banshee.Gio/Banshee.Hardware.Gio/DiscVolume.cs |   20 +
 .../LowLevel/GioVolumeMetadataSource.cs            |   14 +
 .../Banshee.Hardware/IDiscVolume.cs                |    1 +
 .../Banshee.MediaEngine/NullPlayerEngine.cs        |   49 +++
 .../Banshee.MediaEngine/PlayerEngine.cs            |   20 +
 .../Banshee.MediaEngine/PlayerEngineService.cs     |   59 +++
 .../Banshee.AudioCd/AudioCdService.cs              |  384 --------------------
 src/Extensions/Banshee.AudioCd/Makefile.am         |   20 -
 .../Banshee.NowPlaying/FullscreenWindow.cs         |   46 ++-
 .../Banshee.NowPlaying/NowPlayingContents.cs       |  119 ++++++-
 .../Banshee.NowPlaying/NowPlayingInterface.cs      |   13 +-
 .../AudioCdDiscModel.cs                            |   44 +--
 .../AudioCdDuplicator.cs                           |    2 +-
 .../Banshee.OpticalDisc.AudioCd}/AudioCdRipper.cs  |   18 +-
 .../Banshee.OpticalDisc.AudioCd/AudioCdService.cs  |  267 ++++++++++++++
 .../Banshee.OpticalDisc.AudioCd}/AudioCdSource.cs  |  221 +++---------
 .../AudioCdTrackInfo.cs                            |   15 +-
 .../Banshee.OpticalDisc.Dvd/DvdModel.cs            |   48 +++
 .../Banshee.OpticalDisc.Dvd/DvdService.cs          |  132 +++++++
 .../Banshee.OpticalDisc.Dvd/DvdSource.cs           |  102 ++++++
 .../Banshee.OpticalDisc.Dvd/DvdTrackInfo.cs        |   49 +++
 .../Banshee.OpticalDisc.addin.xml}                 |    9 +-
 .../Banshee.OpticalDisc.csproj}                    |   48 ++-
 .../Banshee.OpticalDisc/DiscModel.cs               |   77 ++++
 .../Banshee.OpticalDisc/DiscService.cs             |  218 +++++++++++
 .../Banshee.OpticalDisc/DiscSource.cs              |  199 ++++++++++
 .../Banshee.OpticalDisc/DiscTrackInfo.cs           |   44 +++
 src/Extensions/Banshee.OpticalDisc/Makefile.am     |   29 ++
 .../Resources/ActiveSourceUI.xml                   |    0
 .../Resources/GlobalUI.xml                         |    0
 .../Resources/GlobalUI_Dvd.xml}                    |    6 +-
 .../16x16/actions/media-import-audio-cd.png        |  Bin 867 -> 867 bytes
 .../22x22/actions/media-import-audio-cd.png        |  Bin 1480 -> 1480 bytes
 src/Extensions/Makefile.am                         |    2 +-
 52 files changed, 2466 insertions(+), 732 deletions(-)
---
diff --git a/Banshee.sln b/Banshee.sln
index 45a8030..e4a4439 100644
--- a/Banshee.sln
+++ b/Banshee.sln
@@ -96,8 +96,6 @@ 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}"
-EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.LibraryWatcher", "src\Extensions\Banshee.LibraryWatcher\Banshee.LibraryWatcher.csproj", "{49CA3F27-0BB6-428d-8B3A-20232493652E}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.MiniMode", "src\Extensions\Banshee.MiniMode\Banshee.MiniMode.csproj", "{FCC1AE87-E10B-4B47-8ADE-D5F447E48518}"
@@ -144,6 +142,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.SoundMenu", "src\Ex
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.UbuntuOneMusicStore", "src\Extensions\Banshee.UbuntuOneMusicStore\Banshee.UbuntuOneMusicStore.csproj", "{3B855EBC-8E55-48DF-816B-B241BE45EBD8}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Banshee.OpticalDisc", "src\Extensions\Banshee.OpticalDisc\Banshee.OpticalDisc.csproj", "{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}"
+EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{4F47D6F1-4047-4A89-AE85-3AE5EF9F2961}"
 	ProjectSection(SolutionItems) = postProject
 	EndProjectSection
@@ -209,9 +209,6 @@ Global
 		{10A5B2EE-C9F0-4B7B-B79B-87B7DA9C1DC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{10A5B2EE-C9F0-4B7B-B79B-87B7DA9C1DC1}.Windows|Any CPU.ActiveCfg = Windows|Any CPU
 		{10A5B2EE-C9F0-4B7B-B79B-87B7DA9C1DC1}.Windows|Any CPU.Build.0 = Windows|Any CPU
-		{FDB90084-A1E6-4497-8047-52800A4AAB28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{FDB90084-A1E6-4497-8047-52800A4AAB28}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{FDB90084-A1E6-4497-8047-52800A4AAB28}.Windows|Any CPU.ActiveCfg = Windows|Any CPU
 		{12984BDF-C565-4452-AD47-79BD3C440E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{12984BDF-C565-4452-AD47-79BD3C440E28}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{12984BDF-C565-4452-AD47-79BD3C440E28}.Windows|Any CPU.ActiveCfg = Windows|Any CPU
@@ -458,7 +455,14 @@ Global
 		{EE91126B-5500-4719-9FAA-36CBAC95DEE3}.Windows|Any CPU.ActiveCfg = Windows|Any CPU
 		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Debug|x86.Build.0 = Debug|Any CPU
+		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Release|Any CPU.ActiveCfg = Debug|Any CPU
+		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Release|Any CPU.Build.0 = Debug|Any CPU
+		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Release|x86.ActiveCfg = Debug|Any CPU
+		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Release|x86.Build.0 = Debug|Any CPU
 		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Windows|Any CPU.ActiveCfg = Windows|Any CPU
+		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}.Windows|Any CPU.Build.0 = Windows|Any CPU
 		{FC44E7C6-D625-4FF1-BA85-F074871423EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{FC44E7C6-D625-4FF1-BA85-F074871423EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{FC44E7C6-D625-4FF1-BA85-F074871423EF}.Release|Any CPU.ActiveCfg = Debug|Any CPU
@@ -468,6 +472,9 @@ Global
 		{FCC1AE87-E10B-4B47-8ADE-D5F447E48518}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{FCC1AE87-E10B-4B47-8ADE-D5F447E48518}.Windows|Any CPU.ActiveCfg = Windows|Any CPU
 		{FCC1AE87-E10B-4B47-8ADE-D5F447E48518}.Windows|Any CPU.Build.0 = Windows|Any CPU
+		{FDB90084-A1E6-4497-8047-52800A4AAB28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FDB90084-A1E6-4497-8047-52800A4AAB28}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FDB90084-A1E6-4497-8047-52800A4AAB28}.Windows|Any CPU.ActiveCfg = Windows|Any CPU
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
 		{EB1FDF3F-048C-4010-80F5-D936A312580F} = {E6AD3714-5EA3-49D9-BA8D-12C69B2B8067}
@@ -509,7 +516,6 @@ Global
 		{02FD8195-9796-4AF5-A9D2-D310721963F4} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
 		{BF5D1722-269B-452E-B577-AEBA0CB894BA} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
 		{16FB0D3A-53FA-4B8E-B02B-4AF66E87829A} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
-		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
 		{49CA3F27-0BB6-428d-8B3A-20232493652E} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
 		{FCC1AE87-E10B-4B47-8ADE-D5F447E48518} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
 		{621F56FD-2E25-45E1-A30A-59F98A28A9E7} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
@@ -533,6 +539,7 @@ Global
 		{AF8A9C6D-2188-413D-8EB8-C5E242BD68AC} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
 		{EE91126B-5500-4719-9FAA-36CBAC95DEE3} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
 		{3B855EBC-8E55-48DF-816B-B241BE45EBD8} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
+		{F38B53BA-8F85-4DC6-9B94-029C1CF96F24} = {4DD1DE63-F20B-4FC3-8FDA-F0BDF4183722}
 		{95374549-9553-4C1E-9D89-667755F90E12} = {4F47D6F1-4047-4A89-AE85-3AE5EF9F2961}
 		{95374549-9553-4C1E-9D89-667755F90E13} = {4F47D6F1-4047-4A89-AE85-3AE5EF9F2961}
 		{C856EFD8-E812-4E61-8B76-E3583D94C233} = {4F47D6F1-4047-4A89-AE85-3AE5EF9F2961}
@@ -579,7 +586,7 @@ Global
 		$6.scope = text/x-csharp
 		$0.StandardHeader = $7
 		$7.Text = @\n${FileName}\n\nAuthor:\n  ${AuthorName} <${AuthorEmail}>\n\nCopyright ${Year} ${CopyrightHolder}\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIA
 BILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.
-		$7.inheritsSet = Apache2License
+		$7.IncludeInNewFiles = True
 		version = 1.3
 		outputpath = .
 		name = Banshee
diff --git a/build/build.environment.mk b/build/build.environment.mk
index 0ba40dc..9ccfa9c 100644
--- a/build/build.environment.mk
+++ b/build/build.environment.mk
@@ -125,7 +125,6 @@ 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)
@@ -144,6 +143,7 @@ REF_EXTENSION_MPRIS = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_MULTIMEDIAKEYS = $(LINK_BANSHEE_SERVICES_DEPS)
 REF_EXTENSION_FIXUP = $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_MUSICBRAINZ_DEPS) $(LINK_MIGO_DEPS)
 REF_EXTENSION_NOTIFICATIONAREA = $(LINK_BANSHEE_THICKCLIENT_DEPS)
+REF_EXTENSION_OPTICALDISC = $(LINK_BANSHEE_THICKCLIENT_DEPS) $(LINK_MUSICBRAINZ_DEPS)
 REF_EXTENSION_PLAYER_MIGRATION = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 REF_EXTENSION_PLAYQUEUE = $(LINK_BANSHEE_THICKCLIENT_DEPS)
 LINK_EXTENSION_PLAYQUEUE = -r:$(DIR_BIN)/Banshee.PlayQueue.dll
diff --git a/configure.ac b/configure.ac
index 5ff9787..98e4de6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -317,7 +317,6 @@ 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
@@ -338,6 +337,7 @@ src/Extensions/Banshee.Mpris/Makefile
 src/Extensions/Banshee.MultimediaKeys/Makefile
 src/Extensions/Banshee.NotificationArea/Makefile
 src/Extensions/Banshee.NowPlaying/Makefile
+src/Extensions/Banshee.OpticalDisc/Makefile
 src/Extensions/Banshee.PlayQueue/Makefile
 src/Extensions/Banshee.PlayerMigration/Makefile
 src/Extensions/Banshee.Podcasting/Makefile
diff --git a/data/addin-xml-strings.cs b/data/addin-xml-strings.cs
index 73573cc..dd4da0e 100644
--- a/data/addin-xml-strings.cs
+++ b/data/addin-xml-strings.cs
@@ -39,11 +39,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.");
@@ -134,6 +129,11 @@ internal static class AddinXmlStringCatalog
         Catalog.GetString (@"Display an icon in the notification area for controlling Banshee.");
         Catalog.GetString (@"Utilities");
 
+        // ../src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.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.PlayerMigration/Banshee.PlayerMigration.addin.xml
         Catalog.GetString (@"Importers for Amarok, Rhythmbox and iTunes");
         Catalog.GetString (@"Import your library from Amarok, Rhythmbox or iTunes.");
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..844b386
--- /dev/null
+++ b/libbanshee/banshee-player-dvd.c
@@ -0,0 +1,310 @@
+//
+// 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);
+}
+
+// ---------------------------------------------------------------------------
+// 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);
+    }
+}
+
+void
+_bp_dvd_elements_process_message (BansheePlayer *player, GstMessage *message)
+{
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+    g_return_if_fail (message != NULL);
+
+    player->is_menu = FALSE;
+    // Get available command to know if player is in menu
+    GstQuery *query = gst_navigation_query_new_commands();
+
+    guint n_cmds, i;
+    //execute query over playbin or navigation ?
+    if (gst_element_query (player->playbin, query)
+        && gst_navigation_query_parse_commands_length (query, &n_cmds)) {
+        gst_query_unref (query);
+        return;
+    }
+
+    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);
+}
+
+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;
+    }
+
+    // 6 is the size of "dvd://"
+    // so we skip this part to only get the device
+    new_dvd_device = uri + 6;
+    
+    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 (%s)", player->dvd_device);
+        return FALSE;
+    }
+
+    if (strcmp (new_dvd_device, player->dvd_device) == 0) {
+        bp_debug2 ("bp_dvd: Already playing device (%s)", player->dvd_device);
+        return TRUE;
+    }
+
+    // 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;
+}
+
+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;
+}
+
+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);
+    }
+}
+
+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);
+    }
+}
+
+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..d7f0ec5
--- /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);
+void      _bp_dvd_elements_process_message  (BansheePlayer *player, GstMessage *message);
+
+#endif /* _BANSHEE_PLAYER_DVD_H */
diff --git a/libbanshee/banshee-player-pipeline.c b/libbanshee/banshee-player-pipeline.c
index e25f55f..dcfb4ed 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"
@@ -213,6 +214,7 @@ bp_pipeline_bus_callback (GstBus *bus, GstMessage *message, gpointer userdata)
                 bp_next_track_starting (player);
             }
             _bp_missing_elements_process_message (player, message);
+            _bp_dvd_elements_process_message (player, message);
             break;
         }
 
@@ -413,7 +415,9 @@ _bp_pipeline_construct (BansheePlayer *player)
 
     // 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 24eb5eb..9fed356 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>
@@ -147,6 +148,7 @@ struct BansheePlayer {
     GstState target_state;
     gboolean buffering;
     gchar *cdda_device;
+    gchar *dvd_device;
     gboolean in_gapless_transition;
     gboolean audiosink_has_volume;
     
@@ -195,6 +197,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 5c7d5fe..18c71c7 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 0ce8ed1..c655a72 100644
--- a/libbanshee/banshee-player.c
+++ b/libbanshee/banshee-player.c
@@ -31,6 +31,7 @@
 #include "banshee-player-private.h"
 #include "banshee-player-pipeline.h"
 #include "banshee-player-cdda.h"
+#include "banshee-player-dvd.h"
 #include "banshee-player-missing-elements.h"
 #include "banshee-player-replaygain.h"
 
@@ -110,6 +111,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 +160,8 @@ bp_open (BansheePlayer *player, const gchar *uri, gboolean maybe_video)
     // 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/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
index 315b9ac..657e9bb 100644
--- a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
@@ -302,6 +302,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)
         {
             StopIterating ();
@@ -675,7 +730,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; }
         }
@@ -750,6 +805,10 @@ namespace Banshee.GStreamer
             }
         }
 
+        public override bool InDvdMenu {
+            get { return bp_dvd_is_menu (handle); }
+        }
+
 #region ISupportClutter
 
         private IntPtr clutter_video_sink;
@@ -1025,5 +1084,41 @@ namespace Banshee.GStreamer
 
         [DllImport ("libbanshee.dll")]
         private static extern string gstreamer_version_string ();
+
+        [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.csproj b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
index 772d044..e2f8ed5 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp.csproj
@@ -77,6 +77,7 @@
     <Compile Include="Banshee.GStreamerSharp\Transcoder.cs" />
     <Compile Include="Banshee.GStreamerSharp\VideoManager.cs" />
     <Compile Include="Banshee.GStreamerSharp\Visualization.cs" />
+    <Compile Include="Banshee.GStreamerSharp\DvdManager.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 </Project>
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/DvdManager.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/DvdManager.cs
new file mode 100644
index 0000000..3fb9383
--- /dev/null
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/DvdManager.cs
@@ -0,0 +1,291 @@
+//
+// DvdManager.cs
+//
+// Author:
+//   Olivier Dufour <olivier duff gmail com>
+//
+// Copyright 2011 Olivier Dufour
+//
+// 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 Gst;
+using Gst.BasePlugins;
+using Gst.Cdda;
+using Gst.Interfaces;
+
+using Hyena;
+
+namespace Banshee.GStreamerSharp
+{
+    public class DvdManager
+    {
+        public DvdManager (PlayBin2 playbin)
+        {
+            if (playbin != null) {
+                playbin.AddNotification ("source", OnSourceChanged);
+            }
+        }
+
+        Navigation Navigation {
+            get; set;
+        }
+
+        public string Device {
+            get; set;
+        }
+
+        public bool InDvdMenu {
+            get; set;
+        }
+
+        private Element GetDvdSource (Element playbin)
+        {
+            Element source = null;
+
+            if (playbin == null) {
+                return null;
+            }
+
+            source = playbin ["source"] as Element;
+
+            return source;
+        }
+
+        private void OnSourceChanged (object o, Gst.GLib.NotifyArgs args)
+        {
+            if (Device == null) {
+                return;
+            }
+
+            var dvd_src = GetDvdSource (o as PlayBin2);
+            if (dvd_src == null) {
+                return;
+            }
+
+            // dvd source elements should always have this property
+            if (dvd_src.HasProperty ("device")) {
+                Log.DebugFormat ("dvd: setting device property on source ({0})", Device);
+                dvd_src ["device"] = Device;
+            }
+        }
+
+        public bool HandleURI (PlayBin2 playbin, string uri)
+        {
+            // Processes URIs like dvd://<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 on GstCddaBaseSrc
+            // through the notify::source signal on playbin
+
+            string new_dvd_device;
+
+            if (playbin == null || String.IsNullOrEmpty (uri) || !uri.StartsWith ("dvd://")) {
+                // Something is hosed or the URI isn't actually DVD
+                if (Device != null) {
+                    Log.WarningFormat ("dvd: finished using device ({0})", Device);
+                    Device = null;
+                }
+                return false;
+            }
+
+            // 6 is the size of "dvd://"
+            // so we skip this part to only get the device
+            new_dvd_device = uri.Substring (6);
+
+            if (Device == null) {
+                // If we weren't already playing from a DVD, cache the
+                // device and allow playbin to begin playing it
+                Device = new_dvd_device;
+                Log.DebugFormat ("dvd: storing device node for fast seeks ({0})", Device);
+                return false;
+            }
+
+            if (new_dvd_device == Device) {
+                Log.DebugFormat ("dvd: Already playing device ({0})", Device);
+
+                return true;
+            }
+
+            // We were already playing some CD, but switched to a different device node,
+            // so unset and re-cache the new device node and allow playbin to do its thing
+            Log.DebugFormat ("dvd: switching devices for DVD playback (from {0}, to {1})", Device, new_dvd_device);
+            Device = new_dvd_device;
+
+            return false;
+        }
+
+        public void HandleCommandsChanged (PlayBin2 playbin)
+        {
+            InDvdMenu = false;
+            // Get available command to know if player is in menu
+            Gst.Query query = NavigationQuery.NewCommands ();
+
+            NavigationCommand[] cmds;
+            //execute query over playbin or navigation ?
+            if (!playbin.Query (query) || !NavigationQuery.ParseCommands (query, out cmds)) {
+                return;
+            }
+            foreach (NavigationCommand cmd in cmds) {
+                switch (cmd) {
+                        case NavigationCommand.Activate:
+                        case NavigationCommand.Left:
+                        case NavigationCommand.Right:
+                        case NavigationCommand.Up:
+                        case NavigationCommand.Down:
+                            InDvdMenu = true;
+                            break;
+                        default:
+                            break;
+                }
+            }
+        }
+
+        public void FindNavigation (PlayBin2 playbin)
+        {
+            Element video_sink = null;
+            Element navigation = null;
+            Navigation previous_navigation;
+
+            previous_navigation = Navigation;
+            video_sink = playbin ["video-sink"] as Element;
+
+            if (video_sink == null) {
+                Navigation = null;
+                if (previous_navigation != null) {
+                    previous_navigation = null;
+                }
+            }
+
+            navigation = (video_sink is Bin)
+                ? ((Bin)video_sink).GetByInterface (typeof (NavigationAdapter))
+                : video_sink;
+
+            Navigation = navigation as Navigation;
+
+        }
+
+        public void NotifyMouseMove (PlayBin2 playbin, double x, double y)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendMouseEvent ("mouse-move", 0, x, y);
+            }
+        }
+
+        public void NotifyMouseButtonPressed (PlayBin2 playbin, int button, double x, double y)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendMouseEvent ("mouse-button-press", button, x, y);
+            }
+        }
+
+        public void NotifyMouseButtonReleased (PlayBin2 playbin, int button, double x, double y)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendMouseEvent ("mouse-button-release", button, x, y);
+            }
+        }
+
+        public void NavigateToLeftMenu (PlayBin2 playbin)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendCommand (NavigationCommand.Left);
+            }
+        }
+
+        public void NavigateToRightMenu (PlayBin2 playbin)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendCommand (NavigationCommand.Right);
+            }
+        }
+
+        public void NavigateToUpMenu (PlayBin2 playbin)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendCommand (NavigationCommand.Up);
+            }
+        }
+
+        public void NavigateToDownMenu (PlayBin2 playbin)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendCommand (NavigationCommand.Down);
+            }
+        }
+
+        public void NavigateToMenu (PlayBin2 playbin)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendCommand (NavigationCommand.DvdMenu);
+            }
+        }
+
+        public void ActivateCurrentMenu (PlayBin2 playbin)
+        {
+            if (Navigation == null) {
+                FindNavigation (playbin);
+            }
+            if (Navigation != null) {
+                Navigation.SendCommand (NavigationCommand.Activate);
+            }
+        }
+
+        public void GoToNextChapter (PlayBin2 playbin)
+        {
+            long index;
+            Format format = Util.FormatGetByNick ("chapter");
+            playbin.QueryPosition (ref format, out index);
+            playbin.Seek (1.0, format, SeekFlags.Flush, SeekType.Set, index + 1, SeekType.None, 0l);
+        }
+
+        public void GoToPreviousChapter (PlayBin2 playbin)
+        {
+            long index;
+            Format format = Util.FormatGetByNick ("chapter");
+            playbin.QueryPosition (ref format, out index);
+            playbin.Seek (1.0, format, SeekFlags.Flush, SeekType.Set, index - 1, SeekType.None, 0l);
+        }
+    }
+}
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
index a12d523..245a66c 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
@@ -39,6 +39,7 @@ using Gst;
 using Gst.PbUtils;
 using Gst.BasePlugins;
 using Gst.CorePlugins;
+using Gst.Interfaces;
 
 using Hyena;
 using Hyena.Data;
@@ -309,6 +310,7 @@ namespace Banshee.GStreamerSharp
         ManualResetEvent next_track_set;
         CddaManager cdda_manager;
         VideoManager video_manager = null;
+        DvdManager dvd_manager = null;
         Visualization visualization;
 
         public PlayerEngine ()
@@ -356,11 +358,13 @@ namespace Banshee.GStreamerSharp
             playbin.AboutToFinish += OnAboutToFinish;
 
             cdda_manager = new CddaManager (playbin);
+            dvd_manager = new DvdManager (playbin);
             // FIXME: Disable video stuff until GLib# 3 is used instead of the sopy bundled in GStreamerSharp
             //video_manager = new VideoManager (playbin);
             //video_manager.PrepareWindow += OnVideoPrepareWindow;
             //video_manager.Initialize ();
 
+            dvd_manager.FindNavigation (playbin);
             OnStateChanged (PlayerState.Ready);
         }
 
@@ -505,6 +509,8 @@ namespace Banshee.GStreamerSharp
                         Install.InstallPlugins (missing_details.ToArray (), install_context, OnInstallPluginsReturn);
                     } else if (msg.Src == playbin && msg.Structure.Name == "playbin2-stream-changed") {
                         HandleStreamChanged ();
+                    } else if (NavigationMessage.MessageGetType (msg) == NavigationMessageType.CommandsChanged) {
+                        dvd_manager.HandleCommandsChanged (playbin);
                     }
                     break;
                 case MessageType.Application:
@@ -663,6 +669,8 @@ namespace Banshee.GStreamerSharp
         {
             if (cdda_manager.HandleURI (playbin, uri.AbsoluteUri)) {
                 return;
+            } else if (dvd_manager.HandleURI (playbin, uri.AbsoluteUri)) {
+                return;
             } else if (playbin == null) {
                 throw new ApplicationException ("Could not open resource");
             }
@@ -774,7 +782,7 @@ namespace Banshee.GStreamerSharp
             }
         }
 
-        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; }
         }
@@ -892,6 +900,71 @@ namespace Banshee.GStreamerSharp
             get { return new SafeUri (playbin.Suburi); }
         }
 
+#region DVD support
+
+        public override void NotifyMouseMove (double x, double y)
+        {
+            dvd_manager.NotifyMouseMove (playbin, x, y);
+        }
+
+        public override void NotifyMouseButtonPressed (int button, double x, double y)
+        {
+            dvd_manager.NotifyMouseButtonPressed (playbin, button, x, y);
+        }
+
+        public override void NotifyMouseButtonReleased (int button, double x, double y)
+        {
+            dvd_manager.NotifyMouseButtonReleased (playbin, button, x, y);
+        }
+
+        public override void NavigateToLeftMenu ()
+        {
+            dvd_manager.NavigateToLeftMenu (playbin);
+        }
+
+        public override void NavigateToRightMenu ()
+        {
+            dvd_manager.NavigateToRightMenu (playbin);
+        }
+
+        public override void NavigateToUpMenu ()
+        {
+            dvd_manager.NavigateToUpMenu (playbin);
+        }
+
+        public override void NavigateToDownMenu ()
+        {
+            dvd_manager.NavigateToDownMenu (playbin);
+        }
+
+        public override void NavigateToMenu ()
+        {
+            dvd_manager.NavigateToMenu (playbin);
+        }
+
+        public override void ActivateCurrentMenu ()
+        {
+            dvd_manager.ActivateCurrentMenu (playbin);
+        }
+
+        public override void GoToNextChapter ()
+        {
+            dvd_manager.GoToNextChapter (playbin);
+        }
+
+        public override void GoToPreviousChapter ()
+        {
+            dvd_manager.GoToPreviousChapter (playbin);
+        }
+
+        public override bool InDvdMenu {
+            get { return dvd_manager.InDvdMenu; }
+        }
+
+#endregion
+
+#region Preferences
+
         private PreferenceBase replaygain_preference;
 
         private void InstallPreferences ()
@@ -926,5 +999,6 @@ namespace Banshee.GStreamerSharp
             "If ReplayGain data is present on tracks when playing, allow volume scaling"
         );
 
+#endregion
     }
 }
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/Visualization.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/Visualization.cs
index f55cb7e..d413918 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/Visualization.cs
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/Visualization.cs
@@ -25,13 +25,13 @@
 // 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.Runtime.InteropServices;
-
+using System.Runtime.InteropServices;
+
 using Gst;
 using Gst.Base;
-using Gst.CorePlugins;
+using Gst.CorePlugins;
 
 using Hyena;
 
@@ -81,9 +81,9 @@ namespace Banshee.GStreamerSharp
         public Visualization (Bin audiobin, Pad teepad)
         {
             // The basic pipeline we're constructing is:
-            // .audiotee ! queue ! audioresample ! audioconvert ! fakesink
+            // .audiotee ! queue ! audioresample ! audioconvert ! fakesink
 
-            Element converter, resampler;
+            Element converter, resampler;
             Queue audiosinkqueue;
             Pad pad;
 
@@ -112,9 +112,9 @@ namespace Banshee.GStreamerSharp
         
             // Keep around the 5 most recent seconds of audio so that when resuming
             // visualization we have something to show right away.
-            audiosinkqueue.Leaky = Queue.LeakyType.Downstream;
-            audiosinkqueue.MaxSizeBuffers = 0;
-            audiosinkqueue.MaxSizeBytes = 0;
+            audiosinkqueue.Leaky = Queue.LeakyType.Downstream;
+            audiosinkqueue.MaxSizeBuffers = 0;
+            audiosinkqueue.MaxSizeBytes = 0;
             audiosinkqueue.MaxSizeTime = Clock.Second * 5;
             
             fakesink.Handoff += PCMHandoff;
@@ -151,12 +151,12 @@ namespace Banshee.GStreamerSharp
             // Disable the pipeline till we hear otherwise from managed land.
             Blocked = true;
         }
-
-        ~Visualization ()
-        {
-            if (vis_fft != IntPtr.Zero)
-                gst_fft_f32_free (vis_fft);
-        }
+
+        ~Visualization ()
+        {
+            if (vis_fft != IntPtr.Zero)
+                gst_fft_f32_free (vis_fft);
+        }
 
         public bool Active
         {
@@ -313,6 +313,7 @@ namespace Banshee.GStreamerSharp
                 case EventType.FlushStop:
                 case EventType.Seek:
                 case EventType.NewSegment:
+                case EventType.CustomDownstream:
                     vis_thawing = true;
                 break;
             }
@@ -321,13 +322,15 @@ namespace Banshee.GStreamerSharp
                 return true;
         
             switch (padEvent.Type) {
-            case EventType.Eos:
-                Blocked = false;
-                break;
-        
-            case EventType.NewSegment:
-                Blocked = true;
-                break;
+                case EventType.Eos:
+                case EventType.CustomDownstreamOob:
+                    Blocked = false;
+                    break;
+            
+                case EventType.NewSegment:
+                case EventType.CustomDownstream:
+                    Blocked = true;
+                    break;
             }
         
             return true;
diff --git a/src/Backends/Banshee.GStreamerSharp/Makefile.am b/src/Backends/Banshee.GStreamerSharp/Makefile.am
index 0fd65fb..a919d06 100644
--- a/src/Backends/Banshee.GStreamerSharp/Makefile.am
+++ b/src/Backends/Banshee.GStreamerSharp/Makefile.am
@@ -7,6 +7,7 @@ SOURCES =  \
 	Banshee.GStreamerSharp/AudioCdRipper.cs \
 	Banshee.GStreamerSharp/BpmDetector.cs \
 	Banshee.GStreamerSharp/CddaManager.cs \
+	Banshee.GStreamerSharp/DvdManager.cs \
 	Banshee.GStreamerSharp/PlayerEngine.cs \
 	Banshee.GStreamerSharp/Transcoder.cs \
 	Banshee.GStreamerSharp/VideoManager.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..63ce760 100644
--- a/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/DiscVolume.cs
+++ b/src/Backends/Banshee.Gio/Banshee.Hardware.Gio/DiscVolume.cs
@@ -26,6 +26,7 @@
 
 #if ENABLE_GIO_HARDWARE
 using System;
+using System.Linq;
 
 using Banshee.Hardware;
 
@@ -33,6 +34,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 +46,15 @@ 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/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 abecf9e..780fe07 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 InDvdMenu {
+            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 91162bb..81a5879 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
@@ -335,5 +335,25 @@ namespace Banshee.MediaEngine
             set;
             get;
         }
+
+        public abstract bool InDvdMenu {
+            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 31d8308..57331eb 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
@@ -565,6 +565,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) {
@@ -670,6 +725,10 @@ namespace Banshee.MediaEngine
             get { return active_engine.SubtitleUri; }
         }
 
+        public bool InDvdMenu {
+            get { return active_engine.InDvdMenu; }
+        }
+
         public VideoDisplayContextType VideoDisplayContextType {
             get { return active_engine.VideoDisplayContextType; }
         }
diff --git a/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/FullscreenWindow.cs b/src/Extensions/Banshee.NowPlaying/Banshee.NowPlaying/FullscreenWindow.cs
index dc57fde..97b89c6 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.InDvdMenu) {
+                       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.InDvdMenu) {
+                       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.InDvdMenu) {
+                       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.InDvdMenu) {
+                       ServiceManager.PlayerEngine.NavigateToUpMenu ();
+                    }
+                    break;
+                case Gdk.Key.downarrow:
+                case Gdk.Key.Down:
+                case Gdk.Key.KP_Down:
+                    if (ServiceManager.PlayerEngine.InDvdMenu) {
+                       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..84f3821 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.InDvdMenu) {
+                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.InDvdMenu) {
+                        ServiceManager.PlayerEngine.NotifyMouseButtonPressed ((int)args.Event.Button, args.Event.X, args.Event.Y);
+                    }
+                    break;
+                case Gdk.EventType.ButtonRelease:
+                    if (ServiceManager.PlayerEngine.InDvdMenu) {
+                        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.InDvdMenu) {
+                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..be8a5c2 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,6 @@ 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/Banshee.AudioCd/Banshee.AudioCd/AudioCdDiscModel.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdDiscModel.cs
similarity index 90%
rename from src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDiscModel.cs
rename to src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdDiscModel.cs
index 2e7a7db..842ec2b 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDiscModel.cs
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdDiscModel.cs
@@ -3,6 +3,7 @@
 //
 // Author:
 //   Aaron Bockover <abockover novell com>
+//   Alex Launi <alex launi canonical com>
 //
 // Copyright (C) 2008 Novell, Inc.
 //
@@ -40,15 +41,13 @@ using Banshee.Hardware;
 using Banshee.Collection;
 using Banshee.Collection.Database;
 
-namespace Banshee.AudioCd
+namespace Banshee.OpticalDisc.AudioCd
 {
-    public class AudioCdDiscModel : MemoryTrackListModel
+    public class AudioCdDiscModel : DiscModel
     {
         // 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;
@@ -71,8 +70,8 @@ namespace Banshee.AudioCd
         }
 
         public AudioCdDiscModel (IDiscVolume volume)
+            : base (volume)
         {
-            this.volume = volume;
             disc_title = Catalog.GetString ("Audio CD");
 
             MusicBrainzService.UserAgent = Banshee.Web.Browser.UserAgent;
@@ -83,18 +82,18 @@ namespace Banshee.AudioCd
             OnReloaded ();
         }
 
-        public void LoadModelFromDisc ()
+        public override void LoadModelFromDisc ()
         {
             Clear ();
 
-            LocalDisc mb_disc = LocalDisc.GetFromDevice (volume.DeviceNode);
+            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);
+                AudioCdTrackInfo track = new AudioCdTrackInfo (this, Volume.DeviceNode, i);
                 track.TrackNumber = i + 1;
                 track.TrackCount = n;
                 track.DiscNumber = 1;
@@ -262,35 +261,8 @@ namespace Banshee.AudioCd
             }
         }
 
-        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 {
+        public override string Title {
             get { return disc_title; }
         }
 
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDuplicator.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdDuplicator.cs
similarity index 98%
rename from src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDuplicator.cs
rename to src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdDuplicator.cs
index 6cc8dd9..9696423 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdDuplicator.cs
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdDuplicator.cs
@@ -31,7 +31,7 @@ using Mono.Addins;
 
 using Banshee.Hardware;
 
-namespace Banshee.AudioCd
+namespace Banshee.OpticalDisc.AudioCd
 {
     public static class AudioCdDuplicator
     {
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdRipper.cs
similarity index 94%
rename from src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
rename to src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdRipper.cs
index 0f748c1..f1f1558 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdRipper.cs
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdRipper.cs
@@ -42,7 +42,7 @@ using Banshee.Collection;
 using Banshee.Collection.Database;
 using Banshee.MediaEngine;
 
-namespace Banshee.AudioCd
+namespace Banshee.OpticalDisc.AudioCd
 {
     public class AudioCdRipper : IDisposable
     {
@@ -105,7 +105,7 @@ namespace Banshee.AudioCd
         {
             ResetState ();
 
-            foreach (AudioCdTrackInfo track in source.DiscModel) {
+            foreach (AudioCdTrackInfo track in source.Model) {
                 if (track.RipEnabled) {
                     total_duration += track.Duration;
                     queue.Enqueue (track);
@@ -122,7 +122,7 @@ namespace Banshee.AudioCd
                 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));
+                ), GLib.Markup.EscapeText (source.Model.Title));
             user_job.SetResources (Resource.Cpu);
             user_job.PriorityHints = PriorityHints.SpeedSensitive | PriorityHints.DataLossIfStopped;
             user_job.CanCancel = true;
@@ -130,13 +130,13 @@ namespace Banshee.AudioCd
             user_job.Finished += OnFinished;
             user_job.Register ();
 
-            if (source != null && source.DiscModel != null) {
-                if (!source.DiscModel.LockDoor ()) {
+            if (source != null && source.Model != null) {
+                if (!source.Model.LockDoor ()) {
                     Hyena.Log.Warning ("Could not lock CD-ROM door", false);
                 }
             }
 
-            ripper.Begin (source.DiscModel.Volume.DeviceNode, AudioCdService.ErrorCorrection.Get ());
+            ripper.Begin (source.Model.Volume.DeviceNode, AudioCdService.ErrorCorrection.Get ());
 
             RipNextTrack ();
         }
@@ -145,8 +145,8 @@ namespace Banshee.AudioCd
         {
             ResetState ();
 
-            if (source != null && source.DiscModel != null) {
-                source.DiscModel.UnlockDoor ();
+            if (source != null && source.Model != null) {
+                source.Model.UnlockDoor ();
             }
 
             if (ripper != null) {
@@ -183,7 +183,7 @@ namespace Banshee.AudioCd
             AudioCdTrackInfo track = queue.Dequeue ();
 
             user_job.Title = String.Format (Catalog.GetString ("Importing {0} of {1}"),
-                ++track_index, source.DiscModel.EnabledCount);
+                ++track_index, source.Model.EnabledCount);
             status = String.Format("{0} - {1}", track.ArtistName, track.TrackTitle);
             user_job.Status = status;
 
diff --git a/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdService.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdService.cs
new file mode 100644
index 0000000..cba3f28
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.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.OpticalDisc.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.OpticalDisc.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.AudioCd/Banshee.AudioCd/AudioCdSource.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdSource.cs
similarity index 63%
rename from src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs
rename to src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdSource.cs
index 5e391ab..62a6edb 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdSource.cs
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdSource.cs
@@ -3,6 +3,7 @@
 //
 // Author:
 //   Aaron Bockover <abockover novell com>
+//   Alex Launi <alex launi canonical com>
 //
 // Copyright (C) 2008 Novell, Inc.
 //
@@ -41,25 +42,18 @@ using Banshee.Collection.Database;
 
 using Gtk;
 using Banshee.Gui;
-using Selection = Hyena.Collections.Selection;
 
-namespace Banshee.AudioCd
+namespace Banshee.OpticalDisc.AudioCd
 {
-    public class AudioCdSource : Source, ITrackModelSource, IUnmapableSource,
-        IImportSource, IDurationAggregator, IFileSizeAggregator, IDisposable
+    public class AudioCdSource : DiscSource, IImportSource,
+        IDurationAggregator, IFileSizeAggregator
     {
-        private AudioCdService service;
-        private AudioCdDiscModel disc_model;
         private SourceMessage query_message;
 
         public AudioCdSource (AudioCdService service, AudioCdDiscModel discModel)
-            : base (Catalog.GetString ("Audio CD"), discModel.Title, 59)
+            : base ((DiscService) service, (DiscModel) discModel, Catalog.GetString ("Audio CD"), discModel.Title, 59)
         {
-            this.service = service;
-            this.disc_model = discModel;
-
             TypeUniqueId = "";
-
             Properties.SetString ("TrackView.ColumnControllerXml", String.Format (@"
                 <column-controller>
                   <column>
@@ -69,48 +63,36 @@ namespace Banshee.AudioCd
                 </column-controller>
             "));
 
-            disc_model.MetadataQueryStarted += OnMetadataQueryStarted;
-            disc_model.MetadataQueryFinished += OnMetadataQueryFinished;
-            disc_model.EnabledCountChanged += OnEnabledCountChanged;
-            disc_model.LoadModelFromDisc ();
+            Model.MetadataQueryStarted += OnMetadataQueryStarted;
+            Model.MetadataQueryFinished += OnMetadataQueryFinished;
+            Model.EnabledCountChanged += OnEnabledCountChanged;
+            Model.LoadModelFromDisc ();
 
             SetupGui ();
         }
 
         public TimeSpan Duration {
-            get { return disc_model.Duration; }
+            get { return Model.Duration; }
         }
 
         public long FileSize {
-            get { return disc_model.FileSize; }
+            get { return Model.FileSize; }
         }
 
-        public bool DiscIsPlaying {
-            get {
-                AudioCdTrackInfo playing_track = ServiceManager.PlayerEngine.CurrentTrack as AudioCdTrackInfo;
-                return playing_track != null && playing_track.Model == disc_model;
-            }
+        public new AudioCdDiscModel Model {
+            get { return (AudioCdDiscModel) base.DiscModel; }
+            set { base.DiscModel = value; }
         }
 
-        public void StopPlayingDisc ()
-        {
-            if (DiscIsPlaying) {
-                ServiceManager.PlayerEngine.Close (true);
-            }
-        }
-
-        public void Dispose ()
+        public override void Dispose ()
         {
+            StopPlayingDisc ();
             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; }
+            Model.MetadataQueryStarted -= OnMetadataQueryStarted;
+            Model.MetadataQueryFinished -= OnMetadataQueryFinished;
+            Model.EnabledCountChanged -= OnEnabledCountChanged;
+            Service = null;
+            Model = null;
         }
 
         private void OnEnabledCountChanged (object o, EventArgs args)
@@ -136,12 +118,12 @@ namespace Banshee.AudioCd
 
         private void OnMetadataQueryFinished (object o, EventArgs args)
         {
-            if (disc_model.Title != Name) {
-                Name = disc_model.Title;
+            if (Model.Title != Name) {
+                Name = Model.Title;
                 OnUpdated ();
             }
 
-            if (disc_model.MetadataQuerySuccess) {
+            if (Model.MetadataQuerySuccess) {
                 DestroyQueryMessage ();
                 if (DiscIsPlaying) {
                     ServiceManager.PlayerEngine.TrackInfoUpdated ();
@@ -177,7 +159,7 @@ namespace Banshee.AudioCd
         private void BeginAutoRip ()
         {
             // Make sure the album isn't already in the Library
-            TrackInfo track = disc_model[0];
+            TrackInfo track = Model[0];
             int count = ServiceManager.DbConnection.Query<int> (
                 @"SELECT Count(*) FROM CoreTracks, CoreArtists, CoreAlbums WHERE
                     CoreTracks.PrimarySourceID = ? AND
@@ -227,7 +209,7 @@ namespace Banshee.AudioCd
         internal void DuplicateDisc ()
         {
             try {
-                AudioCdDuplicator.Duplicate (disc_model);
+                AudioCdDuplicator.Duplicate (Model);
             } catch (Exception e) {
                 Hyena.Log.Error (Catalog.GetString ("Could not duplicate audio CD"), e.Message, true);
                 Hyena.Log.Exception (e);
@@ -238,171 +220,58 @@ namespace Banshee.AudioCd
         {
             StopPlayingDisc ();
 
-            foreach (AudioCdTrackInfo track in disc_model) {
+            foreach (AudioCdTrackInfo track in Model) {
                 track.CanPlay = false;
             }
 
-            disc_model.NotifyUpdated ();
+            Model.NotifyUpdated ();
         }
 
         internal void UnlockAllTracks ()
         {
-            foreach (AudioCdTrackInfo track in disc_model) {
+            foreach (AudioCdTrackInfo track in Model) {
                 track.CanPlay = true;
             }
 
-            disc_model.NotifyUpdated ();
+            Model.NotifyUpdated ();
         }
 
         internal void UnlockTrack (AudioCdTrackInfo track)
         {
             track.CanPlay = true;
-            disc_model.NotifyUpdated ();
+            Model.NotifyUpdated ();
         }
 
-#region Source Overrides
-
-        public override int Count {
-            get { return disc_model.Count; }
-        }
-
-        public override string PreferencesPageId {
-            get { return "audio-cd"; }
-        }
+#region DiscSource
 
-        public override bool HasEditableTrackProperties {
-            get { return true; }
+        public override bool CanRepeat {
+            get { return true;}
         }
 
-        public override bool HasViewableTrackProperties {
+        public override bool CanShuffle {
             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; }
-        }
+#region Source Overrides
 
-        public bool CanDeleteTracks {
-            get { return false; }
+        public override int Count {
+            get { return Model.Count; }
         }
 
-        public bool ConfirmRemoveTracks {
-            get { return false; }
+        public override string PreferencesPageId {
+            get { return "audio-cd"; }
         }
 
-        public virtual bool CanRepeat {
+        public override bool HasEditableTrackProperties {
             get { return true; }
         }
 
-        public virtual bool CanShuffle {
+        public override bool HasViewableTrackProperties {
             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
@@ -411,7 +280,7 @@ namespace Banshee.AudioCd
 
         private void SetupGui ()
         {
-            Properties.SetStringList ("Icon.Name", "media-optical", "gnome-dev-cdrom-audio", "source-cd-audio");
+            Properties.SetStringList ("Icon.Name", "media-optical-cd-audio", "media-optical-cd", "media-optical", "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");
@@ -432,7 +301,7 @@ namespace Banshee.AudioCd
 
             Gtk.Action rip_action = uia_service.GlobalActions["RipDiscAction"];
             if (rip_action != null) {
-                string title = disc_model.Title;
+                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 ())
@@ -440,7 +309,7 @@ namespace Banshee.AudioCd
                 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;
+                rip_action.Sensitive = AudioCdRipper.Supported && Model.EnabledCount > 0;
             }
 
             Gtk.Action duplicate_action = uia_service.GlobalActions["DuplicateDiscAction"];
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdTrackInfo.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdTrackInfo.cs
similarity index 91%
rename from src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdTrackInfo.cs
rename to src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdTrackInfo.cs
index 68e6a26..405522c 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd/AudioCdTrackInfo.cs
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.AudioCd/AudioCdTrackInfo.cs
@@ -33,13 +33,13 @@ using Hyena;
 using Banshee.Collection;
 using Banshee.Collection.Database;
 
-namespace Banshee.AudioCd
+namespace Banshee.OpticalDisc.AudioCd
 {
-    public class AudioCdTrackInfo : DatabaseTrackInfo
+    public class AudioCdTrackInfo : DiscTrackInfo
     {
         public AudioCdTrackInfo (AudioCdDiscModel model, string deviceNode, int index)
+            : base (model)
         {
-            this.model = model;
             this.index_on_disc = index;
 
             Uri = new SafeUri (String.Format ("cdda://{0}#{1}", index_on_disc + 1, deviceNode));
@@ -51,9 +51,10 @@ namespace Banshee.AudioCd
             return cd_track == null ? false : (cd_track.Model == Model && cd_track.IndexOnDisc == IndexOnDisc);
         }
 
-        private AudioCdDiscModel model;
-        public AudioCdDiscModel Model {
-            get { return model; }
+        public new AudioCdDiscModel Model {
+            get {
+                return (AudioCdDiscModel) base.Model;
+            }
         }
 
         private int index_on_disc;
@@ -101,7 +102,7 @@ namespace Banshee.AudioCd
                 }
 
                 rip_enabled = value;
-                model.EnabledCount += rip_enabled ? 1 : -1;
+                Model.EnabledCount += rip_enabled ? 1 : -1;
             }
         }
     }
diff --git a/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdModel.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdModel.cs
new file mode 100644
index 0000000..b2fc27a
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.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.OpticalDisc.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.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdService.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdService.cs
new file mode 100644
index 0000000..95c4491
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdService.cs
@@ -0,0 +1,132 @@
+//
+// 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.OpticalDisc.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) {
+                base.Initialize ();
+                SetupActions ();
+            }
+
+        }
+
+        public override void Dispose ()
+        {
+            lock (this) {
+                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 ("GlobalUI_Dvd.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.OpticalDisc.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.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdSource.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdSource.cs
new file mode 100644
index 0000000..05fa717
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdSource.cs
@@ -0,0 +1,102 @@
+//
+// 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.OpticalDisc.Dvd
+{
+    public class DvdSource : DiscSource, IBasicPlaybackController
+    {
+        public DvdSource (DiscService service, DvdModel model)
+            : base (service, (DiscModel) model, Catalog.GetString ("Dvd"), model.Title, 58)
+        {
+            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 void SetupGui ()
+        {
+            Properties.SetStringList ("Icon.Name", "media-optical-dvd", "media-optical", "gnome-dev-dvd");
+            Properties.SetString ("SourcePreferencesActionLabel", Catalog.GetString ("DVD Preferences"));
+            Properties.SetString ("UnmapSourceActionLabel", Catalog.GetString ("Eject Disc"));
+            Properties.SetString ("UnmapSourceActionIconName", "media-eject");
+            Properties.SetString ("ActiveSourceUIResource", "ActiveSourceUI.xml");
+            Properties.SetString ("GtkActionPath", "/DvdContextMenu");
+        }
+
+#endregion
+
+#region IBasicPlaybackController implementation
+
+        public bool First ()
+        {
+            ServiceManager.PlayerEngine.NavigateToMenu ();
+            return true;
+        }
+
+        public bool Next (bool restart, bool changeImmediately)
+        {
+            if (!ServiceManager.PlayerEngine.InDvdMenu) {
+                ServiceManager.PlayerEngine.GoToNextChapter ();
+            }
+            // Do nothing if in the menu
+            return true;
+        }
+
+        public bool Previous (bool restart)
+        {
+            if (!ServiceManager.PlayerEngine.InDvdMenu) {
+                ServiceManager.PlayerEngine.GoToPreviousChapter ();
+            }
+            // Do nothing if in the menu
+            return true;
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdTrackInfo.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.Dvd/DvdTrackInfo.cs
new file mode 100644
index 0000000..e88d761
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.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.OpticalDisc.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.AudioCd/Banshee.AudioCd.addin.xml b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.addin.xml
similarity index 63%
rename from src/Extensions/Banshee.AudioCd/Banshee.AudioCd.addin.xml
rename to src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.addin.xml
index f7351a8..a0256e6 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.addin.xml
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.addin.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Addin 
-    id="Banshee.AudioCd"
+    id="Banshee.OpticalDisc"
     version="1.0"
     compatVersion="1.0"
     copyright="Â 2008 Novell Inc. Licensed under the MIT X11 license."
-    name="Audio CD Support"
+    name="DVD and Audio CD Support"
     category="Core"
-    description="Listen to and rip Audio CDs."
+    description="Watch DVDs, listen to and rip Audio CDs."
     author="Aaron Bockover"
     url="http://banshee.fm/";
     defaultEnabled="true">
@@ -16,7 +16,8 @@
   </Dependencies>
 
   <Extension path="/Banshee/ServiceManager/Service">
-    <Service class="Banshee.AudioCd.AudioCdService"/>
+    <Service class="Banshee.OpticalDisc.AudioCd.AudioCdService"/>
+    <Service class="Banshee.OpticalDisc.Dvd.DvdService"/>
   </Extension>
 
 </Addin>
diff --git a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.csproj b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.csproj
similarity index 74%
rename from src/Extensions/Banshee.AudioCd/Banshee.AudioCd.csproj
rename to src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.csproj
index 419925e..12a68f3 100644
--- a/src/Extensions/Banshee.AudioCd/Banshee.AudioCd.csproj
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc.csproj
@@ -7,20 +7,19 @@
     <ProjectGuid>{F38B53BA-8F85-4DC6-9B94-029C1CF96F24}</ProjectGuid>
     <OutputType>Library</OutputType>
     <UseParentDirectoryAsNamespace>true</UseParentDirectoryAsNamespace>
-    <AssemblyName>Banshee.AudioCd</AssemblyName>
+    <AssemblyName>Banshee.OpticalDisc</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>
+    <RootNamespace>Banshee.OpticalDisc</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>
@@ -28,7 +27,6 @@
     <DebugSymbols>true</DebugSymbols>
     <DebugType>full</DebugType>
     <PlatformTarget>x86</PlatformTarget>
-    <WarningLevel>4</WarningLevel>
     <Optimize>false</Optimize>
     <OutputPath>..\..\..\bin\bin</OutputPath>
   </PropertyGroup>
@@ -58,19 +56,12 @@
     <Reference Include="taglib-sharp">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\..\..\bin\bin\taglib-sharp.dll</HintPath>
-    </Reference>
-    <Reference Include="Mono.Addins">
-      <HintPath>..\..\..\bin\bin\Mono.Addins.dll</HintPath>
+      <Package>taglib-sharp</Package>
     </Reference>
     <Reference Include="Mono.Posix">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\..\..\bin\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>
@@ -87,23 +78,34 @@
     </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>
+    <EmbeddedResource Include="Banshee.OpticalDisc.addin.xml">
+      <LogicalName>Banshee.OpticalDisc.addin.xml</LogicalName>
+    </EmbeddedResource>
+    <EmbeddedResource Include="Resources\GlobalUI_Dvd.xml">
+      <LogicalName>GlobalUI_Dvd.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" />
+    <Compile Include="Banshee.OpticalDisc.AudioCd\AudioCdDiscModel.cs" />
+    <Compile Include="Banshee.OpticalDisc.AudioCd\AudioCdDuplicator.cs" />
+    <Compile Include="Banshee.OpticalDisc.AudioCd\AudioCdRipper.cs" />
+    <Compile Include="Banshee.OpticalDisc.AudioCd\AudioCdService.cs" />
+    <Compile Include="Banshee.OpticalDisc.AudioCd\AudioCdSource.cs" />
+    <Compile Include="Banshee.OpticalDisc.AudioCd\AudioCdTrackInfo.cs" />
+    <Compile Include="Banshee.OpticalDisc.Dvd\DvdSource.cs" />
+    <Compile Include="Banshee.OpticalDisc\DiscService.cs" />
+    <Compile Include="Banshee.OpticalDisc\DiscSource.cs" />
+    <Compile Include="Banshee.OpticalDisc.Dvd\DvdService.cs" />
+    <Compile Include="Banshee.OpticalDisc.Dvd\DvdModel.cs" />
+    <Compile Include="Banshee.OpticalDisc\DiscModel.cs" />
+    <Compile Include="Banshee.OpticalDisc.Dvd\DvdTrackInfo.cs" />
+    <Compile Include="Banshee.OpticalDisc\DiscTrackInfo.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
@@ -121,4 +123,8 @@
       </Properties>
     </MonoDevelop>
   </ProjectExtensions>
+  <ItemGroup>
+    <Folder Include="Banshee.OpticalDisc\" />
+    <Folder Include="Banshee.OpticalDisc.Dvd\" />
+  </ItemGroup>
 </Project>
diff --git a/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/DiscModel.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/DiscModel.cs
new file mode 100644
index 0000000..0c7498a
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/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.OpticalDisc
+{
+    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.OpticalDisc/Banshee.OpticalDisc/DiscService.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/DiscService.cs
new file mode 100644
index 0000000..663aec1
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/DiscService.cs
@@ -0,0 +1,218 @@
+//
+// 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.OpticalDisc
+{
+    public abstract class DiscService : IExtensionService, IDisposable
+    {
+        private List<DeviceCommand> unhandled_device_commands;
+
+        public DiscService ()
+        {
+        }
+
+        public virtual void Initialize ()
+        {
+            if (ServiceManager.HardwareManager == null) {
+                throw new NotSupportedException ("DiscService cannot work when no HardwareManager is available");
+            }
+
+            lock (this) {
+                Sources = new Dictionary<string, DiscSource> ();
+
+                // This says Cdrom, but really it means Cdrom in the general optical 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.OpticalDisc/Banshee.OpticalDisc/DiscSource.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/DiscSource.cs
new file mode 100644
index 0000000..ddc74c5
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/DiscSource.cs
@@ -0,0 +1,199 @@
+//
+// 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.OpticalDisc
+{
+    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.OpticalDisc/Banshee.OpticalDisc/DiscTrackInfo.cs b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/DiscTrackInfo.cs
new file mode 100644
index 0000000..ffb08de
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Banshee.OpticalDisc/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.OpticalDisc
+{
+    public abstract class DiscTrackInfo : DatabaseTrackInfo
+    {
+        public DiscTrackInfo (DiscModel model)
+        {
+            Model = model;
+        }
+
+        public DiscModel Model { get; private set; }
+    }
+}
+
diff --git a/src/Extensions/Banshee.OpticalDisc/Makefile.am b/src/Extensions/Banshee.OpticalDisc/Makefile.am
new file mode 100644
index 0000000..d2a80d0
--- /dev/null
+++ b/src/Extensions/Banshee.OpticalDisc/Makefile.am
@@ -0,0 +1,29 @@
+ASSEMBLY = Banshee.OpticalDisc
+TARGET = library
+LINK = $(REF_EXTENSION_OPTICALDISC)
+INSTALL_DIR = $(EXTENSIONS_INSTALL_DIR)
+
+SOURCES =  \
+	Banshee.OpticalDisc.AudioCd/AudioCdDiscModel.cs \
+	Banshee.OpticalDisc.AudioCd/AudioCdDuplicator.cs \
+	Banshee.OpticalDisc.AudioCd/AudioCdRipper.cs \
+	Banshee.OpticalDisc.AudioCd/AudioCdService.cs \
+	Banshee.OpticalDisc.AudioCd/AudioCdSource.cs \
+	Banshee.OpticalDisc.AudioCd/AudioCdTrackInfo.cs \
+	Banshee.OpticalDisc.Dvd/DvdModel.cs \
+	Banshee.OpticalDisc.Dvd/DvdService.cs \
+	Banshee.OpticalDisc.Dvd/DvdSource.cs \
+	Banshee.OpticalDisc.Dvd/DvdTrackInfo.cs \
+	Banshee.OpticalDisc/DiscModel.cs \
+	Banshee.OpticalDisc/DiscService.cs \
+	Banshee.OpticalDisc/DiscSource.cs \
+	Banshee.OpticalDisc/DiscTrackInfo.cs
+
+RESOURCES =  \
+	Banshee.OpticalDisc.addin.xml \
+	Resources/ActiveSourceUI.xml \
+	Resources/GlobalUI.xml \
+	Resources/GlobalUI_Dvd.xml
+
+include $(top_srcdir)/build/build.mk
+
diff --git a/src/Extensions/Banshee.AudioCd/Resources/ActiveSourceUI.xml b/src/Extensions/Banshee.OpticalDisc/Resources/ActiveSourceUI.xml
similarity index 100%
rename from src/Extensions/Banshee.AudioCd/Resources/ActiveSourceUI.xml
rename to src/Extensions/Banshee.OpticalDisc/Resources/ActiveSourceUI.xml
diff --git a/src/Extensions/Banshee.AudioCd/Resources/GlobalUI.xml b/src/Extensions/Banshee.OpticalDisc/Resources/GlobalUI.xml
similarity index 100%
copy from src/Extensions/Banshee.AudioCd/Resources/GlobalUI.xml
copy to src/Extensions/Banshee.OpticalDisc/Resources/GlobalUI.xml
diff --git a/src/Extensions/Banshee.AudioCd/Resources/GlobalUI.xml b/src/Extensions/Banshee.OpticalDisc/Resources/GlobalUI_Dvd.xml
similarity index 53%
rename from src/Extensions/Banshee.AudioCd/Resources/GlobalUI.xml
rename to src/Extensions/Banshee.OpticalDisc/Resources/GlobalUI_Dvd.xml
index eec6c5f..14b9930 100644
--- a/src/Extensions/Banshee.AudioCd/Resources/GlobalUI.xml
+++ b/src/Extensions/Banshee.OpticalDisc/Resources/GlobalUI_Dvd.xml
@@ -1,7 +1,7 @@
 <ui>
-    <popup name="AudioCdContextMenu">
-        <menuitem name="RipDisc" action="RipDiscAction"/>
-        <menuitem name="DuplicateDisc" action="DuplicateDiscAction"/>
+    <popup name="DvdContextMenu">
+        <menuitem name="GoToMenu" action="GoToMenuAction"/>
+        <menuitem name="AudioTrack" action="AudioTrackAction"/>
         <menuitem name="SourcePreferences" action="SourcePreferencesAction"/>
         <separator/>
         <menuitem name="UnmapSource" action="UnmapSourceAction"/>
diff --git a/src/Extensions/Banshee.AudioCd/ThemeIcons/16x16/actions/media-import-audio-cd.png b/src/Extensions/Banshee.OpticalDisc/ThemeIcons/16x16/actions/media-import-audio-cd.png
similarity index 100%
rename from src/Extensions/Banshee.AudioCd/ThemeIcons/16x16/actions/media-import-audio-cd.png
rename to src/Extensions/Banshee.OpticalDisc/ThemeIcons/16x16/actions/media-import-audio-cd.png
diff --git a/src/Extensions/Banshee.AudioCd/ThemeIcons/22x22/actions/media-import-audio-cd.png b/src/Extensions/Banshee.OpticalDisc/ThemeIcons/22x22/actions/media-import-audio-cd.png
similarity index 100%
rename from src/Extensions/Banshee.AudioCd/ThemeIcons/22x22/actions/media-import-audio-cd.png
rename to src/Extensions/Banshee.OpticalDisc/ThemeIcons/22x22/actions/media-import-audio-cd.png
diff --git a/src/Extensions/Makefile.am b/src/Extensions/Makefile.am
index b5621b1..2f55441 100644
--- a/src/Extensions/Makefile.am
+++ b/src/Extensions/Makefile.am
@@ -2,7 +2,6 @@ SUBDIRS = \
 	Banshee.AmazonMp3 \
 	Banshee.AmazonMp3.Store \
 	Banshee.Audiobook \
-	Banshee.AudioCd \
 	Banshee.BooScript \
 	Banshee.Bpm \
 	Banshee.CoverArt \
@@ -22,6 +21,7 @@ SUBDIRS = \
 	Banshee.MultimediaKeys \
 	Banshee.NotificationArea \
 	Banshee.NowPlaying \
+	Banshee.OpticalDisc \
 	Banshee.PlayerMigration \
 	Banshee.PlayQueue \
 	Banshee.MeeGo \



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]