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 static void _rygel_device_cb (GUPnPControlPoint *, GUPnPDeviceProxy *, gpointer);
26 static void _rygel_av_transport_cb (GUPnPControlPoint *, GUPnPDeviceProxy *, gpointer);
27 static void _rygel_content_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer);
28 static void _rygel_metadata_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer);
29 static void _rygel_select_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer);
30 static void _rygel_upload_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer);
31 static void _rygel_transfer_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer);
32 static void _rygel_do_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer);
34 static unsigned int client_count = 0;
35 static struct dev_ctx **dev_ctx = NULL;
37 /* -------------- MEDIA RYGEL IMPLEMENTATION ---------------- */
39 /* --- PUBLIC FUNCTIONS --- */
41 unsigned char _rygel_init (mediaCtxHandleT *ctx) {
44 GUPnPContext *context;
45 GUPnPControlPoint *control_point;
47 struct timeval tv_start, tv_now;
49 context = gupnp_context_new (NULL, NULL, 0, NULL);
51 control_point = gupnp_control_point_new (context, URN_MEDIA_SERVER);
53 handler_cb = g_signal_connect (control_point, "device-proxy-available",
54 G_CALLBACK (_rygel_device_cb), ctx);
56 /* start searching for servers */
57 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
59 loop = g_main_context_default ();
61 /* 5 seconds should be sufficient to find Rygel */
62 gettimeofday (&tv_start, NULL);
63 gettimeofday (&tv_now, NULL);
64 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
66 g_main_context_iteration (loop, FALSE);
68 if (ctx->media_server)
70 gettimeofday (&tv_now, NULL);
72 /* fail if we found no server */
73 if (!ctx->media_server)
76 /* we have found the server ; stop looking for it... */
77 g_signal_handler_disconnect (control_point, handler_cb);
79 dev_ctx[client_count]->loop = loop;
80 dev_ctx[client_count]->context = context;
81 dev_ctx[client_count]->av_transport = NULL;
82 dev_ctx[client_count]->state = STOP;
83 dev_ctx[client_count]->target_state = STOP;
84 dev_ctx[client_count]->action_args = NULL;
85 dev_ctx[client_count]->transfer_started = 0;
92 void _rygel_free (mediaCtxHandleT *ctx) {
94 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
98 g_main_context_unref (dev_ctx_c->loop);
99 dev_ctx_c->loop = NULL;
100 dev_ctx_c->context = NULL;
101 dev_ctx_c->device_info = NULL;
102 dev_ctx_c->av_transport = NULL;
103 dev_ctx_c->content_dir = NULL;
104 dev_ctx_c->content_res = NULL;
107 json_object* _rygel_list (mediaCtxHandleT *ctx) {
109 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
110 json_object *json_o, *json_a;
111 char *raw, *start, *end, *id, *title;
117 raw = _rygel_list_raw (dev_ctx_c, NULL);
121 start = strstr (raw, "<dc:title>");
125 json_o = json_object_new_object ();
126 json_a = json_object_new_array ();
128 json_object *json_i, *json_id, *json_title;
130 start = strstr (start, "<dc:title>");
132 end = strstr (start, "</dc:title>");
134 length = end - start;
136 asprintf (&id, "%02d", i);
138 title = (char*) malloc (length+1);
139 strncpy (title, start, length);
140 title[length] = '\0';
142 json_i = json_object_new_object ();
143 json_id = json_object_new_string (id);
144 json_title = json_object_new_string (title);
145 json_object_object_add (json_i, "id", json_id);
146 json_object_object_add (json_i, "title", json_title);
147 json_object_array_add (json_a, json_i);
149 free (id); free (title);
153 json_object_object_add (json_o, "list", json_a);
158 unsigned char _rygel_select (mediaCtxHandleT *ctx, unsigned int index) {
160 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
166 if (!_rygel_list_raw (dev_ctx_c, &count) ||
170 if (ctx->index != index)
171 dev_ctx_c->state = STOP;
176 unsigned char _rygel_upload (mediaCtxHandleT *ctx, const char *path, void (*oncompletion)(void*,int), void *closure) {
178 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
179 char *raw, *upload_id;
184 raw = _rygel_list_raw (dev_ctx_c, NULL);
188 /* for now, we always use the same upload container id */
189 upload_id = _rygel_find_upload_id (dev_ctx_c, raw);
191 return _rygel_start_uploading (dev_ctx_c, strdup(path), upload_id);
194 unsigned char _rygel_do (mediaCtxHandleT *ctx, State state, char *args) {
196 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
197 unsigned int index = ctx->index;
199 char *raw, *id, *metadata, *uri;
201 if (!dev_ctx_c || dev_ctx_c->state == state)
204 raw = _rygel_list_raw (dev_ctx_c, &count);
205 if (!raw || index >= count)
208 id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
209 metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
210 uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
212 return _rygel_start_doing (dev_ctx_c, uri, metadata, state, args);
215 /* --- LOCAL HELPER FUNCTIONS --- */
217 char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
219 GUPnPServiceProxy *content_dir_proxy;
220 struct timeval tv_start, tv_now;
222 dev_ctx_c->content_res = NULL;
223 dev_ctx_c->content_num = 0;
224 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
226 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
227 "ObjectID", G_TYPE_STRING, "Filesystem",
228 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
229 "Filter", G_TYPE_STRING, "@childCount",
230 "StartingIndex", G_TYPE_UINT, 0,
231 "RequestedCount", G_TYPE_UINT, 64,
232 "SortCriteria", G_TYPE_STRING, "",
235 gettimeofday (&tv_start, NULL);
236 gettimeofday (&tv_now, NULL);
237 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
239 g_main_context_iteration (dev_ctx_c->loop, FALSE);
241 if (dev_ctx_c->content_res)
243 gettimeofday (&tv_now, NULL);
246 if (count) *count = dev_ctx_c->content_num;
247 return dev_ctx_c->content_res;
250 char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) {
255 found = strstr (raw, "parentID=\"");
258 /* IDs are 32-bit strings */
259 strncpy (id, found, 32);
265 char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
271 for (i = 0; i <= index; i++) {
272 found = strstr (found, "item id=");
276 /* IDs are 32-bit strings */
277 strncpy (id, found, 32);
285 char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
287 GUPnPServiceProxy *content_dir_proxy;
288 struct timeval tv_start, tv_now;
290 dev_ctx_c->content_res = NULL;
292 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
294 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
295 "ObjectID", G_TYPE_STRING, id,
296 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
297 "Filter", G_TYPE_STRING, "*",
298 "StartingIndex", G_TYPE_UINT, 0,
299 "RequestedCount", G_TYPE_UINT, 0,
300 "SortCriteria", G_TYPE_STRING, "",
303 gettimeofday (&tv_start, NULL);
304 gettimeofday (&tv_now, NULL);
305 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
307 g_main_context_iteration (dev_ctx_c->loop, FALSE);
309 if (dev_ctx_c->content_res)
311 gettimeofday (&tv_now, NULL);
314 return dev_ctx_c->content_res;
317 char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
319 char *start, *end, *uri = NULL;
322 /* position ourselves after the first "<res " tag */
323 start = strstr (metadata, "<res ");
326 start = strstr (start, "http://");
328 end = strstr (start, "</res>");
329 length = end - start;
332 uri = (char *)malloc (length + 1);
333 strncpy (uri, start, length);
335 /* if the URI contains "primary_http", it is the main one ; stop here...*/
336 if (strstr (uri, "primary_http"))
339 free (uri); start = end;
345 char * _rygel_time_for_string (char *string) {
348 unsigned int hours, minutes, seconds;
351 total_seconds = atoi (string);
352 hours = total_seconds / 3600;
353 minutes = (total_seconds / 60) - (hours * 60);
354 seconds = total_seconds - (hours * 3600) - (minutes * 60);
356 asprintf (&time, "%u:%02u:%02u", hours, minutes, seconds);
361 unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) {
363 GUPnPServiceProxy *content_dir_proxy;
364 GUPnPDIDLLiteWriter *didl_writer;
365 GUPnPDIDLLiteObject *didl_object;
366 char *didl, *content_type, *mime_type, *upnp_class;
367 struct timeval tv_start, tv_now;
369 didl_writer = gupnp_didl_lite_writer_new (NULL);
370 didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer));
372 /* create the metadata for the file */
373 gupnp_didl_lite_object_set_parent_id (didl_object, upload_id);
374 gupnp_didl_lite_object_set_id (didl_object, "");
375 gupnp_didl_lite_object_set_restricted (didl_object, FALSE);
376 gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path));
377 /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */
378 content_type = g_content_type_guess (path, NULL, 0, NULL);
379 mime_type = g_content_type_get_mime_type (content_type);
380 if (strstr (mime_type, "audio/"))
381 upnp_class = strdup ("object.item.audioItem.musicTrack");
382 else if (strstr (mime_type, "video/"))
383 upnp_class = strdup ("object.item.videoItem");
384 else if (strstr (mime_type, "image/"))
385 upnp_class = strdup ("object.item.imageItem");
387 upnp_class = strdup ("object.item");
388 gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class);
389 didl = gupnp_didl_lite_writer_get_string (didl_writer);
391 dev_ctx_c->transfer_path = path;
392 dev_ctx_c->transfer_started = 0;
393 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
395 gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c,
396 "ContainerID", G_TYPE_STRING, upload_id,
397 "Elements", G_TYPE_STRING, didl,
400 gettimeofday (&tv_start, NULL);
401 gettimeofday (&tv_now, NULL);
402 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
404 g_main_context_iteration (dev_ctx_c->loop, FALSE);
406 if (dev_ctx_c->transfer_started)
408 gettimeofday (&tv_now, NULL);
410 if (!dev_ctx_c->transfer_started)
416 unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state, char *args) {
418 GUPnPServiceProxy *av_transport_proxy;
419 struct timeval tv_start, tv_now;
421 if (!dev_ctx_c->av_transport) {
422 if (!_rygel_find_av_transport (dev_ctx_c))
425 dev_ctx_c->target_state = state;
426 dev_ctx_c->action_args = args;
427 av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
429 gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
430 "InstanceID", G_TYPE_UINT, 0,
431 "CurrentURI", G_TYPE_STRING, uri,
432 "CurrentURIMetaData", G_TYPE_STRING, metadata,
435 gettimeofday (&tv_start, NULL);
436 gettimeofday (&tv_now, NULL);
437 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
439 g_main_context_iteration (dev_ctx_c->loop, FALSE);
441 if (dev_ctx_c->state == state)
443 gettimeofday (&tv_now, NULL);
445 if (dev_ctx_c->state != state)
451 unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
453 GUPnPControlPoint *control_point;
455 struct timeval tv_start, tv_now;
457 control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
459 handler_cb = g_signal_connect (control_point, "device-proxy-available",
460 G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
462 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
464 gettimeofday (&tv_start, NULL);
465 gettimeofday (&tv_now, NULL);
466 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
468 g_main_context_iteration (dev_ctx_c->loop, FALSE);
470 if (dev_ctx_c->av_transport)
472 gettimeofday (&tv_now, NULL);
474 g_signal_handler_disconnect (control_point, handler_cb);
476 if (!dev_ctx_c->av_transport)
483 /* ---- LOCAL CALLBACK FUNCTIONS ---- */
485 static void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
488 mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
489 GUPnPDeviceInfo *device_info;
490 GUPnPServiceInfo *content_dir;
491 const char *device_name;
493 device_info = GUPNP_DEVICE_INFO (proxy);
494 device_name = gupnp_device_info_get_model_name (device_info);
495 content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
497 if (strcmp (device_name, "Rygel") != 0)
502 /* allocate the global array if it has not been not done */
504 dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
506 dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
508 /* create an element for the client in the global array */
509 dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
510 dev_ctx[client_count]->device_info = device_info;
511 dev_ctx[client_count]->content_dir = content_dir;
513 /* make the client context aware of it */
514 ctx->media_server = (void*)dev_ctx[client_count];
517 static void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
520 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
521 GUPnPDeviceInfo *device_info;
522 GUPnPServiceInfo *av_transport;
524 device_info = GUPNP_DEVICE_INFO (proxy);
525 av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
527 dev_ctx_c->av_transport = av_transport;
530 static void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
533 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
534 GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
537 guint32 number_returned;
538 guint32 total_matches;
542 gupnp_service_proxy_end_action (content_dir, action, &error,
543 "Result", G_TYPE_STRING, &result,
544 "NumberReturned", G_TYPE_UINT, &number_returned,
545 "TotalMatches", G_TYPE_UINT, &total_matches,
548 if (number_returned == 0)
551 if (number_returned == 1) {
552 found = strstr (result, "id=\"");
554 strncpy (subid, found, 32); subid[32] = '\0';
556 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
557 "ObjectID", G_TYPE_STRING, subid,
558 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
559 "Filter", G_TYPE_STRING, "@childCount",
560 "StartingIndex", G_TYPE_UINT, 0,
561 "RequestedCount", G_TYPE_UINT, 64,
562 "SortCriteria", G_TYPE_STRING, "",
567 if (number_returned > 1) {
568 dev_ctx_c->content_res = result;
569 dev_ctx_c->content_num = number_returned;
573 static void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
576 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
580 gupnp_service_proxy_end_action (content_dir, action, &error,
581 "Result", G_TYPE_STRING, &result,
584 dev_ctx_c->content_res = result;
587 static void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
591 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
592 GUPnPServiceProxy *av_transport_proxy;
595 struct timeval tv_start, tv_now;
597 av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
599 gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
601 switch (dev_ctx_c->target_state) {
603 gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
604 "InstanceID", G_TYPE_UINT, 0,
605 "Speed", G_TYPE_STRING, "1",
609 gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
610 "InstanceID", G_TYPE_UINT, 0,
614 gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
615 "InstanceID", G_TYPE_UINT, 0,
619 time = _rygel_time_for_string (dev_ctx_c->action_args);
620 gupnp_service_proxy_begin_action (av_transport_proxy, "Seek", _rygel_do_cb, dev_ctx_c,
621 "InstanceID", G_TYPE_UINT, 0,
622 "Unit", G_TYPE_STRING, "ABS_TIME",
623 "Target", G_TYPE_STRING, time,
629 gettimeofday (&tv_start, NULL);
630 gettimeofday (&tv_now, NULL);
631 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
633 g_main_context_iteration (dev_ctx_c->loop, FALSE);
635 if (dev_ctx_c->state == dev_ctx_c->target_state)
637 gettimeofday (&tv_now, NULL);
641 static void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
644 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
645 GUPnPServiceProxy *content_dir_proxy;
647 char *result, *start, *end, *dst_uri, *src_uri;
649 struct timeval tv_start, tv_now;
651 content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
653 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
654 "Result", G_TYPE_STRING, &result,
658 start = strstr (result, "<res importUri=\"");
663 end = strstr (start, "\"");
664 length = end - start;
666 dst_uri = (char*) malloc(length+1);
667 strncpy (dst_uri, start, length);
668 dst_uri[length] = '\0';
670 asprintf (&src_uri, "http://%s:%u%s", gupnp_context_get_host_ip (dev_ctx_c->context),
671 gupnp_context_get_port (dev_ctx_c->context),
672 dev_ctx_c->transfer_path);
675 gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path,
676 dev_ctx_c->transfer_path);
678 gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c,
679 "SourceURI", G_TYPE_STRING, src_uri,
680 "DestinationURI", G_TYPE_STRING, dst_uri,
683 gettimeofday (&tv_start, NULL);
684 gettimeofday (&tv_now, NULL);
685 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
687 g_main_context_iteration (dev_ctx_c->loop, FALSE);
689 if (dev_ctx_c->transfer_started)
691 gettimeofday (&tv_now, NULL);
695 static void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
698 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
702 if (!gupnp_service_proxy_end_action (content_dir, action, &error,
703 "TransferID", G_TYPE_UINT, &transfer_id,
707 dev_ctx_c->transfer_started = 1;
710 static void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
713 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
716 if (!gupnp_service_proxy_end_action (av_transport, action, &error,
720 dev_ctx_c->state = dev_ctx_c->target_state;