[gnome-photos] dlna: Display item on remote DLNA renderer using dLeyna



commit 8e0c3cc17c4562420ff306600b7845af4f917b59
Author: Emanuele Aina <emanuele aina collabora com>
Date:   Mon May 20 11:19:07 2013 +0100

    dlna: Display item on remote DLNA renderer using dLeyna
    
    Let the user choose a DLNA renderer device from the list of DMRs
    discovered on the network and display the current item there.
    
    This adds a soft-dependency on the deyna-renderer DBus service: if the
    service is not activatable the functionality will be automatically
    disabled.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=701775

 configure.ac                             |    2 +-
 po/POTFILES.in                           |    1 +
 src/Makefile.am                          |   50 ++
 src/photos-application.c                 |   24 +
 src/photos-dleyna-renderer-device.xml    |   62 +++
 src/photos-dleyna-renderer-manager.xml   |   46 ++
 src/photos-dleyna-renderer-push-host.xml |   36 ++
 src/photos-dlna-renderer.c               |  881 ++++++++++++++++++++++++++++++
 src/photos-dlna-renderer.h               |  138 +++++
 src/photos-dlna-renderers-dialog.c       |  290 ++++++++++
 src/photos-dlna-renderers-dialog.h       |   71 +++
 src/photos-dlna-renderers-dialog.ui      |   70 +++
 src/photos-dlna-renderers-manager.c      |  267 +++++++++
 src/photos-dlna-renderers-manager.h      |   75 +++
 src/photos-main-toolbar.c                |   10 +
 src/photos-main.c                        |    4 +
 src/photos-mpris-player.xml              |  681 +++++++++++++++++++++++
 src/photos-preview-menu.ui               |    4 +
 src/photos-remote-display-manager.c      |  289 ++++++++++
 src/photos-remote-display-manager.h      |   86 +++
 src/photos.gresource.xml                 |    1 +
 21 files changed, 3087 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 11c29d2..1f0177b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -26,7 +26,7 @@ GLIB_GSETTINGS
 
 EXEMPI_MIN_VERSION=1.99.5
 GLIB_MIN_VERSION=2.35.1
-GTK_MIN_VERSION=3.8.0
+GTK_MIN_VERSION=3.9.4
 LIBEXIF_MIN_VERSION=0.6.14
 LIBRSVG_MIN_VERSION=2.26.0
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ec3b8d5..286d28d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,7 @@ src/eog-uri-converter.c
 src/eog-util.c
 src/photos-application.c
 [type: gettext/glade]src/photos-app-menu.ui
+[type: gettext/glade]src/photos-dlna-renderers-dialog.ui
 src/photos-embed.c
 src/photos-empty-results-box.c
 src/photos-indexing-notification.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 0a8b13f..200afb5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,8 +8,16 @@ gnome_photos_built_sources = \
        photos-about-data.h \
        photos-enums.c \
        photos-enums.h \
+       photos-dleyna-renderer-device.c \
+       photos-dleyna-renderer-device.h \
+       photos-dleyna-renderer-manager.c \
+       photos-dleyna-renderer-manager.h \
+       photos-dleyna-renderer-push-host.c \
+       photos-dleyna-renderer-push-host.h \
        photos-marshalers.c \
        photos-marshalers.h \
+       photos-mpris-player.c \
+       photos-mpris-player.h \
        photos-resources.c \
        photos-resources.h \
        photos-tracker-resources.c \
@@ -66,6 +74,12 @@ gnome_photos_SOURCES = \
        photos-create-collection-job.h \
        photos-delete-item-job.c \
        photos-delete-item-job.h \
+       photos-dlna-renderer.c \
+       photos-dlna-renderer.h \
+       photos-dlna-renderers-dialog.c \
+       photos-dlna-renderers-dialog.h \
+       photos-dlna-renderers-manager.c \
+       photos-dlna-renderers-manager.h \
        photos-embed.c \
        photos-embed.h \
        photos-empty-results-box.c \
@@ -124,6 +138,8 @@ gnome_photos_SOURCES = \
        photos-query.h \
        photos-query-builder.h \
        photos-query-builder.c \
+       photos-remote-display-manager.c \
+       photos-remote-display-manager.h \
        photos-search-type.c \
        photos-search-type.h \
        photos-search-type-manager.c \
@@ -180,10 +196,15 @@ EXTRA_DIST = \
        photos-generate-about \
        photos.gresource.xml \
        photos-app-menu.ui \
+       photos-dlna-renderers-dialog.ui \
        photos-marshalers.list \
        photos-preview-menu.ui \
        photos-selection-menu.ui \
        photos-tracker-resources.xml \
+       photos-dleyna-renderer-device.xml \
+       photos-dleyna-renderer-manager.xml \
+       photos-dleyna-renderer-push-host.xml \
+       photos-mpris-player.xml \
        $(null)
 
 AM_CPPFLAGS = \
@@ -363,6 +384,35 @@ photos-resources.h: photos.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) --sou
                --generate-header \
                $<
 
+photos-dleyna-renderer-device.h photos-dleyna-renderer-device.c: photos-dleyna-renderer-device.xml
+       $(AM_V_GEN)gdbus-codegen \
+               --c-namespace Dleyna \
+               --generate-c-code photos-dleyna-renderer-device \
+               --interface-prefix com.intel.dLeynaRenderer. \
+               --annotate 'com.intel.dLeynaRenderer.RendererDevice.GetIcon()[Bytes]' 
org.gtk.GDBus.C.ForceGVariant true \
+               $<
+
+photos-dleyna-renderer-manager.h photos-dleyna-renderer-manager.c: photos-dleyna-renderer-manager.xml
+       $(AM_V_GEN)gdbus-codegen \
+               --c-namespace Dleyna \
+               --generate-c-code photos-dleyna-renderer-manager \
+               --interface-prefix com.intel.dLeynaRenderer. \
+               $<
+
+photos-dleyna-renderer-push-host.h photos-dleyna-renderer-push-host.c: photos-dleyna-renderer-push-host.xml
+       $(AM_V_GEN)gdbus-codegen \
+               --c-namespace Dleyna \
+               --generate-c-code photos-dleyna-renderer-push-host \
+               --interface-prefix com.intel.dLeynaRenderer. \
+               $<
+
+photos-mpris-player.h photos-mpris-player.c: photos-mpris-player.xml
+       $(AM_V_GEN)gdbus-codegen \
+               --c-namespace Mpris \
+               --generate-c-code photos-mpris-player \
+               --interface-prefix org.mpris.MediaPlayer2. \
+               $<
+
 photos-tracker-resources.h photos-tracker-resources.c: photos-tracker-resources.xml
        $(AM_V_GEN)gdbus-codegen \
                --c-namespace Tracker \
diff --git a/src/photos-application.c b/src/photos-application.c
index 1ee6c58..567966e 100644
--- a/src/photos-application.c
+++ b/src/photos-application.c
@@ -33,6 +33,7 @@
 
 #include "eog-debug.h"
 #include "photos-application.h"
+#include "photos-dlna-renderers-dialog.h"
 #include "photos-item-manager.h"
 #include "photos-main-window.h"
 #include "photos-mode-controller.h"
@@ -52,6 +53,7 @@ struct _PhotosApplicationPrivate
   GSimpleAction *sel_all_action;
   GSimpleAction *sel_none_action;
   GSimpleAction *set_bg_action;
+  GSimpleAction *remote_display_action;
   GtkWidget *main_window;
   PhotosBaseManager *item_mngr;
   PhotosModeController *mode_cntrlr;
@@ -151,6 +153,24 @@ photos_application_properties (PhotosApplication *self)
 
 
 static void
+photos_application_remote_display_current (PhotosApplication *self)
+{
+  PhotosApplicationPrivate *priv = self->priv;
+  PhotosBaseItem *item;
+  GtkWidget *dialog;
+  const gchar *urn;
+
+  item = PHOTOS_BASE_ITEM (photos_base_manager_get_active_object (priv->item_mngr));
+  if (item == NULL)
+    return;
+
+  urn = photos_base_item_get_id (item);
+  dialog = photos_dlna_renderers_dialog_new (GTK_WINDOW (priv->main_window), urn);
+  gtk_widget_show_all (dialog);
+}
+
+
+static void
 photos_application_quit (PhotosApplication *self, GVariant *parameter)
 {
   gtk_widget_destroy (self->priv->main_window);
@@ -266,6 +286,10 @@ photos_application_startup (GApplication *application)
   g_signal_connect_swapped (priv->properties_action, "activate", G_CALLBACK (photos_application_properties), 
self);
   g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (priv->properties_action));
 
+  priv->remote_display_action = g_simple_action_new ("remote-display-current", NULL);
+  g_signal_connect_swapped (priv->remote_display_action, "activate", G_CALLBACK 
(photos_application_remote_display_current), self);
+  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (priv->remote_display_action));
+
   action = g_simple_action_new ("quit", NULL);
   g_signal_connect_swapped (action, "activate", G_CALLBACK (photos_application_quit), self);
   g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
