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]->action_args = NULL;
69 dev_ctx[client_count]->transfer_started = 0;
76 PUBLIC void _rygel_free (mediaCtxHandleT *ctx) {
78 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
82 g_main_context_unref (dev_ctx_c->loop);
83 dev_ctx_c->loop = NULL;
84 dev_ctx_c->context = NULL;
85 dev_ctx_c->device_info = NULL;
86 dev_ctx_c->av_transport = NULL;
87 dev_ctx_c->content_dir = NULL;
88 dev_ctx_c->content_res = NULL;
91 PUBLIC json_object* _rygel_list (mediaCtxHandleT *ctx) {
93 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
94 json_object *json_o, *json_a;
95 char *raw, *start, *end, *id, *title;
101 raw = _rygel_list_raw (dev_ctx_c, NULL);
105 start = strstr (raw, "<dc:title>");
109 json_o = json_object_new_object ();
110 json_a = json_object_new_array ();
112 json_object *json_i, *json_id, *json_title;
114 start = strstr (start, "<dc:title>");
116 end = strstr (start, "</dc:title>");
118 length = end - start;
120 asprintf (&id, "%02d", i);
122 title = (char*) malloc (length+1);
123 strncpy (title, start, length);
124 title[length] = '\0';
126 json_i = json_object_new_object ();
127 json_id = json_object_new_string (id);
128 json_title = json_object_new_string (title);
129 json_object_object_add (json_i, "id", json_id);
130 json_object_object_add (json_i, "title", json_title);
131 json_object_array_add (json_a, json_i);
133 free (id); free (title);
137 json_object_object_add (json_o, "list", json_a);
142 PUBLIC unsigned char _rygel_select (mediaCtxHandleT *ctx, unsigned int index) {
144 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
150 if (!_rygel_list_raw (dev_ctx_c, &count) ||
154 if (ctx->index != index)
155 dev_ctx_c->state = STOP;
160 PUBLIC unsigned char _rygel_upload (mediaCtxHandleT *ctx, char *path) {
162 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
163 char *raw, *upload_id;
168 raw = _rygel_list_raw (dev_ctx_c, NULL);
172 /* for now, we always use the same upload container id */
173 upload_id = _rygel_find_upload_id (dev_ctx_c, raw);
175 return _rygel_start_uploading (dev_ctx_c, path, upload_id);
178 PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state, char *args) {
180 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
181 unsigned int index = ctx->index;
183 char *raw, *id, *metadata, *uri;
185 if (!dev_ctx_c || dev_ctx_c->state == state)
188 raw = _rygel_list_raw (dev_ctx_c, &count);
189 if (!raw || index >= count)
192 id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
193 metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
194 uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
196 return _rygel_start_doing (dev_ctx_c, uri, metadata, state, args);
199 /* --- LOCAL HELPER FUNCTIONS --- */
201 STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
203 GUPnPServiceProxy *content_dir_proxy;
204 struct timeval tv_start, tv_now;
206 dev_ctx_c->content_res = NULL;
207 dev_ctx_c->content_num = 0;
208 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
210 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
211 "ObjectID", G_TYPE_STRING, "Filesystem",
212 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
213 "Filter", G_TYPE_STRING, "@childCount",
214 "StartingIndex", G_TYPE_UINT, 0,
215 "RequestedCount", G_TYPE_UINT, 64,
216 "SortCriteria", G_TYPE_STRING, "",
219 gettimeofday (&tv_start, NULL);
220 gettimeofday (&tv_now, NULL);
221 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
223 g_main_context_iteration (dev_ctx_c->loop, FALSE);
225 if (dev_ctx_c->content_res)
227 gettimeofday (&tv_now, NULL);
230 if (count) *count = dev_ctx_c->content_num;
231 return dev_ctx_c->content_res;
234 STATIC char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) {
239 found = strstr (raw, "parentID=\"");
242 /* IDs are 32-bit strings */
243 strncpy (id, found, 32);
249 STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
255 for (i = 0; i <= index; i++) {
256 found = strstr (found, "item id=");
260 /* IDs are 32-bit strings */
261 strncpy (id, found, 32);
269 STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
271 GUPnPServiceProxy *content_dir_proxy;
272 struct timeval tv_start, tv_now;
274 dev_ctx_c->content_res = NULL;
276 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
278 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
279 "ObjectID", G_TYPE_STRING, id,
280 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
281 "Filter", G_TYPE_STRING, "*",
282 "StartingIndex", G_TYPE_UINT, 0,
283 "RequestedCount", G_TYPE_UINT, 0,
284 "SortCriteria", G_TYPE_STRING, "",
287 gettimeofday (&tv_start, NULL);
288 gettimeofday (&tv_now, NULL);
289 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
291 g_main_context_iteration (dev_ctx_c->loop, FALSE);
293 if (dev_ctx_c->content_res)
295 gettimeofday (&tv_now, NULL);
298 return dev_ctx_c->content_res;
301 STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
303 char *start, *end, *uri = NULL;
306 /* position ourselves after the first "<res " tag */
307 start = strstr (metadata, "<res ");
310 start = strstr (start, "http://");
312 end = strstr (start, "</res>");
313 length = end - start;
316 uri = (char *)malloc (length + 1);
317 strncpy (uri, start, length);
319 /* if the URI contains "primary_http", it is the main one ; stop here...*/
320 if (strstr (uri, "primary_http"))
323 free (uri); start = end;
329 STATIC char * _rygel_time_for_string (char *string) {
332 unsigned int hours, minutes, seconds;
335 total_seconds = atoi (string);
336 hours = total_seconds / 3600;
337 minutes = (total_seconds / 60) - (hours * 60);
338 seconds = total_seconds - (hours * 3600) - (minutes * 60);
340 asprintf (&time, "%u:%02u:%02u", hours, minutes, seconds);
345 STATIC unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) {
347 GUPnPServiceProxy *content_dir_proxy;
348 GUPnPDIDLLiteWriter *didl_writer;
349 GUPnPDIDLLiteObject *didl_object;
350 char *didl, *content_type, *mime_type, *upnp_class;
351 struct timeval tv_start, tv_now;
353 didl_writer = gupnp_didl_lite_writer_new (NULL);
354 didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer));
356 /* create the metadata for the file */
357 gupnp_didl_lite_object_set_parent_id (didl_object, upload_id);
358 gupnp_didl_lite_object_set_id (didl_object, "");
359 gupnp_didl_lite_object_set_restricted (didl_object, FALSE);
360 gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path));
361 /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */
362 content_type = g_content_type_guess (path, NULL, 0, NULL);
363 mime_type = g_content_type_get_mime_type (content_type);
364 if (strstr (mime_type, "audio/"))
365 upnp_class = strdup ("object.item.audioItem.musicTrack");
366 else if (strstr (mime_type, "video/"))
367 upnp_class = strdup ("object.item.videoItem");
368 else if (strstr (mime_type, "image/"))
369 upnp_class = strdup ("object.item.imageItem");
371 upnp_class = strdup ("object.item");
372 gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class);
373 didl = gupnp_didl_lite_writer_get_string (didl_writer);
375 dev_ctx_c->transfer_path = path;
376 dev_ctx_c->transfer_started = 0;
377 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
379 gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c,
380 "ContainerID", G_TYPE_STRING, upload_id,
381 "Elements", G_TYPE_STRING, didl,
384 gettimeofday (&tv_start, NULL);
385 gettimeofday (&tv_now, NULL);
386 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
388 g_main_context_iteration (dev_ctx_c->loop, FALSE);
390 if (dev_ctx_c->transfer_started)
392 gettimeofday (&tv_now, NULL);
394 if (!dev_ctx_c->transfer_started)
400 STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state, char *args) {
402 GUPnPServiceProxy *av_transport_proxy;
403 struct timeval tv_start, tv_now;
405 if (!dev_ctx_c->av_transport) {
406 if (!_rygel_find_av_transport (dev_ctx_c))
409 dev_ctx_c->target_state = state;
410 dev_ctx_c->action_args = args;
411 av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
413 gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
414 "InstanceID", G_TYPE_UINT, 0,
415 "CurrentURI", G_TYPE_STRING, uri,
416 "CurrentURIMetaData", G_TYPE_STRING, metadata,
419 gettimeofday (&tv_start, NULL);
420 gettimeofday (&tv_now, NULL);
421 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
423 g_main_context_iteration (dev_ctx_c->loop, FALSE);
425 if (dev_ctx_c->state == state)
427 gettimeofday (&tv_now, NULL);
429 if (dev_ctx_c->state != state)
435 STATIC unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
437 GUPnPControlPoint *control_point;
439 struct timeval tv_start, tv_now;
441 control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
443 handler_cb = g_signal_connect (control_point, "device-proxy-available",
444 G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
446 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
448 gettimeofday (&tv_start, NULL);
449 gettimeofday (&tv_now, NULL);
450 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
452 g_main_context_iteration (dev_ctx_c->loop, FALSE);
454 if (dev_ctx_c->av_transport)
456 gettimeofday (&tv_now, NULL);
458 g_signal_handler_disconnect (control_point, handler_cb);
460 if (!dev_ctx_c->av_transport)
467 /* ---- LOCAL CALLBACK FUNCTIONS ---- */
469 STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
472 mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
473 GUPnPDeviceInfo *device_info;
474 GUPnPServiceInfo *content_dir;
475 const char *device_name;
477 device_info = GUPNP_DEVICE_INFO (proxy);
478 device_name = gupnp_device_info_get_model_name (device_info);
479 content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
481 if (strcmp (device_name, "Rygel") != 0)
486 /* allocate the global array if it has not been not done */
488 dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
490 dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
492 /* create an element for the client in the global array */
493 dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
494 dev_ctx[client_count]->device_info = device_info;
495 dev_ctx[client_count]->content_dir = content_dir;
497 /* make the client context aware of it */
498 ctx->media_server = (void*)dev_ctx[client_count];
501 STATIC void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
504 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
505 GUPnPDeviceInfo *device_info;
506 GUPnPServiceInfo *av_transport;
508 device_info = GUPNP_DEVICE_INFO (proxy);
509 av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
511 dev_ctx_c->av_transport = av_transport;
514 STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
517 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
518 GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
521 guint32 number_returned;
522 guint32 total_matches;
526 gupnp_service_proxy_end_action (content_dir, action, &error,
527 "Result", G_TYPE_STRING, &result,
528 "NumberReturned", G_TYPE_UINT, &number_returned,
529 "TotalMatches", G_TYPE_UINT, &total_matches,
532 if (number_returned == 0)
535 if (number_returned == 1) {
536 found = strstr (result, "id=\"");
538 strncpy (subid, found, 32); subid[32] = '\0';
540 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
541 "ObjectID", G_TYPE_STRING, subid,
542 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
543 "Filter", G_TYPE_STRING, "@childCount",
544 "StartingIndex", G_TYPE_UINT, 0,
545 "RequestedCount", G_TYPE_UINT, 64,
546 "SortCriteria", G_TYPE_STRING, "",
551 if (number_returned > 1) {
552 dev_ctx_c->content_res = result;
553 dev_ctx_c->content_num = number_returned;
557 STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
560 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
564 gupnp_service_proxy_end_action (content_dir, action, &error,
565 "Result", G_TYPE_STRING, &result,
568 dev_ctx_c->content_res = result;
571 STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
575 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
576 GUPnPServiceProxy *av_transport_proxy;
579 struct timeval tv_start, tv_now;
581 av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
583 gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
585 switch (dev_ctx_c->target_state) {
587 gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
588 "InstanceID", G_TYPE_UINT, 0,
589 "Speed", G_TYPE_STRING, "1",
593 gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
594 "InstanceID", G_TYPE_UINT, 0,
598 gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
599 "InstanceID", G_TYPE_UINT, 0,
603 time = _rygel_time_for_string (dev_ctx_c->action_args);
604 gupnp_service_proxy_begin_action (av_transport_proxy, "Seek", _rygel_do_cb, dev_ctx_c,
605 "InstanceID", G_TYPE_UINT, 0,
606 "Unit", G_TYPE_STRING, "ABS_TIME",
607 "Target", G_TYPE_STRING, time,
613 gettimeofday (&tv_start, NULL);
614 gettimeofday (&tv_now, NULL);
615 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
617 g_main_context_iteration (dev_ctx_c->loop, FALSE);
619 if (dev_ctx_c->state == dev_ctx_c->target_state)
621 gettimeofday (&tv_now, NULL);
625 STATIC void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
628 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
629 GUPnPServiceProxy *content_dir_proxy;
631 char *result, *start, *end, *dst_uri, *src_uri;
633 struct timeval tv_start, tv_now;
635 content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
637 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
638 "Result", G_TYPE_STRING, &result,
642 start = strstr (result, "<res importUri=\"");
647 end = strstr (start, "\"");
648 length = end - start;
650 dst_uri = (char*) malloc(length+1);
651 strncpy (dst_uri, start, length);
652 dst_uri[length] = '\0';
654 asprintf (&src_uri, "http://%s:%u%s", gupnp_context_get_host_ip (dev_ctx_c->context),
655 gupnp_context_get_port (dev_ctx_c->context),
656 dev_ctx_c->transfer_path);
659 gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path,
660 dev_ctx_c->transfer_path);
662 gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c,
663 "SourceURI", G_TYPE_STRING, src_uri,
664 "DestinationURI", G_TYPE_STRING, dst_uri,
667 gettimeofday (&tv_start, NULL);
668 gettimeofday (&tv_now, NULL);
669 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
671 g_main_context_iteration (dev_ctx_c->loop, FALSE);
673 if (dev_ctx_c->transfer_started)
675 gettimeofday (&tv_now, NULL);
679 STATIC void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
682 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
686 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
687 "TransferID", G_TYPE_UINT, &transfer_id,
691 dev_ctx_c->transfer_started = 1;
694 STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
697 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
700 if (!gupnp_service_proxy_end_action (av_transport, action, &error,
704 dev_ctx_c->state = dev_ctx_c->target_state;