diff options
author | Manuel Bachmann <manuel.bachmann@iot.bzh> | 2016-01-11 10:20:21 +0100 |
---|---|---|
committer | Manuel Bachmann <manuel.bachmann@iot.bzh> | 2016-01-11 10:20:21 +0100 |
commit | a5a5383bba11e69379e27919f101c5e7718f8bf2 (patch) | |
tree | 0389edd5ac3053c285c3f8444297bca62c99f649 | |
parent | 937dce0512ae998a2416e44601ccb519a7166cdb (diff) |
Add Media Plugin
Add a Media Plugin, based off Rygel (UPnP), with only an
initialization and a "list" function for now.
Signed-off-by: Manuel Bachmann <manuel.bachmann@iot.bzh>
-rw-r--r-- | CMakeLists.txt | 9 | ||||
-rw-r--r-- | plugins/CMakeLists.txt | 1 | ||||
-rw-r--r-- | plugins/media/CMakeLists.txt | 10 | ||||
-rw-r--r-- | plugins/media/media-api.c | 90 | ||||
-rw-r--r-- | plugins/media/media-api.h | 31 | ||||
-rw-r--r-- | plugins/media/media-rygel.c | 175 | ||||
-rw-r--r-- | plugins/media/media-rygel.h | 48 |
7 files changed, 362 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index f37c44c3..920899a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ PKG_CHECK_MODULES(dbus REQUIRED dbus-1) PKG_CHECK_MODULES(alsa alsa) PKG_CHECK_MODULES(pulseaudio libpulse libpulse-simple) PKG_CHECK_MODULES(librtlsdr librtlsdr>=0.5.0) +PKG_CHECK_MODULES(gupnp gupnp-1.0 gssdp-1.0 gobject-2.0) IF(alsa_FOUND) MESSAGE(STATUS "ALSA found ; will compile Audio plugin... (PLUGIN)") @@ -55,11 +56,15 @@ IF(librtlsdr_FOUND) MESSAGE(STATUS "librtlsdr found ; will compile Radio plugin... (PLUGIN)") ENDIF(librtlsdr_FOUND) +IF(gupnp_FOUND) + MESSAGE(STATUS "gupnp found ; will compile Media plugin... (PLUGIN)") +ENDIF(gupnp_FOUND) + INCLUDE(FindThreads) FIND_PACKAGE(Threads) -SET(include_dirs ${INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/include ${json-c_INCLUDE_DIRS} ${libmicrohttpd_INCLUDE_DIRS} ${uuid_INCLUDE_DIRS} ${dbus_INCLUDE_DIRS} ${alsa_INCLUDE_DIRS} ${pulseaudio_INCLUDE_DIRS} ${librtlsdr_INCLUDE_DIRS}) -SET(link_libraries ${json-c_LIBRARIES} ${libmicrohttpd_LIBRARIES} ${uuid_LIBRARIES} ${dbus_LIBRARIES} ${alsa_LIBRARIES} ${pulseaudio_LIBRARIES} ${librtlsdr_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${libefence_LIBRARIES} -lmagic -lm -ldl) +SET(include_dirs ${INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/include ${json-c_INCLUDE_DIRS} ${libmicrohttpd_INCLUDE_DIRS} ${uuid_INCLUDE_DIRS} ${dbus_INCLUDE_DIRS} ${alsa_INCLUDE_DIRS} ${pulseaudio_INCLUDE_DIRS} ${librtlsdr_INCLUDE_DIRS} ${gupnp_INCLUDE_DIRS}) +SET(link_libraries ${json-c_LIBRARIES} ${libmicrohttpd_LIBRARIES} ${uuid_LIBRARIES} ${dbus_LIBRARIES} ${alsa_LIBRARIES} ${pulseaudio_LIBRARIES} ${librtlsdr_LIBRARIES} ${gupnp_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${libefence_LIBRARIES} -lmagic -lm -ldl) SET(plugin_install_dir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/afb) ADD_DEFINITIONS(-DPLUGIN_INSTALL_DIR="${plugin_install_dir}") diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index d8566161..ba3432b4 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -3,3 +3,4 @@ ADD_SUBDIRECTORY(session) ADD_SUBDIRECTORY(samples) ADD_SUBDIRECTORY(audio) ADD_SUBDIRECTORY(radio) +ADD_SUBDIRECTORY(media) diff --git a/plugins/media/CMakeLists.txt b/plugins/media/CMakeLists.txt new file mode 100644 index 00000000..a6fc34ed --- /dev/null +++ b/plugins/media/CMakeLists.txt @@ -0,0 +1,10 @@ +IF(gupnp_FOUND) + + ADD_LIBRARY(media-api MODULE media-api.c media-rygel.c) + SET_TARGET_PROPERTIES(media-api PROPERTIES PREFIX "") + TARGET_LINK_LIBRARIES(media-api ${link_libraries}) + INCLUDE_DIRECTORIES(${include_dirs}) + INSTALL(TARGETS media-api + LIBRARY DESTINATION ${plugin_install_dir}) + +ENDIF(gupnp_FOUND) diff --git a/plugins/media/media-api.c b/plugins/media/media-api.c new file mode 100644 index 00000000..60228a07 --- /dev/null +++ b/plugins/media/media-api.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author "Manuel Bachmann" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "media-api.h" + +/* ------ LOCAL HELPER FUNCTIONS --------- */ + +/* private client context creation ; default values */ +STATIC mediaCtxHandleT* initMediaCtx () { + + mediaCtxHandleT *ctx; + + ctx = malloc (sizeof(mediaCtxHandleT)); + ctx->media_server = NULL; + + return ctx; +} + +/* called when client session dies [e.g. client quits for more than 15mns] */ +STATIC void freeMedia (void *context, void *handle) { + + free (context); +} + +/* ------ PUBLIC PLUGIN FUNCTIONS --------- */ + +STATIC json_object* init (AFB_request *request) { /* AFB_SESSION_CHECK */ + + json_object *jresp; + + /* create a private client context */ + if (!request->context) + request->context = initMediaCtx(); + + /* initialize server connection */ + _rygel_init (request->context); + + jresp = json_object_new_object(); + json_object_object_add(jresp, "info", json_object_new_string ("Media initialized")); + return jresp; +} + +STATIC json_object* list (AFB_request *request) { /* AFB_SESSION_CHECK */ + + char *result; + + result = _rygel_list (request->context); + + return jsonNewMessage(AFB_SUCCESS, result); +} + +STATIC json_object* ping (AFB_request *request) { /* AFB_SESSION_NONE */ + return jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon - Media API"); +} + + +STATIC AFB_restapi pluginApis[]= { + {"init" , AFB_SESSION_CHECK, (AFB_apiCB)init , "Media API - init"}, + {"list" , AFB_SESSION_CHECK, (AFB_apiCB)list , "Media API - list"}, + {"ping" , AFB_SESSION_NONE, (AFB_apiCB)ping , "Media API - ping"}, + {NULL} +}; + +PUBLIC AFB_plugin* pluginRegister () { + AFB_plugin *plugin = malloc (sizeof(AFB_plugin)); + plugin->type = AFB_PLUGIN_JSON; + plugin->info = "Application Framework Binder - Media plugin"; + plugin->prefix = "media"; + plugin->apis = pluginApis; + + /*plugin->handle = initRadioPlugin();*/ + plugin->freeCtxCB = (AFB_freeCtxCB)freeMedia; + + return (plugin); +}; diff --git a/plugins/media/media-api.h b/plugins/media/media-api.h new file mode 100644 index 00000000..e40c0dce --- /dev/null +++ b/plugins/media/media-api.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author "Manuel Bachmann" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MEDIA_API_H +#define MEDIA_API_H + +#include "media-rygel.h" + +/* -------------- PLUGIN DEFINITIONS ----------------- */ + +/* private client context [will be destroyed when client leaves] */ +typedef struct { + void *media_server; /* handle to implementation (Rygel...) */ +} mediaCtxHandleT; + +#endif /* MEDIA_API_H */ diff --git a/plugins/media/media-rygel.c b/plugins/media/media-rygel.c new file mode 100644 index 00000000..51d718ba --- /dev/null +++ b/plugins/media/media-rygel.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author "Manuel Bachmann" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "media-api.h" + +/* -------------- MEDIA RYGEL IMPLEMENTATION ---------------- */ + +/* --- PUBLIC FUNCTIONS --- */ + +PUBLIC unsigned char _rygel_init (mediaCtxHandleT *ctx) { + + GMainContext *loop; + GUPnPContext *context; + GUPnPControlPoint *control_point; + struct timeval tv_start, tv_now; + + context = gupnp_context_new (NULL, NULL, 0, NULL); + + control_point = gupnp_control_point_new (context, URN_MEDIA_SERVER); + + g_signal_connect (control_point, "device-proxy-available", + G_CALLBACK (_rygel_device_cb), ctx); + + /* start searching for servers */ + gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE); + + loop = g_main_context_default (); + + /* 5 seconds should be sufficient to find Rygel */ + gettimeofday (&tv_start, NULL); + gettimeofday (&tv_now, NULL); + while (tv_now.tv_sec - tv_start.tv_sec <= 5) { + + g_main_context_iteration (loop, FALSE); + + if (ctx->media_server) + break; + } + /* fail if we found no server */ + if (!ctx->media_server) + return -1; + + dev_ctx[client_count]->loop = loop; + dev_ctx[client_count]->context = context; + + client_count++; + + return 0; +} + +PUBLIC void _rygel_free (mediaCtxHandleT *ctx) { + + dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server; + + client_count--; + + g_main_context_unref (dev_ctx_c->loop); + dev_ctx_c->loop = NULL; + dev_ctx_c->context = NULL; + dev_ctx_c->device_info = NULL; + dev_ctx_c->content_dir = NULL; +} + +PUBLIC char* _rygel_list (mediaCtxHandleT *ctx) { + + dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server; + GUPnPServiceProxy *content_dir_proxy; + + dev_ctx_c->content_res = NULL; + content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir); + + gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c, + "ObjectID", G_TYPE_STRING, "Filesystem", + "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren", + "Filter", G_TYPE_STRING, "@childCount", + "StartingIndex", G_TYPE_UINT, 0, + "RequestedCount", G_TYPE_UINT, 64, + "SortCriteria", G_TYPE_STRING, "", + NULL); + + while (!dev_ctx_c->content_res) + g_main_context_iteration (dev_ctx_c->loop, FALSE); + + return dev_ctx_c->content_res; +} + + /* ---- LOCAL CALLBACK FUNCTIONS ---- */ + +STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy, + gpointer data) { + + mediaCtxHandleT *ctx = (mediaCtxHandleT*)data; + GUPnPDeviceInfo *device_info; + GUPnPServiceInfo *content_dir; + const char *device_name; + + device_info = GUPNP_DEVICE_INFO (proxy); + device_name = gupnp_device_info_get_model_name (device_info); + content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR); + + if (strcmp (device_name, "Rygel") != 0) + return; + if (!content_dir) + return; + + /* allocate the global array if it has not been not done */ + if (!dev_ctx) + dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T)); + else + dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T)); + + /* create an element for the client in the global array */ + dev_ctx[client_count] = malloc (sizeof(dev_ctx_T)); + dev_ctx[client_count]->device_info = device_info; + dev_ctx[client_count]->content_dir = content_dir; + + /* make the client context aware of it */ + ctx->media_server = (void*)dev_ctx[client_count]; +} + +STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action, + gpointer data) { + + dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data; + GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir); + GError *error; + char *result; + guint32 number_returned; + guint32 total_matches; + char *found; + char subid[33]; + + gupnp_service_proxy_end_action (content_dir, action, &error, + "Result", G_TYPE_STRING, &result, + "NumberReturned", G_TYPE_UINT, &number_returned, + "TotalMatches", G_TYPE_UINT, &total_matches, + NULL); + + if (number_returned == 0) + return; + + if (number_returned == 1) { + found = strstr (result, "id=\""); + found += 4; + strncpy (subid, found, 32); subid[32] = '\0'; + + gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, NULL, + "ObjectID", G_TYPE_STRING, subid, + "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren", + "Filter", G_TYPE_STRING, "@childCount", + "StartingIndex", G_TYPE_UINT, 0, + "RequestedCount", G_TYPE_UINT, 64, + "SortCriteria", G_TYPE_STRING, "", + NULL); + return; + } + + if (number_returned > 1) + dev_ctx_c->content_res = strdup (result); +} diff --git a/plugins/media/media-rygel.h b/plugins/media/media-rygel.h new file mode 100644 index 00000000..c9705a4a --- /dev/null +++ b/plugins/media/media-rygel.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 "IoT.bzh" + * Author "Manuel Bachmann" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MEDIA_RYGEL_H +#define MEDIA_RYGEL_H + +/* --------------- MEDIA RYGEL DEFINITIONS ------------------ */ + +#include <sys/time.h> +#include <libgupnp/gupnp-control-point.h> + +#include "local-def.h" + +#define URN_MEDIA_SERVER "urn:schemas-upnp-org:device:MediaServer:1" +#define URN_CONTENT_DIR "urn:schemas-upnp-org:service:ContentDirectory" + +typedef struct dev_ctx dev_ctx_T; + +struct dev_ctx { + GMainContext *loop; + GUPnPContext *context; + GUPnPDeviceInfo *device_info; + GUPnPServiceInfo *content_dir; + char *content_res; +}; + +STATIC void _rygel_device_cb (GUPnPControlPoint *, GUPnPDeviceProxy *, gpointer); +STATIC void _rygel_content_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer); + +static struct dev_ctx **dev_ctx = NULL; +static unsigned int client_count = 0; + +#endif /* MEDIA_RYGEL_H */ |