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;
74 PUBLIC void _rygel_free (mediaCtxHandleT *ctx) {
76 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
80 g_main_context_unref (dev_ctx_c->loop);
81 dev_ctx_c->loop = NULL;
82 dev_ctx_c->context = NULL;
83 dev_ctx_c->device_info = NULL;
84 dev_ctx_c->av_transport = NULL;
85 dev_ctx_c->content_dir = NULL;
86 dev_ctx_c->content_res = NULL;
89 PUBLIC char* _rygel_list (mediaCtxHandleT *ctx) {
91 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
92 char *raw, *start, *end, *title, *result = NULL;
98 raw = _rygel_list_raw (dev_ctx_c, NULL);
101 start = strstr (raw, "<dc:title>");
102 if (!start) return NULL;
107 start = strstr (start, "<dc:title>");
109 end = strstr (start, "</dc:title>");
110 start += 10; length = end - start;
112 title = (char*) malloc (length+1);
113 strncpy (title, start, length);
114 title[length] = '\0';
116 asprintf (&result, "%s%02d:%s::", result, i, title);
125 PUBLIC unsigned char _rygel_choose (mediaCtxHandleT *ctx, unsigned int index) {
127 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
133 if (!_rygel_list_raw (dev_ctx_c, &count) ||
137 if (ctx->index != index)
138 dev_ctx_c->state = STOP;
143 PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state) {
145 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
146 unsigned int index = ctx->index;
148 char *raw, *id, *metadata, *uri;
150 if (!dev_ctx_c || dev_ctx_c->state == state)
153 raw = _rygel_list_raw (dev_ctx_c, &count);
154 if (!raw || index >= count)
157 id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
158 metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
159 uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
161 return _rygel_start_doing (dev_ctx_c, uri, metadata, state);
164 /* --- LOCAL HELPER FUNCTIONS --- */
166 STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
168 GUPnPServiceProxy *content_dir_proxy;
169 struct timeval tv_start, tv_now;
171 dev_ctx_c->content_res = NULL;
172 dev_ctx_c->content_num = 0;
174 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
176 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
177 "ObjectID", G_TYPE_STRING, "Filesystem",
178 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
179 "Filter", G_TYPE_STRING, "@childCount",
180 "StartingIndex", G_TYPE_UINT, 0,
181 "RequestedCount", G_TYPE_UINT, 64,
182 "SortCriteria", G_TYPE_STRING, "",
185 gettimeofday (&tv_start, NULL);
186 gettimeofday (&tv_now, NULL);
187 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
189 g_main_context_iteration (dev_ctx_c->loop, FALSE);
191 if (dev_ctx_c->content_res)
193 gettimeofday (&tv_now, NULL);
196 if (count) *count = dev_ctx_c->content_num;
197 return dev_ctx_c->content_res;
200 STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
206 for (i = 0; i <= index; i++) {
207 found = strstr (found, "item id=");
211 /* IDs are 32-bit numbers */
212 strncpy (id, found, 32);
220 STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
222 GUPnPServiceProxy *content_dir_proxy;
223 struct timeval tv_start, tv_now;
225 dev_ctx_c->content_res = NULL;
227 content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
229 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
230 "ObjectID", G_TYPE_STRING, id,
231 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
232 "Filter", G_TYPE_STRING, "*",
233 "StartingIndex", G_TYPE_UINT, 0,
234 "RequestedCount", G_TYPE_UINT, 0,
235 "SortCriteria", G_TYPE_STRING, "",
238 gettimeofday (&tv_start, NULL);
239 gettimeofday (&tv_now, NULL);
240 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
242 g_main_context_iteration (dev_ctx_c->loop, FALSE);
244 if (dev_ctx_c->content_res)
246 gettimeofday (&tv_now, NULL);
249 return dev_ctx_c->content_res;
252 STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
254 char *start, *end, *uri = NULL;
257 /* position ourselves after the first "<res " tag */
258 start = strstr (metadata, "<res ");
261 start = strstr (start, "http://");
263 end = strstr (start, "</res>");
264 length = end - start;
267 uri = (char *)malloc (length + 1);
268 strncpy (uri, start, length);
270 /* if the URI contains "primary_http", it is the main one ; stop here...*/
271 if (strstr (uri, "primary_http"))
274 free (uri); start = end;
280 STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state) {
282 GUPnPServiceProxy *av_transport_proxy;
283 struct timeval tv_start, tv_now;
285 if (!dev_ctx_c->av_transport) {
286 if (!_rygel_find_av_transport (dev_ctx_c))
289 dev_ctx_c->target_state = state;
291 av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
293 gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
294 "InstanceID", G_TYPE_UINT, 0,
295 "CurrentURI", G_TYPE_STRING, uri,
296 "CurrentURIMetaData", G_TYPE_STRING, metadata,
299 gettimeofday (&tv_start, NULL);
300 gettimeofday (&tv_now, NULL);
301 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
303 g_main_context_iteration (dev_ctx_c->loop, FALSE);
305 if (dev_ctx_c->state == state)
307 gettimeofday (&tv_now, NULL);
309 if (dev_ctx_c->state != state)
315 STATIC unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
317 GUPnPControlPoint *control_point;
319 struct timeval tv_start, tv_now;
321 control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
323 handler_cb = g_signal_connect (control_point, "device-proxy-available",
324 G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
326 gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
328 gettimeofday (&tv_start, NULL);
329 gettimeofday (&tv_now, NULL);
330 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
332 g_main_context_iteration (dev_ctx_c->loop, FALSE);
334 if (dev_ctx_c->av_transport)
336 gettimeofday (&tv_now, NULL);
338 g_signal_handler_disconnect (control_point, handler_cb);
340 if (!dev_ctx_c->av_transport)
347 /* ---- LOCAL CALLBACK FUNCTIONS ---- */
349 STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
352 mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
353 GUPnPDeviceInfo *device_info;
354 GUPnPServiceInfo *content_dir;
355 const char *device_name;
357 device_info = GUPNP_DEVICE_INFO (proxy);
358 device_name = gupnp_device_info_get_model_name (device_info);
359 content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
361 if (strcmp (device_name, "Rygel") != 0)
366 /* allocate the global array if it has not been not done */
368 dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
370 dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
372 /* create an element for the client in the global array */
373 dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
374 dev_ctx[client_count]->device_info = device_info;
375 dev_ctx[client_count]->content_dir = content_dir;
377 /* make the client context aware of it */
378 ctx->media_server = (void*)dev_ctx[client_count];
381 STATIC void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
384 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
385 GUPnPDeviceInfo *device_info;
386 GUPnPServiceInfo *av_transport;
388 device_info = GUPNP_DEVICE_INFO (proxy);
389 av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
391 dev_ctx_c->av_transport = av_transport;
394 STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
397 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
398 GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
401 guint32 number_returned;
402 guint32 total_matches;
406 gupnp_service_proxy_end_action (content_dir, action, &error,
407 "Result", G_TYPE_STRING, &result,
408 "NumberReturned", G_TYPE_UINT, &number_returned,
409 "TotalMatches", G_TYPE_UINT, &total_matches,
412 if (number_returned == 0)
415 if (number_returned == 1) {
416 found = strstr (result, "id=\"");
418 strncpy (subid, found, 32); subid[32] = '\0';
420 gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
421 "ObjectID", G_TYPE_STRING, subid,
422 "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
423 "Filter", G_TYPE_STRING, "@childCount",
424 "StartingIndex", G_TYPE_UINT, 0,
425 "RequestedCount", G_TYPE_UINT, 64,
426 "SortCriteria", G_TYPE_STRING, "",
431 if (number_returned > 1) {
432 dev_ctx_c->content_res = result;
433 dev_ctx_c->content_num = number_returned;
437 STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
440 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
444 gupnp_service_proxy_end_action (content_dir, action, &error,
445 "Result", G_TYPE_STRING, &result,
448 dev_ctx_c->content_res = result;
451 STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
455 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
456 GUPnPServiceProxy *av_transport_proxy;
458 struct timeval tv_start, tv_now;
460 av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
462 gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
464 switch (dev_ctx_c->target_state) {
466 gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
467 "InstanceID", G_TYPE_UINT, 0,
468 "Speed", G_TYPE_STRING, "1",
472 gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
473 "InstanceID", G_TYPE_UINT, 0,
477 gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
478 "InstanceID", G_TYPE_UINT, 0,
485 gettimeofday (&tv_start, NULL);
486 gettimeofday (&tv_now, NULL);
487 while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
489 g_main_context_iteration (dev_ctx_c->loop, FALSE);
491 if (dev_ctx_c->state == dev_ctx_c->target_state)
493 gettimeofday (&tv_now, NULL);
497 STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
500 dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
503 gupnp_service_proxy_end_action (av_transport, action, &error,
506 dev_ctx_c->state = dev_ctx_c->target_state;