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 char* _rygel_list (mediaCtxHandleT *ctx) {
93 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
94 char *raw, *start, *end, *title, *result = NULL;
100 raw = _rygel_list_raw (dev_ctx_c, NULL);
104 start = strstr (raw, "<dc:title>");
110 start = strstr (start, "<dc:title>");
112 end = strstr (start, "</dc:title>");
114 length = end - start;
116 title = (char*) malloc (length+1);
117 strncpy (title, start, length);
118 title[length] = '\0';
120 asprintf (&result, "%s%02d:%s::", result, i, title);
128 PUBLIC unsigned char _rygel_select (mediaCtxHandleT *ctx, unsigned int index) {
130 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
136 if (!_rygel_list_raw (dev_ctx_c, &count) ||
140 if (ctx->index != index)
141 dev_ctx_c->state = STOP;
146 PUBLIC unsigned char _rygel_upload (mediaCtxHandleT *ctx, char *path) {
148 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
149 char *raw, *upload_id;
154 raw = _rygel_list_raw (dev_ctx_c, NULL);
158 /* for now, we always use the same upload container id */
159 upload_id = _rygel_find_upload_id (dev_ctx_c, raw);
161 return _rygel_start_uploading (dev_ctx_c, path, upload_id);
164 PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state, char *args) {
166 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
167 unsigned int index = ctx->index;
169 char *raw, *id, *metadata, *uri;
171 if (!dev_ctx_c || dev_ctx_c->state == state)
174 raw = _rygel_list_raw (dev_ctx_c, &count);
175 if (!raw || index >= count)
178 id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
179 metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
180 uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
182 return _rygel_start_doing (dev_ctx_c, uri, metadata, state, args);
185 /* --- LOCAL HELPER FUNCTIONS --- */
187 STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
189 GUPnPServiceProxy *content_dir_proxy;
190 struct timeval tv_start, tv_now;
192 dev_ctx_c->content_res = NULL;
193 dev_ctx_c->content_num = 0;
194 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
196 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
197 "ObjectID", G_TYPE_STRING, "Filesystem",
198 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
199 "Filter", G_TYPE_STRING, "@childCount",
200 "StartingIndex", G_TYPE_UINT, 0,
201 "RequestedCount", G_TYPE_UINT, 64,
202 "SortCriteria", G_TYPE_STRING, "",
205 gettimeofday (&tv_start, NULL);
206 gettimeofday (&tv_now, NULL);
207 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
209 g_main_context_iteration (dev_ctx_c->loop, FALSE);
211 if (dev_ctx_c->content_res)
213 gettimeofday (&tv_now, NULL);
216 if (count) *count = dev_ctx_c->content_num;
217 return dev_ctx_c->content_res;
220 STATIC char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) {
225 found = strstr (raw, "parentID=\"");
228 /* IDs are 32-bit strings */
229 strncpy (id, found, 32);
235 STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
241 for (i = 0; i <= index; i++) {
242 found = strstr (found, "item id=");
246 /* IDs are 32-bit strings */
247 strncpy (id, found, 32);
255 STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
257 GUPnPServiceProxy *content_dir_proxy;
258 struct timeval tv_start, tv_now;
260 dev_ctx_c->content_res = NULL;
262 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
264 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
265 "ObjectID", G_TYPE_STRING, id,
266 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
267 "Filter", G_TYPE_STRING, "*",
268 "StartingIndex", G_TYPE_UINT, 0,
269 "RequestedCount", G_TYPE_UINT, 0,
270 "SortCriteria", G_TYPE_STRING, "",
273 gettimeofday (&tv_start, NULL);
274 gettimeofday (&tv_now, NULL);
275 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
277 g_main_context_iteration (dev_ctx_c->loop, FALSE);
279 if (dev_ctx_c->content_res)
281 gettimeofday (&tv_now, NULL);
284 return dev_ctx_c->content_res;
287 STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
289 char *start, *end, *uri = NULL;
292 /* position ourselves after the first "<res " tag */
293 start = strstr (metadata, "<res ");
296 start = strstr (start, "http://");
298 end = strstr (start, "</res>");
299 length = end - start;
302 uri = (char *)malloc (length + 1);
303 strncpy (uri, start, length);
305 /* if the URI contains "primary_http", it is the main one ; stop here...*/
306 if (strstr (uri, "primary_http"))
309 free (uri); start = end;
315 STATIC char * _rygel_time_for_string (char *string) {
318 unsigned int hours, minutes, seconds;
321 total_seconds = atoi (string);
322 hours = total_seconds / 3600;
323 minutes = (total_seconds / 60) - (hours * 60);
324 seconds = total_seconds - (hours * 3600) - (minutes * 60);
326 asprintf (&time, "%u:%02u:%02u", hours, minutes, seconds);
331 STATIC unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) {
333 GUPnPServiceProxy *content_dir_proxy;
334 GUPnPDIDLLiteWriter *didl_writer;
335 GUPnPDIDLLiteObject *didl_object;
336 char *didl, *content_type, *mime_type, *upnp_class;
337 struct timeval tv_start, tv_now;
339 didl_writer = gupnp_didl_lite_writer_new (NULL);
340 didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer));
342 /* create the metadata for the file */
343 gupnp_didl_lite_object_set_parent_id (didl_object, upload_id);
344 gupnp_didl_lite_object_set_id (didl_object, "");
345 gupnp_didl_lite_object_set_restricted (didl_object, FALSE);
346 gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path));
347 /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */
348 content_type = g_content_type_guess (path, NULL, 0, NULL);
349 mime_type = g_content_type_get_mime_type (content_type);
350 if (strstr (mime_type, "audio/"))
351 upnp_class = strdup ("object.item.audioItem.musicTrack");
352 else if (strstr (mime_type, "video/"))
353 upnp_class = strdup ("object.item.videoItem");
354 else if (strstr (mime_type, "image/"))
355 upnp_class = strdup ("object.item.imageItem");
357 upnp_class = strdup ("object.item");
358 gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class);
359 didl = gupnp_didl_lite_writer_get_string (didl_writer);
361 dev_ctx_c->transfer_path = path;
362 dev_ctx_c->transfer_started = 0;
363 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
365 gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c,
366 "ContainerID", G_TYPE_STRING, upload_id,
367 "Elements", G_TYPE_STRING, didl,
370 gettimeofday (&tv_start, NULL);
371 gettimeofday (&tv_now, NULL);
372 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
374 g_main_context_iteration (dev_ctx_c->loop, FALSE);
376 if (dev_ctx_c->transfer_started)
378 gettimeofday (&tv_now, NULL);
380 if (!dev_ctx_c->transfer_started)
386 STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state, char *args) {
388 GUPnPServiceProxy *av_transport_proxy;
389 struct timeval tv_start, tv_now;
391 if (!dev_ctx_c->av_transport) {
392 if (!_rygel_find_av_transport (dev_ctx_c))
395 dev_ctx_c->target_state = state;
396 dev_ctx_c->action_args = args;
397 av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
399 gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
400 "InstanceID", G_TYPE_UINT, 0,
401 "CurrentURI", G_TYPE_STRING, uri,
402 "CurrentURIMetaData", G_TYPE_STRING, metadata,
405 gettimeofday (&tv_start, NULL);
406 gettimeofday (&tv_now, NULL);
407 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
409 g_main_context_iteration (dev_ctx_c->loop, FALSE);
411 if (dev_ctx_c->state == state)
413 gettimeofday (&tv_now, NULL);
415 if (dev_ctx_c->state != state)
421 STATIC unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
423 GUPnPControlPoint *control_point;
425 struct timeval tv_start, tv_now;
427 control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
429 handler_cb = g_signal_connect (control_point, "device-proxy-available",
430 G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
432 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
434 gettimeofday (&tv_start, NULL);
435 gettimeofday (&tv_now, NULL);
436 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
438 g_main_context_iteration (dev_ctx_c->loop, FALSE);
440 if (dev_ctx_c->av_transport)
442 gettimeofday (&tv_now, NULL);
444 g_signal_handler_disconnect (control_point, handler_cb);
446 if (!dev_ctx_c->av_transport)
453 /* ---- LOCAL CALLBACK FUNCTIONS ---- */
455 STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
458 mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
459 GUPnPDeviceInfo *device_info;
460 GUPnPServiceInfo *content_dir;
461 const char *device_name;
463 device_info = GUPNP_DEVICE_INFO (proxy);
464 device_name = gupnp_device_info_get_model_name (device_info);
465 content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
467 if (strcmp (device_name, "Rygel") != 0)
472 /* allocate the global array if it has not been not done */
474 dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
476 dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
478 /* create an element for the client in the global array */
479 dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
480 dev_ctx[client_count]->device_info = device_info;
481 dev_ctx[client_count]->content_dir = content_dir;
483 /* make the client context aware of it */
484 ctx->media_server = (void*)dev_ctx[client_count];
487 STATIC void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
490 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
491 GUPnPDeviceInfo *device_info;
492 GUPnPServiceInfo *av_transport;
494 device_info = GUPNP_DEVICE_INFO (proxy);
495 av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
497 dev_ctx_c->av_transport = av_transport;
500 STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
503 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
504 GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
507 guint32 number_returned;
508 guint32 total_matches;
512 gupnp_service_proxy_end_action (content_dir, action, &error,
513 "Result", G_TYPE_STRING, &result,
514 "NumberReturned", G_TYPE_UINT, &number_returned,
515 "TotalMatches", G_TYPE_UINT, &total_matches,
518 if (number_returned == 0)
521 if (number_returned == 1) {
522 found = strstr (result, "id=\"");
524 strncpy (subid, found, 32); subid[32] = '\0';
526 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
527 "ObjectID", G_TYPE_STRING, subid,
528 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
529 "Filter", G_TYPE_STRING, "@childCount",
530 "StartingIndex", G_TYPE_UINT, 0,
531 "RequestedCount", G_TYPE_UINT, 64,
532 "SortCriteria", G_TYPE_STRING, "",
537 if (number_returned > 1) {
538 dev_ctx_c->content_res = result;
539 dev_ctx_c->content_num = number_returned;
543 STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
546 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
550 gupnp_service_proxy_end_action (content_dir, action, &error,
551 "Result", G_TYPE_STRING, &result,
554 dev_ctx_c->content_res = result;
557 STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
561 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
562 GUPnPServiceProxy *av_transport_proxy;
565 struct timeval tv_start, tv_now;
567 av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
569 gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
571 switch (dev_ctx_c->target_state) {
573 gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
574 "InstanceID", G_TYPE_UINT, 0,
575 "Speed", G_TYPE_STRING, "1",
579 gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
580 "InstanceID", G_TYPE_UINT, 0,
584 gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
585 "InstanceID", G_TYPE_UINT, 0,
589 time = _rygel_time_for_string (dev_ctx_c->action_args);
590 gupnp_service_proxy_begin_action (av_transport_proxy, "Seek", _rygel_do_cb, dev_ctx_c,
591 "InstanceID", G_TYPE_UINT, 0,
592 "Unit", G_TYPE_STRING, "ABS_TIME",
593 "Target", G_TYPE_STRING, time,
599 gettimeofday (&tv_start, NULL);
600 gettimeofday (&tv_now, NULL);
601 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
603 g_main_context_iteration (dev_ctx_c->loop, FALSE);
605 if (dev_ctx_c->state == dev_ctx_c->target_state)
607 gettimeofday (&tv_now, NULL);
611 STATIC void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
614 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
615 GUPnPServiceProxy *content_dir_proxy;
617 char *result, *start, *end, *dst_uri, *src_uri;
619 struct timeval tv_start, tv_now;
621 content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
623 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
624 "Result", G_TYPE_STRING, &result,
628 start = strstr (result, "<res importUri=\"");
633 end = strstr (start, "\"");
634 length = end - start;
636 dst_uri = (char*) malloc(length+1);
637 strncpy (dst_uri, start, length);
638 dst_uri[length] = '\0';
640 asprintf (&src_uri, "http://%s:%u%s", gupnp_context_get_host_ip (dev_ctx_c->context),
641 gupnp_context_get_port (dev_ctx_c->context),
642 dev_ctx_c->transfer_path);
645 gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path,
646 dev_ctx_c->transfer_path);
648 gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c,
649 "SourceURI", G_TYPE_STRING, src_uri,
650 "DestinationURI", G_TYPE_STRING, dst_uri,
653 gettimeofday (&tv_start, NULL);
654 gettimeofday (&tv_now, NULL);
655 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
657 g_main_context_iteration (dev_ctx_c->loop, FALSE);
659 if (dev_ctx_c->transfer_started)
661 gettimeofday (&tv_now, NULL);
665 STATIC void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
668 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
672 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
673 "TransferID", G_TYPE_UINT, &transfer_id,
677 dev_ctx_c->transfer_started = 1;
680 STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
683 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
686 if (!gupnp_service_proxy_end_action (av_transport, action, &error,
690 dev_ctx_c->state = dev_ctx_c->target_state;