2 * Copyright (C) 2016 "IoT.bzh"
3 * Author "Manuel Bachmann"
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
22 #include "media-api.h"
23 #include "media-rygel.h"
25 /* -------------- MEDIA RYGEL IMPLEMENTATION ---------------- */
27 /* --- PUBLIC FUNCTIONS --- */
29 unsigned char _rygel_init (mediaCtxHandleT *ctx) {
32 GUPnPContext *context;
33 GUPnPControlPoint *control_point;
35 struct timeval tv_start, tv_now;
37 context = gupnp_context_new (NULL, NULL, 0, NULL);
39 control_point = gupnp_control_point_new (context, URN_MEDIA_SERVER);
41 handler_cb = g_signal_connect (control_point, "device-proxy-available",
42 G_CALLBACK (_rygel_device_cb), ctx);
44 /* start searching for servers */
45 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
47 loop = g_main_context_default ();
49 /* 5 seconds should be sufficient to find Rygel */
50 gettimeofday (&tv_start, NULL);
51 gettimeofday (&tv_now, NULL);
52 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
54 g_main_context_iteration (loop, FALSE);
56 if (ctx->media_server)
58 gettimeofday (&tv_now, NULL);
60 /* fail if we found no server */
61 if (!ctx->media_server)
64 /* we have found the server ; stop looking for it... */
65 g_signal_handler_disconnect (control_point, handler_cb);
67 dev_ctx[client_count]->loop = loop;
68 dev_ctx[client_count]->context = context;
69 dev_ctx[client_count]->av_transport = NULL;
70 dev_ctx[client_count]->state = STOP;
71 dev_ctx[client_count]->target_state = STOP;
72 dev_ctx[client_count]->action_args = NULL;
73 dev_ctx[client_count]->transfer_started = 0;
80 void _rygel_free (mediaCtxHandleT *ctx) {
82 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
86 g_main_context_unref (dev_ctx_c->loop);
87 dev_ctx_c->loop = NULL;
88 dev_ctx_c->context = NULL;
89 dev_ctx_c->device_info = NULL;
90 dev_ctx_c->av_transport = NULL;
91 dev_ctx_c->content_dir = NULL;
92 dev_ctx_c->content_res = NULL;
95 json_object* _rygel_list (mediaCtxHandleT *ctx) {
97 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
98 json_object *json_o, *json_a;
99 char *raw, *start, *end, *id, *title;
105 raw = _rygel_list_raw (dev_ctx_c, NULL);
109 start = strstr (raw, "<dc:title>");
113 json_o = json_object_new_object ();
114 json_a = json_object_new_array ();
116 json_object *json_i, *json_id, *json_title;
118 start = strstr (start, "<dc:title>");
120 end = strstr (start, "</dc:title>");
122 length = end - start;
124 asprintf (&id, "%02d", i);
126 title = (char*) malloc (length+1);
127 strncpy (title, start, length);
128 title[length] = '\0';
130 json_i = json_object_new_object ();
131 json_id = json_object_new_string (id);
132 json_title = json_object_new_string (title);
133 json_object_object_add (json_i, "id", json_id);
134 json_object_object_add (json_i, "title", json_title);
135 json_object_array_add (json_a, json_i);
137 free (id); free (title);
141 json_object_object_add (json_o, "list", json_a);
146 unsigned char _rygel_select (mediaCtxHandleT *ctx, unsigned int index) {
148 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
154 if (!_rygel_list_raw (dev_ctx_c, &count) ||
158 if (ctx->index != index)
159 dev_ctx_c->state = STOP;
164 unsigned char _rygel_upload (mediaCtxHandleT *ctx, char *path) {
166 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
167 char *raw, *upload_id;
172 raw = _rygel_list_raw (dev_ctx_c, NULL);
176 /* for now, we always use the same upload container id */
177 upload_id = _rygel_find_upload_id (dev_ctx_c, raw);
179 return _rygel_start_uploading (dev_ctx_c, path, upload_id);
182 unsigned char _rygel_do (mediaCtxHandleT *ctx, State state, char *args) {
184 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
185 unsigned int index = ctx->index;
187 char *raw, *id, *metadata, *uri;
189 if (!dev_ctx_c || dev_ctx_c->state == state)
192 raw = _rygel_list_raw (dev_ctx_c, &count);
193 if (!raw || index >= count)
196 id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
197 metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
198 uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
200 return _rygel_start_doing (dev_ctx_c, uri, metadata, state, args);
203 /* --- LOCAL HELPER FUNCTIONS --- */
205 char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
207 GUPnPServiceProxy *content_dir_proxy;
208 struct timeval tv_start, tv_now;
210 dev_ctx_c->content_res = NULL;
211 dev_ctx_c->content_num = 0;
212 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
214 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
215 "ObjectID", G_TYPE_STRING, "Filesystem",
216 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
217 "Filter", G_TYPE_STRING, "@childCount",
218 "StartingIndex", G_TYPE_UINT, 0,
219 "RequestedCount", G_TYPE_UINT, 64,
220 "SortCriteria", G_TYPE_STRING, "",
223 gettimeofday (&tv_start, NULL);
224 gettimeofday (&tv_now, NULL);
225 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
227 g_main_context_iteration (dev_ctx_c->loop, FALSE);
229 if (dev_ctx_c->content_res)
231 gettimeofday (&tv_now, NULL);
234 if (count) *count = dev_ctx_c->content_num;
235 return dev_ctx_c->content_res;
238 char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) {
243 found = strstr (raw, "parentID=\"");
246 /* IDs are 32-bit strings */
247 strncpy (id, found, 32);
253 char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
259 for (i = 0; i <= index; i++) {
260 found = strstr (found, "item id=");
264 /* IDs are 32-bit strings */
265 strncpy (id, found, 32);
273 char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
275 GUPnPServiceProxy *content_dir_proxy;
276 struct timeval tv_start, tv_now;
278 dev_ctx_c->content_res = NULL;
280 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
282 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
283 "ObjectID", G_TYPE_STRING, id,
284 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
285 "Filter", G_TYPE_STRING, "*",
286 "StartingIndex", G_TYPE_UINT, 0,
287 "RequestedCount", G_TYPE_UINT, 0,
288 "SortCriteria", G_TYPE_STRING, "",
291 gettimeofday (&tv_start, NULL);
292 gettimeofday (&tv_now, NULL);
293 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
295 g_main_context_iteration (dev_ctx_c->loop, FALSE);
297 if (dev_ctx_c->content_res)
299 gettimeofday (&tv_now, NULL);
302 return dev_ctx_c->content_res;
305 char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
307 char *start, *end, *uri = NULL;
310 /* position ourselves after the first "<res " tag */
311 start = strstr (metadata, "<res ");
314 start = strstr (start, "http://");
316 end = strstr (start, "</res>");
317 length = end - start;
320 uri = (char *)malloc (length + 1);
321 strncpy (uri, start, length);
323 /* if the URI contains "primary_http", it is the main one ; stop here...*/
324 if (strstr (uri, "primary_http"))
327 free (uri); start = end;
333 char * _rygel_time_for_string (char *string) {
336 unsigned int hours, minutes, seconds;
339 total_seconds = atoi (string);
340 hours = total_seconds / 3600;
341 minutes = (total_seconds / 60) - (hours * 60);
342 seconds = total_seconds - (hours * 3600) - (minutes * 60);
344 asprintf (&time, "%u:%02u:%02u", hours, minutes, seconds);
349 unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) {
351 GUPnPServiceProxy *content_dir_proxy;
352 GUPnPDIDLLiteWriter *didl_writer;
353 GUPnPDIDLLiteObject *didl_object;
354 char *didl, *content_type, *mime_type, *upnp_class;
355 struct timeval tv_start, tv_now;
357 didl_writer = gupnp_didl_lite_writer_new (NULL);
358 didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer));
360 /* create the metadata for the file */
361 gupnp_didl_lite_object_set_parent_id (didl_object, upload_id);
362 gupnp_didl_lite_object_set_id (didl_object, "");
363 gupnp_didl_lite_object_set_restricted (didl_object, FALSE);
364 gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path));
365 /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */
366 content_type = g_content_type_guess (path, NULL, 0, NULL);
367 mime_type = g_content_type_get_mime_type (content_type);
368 if (strstr (mime_type, "audio/"))
369 upnp_class = strdup ("object.item.audioItem.musicTrack");
370 else if (strstr (mime_type, "video/"))
371 upnp_class = strdup ("object.item.videoItem");
372 else if (strstr (mime_type, "image/"))
373 upnp_class = strdup ("object.item.imageItem");
375 upnp_class = strdup ("object.item");
376 gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class);
377 didl = gupnp_didl_lite_writer_get_string (didl_writer);
379 dev_ctx_c->transfer_path = path;
380 dev_ctx_c->transfer_started = 0;
381 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
383 gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c,
384 "ContainerID", G_TYPE_STRING, upload_id,
385 "Elements", G_TYPE_STRING, didl,
388 gettimeofday (&tv_start, NULL);
389 gettimeofday (&tv_now, NULL);
390 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
392 g_main_context_iteration (dev_ctx_c->loop, FALSE);
394 if (dev_ctx_c->transfer_started)
396 gettimeofday (&tv_now, NULL);
398 if (!dev_ctx_c->transfer_started)
404 unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state, char *args) {
406 GUPnPServiceProxy *av_transport_proxy;
407 struct timeval tv_start, tv_now;
409 if (!dev_ctx_c->av_transport) {
410 if (!_rygel_find_av_transport (dev_ctx_c))
413 dev_ctx_c->target_state = state;
414 dev_ctx_c->action_args = args;
415 av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
417 gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
418 "InstanceID", G_TYPE_UINT, 0,
419 "CurrentURI", G_TYPE_STRING, uri,
420 "CurrentURIMetaData", G_TYPE_STRING, metadata,
423 gettimeofday (&tv_start, NULL);
424 gettimeofday (&tv_now, NULL);
425 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
427 g_main_context_iteration (dev_ctx_c->loop, FALSE);
429 if (dev_ctx_c->state == state)
431 gettimeofday (&tv_now, NULL);
433 if (dev_ctx_c->state != state)
439 unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
441 GUPnPControlPoint *control_point;
443 struct timeval tv_start, tv_now;
445 control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
447 handler_cb = g_signal_connect (control_point, "device-proxy-available",
448 G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
450 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
452 gettimeofday (&tv_start, NULL);
453 gettimeofday (&tv_now, NULL);
454 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
456 g_main_context_iteration (dev_ctx_c->loop, FALSE);
458 if (dev_ctx_c->av_transport)
460 gettimeofday (&tv_now, NULL);
462 g_signal_handler_disconnect (control_point, handler_cb);
464 if (!dev_ctx_c->av_transport)
471 /* ---- LOCAL CALLBACK FUNCTIONS ---- */
473 static void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
476 mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
477 GUPnPDeviceInfo *device_info;
478 GUPnPServiceInfo *content_dir;
479 const char *device_name;
481 device_info = GUPNP_DEVICE_INFO (proxy);
482 device_name = gupnp_device_info_get_model_name (device_info);
483 content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
485 if (strcmp (device_name, "Rygel") != 0)
490 /* allocate the global array if it has not been not done */
492 dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
494 dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
496 /* create an element for the client in the global array */
497 dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
498 dev_ctx[client_count]->device_info = device_info;
499 dev_ctx[client_count]->content_dir = content_dir;
501 /* make the client context aware of it */
502 ctx->media_server = (void*)dev_ctx[client_count];
505 static void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
508 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
509 GUPnPDeviceInfo *device_info;
510 GUPnPServiceInfo *av_transport;
512 device_info = GUPNP_DEVICE_INFO (proxy);
513 av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
515 dev_ctx_c->av_transport = av_transport;
518 static void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
521 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
522 GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
525 guint32 number_returned;
526 guint32 total_matches;
530 gupnp_service_proxy_end_action (content_dir, action, &error,
531 "Result", G_TYPE_STRING, &result,
532 "NumberReturned", G_TYPE_UINT, &number_returned,
533 "TotalMatches", G_TYPE_UINT, &total_matches,
536 if (number_returned == 0)
539 if (number_returned == 1) {
540 found = strstr (result, "id=\"");
542 strncpy (subid, found, 32); subid[32] = '\0';
544 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
545 "ObjectID", G_TYPE_STRING, subid,
546 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
547 "Filter", G_TYPE_STRING, "@childCount",
548 "StartingIndex", G_TYPE_UINT, 0,
549 "RequestedCount", G_TYPE_UINT, 64,
550 "SortCriteria", G_TYPE_STRING, "",
555 if (number_returned > 1) {
556 dev_ctx_c->content_res = result;
557 dev_ctx_c->content_num = number_returned;
561 static void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
564 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
568 gupnp_service_proxy_end_action (content_dir, action, &error,
569 "Result", G_TYPE_STRING, &result,
572 dev_ctx_c->content_res = result;
575 static void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
579 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
580 GUPnPServiceProxy *av_transport_proxy;
583 struct timeval tv_start, tv_now;
585 av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
587 gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
589 switch (dev_ctx_c->target_state) {
591 gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
592 "InstanceID", G_TYPE_UINT, 0,
593 "Speed", G_TYPE_STRING, "1",
597 gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
598 "InstanceID", G_TYPE_UINT, 0,
602 gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
603 "InstanceID", G_TYPE_UINT, 0,
607 time = _rygel_time_for_string (dev_ctx_c->action_args);
608 gupnp_service_proxy_begin_action (av_transport_proxy, "Seek", _rygel_do_cb, dev_ctx_c,
609 "InstanceID", G_TYPE_UINT, 0,
610 "Unit", G_TYPE_STRING, "ABS_TIME",
611 "Target", G_TYPE_STRING, time,
617 gettimeofday (&tv_start, NULL);
618 gettimeofday (&tv_now, NULL);
619 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
621 g_main_context_iteration (dev_ctx_c->loop, FALSE);
623 if (dev_ctx_c->state == dev_ctx_c->target_state)
625 gettimeofday (&tv_now, NULL);
629 static void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
632 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
633 GUPnPServiceProxy *content_dir_proxy;
635 char *result, *start, *end, *dst_uri, *src_uri;
637 struct timeval tv_start, tv_now;
639 content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
641 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
642 "Result", G_TYPE_STRING, &result,
646 start = strstr (result, "<res importUri=\"");
651 end = strstr (start, "\"");
652 length = end - start;
654 dst_uri = (char*) malloc(length+1);
655 strncpy (dst_uri, start, length);
656 dst_uri[length] = '\0';
658 asprintf (&src_uri, "http://%s:%u%s", gupnp_context_get_host_ip (dev_ctx_c->context),
659 gupnp_context_get_port (dev_ctx_c->context),
660 dev_ctx_c->transfer_path);
663 gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path,
664 dev_ctx_c->transfer_path);
666 gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c,
667 "SourceURI", G_TYPE_STRING, src_uri,
668 "DestinationURI", G_TYPE_STRING, dst_uri,
671 gettimeofday (&tv_start, NULL);
672 gettimeofday (&tv_now, NULL);
673 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
675 g_main_context_iteration (dev_ctx_c->loop, FALSE);
677 if (dev_ctx_c->transfer_started)
679 gettimeofday (&tv_now, NULL);
683 static void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
686 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
690 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
691 "TransferID", G_TYPE_UINT, &transfer_id,
695 dev_ctx_c->transfer_started = 1;
698 static void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
701 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
704 if (!gupnp_service_proxy_end_action (av_transport, action, &error,
708 dev_ctx_c->state = dev_ctx_c->target_state;