diff --git a/src/photos-dleyna-renderer-device.xml b/src/photos-dleyna-renderer-device.xml
new file mode 100644
index 0000000..e4a83a8
--- /dev/null
+++ b/src/photos-dleyna-renderer-device.xml
@@ -0,0 +1,62 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+                      "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<!--
+ Photos - access, organize and share your photos on GNOME
+ Copyright © 2013 Intel Corporation. All rights reserved.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+-->
+<!-- GDBus 2.37.1 -->
+<node>
+  <interface name="com.intel.dLeynaRenderer.RendererDevice">
+    <method name="Cancel">
+    </method>
+    <method name="GetIcon">
+      <arg type="s" name="RequestedMimeType" direction="in">
+      </arg>
+      <arg type="s" name="Resolution" direction="in">
+      </arg>
+      <arg type="ay" name="Bytes" direction="out">
+      </arg>
+      <arg type="s" name="MimeType" direction="out">
+      </arg>
+    </method>
+    <property type="s" name="DeviceType" access="read">
+    </property>
+    <property type="s" name="UDN" access="read">
+    </property>
+    <property type="s" name="FriendlyName" access="read">
+    </property>
+    <property type="s" name="IconURL" access="read">
+    </property>
+    <property type="s" name="Manufacturer" access="read">
+    </property>
+    <property type="s" name="ManufacturerUrl" access="read">
+    </property>
+    <property type="s" name="ModelDescription" access="read">
+    </property>
+    <property type="s" name="ModelName" access="read">
+    </property>
+    <property type="s" name="ModelNumber" access="read">
+    </property>
+    <property type="s" name="SerialNumber" access="read">
+    </property>
+    <property type="s" name="PresentationURL" access="read">
+    </property>
+    <property type="s" name="ProtocolInfo" access="read">
+    </property>
+  </interface>
+</node>
diff --git a/src/photos-dleyna-renderer-manager.xml b/src/photos-dleyna-renderer-manager.xml
new file mode 100644
index 0000000..d651352
--- /dev/null
+++ b/src/photos-dleyna-renderer-manager.xml
@@ -0,0 +1,46 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+                      "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<!--
+ Photos - access, organize and share your photos on GNOME
+ Copyright © 2013 Intel Corporation. All rights reserved.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+-->
+<!-- GDBus 2.37.1 -->
+<node>
+  <interface name="com.intel.dLeynaRenderer.Manager">
+    <method name="GetVersion">
+      <arg type="s" name="Version" direction="out">
+      </arg>
+    </method>
+    <method name="Release">
+    </method>
+    <method name="GetRenderers">
+      <arg type="as" name="Renderers" direction="out">
+      </arg>
+    </method>
+    <method name="Rescan">
+    </method>
+    <signal name="FoundRenderer">
+      <arg type="s" name="Path">
+      </arg>
+    </signal>
+    <signal name="LostRenderer">
+      <arg type="s" name="Path">
+      </arg>
+    </signal>
+  </interface>
+</node>
diff --git a/src/photos-dleyna-renderer-push-host.xml b/src/photos-dleyna-renderer-push-host.xml
new file mode 100644
index 0000000..f2e68bf
--- /dev/null
+++ b/src/photos-dleyna-renderer-push-host.xml
@@ -0,0 +1,36 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+                      "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<!--
+ Photos - access, organize and share your photos on GNOME
+ Copyright © 2013 Intel Corporation. All rights reserved.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+-->
+<!-- GDBus 2.37.1 -->
+<node>
+  <interface name="com.intel.dLeynaRenderer.PushHost">
+    <method name="HostFile">
+      <arg type="s" name="Path" direction="in">
+      </arg>
+      <arg type="s" name="Uri" direction="out">
+      </arg>
+    </method>
+    <method name="RemoveFile">
+      <arg type="s" name="Path" direction="in">
+      </arg>
+    </method>
+  </interface>
+</node>
diff --git a/src/photos-dlna-renderer.c b/src/photos-dlna-renderer.c
new file mode 100644
index 0000000..8ded217
--- /dev/null
+++ b/src/photos-dlna-renderer.c
@@ -0,0 +1,881 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "photos-dlna-renderer.h"
+
+
+typedef enum
+{
+  INIT_NOTHING   = 0,
+  INIT_DEVICE    = 1 << 0,
+  INIT_PUSH_HOST = 1 << 1,
+  INIT_PLAYER    = 1 << 2,
+  INIT_READY     = INIT_DEVICE | INIT_PUSH_HOST | INIT_PLAYER
+} InitStatus;
+
+
+struct _PhotosDlnaRendererPrivate
+{
+  GBusType bus_type;
+  GDBusProxyFlags flags;
+  gchar *object_path;
+  gchar *well_known_name;
+
+  GTask *init_task;
+  GError *init_error;
+
+  DleynaRendererDevice *device;
+  DleynaPushHost *push_host;
+  MprisPlayer *player;
+
+  InitStatus init_status;
+
+  GHashTable *urls_to_item;
+};
+
+
+enum
+{
+  PROP_0,
+  PROP_BUS_TYPE,
+  PROP_WELL_KNOWN_NAME,
+  PROP_FLAGS,
+  PROP_OBJECT_PATH,
+  PROP_SHARED_COUNT,
+};
+
+
+static void photos_dlna_renderer_async_initable_iface_init (GAsyncInitableIface *iface);
+
+
+G_DEFINE_TYPE_WITH_CODE (PhotosDlnaRenderer, photos_dlna_renderer, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, 
photos_dlna_renderer_async_initable_iface_init));
+
+
+static void
+photos_dlna_renderer_init_cleanup (PhotosDlnaRenderer *self)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  g_clear_error (&priv->init_error);
+  g_clear_object (&priv->init_task);
+}
+
+
+static void
+photos_dlna_renderer_dispose (GObject *object)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (object);
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  g_clear_object (&priv->device);
+  g_clear_object (&priv->push_host);
+  g_clear_object (&priv->player);
+  g_clear_pointer (&priv->urls_to_item, g_hash_table_unref);
+
+  photos_dlna_renderer_init_cleanup (self);
+
+  G_OBJECT_CLASS (photos_dlna_renderer_parent_class)->dispose (object);
+}
+
+
+static void
+photos_dlna_renderer_finalize (GObject *object)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (object);
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  g_free (priv->well_known_name);
+  g_free (priv->object_path);
+
+  G_OBJECT_CLASS (photos_dlna_renderer_parent_class)->finalize (object);
+}
+
+
+static void
+photos_dlna_renderer_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (object);
+
+  switch (prop_id)
+    {
+    case PROP_SHARED_COUNT:
+      g_value_set_uint (value, photos_dlna_renderer_get_shared_count (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+photos_dlna_renderer_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (object);
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  switch (prop_id)
+    {
+    case PROP_FLAGS:
+      priv->flags = g_value_get_flags (value);
+      break;
+
+    case PROP_BUS_TYPE:
+      priv->bus_type = g_value_get_enum (value);
+      break;
+
+    case PROP_WELL_KNOWN_NAME:
+      priv->well_known_name = g_value_dup_string (value);
+      break;
+
+    case PROP_OBJECT_PATH:
+      priv->object_path = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+photos_dlna_renderer_init (PhotosDlnaRenderer *self)
+{
+  PhotosDlnaRendererPrivate *priv;
+
+  self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self, PHOTOS_TYPE_DLNA_RENDERER,
+                                                   PhotosDlnaRendererPrivate);
+
+  priv->urls_to_item = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+
+static void
+photos_dlna_renderer_class_init (PhotosDlnaRendererClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->dispose = photos_dlna_renderer_dispose;
+  gobject_class->finalize = photos_dlna_renderer_finalize;
+  gobject_class->get_property = photos_dlna_renderer_get_property;
+  gobject_class->set_property = photos_dlna_renderer_set_property;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_FLAGS,
+                                   g_param_spec_flags ("flags",
+                                                       "Flags",
+                                                       "Proxy flags",
+                                                       G_TYPE_DBUS_PROXY_FLAGS,
+                                                       G_DBUS_PROXY_FLAGS_NONE,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_BUS_TYPE,
+                                   g_param_spec_enum ("bus-type",
+                                                      "Bus Type",
+                                                      "The bus to connect to, defaults to the session one",
+                                                      G_TYPE_BUS_TYPE,
+                                                      G_BUS_TYPE_SESSION,
+                                                      G_PARAM_WRITABLE |
+                                                      G_PARAM_CONSTRUCT_ONLY |
+                                                      G_PARAM_STATIC_NAME |
+                                                      G_PARAM_STATIC_BLURB |
+                                                      G_PARAM_STATIC_NICK));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_WELL_KNOWN_NAME,
+                                   g_param_spec_string ("well-known-name",
+                                                        "Well-Known Name",
+                                                        "The well-known name of the service",
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_OBJECT_PATH,
+                                   g_param_spec_string ("object-path",
+                                                        "Object Path",
+                                                        "The object path the proxy is for",
+                                                        NULL,
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_SHARED_COUNT,
+                                   g_param_spec_uint ("shared-count",
+                                                      "Shared Count",
+                                                      "The number of shared items",
+                                                      0, G_MAXUINT, 0,
+                                                      G_PARAM_READABLE |
+                                                      G_PARAM_STATIC_NAME |
+                                                      G_PARAM_STATIC_BLURB |
+                                                      G_PARAM_STATIC_NICK));
+
+  g_type_class_add_private (class, sizeof (PhotosDlnaRendererPrivate));
+}
+
+
+void
+photos_dlna_renderer_new_for_bus (GBusType             bus_type,
+                                  GDBusProxyFlags      flags,
+                                  const gchar         *well_known_name,
+                                  const gchar         *object_path,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  g_async_initable_new_async (PHOTOS_TYPE_DLNA_RENDERER, G_PRIORITY_DEFAULT, cancellable,
+                              callback, user_data, "flags", flags, "bus-type", bus_type,
+                              "well-known-name", well_known_name,
+                              "object-path", object_path, NULL);
+}
+
+
+/* photos_dlna_renderer_init_check_complete:
+ *
+ * Checks that all the async subtasks have completed and return the result.
+ *
+ * Note that if the caller does not take ownership of the object (eg. by not
+ * calling g_task_propagate_pointer() or in case of errors) this may lead to
+ * the object being disposed.
+ */
+static gboolean
+photos_dlna_renderer_init_check_complete (PhotosDlnaRenderer *self,
+                                          GError             *error)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  g_return_val_if_fail (g_task_is_valid (priv->init_task, self), TRUE);
+
+  /* save the first error and then wait for all tasks to be completed
+   * (succeeding or failing) before returning it */
+  if (error != NULL && priv->init_error == NULL)
+    priv->init_error = g_error_copy (error);
+
+  if (priv->init_status != INIT_READY)
+    return FALSE;
+
+  /* make sure that `self` gets NULLified if g_object_unref() is
+   * called inside g_task_return_pointer() to prevent calling
+   * photos_dlna_renderer_init_cleanup() on an already freed object */
+  g_object_add_weak_pointer (G_OBJECT (self), (gpointer *)&self);
+
+  if (priv->init_error != NULL)
+    {
+      g_task_return_error (priv->init_task, priv->init_error);
+      priv->init_error = NULL;
+    }
+  else
+    g_task_return_pointer (priv->init_task, self, g_object_unref);
+
+  if (self != NULL)
+    photos_dlna_renderer_init_cleanup (self);
+
+  g_object_remove_weak_pointer (G_OBJECT (self), (gpointer *)&self);
+
+  return TRUE;
+}
+
+
+static void
+photos_dlna_renderer_device_proxy_new_cb (GObject      *source_object,
+                                          GAsyncResult *res,
+                                          gpointer      user_data)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (user_data);
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  GError *error = NULL;
+
+  priv->init_status |= INIT_DEVICE;
+  priv->device = dleyna_renderer_device_proxy_new_for_bus_finish (res, &error);
+
+  if (error != NULL)
+    g_warning ("Unable to load the RendererDevice interface: %s", error->message);
+
+  photos_dlna_renderer_init_check_complete (self, error);
+
+  g_clear_error (&error);
+}
+
+
+static void
+photos_dlna_renderer_push_host_proxy_new_cb (GObject      *source_object,
+                                             GAsyncResult *res,
+                                             gpointer      user_data)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (user_data);
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  GError *error = NULL;
+
+  priv->init_status |= INIT_PUSH_HOST;
+  priv->push_host = dleyna_push_host_proxy_new_for_bus_finish (res, &error);
+
+  if (error != NULL)
+    g_warning ("Unable to load the PushHost interface: %s", error->message);
+
+  photos_dlna_renderer_init_check_complete (self, error);
+
+  g_clear_error (&error);
+}
+
+
+static void
+photos_dlna_renderer_player_proxy_new_cb (GObject      *source_object,
+                                          GAsyncResult *res,
+                                          gpointer      user_data)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (user_data);
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  GError *error = NULL;
+
+  priv->init_status |= INIT_PLAYER;
+  priv->player = mpris_player_proxy_new_for_bus_finish (res, &error);
+
+  if (error != NULL)
+    g_warning ("Unable to load the Player interface: %s", error->message);
+
+  photos_dlna_renderer_init_check_complete (self, error);
+
+  g_clear_error (&error);
+}
+
+
+static void
+photos_dlna_renderer_init_async (GAsyncInitable      *initable,
+                                 int                  io_priority,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (initable);
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  priv->init_task = g_task_new (initable, cancellable, callback, user_data);
+  g_task_set_priority (priv->init_task, io_priority);
+
+  /* Load all three DBus interface proxies asynchronously and then complete
+   * the initialization when all of them are ready */
+  dleyna_renderer_device_proxy_new_for_bus (priv->bus_type, priv->flags, priv->well_known_name,
+                                            priv->object_path, cancellable,
+                                            photos_dlna_renderer_device_proxy_new_cb,
+                                            self);
+  dleyna_push_host_proxy_new_for_bus (priv->bus_type, priv->flags, priv->well_known_name,
+                                      priv->object_path, cancellable,
+                                      photos_dlna_renderer_push_host_proxy_new_cb,
+                                      self);
+  mpris_player_proxy_new_for_bus (priv->bus_type, priv->flags, priv->well_known_name,
+                                  priv->object_path, cancellable,
+                                  photos_dlna_renderer_player_proxy_new_cb,
+                                  self);
+}
+
+
+static gboolean
+photos_dlna_renderer_init_finish (GAsyncInitable *initable,
+                                  GAsyncResult   *result,
+                                  GError        **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (initable)), FALSE);
+
+  return g_task_propagate_pointer (G_TASK (result), error) != NULL;
+}
+
+
+PhotosDlnaRenderer*
+photos_dlna_renderer_new_for_bus_finish (GAsyncResult *result,
+                                         GError      **error)
+{
+  GObject *object, *source_object;
+  GError *err = NULL;
+
+  source_object = g_async_result_get_source_object (result);
+  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, &err);
+  g_object_unref (source_object);
+
+  if (err != NULL)
+    {
+      g_clear_object (&object);
+      g_propagate_error (error, err);
+      return NULL;
+    }
+
+  return PHOTOS_DLNA_RENDERER (object);
+}
+
+
+static void
+photos_dlna_renderer_async_initable_iface_init (GAsyncInitableIface *iface)
+{
+  iface->init_async = photos_dlna_renderer_init_async;
+  iface->init_finish = photos_dlna_renderer_init_finish;
+}
+
+
+const gchar *
+photos_dlna_renderer_get_object_path (PhotosDlnaRenderer *self)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  return priv->object_path;
+}
+
+
+DleynaRendererDevice *
+photos_dlna_renderer_get_device (PhotosDlnaRenderer *self)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  return priv->device;
+}
+
+
+static void
+photos_dlna_renderer_share_play_cb (GObject      *source_object,
+                                    GAsyncResult *res,
+                                    gpointer      user_data)
+{
+  GTask *task = G_TASK (user_data);
+  PhotosDlnaRenderer *self;
+  PhotosDlnaRendererPrivate *priv;
+  PhotosBaseItem *item;
+  GError *error = NULL;
+
+  self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+  priv = self->priv;
+
+  mpris_player_call_play_finish (MPRIS_PLAYER (source_object), res, &error);
+  if (error != NULL)
+    {
+      g_warning ("Failed to call the Play method: %s", error->message);
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  item = g_object_get_data (G_OBJECT (task), "item");
+  g_task_return_pointer (task, g_object_ref (item), g_object_unref);
+
+out:
+  g_object_unref (task);
+}
+
+
+static void
+photos_dlna_renderer_share_open_uri_cb (GObject      *source_object,
+                                        GAsyncResult *res,
+                                        gpointer      user_data)
+{
+  GTask *task = G_TASK (user_data);
+  PhotosDlnaRenderer *self;
+  PhotosDlnaRendererPrivate *priv;
+  GError *error = NULL;
+
+  self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+  priv = self->priv;
+
+  mpris_player_call_open_uri_finish (MPRIS_PLAYER (source_object), res, &error);
+  if (error != NULL)
+    {
+      g_warning ("Failed to call the OpenUri method: %s", error->message);
+      g_task_return_error (task, error);
+      g_object_unref (task);
+      return;
+    }
+
+  /* 3) Mpris.Player.Play() */
+  mpris_player_call_play (priv->player,
+                          g_task_get_cancellable (task),
+                          photos_dlna_renderer_share_play_cb,
+                          task);
+}
+
+
+static void
+photos_dlna_renderer_share_host_file_cb (GObject      *source_object,
+                                         GAsyncResult *res,
+                                         gpointer      user_data)
+{
+  GTask *task = G_TASK (user_data);
+  PhotosDlnaRenderer *self;
+  PhotosDlnaRendererPrivate *priv;
+  PhotosBaseItem *item;
+  gchar *hosted_url;
+  GError *error = NULL;
+
+  self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+  priv = self->priv;
+
+  dleyna_push_host_call_host_file_finish (DLEYNA_PUSH_HOST (source_object), &hosted_url, res, &error);
+  if (error != NULL)
+    {
+      g_warning ("Failed to call the HostFile method: %s", error->message);
+      g_task_return_error (task, error);
+      g_object_unref (task);
+      return;
+    }
+
+  item = g_object_get_data (G_OBJECT (task), "item");
+  g_hash_table_replace (priv->urls_to_item, hosted_url, g_object_ref (item));
+  g_object_notify (G_OBJECT (self), "shared-count");
+
+  /* 2) Mpris.Player.OpenUri(hosted_url) */
+  g_debug ("%s %s", __func__, hosted_url);
+  mpris_player_call_open_uri (priv->player, hosted_url,
+                              g_task_get_cancellable (task),
+                              photos_dlna_renderer_share_open_uri_cb,
+                              task);
+}
+
+void
+photos_dlna_renderer_share (PhotosDlnaRenderer  *self,
+                            PhotosBaseItem      *item,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  GTask *task;
+  const gchar *uri;
+  gchar *filename;
+  GError *error = NULL;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_object_set_data_full (G_OBJECT (task), "item", g_object_ref (item), g_object_unref);
+
+  uri = photos_base_item_get_uri (item);
+  filename = g_filename_from_uri (uri, NULL, &error);
+  if (error != NULL)
+    {
+      g_warning ("Unable to extract the local filename for the shared item: %s", error->message);
+      g_task_return_error (task, error);
+      g_object_unref (task);
+      return;
+    }
+
+  /* This will call a sequence of DBus methods to send the item to the DMR:
+   * 1) DleynaRenderer.PushHost.HostFile (filename)
+   *    → returns hosted_url, the HTTP URL published by the local webserver
+   * 2) Mpris.Player.OpenUri(hosted_url)
+   * 3) Mpris.Player.Play()
+   *    → not really needed as OpenUri should automatically switch to Play to
+   *      avoid races, but with dleyna-renderer v0.0.1-22-g6981acf it is still
+   *      needed, see https://github.com/01org/dleyna-renderer/issues/78
+   */
+
+  /* 1) DleynaRenderer.PushHost.HostFile() */
+  dleyna_push_host_call_host_file (priv->push_host, filename,
+                                   cancellable,
+                                   photos_dlna_renderer_share_host_file_cb,
+                                   task);
+  g_free (filename);
+}
+
+
+PhotosBaseItem *
+photos_dlna_renderer_share_finish (PhotosDlnaRenderer *self,
+                                   GAsyncResult       *res,
+                                   GError            **error)
+{
+  g_return_val_if_fail (g_task_is_valid (res, G_OBJECT (self)), FALSE);
+
+  return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+
+static gboolean
+photos_dlna_renderer_match_by_item_value (gpointer key,
+                                          gpointer value,
+                                          gpointer user_data)
+{
+  PhotosBaseItem *a = PHOTOS_BASE_ITEM (value);
+  PhotosBaseItem *b = PHOTOS_BASE_ITEM (user_data);
+
+  return g_strcmp0 (photos_base_item_get_id (a), photos_base_item_get_id (b)) == 0;
+}
+
+
+static void
+photos_dlna_renderer_unshare_remove_file_cb (GObject      *source_object,
+                                             GAsyncResult *res,
+                                             gpointer      user_data)
+{
+  GTask *task = G_TASK (user_data);
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  PhotosBaseItem *item;
+  gboolean success = TRUE;
+  GError *error = NULL;
+
+  item = g_object_get_data (G_OBJECT (task), "item");
+  g_hash_table_foreach_remove (priv->urls_to_item, photos_dlna_renderer_match_by_item_value, item);
+  g_object_notify (G_OBJECT (self), "shared-count");
+
+  dleyna_push_host_call_remove_file_finish (DLEYNA_PUSH_HOST (source_object), res, &error);
+  if (error != NULL)
+    {
+      /* Assume that ignoring RemoveFile() errors is safe, since they are
+       * likely caused by the file being already removed or the DBus service
+       * having been restarted.  */
+      g_warning ("Failed to call the RemoveFile method: %s", error->message);
+      g_error_free (error);
+      success = FALSE;
+    }
+
+  g_task_return_boolean (task, success);
+
+out:
+  g_object_unref (task);
+}
+
+
+void
+photos_dlna_renderer_unshare (PhotosDlnaRenderer  *self,
+                              PhotosBaseItem      *item,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  GTask *task;
+  const gchar *uri;
+  gchar *filename;
+  GError *error = NULL;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_object_set_data_full (G_OBJECT (task), "item", g_object_ref (item), g_object_unref);
+
+  uri = photos_base_item_get_uri (item);
+  filename = g_filename_from_uri (uri, NULL, &error);
+  if (error != NULL)
+    {
+      g_warning ("Unable to extract the local filename for the shared item: %s", error->message);
+      g_task_return_error (task, error);
+      g_object_unref (task);
+      return;
+    }
+
+  dleyna_push_host_call_remove_file (priv->push_host, filename, cancellable,
+                                     photos_dlna_renderer_unshare_remove_file_cb, task);
+  g_free (filename);
+}
+
+
+void
+photos_dlna_renderer_unshare_finish (PhotosDlnaRenderer *self,
+                                     GAsyncResult       *res,
+                                     GError            **error)
+{
+  g_return_if_fail (g_task_is_valid (res, G_OBJECT (self)));
+
+  g_task_propagate_boolean (G_TASK (res), error);
+}
+
+
+static void
+photos_dlna_renderer_unshare_all_unshare_cb (GObject      *source_object,
+                                             GAsyncResult *res,
+                                             gpointer      user_data)
+{
+  PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (source_object);
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  GTask *task = G_TASK (user_data);
+  guint remaining;
+  GError *error = NULL;
+
+  /* decrement the remaining count */
+  remaining = GPOINTER_TO_UINT (g_task_get_task_data (task));
+  g_task_set_task_data (task, GUINT_TO_POINTER (--remaining), NULL);
+
+  photos_dlna_renderer_unshare_finish (self, res, &error);
+
+  if (error != NULL)
+    g_warning ("Unable to unshare item: %s", error->message);
+
+  if (remaining == 0)
+    {
+      g_task_return_boolean (task, TRUE);
+      g_object_unref (task);
+      return;
+    }
+}
+
+void
+photos_dlna_renderer_unshare_all (PhotosDlnaRenderer  *self,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  GTask *task;
+  GList *items, *item;
+  guint remaining;
+  GError *error = NULL;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  items = g_hash_table_get_values (priv->urls_to_item);
+
+  remaining = g_list_length (items);
+  g_task_set_task_data (task, GUINT_TO_POINTER (remaining), NULL);
+
+  for (item = items; item != NULL; item = g_list_next (item))
+    photos_dlna_renderer_unshare (self, PHOTOS_BASE_ITEM (item->data), cancellable,
+                                  photos_dlna_renderer_unshare_all_unshare_cb, task);
+
+  g_list_free (items);
+}
+
+
+void
+photos_dlna_renderer_unshare_all_finish (PhotosDlnaRenderer *self,
+                                         GAsyncResult       *res,
+                                         GError            **error)
+{
+  g_return_if_fail (g_task_is_valid (res, G_OBJECT (self)));
+
+  g_task_propagate_boolean (G_TASK (res), error);
+}
+
+
+static void
+photos_dlna_renderer_device_get_icon_cb (GObject      *source_object,
+                                         GAsyncResult *res,
+                                         gpointer      user_data)
+{
+  GTask *task = G_TASK (user_data);
+  PhotosDlnaRenderer *self;
+  GInputStream *icon_stream = NULL;
+  GdkPixbuf *pixbuf = NULL;
+  GVariant *icon_variant = NULL;
+  GBytes *icon_bytes = NULL;
+  const gchar *icon_data;
+  gssize icon_data_size;
+  gchar *mimetype = NULL;
+  GError *error = NULL;
+
+  self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+
+  /* The icon data is forced to be a GVariant since the GDBus bindings assume
+   * bytestrings (type 'ay') to be nul-terminated and thus do not return the length
+   * of the buffer */
+  dleyna_renderer_device_call_get_icon_finish (DLEYNA_RENDERER_DEVICE (source_object),
+                                               &icon_variant, &mimetype, res, &error);
+  if (error != NULL)
+    {
+      g_warning ("Failed to call the GetIcon method: %s", error->message);
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  /* We know that the serialization of variant containing just a byte array
+   * 'ay' is the byte  array itself */
+  icon_bytes = g_variant_get_data_as_bytes (icon_variant);
+  icon_data = g_bytes_get_data (icon_bytes, &icon_data_size);
+  icon_stream = g_memory_input_stream_new_from_data (icon_data, icon_data_size, NULL);
+
+  pixbuf = gdk_pixbuf_new_from_stream (icon_stream, g_task_get_cancellable (task), &error);
+  if (error != NULL)
+    {
+      g_warning ("Failed to parse icon data: %s", error->message);
+      g_task_return_error (task, error);
+      goto out;
+    }
+
+  g_task_return_pointer (task, pixbuf, g_object_unref);
+
+out:
+  g_free (mimetype);
+  g_clear_pointer (&icon_variant, g_variant_unref);
+  g_clear_pointer (&icon_bytes, g_bytes_unref);
+  g_clear_object (&icon_stream);
+  g_object_unref (task);
+}
+
+
+
+const gchar *
+photos_dlna_renderer_get_friendly_name (PhotosDlnaRenderer *self)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  return dleyna_renderer_device_get_friendly_name (priv->device);
+}
+
+
+void
+photos_dlna_renderer_get_icon (PhotosDlnaRenderer  *self,
+                               const gchar         *resolution,
+                               const gchar         *requested_mimetype,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+  GTask *task;
+  GError *error = NULL;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  dleyna_renderer_device_call_get_icon (priv->device, requested_mimetype, resolution,
+                                        cancellable, photos_dlna_renderer_device_get_icon_cb,
+                                        task);
+}
+
+
+GdkPixbuf *
+photos_dlna_renderer_get_icon_finish (PhotosDlnaRenderer *self,
+                                      GAsyncResult       *res,
+                                      GError            **error)
+{
+  g_return_if_fail (g_task_is_valid (res, G_OBJECT (self)));
+
+  return GDK_PIXBUF (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+
+guint
+photos_dlna_renderer_get_shared_count (PhotosDlnaRenderer *self)
+{
+  PhotosDlnaRendererPrivate *priv = self->priv;
+
+  return g_hash_table_size (priv->urls_to_item);
+}
diff --git a/src/photos-dlna-renderer.h b/src/photos-dlna-renderer.h
new file mode 100644
index 0000000..302f4ba
--- /dev/null
+++ b/src/photos-dlna-renderer.h
@@ -0,0 +1,138 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_DLNA_RENDERER_H
+#define PHOTOS_DLNA_RENDERER_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "photos-base-item.h"
+#include "photos-dleyna-renderer-device.h"
+#include "photos-dleyna-renderer-push-host.h"
+#include "photos-mpris-player.h"
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_DLNA_RENDERER (photos_dlna_renderer_get_type ())
+
+#define PHOTOS_DLNA_RENDERER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERER, PhotosDlnaRenderer))
+
+#define PHOTOS_DLNA_RENDERER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+   PHOTOS_TYPE_DLNA_RENDERER, PhotosDlnaRendererClass))
+
+#define PHOTOS_IS_DLNA_RENDERER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERER))
+
+#define PHOTOS_IS_DLNA_RENDERER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+   PHOTOS_TYPE_DLNA_RENDERER))
+
+#define PHOTOS_DLNA_RENDERER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERER, PhotosDlnaRendererClass))
+
+typedef struct _PhotosDlnaRenderer        PhotosDlnaRenderer;
+typedef struct _PhotosDlnaRendererClass   PhotosDlnaRendererClass;
+typedef struct _PhotosDlnaRendererPrivate PhotosDlnaRendererPrivate;
+
+struct _PhotosDlnaRenderer
+{
+  GObject parent_instance;
+  PhotosDlnaRendererPrivate *priv;
+};
+
+struct _PhotosDlnaRendererClass
+{
+  GObjectClass parent_class;
+};
+
+GType                 photos_dlna_renderer_get_type           (void) G_GNUC_CONST;
+
+void                  photos_dlna_renderer_new_for_bus        (GBusType             bus_type,
+                                                               GDBusProxyFlags      flags,
+                                                               const gchar         *name,
+                                                               const gchar         *object_path,
+                                                               GCancellable        *cancellable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data);
+
+PhotosDlnaRenderer   *photos_dlna_renderer_new_for_bus_finish (GAsyncResult        *res,
+                                                               GError             **error);
+
+const gchar          *photos_dlna_renderer_get_object_path    (PhotosDlnaRenderer  *renderer);
+
+DleynaRendererDevice *photos_dlna_renderer_get_device         (PhotosDlnaRenderer  *renderer);
+
+DleynaPushHost       *photos_dlna_renderer_get_push_host      (PhotosDlnaRenderer  *renderer);
+
+MprisPlayer          *photos_dlna_renderer_get_player         (PhotosDlnaRenderer  *renderer);
+
+void                  photos_dlna_renderer_share              (PhotosDlnaRenderer  *renderer,
+                                                               PhotosBaseItem      *item,
+                                                               GCancellable        *cancellable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data);
+
+PhotosBaseItem       *photos_dlna_renderer_share_finish       (PhotosDlnaRenderer  *renderer,
+                                                               GAsyncResult        *res,
+                                                               GError             **error);
+
+void                  photos_dlna_renderer_unshare            (PhotosDlnaRenderer  *self,
+                                                               PhotosBaseItem      *item,
+                                                               GCancellable        *cancellable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data);
+
+void                  photos_dlna_renderer_unshare_finish     (PhotosDlnaRenderer  *self,
+                                                               GAsyncResult        *res,
+                                                               GError             **error);
+
+void                  photos_dlna_renderer_unshare_all        (PhotosDlnaRenderer  *self,
+                                                               GCancellable        *cancellable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data);
+
+void                  photos_dlna_renderer_unshare_all_finish (PhotosDlnaRenderer  *self,
+                                                               GAsyncResult        *res,
+                                                               GError             **error);
+
+const gchar          *photos_dlna_renderer_get_friendly_name  (PhotosDlnaRenderer  *self);
+
+void                  photos_dlna_renderer_get_icon           (PhotosDlnaRenderer  *self,
+                                                               const gchar         *requested_mimetype,
+                                                               const gchar         *resolution,
+                                                               GCancellable        *cancellable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data);
+
+GdkPixbuf *           photos_dlna_renderer_get_icon_finish    (PhotosDlnaRenderer  *self,
+                                                               GAsyncResult        *res,
+                                                               GError             **error);
+
+guint                 photos_dlna_renderer_get_shared_count   (PhotosDlnaRenderer *self);
+
+G_END_DECLS
+
+#endif /* PHOTOS_DLNA_RENDERER_H */
diff --git a/src/photos-dlna-renderers-dialog.c b/src/photos-dlna-renderers-dialog.c
new file mode 100644
index 0000000..f18b6ae
--- /dev/null
+++ b/src/photos-dlna-renderers-dialog.c
@@ -0,0 +1,290 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "photos-dlna-renderers-dialog.h"
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "photos-dleyna-renderer-device.h"
+#include "photos-dleyna-renderer-push-host.h"
+#include "photos-dlna-renderer.h"
+#include "photos-dlna-renderers-manager.h"
+#include "photos-item-manager.h"
+#include "photos-local-item.h"
+#include "photos-mode-controller.h"
+#include "photos-remote-display-manager.h"
+
+
+struct _PhotosDlnaRenderersDialogPrivate
+{
+  PhotosBaseManager *item_mngr;
+  PhotosDlnaRenderersManager *renderers_mngr;
+  PhotosRemoteDisplayManager *remote_mngr;
+  PhotosModeController *mode_cntrlr;
+  GtkListBox *listbox;
+  gchar *urn;
+};
+
+
+enum
+{
+  PROP_0,
+  PROP_URN
+};
+
+
+G_DEFINE_TYPE (PhotosDlnaRenderersDialog, photos_dlna_renderers_dialog, GTK_TYPE_DIALOG);
+
+
+static void
+photos_dlna_renderers_separator_cb (GtkListBoxRow *row,
+                                    GtkListBoxRow *before,
+                                    gpointer user_data)
+{
+  GtkWidget *separator;
+
+  separator = gtk_list_box_row_get_header (row);
+  if (separator == NULL && before != NULL)
+    {
+      separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+      gtk_list_box_row_set_header (row, separator);
+      gtk_widget_show (separator);
+    }
+}
+
+
+static void
+photos_dlna_renderers_dialog_row_activated_cb (PhotosDlnaRenderersDialog *self,
+                                               GtkListBoxRow             *row,
+                                               gpointer                   user_data)
+{
+  PhotosDlnaRenderersDialogPrivate *priv = self->priv;
+  PhotosBaseItem *item;
+  PhotosDlnaRenderer *renderer;
+
+  renderer = g_object_get_data (G_OBJECT (row), "renderer");
+  item = PHOTOS_BASE_ITEM (photos_base_manager_get_object_by_id (priv->item_mngr,
+                                                                 priv->urn));
+  photos_remote_display_manager_set_renderer (priv->remote_mngr, renderer);
+  photos_remote_display_manager_render (priv->remote_mngr, item);
+
+  photos_mode_controller_set_window_mode (priv->mode_cntrlr, PHOTOS_WINDOW_MODE_OVERVIEW);
+
+  gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT);
+}
+
+
+static void
+photos_dlna_renderers_dialog_dispose (GObject *object)
+{
+  PhotosDlnaRenderersDialog *self = PHOTOS_DLNA_RENDERERS_DIALOG (object);
+  PhotosDlnaRenderersDialogPrivate *priv = self->priv;
+
+  g_clear_object (&priv->item_mngr);
+  g_clear_object (&priv->renderers_mngr);
+  g_clear_object (&priv->remote_mngr);
+  g_clear_object (&priv->mode_cntrlr);
+
+  G_OBJECT_CLASS (photos_dlna_renderers_dialog_parent_class)->dispose (object);
+}
+
+
+static void
+photos_dlna_renderers_dialog_finalize (GObject *object)
+{
+  PhotosDlnaRenderersDialog *self = PHOTOS_DLNA_RENDERERS_DIALOG (object);
+  PhotosDlnaRenderersDialogPrivate *priv = self->priv;
+
+  g_free (priv->urn);
+
+  G_OBJECT_CLASS (photos_dlna_renderers_dialog_parent_class)->finalize (object);
+}
+
+
+static void
+photos_dlna_renderers_dialog_set_property (GObject      *object,
+                                           guint         prop_id,
+                                           const GValue *value,
+                                           GParamSpec   *pspec)
+{
+  PhotosDlnaRenderersDialog *self = PHOTOS_DLNA_RENDERERS_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_URN:
+      self->priv->urn = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+photos_dlna_renderers_dialog_set_icon_cb (GObject      *source_object,
+                                          GAsyncResult *res,
+                                          gpointer      user_data)
+{
+  GdkPixbuf *pixbuf;
+  GtkImage *image = GTK_IMAGE (user_data);
+  PhotosDlnaRenderer *renderer = PHOTOS_DLNA_RENDERER (source_object);
+  GError *error = NULL;
+
+  pixbuf = photos_dlna_renderer_get_icon_finish (renderer, res, &error);
+  if (error != NULL)
+    {
+      g_warning ("Unable to load renderer icon: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  gtk_image_set_from_pixbuf (image, pixbuf);
+
+out:
+  /* release the ref we took before the async call */
+  g_object_unref (image);
+}
+
+
+static void
+photos_dlna_renderers_dialog_add_renderer (PhotosDlnaRenderersDialog *self,
+                                           PhotosDlnaRenderer *renderer)
+{
+  PhotosDlnaRenderersDialogPrivate *priv = self->priv;
+  GIcon *icon;
+  GtkWidget *row;
+  GtkWidget *row_grid;
+  GtkWidget *image;
+  GtkWidget *label;
+  const gchar *name;
+  const gchar *icon_url;
+
+  row = gtk_list_box_row_new ();
+  row_grid = gtk_grid_new ();
+  gtk_container_set_border_width (GTK_CONTAINER (row_grid), 12);
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (row_grid), GTK_ORIENTATION_HORIZONTAL);
+  gtk_grid_set_column_spacing (GTK_GRID (row_grid), 12);
+  gtk_container_add (GTK_CONTAINER (row), row_grid);
+  gtk_container_add (GTK_CONTAINER (priv->listbox), row);
+
+  g_object_set_data_full (G_OBJECT (row), "renderer", renderer, g_object_unref);
+
+  name = photos_dlna_renderer_get_friendly_name (renderer);
+
+  icon = g_themed_icon_new_with_default_fallbacks ("video-display-symbolic");
+  image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
+
+  g_object_ref (image); /* keep a ref for the following async call and release it in the callback */
+  photos_dlna_renderer_get_icon (renderer, "", "", NULL,
+                                 photos_dlna_renderers_dialog_set_icon_cb, image);
+
+  gtk_container_add (GTK_CONTAINER (row_grid), image);
+
+  label = gtk_label_new (NULL);
+  gtk_label_set_text (GTK_LABEL (label), name);
+  gtk_container_add (GTK_CONTAINER (row_grid), label);
+}
+
+
+void
+photos_dlna_renderers_dialog_response_cb (GtkDialog *dialog,
+                                          gint       response_id,
+                                          gpointer   user_data)
+{
+  gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+
+static void
+photos_dlna_renderers_dialog_init (PhotosDlnaRenderersDialog *self)
+{
+  PhotosDlnaRenderersDialogPrivate *priv;
+  GList *renderers;
+
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                            PHOTOS_TYPE_DLNA_RENDERERS_DIALOG,
+                                            PhotosDlnaRenderersDialogPrivate);
+  priv = self->priv;
+  priv->item_mngr = photos_item_manager_new ();
+  priv->renderers_mngr = photos_dlna_renderers_manager_dup_singleton ();
+  priv->remote_mngr = photos_remote_display_manager_dup_singleton ();
+  priv->mode_cntrlr = photos_mode_controller_new ();
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_list_box_set_header_func (priv->listbox, photos_dlna_renderers_separator_cb, NULL, NULL);
+
+  renderers = photos_dlna_renderers_manager_dup_renderers (priv->renderers_mngr);
+
+  while (renderers != NULL)
+    {
+      PhotosDlnaRenderer *renderer = PHOTOS_DLNA_RENDERER (renderers->data);
+
+      photos_dlna_renderers_dialog_add_renderer (self, renderer);
+      renderers = g_list_delete_link (renderers, renderers);
+    }
+
+  g_signal_connect (self, "response", G_CALLBACK (photos_dlna_renderers_dialog_response_cb), NULL);
+}
+
+
+static void
+photos_dlna_renderers_dialog_class_init (PhotosDlnaRenderersDialogClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+  object_class->dispose = photos_dlna_renderers_dialog_dispose;
+  object_class->finalize = photos_dlna_renderers_dialog_finalize;
+  object_class->set_property = photos_dlna_renderers_dialog_set_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_URN,
+                                   g_param_spec_string ("urn",
+                                                        "Uniform Resource Name",
+                                                        "An unique ID associated with this item",
+                                                        NULL,
+                                                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/photos/dlna-renderers-dialog.ui");
+
+  gtk_widget_class_bind_child (widget_class, PhotosDlnaRenderersDialogPrivate, listbox);
+  gtk_widget_class_bind_callback (widget_class, photos_dlna_renderers_dialog_row_activated_cb);
+
+  g_type_class_add_private (class, sizeof (PhotosDlnaRenderersDialogPrivate));
+}
+
+
+GtkWidget *
+photos_dlna_renderers_dialog_new (GtkWindow *parent,
+                                  const gchar *urn)
+{
+  g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL);
+
+  return g_object_new (PHOTOS_TYPE_DLNA_RENDERERS_DIALOG,
+                       "transient-for", parent, "urn", urn, NULL);
+}
diff --git a/src/photos-dlna-renderers-dialog.h b/src/photos-dlna-renderers-dialog.h
new file mode 100644
index 0000000..3858252
--- /dev/null
+++ b/src/photos-dlna-renderers-dialog.h
@@ -0,0 +1,71 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_DLNA_RENDERERS_DIALOG_H
+#define PHOTOS_DLNA_RENDERERS_DIALOG_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_DLNA_RENDERERS_DIALOG (photos_dlna_renderers_dialog_get_type ())
+
+#define PHOTOS_DLNA_RENDERERS_DIALOG(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERERS_DIALOG, PhotosDlnaRenderersDialog))
+
+#define PHOTOS_DLNA_RENDERERS_DIALOG_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+   PHOTOS_TYPE_DLNA_RENDERERS_DIALOG, PhotosDlnaRenderersDialogClass))
+
+#define PHOTOS_IS_DLNA_RENDERERS_DIALOG(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERERS_DIALOG))
+
+#define PHOTOS_IS_DLNA_RENDERERS_DIALOG_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+   PHOTOS_TYPE_DLNA_RENDERERS_DIALOG))
+
+#define PHOTOS_DLNA_RENDERERS_DIALOG_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERERS_DIALOG, PhotosDlnaRenderersDialogClass))
+
+typedef struct _PhotosDlnaRenderersDialog        PhotosDlnaRenderersDialog;
+typedef struct _PhotosDlnaRenderersDialogClass   PhotosDlnaRenderersDialogClass;
+typedef struct _PhotosDlnaRenderersDialogPrivate PhotosDlnaRenderersDialogPrivate;
+
+struct _PhotosDlnaRenderersDialog
+{
+  GtkDialog parent_instance;
+  PhotosDlnaRenderersDialogPrivate *priv;
+};
+
+struct _PhotosDlnaRenderersDialogClass
+{
+  GtkDialogClass parent_class;
+};
+
+GType               photos_dlna_renderers_dialog_get_type           (void) G_GNUC_CONST;
+
+GtkWidget          *photos_dlna_renderers_dialog_new                (GtkWindow *parent, const gchar *urn);
+
+G_END_DECLS
+
+#endif /* PHOTOS_DLNA_RENDERERS_DIALOG_H */
diff --git a/src/photos-dlna-renderers-dialog.ui b/src/photos-dlna-renderers-dialog.ui
new file mode 100644
index 0000000..dae7a15
--- /dev/null
+++ b/src/photos-dlna-renderers-dialog.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gnome-photos">
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="PhotosDlnaRenderersDialog" parent="GtkDialog">
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">DLNA Renderer Devices</property>
+    <property name="resizable">True</property>
+    <property name="modal">True</property>
+    <property name="default_width">400</property>
+    <property name="default_height">300</property>
+    <property name="type_hint">dialog</property>
+    <property name="skip_taskbar_hint">True</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">14</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action-area">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button-cancel">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="can_focus">False</property>
+            <property name="border-width">6</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkListBox" id="listbox">
+                <property name="visible">True</property>
+                <signal name="row-activated" object="PhotosDlnaRenderersDialog" swapped="yes"
+                        handler="photos_dlna_renderers_dialog_row_activated_cb" />
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">button-cancel</action-widget>
+    </action-widgets>
+  </template>
+</interface>
diff --git a/src/photos-dlna-renderers-manager.c b/src/photos-dlna-renderers-manager.c
new file mode 100644
index 0000000..888fbeb
--- /dev/null
+++ b/src/photos-dlna-renderers-manager.c
@@ -0,0 +1,267 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "photos-dlna-renderers-manager.h"
+
+#include <gio/gio.h>
+
+#include "photos-dleyna-renderer-manager.h"
+#include "photos-dlna-renderer.h"
+
+struct _PhotosDlnaRenderersManagerPrivate
+{
+  DleynaManager *proxy;
+  GHashTable *renderers;
+  GError *error;
+};
+
+enum
+{
+  RENDERER_FOUND,
+  RENDERER_LOST,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static GObject *photos_dlna_renderers_manager_singleton = NULL;
+
+G_DEFINE_TYPE (PhotosDlnaRenderersManager, photos_dlna_renderers_manager, G_TYPE_OBJECT);
+
+static void
+photos_dlna_renderers_manager_dispose (GObject *object)
+{
+  PhotosDlnaRenderersManager *self = PHOTOS_DLNA_RENDERERS_MANAGER (object);
+  PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+
+  g_clear_object (&priv->proxy);
+  g_clear_pointer (&priv->renderers, g_hash_table_unref);
+  g_clear_error (&priv->error);
+
+  G_OBJECT_CLASS (photos_dlna_renderers_manager_parent_class)->dispose (object);
+}
+
+static void
+photos_dlna_renderers_manager_renderer_new_cb (GObject      *source_object,
+                                               GAsyncResult *res,
+                                               gpointer      user_data)
+{
+  PhotosDlnaRenderersManager *self = PHOTOS_DLNA_RENDERERS_MANAGER (user_data);
+  PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+  PhotosDlnaRenderer *renderer;
+  DleynaRendererDevice *device;
+  const gchar *object_path;
+  GError *error = NULL;
+
+  renderer = photos_dlna_renderer_new_for_bus_finish (res, &error);
+  if (error != NULL)
+    {
+      g_warning ("Unable to load renderer object: %s", error->message);
+      g_propagate_error (&priv->error, error);
+      return;
+    }
+
+  device = photos_dlna_renderer_get_device (renderer);
+  object_path = photos_dlna_renderer_get_object_path (renderer);
+  g_debug ("%s '%s' %s %s", __func__,
+           dleyna_renderer_device_get_friendly_name (device),
+           dleyna_renderer_device_get_udn (device),
+           object_path);
+  g_hash_table_insert (priv->renderers, (gpointer) object_path, renderer);
+  g_signal_emit (self, signals[RENDERER_FOUND], 0, renderer);
+}
+
+static void
+photos_dlna_renderers_manager_renderer_found_cb (PhotosDlnaRenderersManager *self,
+                                                 const gchar                *object_path,
+                                                 gpointer                   *data)
+{
+  photos_dlna_renderer_new_for_bus (G_BUS_TYPE_SESSION,
+                                    G_DBUS_PROXY_FLAGS_NONE,
+                                    "com.intel.dleyna-renderer",
+                                    object_path,
+                                    NULL, /* GCancellable */
+                                    photos_dlna_renderers_manager_renderer_new_cb,
+                                    self);
+}
+
+static void
+photos_dlna_renderers_manager_renderer_lost_cb (PhotosDlnaRenderersManager *self,
+                                                const gchar                *object_path,
+                                                gpointer                   *data)
+{
+  PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+  PhotosDlnaRenderer *renderer;
+  DleynaRendererDevice *device;
+
+  renderer = PHOTOS_DLNA_RENDERER (g_hash_table_lookup (priv->renderers, object_path));
+  g_return_if_fail (renderer != NULL);
+
+  g_hash_table_steal (priv->renderers, object_path);
+  device = photos_dlna_renderer_get_device (renderer);
+  g_debug ("%s '%s' %s %s", __func__,
+           dleyna_renderer_device_get_friendly_name (device),
+           dleyna_renderer_device_get_udn (device),
+           object_path);
+  g_signal_emit (self, signals[RENDERER_LOST], 0, renderer);
+  g_object_unref (renderer);
+}
+
+static void
+photos_dlna_renderers_manager_proxy_get_renderers_cb (GObject      *source_object,
+                                                      GAsyncResult *res,
+                                                      gpointer      user_data)
+{
+  PhotosDlnaRenderersManager *self = user_data;
+  PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+  gchar **object_paths, **path;
+  GError *error = NULL;
+
+  dleyna_manager_call_get_renderers_finish (priv->proxy, &object_paths, res, &error);
+  if (error != NULL)
+    {
+      g_warning ("Unable to fetch the list of available renderers: %s", error->message);
+      g_propagate_error (&priv->error, error);
+      return;
+    }
+
+  for (path = object_paths; *path != NULL; path++)
+    photos_dlna_renderers_manager_renderer_found_cb (self, *path, NULL);
+
+  g_strfreev (object_paths);
+}
+
+static void
+photos_dlna_renderers_manager_proxy_new_cb (GObject      *source_object,
+                                            GAsyncResult *res,
+                                            gpointer      user_data)
+{
+  PhotosDlnaRenderersManager *self = user_data;
+  PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+  GError *error = NULL;
+
+  priv->proxy = dleyna_manager_proxy_new_for_bus_finish (res, &error);
+  if (error != NULL)
+    {
+      g_warning ("Unable to connect to the dLeynaRenderer.Manager DBus object: %s", error->message);
+      g_propagate_error (&priv->error, error);
+      return;
+    }
+
+  g_debug ("%s DLNA renderers manager initialized", __func__);
+
+  g_signal_connect_swapped (priv->proxy, "found-renderer",
+                            G_CALLBACK (photos_dlna_renderers_manager_renderer_found_cb), self);
+  g_signal_connect_swapped (priv->proxy, "lost-renderer",
+                            G_CALLBACK (photos_dlna_renderers_manager_renderer_lost_cb), self);
+
+  dleyna_manager_call_get_renderers (priv->proxy, NULL,
+                                     photos_dlna_renderers_manager_proxy_get_renderers_cb, self);
+}
+
+static GObject *
+photos_dlna_renderers_manager_constructor (GType                  type,
+                                           guint                  n_construct_params,
+                                           GObjectConstructParam *construct_params)
+{
+  if (photos_dlna_renderers_manager_singleton != NULL)
+    return g_object_ref (photos_dlna_renderers_manager_singleton);
+
+  photos_dlna_renderers_manager_singleton =
+      G_OBJECT_CLASS (photos_dlna_renderers_manager_parent_class)->constructor
+          (type, n_construct_params, construct_params);
+
+  g_object_add_weak_pointer (photos_dlna_renderers_manager_singleton, (gpointer) 
&photos_dlna_renderers_manager_singleton);
+
+  return photos_dlna_renderers_manager_singleton;
+}
+
+static void
+photos_dlna_renderers_manager_init (PhotosDlnaRenderersManager *self)
+{
+  PhotosDlnaRenderersManagerPrivate *priv;
+  GError *error = NULL;
+
+  self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self, PHOTOS_TYPE_DLNA_RENDERERS_MANAGER,
+                                                   PhotosDlnaRenderersManagerPrivate);
+
+  dleyna_manager_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                    G_DBUS_PROXY_FLAGS_NONE,
+                                    "com.intel.dleyna-renderer",
+                                    "/com/intel/dLeynaRenderer",
+                                    NULL, /* GCancellable */
+                                    photos_dlna_renderers_manager_proxy_new_cb,
+                                    self);
+  priv->renderers = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+}
+
+static void
+photos_dlna_renderers_manager_class_init (PhotosDlnaRenderersManagerClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->constructor = photos_dlna_renderers_manager_constructor;
+  object_class->dispose = photos_dlna_renderers_manager_dispose;
+
+  signals[RENDERER_FOUND] = g_signal_new ("renderer-found", G_TYPE_FROM_CLASS (class),
+                                          G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+                                          g_cclosure_marshal_VOID__OBJECT,
+                                          G_TYPE_NONE, 1, PHOTOS_TYPE_DLNA_RENDERER);
+
+  signals[RENDERER_LOST] = g_signal_new ("renderer-lost", G_TYPE_FROM_CLASS (class),
+                                          G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+                                          g_cclosure_marshal_VOID__OBJECT,
+                                          G_TYPE_NONE, 1, PHOTOS_TYPE_DLNA_RENDERER);
+
+  g_type_class_add_private (class, sizeof (PhotosDlnaRenderersManagerPrivate));
+}
+
+PhotosDlnaRenderersManager *
+photos_dlna_renderers_manager_dup_singleton (void)
+{
+  return g_object_new (PHOTOS_TYPE_DLNA_RENDERERS_MANAGER, NULL);
+}
+
+GList *
+photos_dlna_renderers_manager_dup_renderers (PhotosDlnaRenderersManager *self)
+{
+  PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+  GList *renderers;
+
+  renderers = g_hash_table_get_values (priv->renderers);
+  g_list_foreach (renderers, (GFunc) g_object_ref, NULL);
+
+  return renderers;
+}
+
+gboolean
+photos_dlna_renderers_manager_is_available (void)
+{
+  PhotosDlnaRenderersManager *self;
+
+  if (photos_dlna_renderers_manager_singleton == NULL)
+    return FALSE;
+
+  self = PHOTOS_DLNA_RENDERERS_MANAGER (photos_dlna_renderers_manager_singleton);
+
+  return self->priv->error == NULL;
+}
diff --git a/src/photos-dlna-renderers-manager.h b/src/photos-dlna-renderers-manager.h
new file mode 100644
index 0000000..5d523a1
--- /dev/null
+++ b/src/photos-dlna-renderers-manager.h
@@ -0,0 +1,75 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_DLNA_RENDERERS_MANAGER_H
+#define PHOTOS_DLNA_RENDERERS_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_DLNA_RENDERERS_MANAGER (photos_dlna_renderers_manager_get_type ())
+
+#define PHOTOS_DLNA_RENDERERS_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERERS_MANAGER, PhotosDlnaRenderersManager))
+
+#define PHOTOS_DLNA_RENDERERS_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+   PHOTOS_TYPE_DLNA_RENDERERS_MANAGER, PhotosDlnaRenderersManagerClass))
+
+#define PHOTOS_IS_DLNA_RENDERERS_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERERS_MANAGER))
+
+#define PHOTOS_IS_DLNA_RENDERERS_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+   PHOTOS_TYPE_DLNA_RENDERERS_MANAGER))
+
+#define PHOTOS_DLNA_RENDERERS_MANAGER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+   PHOTOS_TYPE_DLNA_RENDERERS_MANAGER, PhotosDlnaRenderersManagerClass))
+
+typedef struct _PhotosDlnaRenderersManager        PhotosDlnaRenderersManager;
+typedef struct _PhotosDlnaRenderersManagerClass   PhotosDlnaRenderersManagerClass;
+typedef struct _PhotosDlnaRenderersManagerPrivate PhotosDlnaRenderersManagerPrivate;
+
+struct _PhotosDlnaRenderersManager
+{
+  GObject parent_instance;
+  PhotosDlnaRenderersManagerPrivate *priv;
+};
+
+struct _PhotosDlnaRenderersManagerClass
+{
+  GObjectClass parent_class;
+};
+
+GType                       photos_dlna_renderers_manager_get_type      (void) G_GNUC_CONST;
+
+PhotosDlnaRenderersManager *photos_dlna_renderers_manager_dup_singleton (void);
+
+GList                      *photos_dlna_renderers_manager_dup_renderers (PhotosDlnaRenderersManager *self);
+
+gboolean                    photos_dlna_renderers_manager_is_available  (void);
+
+G_END_DECLS
+
+#endif /* PHOTOS_DLNA_RENDERERS_MANAGER_H */
diff --git a/src/photos-main-toolbar.c b/src/photos-main-toolbar.c
index 1c7c509..75cbd14 100644
--- a/src/photos-main-toolbar.c
+++ b/src/photos-main-toolbar.c
@@ -365,6 +365,9 @@ photos_main_toolbar_populate_for_preview (PhotosMainToolbar *self)
   GMenu *preview_menu;
   GtkWidget *back_button;
   GtkWidget *menu_button;
+  GtkApplication *app;
+  gboolean remote_display_available;
+  GAction *remote_display_action;
 
   photos_header_bar_set_mode (PHOTOS_HEADER_BAR (priv->toolbar), PHOTOS_HEADER_BAR_MODE_STANDALONE);
 
@@ -379,6 +382,13 @@ photos_main_toolbar_populate_for_preview (PhotosMainToolbar *self)
   gd_header_bar_pack_end (GD_HEADER_BAR (priv->toolbar), menu_button);
 
   g_simple_action_set_enabled (priv->gear_menu, TRUE);
+
+  /* Disable the remote-display-current action if the dLeyna services are not
+   * available */
+  app = photos_application_new ();
+  remote_display_action = g_action_map_lookup_action (G_ACTION_MAP (app), "remote-display-current");
+  remote_display_available = photos_dlna_renderers_manager_is_available ();
+  g_simple_action_set_enabled (G_SIMPLE_ACTION (remote_display_action), remote_display_available);
 }
 
 
diff --git a/src/photos-main.c b/src/photos-main.c
index 0220931..61d8b3d 100644
--- a/src/photos-main.c
+++ b/src/photos-main.c
@@ -29,12 +29,14 @@
 #include <glib/gi18n.h>
 
 #include "photos-application.h"
+#include "photos-remote-display-manager.h"
 
 
 gint
 main (gint argc, gchar *argv[])
 {
   GtkApplication *app;
+  PhotosRemoteDisplayManager *remote_display_mngr;
   gint exit_status;
 
   bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
@@ -44,7 +46,9 @@ main (gint argc, gchar *argv[])
   g_set_prgname (PACKAGE_TARNAME);
 
   app = photos_application_new ();
+  remote_display_mngr = photos_remote_display_manager_dup_singleton ();
   exit_status = g_application_run (G_APPLICATION (app), argc, argv);
+  g_object_unref (remote_display_mngr);
   g_object_unref (app);
 
   return exit_status;
diff --git a/src/photos-mpris-player.xml b/src/photos-mpris-player.xml
new file mode 100644
index 0000000..c956ff9
--- /dev/null
+++ b/src/photos-mpris-player.xml
@@ -0,0 +1,681 @@
+<?xml version="1.0" ?>
+<!--
+ Media Player Remote Interfacing Specification for D-Bus
+ http://cgit.freedesktop.org/xdg/mpris-spec/plain/spec/Player_Node.xml?id=v2.2
+-->
+<node name="/Player_Node" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0";>
+  <interface name="org.mpris.MediaPlayer2.Player">
+
+    <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+      <p>
+        This interface implements the methods for querying and providing basic
+        control over what is currently playing.
+      </p>
+    </tp:docstring>
+
+    <tp:enum name="Playback_Status" tp:name-for-bindings="Playback_Status" type="s">
+      <tp:enumvalue suffix="Playing" value="Playing">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>A track is currently playing.</p>
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="Paused" value="Paused">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>A track is currently paused.</p>
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="Stopped" value="Stopped">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>There is no track currently playing.</p>
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>A playback state.</p>
+      </tp:docstring>
+    </tp:enum>
+
+    <tp:enum name="Loop_Status" tp:name-for-bindings="Loop_Status" type="s">
+      <tp:enumvalue suffix="None" value="None">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>The playback will stop when there are no more tracks to play</p>
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="Track" value="Track">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>The current track will start again from the begining once it has finished playing</p>
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:enumvalue suffix="Playlist" value="Playlist">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>The playback loops through a list of tracks</p>
+        </tp:docstring>
+      </tp:enumvalue>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>A repeat / loop status</p>
+      </tp:docstring>
+    </tp:enum>
+
+    <tp:simple-type name="Track_Id" type="o" array-name="Track_Id_List">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Unique track identifier.</p>
+        <p>
+          If the media player implements the TrackList interface and allows
+          the same track to appear multiple times in the tracklist,
+          this must be unique within the scope of the tracklist.
+        </p>
+        <p>
+          Note that this should be a valid D-Bus object id, although clients
+          should not assume that any object is actually exported with any
+          interfaces at that path.
+        </p>
+        <p>
+          Media players may not use any paths starting with
+          <literal>/org/mpris</literal> unless explicitly allowed by this specification.
+          Such paths are intended to have special meaning, such as
+          <literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
+          to indicate "no track".
+        </p>
+        <tp:rationale>
+          <p>
+            This is a D-Bus object id as that is the definitive way to have
+            unique identifiers on D-Bus.  It also allows for future optional
+            expansions to the specification where tracks are exported to D-Bus
+            with an interface similar to org.gnome.UPnP.MediaItem2.
+          </p>
+        </tp:rationale>
+      </tp:docstring>
+    </tp:simple-type>
+
+    <tp:simple-type name="Playback_Rate" type="d">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>A playback rate</p>
+        <p>
+          This is a multiplier, so a value of 0.5 indicates that playback is
+          happening at half speed, while 1.5 means that 1.5 seconds of "track time"
+          is consumed every second.
+        </p>
+      </tp:docstring>
+    </tp:simple-type>
+
+    <tp:simple-type name="Volume" type="d">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Audio volume level</p>
+        <ul>
+          <li>0.0 means mute.</li>
+          <li>1.0 is a sensible maximum volume level (ex: 0dB).</li>
+        </ul>
+        <p>
+          Note that the volume may be higher than 1.0, although generally
+          clients should not attempt to set it above 1.0.
+        </p>
+      </tp:docstring>
+    </tp:simple-type>
+
+    <tp:simple-type name="Time_In_Us" type="x">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Time in microseconds.</p>
+      </tp:docstring>
+    </tp:simple-type>
+
+    <method name="Next" tp:name-for-bindings="Next">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Skips to the next track in the tracklist.</p>
+        <p>
+          If there is no next track (and endless playback and track
+          repeat are both off), stop playback.
+        </p>
+        <p>If playback is paused or stopped, it remains that way.</p>
+        <p>
+          If <tp:member-ref>CanGoNext</tp:member-ref> is
+          <strong>false</strong>, attempting to call this method should have
+          no effect.
+        </p>
+      </tp:docstring>
+    </method>
+
+    <method name="Previous" tp:name-for-bindings="Previous">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Skips to the previous track in the tracklist.</p>
+        <p>
+          If there is no previous track (and endless playback and track
+          repeat are both off), stop playback.
+        </p>
+        <p>If playback is paused or stopped, it remains that way.</p>
+        <p>
+          If <tp:member-ref>CanGoPrevious</tp:member-ref> is
+          <strong>false</strong>, attempting to call this method should have
+          no effect.
+        </p>
+      </tp:docstring>
+    </method>
+
+    <method name="Pause" tp:name-for-bindings="Pause">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Pauses playback.</p>
+        <p>If playback is already paused, this has no effect.</p>
+        <p>
+          Calling Play after this should cause playback to start again
+          from the same position.
+        </p>
+        <p>
+          If <tp:member-ref>CanPause</tp:member-ref> is
+          <strong>false</strong>, attempting to call this method should have
+          no effect.
+        </p>
+      </tp:docstring>
+    </method>
+
+    <method name="PlayPause" tp:name-for-bindings="PlayPause">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Pauses playback.</p>
+        <p>If playback is already paused, resumes playback.</p>
+        <p>If playback is stopped, starts playback.</p>
+        <p>
+          If <tp:member-ref>CanPause</tp:member-ref> is
+          <strong>false</strong>, attempting to call this method should have
+          no effect and raise an error.
+        </p>
+      </tp:docstring>
+    </method>
+
+    <method name="Stop" tp:name-for-bindings="Stop">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Stops playback.</p>
+        <p>If playback is already stopped, this has no effect.</p>
+        <p>
+          Calling Play after this should cause playback to
+          start again from the beginning of the track.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, attempting to call this method should have
+          no effect and raise an error.
+        </p>
+      </tp:docstring>
+    </method>
+
+    <method name="Play" tp:name-for-bindings="Play">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Starts or resumes playback.</p>
+        <p>If already playing, this has no effect.</p>
+        <p>If there is no track to play, this has no effect.</p>
+        <p>
+          If <tp:member-ref>CanPlay</tp:member-ref> is
+          <strong>false</strong>, attempting to call this method should have
+          no effect.
+        </p>
+      </tp:docstring>
+    </method>
+
+    <method name="Seek" tp:name-for-bindings="Seek">
+      <arg direction="in" type="x" name="Offset" tp:type="Time_In_Us">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>The number of microseconds to seek forward.</p>
+        </tp:docstring>
+      </arg>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>
+          Seeks forward in the current track by the specified number
+          of microseconds.
+        </p>
+        <p>
+          A negative value seeks back. If this would mean seeking
+          back further than the start of the track, the position
+          is set to 0.
+        </p>
+        <p>
+          If the value passed in would mean seeking beyond the end
+          of the track, acts like a call to Next.
+        </p>
+        <p>
+          If the <tp:member-ref>CanSeek</tp:member-ref> property is false,
+          this has no effect.
+        </p>
+      </tp:docstring>
+    </method>
+
+    <method name="SetPosition" tp:name-for-bindings="Set_Position">
+      <arg direction="in" type="o" tp:type="Track_Id" name="TrackId">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>The currently playing track's identifier.</p>
+          <p>
+            If this does not match the id of the currently-playing track,
+            the call is ignored as "stale".
+          </p>
+          <p>
+            <literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
+            is <em>not</em> a valid value for this argument.
+          </p>
+        </tp:docstring>
+      </arg>
+      <arg direction="in" type="x" tp:type="Time_In_Us" name="Position">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>Track position in microseconds.</p>
+          <p>This must be between 0 and &lt;track_length&gt;.</p>
+        </tp:docstring>
+      </arg>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Sets the current track position in microseconds.</p>
+        <p>If the Position argument is less than 0, do nothing.</p>
+        <p>
+          If the Position argument is greater than the track length,
+          do nothing.
+        </p>
+        <p>
+          If the <tp:member-ref>CanSeek</tp:member-ref> property is false,
+          this has no effect.
+        </p>
+        <tp:rationale>
+          <p>
+            The reason for having this method, rather than making
+            <tp:member-ref>Position</tp:member-ref> writable, is to include
+            the TrackId argument to avoid race conditions where a client tries
+            to seek to a position when the track has already changed.
+          </p>
+        </tp:rationale>
+      </tp:docstring>
+    </method>
+
+    <method name="OpenUri" tp:name-for-bindings="Open_Uri">
+      <arg direction="in" type="s" tp:type="Uri" name="Uri">
+        <tp:docstring>
+          <p>
+            Uri of the track to load. Its uri scheme should be an element of the
+            <literal>org.mpris.MediaPlayer2.SupportedUriSchemes</literal>
+            property and the mime-type should match one of the elements of the
+            <literal>org.mpris.MediaPlayer2.SupportedMimeTypes</literal>.
+          </p>
+        </tp:docstring>
+      </arg>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Opens the Uri given as an argument</p>
+        <p>If the playback is stopped, starts playing</p>
+        <p>
+          If the uri scheme or the mime-type of the uri to open is not supported,
+          this method does nothing and may raise an error.  In particular, if the
+          list of available uri schemes is empty, this method may not be
+          implemented.
+        </p>
+        <p>Clients should not assume that the Uri has been opened as soon as this
+           method returns. They should wait until the mpris:trackid field in the
+           <tp:member-ref>Metadata</tp:member-ref> property changes.
+        </p>
+        <p>
+          If the media player implements the TrackList interface, then the
+          opened track should be made part of the tracklist, the
+          <literal>org.mpris.MediaPlayer2.TrackList.TrackAdded</literal> or
+          <literal>org.mpris.MediaPlayer2.TrackList.TrackListReplaced</literal>
+          signal should be fired, as well as the
+          <literal>org.freedesktop.DBus.Properties.PropertiesChanged</literal>
+          signal on the tracklist interface.
+        </p>
+      </tp:docstring>
+    </method>
+
+    <property name="PlaybackStatus" tp:name-for-bindings="Playback_Status" type="s" 
tp:type="Playback_Status" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>The current playback status.</p>
+        <p>
+          May be "Playing", "Paused" or "Stopped".
+        </p>
+      </tp:docstring>
+    </property>
+
+    <property name="LoopStatus" type="s" access="readwrite"
+              tp:name-for-bindings="Loop_Status" tp:type="Loop_Status">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>The current loop / repeat status</p>
+        <p>May be:
+          <ul>
+            <li>"None" if the playback will stop when there are no more tracks to play</li>
+            <li>"Track" if the current track will start again from the begining once it has finished 
playing</li>
+            <li>"Playlist" if the playback loops through a list of tracks</li>
+          </ul>
+        </p>
+        <p>
+          This property is optional, and clients should deal with NotSupported
+          errors gracefully.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, attempting to set this property should have
+          no effect and raise an error.
+        </p>
+      </tp:docstring>
+    </property>
+
+    <property name="Rate" tp:name-for-bindings="Rate" type="d" tp:type="Playback_Rate" access="readwrite">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>The current playback rate.</p>
+        <p>
+          The value must fall in the range described by
+          <tp:member-ref>MinimumRate</tp:member-ref> and
+          <tp:member-ref>MaximumRate</tp:member-ref>, and must not be 0.0.  If
+          playback is paused, the <tp:member-ref>PlaybackStatus</tp:member-ref>
+          property should be used to indicate this.  A value of 0.0 should not
+          be set by the client.  If it is, the media player should act as
+          though <tp:member-ref>Pause</tp:member-ref> was called.
+        </p>
+        <p>
+          If the media player has no ability to play at speeds other than the
+          normal playback rate, this must still be implemented, and must
+          return 1.0.  The <tp:member-ref>MinimumRate</tp:member-ref> and
+          <tp:member-ref>MaximumRate</tp:member-ref> properties must also be
+          set to 1.0.
+        </p>
+        <p>
+          Not all values may be accepted by the media player.  It is left to
+          media player implementations to decide how to deal with values they
+          cannot use; they may either ignore them or pick a "best fit" value.
+          Clients are recommended to only use sensible fractions or multiples
+          of 1 (eg: 0.5, 0.25, 1.5, 2.0, etc).
+        </p>
+        <tp:rationale>
+          <p>
+            This allows clients to display (reasonably) accurate progress bars
+            without having to regularly query the media player for the current
+            position.
+          </p>
+        </tp:rationale>
+      </tp:docstring>
+    </property>
+
+    <property name="Shuffle" tp:name-for-bindings="Shuffle" type="b" access="readwrite">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>
+          A value of <strong>false</strong> indicates that playback is
+          progressing linearly through a playlist, while <strong>true</strong>
+          means playback is progressing through a playlist in some other order.
+        </p>
+        <p>
+          This property is optional, and clients should deal with NotSupported
+          errors gracefully.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, attempting to set this property should have
+          no effect and raise an error.
+        </p>
+      </tp:docstring>
+    </property>
+
+    <property name="Metadata" tp:name-for-bindings="Metadata" type="a{sv}" tp:type="Metadata_Map" 
access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>The metadata of the current element.</p>
+        <p>
+          If there is a current track, this must have a "mpris:trackid" entry
+          (of D-Bus type "o") at the very least, which contains a D-Bus path that
+          uniquely identifies this track.
+        </p>
+        <p>
+          See the type documentation for more details.
+        </p>
+      </tp:docstring>
+    </property>
+
+    <property name="Volume" type="d" tp:type="Volume" tp:name-for-bindings="Volume" access="readwrite">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true" />
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>The volume level.</p>
+        <p>
+          When setting, if a negative value is passed, the volume
+          should be set to 0.0.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, attempting to set this property should have
+          no effect and raise an error.
+        </p>
+      </tp:docstring>
+    </property>
+
+    <property name="Position" type="x" tp:type="Time_In_Us" tp:name-for-bindings="Position" access="read">
+        <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>
+            The current track position in microseconds, between 0 and
+            the 'mpris:length' metadata entry (see Metadata).
+          </p>
+          <p>
+            Note: If the media player allows it, the current playback position
+            can be changed either the SetPosition method or the Seek method on
+            this interface.  If this is not the case, the
+            <tp:member-ref>CanSeek</tp:member-ref> property is false, and
+            setting this property has no effect and can raise an error.
+          </p>
+          <p>
+            If the playback progresses in a way that is inconstistant with the
+            <tp:member-ref>Rate</tp:member-ref> property, the
+            <tp:member-ref>Seeked</tp:member-ref> signal is emited.
+          </p>
+        </tp:docstring>
+    </property>
+
+    <property name="MinimumRate" tp:name-for-bindings="Minimum_Rate" type="d" tp:type="Playback_Rate" 
access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>
+          The minimum value which the <tp:member-ref>Rate</tp:member-ref>
+          property can take.
+          Clients should not attempt to set the
+          <tp:member-ref>Rate</tp:member-ref> property below this value.
+        </p>
+        <p>
+          Note that even if this value is 0.0 or negative, clients should
+          not attempt to set the <tp:member-ref>Rate</tp:member-ref> property
+          to 0.0.
+        </p>
+        <p>This value should always be 1.0 or less.</p>
+      </tp:docstring>
+    </property>
+
+    <property name="MaximumRate" tp:name-for-bindings="Maximum_Rate" type="d" tp:type="Playback_Rate" 
access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>
+          The maximum value which the <tp:member-ref>Rate</tp:member-ref>
+          property can take.
+          Clients should not attempt to set the
+          <tp:member-ref>Rate</tp:member-ref> property above this value.
+        </p>
+        <p>
+          This value should always be 1.0 or greater.
+        </p>
+      </tp:docstring>
+    </property>
+
+    <property name="CanGoNext" tp:name-for-bindings="Can_Go_Next" type="b" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>
+          Whether the client can call the <tp:member-ref>Next</tp:member-ref>
+          method on this interface and expect the current track to change.
+        </p>
+        <p>
+          If it is unknown whether a call to <tp:member-ref>Next</tp:member-ref> will
+          be successful (for example, when streaming tracks), this property should
+          be set to <strong>true</strong>.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, this property should also be
+          <strong>false</strong>.
+        </p>
+        <tp:rationale>
+          <p>
+            Even when playback can generally be controlled, there may not
+            always be a next track to move to.
+          </p>
+        </tp:rationale>
+      </tp:docstring>
+    </property>
+
+    <property name="CanGoPrevious" tp:name-for-bindings="Can_Go_Previous" type="b" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>
+          Whether the client can call the
+          <tp:member-ref>Previous</tp:member-ref> method on this interface and
+          expect the current track to change.
+        </p>
+        <p>
+          If it is unknown whether a call to <tp:member-ref>Previous</tp:member-ref>
+          will be successful (for example, when streaming tracks), this property
+          should be set to <strong>true</strong>.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, this property should also be
+          <strong>false</strong>.
+        </p>
+        <tp:rationale>
+          <p>
+            Even when playback can generally be controlled, there may not
+            always be a next previous to move to.
+          </p>
+        </tp:rationale>
+
+      </tp:docstring>
+    </property>
+
+    <property name="CanPlay" tp:name-for-bindings="Can_Play" type="b" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Whether playback can be started using
+           <tp:member-ref>Play</tp:member-ref> or
+           <tp:member-ref>PlayPause</tp:member-ref>.
+        </p>
+        <p>
+          Note that this is related to whether there is a "current track": the
+          value should not depend on whether the track is currently paused or
+          playing.  In fact, if a track is currently playing (and
+          <tp:member-ref>CanControl</tp:member-ref> is <strong>true</strong>),
+          this should be <strong>true</strong>.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, this property should also be
+          <strong>false</strong>.
+        </p>
+        <tp:rationale>
+          <p>
+            Even when playback can generally be controlled, it may not be
+            possible to enter a "playing" state, for example if there is no
+            "current track".
+          </p>
+        </tp:rationale>
+      </tp:docstring>
+    </property>
+
+    <property name="CanPause" tp:name-for-bindings="Can_Pause" type="b" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Whether playback can be paused using
+           <tp:member-ref>Pause</tp:member-ref> or
+           <tp:member-ref>PlayPause</tp:member-ref>.
+        </p>
+        <p>
+          Note that this is an intrinsic property of the current track: its
+          value should not depend on whether the track is currently paused or
+          playing.  In fact, if playback is currently paused (and
+          <tp:member-ref>CanControl</tp:member-ref> is <strong>true</strong>),
+          this should be <strong>true</strong>.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, this property should also be
+          <strong>false</strong>.
+        </p>
+        <tp:rationale>
+          <p>
+            Not all media is pausable: it may not be possible to pause some
+            streamed media, for example.
+          </p>
+        </tp:rationale>
+      </tp:docstring>
+    </property>
+
+    <property name="CanSeek" tp:name-for-bindings="Can_Seek" type="b" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>
+          Whether the client can control the playback position using
+          <tp:member-ref>Seek</tp:member-ref> and
+          <tp:member-ref>SetPosition</tp:member-ref>.  This may be different for
+          different tracks.
+        </p>
+        <p>
+          If <tp:member-ref>CanControl</tp:member-ref> is
+          <strong>false</strong>, this property should also be
+          <strong>false</strong>.
+        </p>
+        <tp:rationale>
+          <p>
+            Not all media is seekable: it may not be possible to seek when
+            playing some streamed media, for example.
+          </p>
+        </tp:rationale>
+      </tp:docstring>
+    </property>
+
+    <property name="CanControl" tp:name-for-bindings="Can_Control" type="b" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Whether the media player may be controlled over this interface.</p>
+        <p>
+          This property is not expected to change, as it describes an intrinsic
+          capability of the implementation.
+        </p>
+        <p>
+          If this is <strong>false</strong>, clients should assume that all
+          properties on this interface are read-only (and will raise errors
+          if writing to them is attempted), no methods are implemented
+          and all other properties starting with "Can" are also
+          <strong>false</strong>.
+        </p>
+        <tp:rationale>
+          <p>
+            This allows clients to determine whether to present and enable
+            controls to the user in advance of attempting to call methods
+            and write to properties.
+          </p>
+        </tp:rationale>
+      </tp:docstring>
+    </property>
+
+    <signal name="Seeked" tp:name-for-bindings="Seeked">
+      <arg name="Position" type="x" tp:type="Time_In_Us">
+        <tp:docstring>
+          <p>The new position, in microseconds.</p>
+        </tp:docstring>
+      </arg>
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>
+          Indicates that the track position has changed in a way that is
+          inconsistant with the current playing state.
+        </p>
+        <p>When this signal is not received, clients should assume that:</p>
+        <ul>
+          <li>
+            When playing, the position progresses according to the rate property.
+          </li>
+          <li>When paused, it remains constant.</li>
+        </ul>
+        <p>
+          This signal does not need to be emitted when playback starts
+          or when the track changes, unless the track is starting at an
+          unexpected position. An expected position would be the last
+          known one when going from Paused to Playing, and 0 when going from
+          Stopped to Playing.
+        </p>
+      </tp:docstring>
+    </signal>
+
+  </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/src/photos-preview-menu.ui b/src/photos-preview-menu.ui
index ebd9cd9..78eb7bf 100644
--- a/src/photos-preview-menu.ui
+++ b/src/photos-preview-menu.ui
@@ -11,6 +11,10 @@
         <attribute name="accel">&lt;Primary&gt;p</attribute>
       </item>
       <item>
+        <attribute name="action">app.remote-display-current</attribute>
+        <attribute name="label" translatable="yes">Display on…</attribute>
+      </item>
+      <item>
         <attribute name="action">app.set-background</attribute>
         <attribute name="label" translatable="yes">Set as Background</attribute>
       </item>
diff --git a/src/photos-remote-display-manager.c b/src/photos-remote-display-manager.c
new file mode 100644
index 0000000..36b10d1
--- /dev/null
+++ b/src/photos-remote-display-manager.c
@@ -0,0 +1,289 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "photos-remote-display-manager.h"
+
+#include <gio/gio.h>
+
+#include "photos-dlna-renderers-manager.h"
+
+typedef struct {
+    PhotosRemoteDisplayManager *manager;
+    PhotosDlnaRenderer *renderer;
+    PhotosBaseItem *item;
+} Share;
+
+struct _PhotosRemoteDisplayManagerPrivate
+{
+  PhotosDlnaRenderersManager *renderers_mngr;
+  PhotosDlnaRenderer *renderer;
+};
+
+enum
+{
+  SHARE_BEGAN,
+  SHARE_ENDED,
+  SHARE_ERROR,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static GObject *remote_display_manager_singleton = NULL;
+
+G_DEFINE_TYPE (PhotosRemoteDisplayManager, photos_remote_display_manager, G_TYPE_OBJECT);
+
+
+static Share *
+photos_remote_display_manager_share_new (PhotosRemoteDisplayManager *manager,
+                                         PhotosDlnaRenderer *renderer,
+                                         PhotosBaseItem *item)
+{
+  Share *share;
+
+  share = g_new (Share, 1);
+  share->manager = manager;
+  share->renderer = g_object_ref (renderer);
+  share->item = g_object_ref (item);
+
+  return share;
+}
+
+
+static void
+photos_remote_display_manager_share_destroy (Share *share)
+{
+  g_object_unref (share->renderer);
+  g_object_unref (share->item);
+  g_free (share);
+}
+
+static void
+photos_remote_display_manager_dispose (GObject *object)
+{
+  PhotosRemoteDisplayManager *self = PHOTOS_REMOTE_DISPLAY_MANAGER (object);
+  PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+  g_clear_object (&priv->renderers_mngr);
+  g_clear_object (&priv->renderer);
+
+  G_OBJECT_CLASS (photos_remote_display_manager_parent_class)->dispose (object);
+}
+
+
+static GObject *
+photos_remote_display_manager_constructor (GType                  type,
+                                           guint                  n_construct_params,
+                                           GObjectConstructParam *construct_params)
+{
+  if (remote_display_manager_singleton != NULL)
+    return g_object_ref (remote_display_manager_singleton);
+
+  remote_display_manager_singleton =
+      G_OBJECT_CLASS (photos_remote_display_manager_parent_class)->constructor
+          (type, n_construct_params, construct_params);
+
+  g_object_add_weak_pointer (remote_display_manager_singleton, (gpointer) &remote_display_manager_singleton);
+
+  return remote_display_manager_singleton;
+}
+
+static void
+photos_remote_display_manager_renderer_lost_cb (PhotosRemoteDisplayManager *self,
+                                                PhotosDlnaRenderer         *renderer,
+                                                gpointer                    user_data)
+{
+  PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+  if (renderer == priv->renderer)
+    photos_remote_display_manager_stop (self);
+}
+
+static void
+photos_remote_display_manager_init (PhotosRemoteDisplayManager *self)
+{
+  PhotosRemoteDisplayManagerPrivate *priv;
+
+  self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self, PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER,
+                                            PhotosRemoteDisplayManagerPrivate);
+
+  /* Keep a connection to the renderers manager alive to keep the list of
+   * renderers up-to-date */
+  priv->renderers_mngr = photos_dlna_renderers_manager_dup_singleton ();
+
+  g_signal_connect_object (priv->renderers_mngr, "renderer-lost",
+                           G_CALLBACK (photos_remote_display_manager_renderer_lost_cb), self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+photos_remote_display_manager_class_init (PhotosRemoteDisplayManagerClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->constructor = photos_remote_display_manager_constructor;
+  object_class->dispose = photos_remote_display_manager_dispose;
+
+  signals[SHARE_BEGAN] = g_signal_new ("share-began", G_TYPE_FROM_CLASS (class),
+                                       G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+                                       G_TYPE_NONE, 2,
+                                       PHOTOS_TYPE_DLNA_RENDERER,
+                                       PHOTOS_TYPE_BASE_ITEM);
+
+  signals[SHARE_ENDED] = g_signal_new ("share-ended", G_TYPE_FROM_CLASS (class),
+                                       G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+                                       G_TYPE_NONE, 1,
+                                       PHOTOS_TYPE_DLNA_RENDERER);
+
+  signals[SHARE_ERROR] = g_signal_new ("share-error", G_TYPE_FROM_CLASS (class),
+                                       G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+                                       G_TYPE_NONE, 3,
+                                       PHOTOS_TYPE_DLNA_RENDERER,
+                                       PHOTOS_TYPE_BASE_ITEM,
+                                       G_TYPE_ERROR);
+
+  g_type_class_add_private (class, sizeof (PhotosRemoteDisplayManagerPrivate));
+}
+
+
+static void
+photos_remote_display_manager_share_cb (GObject      *source_object,
+                                        GAsyncResult *res,
+                                        gpointer      user_data)
+{
+  Share *share = user_data;
+  PhotosRemoteDisplayManager *self = share->manager;
+  PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+  PhotosDlnaRenderer *renderer = PHOTOS_DLNA_RENDERER (source_object);
+  PhotosBaseItem *item;
+  GError *error = NULL;
+
+  item = photos_dlna_renderer_share_finish (priv->renderer, res, &error);
+  g_object_unref (item); /* We already hold a ref to the item to be shared */
+
+  if (error != NULL)
+    {
+      g_warning ("Unable to remotely display item '%s': %s",
+                 item != NULL ? photos_base_item_get_id (item) : "(none)",
+                 error->message);
+      g_signal_emit (share->manager, signals[SHARE_ERROR], 0, share->renderer, share->item, error);
+      g_error_free (error);
+      goto out;
+    }
+
+  g_signal_emit (share->manager, signals[SHARE_BEGAN], 0, share->renderer, share->item);
+
+out:
+  photos_remote_display_manager_share_destroy (share);
+}
+
+
+static void
+photos_remote_display_manager_unshare_all_cb (GObject      *source_object,
+                                              GAsyncResult *res,
+                                              gpointer      user_data)
+{
+  PhotosDlnaRenderer *renderer = PHOTOS_DLNA_RENDERER (source_object);
+  PhotosRemoteDisplayManager *self = PHOTOS_REMOTE_DISPLAY_MANAGER (user_data);
+  GError *error = NULL;
+
+  photos_dlna_renderer_unshare_all_finish (renderer, res, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("Error while unsharing: %s", error->message);
+      g_error_free (error);
+    }
+
+  /* Avoid firing ::share-ended if any other item has been shared between the
+   * _unshare_all() call and this callback */
+  if (!photos_remote_display_manager_is_active (self))
+    g_signal_emit (self, signals[SHARE_ENDED], 0, renderer);
+}
+
+
+PhotosRemoteDisplayManager *
+photos_remote_display_manager_dup_singleton (void)
+{
+  return g_object_new (PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER, NULL);
+}
+
+
+void
+photos_remote_display_manager_set_renderer (PhotosRemoteDisplayManager *self,
+                                            PhotosDlnaRenderer         *renderer)
+{
+  PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+  g_clear_object (&priv->renderer);
+
+  if (renderer)
+    priv->renderer = g_object_ref (renderer);
+}
+
+
+PhotosDlnaRenderer *
+photos_remote_display_manager_get_renderer (PhotosRemoteDisplayManager *self)
+{
+  PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+  return priv->renderer;
+}
+
+
+void
+photos_remote_display_manager_render (PhotosRemoteDisplayManager *self,
+                                      PhotosBaseItem             *item)
+{
+  PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+  Share *share;
+
+  g_return_if_fail (priv->renderer != NULL);
+
+  share = photos_remote_display_manager_share_new (self, priv->renderer, item);
+  photos_dlna_renderer_share (share->renderer, share->item, NULL,
+                              photos_remote_display_manager_share_cb, share);
+}
+
+
+void
+photos_remote_display_manager_stop (PhotosRemoteDisplayManager *self)
+{
+  PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+  g_return_if_fail (priv->renderer != NULL);
+
+  photos_dlna_renderer_unshare_all (priv->renderer, NULL,
+                                    photos_remote_display_manager_unshare_all_cb, self);
+}
+
+
+gboolean
+photos_remote_display_manager_is_active (PhotosRemoteDisplayManager *self)
+{
+  PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+  if (priv->renderer == NULL)
+    return FALSE;
+
+  return photos_dlna_renderer_get_shared_count (priv->renderer) > 0;
+}
diff --git a/src/photos-remote-display-manager.h b/src/photos-remote-display-manager.h
new file mode 100644
index 0000000..0dfbcfa
--- /dev/null
+++ b/src/photos-remote-display-manager.h
@@ -0,0 +1,86 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_REMOTE_DISPLAY_MANAGER_H
+#define PHOTOS_REMOTE_DISPLAY_MANAGER_H
+
+#include <glib-object.h>
+
+#include "photos-base-item.h"
+#include "photos-dlna-renderer.h"
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER (photos_remote_display_manager_get_type ())
+
+#define PHOTOS_REMOTE_DISPLAY_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+   PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER, PhotosRemoteDisplayManager))
+
+#define PHOTOS_REMOTE_DISPLAY_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), \
+   PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER, PhotosRemoteDisplayManagerClass))
+
+#define PHOTOS_IS_REMOTE_DISPLAY_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+   PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER))
+
+#define PHOTOS_IS_REMOTE_DISPLAY_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+   PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER))
+
+#define PHOTOS_REMOTE_DISPLAY_MANAGER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+   PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER, PhotosRemoteDisplayManagerClass))
+
+typedef struct _PhotosRemoteDisplayManager        PhotosRemoteDisplayManager;
+typedef struct _PhotosRemoteDisplayManagerClass   PhotosRemoteDisplayManagerClass;
+typedef struct _PhotosRemoteDisplayManagerPrivate PhotosRemoteDisplayManagerPrivate;
+
+struct _PhotosRemoteDisplayManager
+{
+  GObject parent_instance;
+  PhotosRemoteDisplayManagerPrivate *priv;
+};
+
+struct _PhotosRemoteDisplayManagerClass
+{
+  GObjectClass parent_class;
+};
+
+GType                       photos_remote_display_manager_get_type        (void) G_GNUC_CONST;
+
+PhotosRemoteDisplayManager *photos_remote_display_manager_dup_singleton   (void);
+
+void                        photos_remote_display_manager_set_renderer    (PhotosRemoteDisplayManager *self,
+                                                                           PhotosDlnaRenderer         
*renderer);
+
+PhotosDlnaRenderer         *photos_remote_display_manager_get_renderer    (PhotosRemoteDisplayManager *self);
+
+void                        photos_remote_display_manager_render          (PhotosRemoteDisplayManager *self,
+                                                                           PhotosBaseItem             *item);
+
+void                        photos_remote_display_manager_stop            (PhotosRemoteDisplayManager *self);
+
+gboolean                    photos_remote_display_manager_is_active       (PhotosRemoteDisplayManager *self);
+
+G_END_DECLS
+
+#endif /* PHOTOS_REMOTE_DISPLAY_MANAGER_H */
diff --git a/src/photos.gresource.xml b/src/photos.gresource.xml
index 458373e..2414660 100644
--- a/src/photos.gresource.xml
+++ b/src/photos.gresource.xml
@@ -2,6 +2,7 @@
 <gresources>
   <gresource prefix="/org/gnome/photos">
     <file alias="app-menu.ui" preprocess="xml-stripblanks">photos-app-menu.ui</file>
+    <file alias="dlna-renderers-dialog.ui" preprocess="xml-stripblanks" 
compressed="true">photos-dlna-renderers-dialog.ui</file>
     <file alias="preview-menu.ui" preprocess="xml-stripblanks">photos-preview-menu.ui</file>
     <file alias="selection-menu.ui" preprocess="xml-stripblanks">photos-selection-menu.ui</file>
   </gresource>


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