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.
18 #include "media-api.h"
20 /* -------------- MEDIA RYGEL IMPLEMENTATION ---------------- */
22 /* --- PUBLIC FUNCTIONS --- */
24 PUBLIC unsigned char _rygel_init (mediaCtxHandleT *ctx) {
27 GUPnPContext *context;
28 GUPnPControlPoint *control_point;
30 struct timeval tv_start, tv_now;
32 context = gupnp_context_new (NULL, NULL, 0, NULL);
34 control_point = gupnp_control_point_new (context, URN_MEDIA_SERVER);
36 handler_cb = g_signal_connect (control_point, "device-proxy-available",
37 G_CALLBACK (_rygel_device_cb), ctx);
39 /* start searching for servers */
40 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
42 loop = g_main_context_default ();
44 /* 5 seconds should be sufficient to find Rygel */
45 gettimeofday (&tv_start, NULL);
46 gettimeofday (&tv_now, NULL);
47 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
49 g_main_context_iteration (loop, FALSE);
51 if (ctx->media_server)
53 gettimeofday (&tv_now, NULL);
55 /* fail if we found no server */
56 if (!ctx->media_server)
59 /* we have found the server ; stop looking for it... */
60 g_signal_handler_disconnect (control_point, handler_cb);
62 dev_ctx[client_count]->loop = loop;
63 dev_ctx[client_count]->context = context;
64 dev_ctx[client_count]->av_transport = NULL;
65 dev_ctx[client_count]->state = STOP;
66 dev_ctx[client_count]->target_state = STOP;
67 dev_ctx[client_count]->action_args = NULL;
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 json_object* _rygel_list (mediaCtxHandleT *ctx) {
92 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
93 json_object *json_o, *json_a;
94 char *raw, *start, *end, *id, *title;
100 raw = _rygel_list_raw (dev_ctx_c, NULL);
104 start = strstr (raw, "<dc:title>");
108 json_o = json_object_new_object ();
109 json_a = json_object_new_array ();
111 json_object *json_i, *json_id, *json_title;
113 start = strstr (start, "<dc:title>");
115 end = strstr (start, "</dc:title>");
117 length = end - start;
119 asprintf (&id, "%02d", i);
121 title = (char*) malloc (length+1);
122 strncpy (title, start, length);
123 title[length] = '\0';
125 json_i = json_object_new_object ();
126 json_id = json_object_new_string (id);
127 json_title = json_object_new_string (title);
128 json_object_object_add (json_i, "id", json_id);
129 json_object_object_add (json_i, "title", json_title);
130 json_object_array_add (json_a, json_i);
132 free (id); free (title);
136 json_object_object_add (json_o, "list", json_a);
141 PUBLIC unsigned char _rygel_select (mediaCtxHandleT *ctx, unsigned int index) {
143 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
149 if (!_rygel_list_raw (dev_ctx_c, &count) ||
153 if (ctx->index != index)
154 dev_ctx_c->state = STOP;
159 PUBLIC unsigned char _rygel_upload (mediaCtxHandleT *ctx, char *path) {
161 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
162 char *raw, *upload_id;
167 raw = _rygel_list_raw (dev_ctx_c, NULL);
171 /* for now, we always use the same upload container id */
172 upload_id = _rygel_find_upload_id (dev_ctx_c, raw);
174 return _rygel_start_uploading (dev_ctx_c, path, upload_id);
177 PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state, char *args) {
179 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
180 unsigned int index = ctx->index;
182 char *raw, *id, *metadata, *uri;
184 if (!dev_ctx_c || dev_ctx_c->state == state)
187 raw = _rygel_list_raw (dev_ctx_c, &count);
188 if (!raw || index >= count)
191 id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
192 metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
193 uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
195 return _rygel_start_doing (dev_ctx_c, uri, metadata, state, args);
198 /* --- LOCAL HELPER FUNCTIONS --- */
200 STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
202 GUPnPServiceProxy *content_dir_proxy;
203 struct timeval tv_start, tv_now;
205 dev_ctx_c->content_res = NULL;
206 dev_ctx_c->content_num = 0;
207 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
209 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
210 "ObjectID", G_TYPE_STRING, "Filesystem",
211 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
212 "Filter", G_TYPE_STRING, "@childCount",
213 "StartingIndex", G_TYPE_UINT, 0,
214 "RequestedCount", G_TYPE_UINT, 64,
215 "SortCriteria", G_TYPE_STRING, "",
218 gettimeofday (&tv_start, NULL);
219 gettimeofday (&tv_now, NULL);
220 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
222 g_main_context_iteration (dev_ctx_c->loop, FALSE);
224 if (dev_ctx_c->content_res)
226 gettimeofday (&tv_now, NULL);
229 if (count) *count = dev_ctx_c->content_num;
230 return dev_ctx_c->content_res;
233 STATIC char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) {
238 found = strstr (raw, "parentID=\"");
241 /* IDs are 32-bit strings */
242 strncpy (id, found, 32);
248 STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
254 for (i = 0; i <= index; i++) {
255 found = strstr (found, "item id=");
259 /* IDs are 32-bit strings */
260 strncpy (id, found, 32);
268 STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
270 GUPnPServiceProxy *content_dir_proxy;
271 struct timeval tv_start, tv_now;
273 dev_ctx_c->content_res = NULL;
275 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
277 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
278 "ObjectID", G_TYPE_STRING, id,
279 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
280 "Filter", G_TYPE_STRING, "*",
281 "StartingIndex", G_TYPE_UINT, 0,
282 "RequestedCount", G_TYPE_UINT, 0,
283 "SortCriteria", G_TYPE_STRING, "",
286 gettimeofday (&tv_start, NULL);
287 gettimeofday (&tv_now, NULL);
288 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
290 g_main_context_iteration (dev_ctx_c->loop, FALSE);
292 if (dev_ctx_c->content_res)
294 gettimeofday (&tv_now, NULL);
297 return dev_ctx_c->content_res;
300 STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
302 char *start, *end, *uri = NULL;
305 /* position ourselves after the first "<res " tag */
306 start = strstr (metadata, "<res ");
309 start = strstr (start, "http://");
311 end = strstr (start, "</res>");
312 length = end - start;
315 uri = (char *)malloc (length + 1);
316 strncpy (uri, start, length);
318 /* if the URI contains "primary_http", it is the main one ; stop here...*/
319 if (strstr (uri, "primary_http"))
322 free (uri); start = end;
328 STATIC char * _rygel_time_for_string (char *string) {
331 unsigned int hours, minutes, seconds;
334 total_seconds = atoi (string);
335 hours = total_seconds / 3600;
336 minutes = (total_seconds / 60) - (hours * 60);
337 seconds = total_seconds - (hours * 3600) - (minutes * 60);
339 asprintf (&time, "%u:%02u:%02u", hours, minutes, seconds);
344 STATIC unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) {
346 GUPnPServiceProxy *content_dir_proxy;
347 GUPnPDIDLLiteWriter *didl_writer;
348 GUPnPDIDLLiteObject *didl_object;
349 char *didl, *content_type, *mime_type, *upnp_class;
350 struct timeval tv_start, tv_now;
352 didl_writer = gupnp_didl_lite_writer_new (NULL);
353 didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer));
355 /* create the metadata for the file */
356 gupnp_didl_lite_object_set_parent_id (didl_object, upload_id);
357 gupnp_didl_lite_object_set_id (didl_object, "");
358 gupnp_didl_lite_object_set_restricted (didl_object, FALSE);
359 gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path));
360 /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */
361 content_type = g_content_type_guess (path, NULL, 0, NULL);
362 mime_type = g_content_type_get_mime_type (content_type);
363 if (strstr (mime_type, "audio/"))
364 upnp_class = strdup ("object.item.audioItem.musicTrack");
365 else if (strstr (mime_type, "video/"))
366 upnp_class = strdup ("object.item.videoItem");
367 else if (strstr (mime_type, "image/"))
368 upnp_class = strdup ("object.item.imageItem");
370 upnp_class = strdup ("object.item");
371 gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class);
372 didl = gupnp_didl_lite_writer_get_string (didl_writer);
374 dev_ctx_c->transfer_path = path;
375 dev_ctx_c->transfer_started = 0;
376 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
378 gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c,
379 "ContainerID", G_TYPE_STRING, upload_id,
380 "Elements", G_TYPE_STRING, didl,
383 gettimeofday (&tv_start, NULL);
384 gettimeofday (&tv_now, NULL);
385 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
387 g_main_context_iteration (dev_ctx_c->loop, FALSE);
389 if (dev_ctx_c->transfer_started)
391 gettimeofday (&tv_now, NULL);
393 if (!dev_ctx_c->transfer_started)
399 STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state, char *args) {
401 GUPnPServiceProxy *av_transport_proxy;
402 struct timeval tv_start, tv_now;
404 if (!dev_ctx_c->av_transport) {
405 if (!_rygel_find_av_transport (dev_ctx_c))
408 dev_ctx_c->target_state = state;
409 dev_ctx_c->action_args = args;
410 av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
412 gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
413 "InstanceID", G_TYPE_UINT, 0,
414 "CurrentURI", G_TYPE_STRING, uri,
415 "CurrentURIMetaData", G_TYPE_STRING, metadata,
418 gettimeofday (&tv_start, NULL);
419 gettimeofday (&tv_now, NULL);
420 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
422 g_main_context_iteration (dev_ctx_c->loop, FALSE);
424 if (dev_ctx_c->state == state)
426 gettimeofday (&tv_now, NULL);
428 if (dev_ctx_c->state != state)
434 STATIC unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
436 GUPnPControlPoint *control_point;
438 struct timeval tv_start, tv_now;
440 control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
442 handler_cb = g_signal_connect (control_point, "device-proxy-available",
443 G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
445 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
447 gettimeofday (&tv_start, NULL);
448 gettimeofday (&tv_now, NULL);
449 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
451 g_main_context_iteration (dev_ctx_c->loop, FALSE);
453 if (dev_ctx_c->av_transport)
455 gettimeofday (&tv_now, NULL);
457 g_signal_handler_disconnect (control_point, handler_cb);
459 if (!dev_ctx_c->av_transport)
466 /* ---- LOCAL CALLBACK FUNCTIONS ---- */
468 STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
471 mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
472 GUPnPDeviceInfo *device_info;
473 GUPnPServiceInfo *content_dir;
474 const char *device_name;
476 device_info = GUPNP_DEVICE_INFO (proxy);
477 device_name = gupnp_device_info_get_model_name (device_info);
478 content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
480 if (strcmp (device_name, "Rygel") != 0)
485 /* allocate the global array if it has not been not done */
487 dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
489 dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
491 /* create an element for the client in the global array */
492 dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
493 dev_ctx[client_count]->device_info = device_info;
494 dev_ctx[client_count]->content_dir = content_dir;
496 /* make the client context aware of it */
497 ctx->media_server = (void*)dev_ctx[client_count];
500 STATIC void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
503 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
504 GUPnPDeviceInfo *device_info;
505 GUPnPServiceInfo *av_transport;
507 device_info = GUPNP_DEVICE_INFO (proxy);
508 av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
510 dev_ctx_c->av_transport = av_transport;
513 STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
516 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
517 GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
520 guint32 number_returned;
521 guint32 total_matches;
525 gupnp_service_proxy_end_action (content_dir, action, &error,
526 "Result", G_TYPE_STRING, &result,
527 "NumberReturned", G_TYPE_UINT, &number_returned,
528 "TotalMatches", G_TYPE_UINT, &total_matches,
531 if (number_returned == 0)
534 if (number_returned == 1) {
535 found = strstr (result, "id=\"");
537 strncpy (subid, found, 32); subid[32] = '\0';
539 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
540 "ObjectID", G_TYPE_STRING, subid,
541 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
542 "Filter", G_TYPE_STRING, "@childCount",
543 "StartingIndex", G_TYPE_UINT, 0,
544 "RequestedCount", G_TYPE_UINT, 64,
545 "SortCriteria", G_TYPE_STRING, "",
550 if (number_returned > 1) {
551 dev_ctx_c->content_res = result;
552 dev_ctx_c->content_num = number_returned;
556 STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
559 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
563 gupnp_service_proxy_end_action (content_dir, action, &error,
564 "Result", G_TYPE_STRING, &result,
567 dev_ctx_c->content_res = result;
570 STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
574 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
575 GUPnPServiceProxy *av_transport_proxy;
578 struct timeval tv_start, tv_now;
580 av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
582 gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
584 switch (dev_ctx_c->target_state) {
586 gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
587 "InstanceID", G_TYPE_UINT, 0,
588 "Speed", G_TYPE_STRING, "1",
592 gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
593 "InstanceID", G_TYPE_UINT, 0,
597 gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
598 "InstanceID", G_TYPE_UINT, 0,
602 time = _rygel_time_for_string (dev_ctx_c->action_args);
603 gupnp_service_proxy_begin_action (av_transport_proxy, "Seek", _rygel_do_cb, dev_ctx_c,
604 "InstanceID", G_TYPE_UINT, 0,
605 "Unit", G_TYPE_STRING, "ABS_TIME",
606 "Target", G_TYPE_STRING, time,
612 gettimeofday (&tv_start, NULL);
613 gettimeofday (&tv_now, NULL);
614 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
616 g_main_context_iteration (dev_ctx_c->loop, FALSE);
618 if (dev_ctx_c->state == dev_ctx_c->target_state)
620 gettimeofday (&tv_now, NULL);
624 STATIC void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
627 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
628 GUPnPServiceProxy *content_dir_proxy;
630 char *result, *start, *end, *dst_uri, *src_uri;
632 struct timeval tv_start, tv_now;
634 content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
636 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
637 "Result", G_TYPE_STRING, &result,
641 start = strstr (result, "<res importUri=\"");
646 end = strstr (start, "\"");
647 length = end - start;
649 dst_uri = (char*) malloc(length+1);
650 strncpy (dst_uri, start, length);
651 dst_uri[length] = '\0';
653 asprintf (&src_uri, "http://%s:%u%s", gupnp_context_get_host_ip (dev_ctx_c->context),
654 gupnp_context_get_port (dev_ctx_c->context),
655 dev_ctx_c->transfer_path);
658 gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path,
659 dev_ctx_c->transfer_path);
661 gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c,
662 "SourceURI", G_TYPE_STRING, src_uri,
663 "DestinationURI", G_TYPE_STRING, dst_uri,
666 gettimeofday (&tv_start, NULL);
667 gettimeofday (&tv_now, NULL);
668 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
670 g_main_context_iteration (dev_ctx_c->loop, FALSE);
672 if (dev_ctx_c->transfer_started)
674 gettimeofday (&tv_now, NULL);
678 STATIC void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
681 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
685 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
686 "TransferID", G_TYPE_UINT, &transfer_id,
690 dev_ctx_c->transfer_started = 1;
693 STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
696 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
699 if (!gupnp_service_proxy_end_action (av_transport, action, &error,
703 dev_ctx_c->state = dev_ctx_c->target_state;