summaryrefslogtreecommitdiff
path: root/libdleyna
diff options
context:
space:
mode:
authorRegis Merlino <regis.merlino@intel.com>2013-03-11 10:54:56 -0700
committerRegis Merlino <regis.merlino@intel.com>2013-03-11 10:54:56 -0700
commitc5a606affb909010ab43c1ef00e5750546fd1bb3 (patch)
tree19f1780f78caee4bfa33c9c42aa3f0b95dc68d29 /libdleyna
parent8de18eed056cefc2b91e6bb16e00e52378718ac0 (diff)
downloaddleyna-renderer-c5a606affb909010ab43c1ef00e5750546fd1bb3.tar.gz
[Architecture] Change directory structure to enable build from a master project
Signed-off-by: Regis Merlino <regis.merlino@intel.com>
Diffstat (limited to 'libdleyna')
-rw-r--r--libdleyna/renderer/Makefile.am61
-rw-r--r--libdleyna/renderer/async.c72
-rw-r--r--libdleyna/renderer/async.h54
-rw-r--r--libdleyna/renderer/control-point-renderer.h30
-rw-r--r--libdleyna/renderer/device.c2373
-rw-r--r--libdleyna/renderer/device.h149
-rw-r--r--libdleyna/renderer/dleyna-renderer-1.0.pc.in11
-rw-r--r--libdleyna/renderer/dleyna-renderer-service.conf.in37
-rw-r--r--libdleyna/renderer/host-service.c567
-rw-r--r--libdleyna/renderer/host-service.h43
-rw-r--r--libdleyna/renderer/prop-defs.h70
-rw-r--r--libdleyna/renderer/server.c917
-rw-r--r--libdleyna/renderer/server.h40
-rw-r--r--libdleyna/renderer/task.c378
-rw-r--r--libdleyna/renderer/task.h174
-rw-r--r--libdleyna/renderer/upnp.c752
-rw-r--r--libdleyna/renderer/upnp.h103
17 files changed, 5831 insertions, 0 deletions
diff --git a/libdleyna/renderer/Makefile.am b/libdleyna/renderer/Makefile.am
new file mode 100644
index 0000000..94d7b9e
--- /dev/null
+++ b/libdleyna/renderer/Makefile.am
@@ -0,0 +1,61 @@
+libdleyna_rendererincdir = $(includedir)/dleyna-1.0/libdleyna/renderer
+
+DLEYNA_RENDERER_VERSION = 1:0:0
+
+AM_CFLAGS = $(GLIB_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(DLEYNA_CORE_CFLAGS) \
+ $(GSSDP_CFLAGS) \
+ $(GUPNP_CFLAGS) \
+ $(GUPNPAV_CFLAGS) \
+ $(GUPNPDLNA_CFLAGS) \
+ $(SOUP_CFLAGS) \
+ -include config.h
+
+ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
+
+lib_LTLIBRARIES = libdleyna-renderer-1.0.la
+
+libdleyna_rendererinc_HEADERS = control-point-renderer.h
+
+libdleyna_renderer_1_0_la_LDFLAGS = -version-info $(DLEYNA_RENDERER_VERSION) \
+ -no-undefined
+
+libdleyna_renderer_1_0_la_SOURCES = $(libdleyna_rendererinc_HEADERS) \
+ async.c \
+ device.c \
+ host-service.c \
+ server.c \
+ task.c \
+ upnp.c
+
+libdleyna_renderer_1_0_la_LIBADD = $(GLIB_LIBS) \
+ $(GIO_LIBS) \
+ $(DLEYNA_CORE_LIBS) \
+ $(GSSDP_LIBS) \
+ $(GUPNP_LIBS) \
+ $(GUPNPAV_LIBS) \
+ $(GUPNPDLNA_LIBS) \
+ $(SOUP_LIBS)
+
+MAINTAINERCLEANFILES = Makefile.in \
+ aclocal.m4 \
+ configure \
+ config.h.in \
+ config.h.in~ \
+ build-aux/depcomp \
+ build-aux/compile \
+ build-aux/missing \
+ build-aux/install-sh
+
+sysconf_DATA = dleyna-renderer-service.conf
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = dleyna-renderer-1.0.pc
+
+EXTRA_DIST = $(sysconf_DATA)
+CLEANFILES = $(pkgconfig_DATA) dleyna-renderer-service.conf
+DISTCLEANFILES = $(pkgconfig_DATA) dleyna-renderer-service.conf
+
+maintainer-clean-local:
+ rm -rf build-aux
diff --git a/libdleyna/renderer/async.c b/libdleyna/renderer/async.c
new file mode 100644
index 0000000..03d48a6
--- /dev/null
+++ b/libdleyna/renderer/async.c
@@ -0,0 +1,72 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#include <libdleyna/core/error.h>
+#include <libdleyna/core/log.h>
+
+#include "async.h"
+
+void dlr_async_task_delete(dlr_async_task_t *task)
+{
+ if (task->free_private)
+ task->free_private(task->private);
+ if (task->cancellable)
+ g_object_unref(task->cancellable);
+}
+
+gboolean dlr_async_task_complete(gpointer user_data)
+{
+ dlr_async_task_t *cb_data = user_data;
+
+ DLEYNA_LOG_DEBUG("Enter. Error %p", (void *)cb_data->error);
+ DLEYNA_LOG_DEBUG_NL();
+
+ if (cb_data->proxy != NULL)
+ g_object_remove_weak_pointer((G_OBJECT(cb_data->proxy)),
+ (gpointer *)&cb_data->proxy);
+
+ cb_data->cb(&cb_data->task, cb_data->error);
+
+ return FALSE;
+}
+
+void dlr_async_task_cancelled(GCancellable *cancellable, gpointer user_data)
+{
+ dlr_async_task_t *cb_data = user_data;
+
+ if (cb_data->proxy != NULL)
+ gupnp_service_proxy_cancel_action(cb_data->proxy,
+ cb_data->action);
+
+ if (!cb_data->error)
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_CANCELLED,
+ "Operation cancelled.");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+}
+
+void dlr_async_task_cancel(dlr_async_task_t *task)
+{
+ if (task->cancellable)
+ g_cancellable_cancel(task->cancellable);
+}
diff --git a/libdleyna/renderer/async.h b/libdleyna/renderer/async.h
new file mode 100644
index 0000000..cf494c7
--- /dev/null
+++ b/libdleyna/renderer/async.h
@@ -0,0 +1,54 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#ifndef DLR_ASYNC_H__
+#define DLR_ASYNC_H__
+
+#include <libgupnp/gupnp-control-point.h>
+
+#include "device.h"
+#include "task.h"
+#include "upnp.h"
+
+typedef struct dlr_async_task_t_ dlr_async_task_t;
+struct dlr_async_task_t_ {
+ dlr_task_t task; /* pseudo inheritance - MUST be first field */
+ dlr_upnp_task_complete_t cb;
+ GError *error;
+ GUPnPServiceProxyAction *action;
+ GUPnPServiceProxy *proxy;
+ GCancellable *cancellable;
+ gulong cancel_id;
+ gpointer private;
+ GDestroyNotify free_private;
+ dlr_device_t *device;
+};
+
+gboolean dlr_async_task_complete(gpointer user_data);
+
+void dlr_async_task_cancelled(GCancellable *cancellable, gpointer user_data);
+
+void dlr_async_task_delete(dlr_async_task_t *task);
+
+void dlr_async_task_cancel(dlr_async_task_t *task);
+
+#endif /* DLR_ASYNC_H__ */
diff --git a/libdleyna/renderer/control-point-renderer.h b/libdleyna/renderer/control-point-renderer.h
new file mode 100644
index 0000000..37feb23
--- /dev/null
+++ b/libdleyna/renderer/control-point-renderer.h
@@ -0,0 +1,30 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Regis Merlino <regis.merlino@intel.com>
+ *
+ */
+
+#ifndef DLEYNA_CONTROL_POINT_RENDERER_H__
+#define DLEYNA_CONTROL_POINT_RENDERER_H__
+
+#include <libdleyna/core/control-point.h>
+
+const dleyna_control_point_t *dleyna_control_point_get_renderer(void);
+
+#endif /* DLEYNA_CONTROL_POINT_RENDERER_H__ */
diff --git a/libdleyna/renderer/device.c b/libdleyna/renderer/device.c
new file mode 100644
index 0000000..7ff06b8
--- /dev/null
+++ b/libdleyna/renderer/device.c
@@ -0,0 +1,2373 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+
+#include <string.h>
+#include <math.h>
+
+#include <libgupnp/gupnp-control-point.h>
+#include <libgupnp-av/gupnp-av.h>
+
+#include <libdleyna/core/error.h>
+#include <libdleyna/core/log.h>
+#include <libdleyna/core/service-task.h>
+
+#include "async.h"
+#include "device.h"
+#include "prop-defs.h"
+#include "server.h"
+
+typedef void (*dlr_device_local_cb_t)(dlr_async_task_t *cb_data);
+
+typedef struct dlr_device_data_t_ dlr_device_data_t;
+struct dlr_device_data_t_ {
+ dlr_device_local_cb_t local_cb;
+};
+
+/* Private structure used in chain task */
+typedef struct prv_new_device_ct_t_ prv_new_device_ct_t;
+struct prv_new_device_ct_t_ {
+ dlr_device_t *dev;
+ const dleyna_connector_dispatch_cb_t *dispatch_table;
+};
+
+static void prv_last_change_cb(GUPnPServiceProxy *proxy,
+ const char *variable,
+ GValue *value,
+ gpointer user_data);
+
+static void prv_sink_change_cb(GUPnPServiceProxy *proxy,
+ const char *variable,
+ GValue *value,
+ gpointer user_data);
+
+static void prv_rc_last_change_cb(GUPnPServiceProxy *proxy,
+ const char *variable,
+ GValue *value,
+ gpointer user_data);
+
+static void prv_props_update(dlr_device_t *device, dlr_task_t *task);
+
+static void prv_unref_variant(gpointer variant)
+{
+ GVariant *var = variant;
+ if (var)
+ g_variant_unref(var);
+}
+
+static void prv_props_init(dlr_props_t *props)
+{
+ props->root_props = g_hash_table_new_full(g_str_hash, g_str_equal,
+ NULL, prv_unref_variant);
+ props->player_props = g_hash_table_new_full(g_str_hash, g_str_equal,
+ NULL, prv_unref_variant);
+ props->device_props = g_hash_table_new_full(g_str_hash, g_str_equal,
+ NULL, prv_unref_variant);
+ props->synced = FALSE;
+}
+
+static void prv_props_free(dlr_props_t *props)
+{
+ g_hash_table_unref(props->root_props);
+ g_hash_table_unref(props->player_props);
+ g_hash_table_unref(props->device_props);
+}
+
+static void prv_service_proxies_free(dlr_service_proxies_t *service_proxies)
+{
+ if (service_proxies->av_proxy)
+ g_object_unref(service_proxies->av_proxy);
+ if (service_proxies->rc_proxy)
+ g_object_unref(service_proxies->rc_proxy);
+ if (service_proxies->cm_proxy)
+ g_object_unref(service_proxies->cm_proxy);
+}
+
+static void prv_context_unsubscribe(dlr_device_context_t *ctx)
+{
+ DLEYNA_LOG_DEBUG("Enter");
+
+ if (ctx->timeout_id_cm) {
+ (void) g_source_remove(ctx->timeout_id_cm);
+ ctx->timeout_id_cm = 0;
+ }
+ if (ctx->timeout_id_av) {
+ (void) g_source_remove(ctx->timeout_id_av);
+ ctx->timeout_id_av = 0;
+ }
+ if (ctx->timeout_id_rc) {
+ (void) g_source_remove(ctx->timeout_id_rc);
+ ctx->timeout_id_rc = 0;
+ }
+
+ if (ctx->subscribed_cm) {
+ (void) gupnp_service_proxy_remove_notify(
+ ctx->service_proxies.cm_proxy, "SinkProtocolInfo",
+ prv_sink_change_cb, ctx->device);
+
+ gupnp_service_proxy_set_subscribed(
+ ctx->service_proxies.cm_proxy, FALSE);
+
+ ctx->subscribed_cm = FALSE;
+ }
+ if (ctx->subscribed_av) {
+ (void) gupnp_service_proxy_remove_notify(
+ ctx->service_proxies.av_proxy, "LastChange",
+ prv_last_change_cb, ctx->device);
+
+ gupnp_service_proxy_set_subscribed(
+ ctx->service_proxies.av_proxy, FALSE);
+
+ ctx->subscribed_av = FALSE;
+ }
+ if (ctx->subscribed_rc) {
+ (void) gupnp_service_proxy_remove_notify(
+ ctx->service_proxies.rc_proxy, "LastChange",
+ prv_rc_last_change_cb, ctx->device);
+
+ gupnp_service_proxy_set_subscribed(
+ ctx->service_proxies.rc_proxy, FALSE);
+
+ ctx->subscribed_rc = FALSE;
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+static void prv_dlr_context_delete(gpointer context)
+{
+ dlr_device_context_t *ctx = context;
+
+ if (ctx) {
+ prv_context_unsubscribe(ctx);
+
+ g_free(ctx->ip_address);
+ if (ctx->device_proxy)
+ g_object_unref(ctx->device_proxy);
+ prv_service_proxies_free(&ctx->service_proxies);
+ g_free(ctx);
+ }
+}
+
+static void prv_change_props(GHashTable *props,
+ const gchar *key,
+ GVariant *value,
+ GVariantBuilder *changed_props_vb)
+{
+ g_hash_table_insert(props, (gpointer) key, value);
+ if (changed_props_vb)
+ g_variant_builder_add(changed_props_vb, "{sv}", key, value);
+}
+
+static void prv_emit_signal_properties_changed(dlr_device_t *device,
+ const char *interface,
+ GVariant *changed_props)
+{
+#if DLR_LOG_LEVEL & DLR_LOG_LEVEL_DEBUG
+ gchar *params;
+#endif
+ GVariant *val = g_variant_ref_sink(g_variant_new("(s@a{sv}as)",
+ interface,
+ changed_props,
+ NULL));
+
+ DLEYNA_LOG_DEBUG("Emitted Signal: %s.%s - ObjectPath: %s",
+ DLR_INTERFACE_PROPERTIES,
+ DLR_INTERFACE_PROPERTIES_CHANGED,
+ device->path);
+
+#if DLR_LOG_LEVEL & DLR_LOG_LEVEL_DEBUG
+ params = g_variant_print(val, FALSE);
+ DLEYNA_LOG_DEBUG("Params: %s", params);
+ g_free(params);
+#endif
+
+ dlr_renderer_get_connector()->notify(device->connection,
+ device->path,
+ DLR_INTERFACE_PROPERTIES,
+ DLR_INTERFACE_PROPERTIES_CHANGED,
+ val,
+ NULL);
+
+ g_variant_unref(val);
+}
+
+static void prv_merge_meta_data(dlr_device_t *device,
+ const gchar *key,
+ GVariant *value,
+ GVariantBuilder *changed_props_vb)
+{
+ GVariant *current_meta_data;
+ GVariantIter viter;
+ GVariantBuilder *vb;
+ GVariant *val;
+ gchar *vkey;
+ gboolean replaced = FALSE;
+ GVariant *new_val;
+
+ vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+
+ current_meta_data = g_hash_table_lookup(device->props.player_props,
+ DLR_INTERFACE_PROP_METADATA);
+ if (current_meta_data) {
+ g_variant_iter_init(&viter, current_meta_data);
+ while (g_variant_iter_next(&viter, "{&sv}", &vkey, &val)) {
+ if (!strcmp(key, vkey)) {
+ new_val = value;
+ replaced = TRUE;
+ } else {
+ new_val = val;
+ }
+ g_variant_builder_add(vb, "{sv}", vkey, new_val);
+ g_variant_unref(val);
+ }
+ }
+
+ if (!replaced)
+ g_variant_builder_add(vb, "{sv}", key, value);
+
+ val = g_variant_ref_sink(g_variant_builder_end(vb));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_METADATA,
+ val,
+ changed_props_vb);
+ g_variant_builder_unref(vb);
+}
+
+static void prv_context_new(const gchar *ip_address,
+ GUPnPDeviceProxy *proxy,
+ dlr_device_t *device,
+ dlr_device_context_t **context)
+{
+ const gchar *cm_type =
+ "urn:schemas-upnp-org:service:ConnectionManager";
+ const gchar *av_type =
+ "urn:schemas-upnp-org:service:AVTransport";
+ const gchar *rc_type =
+ "urn:schemas-upnp-org:service:RenderingControl";
+ dlr_device_context_t *ctx = g_new(dlr_device_context_t, 1);
+ dlr_service_proxies_t *service_proxies = &ctx->service_proxies;
+
+ ctx->ip_address = g_strdup(ip_address);
+ ctx->device_proxy = proxy;
+ ctx->device = device;
+ ctx->subscribed_av = FALSE;
+ ctx->subscribed_cm = FALSE;
+ ctx->subscribed_rc = FALSE;
+ ctx->timeout_id_av = 0;
+ ctx->timeout_id_cm = 0;
+ ctx->timeout_id_rc = 0;
+
+ g_object_ref(proxy);
+
+ service_proxies->cm_proxy = (GUPnPServiceProxy *)
+ gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy,
+ cm_type);
+ service_proxies->av_proxy = (GUPnPServiceProxy *)
+ gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy,
+ av_type);
+ service_proxies->rc_proxy = (GUPnPServiceProxy *)
+ gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy,
+ rc_type);
+
+ *context = ctx;
+}
+
+static dlr_device_context_t *prv_device_get_subscribed_context(
+ const dlr_device_t *device)
+{
+ dlr_device_context_t *context;
+ unsigned int i;
+
+ for (i = 0; i < device->contexts->len; ++i) {
+ context = g_ptr_array_index(device->contexts, i);
+ if (context->subscribed_av || context->subscribed_cm ||
+ context->subscribed_rc)
+ goto on_found;
+ }
+
+ return NULL;
+
+on_found:
+
+ return context;
+}
+
+static void prv_device_append_new_context(dlr_device_t *device,
+ const gchar *ip_address,
+ GUPnPDeviceProxy *proxy)
+{
+ dlr_device_context_t *new_context;
+
+ prv_context_new(ip_address, proxy, device, &new_context);
+ g_ptr_array_add(device->contexts, new_context);
+}
+
+static void prv_device_subscribe_context(dlr_device_t *device)
+{
+ dlr_device_context_t *subscribed_context;
+ dlr_device_context_t *preferred_context;
+
+ subscribed_context = prv_device_get_subscribed_context(device);
+ preferred_context = dlr_device_get_context(device);
+
+ if (subscribed_context != preferred_context) {
+ if (subscribed_context) {
+ DLEYNA_LOG_DEBUG(
+ "Subscription switch from <%s> to <%s>",
+ subscribed_context->ip_address,
+ preferred_context->ip_address);
+ prv_context_unsubscribe(subscribed_context);
+ }
+ dlr_device_subscribe_to_service_changes(device);
+ }
+}
+
+void dlr_device_append_new_context(dlr_device_t *device,
+ const gchar *ip_address,
+ GUPnPDeviceProxy *proxy)
+{
+ prv_device_append_new_context(device, ip_address, proxy);
+ prv_device_subscribe_context(device);
+}
+
+void dlr_device_delete(void *device)
+{
+ unsigned int i;
+ dlr_device_t *dev = device;
+
+ if (dev) {
+ if (dev->timeout_id)
+ (void) g_source_remove(dev->timeout_id);
+
+ for (i = 0; i < DLR_INTERFACE_INFO_MAX && dev->ids[i]; ++i)
+ (void) dlr_renderer_get_connector()->unpublish_object(
+ dev->connection,
+ dev->ids[i]);
+ g_ptr_array_unref(dev->contexts);
+ g_free(dev->path);
+ prv_props_free(&dev->props);
+
+ if (dev->transport_play_speeds != NULL)
+ g_ptr_array_free(dev->transport_play_speeds, TRUE);
+ g_free(dev->rate);
+ g_free(dev);
+ }
+}
+
+void dlr_device_unsubscribe(void *device)
+{
+ unsigned int i;
+ dlr_device_t *dev = device;
+ dlr_device_context_t *context;
+
+ if (dev) {
+ for (i = 0; i < dev->contexts->len; ++i) {
+ context = g_ptr_array_index(dev->contexts, i);
+ prv_context_unsubscribe(context);
+ }
+ }
+}
+
+static gboolean prv_re_enable_cm_subscription(gpointer user_data)
+{
+ dlr_device_context_t *context = user_data;
+
+ context->timeout_id_cm = 0;
+
+ return FALSE;
+}
+
+static void prv_cm_subscription_lost_cb(GUPnPServiceProxy *proxy,
+ const GError *reason,
+ gpointer user_data)
+{
+ dlr_device_context_t *context = user_data;
+ dlr_service_proxies_t *service_proxies = &context->service_proxies;
+
+ if (!context->timeout_id_cm) {
+ gupnp_service_proxy_set_subscribed(service_proxies->cm_proxy,
+ TRUE);
+ context->timeout_id_cm = g_timeout_add_seconds(10,
+ prv_re_enable_cm_subscription,
+ context);
+ } else {
+ g_source_remove(context->timeout_id_cm);
+ (void) gupnp_service_proxy_remove_notify(
+ service_proxies->cm_proxy, "SinkProtocolInfo",
+ prv_sink_change_cb, context->device);
+
+ context->timeout_id_cm = 0;
+ context->subscribed_cm = FALSE;
+ }
+}
+
+static gboolean prv_re_enable_av_subscription(gpointer user_data)
+{
+ dlr_device_context_t *context = user_data;
+
+ context->timeout_id_av = 0;
+
+ return FALSE;
+}
+
+static void prv_av_subscription_lost_cb(GUPnPServiceProxy *proxy,
+ const GError *reason,
+ gpointer user_data)
+{
+ dlr_device_context_t *context = user_data;
+ dlr_service_proxies_t *service_proxies = &context->service_proxies;
+
+ if (!context->timeout_id_av) {
+ gupnp_service_proxy_set_subscribed(service_proxies->av_proxy,
+ TRUE);
+ context->timeout_id_av = g_timeout_add_seconds(10,
+ prv_re_enable_av_subscription,
+ context);
+ } else {
+ g_source_remove(context->timeout_id_av);
+ (void) gupnp_service_proxy_remove_notify(
+ service_proxies->av_proxy, "LastChange",
+ prv_last_change_cb, context->device);
+
+ context->timeout_id_av = 0;
+ context->subscribed_av = FALSE;
+ }
+}
+
+static gboolean prv_re_enable_rc_subscription(gpointer user_data)
+{
+ dlr_device_context_t *context = user_data;
+
+ context->timeout_id_rc = 0;
+
+ return FALSE;
+}
+
+static void prv_rc_subscription_lost_cb(GUPnPServiceProxy *proxy,
+ const GError *reason,
+ gpointer user_data)
+{
+ dlr_device_context_t *context = user_data;
+ dlr_service_proxies_t *service_proxies = &context->service_proxies;
+
+ if (!context->timeout_id_rc) {
+ gupnp_service_proxy_set_subscribed(service_proxies->rc_proxy,
+ TRUE);
+ context->timeout_id_rc = g_timeout_add_seconds(10,
+ prv_re_enable_rc_subscription,
+ context);
+ } else {
+ g_source_remove(context->timeout_id_rc);
+ (void) gupnp_service_proxy_remove_notify(
+ service_proxies->rc_proxy, "LastChange",
+ prv_rc_last_change_cb, context->device);
+
+ context->timeout_id_rc = 0;
+ context->subscribed_rc = FALSE;
+ }
+}
+
+void dlr_device_subscribe_to_service_changes(dlr_device_t *device)
+{
+ dlr_device_context_t *context;
+ dlr_service_proxies_t *service_proxies;
+
+ context = dlr_device_get_context(device);
+ service_proxies = &context->service_proxies;
+
+ DLEYNA_LOG_DEBUG("Subscribing through context <%s>",
+ context->ip_address);
+
+ if (service_proxies->cm_proxy) {
+ gupnp_service_proxy_set_subscribed(service_proxies->cm_proxy,
+ TRUE);
+ (void) gupnp_service_proxy_add_notify(service_proxies->cm_proxy,
+ "SinkProtocolInfo",
+ G_TYPE_STRING,
+ prv_sink_change_cb,
+ device);
+ context->subscribed_cm = TRUE;
+
+ g_signal_connect(service_proxies->cm_proxy,
+ "subscription-lost",
+ G_CALLBACK(prv_cm_subscription_lost_cb),
+ context);
+ }
+
+ if (service_proxies->av_proxy) {
+ gupnp_service_proxy_set_subscribed(service_proxies->av_proxy,
+ TRUE);
+ (void) gupnp_service_proxy_add_notify(service_proxies->av_proxy,
+ "LastChange",
+ G_TYPE_STRING,
+ prv_last_change_cb,
+ device);
+ context->subscribed_av = TRUE;
+
+ g_signal_connect(service_proxies->av_proxy,
+ "subscription-lost",
+ G_CALLBACK(prv_av_subscription_lost_cb),
+ context);
+ }
+
+ if (service_proxies->rc_proxy) {
+ gupnp_service_proxy_set_subscribed(service_proxies->rc_proxy,
+ TRUE);
+ (void) gupnp_service_proxy_add_notify(service_proxies->rc_proxy,
+ "LastChange",
+ G_TYPE_STRING,
+ prv_rc_last_change_cb,
+ device);
+ context->subscribed_rc = TRUE;
+
+ g_signal_connect(service_proxies->av_proxy,
+ "subscription-lost",
+ G_CALLBACK(prv_rc_subscription_lost_cb),
+ context);
+ }
+}
+
+static void prv_as_prop_from_hash_table(const gchar *prop_name,
+ GHashTable *values, GHashTable *props)
+{
+ GVariantBuilder vb;
+ GHashTableIter iter;
+ gpointer key;
+ GVariant *val;
+
+ g_variant_builder_init(&vb, G_VARIANT_TYPE("as"));
+ g_hash_table_iter_init(&iter, values);
+
+ while (g_hash_table_iter_next(&iter, &key, NULL))
+ g_variant_builder_add(&vb, "s", key);
+
+ val = g_variant_ref_sink(g_variant_builder_end(&vb));
+ g_hash_table_insert(props, (gchar *)prop_name, val);
+}
+
+static void prv_process_protocol_info(dlr_device_t *device,
+ const gchar *protocol_info)
+{
+ GVariant *val;
+ gchar **entries;
+ gchar **type_info;
+ unsigned int i;
+ GHashTable *protocols;
+ GHashTable *types;
+ const char http_prefix[] = "http-";
+
+ DLEYNA_LOG_DEBUG("Enter");
+ DLEYNA_LOG_DEBUG("prv_process_protocol_info: %s", protocol_info);
+
+ protocols = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+ NULL);
+ types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ val = g_variant_ref_sink(g_variant_new_string(protocol_info));
+ g_hash_table_insert(device->props.device_props,
+ DLR_INTERFACE_PROP_PROTOCOL_INFO,
+ val);
+
+ entries = g_strsplit(protocol_info, ",", 0);
+
+ for (i = 0; entries[i]; ++i) {
+ type_info = g_strsplit(entries[i], ":", 0);
+
+ if (type_info[0] && type_info[1] && type_info[2]) {
+ if (!g_ascii_strncasecmp(http_prefix, type_info[0],
+ sizeof(http_prefix) - 1)) {
+ type_info[0][sizeof(http_prefix) - 2] = 0;
+ }
+
+ g_hash_table_insert(protocols,
+ g_ascii_strdown(type_info[0], -1),
+ NULL);
+ g_hash_table_insert(types,
+ g_ascii_strdown(type_info[2], -1),
+ NULL);
+ }
+
+ g_strfreev(type_info);
+ }
+
+ g_strfreev(entries);
+
+ prv_as_prop_from_hash_table(DLR_INTERFACE_PROP_SUPPORTED_URIS,
+ protocols,
+ device->props.root_props);
+
+ prv_as_prop_from_hash_table(DLR_INTERFACE_PROP_SUPPORTED_MIME,
+ types,
+ device->props.root_props);
+
+ g_hash_table_unref(types);
+ g_hash_table_unref(protocols);
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+static void prv_get_protocol_info_cb(GUPnPServiceProxy *proxy,
+ GUPnPServiceProxyAction *action,
+ gpointer user_data)
+{
+ gchar *result = NULL;
+ GError *error = NULL;
+ prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *)user_data;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ if (!gupnp_service_proxy_end_action(proxy, action, &error, "Sink",
+ G_TYPE_STRING, &result, NULL)) {
+ DLEYNA_LOG_WARNING("GetProtocolInfo operation failed: %s",
+ error->message);
+ goto on_error;
+ }
+
+ prv_process_protocol_info(priv_t->dev, result);
+
+on_error:
+
+ if (error)
+ g_error_free(error);
+
+ g_free(result);
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+static GUPnPServiceProxyAction *prv_get_protocol_info(
+ dleyna_service_task_t *task,
+ GUPnPServiceProxy *proxy,
+ gboolean *failed)
+{
+ *failed = FALSE;
+
+ return gupnp_service_proxy_begin_action(
+ proxy, "GetProtocolInfo",
+ dleyna_service_task_begin_action_cb,
+ task, NULL);
+}
+
+static GUPnPServiceProxyAction *prv_subscribe(dleyna_service_task_t *task,
+ GUPnPServiceProxy *proxy,
+ gboolean *failed)
+{
+ dlr_device_t *device;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = (dlr_device_t *)dleyna_service_task_get_user_data(task);
+
+ prv_device_subscribe_context(device);
+
+ *failed = FALSE;
+
+ DLEYNA_LOG_DEBUG("Exit");
+
+ return NULL;
+}
+
+static GUPnPServiceProxyAction *prv_declare(dleyna_service_task_t *task,
+ GUPnPServiceProxy *proxy,
+ gboolean *failed)
+{
+ unsigned int i;
+ dlr_device_t *device;
+ prv_new_device_ct_t *priv_t;
+ const dleyna_connector_dispatch_cb_t *table;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ *failed = FALSE;
+
+ priv_t = (prv_new_device_ct_t *)dleyna_service_task_get_user_data(task);
+ device = priv_t->dev;
+
+ table = priv_t->dispatch_table;
+
+ for (i = 0; i < DLR_INTERFACE_INFO_MAX; ++i) {
+ device->ids[i] = dlr_renderer_get_connector()->publish_object(
+ device->connection,
+ device->path,
+ FALSE,
+ i,
+ table + i);
+
+ if (!device->ids[i]) {
+ *failed = TRUE;
+ goto on_error;
+ }
+ }
+
+on_error:
+
+DLEYNA_LOG_DEBUG("Exit");
+
+ return NULL;
+}
+
+dlr_device_t *dlr_device_new(
+ dleyna_connector_id_t connection,
+ GUPnPDeviceProxy *proxy,
+ const gchar *ip_address,
+ guint counter,
+ const dleyna_connector_dispatch_cb_t *dispatch_table,
+ const dleyna_task_queue_key_t *queue_id)
+{
+ dlr_device_t *dev = g_new0(dlr_device_t, 1);
+ prv_new_device_ct_t *priv_t;
+ gchar *new_path;
+ dlr_device_context_t *context;
+ GUPnPServiceProxy *s_proxy;
+
+ DLEYNA_LOG_DEBUG("New Device on %s", ip_address);
+
+ new_path = g_strdup_printf("%s/%u", DLEYNA_SERVER_PATH, counter);
+ DLEYNA_LOG_DEBUG("Server Path %s", new_path);
+
+ dev = g_new0(dlr_device_t, 1);
+ priv_t = g_new0(prv_new_device_ct_t, 1);
+
+ dev->connection = connection;
+ dev->contexts = g_ptr_array_new_with_free_func(prv_dlr_context_delete);
+ dev->path = new_path;
+ dev->rate = g_strdup("1");
+
+ priv_t->dev = dev;
+ priv_t->dispatch_table = dispatch_table;
+
+ prv_props_init(&dev->props);
+
+ prv_device_append_new_context(dev, ip_address, proxy);
+
+ context = dlr_device_get_context(dev);
+ s_proxy = context->service_proxies.cm_proxy;
+
+ dleyna_service_task_add(queue_id, prv_get_protocol_info, s_proxy,
+ prv_get_protocol_info_cb, NULL, priv_t);
+
+ dleyna_service_task_add(queue_id, prv_subscribe, s_proxy,
+ NULL, NULL, dev);
+
+ dleyna_service_task_add(queue_id, prv_declare, s_proxy,
+ NULL, g_free, priv_t);
+
+ dleyna_task_queue_start(queue_id);
+
+ DLEYNA_LOG_DEBUG("Exit");
+
+ return dev;
+}
+
+dlr_device_t *dlr_device_from_path(const gchar *path, GHashTable *device_list)
+{
+ GHashTableIter iter;
+ gpointer value;
+ dlr_device_t *device;
+ dlr_device_t *retval = NULL;
+
+ g_hash_table_iter_init(&iter, device_list);
+ while (g_hash_table_iter_next(&iter, NULL, &value)) {
+ device = value;
+ if (!strcmp(device->path, path)) {
+ retval = device;
+ break;
+ }
+ }
+
+ return retval;
+}
+
+dlr_device_context_t *dlr_device_get_context(dlr_device_t *device)
+{
+ dlr_device_context_t *context;
+ unsigned int i;
+ const char ip4_local_prefix[] = "127.0.0.";
+
+ for (i = 0; i < device->contexts->len; ++i) {
+ context = g_ptr_array_index(device->contexts, i);
+ if (!strncmp(context->ip_address, ip4_local_prefix,
+ sizeof(ip4_local_prefix) - 1) ||
+ !strcmp(context->ip_address, "::1") ||
+ !strcmp(context->ip_address, "0:0:0:0:0:0:0:1"))
+ break;
+ }
+
+ if (i == device->contexts->len)
+ context = g_ptr_array_index(device->contexts, 0);
+
+ return context;
+}
+
+static void prv_get_prop(dlr_async_task_t *cb_data)
+{
+ dlr_task_get_prop_t *get_prop = &cb_data->task.ut.get_prop;
+ GVariant *res = NULL;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ if (!strcmp(get_prop->interface_name,
+ DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE)) {
+ res = g_hash_table_lookup(cb_data->device->props.device_props,
+ get_prop->prop_name);
+ } else if (!strcmp(get_prop->interface_name, DLR_INTERFACE_SERVER)) {
+ res = g_hash_table_lookup(cb_data->device->props.root_props,
+ get_prop->prop_name);
+ } else if (!strcmp(get_prop->interface_name, DLR_INTERFACE_PLAYER)) {
+ res = g_hash_table_lookup(cb_data->device->props.player_props,
+ get_prop->prop_name);
+ } else if (!strcmp(get_prop->interface_name, "")) {
+ res = g_hash_table_lookup(cb_data->device->props.root_props,
+ get_prop->prop_name);
+ if (!res)
+ res = g_hash_table_lookup(
+ cb_data->device->props.player_props,
+ get_prop->prop_name);
+
+ if (!res)
+ res = g_hash_table_lookup(
+ cb_data->device->props.device_props,
+ get_prop->prop_name);
+ } else {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_UNKNOWN_INTERFACE,
+ "Unknown Interface");
+ }
+
+ if (!res) {
+ if (!cb_data->error)
+ cb_data->error =
+ g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_UNKNOWN_PROPERTY,
+ "Property not defined for object");
+ } else {
+ cb_data->task.result = g_variant_ref(res);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+static void prv_add_props(GHashTable *props, GVariantBuilder *vb)
+{
+ GHashTableIter iter;
+ gpointer key;
+ gpointer value;
+
+ g_hash_table_iter_init(&iter, props);
+
+ while (g_hash_table_iter_next(&iter, &key, &value))
+ g_variant_builder_add(vb, "{sv}", (gchar *)key,
+ (GVariant *)value);
+}
+
+static void prv_get_props(dlr_async_task_t *cb_data)
+{
+ dlr_task_get_props_t *get_props = &cb_data->task.ut.get_props;
+ GVariantBuilder *vb;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+
+ if (!strcmp(get_props->interface_name,
+ DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE)) {
+ prv_add_props(cb_data->device->props.device_props, vb);
+ } else if (!strcmp(get_props->interface_name, DLR_INTERFACE_SERVER)) {
+ prv_add_props(cb_data->device->props.root_props, vb);
+ prv_add_props(cb_data->device->props.device_props, vb);
+ } else if (!strcmp(get_props->interface_name, DLR_INTERFACE_PLAYER)) {
+ prv_add_props(cb_data->device->props.player_props, vb);
+ } else if (!strcmp(get_props->interface_name, "")) {
+ prv_add_props(cb_data->device->props.root_props, vb);
+ prv_add_props(cb_data->device->props.player_props, vb);
+ prv_add_props(cb_data->device->props.device_props, vb);
+ } else {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_UNKNOWN_INTERFACE,
+ "Unknown Interface");
+ goto on_error;
+ }
+
+ cb_data->task.result = g_variant_ref_sink(g_variant_builder_end(vb));
+
+on_error:
+
+ g_variant_builder_unref(vb);
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+static const gchar *prv_map_transport_state(const gchar *upnp_state)
+{
+ const gchar *retval;
+
+ if (!strcmp(upnp_state, "PLAYING"))
+ retval = "Playing";
+ else if (!strcmp(upnp_state, "PAUSED_PLAYBACK") ||
+ !strcmp(upnp_state, "PAUSED_RECORDING"))
+ retval = "Paused";
+ else
+ retval = "Stopped";
+
+ return retval;
+}
+
+static gdouble prv_map_transport_speed(const gchar *upnp_speed)
+{
+ gdouble retval = 1;
+ gchar **parts = NULL;
+ gint num;
+ gint dom;
+
+ if (upnp_speed[0]) {
+ parts = g_strsplit(upnp_speed, "/", 0);
+ if (!parts[0])
+ goto on_error;
+
+ g_strstrip(parts[0]);
+ num = atoi(parts[0]);
+
+ if (parts[1]) {
+ if (parts[2])
+ goto on_error;
+
+ g_strstrip(parts[1]);
+ dom = atoi(parts[1]);
+ if (dom == 0)
+ goto on_error;
+
+ retval = num / (gdouble) dom;
+ } else {
+ retval = num;
+ }
+ }
+
+on_error:
+
+ if (parts)
+ g_strfreev(parts);
+
+ return retval;
+}
+
+static void prv_add_actions(dlr_device_t *device,
+ const gchar *actions,
+ GVariantBuilder *changed_props_vb)
+{
+ gchar **parts;
+ unsigned int i = 0;
+ GVariant *true_val;
+ GVariant *false_val;
+ gboolean play = FALSE;
+ gboolean ppause = FALSE;
+ gboolean seek = FALSE;
+ gboolean next = FALSE;
+ gboolean previous = FALSE;
+ GVariant *val;
+
+ parts = g_strsplit(actions, ",", 0);
+
+ true_val = g_variant_ref_sink(g_variant_new_boolean(TRUE));
+ false_val = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+
+ while (parts[i]) {
+ g_strstrip(parts[i]);
+
+ if (!strcmp(parts[i], "Play"))
+ play = TRUE;
+ else if (!strcmp(parts[i], "Pause"))
+ ppause = TRUE;
+ else if (!strcmp(parts[i], "Seek"))
+ seek = TRUE;
+ else if (!strcmp(parts[i], "Next"))
+ next = TRUE;
+ else if (!strcmp(parts[i], "Previous"))
+ previous = TRUE;
+ ++i;
+ }
+
+ g_variant_ref(false_val);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_CONTROL, false_val,
+ changed_props_vb);
+
+ val = play ? true_val : false_val;
+ g_variant_ref(val);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_PLAY, val,
+ changed_props_vb);
+
+ val = ppause ? true_val : false_val;
+ g_variant_ref(val);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_PAUSE, val,
+ changed_props_vb);
+
+ val = seek ? true_val : false_val;
+ g_variant_ref(val);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_SEEK, val,
+ changed_props_vb);
+
+ val = next ? true_val : false_val;
+ g_variant_ref(val);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_NEXT, val,
+ changed_props_vb);
+
+ val = previous ? true_val : false_val;
+ g_variant_ref(val);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_PREVIOUS, val,
+ changed_props_vb);
+
+ g_variant_unref(true_val);
+ g_variant_unref(false_val);
+ g_strfreev(parts);
+}
+
+static void prv_add_all_actions(dlr_device_t *device,
+ GVariantBuilder *changed_props_vb)
+{
+ GVariant *val;
+
+ val = g_variant_ref_sink(g_variant_new_boolean(TRUE));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_PLAY, val,
+ changed_props_vb);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_PAUSE, g_variant_ref(val),
+ changed_props_vb);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_SEEK, g_variant_ref(val),
+ changed_props_vb);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_NEXT, g_variant_ref(val),
+ changed_props_vb);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_PREVIOUS, g_variant_ref(val),
+ changed_props_vb);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CAN_CONTROL, g_variant_ref(val),
+ changed_props_vb);
+}
+
+static gint64 prv_duration_to_int64(const gchar *duration)
+{
+ gchar **parts;
+ unsigned int i = 0;
+ unsigned int count;
+ gint64 pos = 0;
+
+ parts = g_strsplit(duration, ":", 0);
+ for (count = 0; parts[count]; ++count)
+ ;
+
+ if (count != 3)
+ goto on_error;
+
+ /* TODO: This does not handle fractional seconds */
+
+ i = 1;
+ do {
+ --count;
+ g_strstrip(parts[count]);
+ pos += atoi(parts[count]) * i;
+ i *= 60;
+ } while (count > 0);
+
+ pos *= 1000000;
+
+on_error:
+
+ g_strfreev(parts);
+
+ return pos;
+}
+
+static gchar *prv_int64_to_duration(gint64 micro_seconds)
+{
+ GString *retval;
+ unsigned int seconds;
+
+ if (micro_seconds < 0) {
+ retval = g_string_new("-");
+ micro_seconds = -micro_seconds;
+ } else {
+ retval = g_string_new("");
+ }
+
+ /* TODO: This does not handle fractional seconds */
+
+ seconds = micro_seconds / 1000000;
+ g_string_append_printf(retval, "%02u:%02u:%02u",
+ seconds / 3600,
+ (seconds / 60) % 60,
+ seconds % 60);
+
+ return g_string_free(retval, FALSE);
+}
+
+static void prv_add_reltime(dlr_device_t *device,
+ const gchar *reltime,
+ GVariantBuilder *changed_props_vb)
+{
+ GVariant *val;
+ gint64 pos = prv_duration_to_int64(reltime);
+
+ val = g_variant_ref_sink(g_variant_new_int64(pos));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_POSITION, val,
+ changed_props_vb);
+}
+
+static void prv_found_item(GUPnPDIDLLiteParser *parser,
+ GUPnPDIDLLiteObject *object,
+ gpointer user_data)
+{
+ GVariantBuilder *vb = user_data;
+ gchar *track_id;
+ int track_number = gupnp_didl_lite_object_get_track_number(object);
+ GVariant *value;
+ const gchar *str_value;
+ GVariantBuilder *artists_vb;
+ GVariantBuilder *album_artists_vb;
+ GList *artists;
+ GList *head;
+ const gchar *artist_name;
+ const gchar *artist_role;
+
+ track_id = g_strdup_printf(DLEYNA_SERVER_OBJECT"/track/%u",
+ track_number != -1 ? track_number : 0);
+
+ value = g_variant_new_string(track_id);
+ g_variant_builder_add(vb, "{sv}", "mpris:trackid", value);
+ g_free(track_id);
+
+ if (track_number != -1) {
+ value = g_variant_new_int32(track_number);
+ g_variant_builder_add(vb, "{sv}", "mpris:trackNumber", value);
+ }
+
+ str_value = gupnp_didl_lite_object_get_title(object);
+ if (str_value) {
+ value = g_variant_new_string(str_value);
+ g_variant_builder_add(vb, "{sv}", "xesam:title", value);
+ }
+
+ str_value = gupnp_didl_lite_object_get_album_art(object);
+ if (str_value) {
+ value = g_variant_new_string(str_value);
+ g_variant_builder_add(vb, "{sv}", "mpris:artUrl", value);
+ }
+
+ str_value = gupnp_didl_lite_object_get_album(object);
+ if (str_value) {
+ value = g_variant_new_string(str_value);
+ g_variant_builder_add(vb, "{sv}", "xesam:album", value);
+ }
+
+ str_value = gupnp_didl_lite_object_get_genre(object);
+ if (str_value) {
+ value = g_variant_new_string(str_value);
+ g_variant_builder_add(vb, "{sv}", "xesam:genre", value);
+ }
+
+ artists = gupnp_didl_lite_object_get_artists(object);
+ head = artists;
+
+ if (artists) {
+ artists_vb = g_variant_builder_new(G_VARIANT_TYPE("as"));
+ album_artists_vb = g_variant_builder_new(G_VARIANT_TYPE("as"));
+ do {
+ artist_name =
+ gupnp_didl_lite_contributor_get_name(
+ artists->data);
+ artist_role = gupnp_didl_lite_contributor_get_role(
+ artists->data);
+ if (!artist_role)
+ g_variant_builder_add(artists_vb, "s",
+ artist_name);
+ else if (!strcmp(artist_role, "AlbumArtist"))
+ g_variant_builder_add(album_artists_vb, "s",
+ artist_name);
+ g_object_unref(artists->data);
+ artists = g_list_next(artists);
+ } while (artists);
+ g_list_free(head);
+ value = g_variant_builder_end(artists_vb);
+ g_variant_builder_add(vb, "{sv}", "xesam:artist", value);
+ value = g_variant_builder_end(album_artists_vb);
+ g_variant_builder_add(vb, "{sv}", "xesam:albumArtist", value);
+ g_variant_builder_unref(artists_vb);
+ g_variant_builder_unref(album_artists_vb);
+ }
+}
+
+static void prv_add_track_meta_data(dlr_device_t *device,
+ const gchar *metadata,
+ const gchar *duration,
+ const gchar *uri,
+ GVariantBuilder *changed_props_vb)
+{
+ gchar *didl = g_strdup_printf("<DIDL-Lite>%s</DIDL-Lite>", metadata);
+ GUPnPDIDLLiteParser *parser = NULL;
+ GVariantBuilder *vb;
+ GError *upnp_error = NULL;
+ GVariant *val;
+ gint error_code;
+
+ parser = gupnp_didl_lite_parser_new();
+
+ vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+
+ if (duration) {
+ val = g_variant_new_int64(prv_duration_to_int64(duration));
+ g_variant_builder_add(vb, "{sv}", "mpris:length", val);
+ }
+
+ if (uri) {
+ val = g_variant_new_string(uri);
+ g_variant_builder_add(vb, "{sv}", "xesam:url", val);
+ }
+
+ g_signal_connect(parser, "object-available" ,
+ G_CALLBACK(prv_found_item), vb);
+
+ if (!gupnp_didl_lite_parser_parse_didl(parser, didl, &upnp_error)) {
+ error_code = upnp_error->code;
+ g_error_free(upnp_error);
+ if (error_code != GUPNP_XML_ERROR_EMPTY_NODE)
+ goto on_error;
+ }
+
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_METADATA,
+ g_variant_ref_sink(g_variant_builder_end(vb)),
+ changed_props_vb);
+
+on_error:
+
+ if (parser)
+ g_object_unref(parser);
+
+ g_variant_builder_unref(vb);
+ g_free(didl);
+}
+
+static void prv_last_change_cb(GUPnPServiceProxy *proxy,
+ const char *variable,
+ GValue *value,
+ gpointer user_data)
+{
+ GUPnPLastChangeParser *parser;
+ dlr_device_t *device = user_data;
+ GVariantBuilder *changed_props_vb;
+ GVariant *changed_props;
+ gchar *meta_data = NULL;
+ gchar *actions = NULL;
+ gchar *play_speed = NULL;
+ gchar *state = NULL;
+ gchar *duration = NULL;
+ gchar *uri = NULL;
+ guint tracks_number = G_MAXUINT;
+ guint current_track = G_MAXUINT;
+ GVariant *val;
+
+ parser = gupnp_last_change_parser_new();
+
+ if (!gupnp_last_change_parser_parse_last_change(
+ parser, 0,
+ g_value_get_string(value),
+ NULL,
+ "CurrentTrackMetaData", G_TYPE_STRING, &meta_data,
+ "CurrentTransportActions", G_TYPE_STRING, &actions,
+ "TransportPlaySpeed", G_TYPE_STRING, &play_speed,
+ "TransportState", G_TYPE_STRING, &state,
+ "CurrentTrackDuration", G_TYPE_STRING, &duration,
+ "CurrentTrackURI", G_TYPE_STRING, &uri,
+ "NumberOfTracks", G_TYPE_UINT, &tracks_number,
+ "CurrentTrack", G_TYPE_UINT, &current_track,
+ NULL))
+ goto on_error;
+
+ changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+
+ if (meta_data) {
+ prv_add_track_meta_data(device,
+ meta_data,
+ duration,
+ uri,
+ changed_props_vb);
+ g_free(meta_data);
+ } else {
+ if (duration) {
+ val = g_variant_new_int64(prv_duration_to_int64(
+ duration));
+ val = g_variant_ref_sink(val);
+ prv_merge_meta_data(device,
+ "mpris:length",
+ val,
+ changed_props_vb);
+ g_variant_unref(val);
+ }
+
+ if (uri) {
+ val = g_variant_ref_sink(g_variant_new_string(uri));
+ prv_merge_meta_data(device,
+ "xesam:url",
+ val,
+ changed_props_vb);
+ g_variant_unref(val);
+ }
+ }
+
+ g_free(duration);
+ g_free(uri);
+
+ if (actions) {
+ prv_add_actions(device, actions, changed_props_vb);
+ g_free(actions);
+ }
+
+ if (play_speed) {
+ val = g_variant_ref_sink(
+ g_variant_new_double(
+ prv_map_transport_speed(play_speed)));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_RATE, val,
+ changed_props_vb);
+
+ g_free(device->rate);
+ device->rate = play_speed;
+ }
+
+ if (state) {
+ val = g_variant_ref_sink(
+ g_variant_new_string(
+ prv_map_transport_state(state)));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_PLAYBACK_STATUS, val,
+ changed_props_vb);
+ g_free(state);
+ }
+
+ if (tracks_number != G_MAXUINT) {
+ val = g_variant_ref_sink(g_variant_new_uint32(tracks_number));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_NUMBER_OF_TRACKS, val,
+ changed_props_vb);
+ }
+
+ if (current_track != G_MAXUINT) {
+ val = g_variant_ref_sink(g_variant_new_uint32(current_track));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_CURRENT_TRACK, val,
+ changed_props_vb);
+ }
+
+ changed_props = g_variant_ref_sink(
+ g_variant_builder_end(changed_props_vb));
+ prv_emit_signal_properties_changed(device,
+ DLR_INTERFACE_PLAYER,
+ changed_props);
+ g_variant_unref(changed_props);
+ g_variant_builder_unref(changed_props_vb);
+
+on_error:
+
+ g_object_unref(parser);
+}
+
+static void prv_rc_last_change_cb(GUPnPServiceProxy *proxy,
+ const char *variable,
+ GValue *value,
+ gpointer user_data)
+{
+ GUPnPLastChangeParser *parser;
+ dlr_device_t *device = user_data;
+ GVariantBuilder *changed_props_vb;
+ GVariant *changed_props;
+ GVariant *val;
+ guint device_volume;
+ double mpris_volume;
+
+ parser = gupnp_last_change_parser_new();
+
+ if (!gupnp_last_change_parser_parse_last_change(
+ parser, 0,
+ g_value_get_string(value),
+ NULL,
+ "Volume", G_TYPE_UINT, &device_volume,
+ NULL))
+ goto on_error;
+
+ if (device->props.synced == FALSE)
+ prv_props_update(device, NULL);
+
+ if (device->max_volume == 0)
+ goto on_error;
+
+ changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+
+ mpris_volume = (double)device_volume / (double)device->max_volume;
+ val = g_variant_ref_sink(g_variant_new_double(mpris_volume));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_VOLUME, val,
+ changed_props_vb);
+
+ changed_props = g_variant_ref_sink(
+ g_variant_builder_end(changed_props_vb));
+ prv_emit_signal_properties_changed(device,
+ DLR_INTERFACE_PLAYER,
+ changed_props);
+ g_variant_unref(changed_props);
+ g_variant_builder_unref(changed_props_vb);
+
+on_error:
+
+ g_object_unref(parser);
+}
+
+static void prv_sink_change_cb(GUPnPServiceProxy *proxy,
+ const char *variable,
+ GValue *value,
+ gpointer user_data)
+{
+ dlr_device_t *device = user_data;
+ const gchar *sink;
+
+ sink = g_value_get_string(value);
+
+ if (sink)
+ prv_process_protocol_info(device, sink);
+}
+
+static void prv_get_position_info_cb(GUPnPServiceProxy *proxy,
+ GUPnPServiceProxyAction *action,
+ gpointer user_data)
+{
+ gchar *rel_pos = NULL;
+ dlr_async_task_t *cb_data = user_data;
+ GError *upnp_error = NULL;
+ dlr_device_data_t *device_data = cb_data->private;
+ GVariantBuilder *changed_props_vb;
+ GVariant *changed_props;
+
+ if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
+ &upnp_error,
+ "RelTime",
+ G_TYPE_STRING, &rel_pos, NULL)) {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OPERATION_FAILED,
+ "GetPositionInfo operation failed: %s",
+ upnp_error->message);
+ g_error_free(upnp_error);
+
+ goto on_error;
+ }
+
+ changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+
+ g_strstrip(rel_pos);
+ prv_add_reltime(cb_data->device, rel_pos, changed_props_vb);
+ g_free(rel_pos);
+
+ changed_props = g_variant_ref_sink(
+ g_variant_builder_end(changed_props_vb));
+ prv_emit_signal_properties_changed(cb_data->device,
+ DLR_INTERFACE_PLAYER,
+ changed_props);
+ g_variant_unref(changed_props);
+ g_variant_builder_unref(changed_props_vb);
+
+on_error:
+
+ device_data->local_cb(cb_data);
+}
+
+static void prv_get_position_info(dlr_async_task_t *cb_data)
+{
+ dlr_device_context_t *context;
+
+ context = dlr_device_get_context(cb_data->device);
+
+ cb_data->cancel_id =
+ g_cancellable_connect(cb_data->cancellable,
+ G_CALLBACK(dlr_async_task_cancelled),
+ cb_data, NULL);
+ cb_data->proxy = context->service_proxies.av_proxy;
+
+ g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)),
+ (gpointer *)&cb_data->proxy);
+
+ cb_data->action =
+ gupnp_service_proxy_begin_action(cb_data->proxy,
+ "GetPositionInfo",
+ prv_get_position_info_cb,
+ cb_data,
+ "InstanceID", G_TYPE_INT, 0,
+ NULL);
+}
+
+/***********************************************************************/
+/* Rational numbers parameters of the following functions are formed */
+/* like this : «2» «5/6». A decimal notation like «2.6» is not allowed */
+/***********************************************************************/
+static inline long prv_rational_get_numerator(const char *r)
+{
+ return strtol(r, NULL, 10);
+}
+
+static long prv_rational_get_denominator(const char *r)
+{
+ char *div_pos = strstr(r, "/");
+ if (div_pos == NULL)
+ goto exit;
+
+ return strtol(div_pos + 1, NULL, 10);
+
+exit:
+ return 1;
+}
+
+static double prv_rational_to_double(const char *r, double precision)
+{
+ long p;
+ long q;
+ double result;
+
+ p = prv_rational_get_numerator(r);
+ if (p == 0)
+ goto error;
+
+ q = prv_rational_get_denominator(r);
+ if (q == 0)
+ goto error;
+
+ result = (double)p/(double)q;
+
+ if (precision != 0)
+ result = round(result/precision) * precision;
+
+ return result;
+
+error:
+ return 0.0;
+}
+
+static inline gboolean prv_rational_is_invalid(const char *val)
+{
+ return (prv_rational_get_numerator(val) == 0) ||
+ (prv_rational_get_denominator(val) == 0);
+}
+
+static gint prv_compare_rationals(const gchar *a, const gchar *b)
+{
+ long a_numerator = prv_rational_get_numerator(a);
+ long b_numerator = prv_rational_get_numerator(b);
+ long a_denominator = prv_rational_get_denominator(a);
+ long b_denominator = prv_rational_get_denominator(b);
+
+ return (a_numerator * b_denominator) - (b_numerator * a_denominator);
+}
+
+static void prv_get_rates_values(const GUPnPServiceStateVariableInfo *svi,
+ GVariant **mpris_tp_speeds,
+ GPtrArray **upnp_tp_speeds,
+ double *min_rate, double *max_rate)
+{
+ char *rate;
+ char *min_rate_str;
+ char *max_rate_str;
+ GList *list;
+ GVariantBuilder vb;
+ const double precision = 0.01;
+
+ if ((svi == NULL) || (svi->allowed_values == NULL))
+ goto exit;
+
+ g_variant_builder_init(&vb, G_VARIANT_TYPE("ad"));
+
+ list = svi->allowed_values;
+
+ min_rate_str = list->data;
+ max_rate_str = min_rate_str;
+
+ if (*upnp_tp_speeds != NULL)
+ g_ptr_array_free(*upnp_tp_speeds, TRUE);
+
+ *upnp_tp_speeds = g_ptr_array_new_with_free_func(g_free);
+
+ for (; list != NULL; list = list->next) {
+ rate = (char *)list->data;
+
+ if (prv_rational_is_invalid(rate))
+ continue;
+
+ g_ptr_array_add(*upnp_tp_speeds, g_strdup(rate));
+
+ g_variant_builder_add(&vb, "d",
+ prv_rational_to_double(rate, precision));
+
+ if (prv_compare_rationals(min_rate_str, rate) > 0)
+ min_rate_str = rate;
+ else if (prv_compare_rationals(max_rate_str, rate) < 0)
+ max_rate_str = rate;
+ }
+
+ *mpris_tp_speeds = g_variant_builder_end(&vb);
+
+ *min_rate = prv_rational_to_double(min_rate_str, precision);
+ *max_rate = prv_rational_to_double(max_rate_str, precision);
+
+exit:
+ return;
+}
+
+static void prv_get_av_service_states_values(GUPnPServiceProxy *av_proxy,
+ GVariant **mpris_tp_speeds,
+ GPtrArray **upnp_tp_speeds,
+ double *min_rate,
+ double *max_rate)
+{
+ const GUPnPServiceStateVariableInfo *svi;
+ GUPnPServiceIntrospection *introspection;
+ GError *error = NULL;
+
+ introspection = gupnp_service_info_get_introspection(
+ GUPNP_SERVICE_INFO(av_proxy),
+ &error);
+ if (error != NULL) {
+ DLEYNA_LOG_DEBUG(
+ "failed to fetch AV service introspection file");
+
+ g_error_free(error);
+
+ goto exit;
+ }
+
+ svi = gupnp_service_introspection_get_state_variable(
+ introspection,
+ "TransportPlaySpeed");
+
+ prv_get_rates_values(svi,
+ mpris_tp_speeds, upnp_tp_speeds,
+ min_rate, max_rate);
+
+ g_object_unref(introspection);
+
+exit:
+
+ return;
+}
+
+static void prv_get_rc_service_states_values(GUPnPServiceProxy *rc_proxy,
+ guint *max_volume)
+{
+ const GUPnPServiceStateVariableInfo *svi;
+ GUPnPServiceIntrospection *introspection;
+ GError *error = NULL;
+
+ introspection = gupnp_service_info_get_introspection(
+ GUPNP_SERVICE_INFO(rc_proxy),
+ &error);
+ if (error != NULL) {
+ DLEYNA_LOG_DEBUG(
+ "failed to fetch RC service introspection file");
+
+ g_error_free(error);
+
+ goto exit;
+ }
+
+ svi = gupnp_service_introspection_get_state_variable(introspection,
+ "Volume");
+ if (svi != NULL)
+ *max_volume = g_value_get_uint(&svi->maximum);
+
+ g_object_unref(introspection);
+
+exit:
+
+ return;
+}
+
+static void prv_update_device_props(GUPnPDeviceInfo *proxy, GHashTable *props)
+{
+ GVariant *val;
+ gchar *str;
+
+ val = g_variant_ref_sink(g_variant_new_string(
+ gupnp_device_info_get_device_type(proxy)));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_DEVICE_TYPE, val);
+
+ val = g_variant_ref_sink(g_variant_new_string(
+ gupnp_device_info_get_udn(proxy)));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_UDN, val);
+
+ str = gupnp_device_info_get_friendly_name(proxy);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_FRIENDLY_NAME, val);
+ g_free(str);
+
+ str = gupnp_device_info_get_icon_url(proxy, NULL, -1, -1, -1, FALSE,
+ NULL, NULL, NULL, NULL);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_ICON_URL, val);
+ g_free(str);
+
+ str = gupnp_device_info_get_manufacturer(proxy);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_MANUFACTURER, val);
+ g_free(str);
+
+ str = gupnp_device_info_get_manufacturer_url(proxy);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_MANUFACTURER_URL, val);
+ g_free(str);
+
+ str = gupnp_device_info_get_model_description(proxy);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_DESCRIPTION, val);
+ g_free(str);
+
+ str = gupnp_device_info_get_model_name(proxy);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_NAME, val);
+ g_free(str);
+
+ str = gupnp_device_info_get_model_number(proxy);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_NUMBER, val);
+ g_free(str);
+
+ str = gupnp_device_info_get_serial_number(proxy);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_SERIAL_NUMBER, val);
+ g_free(str);
+
+ str = gupnp_device_info_get_presentation_url(proxy);
+ val = g_variant_ref_sink(g_variant_new_string(str));
+ g_hash_table_insert(props, DLR_INTERFACE_PROP_PRESENTATION_URL, val);
+ g_free(str);
+
+}
+
+static void prv_props_update(dlr_device_t *device, dlr_task_t *task)
+{
+ GVariant *val;
+ GUPnPDeviceInfo *info;
+ dlr_device_context_t *context;
+ dlr_service_proxies_t *service_proxies;
+ dlr_props_t *props = &device->props;
+ GVariantBuilder *changed_props_vb;
+ GVariant *changed_props;
+ double min_rate = 0;
+ double max_rate = 0;
+ GVariant *mpris_transport_play_speeds = NULL;
+
+ context = dlr_device_get_context(device);
+
+ val = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+ g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_CAN_QUIT,
+ val);
+
+ g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_CAN_RAISE,
+ g_variant_ref(val));
+
+ g_hash_table_insert(props->root_props,
+ DLR_INTERFACE_PROP_CAN_SET_FULLSCREEN,
+ g_variant_ref(val));
+
+ g_hash_table_insert(props->root_props,
+ DLR_INTERFACE_PROP_HAS_TRACK_LIST,
+ g_variant_ref(val));
+
+ info = (GUPnPDeviceInfo *)context->device_proxy;
+
+ prv_update_device_props(info, props->device_props);
+
+ val = g_hash_table_lookup(props->device_props,
+ DLR_INTERFACE_PROP_FRIENDLY_NAME);
+ g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_IDENTITY,
+ g_variant_ref(val));
+
+ changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
+
+ service_proxies = &context->service_proxies;
+
+ if (service_proxies->av_proxy)
+ prv_get_av_service_states_values(service_proxies->av_proxy,
+ &mpris_transport_play_speeds,
+ &device->transport_play_speeds,
+ &min_rate,
+ &max_rate);
+
+ if (service_proxies->rc_proxy)
+ prv_get_rc_service_states_values(service_proxies->rc_proxy,
+ &device->max_volume);
+
+ if (min_rate != 0) {
+ val = g_variant_ref_sink(g_variant_new_double(min_rate));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_MINIMUM_RATE, val,
+ changed_props_vb);
+ }
+
+ if (max_rate != 0) {
+ val = g_variant_ref_sink(g_variant_new_double(max_rate));
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_MAXIMUM_RATE,
+ val, changed_props_vb);
+ }
+
+ if (mpris_transport_play_speeds != NULL) {
+ val = g_variant_ref_sink(mpris_transport_play_speeds);
+ prv_change_props(device->props.player_props,
+ DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS,
+ val, changed_props_vb);
+ }
+
+ prv_add_all_actions(device, changed_props_vb);
+ device->props.synced = TRUE;
+
+ changed_props = g_variant_ref_sink(
+ g_variant_builder_end(changed_props_vb));
+ prv_emit_signal_properties_changed(device,
+ DLR_INTERFACE_PLAYER,
+ changed_props);
+ g_variant_unref(changed_props);
+ g_variant_builder_unref(changed_props_vb);
+}
+
+static void prv_complete_get_prop(dlr_async_task_t *cb_data)
+{
+ prv_get_prop(cb_data);
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
+}
+
+static void prv_complete_get_props(dlr_async_task_t *cb_data)
+{
+ prv_get_props(cb_data);
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
+}
+
+static void prv_simple_call_cb(GUPnPServiceProxy *proxy,
+ GUPnPServiceProxyAction *action,
+ gpointer user_data)
+{
+ dlr_async_task_t *cb_data = user_data;
+ GError *upnp_error = NULL;
+
+ if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
+ &upnp_error, NULL)) {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OPERATION_FAILED,
+ "Operation failed: %s",
+ upnp_error->message);
+ g_error_free(upnp_error);
+ }
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
+}
+
+static void prv_set_volume(dlr_async_task_t *cb_data, GVariant *params)
+{
+ double volume;
+
+ volume = g_variant_get_double(params) * cb_data->device->max_volume;
+
+ DLEYNA_LOG_INFO("Set device volume to %d/%d", (guint)volume,
+ cb_data->device->max_volume);
+
+ cb_data->action =
+ gupnp_service_proxy_begin_action(cb_data->proxy, "SetVolume",
+ prv_simple_call_cb, cb_data,
+ "InstanceID", G_TYPE_INT, 0,
+ "Channel",
+ G_TYPE_STRING, "Master",
+ "DesiredVolume",
+ G_TYPE_UINT, (guint) volume,
+ NULL);
+}
+
+static GVariant *prv_get_rate_value_from_double(GVariant *params,
+ gchar **upnp_rate,
+ dlr_async_task_t *cb_data)
+{
+ GVariant *val = NULL;
+ GVariant *tps;
+ GVariantIter iter;
+ double tps_value;
+ double mpris_rate;
+ GPtrArray *upnp_tp_speeds;
+ int i;
+
+ tps = g_hash_table_lookup(cb_data->device->props.player_props,
+ DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS);
+
+ if (tps == NULL) {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OPERATION_FAILED,
+ "TransportPlaySpeeds list is empty");
+ goto exit;
+ }
+
+ mpris_rate = g_variant_get_double(params);
+
+ upnp_tp_speeds = cb_data->device->transport_play_speeds;
+
+ i = 0;
+
+ g_variant_iter_init(&iter, tps);
+ while (g_variant_iter_next(&iter, "d", &tps_value)) {
+
+ if (fabs(mpris_rate - tps_value) <= 0.01) {
+ val = g_variant_ref_sink(
+ g_variant_new_double(tps_value));
+
+ *upnp_rate = g_ptr_array_index(upnp_tp_speeds, i);
+
+ break;
+ }
+
+ i++;
+ }
+
+ if (val == NULL)
+ cb_data->error =
+ g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY,
+ "Value %.2f not in TransportPlaySpeeds",
+ mpris_rate);
+
+exit:
+
+ return val;
+}
+
+static void prv_set_rate(GVariant *params, dlr_async_task_t *cb_data)
+{
+ GVariant *val;
+ gchar *rate;
+
+ if (g_variant_is_of_type(params, G_VARIANT_TYPE_DOUBLE) == FALSE) {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_BAD_QUERY,
+ "Parameter is not a double");
+ goto exit;
+ }
+
+ rate = NULL;
+
+ val = prv_get_rate_value_from_double(params, &rate, cb_data);
+ if (val == NULL)
+ goto exit;
+
+ g_free(cb_data->device->rate);
+ cb_data->device->rate = g_strdup(rate);
+
+ DLEYNA_LOG_INFO("Set device rate to %s", cb_data->device->rate);
+
+ prv_change_props(cb_data->device->props.player_props,
+ DLR_INTERFACE_PROP_RATE, val, NULL);
+
+exit:
+
+ return;
+}
+
+void dlr_device_set_prop(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+ dlr_device_context_t *context;
+ dlr_task_set_prop_t *set_prop = &task->ut.set_prop;
+
+ cb_data->cb = cb;
+ cb_data->device = device;
+
+ if (g_strcmp0(set_prop->interface_name, DLR_INTERFACE_PLAYER) != 0 &&
+ g_strcmp0(set_prop->interface_name, "") != 0) {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_UNKNOWN_INTERFACE,
+ "Interface %s not managed for property setting",
+ set_prop->interface_name);
+ goto exit;
+ }
+
+ if (g_strcmp0(set_prop->prop_name, DLR_INTERFACE_PROP_RATE) == 0) {
+ prv_set_rate(set_prop->params, cb_data);
+ goto exit;
+ }
+
+ if (g_strcmp0(set_prop->prop_name, DLR_INTERFACE_PROP_VOLUME) != 0) {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_UNKNOWN_PROPERTY,
+ "Property %s not managed for setting",
+ set_prop->prop_name);
+ goto exit;
+ }
+
+ context = dlr_device_get_context(device);
+
+ cb_data->cancel_id =
+ g_cancellable_connect(cb_data->cancellable,
+ G_CALLBACK(dlr_async_task_cancelled),
+ cb_data, NULL);
+ cb_data->proxy = context->service_proxies.rc_proxy;
+
+ g_object_add_weak_pointer((G_OBJECT(context->service_proxies.rc_proxy)),
+ (gpointer *)&cb_data->proxy);
+
+ prv_set_volume(cb_data, set_prop->params);
+ return;
+
+exit:
+
+ g_idle_add(dlr_async_task_complete, cb_data);
+}
+
+void dlr_device_get_prop(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+ dlr_task_get_prop_t *get_prop = &task->ut.get_prop;
+ dlr_device_data_t *device_cb_data;
+
+ /* Need to check to see if the property is DLR_INTERFACE_PROP_POSITION.
+ If it is we need to call GetPositionInfo. This value is not evented.
+ Otherwise we can just update the value straight away. */
+
+ if ((!strcmp(get_prop->interface_name, DLR_INTERFACE_PLAYER) ||
+ !strcmp(get_prop->interface_name, "")) &&
+ (!strcmp(task->ut.get_prop.prop_name,
+ DLR_INTERFACE_PROP_POSITION))) {
+ /* Need to read the current position. This property is not
+ evented */
+
+ device_cb_data = g_new(dlr_device_data_t, 1);
+ device_cb_data->local_cb = prv_complete_get_prop;
+
+ cb_data->cb = cb;
+ cb_data->private = device_cb_data;
+ cb_data->free_private = g_free;
+ cb_data->device = device;
+
+ prv_get_position_info(cb_data);
+ } else {
+ cb_data->cb = cb;
+ cb_data->device = device;
+
+ if (!device->props.synced)
+ prv_props_update(device, task);
+
+ prv_get_prop(cb_data);
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ }
+}
+
+void dlr_device_get_all_props(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+ dlr_task_get_props_t *get_props = &task->ut.get_props;
+ dlr_device_data_t *device_cb_data;
+
+ if (!device->props.synced)
+ prv_props_update(device, task);
+
+ if ((!strcmp(get_props->interface_name, DLR_INTERFACE_PLAYER) ||
+ !strcmp(get_props->interface_name, ""))) {
+
+ /* Need to read the current position. This property is not
+ evented */
+
+ device_cb_data = g_new(dlr_device_data_t, 1);
+ device_cb_data->local_cb = prv_complete_get_props;
+
+ cb_data->cb = cb;
+ cb_data->private = device_cb_data;
+ cb_data->device = device;
+ cb_data->free_private = g_free;
+
+ prv_get_position_info(cb_data);
+ } else {
+ cb_data->cb = cb;
+ cb_data->device = device;
+
+ prv_get_props(cb_data);
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ }
+}
+
+void dlr_device_play(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_context_t *context;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_INFO("Play at speed %s", device->rate);
+
+ context = dlr_device_get_context(device);
+ cb_data->cb = cb;
+ cb_data->device = device;
+
+ cb_data->cancel_id =
+ g_cancellable_connect(cb_data->cancellable,
+ G_CALLBACK(dlr_async_task_cancelled),
+ cb_data, NULL);
+ cb_data->proxy = context->service_proxies.av_proxy;
+
+ g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)),
+ (gpointer *)&cb_data->proxy);
+
+ cb_data->action =
+ gupnp_service_proxy_begin_action(cb_data->proxy,
+ "Play",
+ prv_simple_call_cb,
+ cb_data,
+ "InstanceID", G_TYPE_INT, 0,
+ "Speed", G_TYPE_STRING,
+ device->rate, NULL);
+}
+
+void dlr_device_play_pause(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ GVariant *state;
+
+ state = g_hash_table_lookup(device->props.player_props,
+ DLR_INTERFACE_PROP_PLAYBACK_STATUS);
+
+ if (state && !strcmp(g_variant_get_string(state, NULL), "Playing"))
+ dlr_device_pause(device, task, cb);
+ else
+ dlr_device_play(device, task, cb);
+}
+
+static void prv_simple_command(dlr_device_t *device, dlr_task_t *task,
+ const gchar *command_name,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_context_t *context;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_INFO("%s", command_name);
+
+ context = dlr_device_get_context(device);
+ cb_data->cb = cb;
+ cb_data->device = device;
+
+ cb_data->cancel_id =
+ g_cancellable_connect(cb_data->cancellable,
+ G_CALLBACK(dlr_async_task_cancelled),
+ cb_data, NULL);
+ cb_data->proxy = context->service_proxies.av_proxy;
+
+ g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)),
+ (gpointer *)&cb_data->proxy);
+
+ cb_data->action =
+ gupnp_service_proxy_begin_action(cb_data->proxy,
+ command_name,
+ prv_simple_call_cb,
+ cb_data,
+ "InstanceID", G_TYPE_INT, 0,
+ NULL);
+}
+
+void dlr_device_pause(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ prv_simple_command(device, task, "Pause", cb);
+}
+
+void dlr_device_stop(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ prv_simple_command(device, task, "Stop", cb);
+}
+
+void dlr_device_next(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ prv_simple_command(device, task, "Next", cb);
+}
+
+void dlr_device_previous(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ prv_simple_command(device, task, "Previous", cb);
+}
+
+void dlr_device_open_uri(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_context_t *context;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+ dlr_task_open_uri_t *open_uri_data = &task->ut.open_uri;
+
+ DLEYNA_LOG_INFO("URI: %s", open_uri_data->uri);
+
+ context = dlr_device_get_context(device);
+ cb_data->cb = cb;
+ cb_data->device = device;
+
+ cb_data->cancel_id =
+ g_cancellable_connect(cb_data->cancellable,
+ G_CALLBACK(dlr_async_task_cancelled),
+ cb_data, NULL);
+ cb_data->proxy = context->service_proxies.av_proxy;
+
+ g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)),
+ (gpointer *)&cb_data->proxy);
+
+ cb_data->action =
+ gupnp_service_proxy_begin_action(cb_data->proxy,
+ "SetAVTransportURI",
+ prv_simple_call_cb,
+ cb_data,
+ "InstanceID", G_TYPE_INT, 0,
+ "CurrentURI", G_TYPE_STRING,
+ open_uri_data->uri,
+ "CurrentURIMetaData",
+ G_TYPE_STRING, "",
+ NULL);
+}
+
+static void prv_device_set_position(dlr_device_t *device, dlr_task_t *task,
+ const gchar *pos_type,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_context_t *context;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+ dlr_task_seek_t *seek_data = &task->ut.seek;
+ gchar *position;
+
+ context = dlr_device_get_context(device);
+ cb_data->cb = cb;
+ cb_data->device = device;
+
+ if (!strcmp(pos_type, "TRACK_NR"))
+ position = g_strdup_printf("%u", seek_data->track_number);
+ else
+ position = prv_int64_to_duration(seek_data->position);
+
+ DLEYNA_LOG_INFO("set %s position : %s", pos_type, position);
+
+ cb_data->cancel_id =
+ g_cancellable_connect(cb_data->cancellable,
+ G_CALLBACK(dlr_async_task_cancelled),
+ cb_data, NULL);
+ cb_data->cancellable = cb_data->cancellable;
+ cb_data->proxy = context->service_proxies.av_proxy;
+
+ g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)),
+ (gpointer *)&cb_data->proxy);
+
+ cb_data->action =
+ gupnp_service_proxy_begin_action(cb_data->proxy,
+ "Seek",
+ prv_simple_call_cb,
+ cb_data,
+ "InstanceID", G_TYPE_INT, 0,
+ "Unit", G_TYPE_STRING,
+ pos_type,
+ "Target",
+ G_TYPE_STRING, position,
+ NULL);
+
+ g_free(position);
+}
+
+void dlr_device_seek(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ prv_device_set_position(device, task, "REL_TIME", cb);
+}
+
+void dlr_device_set_position(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ prv_device_set_position(device, task, "ABS_TIME", cb);
+}
+
+void dlr_device_goto_track(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ prv_device_set_position(device, task, "TRACK_NR", cb);
+}
+
+void dlr_device_host_uri(dlr_device_t *device, dlr_task_t *task,
+ dlr_host_service_t *host_service,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_context_t *context;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+ dlr_task_host_uri_t *host_uri = &task->ut.host_uri;
+ gchar *url;
+ GError *error = NULL;
+
+ context = dlr_device_get_context(device);
+ url = dlr_host_service_add(host_service, context->ip_address,
+ host_uri->client, host_uri->uri,
+ &error);
+
+ cb_data->cb = cb;
+ cb_data->device = device;
+ if (url) {
+ cb_data->task.result = g_variant_ref_sink(
+ g_variant_new_string(url));
+ g_free(url);
+ } else {
+ cb_data->error = error;
+ }
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+}
+
+void dlr_device_remove_uri(dlr_device_t *device, dlr_task_t *task,
+ dlr_host_service_t *host_service,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_context_t *context;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+ dlr_task_host_uri_t *host_uri = &task->ut.host_uri;
+
+ context = dlr_device_get_context(device);
+ cb_data->cb = cb;
+ cb_data->device = device;
+
+ if (!dlr_host_service_remove(host_service, context->ip_address,
+ host_uri->client, host_uri->uri)) {
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "File not hosted for specified device");
+ }
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+}
diff --git a/libdleyna/renderer/device.h b/libdleyna/renderer/device.h
new file mode 100644
index 0000000..a0c1bf0
--- /dev/null
+++ b/libdleyna/renderer/device.h
@@ -0,0 +1,149 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#ifndef DLR_DEVICE_H__
+#define DLR_DEVICE_H__
+
+#include <gio/gio.h>
+#include <glib.h>
+
+#include <libgupnp/gupnp-service-proxy.h>
+#include <libgupnp/gupnp-device-proxy.h>
+
+#include <libdleyna/core/connector.h>
+
+#include "host-service.h"
+#include "server.h"
+#include "upnp.h"
+
+typedef struct dlr_service_proxies_t_ dlr_service_proxies_t;
+struct dlr_service_proxies_t_ {
+ GUPnPServiceProxy *cm_proxy;
+ GUPnPServiceProxy *av_proxy;
+ GUPnPServiceProxy *rc_proxy;
+};
+
+typedef struct dlr_device_context_t_ dlr_device_context_t;
+struct dlr_device_context_t_ {
+ gchar *ip_address;
+ GUPnPDeviceProxy *device_proxy;
+ dlr_service_proxies_t service_proxies;
+ dlr_device_t *device;
+ gboolean subscribed_av;
+ gboolean subscribed_cm;
+ gboolean subscribed_rc;
+ guint timeout_id_av;
+ guint timeout_id_cm;
+ guint timeout_id_rc;
+};
+
+typedef struct dlr_props_t_ dlr_props_t;
+struct dlr_props_t_ {
+ GHashTable *root_props;
+ GHashTable *player_props;
+ GHashTable *device_props;
+ gboolean synced;
+};
+
+struct dlr_device_t_ {
+ dleyna_connector_id_t connection;
+ guint ids[DLR_INTERFACE_INFO_MAX];
+ gchar *path;
+ GPtrArray *contexts;
+ dlr_props_t props;
+ guint timeout_id;
+ guint max_volume;
+ GPtrArray *transport_play_speeds;
+ gchar *rate;
+};
+
+dlr_device_t *dlr_device_new(
+ dleyna_connector_id_t connection,
+ GUPnPDeviceProxy *proxy,
+ const gchar *ip_address,
+ guint counter,
+ const dleyna_connector_dispatch_cb_t *dispatch_table,
+ const dleyna_task_queue_key_t *queue_id);
+
+void dlr_device_delete(void *device);
+
+void dlr_device_unsubscribe(void *device);
+
+void dlr_device_append_new_context(dlr_device_t *device,
+ const gchar *ip_address,
+ GUPnPDeviceProxy *proxy);
+
+dlr_device_t *dlr_device_from_path(const gchar *path, GHashTable *device_list);
+
+dlr_device_context_t *dlr_device_get_context(dlr_device_t *device);
+
+void dlr_device_subscribe_to_service_changes(dlr_device_t *device);
+
+
+void dlr_device_set_prop(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_get_prop(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_get_all_props(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_play(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_pause(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_play_pause(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_stop(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_next(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_previous(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_open_uri(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_seek(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_set_position(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_goto_track(dlr_device_t *device, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_host_uri(dlr_device_t *device, dlr_task_t *task,
+ dlr_host_service_t *host_service,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_device_remove_uri(dlr_device_t *device, dlr_task_t *task,
+ dlr_host_service_t *host_service,
+ dlr_upnp_task_complete_t cb);
+
+#endif /* DLR_DEVICE_H__ */
diff --git a/libdleyna/renderer/dleyna-renderer-1.0.pc.in b/libdleyna/renderer/dleyna-renderer-1.0.pc.in
new file mode 100644
index 0000000..3e6857a
--- /dev/null
+++ b/libdleyna/renderer/dleyna-renderer-1.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libexecdir=@libexecdir@
+includedir=${prefix}/include
+libdir=@libdir@
+
+Name: @PACKAGE@
+Description: UPnP & DLNA renderer library
+Libs: -L${libdir} -ldleyna-renderer-1.0
+Requires.private: glib-2.0 gio-2.0 libsoup-2.4 gupnp-1.0 gupnp-av-1.0 dleyna-core-1.0
+Version: @VERSION@ \ No newline at end of file
diff --git a/libdleyna/renderer/dleyna-renderer-service.conf.in b/libdleyna/renderer/dleyna-renderer-service.conf.in
new file mode 100644
index 0000000..2edc4e3
--- /dev/null
+++ b/libdleyna/renderer/dleyna-renderer-service.conf.in
@@ -0,0 +1,37 @@
+# Configuration file for dleyna-renderer
+#
+#
+#
+# General configuration options
+[general]
+
+# true: Service always stay in memory running
+# false: Service quit when the last client disconnects.
+never-quit=@never_quit@
+
+# IPC connector name
+connector-name=@with_connector_name@
+
+# Log configuration options
+[log]
+
+# Define the logging output method. 3 technologies are defined:
+#
+# 0=Syslog
+# 1=GLib
+# 2=File
+log-type=@with_log_type@
+
+# Comma-separated list of logging level.
+# Log levels are: 1=critical, 2=error, 3=warning, 4=message, 5=info, 6=debug
+#
+# Allowed values for log-levels are
+# 0 = disabled
+# 7 = default (=1,2,5)
+# 8 = all (=1,2,3,4,5,6)
+# 1,..,6 = a comma separated list of log level
+#
+# IMPORTANT: This log level is a subset of the log level defined at compile time
+# You can't enable levels disabled at compile time
+# level=8 means all level flags defined at compile time.
+log-level=@with_log_level@
diff --git a/libdleyna/renderer/host-service.c b/libdleyna/renderer/host-service.c
new file mode 100644
index 0000000..42ee74e
--- /dev/null
+++ b/libdleyna/renderer/host-service.c
@@ -0,0 +1,567 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libsoup/soup.h>
+#include <glib.h>
+
+#include <libgupnp-av/gupnp-dlna.h>
+#include <libgupnp-dlna/gupnp-dlna-profile.h>
+#include <libgupnp-dlna/gupnp-dlna-profile-guesser.h>
+
+#include <libdleyna/core/error.h>
+#include <libdleyna/core/log.h>
+
+#include "host-service.h"
+
+#define DLR_HOST_SERVICE_ROOT "/dleynarenderer"
+
+typedef struct dlr_host_file_t_ dlr_host_file_t;
+struct dlr_host_file_t_ {
+ unsigned int id;
+ GPtrArray *clients;
+ gchar *mime_type;
+ GMappedFile *mapped_file;
+ unsigned int mapped_count;
+ gchar *path;
+ gchar *dlna_header;
+};
+
+typedef struct dlr_host_server_t_ dlr_host_server_t;
+struct dlr_host_server_t_ {
+ GHashTable *files;
+ SoupServer *soup_server;
+ unsigned int counter;
+};
+
+struct dlr_host_service_t_ {
+ GHashTable *servers;
+};
+
+static gchar *prv_compute_dlna_header(const gchar *filename)
+{
+ gchar *uri;
+ GString *header;
+ GError *error = NULL;
+ GUPnPDLNAProfile *profile;
+ GUPnPDLNAProfileGuesser *guesser;
+ gboolean relaxed_mode = TRUE;
+ gboolean extended_mode = TRUE;
+ const char *profile_name;
+ const char *mime_type;
+ GUPnPDLNAOperation operation;
+ GUPnPDLNAFlags flags;
+ GUPnPDLNAConversion conversion;
+
+ header = g_string_new("");
+
+ guesser = gupnp_dlna_profile_guesser_new(relaxed_mode, extended_mode);
+
+ uri = g_filename_to_uri(filename, NULL, &error);
+ if (uri == NULL) {
+ DLEYNA_LOG_WARNING("Unable to convert filename: %s", filename);
+
+ if (error) {
+ DLEYNA_LOG_WARNING("Error: %s", error->message);
+
+ g_error_free(error);
+ }
+
+ goto on_error;
+ }
+
+ profile = gupnp_dlna_profile_guesser_guess_profile_sync(guesser,
+ uri,
+ 5000,
+ NULL,
+ &error);
+ if (profile == NULL) {
+ DLEYNA_LOG_WARNING("Unable to guess profile for URI: %s", uri);
+
+ if (error) {
+ DLEYNA_LOG_WARNING("Error: %s", error->message);
+
+ g_error_free(error);
+ }
+
+ goto on_error;
+ }
+
+ profile_name = gupnp_dlna_profile_get_name(profile);
+ if (profile_name != NULL)
+ g_string_append_printf(header, "DLNA.ORG_PN=%s;", profile_name);
+
+ operation = GUPNP_DLNA_OPERATION_RANGE;
+ g_string_append_printf(header, "DLNA.ORG_OP=%.2x;", operation);
+
+ conversion = GUPNP_DLNA_CONVERSION_NONE;
+ g_string_append_printf(header, "DLNA.ORG_CI=%.2x;", conversion);
+
+ mime_type = gupnp_dlna_profile_get_mime(profile);
+ if (mime_type != NULL) {
+ flags = GUPNP_DLNA_FLAGS_BACKGROUND_TRANSFER_MODE;
+ flags |= GUPNP_DLNA_FLAGS_CONNECTION_STALL;
+ flags |= GUPNP_DLNA_FLAGS_DLNA_V15;
+
+ if (g_content_type_is_a(mime_type, "image/*")) {
+ flags |= GUPNP_DLNA_FLAGS_INTERACTIVE_TRANSFER_MODE;
+ } else if (g_content_type_is_a(mime_type, "audio/*") ||
+ g_content_type_is_a(mime_type, "video/*")) {
+ flags |= GUPNP_DLNA_FLAGS_STREAMING_TRANSFER_MODE;
+ } else {
+ DLEYNA_LOG_WARNING("Unsupported Mime Type: %s",
+ mime_type);
+
+ goto on_error;
+ }
+
+ g_string_append_printf(header, "DLNA.ORG_FLAGS=%.8x", flags);
+ g_string_append_printf(header, "000000000000000000000000");
+ } else {
+ DLEYNA_LOG_WARNING("Unable to discover mime_type");
+ }
+
+on_error:
+
+ DLEYNA_LOG_DEBUG("contentFeatures.dlna.org: %s", header->str);
+
+ g_object_unref(guesser);
+
+ g_free(uri);
+
+ return g_string_free(header, FALSE);
+}
+
+static void prv_host_file_delete(gpointer host_file)
+{
+ dlr_host_file_t *hf = host_file;
+ unsigned int i;
+
+ if (hf) {
+ g_free(hf->path);
+ for (i = 0; i < hf->mapped_count; ++i)
+ g_mapped_file_unref(hf->mapped_file);
+
+ g_ptr_array_unref(hf->clients);
+
+ g_free(hf->mime_type);
+ g_free(hf->dlna_header);
+ g_free(hf);
+ }
+}
+
+static dlr_host_file_t *prv_host_file_new(const gchar *file, unsigned int id,
+ GError **error)
+{
+ dlr_host_file_t *hf = NULL;
+ gchar *extension;
+ gchar *content_type = NULL;
+
+ if (!g_file_test(file, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) {
+ *error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "File %s does not exist or is not a regular file",
+ file);
+ goto on_error;
+ }
+
+ hf = g_new0(dlr_host_file_t, 1);
+ hf->id = id;
+ hf->clients = g_ptr_array_new_with_free_func(g_free);
+
+ content_type = g_content_type_guess(file, NULL, 0, NULL);
+
+ if (!content_type) {
+ *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME,
+ "Unable to determine Content Type for %s",
+ file);
+ goto on_error;
+ }
+
+ hf->mime_type = g_content_type_get_mime_type(content_type);
+
+ if (!hf->mime_type) {
+ *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME,
+ "Unable to determine MIME Type for %s",
+ file);
+ goto on_error;
+ }
+
+ g_free(content_type);
+
+ extension = strrchr(file, '.');
+ hf->path = g_strdup_printf(DLR_HOST_SERVICE_ROOT"/%d%s",
+ hf->id, extension ? extension : "");
+
+ hf->dlna_header = prv_compute_dlna_header(file);
+
+ return hf;
+
+on_error:
+
+ g_free(content_type);
+ prv_host_file_delete(hf);
+
+ return NULL;
+}
+
+static void prv_host_server_delete(gpointer host_server)
+{
+ dlr_host_server_t *server = host_server;
+
+ if (server) {
+ soup_server_quit(server->soup_server);
+ g_object_unref(server->soup_server);
+ g_hash_table_unref(server->files);
+ g_free(server);
+ }
+}
+
+static dlr_host_file_t *prv_host_server_find_file(dlr_host_server_t *hs,
+ const gchar *url,
+ const gchar **file_name)
+{
+ dlr_host_file_t *retval = NULL;
+ GHashTableIter iter;
+ gpointer key;
+ gpointer value;
+
+ g_hash_table_iter_init(&iter, hs->files);
+
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ if (!strcmp(((dlr_host_file_t *)value)->path, url)) {
+ retval = value;
+ *file_name = key;
+ break;
+ }
+ }
+
+ return retval;
+}
+
+static void prv_soup_message_finished_cb(SoupMessage *msg, gpointer user_data)
+{
+ dlr_host_file_t *hf = user_data;
+
+ if (hf->mapped_count > 0) {
+ g_mapped_file_unref(hf->mapped_file);
+ --hf->mapped_count;
+
+ if (hf->mapped_count == 0)
+ hf->mapped_file = NULL;
+ }
+}
+
+static void prv_soup_server_cb(SoupServer *server, SoupMessage *msg,
+ const char *path, GHashTable *query,
+ SoupClientContext *client, gpointer user_data)
+{
+ dlr_host_file_t *hf;
+ dlr_host_server_t *hs = user_data;
+ const gchar *file_name;
+ const char *hdr;
+
+ if ((msg->method != SOUP_METHOD_GET) &&
+ (msg->method != SOUP_METHOD_HEAD)) {
+ soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
+ goto on_error;
+ }
+
+ hf = prv_host_server_find_file(hs, path, &file_name);
+
+ if (!hf) {
+ soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND);
+ goto on_error;
+ }
+
+ hdr = soup_message_headers_get_one(msg->request_headers,
+ "getContentFeatures.dlna.org");
+
+ if (hdr) {
+ if (strcmp(hdr, "1") != 0) {
+ soup_message_set_status(msg, SOUP_STATUS_BAD_REQUEST);
+ goto on_error;
+ }
+
+ if ((hf->dlna_header) && strlen(hf->dlna_header) > 0)
+ soup_message_headers_append(msg->response_headers,
+ "contentFeatures.dlna.org",
+ hf->dlna_header);
+ }
+
+ if (hf->mapped_file) {
+ g_mapped_file_ref(hf->mapped_file);
+ ++hf->mapped_count;
+ } else {
+ hf->mapped_file = g_mapped_file_new(file_name,
+ FALSE,
+ NULL);
+
+ if (!hf->mapped_file) {
+ soup_message_set_status(msg,
+ SOUP_STATUS_NOT_FOUND);
+ goto on_error;
+ }
+
+ hf->mapped_count = 1;
+ }
+
+ if (msg->method == SOUP_METHOD_GET) {
+ g_signal_connect(msg, "finished",
+ G_CALLBACK(prv_soup_message_finished_cb), hf);
+
+ soup_message_set_response(
+ msg, hf->mime_type,
+ SOUP_MEMORY_STATIC,
+ g_mapped_file_get_contents(hf->mapped_file),
+ g_mapped_file_get_length(hf->mapped_file));
+ } else {
+ soup_message_headers_set_content_type(msg->response_headers,
+ hf->mime_type, NULL);
+
+ soup_message_headers_set_content_length(
+ msg->response_headers,
+ g_mapped_file_get_length(hf->mapped_file));
+ }
+
+ soup_message_set_status(msg, SOUP_STATUS_OK);
+
+on_error:
+
+ return;
+}
+
+static dlr_host_server_t *prv_host_server_new(const gchar *device_if,
+ GError **error)
+{
+ dlr_host_server_t *server = NULL;
+ SoupAddress *addr;
+
+ addr = soup_address_new(device_if, SOUP_ADDRESS_ANY_PORT);
+
+ if (soup_address_resolve_sync(addr, NULL) != SOUP_STATUS_OK) {
+ *error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_HOST_FAILED,
+ "Unable to create host server on %s",
+ device_if);
+ goto on_error;
+ }
+
+ server = g_new(dlr_host_server_t, 1);
+ server->files = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, prv_host_file_delete);
+
+ server->soup_server = soup_server_new(SOUP_SERVER_INTERFACE, addr,
+ NULL);
+ soup_server_add_handler(server->soup_server, DLR_HOST_SERVICE_ROOT,
+ prv_soup_server_cb, server, NULL);
+ soup_server_run_async(server->soup_server);
+ server->counter = 0;
+
+on_error:
+
+ g_object_unref(addr);
+
+ return server;
+}
+
+void dlr_host_service_new(dlr_host_service_t **host_service)
+{
+ dlr_host_service_t *hs;
+
+ hs = g_new(dlr_host_service_t, 1);
+ hs->servers = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, prv_host_server_delete);
+
+ *host_service = hs;
+}
+
+static gchar *prv_add_new_file(dlr_host_server_t *server, const gchar *client,
+ const gchar *device_if, const gchar *file,
+ GError **error)
+{
+ unsigned int i;
+ dlr_host_file_t *hf;
+ gchar *str;
+
+ hf = g_hash_table_lookup(server->files, file);
+
+ if (!hf) {
+ hf = prv_host_file_new(file, server->counter++, error);
+
+ if (!hf)
+ goto on_error;
+
+ g_ptr_array_add(hf->clients, g_strdup(client));
+ g_hash_table_insert(server->files, g_strdup(file), hf);
+ } else {
+ for (i = 0; i < hf->clients->len; ++i)
+ if (!strcmp(g_ptr_array_index(hf->clients, i), client))
+ break;
+
+ if (i == hf->clients->len)
+ g_ptr_array_add(hf->clients, g_strdup(client));
+ }
+
+ str = g_strdup_printf("http://%s:%d%s", device_if,
+ soup_server_get_port(server->soup_server),
+ hf->path);
+
+ return str;
+
+on_error:
+
+ return NULL;
+}
+
+gchar *dlr_host_service_add(dlr_host_service_t *host_service,
+ const gchar *device_if, const gchar *client,
+ const gchar *file, GError **error)
+{
+ dlr_host_server_t *server;
+ gchar *retval = NULL;
+
+ server = g_hash_table_lookup(host_service->servers, device_if);
+
+ if (!server) {
+ server = prv_host_server_new(device_if, error);
+
+ if (!server)
+ goto on_error;
+
+ g_hash_table_insert(host_service->servers, g_strdup(device_if),
+ server);
+ }
+
+ retval = prv_add_new_file(server, client, device_if, file, error);
+
+on_error:
+
+ return retval;
+}
+
+static gboolean prv_remove_client(dlr_host_service_t *host_service,
+ const gchar *client,
+ dlr_host_server_t *server,
+ const gchar *device_if,
+ const gchar *file,
+ dlr_host_file_t *hf)
+{
+ unsigned int i;
+ gboolean retval = FALSE;
+
+ for (i = 0; i < hf->clients->len; ++i)
+ if (!strcmp(g_ptr_array_index(hf->clients, i), client))
+ break;
+
+ if (i == hf->clients->len)
+ goto on_error;
+
+ g_ptr_array_remove_index(hf->clients, i);
+
+ retval = TRUE;
+
+on_error:
+
+ return retval;
+}
+
+gboolean dlr_host_service_remove(dlr_host_service_t *host_service,
+ const gchar *device_if, const gchar *client,
+ const gchar *file)
+{
+ gboolean retval = FALSE;
+ dlr_host_file_t *hf;
+ dlr_host_server_t *server;
+
+ server = g_hash_table_lookup(host_service->servers, device_if);
+
+ if (!server)
+ goto on_error;
+
+ hf = g_hash_table_lookup(server->files, file);
+
+ if (!hf)
+ goto on_error;
+
+ retval = prv_remove_client(host_service, client, server,
+ device_if, file, hf);
+ if (!retval)
+ goto on_error;
+
+ if (hf->clients->len == 0)
+ g_hash_table_remove(server->files, file);
+
+ if (g_hash_table_size(server->files) == 0)
+ g_hash_table_remove(host_service->servers, device_if);
+
+on_error:
+
+ return retval;
+}
+
+void dlr_host_service_lost_client(dlr_host_service_t *host_service,
+ const gchar *client)
+{
+ GHashTableIter iter;
+ GHashTableIter iter2;
+ gpointer value;
+ gpointer key;
+ gpointer value2;
+ gpointer key2;
+ dlr_host_server_t *server;
+ dlr_host_file_t *hf;
+
+ g_hash_table_iter_init(&iter, host_service->servers);
+
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ server = value;
+ g_hash_table_iter_init(&iter2, server->files);
+
+ while (g_hash_table_iter_next(&iter2, &key2, &value2)) {
+ hf = value2;
+
+ if (!prv_remove_client(host_service, client, server,
+ key, key2, hf))
+ continue;
+
+ if (hf->clients->len > 0)
+ continue;
+
+ g_hash_table_iter_remove(&iter2);
+ }
+
+ if (g_hash_table_size(server->files) == 0)
+ g_hash_table_iter_remove(&iter);
+ }
+}
+
+void dlr_host_service_delete(dlr_host_service_t *host_service)
+{
+ if (host_service) {
+ g_hash_table_unref(host_service->servers);
+ g_free(host_service);
+ }
+}
diff --git a/libdleyna/renderer/host-service.h b/libdleyna/renderer/host-service.h
new file mode 100644
index 0000000..a05a529
--- /dev/null
+++ b/libdleyna/renderer/host-service.h
@@ -0,0 +1,43 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#ifndef DLR_HOST_SERVICE_H__
+#define DLR_HOST_SERVICE_H__
+
+typedef struct dlr_host_service_t_ dlr_host_service_t;
+
+void dlr_host_service_new(dlr_host_service_t **host_service);
+
+gchar *dlr_host_service_add(dlr_host_service_t *host_service,
+ const gchar *device_if, const gchar *client,
+ const gchar *file, GError **error);
+
+gboolean dlr_host_service_remove(dlr_host_service_t *host_service,
+ const gchar *device_if, const gchar *client,
+ const gchar *file);
+
+void dlr_host_service_lost_client(dlr_host_service_t *host_service,
+ const gchar *client);
+
+void dlr_host_service_delete(dlr_host_service_t *host_service);
+
+#endif /*DLR_HOST_SERVICE_H__ */
diff --git a/libdleyna/renderer/prop-defs.h b/libdleyna/renderer/prop-defs.h
new file mode 100644
index 0000000..ae2f313
--- /dev/null
+++ b/libdleyna/renderer/prop-defs.h
@@ -0,0 +1,70 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#ifndef DLR_PROPS_DEFS_H__
+#define DLR_PROPS_DEFS_H__
+
+#define DLR_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties"
+#define DLR_INTERFACE_SERVER "org.mpris.MediaPlayer2"
+#define DLR_INTERFACE_PLAYER "org.mpris.MediaPlayer2.Player"
+
+#define DLR_INTERFACE_PROPERTIES_CHANGED "PropertiesChanged"
+
+#define DLR_INTERFACE_PROP_CAN_QUIT "CanQuit"
+#define DLR_INTERFACE_PROP_CAN_RAISE "CanRaise"
+#define DLR_INTERFACE_PROP_CAN_SET_FULLSCREEN "CanSetFullscreen"
+#define DLR_INTERFACE_PROP_HAS_TRACK_LIST "HasTrackList"
+#define DLR_INTERFACE_PROP_IDENTITY "Identity"
+#define DLR_INTERFACE_PROP_SUPPORTED_URIS "SupportedUriSchemes"
+#define DLR_INTERFACE_PROP_SUPPORTED_MIME "SupportedMimeTypes"
+
+#define DLR_INTERFACE_PROP_PLAYBACK_STATUS "PlaybackStatus"
+#define DLR_INTERFACE_PROP_RATE "Rate"
+#define DLR_INTERFACE_PROP_CAN_PLAY "CanPlay"
+#define DLR_INTERFACE_PROP_CAN_SEEK "CanSeek"
+#define DLR_INTERFACE_PROP_CAN_CONTROL "CanControl"
+#define DLR_INTERFACE_PROP_CAN_PAUSE "CanPause"
+#define DLR_INTERFACE_PROP_CAN_NEXT "CanGoNext"
+#define DLR_INTERFACE_PROP_CAN_PREVIOUS "CanGoPrevious"
+#define DLR_INTERFACE_PROP_POSITION "Position"
+#define DLR_INTERFACE_PROP_METADATA "Metadata"
+#define DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS "TransportPlaySpeeds"
+#define DLR_INTERFACE_PROP_MINIMUM_RATE "MinimumRate"
+#define DLR_INTERFACE_PROP_MAXIMUM_RATE "MaximumRate"
+#define DLR_INTERFACE_PROP_VOLUME "Volume"
+#define DLR_INTERFACE_PROP_CURRENT_TRACK "CurrentTrack"
+#define DLR_INTERFACE_PROP_NUMBER_OF_TRACKS "NumberOfTracks"
+
+#define DLR_INTERFACE_PROP_DEVICE_TYPE "DeviceType"
+#define DLR_INTERFACE_PROP_UDN "UDN"
+#define DLR_INTERFACE_PROP_FRIENDLY_NAME "FriendlyName"
+#define DLR_INTERFACE_PROP_ICON_URL "IconURL"
+#define DLR_INTERFACE_PROP_MANUFACTURER "Manufacturer"
+#define DLR_INTERFACE_PROP_MANUFACTURER_URL "ManufacturerUrl"
+#define DLR_INTERFACE_PROP_MODEL_DESCRIPTION "ModelDescription"
+#define DLR_INTERFACE_PROP_MODEL_NAME "ModelName"
+#define DLR_INTERFACE_PROP_MODEL_NUMBER "ModelNumber"
+#define DLR_INTERFACE_PROP_SERIAL_NUMBER "SerialNumber"
+#define DLR_INTERFACE_PROP_PRESENTATION_URL "PresentationURL"
+#define DLR_INTERFACE_PROP_PROTOCOL_INFO "ProtocolInfo"
+
+#endif /* DLR_PROPS_DEFS_H__ */
diff --git a/libdleyna/renderer/server.c b/libdleyna/renderer/server.c
new file mode 100644
index 0000000..75bb358
--- /dev/null
+++ b/libdleyna/renderer/server.c
@@ -0,0 +1,917 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/signalfd.h>
+
+#include <libdleyna/core/connector.h>
+#include <libdleyna/core/control-point.h>
+#include <libdleyna/core/error.h>
+#include <libdleyna/core/log.h>
+#include <libdleyna/core/task-processor.h>
+
+#include "async.h"
+#include "control-point-renderer.h"
+#include "device.h"
+#include "prop-defs.h"
+#include "server.h"
+#include "upnp.h"
+
+#ifdef UA_PREFIX
+ #define DLR_PRG_NAME UA_PREFIX " dLeyna/" VERSION
+#else
+ #define DLR_PRG_NAME "dLeyna/" VERSION
+#endif
+
+#define DLR_INTERFACE_GET_VERSION "GetVersion"
+#define DLR_INTERFACE_GET_SERVERS "GetServers"
+#define DLR_INTERFACE_RELEASE "Release"
+
+#define DLR_INTERFACE_FOUND_SERVER "FoundServer"
+#define DLR_INTERFACE_LOST_SERVER "LostServer"
+
+#define DLR_INTERFACE_HOST_FILE "HostFile"
+#define DLR_INTERFACE_REMOVE_FILE "RemoveFile"
+
+#define DLR_INTERFACE_VERSION "Version"
+#define DLR_INTERFACE_SERVERS "Servers"
+
+#define DLR_INTERFACE_PATH "Path"
+#define DLR_INTERFACE_URI "Uri"
+#define DLR_INTERFACE_ID "Id"
+
+#define DLR_INTERFACE_CHANGED_PROPERTIES "changed_properties"
+#define DLR_INTERFACE_INVALIDATED_PROPERTIES "invalidated_properties"
+#define DLR_INTERFACE_GET "Get"
+#define DLR_INTERFACE_GET_ALL "GetAll"
+#define DLR_INTERFACE_SET "Set"
+#define DLR_INTERFACE_INTERFACE_NAME "interface_name"
+#define DLR_INTERFACE_PROPERTY_NAME "property_name"
+#define DLR_INTERFACE_PROPERTIES_VALUE "properties"
+#define DLR_INTERFACE_VALUE "value"
+#define DLR_INTERFACE_OFFSET "offset"
+#define DLR_INTERFACE_POSITION "position"
+#define DLR_INTERFACE_TRACKID "trackid"
+#define DLR_INTERFACE_TRACK_NUMBER "TrackNumber"
+
+#define DLR_INTERFACE_RAISE "Raise"
+#define DLR_INTERFACE_QUIT "Quit"
+#define DLR_INTERFACE_PLAY "Play"
+#define DLR_INTERFACE_PLAY_PAUSE "PlayPause"
+#define DLR_INTERFACE_NEXT "Next"
+#define DLR_INTERFACE_PREVIOUS "Previous"
+#define DLR_INTERFACE_PAUSE "Pause"
+#define DLR_INTERFACE_STOP "Stop"
+#define DLR_INTERFACE_OPEN_URI "OpenUri"
+#define DLR_INTERFACE_SEEK "Seek"
+#define DLR_INTERFACE_SET_POSITION "SetPosition"
+#define DLR_INTERFACE_GOTO_TRACK "GotoTrack"
+
+#define DLR_INTERFACE_CANCEL "Cancel"
+
+typedef struct dlr_context_t_ dlr_context_t;
+struct dlr_context_t_ {
+ guint dlr_id;
+ dleyna_connector_id_t connection;
+ guint watchers;
+ dleyna_task_processor_t *processor;
+ const dleyna_connector_t *connector;
+ dlr_upnp_t *upnp;
+ dleyna_settings_t *settings;
+};
+
+static dlr_context_t g_context;
+
+static const gchar g_root_introspection[] =
+ "<node>"
+ " <interface name='"DLEYNA_SERVER_INTERFACE_MANAGER"'>"
+ " <method name='"DLR_INTERFACE_GET_VERSION"'>"
+ " <arg type='s' name='"DLR_INTERFACE_VERSION"'"
+ " direction='out'/>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_RELEASE"'>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_GET_SERVERS"'>"
+ " <arg type='as' name='"DLR_INTERFACE_SERVERS"'"
+ " direction='out'/>"
+ " </method>"
+ " <signal name='"DLR_INTERFACE_FOUND_SERVER"'>"
+ " <arg type='s' name='"DLR_INTERFACE_PATH"'/>"
+ " </signal>"
+ " <signal name='"DLR_INTERFACE_LOST_SERVER"'>"
+ " <arg type='s' name='"DLR_INTERFACE_PATH"'/>"
+ " </signal>"
+ " </interface>"
+ "</node>";
+
+static const gchar g_server_introspection[] =
+ "<node>"
+ " <interface name='"DLR_INTERFACE_PROPERTIES"'>"
+ " <method name='"DLR_INTERFACE_GET"'>"
+ " <arg type='s' name='"DLR_INTERFACE_INTERFACE_NAME"'"
+ " direction='in'/>"
+ " <arg type='s' name='"DLR_INTERFACE_PROPERTY_NAME"'"
+ " direction='in'/>"
+ " <arg type='v' name='"DLR_INTERFACE_VALUE"'"
+ " direction='out'/>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_GET_ALL"'>"
+ " <arg type='s' name='"DLR_INTERFACE_INTERFACE_NAME"'"
+ " direction='in'/>"
+ " <arg type='a{sv}' name='"DLR_INTERFACE_PROPERTIES_VALUE"'"
+ " direction='out'/>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_SET"'>"
+ " <arg type='s' name='"DLR_INTERFACE_INTERFACE_NAME"'"
+ " direction='in'/>"
+ " <arg type='s' name='"DLR_INTERFACE_PROPERTY_NAME"'"
+ " direction='in'/>"
+ " <arg type='v' name='"DLR_INTERFACE_VALUE"'"
+ " direction='in'/>"
+ " </method>"
+ " <signal name='"DLR_INTERFACE_PROPERTIES_CHANGED"'>"
+ " <arg type='s' name='"DLR_INTERFACE_INTERFACE_NAME"'/>"
+ " <arg type='a{sv}' name='"DLR_INTERFACE_CHANGED_PROPERTIES"'/>"
+ " <arg type='as' name='"DLR_INTERFACE_INVALIDATED_PROPERTIES"'/>"
+ " </signal>"
+ " </interface>"
+ " <interface name='"DLR_INTERFACE_SERVER"'>"
+ " <method name='"DLR_INTERFACE_RAISE"'>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_QUIT"'>"
+ " </method>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_QUIT"'"
+ " access='read'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_RAISE"'"
+ " access='read'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_SET_FULLSCREEN"'"
+ " access='read'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_HAS_TRACK_LIST"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_IDENTITY"'"
+ " access='read'/>"
+ " <property type='as' name='"DLR_INTERFACE_PROP_SUPPORTED_URIS"'"
+ " access='read'/>"
+ " <property type='as' name='"DLR_INTERFACE_PROP_SUPPORTED_MIME"'"
+ " access='read'/>"
+ " </interface>"
+ " <interface name='"DLR_INTERFACE_PLAYER"'>"
+ " <method name='"DLR_INTERFACE_PLAY"'>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_PAUSE"'>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_PLAY_PAUSE"'>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_STOP"'>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_NEXT"'>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_PREVIOUS"'>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_OPEN_URI"'>"
+ " <arg type='s' name='"DLR_INTERFACE_URI"'"
+ " direction='in'/>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_SEEK"'>"
+ " <arg type='x' name='"DLR_INTERFACE_OFFSET"'"
+ " direction='in'/>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_SET_POSITION"'>"
+ " <arg type='o' name='"DLR_INTERFACE_TRACKID"'"
+ " direction='in'/>"
+ " <arg type='x' name='"DLR_INTERFACE_POSITION"'"
+ " direction='in'/>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_GOTO_TRACK"'>"
+ " <arg type='u' name='"DLR_INTERFACE_TRACK_NUMBER"'"
+ " direction='in'/>"
+ " </method>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_PLAYBACK_STATUS"'"
+ " access='read'/>"
+ " <property type='d' name='"DLR_INTERFACE_PROP_RATE"'"
+ " access='readwrite'/>"
+ " <property type='d' name='"DLR_INTERFACE_PROP_MINIMUM_RATE"'"
+ " access='read'/>"
+ " <property type='d' name='"DLR_INTERFACE_PROP_MAXIMUM_RATE"'"
+ " access='read'/>"
+ " <property type='ad'"
+ " name='"DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS"'"
+ " access='read'/>"
+ " <property type='d' name='"DLR_INTERFACE_PROP_VOLUME"'"
+ " access='readwrite'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_PLAY"'"
+ " access='read'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_SEEK"'"
+ " access='read'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_CONTROL"'"
+ " access='read'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_PAUSE"'"
+ " access='read'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_NEXT"'"
+ " access='read'/>"
+ " <property type='b' name='"DLR_INTERFACE_PROP_CAN_PREVIOUS"'"
+ " access='read'/>"
+ " <property type='x' name='"DLR_INTERFACE_PROP_POSITION"'"
+ " access='read'/>"
+ " <property type='a{sv}' name='"DLR_INTERFACE_PROP_METADATA"'"
+ " access='read'/>"
+ " <property type='u' name='"DLR_INTERFACE_PROP_CURRENT_TRACK"'"
+ " access='read'/>"
+ " <property type='u' name='"DLR_INTERFACE_PROP_NUMBER_OF_TRACKS"'"
+ " access='read'/>"
+ " </interface>"
+ " <interface name='"DLEYNA_INTERFACE_PUSH_HOST"'>"
+ " <method name='"DLR_INTERFACE_HOST_FILE"'>"
+ " <arg type='s' name='"DLR_INTERFACE_PATH"'"
+ " direction='in'/>"
+ " <arg type='s' name='"DLR_INTERFACE_URI"'"
+ " direction='out'/>"
+ " </method>"
+ " <method name='"DLR_INTERFACE_REMOVE_FILE"'>"
+ " <arg type='s' name='"DLR_INTERFACE_PATH"'"
+ " direction='in'/>"
+ " </method>"
+ " </interface>"
+ " <interface name='"DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE"'>"
+ " <method name='"DLR_INTERFACE_CANCEL"'>"
+ " </method>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_DEVICE_TYPE"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_UDN"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_FRIENDLY_NAME"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_ICON_URL"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_MANUFACTURER"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_MANUFACTURER_URL"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_MODEL_DESCRIPTION"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_MODEL_NAME"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_MODEL_NUMBER"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_SERIAL_NUMBER"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_PRESENTATION_URL"'"
+ " access='read'/>"
+ " <property type='s' name='"DLR_INTERFACE_PROP_PROTOCOL_INFO"'"
+ " access='read'/>"
+ " </interface>"
+ "</node>";
+
+static void prv_process_task(dleyna_task_atom_t *task, gpointer user_data);
+
+static void prv_dlr_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation);
+
+static void prv_dlr_device_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation);
+
+static void prv_props_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation);
+
+static void prv_dlr_player_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation);
+
+static void prv_dlr_push_host_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation);
+
+static void prv_renderer_device_method_call(
+ dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation);
+
+static const dleyna_connector_dispatch_cb_t g_root_vtables[1] = {
+ prv_dlr_method_call
+};
+
+static const dleyna_connector_dispatch_cb_t
+ g_server_vtables[DLR_INTERFACE_INFO_MAX] = {
+ /* MUST be in the exact same order as g_msu_server_introspection */
+ prv_props_method_call,
+ prv_dlr_device_method_call,
+ prv_dlr_player_method_call,
+ prv_dlr_push_host_method_call,
+ prv_renderer_device_method_call
+};
+
+const dleyna_connector_t *dlr_renderer_get_connector(void)
+{
+ return g_context.connector;
+}
+
+dleyna_task_processor_t *dlr_renderer_service_get_task_processor(void)
+{
+ return g_context.processor;
+}
+
+dlr_upnp_t *dlr_renderer_service_get_upnp(void)
+{
+ return g_context.upnp;
+}
+
+static void prv_process_sync_task(dlr_task_t *task)
+{
+ GError *error;
+
+ switch (task->type) {
+ case DLR_TASK_GET_VERSION:
+ dlr_task_complete(task);
+ dleyna_task_queue_task_completed(task->atom.queue_id);
+ break;
+ case DLR_TASK_GET_SERVERS:
+ task->result = dlr_upnp_get_server_ids(g_context.upnp);
+ dlr_task_complete(task);
+ dleyna_task_queue_task_completed(task->atom.queue_id);
+ break;
+ case DLR_TASK_RAISE:
+ case DLR_TASK_QUIT:
+ error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_NOT_SUPPORTED,
+ "Command not supported.");
+ dlr_task_fail(task, error);
+ dleyna_task_queue_task_completed(task->atom.queue_id);
+ g_error_free(error);
+ break;
+ default:
+ break;
+ }
+}
+
+static void prv_async_task_complete(dlr_task_t *task, GError *error)
+{
+ DLEYNA_LOG_DEBUG("Enter");
+
+ if (error) {
+ dlr_task_fail(task, error);
+ g_error_free(error);
+ } else {
+ dlr_task_complete(task);
+ }
+
+ dleyna_task_queue_task_completed(task->atom.queue_id);
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+static void prv_process_async_task(dlr_task_t *task)
+{
+ dlr_async_task_t *async_task = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ async_task->cancellable = g_cancellable_new();
+
+ switch (task->type) {
+ case DLR_TASK_GET_PROP:
+ dlr_upnp_get_prop(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_GET_ALL_PROPS:
+ dlr_upnp_get_all_props(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_SET_PROP:
+ dlr_upnp_set_prop(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_PLAY:
+ dlr_upnp_play(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_PAUSE:
+ dlr_upnp_pause(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_PLAY_PAUSE:
+ dlr_upnp_play_pause(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_STOP:
+ dlr_upnp_stop(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_NEXT:
+ dlr_upnp_next(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_PREVIOUS:
+ dlr_upnp_previous(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_OPEN_URI:
+ dlr_upnp_open_uri(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_SEEK:
+ dlr_upnp_seek(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_SET_POSITION:
+ dlr_upnp_set_position(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_GOTO_TRACK:
+ dlr_upnp_goto_track(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_HOST_URI:
+ dlr_upnp_host_uri(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ case DLR_TASK_REMOVE_URI:
+ dlr_upnp_remove_uri(g_context.upnp, task,
+ prv_async_task_complete);
+ break;
+ default:
+ break;
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+static void prv_process_task(dleyna_task_atom_t *task, gpointer user_data)
+{
+ dlr_task_t *client_task = (dlr_task_t *)task;
+
+ if (client_task->synchronous)
+ prv_process_sync_task(client_task);
+ else
+ prv_process_async_task(client_task);
+}
+
+static void prv_cancel_task(dleyna_task_atom_t *task, gpointer user_data)
+{
+ dlr_task_cancel((dlr_task_t *)task);
+}
+
+static void prv_delete_task(dleyna_task_atom_t *task, gpointer user_data)
+{
+ dlr_task_delete((dlr_task_t *)task);
+}
+
+static void prv_remove_client(const gchar *name)
+{
+ dleyna_task_processor_remove_queues_for_source(g_context.processor,
+ name);
+
+ dlr_upnp_lost_client(g_context.upnp, name);
+
+ g_context.watchers--;
+ if (g_context.watchers == 0)
+ if (!dleyna_settings_is_never_quit(g_context.settings))
+ dleyna_task_processor_set_quitting(g_context.processor);
+}
+
+static void prv_lost_client(const gchar *name)
+{
+ DLEYNA_LOG_INFO("Client %s lost", name);
+ prv_remove_client(name);
+}
+
+static void prv_control_point_initialize(const dleyna_connector_t *connector,
+ dleyna_task_processor_t *processor,
+ dleyna_settings_t *settings)
+{
+ memset(&g_context, 0, sizeof(g_context));
+
+ g_context.processor = processor;
+ g_context.settings = settings;
+ g_context.connector = connector;
+ g_context.connector->set_client_lost_cb(prv_lost_client);
+
+ g_set_prgname(DLR_PRG_NAME);
+}
+
+static void prv_control_point_stop_service(void)
+{
+ dlr_upnp_unsubscribe(g_context.upnp);
+
+ if (g_context.upnp)
+ dlr_upnp_delete(g_context.upnp);
+
+ if (g_context.connection) {
+ if (g_context.dlr_id)
+ g_context.connector->unpublish_object(
+ g_context.connection,
+ g_context.dlr_id);
+ }
+}
+
+static void prv_control_point_free(void)
+{
+}
+
+static void prv_add_task(dlr_task_t *task, const gchar *source,
+ const gchar *sink)
+{
+ const dleyna_task_queue_key_t *queue_id;
+
+ if (g_context.connector->watch_client(source))
+ g_context.watchers++;
+
+ queue_id = dleyna_task_processor_lookup_queue(g_context.processor,
+ source, sink);
+ if (!queue_id)
+ queue_id = dleyna_task_processor_add_queue(
+ g_context.processor,
+ source,
+ sink,
+ DLEYNA_TASK_QUEUE_FLAG_AUTO_START,
+ prv_process_task,
+ prv_cancel_task,
+ prv_delete_task);
+
+ dleyna_task_queue_add_task(queue_id, &task->atom);
+}
+
+static void prv_dlr_method_call(dleyna_connector_id_t conn,
+ const gchar *sender, const gchar *object,
+ const gchar *interface,
+ const gchar *method, GVariant *parameters,
+ dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task;
+
+ DLEYNA_LOG_INFO("Calling %s method", method);
+
+ if (!strcmp(method, DLR_INTERFACE_RELEASE)) {
+ g_context.connector->unwatch_client(sender);
+ prv_remove_client(sender);
+ g_context.connector->return_response(invocation, NULL);
+ } else {
+ if (!strcmp(method, DLR_INTERFACE_GET_VERSION))
+ task = dlr_task_get_version_new(invocation);
+ else if (!strcmp(method, DLR_INTERFACE_GET_SERVERS))
+ task = dlr_task_get_servers_new(invocation);
+ else
+ goto finished;
+
+ prv_add_task(task, sender, DLR_RENDERER_SINK);
+ }
+
+finished:
+
+ return;
+}
+
+static const gchar *prv_get_device_id(const gchar *object, GError **error)
+{
+ dlr_device_t *device;
+
+ device = dlr_device_from_path(object,
+ dlr_upnp_get_server_udn_map(g_context.upnp));
+
+
+ if (!device) {
+ DLEYNA_LOG_WARNING("Cannot locate device for %s", object);
+
+ *error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate device corresponding to the specified path");
+ goto on_error;
+ }
+
+ return device->path;
+
+on_error:
+
+ return NULL;
+}
+
+static void prv_props_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task;
+ const gchar *device_id;
+ GError *error = NULL;
+
+ device_id = prv_get_device_id(object, &error);
+ if (!device_id) {
+ g_context.connector->return_error(invocation, error);
+ g_error_free(error);
+
+ goto finished;
+ }
+
+ if (!strcmp(method, DLR_INTERFACE_GET_ALL))
+ task = dlr_task_get_props_new(invocation, object, parameters);
+ else if (!strcmp(method, DLR_INTERFACE_GET))
+ task = dlr_task_get_prop_new(invocation, object, parameters);
+ else if (!strcmp(method, DLR_INTERFACE_SET))
+ task = dlr_task_set_prop_new(invocation, object, parameters);
+ else
+ goto finished;
+
+ prv_add_task(task, sender, device_id);
+
+finished:
+
+ return;
+}
+
+static void prv_dlr_device_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task;
+ const gchar *device_id;
+ GError *error = NULL;
+
+ device_id = prv_get_device_id(object, &error);
+ if (!device_id) {
+ g_context.connector->return_error(invocation, error);
+ g_error_free(error);
+
+ goto finished;
+ }
+
+ if (!strcmp(method, DLR_INTERFACE_RAISE))
+ task = dlr_task_raise_new(invocation);
+ else if (!strcmp(method, DLR_INTERFACE_QUIT))
+ task = dlr_task_quit_new(invocation);
+ else
+ goto finished;
+
+ prv_add_task(task, sender, device_id);
+
+finished:
+
+ return;
+}
+
+static void prv_dlr_player_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task;
+ const gchar *device_id;
+ GError *error = NULL;
+
+ device_id = prv_get_device_id(object, &error);
+ if (!device_id) {
+ g_context.connector->return_error(invocation, error);
+ g_error_free(error);
+
+ goto finished;
+ }
+
+ if (!strcmp(method, DLR_INTERFACE_PLAY))
+ task = dlr_task_play_new(invocation, object);
+ else if (!strcmp(method, DLR_INTERFACE_PAUSE))
+ task = dlr_task_pause_new(invocation, object);
+ else if (!strcmp(method, DLR_INTERFACE_PLAY_PAUSE))
+ task = dlr_task_play_pause_new(invocation, object);
+ else if (!strcmp(method, DLR_INTERFACE_STOP))
+ task = dlr_task_stop_new(invocation, object);
+ else if (!strcmp(method, DLR_INTERFACE_NEXT))
+ task = dlr_task_next_new(invocation, object);
+ else if (!strcmp(method, DLR_INTERFACE_PREVIOUS))
+ task = dlr_task_previous_new(invocation, object);
+ else if (!strcmp(method, DLR_INTERFACE_OPEN_URI))
+ task = dlr_task_open_uri_new(invocation, object, parameters);
+ else if (!strcmp(method, DLR_INTERFACE_SEEK))
+ task = dlr_task_seek_new(invocation, object, parameters);
+ else if (!strcmp(method, DLR_INTERFACE_SET_POSITION))
+ task = dlr_task_set_position_new(invocation, object,
+ parameters);
+ else if (!strcmp(method, DLR_INTERFACE_GOTO_TRACK))
+ task = dlr_task_goto_track_new(invocation, object, parameters);
+ else
+ goto finished;
+
+ prv_add_task(task, sender, device_id);
+
+finished:
+
+ return;
+}
+
+static void prv_dlr_push_host_method_call(dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task;
+ const gchar *device_id;
+ GError *error = NULL;
+
+ device_id = prv_get_device_id(object, &error);
+ if (!device_id) {
+ g_context.connector->return_error(invocation, error);
+ g_error_free(error);
+
+ goto on_error;
+ }
+
+ if (!strcmp(method, DLR_INTERFACE_HOST_FILE))
+ task = dlr_task_host_uri_new(invocation, object, sender,
+ parameters);
+ else if (!strcmp(method, DLR_INTERFACE_REMOVE_FILE))
+ task = dlr_task_remove_uri_new(invocation, object, sender,
+ parameters);
+ else
+ goto on_error;
+
+ prv_add_task(task, sender, device_id);
+
+on_error:
+
+ return;
+}
+
+static void prv_renderer_device_method_call(
+ dleyna_connector_id_t conn,
+ const gchar *sender,
+ const gchar *object,
+ const gchar *interface,
+ const gchar *method,
+ GVariant *parameters,
+ dleyna_connector_msg_id_t invocation)
+{
+ const gchar *device_id = NULL;
+ GError *error = NULL;
+ const dleyna_task_queue_key_t *queue_id;
+
+ device_id = prv_get_device_id(object, &error);
+ if (!device_id) {
+ g_context.connector->return_error(invocation, error);
+ g_error_free(error);
+
+ goto finished;
+ }
+
+ if (!strcmp(method, DLR_INTERFACE_CANCEL)) {
+ queue_id = dleyna_task_processor_lookup_queue(
+ g_context.processor,
+ sender, device_id);
+ if (queue_id)
+ dleyna_task_processor_cancel_queue(queue_id);
+
+ g_context.connector->return_response(invocation, NULL);
+ }
+
+finished:
+
+ return;
+}
+
+static void prv_found_media_server(const gchar *path)
+{
+ DLEYNA_LOG_INFO("New media server %s", path);
+
+ (void) g_context.connector->notify(g_context.connection,
+ DLEYNA_SERVER_OBJECT,
+ DLEYNA_SERVER_INTERFACE_MANAGER,
+ DLR_INTERFACE_FOUND_SERVER,
+ g_variant_new("(s)", path),
+ NULL);
+}
+
+static void prv_lost_media_server(const gchar *path)
+{
+ DLEYNA_LOG_INFO("Lost %s", path);
+
+ (void) g_context.connector->notify(g_context.connection,
+ DLEYNA_SERVER_OBJECT,
+ DLEYNA_SERVER_INTERFACE_MANAGER,
+ DLR_INTERFACE_LOST_SERVER,
+ g_variant_new("(s)", path),
+ NULL);
+
+ dleyna_task_processor_remove_queues_for_sink(g_context.processor, path);
+}
+
+static gboolean prv_control_point_start_service(
+ dleyna_connector_id_t connection)
+{
+ gboolean retval = TRUE;
+
+ g_context.connection = connection;
+
+ g_context.dlr_id = g_context.connector->publish_object(
+ connection,
+ DLEYNA_SERVER_OBJECT,
+ TRUE,
+ 0,
+ g_root_vtables);
+
+ if (!g_context.dlr_id) {
+ retval = FALSE;
+ goto out;
+ } else {
+ g_context.upnp = dlr_upnp_new(connection,
+ g_server_vtables,
+ prv_found_media_server,
+ prv_lost_media_server);
+ }
+
+out:
+
+ return retval;
+}
+
+static const gchar *prv_control_point_server_name(void)
+{
+ return DLEYNA_SERVER_NAME;
+}
+
+static const gchar *prv_control_point_server_introspection(void)
+{
+ return g_server_introspection;
+}
+
+static const gchar *prv_control_point_root_introspection(void)
+{
+ return g_root_introspection;
+}
+
+static const dleyna_control_point_t g_control_point = {
+ prv_control_point_initialize,
+ prv_control_point_free,
+ prv_control_point_server_name,
+ prv_control_point_server_introspection,
+ prv_control_point_root_introspection,
+ prv_control_point_start_service,
+ prv_control_point_stop_service
+};
+
+const dleyna_control_point_t *dleyna_control_point_get_renderer(void)
+{
+ return &g_control_point;
+}
+
diff --git a/libdleyna/renderer/server.h b/libdleyna/renderer/server.h
new file mode 100644
index 0000000..ad770e9
--- /dev/null
+++ b/libdleyna/renderer/server.h
@@ -0,0 +1,40 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Regis Merlino <regis.merlino@intel.com>
+ *
+ */
+
+#ifndef DLR_SERVER_H__
+#define DLR_SERVER_H__
+
+#include <libdleyna/core/connector.h>
+#include <libdleyna/core/task-processor.h>
+
+#define DLR_RENDERER_SINK "dleyna-renderer"
+
+typedef struct dlr_device_t_ dlr_device_t;
+typedef struct dlr_upnp_t_ dlr_upnp_t;
+
+dlr_upnp_t *dlr_renderer_service_get_upnp(void);
+
+dleyna_task_processor_t *dlr_renderer_service_get_task_processor(void);
+
+const dleyna_connector_t *dlr_renderer_get_connector(void);
+
+#endif /* DLR_SERVER_H__ */
diff --git a/libdleyna/renderer/task.c b/libdleyna/renderer/task.c
new file mode 100644
index 0000000..f461824
--- /dev/null
+++ b/libdleyna/renderer/task.c
@@ -0,0 +1,378 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#include <libdleyna/core/error.h>
+#include <libdleyna/core/task-processor.h>
+
+#include "async.h"
+#include "server.h"
+
+dlr_task_t *dlr_task_get_version_new(dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task = g_new0(dlr_task_t, 1);
+
+ task->type = DLR_TASK_GET_VERSION;
+ task->invocation = invocation;
+ task->result_format = "(@s)";
+ task->result = g_variant_ref_sink(g_variant_new_string(VERSION));
+ task->synchronous = TRUE;
+
+ return task;
+}
+
+dlr_task_t *dlr_task_get_servers_new(dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task = g_new0(dlr_task_t, 1);
+
+ task->type = DLR_TASK_GET_SERVERS;
+ task->invocation = invocation;
+ task->result_format = "(@as)";
+ task->synchronous = TRUE;
+
+ return task;
+}
+
+dlr_task_t *dlr_task_raise_new(dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task = g_new0(dlr_task_t, 1);
+
+ task->type = DLR_TASK_RAISE;
+ task->invocation = invocation;
+ task->synchronous = TRUE;
+
+ return task;
+}
+
+dlr_task_t *dlr_task_quit_new(dleyna_connector_msg_id_t invocation)
+{
+ dlr_task_t *task = g_new0(dlr_task_t, 1);
+
+ task->type = DLR_TASK_QUIT;
+ task->invocation = invocation;
+ task->synchronous = TRUE;
+
+ return task;
+}
+
+static void prv_dlr_task_delete(dlr_task_t *task)
+{
+ if (!task->synchronous)
+ dlr_async_task_delete((dlr_async_task_t *)task);
+
+ switch (task->type) {
+ case DLR_TASK_GET_ALL_PROPS:
+ g_free(task->ut.get_props.interface_name);
+ break;
+ case DLR_TASK_GET_PROP:
+ g_free(task->ut.get_prop.interface_name);
+ g_free(task->ut.get_prop.prop_name);
+ break;
+ case DLR_TASK_SET_PROP:
+ g_free(task->ut.set_prop.interface_name);
+ g_free(task->ut.set_prop.prop_name);
+ g_variant_unref(task->ut.set_prop.params);
+ break;
+ case DLR_TASK_OPEN_URI:
+ g_free(task->ut.open_uri.uri);
+ break;
+ case DLR_TASK_HOST_URI:
+ case DLR_TASK_REMOVE_URI:
+ g_free(task->ut.host_uri.uri);
+ g_free(task->ut.host_uri.client);
+ break;
+ default:
+ break;
+ }
+
+ g_free(task->path);
+ if (task->result)
+ g_variant_unref(task->result);
+
+ g_free(task);
+}
+
+static dlr_task_t *prv_device_task_new(dlr_task_type_t type,
+ dleyna_connector_msg_id_t invocation,
+ const gchar *path,
+ const gchar *result_format)
+{
+ dlr_task_t *task = (dlr_task_t *)g_new0(dlr_async_task_t, 1);
+
+ task->type = type;
+ task->invocation = invocation;
+ task->result_format = result_format;
+
+ task->path = g_strdup(path);
+ g_strstrip(task->path);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_get_prop_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters)
+{
+ dlr_task_t *task;
+
+ task = prv_device_task_new(DLR_TASK_GET_PROP, invocation, path, "(v)");
+
+ g_variant_get(parameters, "(ss)", &task->ut.get_prop.interface_name,
+ &task->ut.get_prop.prop_name);
+
+ g_strstrip(task->ut.get_prop.interface_name);
+ g_strstrip(task->ut.get_prop.prop_name);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_get_props_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters)
+{
+ dlr_task_t *task;
+
+ task = prv_device_task_new(DLR_TASK_GET_ALL_PROPS, invocation, path,
+ "(@a{sv})");
+
+ g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name);
+ g_strstrip(task->ut.get_props.interface_name);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_set_prop_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters)
+{
+ dlr_task_t *task;
+
+ task = prv_device_task_new(DLR_TASK_SET_PROP, invocation, path, NULL);
+
+ g_variant_get(parameters, "(ssv)", &task->ut.set_prop.interface_name,
+ &task->ut.set_prop.prop_name, &task->ut.set_prop.params);
+
+ g_strstrip(task->ut.set_prop.interface_name);
+ g_strstrip(task->ut.set_prop.prop_name);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_play_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path)
+{
+ return prv_device_task_new(DLR_TASK_PLAY, invocation, path, NULL);
+}
+
+dlr_task_t *dlr_task_pause_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path)
+{
+ return prv_device_task_new(DLR_TASK_PAUSE, invocation, path, NULL);
+}
+
+dlr_task_t *dlr_task_play_pause_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path)
+{
+ return prv_device_task_new(DLR_TASK_PLAY_PAUSE, invocation, path, NULL);
+}
+
+dlr_task_t *dlr_task_stop_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path)
+{
+ return prv_device_task_new(DLR_TASK_STOP, invocation, path, NULL);
+}
+
+dlr_task_t *dlr_task_next_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path)
+{
+ return prv_device_task_new(DLR_TASK_NEXT, invocation, path, NULL);
+}
+
+dlr_task_t *dlr_task_previous_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path)
+{
+ return prv_device_task_new(DLR_TASK_PREVIOUS, invocation, path, NULL);
+}
+
+dlr_task_t *dlr_task_seek_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters)
+{
+ dlr_task_t *task = prv_device_task_new(DLR_TASK_SEEK, invocation,
+ path, NULL);
+
+ g_variant_get(parameters, "(x)", &task->ut.seek.position);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_set_position_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters)
+{
+ gchar *track_id;
+
+ dlr_task_t *task = prv_device_task_new(DLR_TASK_SET_POSITION,
+ invocation, path, NULL);
+
+ g_variant_get(parameters, "(&ox)", &track_id, &task->ut.seek.position);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_goto_track_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters)
+{
+ dlr_task_t *task = prv_device_task_new(DLR_TASK_GOTO_TRACK,
+ invocation, path, NULL);
+
+ g_variant_get(parameters, "(u)", &task->ut.seek.track_number);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_open_uri_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters)
+{
+ dlr_task_t *task;
+
+ task = prv_device_task_new(DLR_TASK_OPEN_URI, invocation, path,
+ NULL);
+
+ g_variant_get(parameters, "(s)", &task->ut.open_uri.uri);
+ g_strstrip(task->ut.open_uri.uri);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_host_uri_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path,
+ const gchar *sender,
+ GVariant *parameters)
+{
+ dlr_task_t *task;
+
+ task = prv_device_task_new(DLR_TASK_HOST_URI, invocation, path,
+ "(@s)");
+
+ g_variant_get(parameters, "(s)", &task->ut.host_uri.uri);
+ g_strstrip(task->ut.host_uri.uri);
+ task->ut.host_uri.client = g_strdup(sender);
+
+ return task;
+}
+
+dlr_task_t *dlr_task_remove_uri_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path,
+ const gchar *sender,
+ GVariant *parameters)
+{
+ dlr_task_t *task;
+
+ task = prv_device_task_new(DLR_TASK_REMOVE_URI, invocation, path, NULL);
+
+ g_variant_get(parameters, "(s)", &task->ut.host_uri.uri);
+ g_strstrip(task->ut.host_uri.uri);
+ task->ut.host_uri.client = g_strdup(sender);
+
+ return task;
+}
+
+void dlr_task_complete(dlr_task_t *task)
+{
+ if (!task)
+ goto finished;
+
+ if (task->invocation) {
+ if (task->result_format && task->result)
+ dlr_renderer_get_connector()->return_response(
+ task->invocation,
+ g_variant_new(task->result_format,
+ task->result));
+ else
+ dlr_renderer_get_connector()->return_response(
+ task->invocation,
+ NULL);
+
+ task->invocation = NULL;
+ }
+
+finished:
+
+ return;
+}
+
+void dlr_task_fail(dlr_task_t *task, GError *error)
+{
+ if (!task)
+ goto finished;
+
+ if (task->invocation) {
+ dlr_renderer_get_connector()->return_error(task->invocation,
+ error);
+ task->invocation = NULL;
+ }
+
+finished:
+
+ return;
+}
+
+void dlr_task_cancel(dlr_task_t *task)
+{
+ GError *error;
+
+ if (!task)
+ goto finished;
+
+ if (task->invocation) {
+ error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED,
+ "Operation cancelled.");
+ dlr_renderer_get_connector()->return_error(task->invocation,
+ error);
+ task->invocation = NULL;
+ g_error_free(error);
+ }
+
+ if (!task->synchronous)
+ dlr_async_task_cancel((dlr_async_task_t *)task);
+
+finished:
+
+ return;
+}
+
+void dlr_task_delete(dlr_task_t *task)
+{
+ GError *error;
+
+ if (!task)
+ goto finished;
+
+ if (task->invocation) {
+ error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_DIED,
+ "Unable to complete command.");
+ dlr_renderer_get_connector()->return_error(task->invocation,
+ error);
+ g_error_free(error);
+ }
+
+ prv_dlr_task_delete(task);
+
+finished:
+
+ return;
+}
diff --git a/libdleyna/renderer/task.h b/libdleyna/renderer/task.h
new file mode 100644
index 0000000..4fd11b9
--- /dev/null
+++ b/libdleyna/renderer/task.h
@@ -0,0 +1,174 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#ifndef DLR_TASK_H__
+#define DLR_TASK_H__
+
+#include <gio/gio.h>
+#include <glib.h>
+
+#include <libdleyna/core/connector.h>
+#include <libdleyna/core/task-atom.h>
+
+enum dlr_task_type_t_ {
+ DLR_TASK_GET_VERSION,
+ DLR_TASK_GET_SERVERS,
+ DLR_TASK_RAISE,
+ DLR_TASK_QUIT,
+ DLR_TASK_SET_PROP,
+ DLR_TASK_GET_ALL_PROPS,
+ DLR_TASK_GET_PROP,
+ DLR_TASK_PAUSE,
+ DLR_TASK_PLAY,
+ DLR_TASK_PLAY_PAUSE,
+ DLR_TASK_STOP,
+ DLR_TASK_NEXT,
+ DLR_TASK_PREVIOUS,
+ DLR_TASK_OPEN_URI,
+ DLR_TASK_SEEK,
+ DLR_TASK_SET_POSITION,
+ DLR_TASK_GOTO_TRACK,
+ DLR_TASK_HOST_URI,
+ DLR_TASK_REMOVE_URI
+};
+typedef enum dlr_task_type_t_ dlr_task_type_t;
+
+typedef void (*dlr_cancel_task_t)(void *handle);
+
+typedef struct dlr_task_get_props_t_ dlr_task_get_props_t;
+struct dlr_task_get_props_t_ {
+ gchar *interface_name;
+};
+
+typedef struct dlr_task_get_prop_t_ dlr_task_get_prop_t;
+struct dlr_task_get_prop_t_ {
+ gchar *prop_name;
+ gchar *interface_name;
+};
+
+typedef struct dlr_task_set_prop_t_ dlr_task_set_prop_t;
+struct dlr_task_set_prop_t_ {
+ gchar *prop_name;
+ gchar *interface_name;
+ GVariant *params;
+};
+
+typedef struct dlr_task_open_uri_t_ dlr_task_open_uri_t;
+struct dlr_task_open_uri_t_ {
+ gchar *uri;
+};
+
+typedef struct dlr_task_seek_t_ dlr_task_seek_t;
+struct dlr_task_seek_t_ {
+ gint64 position;
+ guint32 track_number;
+};
+
+typedef struct dlr_task_host_uri_t_ dlr_task_host_uri_t;
+struct dlr_task_host_uri_t_ {
+ gchar *uri;
+ gchar *client;
+};
+
+typedef struct dlr_task_t_ dlr_task_t;
+struct dlr_task_t_ {
+ dleyna_task_atom_t atom; /* pseudo inheritance - MUST be first field */
+ dlr_task_type_t type;
+ gchar *path;
+ const gchar *result_format;
+ GVariant *result;
+ dleyna_connector_msg_id_t invocation;
+ gboolean synchronous;
+ union {
+ dlr_task_get_props_t get_props;
+ dlr_task_get_prop_t get_prop;
+ dlr_task_set_prop_t set_prop;
+ dlr_task_open_uri_t open_uri;
+ dlr_task_host_uri_t host_uri;
+ dlr_task_seek_t seek;
+ } ut;
+};
+
+dlr_task_t *dlr_task_get_version_new(dleyna_connector_msg_id_t invocation);
+
+dlr_task_t *dlr_task_get_servers_new(dleyna_connector_msg_id_t invocation);
+
+dlr_task_t *dlr_task_raise_new(dleyna_connector_msg_id_t invocation);
+
+dlr_task_t *dlr_task_quit_new(dleyna_connector_msg_id_t invocation);
+
+dlr_task_t *dlr_task_set_prop_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters);
+
+dlr_task_t *dlr_task_get_prop_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters);
+
+dlr_task_t *dlr_task_get_props_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters);
+
+dlr_task_t *dlr_task_play_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path);
+
+dlr_task_t *dlr_task_pause_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path);
+
+dlr_task_t *dlr_task_play_pause_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path);
+
+dlr_task_t *dlr_task_stop_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path);
+
+dlr_task_t *dlr_task_next_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path);
+
+dlr_task_t *dlr_task_previous_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path);
+
+dlr_task_t *dlr_task_seek_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters);
+
+dlr_task_t *dlr_task_set_position_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters);
+
+dlr_task_t *dlr_task_goto_track_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters);
+
+dlr_task_t *dlr_task_open_uri_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, GVariant *parameters);
+
+dlr_task_t *dlr_task_host_uri_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, const gchar *sender,
+ GVariant *parameters);
+
+dlr_task_t *dlr_task_remove_uri_new(dleyna_connector_msg_id_t invocation,
+ const gchar *path, const gchar *sender,
+ GVariant *parameters);
+
+void dlr_task_complete(dlr_task_t *task);
+
+void dlr_task_fail(dlr_task_t *task, GError *error);
+
+void dlr_task_delete(dlr_task_t *task);
+
+void dlr_task_cancel(dlr_task_t *task);
+
+#endif
diff --git a/libdleyna/renderer/upnp.c b/libdleyna/renderer/upnp.c
new file mode 100644
index 0000000..e6d905f
--- /dev/null
+++ b/libdleyna/renderer/upnp.c
@@ -0,0 +1,752 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#include <string.h>
+
+#include <libgssdp/gssdp-resource-browser.h>
+#include <libgupnp/gupnp-context-manager.h>
+#include <libgupnp/gupnp-error.h>
+
+#include <libdleyna/core/error.h>
+#include <libdleyna/core/log.h>
+#include <libdleyna/core/service-task.h>
+
+#include "async.h"
+#include "device.h"
+#include "host-service.h"
+#include "prop-defs.h"
+#include "upnp.h"
+
+struct dlr_upnp_t_ {
+ dleyna_connector_id_t connection;
+ const dleyna_connector_dispatch_cb_t *interface_info;
+ dlr_upnp_callback_t found_server;
+ dlr_upnp_callback_t lost_server;
+ GUPnPContextManager *context_manager;
+ void *user_data;
+ GHashTable *server_udn_map;
+ GHashTable *server_uc_map;
+ guint counter;
+ dlr_host_service_t *host_service;
+};
+
+/* Private structure used in service task */
+typedef struct prv_device_new_ct_t_ prv_device_new_ct_t;
+struct prv_device_new_ct_t_ {
+ dlr_upnp_t *upnp;
+ char *udn;
+ dlr_device_t *device;
+ const dleyna_task_queue_key_t *queue_id;
+};
+
+static void prv_device_new_free(prv_device_new_ct_t *priv_t)
+{
+ if (priv_t) {
+ g_free(priv_t->udn);
+ g_free(priv_t);
+ }
+}
+
+static void prv_device_chain_end(gboolean cancelled, gpointer data)
+{
+ dlr_device_t *device;
+ prv_device_new_ct_t *priv_t = (prv_device_new_ct_t *)data;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = priv_t->device;
+
+ if (cancelled)
+ goto on_clear;
+
+ DLEYNA_LOG_DEBUG("Notify new server available: %s", device->path);
+ g_hash_table_insert(priv_t->upnp->server_udn_map, g_strdup(priv_t->udn),
+ device);
+ priv_t->upnp->found_server(device->path);
+
+on_clear:
+
+ g_hash_table_remove(priv_t->upnp->server_uc_map, priv_t->udn);
+ prv_device_new_free(priv_t);
+
+ if (cancelled)
+ dlr_device_delete(device);
+
+ DLEYNA_LOG_DEBUG("Exit");
+ DLEYNA_LOG_DEBUG_NL();
+}
+
+static void prv_server_available_cb(GUPnPControlPoint *cp,
+ GUPnPDeviceProxy *proxy,
+ gpointer user_data)
+{
+ dlr_upnp_t *upnp = user_data;
+ const char *udn;
+ dlr_device_t *device;
+ const gchar *ip_address;
+ dlr_device_context_t *context;
+ const dleyna_task_queue_key_t *queue_id;
+ unsigned int i;
+ prv_device_new_ct_t *priv_t;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)proxy);
+
+ if (!udn)
+ goto on_error;
+
+ ip_address = gupnp_context_get_host_ip(
+ gupnp_control_point_get_context(cp));
+
+ DLEYNA_LOG_DEBUG("UDN %s", udn);
+ DLEYNA_LOG_DEBUG("IP Address %s", ip_address);
+
+ device = g_hash_table_lookup(upnp->server_udn_map, udn);
+
+ if (!device) {
+ priv_t = g_hash_table_lookup(upnp->server_uc_map, udn);
+
+ if (priv_t)
+ device = priv_t->device;
+ }
+
+ if (!device) {
+ DLEYNA_LOG_DEBUG("Device not found. Adding");
+
+ priv_t = g_new0(prv_device_new_ct_t, 1);
+
+ queue_id = dleyna_task_processor_add_queue(
+ dlr_renderer_service_get_task_processor(),
+ dleyna_service_task_create_source(),
+ DLR_RENDERER_SINK,
+ DLEYNA_TASK_QUEUE_FLAG_AUTO_REMOVE,
+ dleyna_service_task_process_cb,
+ dleyna_service_task_cancel_cb,
+ dleyna_service_task_delete_cb);
+ dleyna_task_queue_set_finally(queue_id, prv_device_chain_end);
+ dleyna_task_queue_set_user_data(queue_id, priv_t);
+
+ device = dlr_device_new(upnp->connection, proxy, ip_address,
+ upnp->counter,
+ upnp->interface_info,
+ queue_id);
+
+ upnp->counter++;
+
+ priv_t->upnp = upnp;
+ priv_t->udn = g_strdup(udn);
+ priv_t->queue_id = queue_id;
+ priv_t->device = device;
+
+ g_hash_table_insert(upnp->server_uc_map, g_strdup(udn), priv_t);
+ } else {
+ DLEYNA_LOG_DEBUG("Device Found");
+
+ for (i = 0; i < device->contexts->len; ++i) {
+ context = g_ptr_array_index(device->contexts, i);
+ if (!strcmp(context->ip_address, ip_address))
+ break;
+ }
+
+ if (i == device->contexts->len) {
+ DLEYNA_LOG_DEBUG("Adding Context");
+ dlr_device_append_new_context(device, ip_address,
+ proxy);
+ }
+ }
+
+on_error:
+
+ DLEYNA_LOG_DEBUG("Exit");
+ DLEYNA_LOG_DEBUG_NL();
+
+ return;
+}
+
+static gboolean prv_subscribe_to_service_changes(gpointer user_data)
+{
+ dlr_device_t *device = user_data;
+
+ device->timeout_id = 0;
+ dlr_device_subscribe_to_service_changes(device);
+
+ return FALSE;
+}
+
+static void prv_server_unavailable_cb(GUPnPControlPoint *cp,
+ GUPnPDeviceProxy *proxy,
+ gpointer user_data)
+{
+ dlr_upnp_t *upnp = user_data;
+ const char *udn;
+ dlr_device_t *device;
+ const gchar *ip_address;
+ unsigned int i;
+ dlr_device_context_t *context;
+ gboolean subscribed;
+ gboolean under_construction = FALSE;
+ prv_device_new_ct_t *priv_t;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)proxy);
+
+ if (!udn)
+ goto on_error;
+
+ ip_address = gupnp_context_get_host_ip(
+ gupnp_control_point_get_context(cp));
+
+ DLEYNA_LOG_DEBUG("UDN %s", udn);
+ DLEYNA_LOG_DEBUG("IP Address %s", ip_address);
+
+ device = g_hash_table_lookup(upnp->server_udn_map, udn);
+
+ if (!device) {
+ priv_t = g_hash_table_lookup(upnp->server_uc_map, udn);
+
+ if (priv_t) {
+ device = priv_t->device;
+ under_construction = TRUE;
+ }
+ }
+
+ if (!device) {
+ DLEYNA_LOG_WARNING("Device not found. Ignoring");
+ goto on_error;
+ }
+
+ for (i = 0; i < device->contexts->len; ++i) {
+ context = g_ptr_array_index(device->contexts, i);
+ if (!strcmp(context->ip_address, ip_address))
+ break;
+ }
+
+ if (i < device->contexts->len) {
+ subscribed = (context->subscribed_av || context->subscribed_cm);
+
+ (void) g_ptr_array_remove_index(device->contexts, i);
+
+ if (device->contexts->len == 0) {
+ if (!under_construction) {
+ DLEYNA_LOG_DEBUG(
+ "Last Context lost. Delete device");
+
+ upnp->lost_server(device->path);
+ g_hash_table_remove(upnp->server_udn_map, udn);
+ } else {
+ DLEYNA_LOG_WARNING(
+ "Device under construction. Cancelling");
+
+ dleyna_task_processor_cancel_queue(
+ priv_t->queue_id);
+ }
+ } else if (subscribed && !device->timeout_id) {
+ DLEYNA_LOG_DEBUG("Subscribe on new context");
+
+ device->timeout_id = g_timeout_add_seconds(1,
+ prv_subscribe_to_service_changes,
+ device);
+ }
+ }
+
+on_error:
+
+ return;
+}
+
+static void prv_on_context_available(GUPnPContextManager *context_manager,
+ GUPnPContext *context,
+ gpointer user_data)
+{
+ dlr_upnp_t *upnp = user_data;
+ GUPnPControlPoint *cp;
+
+ cp = gupnp_control_point_new(
+ context,
+ "urn:schemas-upnp-org:device:MediaRenderer:1");
+
+ g_signal_connect(cp, "device-proxy-available",
+ G_CALLBACK(prv_server_available_cb), upnp);
+
+ g_signal_connect(cp, "device-proxy-unavailable",
+ G_CALLBACK(prv_server_unavailable_cb), upnp);
+
+ gssdp_resource_browser_set_active(GSSDP_RESOURCE_BROWSER(cp), TRUE);
+ gupnp_context_manager_manage_control_point(upnp->context_manager, cp);
+ g_object_unref(cp);
+}
+
+dlr_upnp_t *dlr_upnp_new(dleyna_connector_id_t connection,
+ const dleyna_connector_dispatch_cb_t *dispatch_table,
+ dlr_upnp_callback_t found_server,
+ dlr_upnp_callback_t lost_server)
+{
+ dlr_upnp_t *upnp = g_new0(dlr_upnp_t, 1);
+
+ upnp->connection = connection;
+ upnp->interface_info = dispatch_table;
+ upnp->found_server = found_server;
+ upnp->lost_server = lost_server;
+
+ upnp->server_udn_map = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free,
+ dlr_device_delete);
+
+ upnp->server_uc_map = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ upnp->context_manager = gupnp_context_manager_create(0);
+
+ g_signal_connect(upnp->context_manager, "context-available",
+ G_CALLBACK(prv_on_context_available),
+ upnp);
+
+ dlr_host_service_new(&upnp->host_service);
+
+ return upnp;
+}
+
+void dlr_upnp_delete(dlr_upnp_t *upnp)
+{
+ if (upnp) {
+ dlr_host_service_delete(upnp->host_service);
+ g_object_unref(upnp->context_manager);
+ g_hash_table_unref(upnp->server_udn_map);
+ g_hash_table_unref(upnp->server_uc_map);
+
+ g_free(upnp);
+ }
+}
+
+GVariant *dlr_upnp_get_server_ids(dlr_upnp_t *upnp)
+{
+ GVariantBuilder vb;
+ GHashTableIter iter;
+ gpointer value;
+ dlr_device_t *device;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ g_variant_builder_init(&vb, G_VARIANT_TYPE("as"));
+ g_hash_table_iter_init(&iter, upnp->server_udn_map);
+
+ while (g_hash_table_iter_next(&iter, NULL, &value)) {
+ device = value;
+ g_variant_builder_add(&vb, "s", device->path);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+
+ return g_variant_ref_sink(g_variant_builder_end(&vb));
+}
+
+GHashTable *dlr_upnp_get_server_udn_map(dlr_upnp_t *upnp)
+{
+ return upnp->server_udn_map;
+}
+
+
+void dlr_upnp_set_prop(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_set_prop(device, task, cb);
+ }
+}
+
+void dlr_upnp_get_prop(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ DLEYNA_LOG_DEBUG("Path: %s", task->path);
+ DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name);
+ DLEYNA_LOG_DEBUG("Prop.%s", task->ut.get_prop.prop_name);
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ DLEYNA_LOG_WARNING("Cannot locate device");
+
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_get_prop(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_get_all_props(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ DLEYNA_LOG_DEBUG("Path: %s", task->path);
+ DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name);
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_get_all_props(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_play(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_play(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_pause(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_pause(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_play_pause(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_play_pause(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_stop(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_stop(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_next(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_next(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_previous(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_previous(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_open_uri(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_open_uri(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_seek(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_seek(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_set_position(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_set_position(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_goto_track(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_goto_track(device, task, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_host_uri(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_host_uri(device, task, upnp->host_service, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_remove_uri(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb)
+{
+ dlr_device_t *device;
+ dlr_async_task_t *cb_data = (dlr_async_task_t *)task;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ device = dlr_device_from_path(task->path, upnp->server_udn_map);
+
+ if (!device) {
+ cb_data->cb = cb;
+ cb_data->error = g_error_new(DLEYNA_SERVER_ERROR,
+ DLEYNA_ERROR_OBJECT_NOT_FOUND,
+ "Cannot locate a device for the specified object");
+
+ (void) g_idle_add(dlr_async_task_complete, cb_data);
+ } else {
+ dlr_device_remove_uri(device, task, upnp->host_service, cb);
+ }
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
+
+void dlr_upnp_lost_client(dlr_upnp_t *upnp, const gchar *client_name)
+{
+ dlr_host_service_lost_client(upnp->host_service, client_name);
+}
+
+void dlr_upnp_unsubscribe(dlr_upnp_t *upnp)
+{
+ GHashTableIter iter;
+ dlr_device_t *device;
+
+ DLEYNA_LOG_DEBUG("Enter");
+
+ g_hash_table_iter_init(&iter, upnp->server_udn_map);
+ while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&device))
+ dlr_device_unsubscribe(device);
+
+ DLEYNA_LOG_DEBUG("Exit");
+}
diff --git a/libdleyna/renderer/upnp.h b/libdleyna/renderer/upnp.h
new file mode 100644
index 0000000..9e19163
--- /dev/null
+++ b/libdleyna/renderer/upnp.h
@@ -0,0 +1,103 @@
+/*
+ * dLeyna
+ *
+ * Copyright (C) 2012-2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Mark Ryan <mark.d.ryan@intel.com>
+ *
+ */
+
+#ifndef DLR_UPNP_H__
+#define DLR_UPNP_H__
+
+#include <libdleyna/core/connector.h>
+
+#include "server.h"
+#include "task.h"
+
+enum dlr_interface_type_ {
+ DLR_INTERFACE_INFO_PROPERTIES,
+ DLR_INTERFACE_INFO_ROOT,
+ DLR_INTERFACE_INFO_PLAYER,
+ DLR_INTERFACE_INFO_PUSH_HOST,
+ DLR_INTERFACE_INFO_DEVICE,
+ DLR_INTERFACE_INFO_MAX
+};
+
+typedef void (*dlr_upnp_callback_t)(const gchar *path);
+typedef void (*dlr_upnp_task_complete_t)(dlr_task_t *task, GError *error);
+
+dlr_upnp_t *dlr_upnp_new(dleyna_connector_id_t connection,
+ const dleyna_connector_dispatch_cb_t *dispatch_table,
+ dlr_upnp_callback_t found_server,
+ dlr_upnp_callback_t lost_server);
+
+void dlr_upnp_delete(dlr_upnp_t *upnp);
+
+GVariant *dlr_upnp_get_server_ids(dlr_upnp_t *upnp);
+
+GHashTable *dlr_upnp_get_server_udn_map(dlr_upnp_t *upnp);
+
+void dlr_upnp_set_prop(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_get_prop(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_get_all_props(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_play(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_pause(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_play_pause(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_stop(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_next(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_previous(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_open_uri(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_seek(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_set_position(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_goto_track(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_host_uri(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_remove_uri(dlr_upnp_t *upnp, dlr_task_t *task,
+ dlr_upnp_task_complete_t cb);
+
+void dlr_upnp_lost_client(dlr_upnp_t *upnp, const gchar *client_name);
+
+void dlr_upnp_unsubscribe(dlr_upnp_t *upnp);
+
+#endif /* DLR_UPNP_H__ */