3ad008efbb398dcd06785c0b345358f3a974451d
[src/app-framework-binder.git] / plugins / media / media-rygel.c
1 /*
2  * Copyright (C) 2016 "IoT.bzh"
3  * Author "Manuel Bachmann"
4  *
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.
9  *
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.
14  *
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/>.
17  */
18
19 #include "media-api.h"
20
21 /* -------------- MEDIA RYGEL IMPLEMENTATION ---------------- */
22
23 /* --- PUBLIC FUNCTIONS --- */
24
25 PUBLIC unsigned char _rygel_init (mediaCtxHandleT *ctx) {
26
27     GMainContext *loop;
28     GUPnPContext *context;
29     GUPnPControlPoint *control_point;
30     gint handler_cb;
31     struct timeval tv_start, tv_now;
32
33     context = gupnp_context_new (NULL, NULL, 0, NULL);
34
35     control_point = gupnp_control_point_new (context, URN_MEDIA_SERVER);
36
37     handler_cb = g_signal_connect (control_point, "device-proxy-available",
38                                    G_CALLBACK (_rygel_device_cb), ctx);
39
40     /* start searching for servers */
41     gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
42
43     loop = g_main_context_default ();
44
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) {
49
50         g_main_context_iteration (loop, FALSE);
51
52         if (ctx->media_server)
53             break;
54         gettimeofday (&tv_now, NULL);
55     }
56     /* fail if we found no server */
57     if (!ctx->media_server)
58       return 0;
59
60     /* we have found the server ; stop looking for it... */
61     g_signal_handler_disconnect (control_point, handler_cb);
62
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
69     client_count++;
70
71     return 1;
72 }
73
74 PUBLIC void _rygel_free (mediaCtxHandleT *ctx) {
75
76     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
77
78     client_count--;
79
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;
87 }
88
89 PUBLIC char* _rygel_list (mediaCtxHandleT *ctx) {
90
91     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
92     char *raw, *start, *end, *title, *result = NULL;
93     int length, i = 0;
94
95     if (!dev_ctx_c)
96       return NULL;
97
98     raw = _rygel_list_raw (dev_ctx_c, NULL);
99
100     if (raw) {
101         start = strstr (raw, "<dc:title>");
102         if (!start) return NULL;
103
104         result = strdup("");
105
106         while (start) {
107             start = strstr (start, "<dc:title>");
108             if (!start) break;
109             end = strstr (start, "</dc:title>");
110             start += 10; length = end - start;
111
112             title = (char*) malloc (length+1);
113             strncpy (title, start, length);
114             title[length] = '\0';
115
116             asprintf (&result, "%s%02d:%s::", result, i, title);
117
118             free (title); i++;
119         }
120     }
121
122     return result;
123 }
124
125 PUBLIC unsigned char _rygel_choose (mediaCtxHandleT *ctx, unsigned int index) {
126
127     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
128     unsigned int count;
129
130     if (!dev_ctx_c)
131       return 0;
132
133     if (!_rygel_list_raw (dev_ctx_c, &count) ||
134         index >= count)
135       return 0;
136
137     if (ctx->index != index)
138       dev_ctx_c->state = STOP;
139
140     return 1;
141 }
142
143 PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state) {
144
145     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
146     unsigned int index = ctx->index;
147     unsigned int count;
148     char *raw, *id, *metadata, *uri;
149
150     if (!dev_ctx_c || dev_ctx_c->state == state)
151         return 0;
152
153     raw = _rygel_list_raw (dev_ctx_c, &count);
154     if (!raw || index >= count)
155       return 0;
156
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);
160
161     return _rygel_start_doing (dev_ctx_c, uri, metadata, state);
162 }
163
164 /* --- LOCAL HELPER FUNCTIONS --- */
165
166 STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
167
168     GUPnPServiceProxy *content_dir_proxy;
169     struct timeval tv_start, tv_now;
170
171     dev_ctx_c->content_res = NULL;
172     dev_ctx_c->content_num = 0;
173
174     content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
175
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, "",
183                                        NULL);
184
185     gettimeofday (&tv_start, NULL);
186     gettimeofday (&tv_now, NULL);
187     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
188
189         g_main_context_iteration (dev_ctx_c->loop, FALSE);
190
191         if (dev_ctx_c->content_res)
192             break;
193         gettimeofday (&tv_now, NULL);
194     }
195
196     if (count) *count = dev_ctx_c->content_num;
197     return dev_ctx_c->content_res;
198 }
199
200 STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
201
202     char *found = raw;
203     char id[33];
204     int i;
205
206     for (i = 0; i <= index; i++) {
207         found = strstr (found, "item id=");
208         found += 9;
209
210         if (i == index) {
211             /* IDs are 32-bit numbers */
212             strncpy (id, found, 32);
213             id[32] = '\0';
214         }
215     }
216
217     return strdup(id);
218 }
219
220 STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
221
222     GUPnPServiceProxy *content_dir_proxy;
223     struct timeval tv_start, tv_now;
224
225     dev_ctx_c->content_res = NULL;
226
227     content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
228
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, "",
236                                        NULL);
237
238     gettimeofday (&tv_start, NULL);
239     gettimeofday (&tv_now, NULL);
240     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
241
242         g_main_context_iteration (dev_ctx_c->loop, FALSE);
243
244         if (dev_ctx_c->content_res)
245             break;
246         gettimeofday (&tv_now, NULL);
247     }
248
249     return dev_ctx_c->content_res;
250 }
251
252 STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
253
254     char *start, *end, *uri = NULL;
255     int length;
256
257     /* position ourselves after the first "<res " tag */
258     start = strstr (metadata, "<res ");
259
260     while (start) {
261         start = strstr (start, "http://");
262         if (!start) break;
263         end = strstr (start, "</res>");
264         length = end - start;
265
266
267         uri = (char *)malloc (length + 1);
268         strncpy (uri, start, length);
269         uri[length] = '\0';
270         /* if the URI contains "primary_http", it is the main one ; stop here...*/
271         if (strstr (uri, "primary_http"))
272           break;
273
274         free (uri); start = end;
275     }
276
277     return uri;
278 }
279
280 STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state) {
281
282     GUPnPServiceProxy *av_transport_proxy;
283     struct timeval tv_start, tv_now;
284
285     if (!dev_ctx_c->av_transport) {
286       if (!_rygel_find_av_transport (dev_ctx_c))
287          return 0;
288     }
289     dev_ctx_c->target_state = state;
290
291     av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
292
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,
297                                       NULL);
298
299     gettimeofday (&tv_start, NULL);
300     gettimeofday (&tv_now, NULL);
301     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
302
303       g_main_context_iteration (dev_ctx_c->loop, FALSE);
304
305       if (dev_ctx_c->state == state)
306         break;
307       gettimeofday (&tv_now, NULL);
308     }
309     if (dev_ctx_c->state != state)
310       return 0;
311
312     return 1;
313 }
314
315 STATIC unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
316
317     GUPnPControlPoint *control_point;
318     gint handler_cb;
319     struct timeval tv_start, tv_now;
320
321     control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
322
323     handler_cb = g_signal_connect (control_point, "device-proxy-available",
324                                    G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
325
326     gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
327
328     gettimeofday (&tv_start, NULL);
329     gettimeofday (&tv_now, NULL);
330     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
331
332         g_main_context_iteration (dev_ctx_c->loop, FALSE);
333
334         if (dev_ctx_c->av_transport)
335             break;
336         gettimeofday (&tv_now, NULL);
337     }
338     g_signal_handler_disconnect (control_point, handler_cb);
339
340     if (!dev_ctx_c->av_transport)
341       return 0;
342
343     return 1;
344 }
345
346
347  /* ---- LOCAL CALLBACK FUNCTIONS ---- */
348
349 STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
350                               gpointer data) {
351
352     mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
353     GUPnPDeviceInfo *device_info;
354     GUPnPServiceInfo *content_dir;
355     const char *device_name;
356
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);
360
361     if (strcmp (device_name, "Rygel") != 0)
362         return;
363     if (!content_dir)
364         return;
365
366     /* allocate the global array if it has not been not done */
367     if (!dev_ctx)
368         dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
369     else
370         dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
371
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;
376
377     /* make the client context aware of it */
378     ctx->media_server = (void*)dev_ctx[client_count];
379 }
380
381 STATIC void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
382                                     gpointer data) {
383
384     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
385     GUPnPDeviceInfo *device_info;
386     GUPnPServiceInfo *av_transport;
387
388     device_info = GUPNP_DEVICE_INFO (proxy);
389     av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
390
391     dev_ctx_c->av_transport = av_transport;
392 }
393
394 STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
395                                gpointer data) {
396
397     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
398     GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
399     GError *error;
400     char *result;
401     guint32 number_returned;
402     guint32 total_matches;
403     char *found;
404     char subid[33];
405
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,
410                                      NULL);
411
412     if (number_returned == 0)
413         return;
414
415     if (number_returned == 1) {
416         found = strstr (result, "id=\"");       
417         found += 4;
418         strncpy (subid, found, 32); subid[32] = '\0';
419
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, "",
427                                            NULL);
428         return;
429     }
430
431     if (number_returned > 1) {
432         dev_ctx_c->content_res = result;
433         dev_ctx_c->content_num = number_returned;
434     }
435 }
436
437 STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
438                                 gpointer data) {
439
440     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
441     GError *error;
442     char *result;
443
444     gupnp_service_proxy_end_action (content_dir, action, &error,
445                                     "Result", G_TYPE_STRING, &result,
446                                      NULL);
447
448     dev_ctx_c->content_res = result;
449 }
450
451 STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
452                             gpointer data)
453 {
454
455     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
456     GUPnPServiceProxy *av_transport_proxy;
457     GError *error;
458     struct timeval tv_start, tv_now;
459
460     av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
461
462     gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
463
464     switch (dev_ctx_c->target_state) {
465         case PLAY:
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",
469                                             NULL);
470           break;
471        case PAUSE:
472           gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
473                                            "InstanceID", G_TYPE_UINT, 0,
474                                             NULL);
475           break;
476        case STOP:
477           gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
478                                            "InstanceID", G_TYPE_UINT, 0,
479                                             NULL);
480           break;
481        default:
482          break;
483     }
484
485     gettimeofday (&tv_start, NULL);
486     gettimeofday (&tv_now, NULL);
487     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
488
489         g_main_context_iteration (dev_ctx_c->loop, FALSE);
490
491         if (dev_ctx_c->state == dev_ctx_c->target_state)
492             break;
493         gettimeofday (&tv_now, NULL);
494     }
495 }
496
497 STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
498                           gpointer data)
499 {
500     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
501     GError *error;
502
503     gupnp_service_proxy_end_action (av_transport, action, &error,
504                                     NULL);
505
506     dev_ctx_c->state = dev_ctx_c->target_state;
507 }