2 * Copyright (C) 2016 "IoT.bzh"
3 * Author "Manuel Bachmann"
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "media-api.h"
21 /* -------------- MEDIA RYGEL IMPLEMENTATION ---------------- */
23 /* --- PUBLIC FUNCTIONS --- */
25 PUBLIC unsigned char _rygel_init (mediaCtxHandleT *ctx) {
28 GUPnPContext *context;
29 GUPnPControlPoint *control_point;
31 struct timeval tv_start, tv_now;
33 context = gupnp_context_new (NULL, NULL, 0, NULL);
35 control_point = gupnp_control_point_new (context, URN_MEDIA_SERVER);
37 handler_cb = g_signal_connect (control_point, "device-proxy-available",
38 G_CALLBACK (_rygel_device_cb), ctx);
40 /* start searching for servers */
41 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
43 loop = g_main_context_default ();
45 /* 5 seconds should be sufficient to find Rygel */
46 gettimeofday (&tv_start, NULL);
47 gettimeofday (&tv_now, NULL);
48 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
50 g_main_context_iteration (loop, FALSE);
52 if (ctx->media_server)
54 gettimeofday (&tv_now, NULL);
56 /* fail if we found no server */
57 if (!ctx->media_server)
60 /* we have found the server ; stop looking for it... */
61 g_signal_handler_disconnect (control_point, handler_cb);
63 dev_ctx[client_count]->loop = loop;
64 dev_ctx[client_count]->context = context;
65 dev_ctx[client_count]->av_transport = NULL;
66 dev_ctx[client_count]->state = STOP;
67 dev_ctx[client_count]->target_state = STOP;
68 dev_ctx[client_count]->transfer_started = 0;
75 PUBLIC void _rygel_free (mediaCtxHandleT *ctx) {
77 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
81 g_main_context_unref (dev_ctx_c->loop);
82 dev_ctx_c->loop = NULL;
83 dev_ctx_c->context = NULL;
84 dev_ctx_c->device_info = NULL;
85 dev_ctx_c->av_transport = NULL;
86 dev_ctx_c->content_dir = NULL;
87 dev_ctx_c->content_res = NULL;
90 PUBLIC char* _rygel_list (mediaCtxHandleT *ctx) {
92 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
93 char *raw, *start, *end, *title, *result = NULL;
99 raw = _rygel_list_raw (dev_ctx_c, NULL);
103 start = strstr (raw, "<dc:title>");
109 start = strstr (start, "<dc:title>");
111 end = strstr (start, "</dc:title>");
113 length = end - start;
115 title = (char*) malloc (length+1);
116 strncpy (title, start, length);
117 title[length] = '\0';
119 asprintf (&result, "%s%02d:%s::", result, i, title);
127 PUBLIC unsigned char _rygel_choose (mediaCtxHandleT *ctx, unsigned int index) {
129 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
135 if (!_rygel_list_raw (dev_ctx_c, &count) ||
139 if (ctx->index != index)
140 dev_ctx_c->state = STOP;
145 PUBLIC unsigned char _rygel_upload (mediaCtxHandleT *ctx, char *path) {
147 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
148 char *raw, *upload_id;
153 raw = _rygel_list_raw (dev_ctx_c, NULL);
157 /* for now, we always use the same upload container id */
158 upload_id = _rygel_find_upload_id (dev_ctx_c, raw);
160 return _rygel_start_uploading (dev_ctx_c, path, upload_id);
163 PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state) {
165 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
166 unsigned int index = ctx->index;
168 char *raw, *id, *metadata, *uri;
170 if (!dev_ctx_c || dev_ctx_c->state == state)
173 raw = _rygel_list_raw (dev_ctx_c, &count);
174 if (!raw || index >= count)
177 id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
178 metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
179 uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
181 return _rygel_start_doing (dev_ctx_c, uri, metadata, state);
184 /* --- LOCAL HELPER FUNCTIONS --- */
186 STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
188 GUPnPServiceProxy *content_dir_proxy;
189 struct timeval tv_start, tv_now;
191 dev_ctx_c->content_res = NULL;
192 dev_ctx_c->content_num = 0;
193 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
195 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
196 "ObjectID", G_TYPE_STRING, "Filesystem",
197 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
198 "Filter", G_TYPE_STRING, "@childCount",
199 "StartingIndex", G_TYPE_UINT, 0,
200 "RequestedCount", G_TYPE_UINT, 64,
201 "SortCriteria", G_TYPE_STRING, "",
204 gettimeofday (&tv_start, NULL);
205 gettimeofday (&tv_now, NULL);
206 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
208 g_main_context_iteration (dev_ctx_c->loop, FALSE);
210 if (dev_ctx_c->content_res)
212 gettimeofday (&tv_now, NULL);
215 if (count) *count = dev_ctx_c->content_num;
216 return dev_ctx_c->content_res;
219 STATIC char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) {
224 found = strstr (raw, "parentID=\"");
227 /* IDs are 32-bit strings */
228 strncpy (id, found, 32);
234 STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
240 for (i = 0; i <= index; i++) {
241 found = strstr (found, "item id=");
245 /* IDs are 32-bit strings */
246 strncpy (id, found, 32);
254 STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
256 GUPnPServiceProxy *content_dir_proxy;
257 struct timeval tv_start, tv_now;
259 dev_ctx_c->content_res = NULL;
261 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
263 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
264 "ObjectID", G_TYPE_STRING, id,
265 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
266 "Filter", G_TYPE_STRING, "*",
267 "StartingIndex", G_TYPE_UINT, 0,
268 "RequestedCount", G_TYPE_UINT, 0,
269 "SortCriteria", G_TYPE_STRING, "",
272 gettimeofday (&tv_start, NULL);
273 gettimeofday (&tv_now, NULL);
274 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
276 g_main_context_iteration (dev_ctx_c->loop, FALSE);
278 if (dev_ctx_c->content_res)
280 gettimeofday (&tv_now, NULL);
283 return dev_ctx_c->content_res;
286 STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
288 char *start, *end, *uri = NULL;
291 /* position ourselves after the first "<res " tag */
292 start = strstr (metadata, "<res ");
295 start = strstr (start, "http://");
297 end = strstr (start, "</res>");
298 length = end - start;
301 uri = (char *)malloc (length + 1);
302 strncpy (uri, start, length);
304 /* if the URI contains "primary_http", it is the main one ; stop here...*/
305 if (strstr (uri, "primary_http"))
308 free (uri); start = end;
314 STATIC unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) {
316 GUPnPServiceProxy *content_dir_proxy;
317 GUPnPDIDLLiteWriter *didl_writer;
318 GUPnPDIDLLiteObject *didl_object;
319 char *didl, *content_type, *mime_type, *upnp_class;
320 struct timeval tv_start, tv_now;
322 didl_writer = gupnp_didl_lite_writer_new (NULL);
323 didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer));
325 /* create the metadata for the file */
326 gupnp_didl_lite_object_set_parent_id (didl_object, upload_id);
327 gupnp_didl_lite_object_set_id (didl_object, "");
328 gupnp_didl_lite_object_set_restricted (didl_object, FALSE);
329 gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path));
330 /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */
331 content_type = g_content_type_guess (path, NULL, 0, NULL);
332 mime_type = g_content_type_get_mime_type (content_type);
333 if (strstr (mime_type, "audio/"))
334 upnp_class = strdup ("object.item.audioItem.musicTrack");
335 else if (strstr (mime_type, "video/"))
336 upnp_class = strdup ("object.item.videoItem");
337 else if (strstr (mime_type, "image/"))
338 upnp_class = strdup ("object.item.imageItem");
340 upnp_class = strdup ("object.item");
341 gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class);
342 didl = gupnp_didl_lite_writer_get_string (didl_writer);
344 dev_ctx_c->transfer_path = path;
345 dev_ctx_c->transfer_started = 0;
346 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
348 gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c,
349 "ContainerID", G_TYPE_STRING, upload_id,
350 "Elements", G_TYPE_STRING, didl,
353 gettimeofday (&tv_start, NULL);
354 gettimeofday (&tv_now, NULL);
355 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
357 g_main_context_iteration (dev_ctx_c->loop, FALSE);
359 if (dev_ctx_c->transfer_started)
361 gettimeofday (&tv_now, NULL);
363 if (!dev_ctx_c->transfer_started)
369 STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state) {
371 GUPnPServiceProxy *av_transport_proxy;
372 struct timeval tv_start, tv_now;
374 if (!dev_ctx_c->av_transport) {
375 if (!_rygel_find_av_transport (dev_ctx_c))
378 dev_ctx_c->target_state = state;
379 av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
381 gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
382 "InstanceID", G_TYPE_UINT, 0,
383 "CurrentURI", G_TYPE_STRING, uri,
384 "CurrentURIMetaData", G_TYPE_STRING, metadata,
387 gettimeofday (&tv_start, NULL);
388 gettimeofday (&tv_now, NULL);
389 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
391 g_main_context_iteration (dev_ctx_c->loop, FALSE);
393 if (dev_ctx_c->state == state)
395 gettimeofday (&tv_now, NULL);
397 if (dev_ctx_c->state != state)
403 STATIC unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
405 GUPnPControlPoint *control_point;
407 struct timeval tv_start, tv_now;
409 control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
411 handler_cb = g_signal_connect (control_point, "device-proxy-available",
412 G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
414 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
416 gettimeofday (&tv_start, NULL);
417 gettimeofday (&tv_now, NULL);
418 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
420 g_main_context_iteration (dev_ctx_c->loop, FALSE);
422 if (dev_ctx_c->av_transport)
424 gettimeofday (&tv_now, NULL);
426 g_signal_handler_disconnect (control_point, handler_cb);
428 if (!dev_ctx_c->av_transport)
435 /* ---- LOCAL CALLBACK FUNCTIONS ---- */
437 STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
440 mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
441 GUPnPDeviceInfo *device_info;
442 GUPnPServiceInfo *content_dir;
443 const char *device_name;
445 device_info = GUPNP_DEVICE_INFO (proxy);
446 device_name = gupnp_device_info_get_model_name (device_info);
447 content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
449 if (strcmp (device_name, "Rygel") != 0)
454 /* allocate the global array if it has not been not done */
456 dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
458 dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
460 /* create an element for the client in the global array */
461 dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
462 dev_ctx[client_count]->device_info = device_info;
463 dev_ctx[client_count]->content_dir = content_dir;
465 /* make the client context aware of it */
466 ctx->media_server = (void*)dev_ctx[client_count];
469 STATIC void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
472 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
473 GUPnPDeviceInfo *device_info;
474 GUPnPServiceInfo *av_transport;
476 device_info = GUPNP_DEVICE_INFO (proxy);
477 av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
479 dev_ctx_c->av_transport = av_transport;
482 STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
485 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
486 GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
489 guint32 number_returned;
490 guint32 total_matches;
494 gupnp_service_proxy_end_action (content_dir, action, &error,
495 "Result", G_TYPE_STRING, &result,
496 "NumberReturned", G_TYPE_UINT, &number_returned,
497 "TotalMatches", G_TYPE_UINT, &total_matches,
500 if (number_returned == 0)
503 if (number_returned == 1) {
504 found = strstr (result, "id=\"");
506 strncpy (subid, found, 32); subid[32] = '\0';
508 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
509 "ObjectID", G_TYPE_STRING, subid,
510 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
511 "Filter", G_TYPE_STRING, "@childCount",
512 "StartingIndex", G_TYPE_UINT, 0,
513 "RequestedCount", G_TYPE_UINT, 64,
514 "SortCriteria", G_TYPE_STRING, "",
519 if (number_returned > 1) {
520 dev_ctx_c->content_res = result;
521 dev_ctx_c->content_num = number_returned;
525 STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
528 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
532 gupnp_service_proxy_end_action (content_dir, action, &error,
533 "Result", G_TYPE_STRING, &result,
536 dev_ctx_c->content_res = result;
539 STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
543 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
544 GUPnPServiceProxy *av_transport_proxy;
546 struct timeval tv_start, tv_now;
548 av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
550 gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
552 switch (dev_ctx_c->target_state) {
554 gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
555 "InstanceID", G_TYPE_UINT, 0,
556 "Speed", G_TYPE_STRING, "1",
560 gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
561 "InstanceID", G_TYPE_UINT, 0,
565 gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
566 "InstanceID", G_TYPE_UINT, 0,
573 gettimeofday (&tv_start, NULL);
574 gettimeofday (&tv_now, NULL);
575 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
577 g_main_context_iteration (dev_ctx_c->loop, FALSE);
579 if (dev_ctx_c->state == dev_ctx_c->target_state)
581 gettimeofday (&tv_now, NULL);
585 STATIC void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
588 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
589 GUPnPServiceProxy *content_dir_proxy;
591 char *result, *start, *end, *dst_uri, *src_uri;
593 struct timeval tv_start, tv_now;
595 content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
597 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
598 "Result", G_TYPE_STRING, &result,
602 start = strstr (result, "<res importUri=\"");
607 end = strstr (start, "\"");
608 length = end - start;
610 dst_uri = (char*) malloc(length+1);
611 strncpy (dst_uri, start, length);
612 dst_uri[length] = '\0';
614 asprintf (&src_uri, "http://%s:%u%s", gupnp_context_get_host_ip (dev_ctx_c->context),
615 gupnp_context_get_port (dev_ctx_c->context),
616 dev_ctx_c->transfer_path);
619 gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path,
620 dev_ctx_c->transfer_path);
622 gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c,
623 "SourceURI", G_TYPE_STRING, src_uri,
624 "DestinationURI", G_TYPE_STRING, dst_uri,
627 gettimeofday (&tv_start, NULL);
628 gettimeofday (&tv_now, NULL);
629 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
631 g_main_context_iteration (dev_ctx_c->loop, FALSE);
633 if (dev_ctx_c->transfer_started)
635 gettimeofday (&tv_now, NULL);
639 STATIC void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
642 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
646 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
647 "TransferID", G_TYPE_UINT, &transfer_id,
651 dev_ctx_c->transfer_started = 1;
654 STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
657 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
660 if (!gupnp_service_proxy_end_action (av_transport, action, &error,
664 dev_ctx_c->state = dev_ctx_c->target_state;