]> git.sesse.net Git - vlc/blob - src/control/media_list_player.c
media_list_player: Use stop() so that we don't lock twice.
[vlc] / src / control / media_list_player.c
1 /*****************************************************************************
2  * media_list_player.c: libvlc new API media_list player functions
3  *****************************************************************************
4  * Copyright (C) 2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #include <vlc/libvlc.h>
25 #include <vlc/libvlc_media.h>
26 #include <vlc/libvlc_media_list.h>
27 #include <vlc/libvlc_media_player.h>
28 #include <vlc/libvlc_media_list_player.h>
29 #include <vlc/libvlc_events.h>
30 #include <assert.h>
31
32 #include "libvlc_internal.h"
33
34 #include "media_internal.h" // Abuse, could and should be removed
35 #include "media_list_path.h"
36
37 //#define DEBUG_MEDIA_LIST_PLAYER
38
39 /* This is a very dummy implementation of playlist on top of
40  * media_list and media_player.
41  *
42  * All this code is doing is simply computing the next item
43  * of a tree of media_list (see get_next_index()), and play
44  * the next item when the current is over. This is happening
45  * via the event callback media_player_reached_end().
46  *
47  * This is thread safe, and we use a two keys (locks) scheme
48  * to discriminate between callbacks and regular uses.
49  */
50
51 struct libvlc_media_list_player_t
52 {
53     libvlc_event_manager_t *    p_event_manager;
54     libvlc_instance_t *         p_libvlc_instance;
55     int                         i_refcount;
56     /* Protect access to this structure. */
57     vlc_mutex_t                 object_lock;
58     /* Protect access to this structure and from callback execution. */
59     vlc_mutex_t                 mp_callback_lock;
60     /* Indicate to media player callbacks that they are cancelled. */
61     bool                        are_mp_callback_cancelled;
62     libvlc_media_list_path_t    current_playing_item_path;
63     libvlc_media_t *            p_current_playing_item;
64     libvlc_media_list_t *       p_mlist;
65     libvlc_media_player_t *     p_mi;
66 };
67
68 /* This is not yet exported by libvlccore */
69 static inline void vlc_assert_locked(vlc_mutex_t *mutex)
70 {
71     VLC_UNUSED(mutex);
72 }
73
74 /*
75  * Forward declaration
76  */
77
78 static void next(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e);
79 static void stop(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e);
80
81 /*
82  * Private functions
83  */
84
85 /**************************************************************************
86  * Shortcuts
87  **************************************************************************/
88 static inline void lock(libvlc_media_list_player_t * p_mlp)
89 {
90     // Obtain an access to this structure
91     vlc_mutex_lock(&p_mlp->object_lock);
92     
93     // Make sure no callback will occurs at the same time
94     vlc_mutex_lock(&p_mlp->mp_callback_lock);
95 }
96
97 static inline void unlock(libvlc_media_list_player_t * p_mlp)
98 {
99     vlc_mutex_unlock(&p_mlp->mp_callback_lock);
100     vlc_mutex_unlock(&p_mlp->object_lock);
101 }
102
103 static inline void assert_locked(libvlc_media_list_player_t * p_mlp)
104 {
105     vlc_assert_locked(&p_mlp->mp_callback_lock);
106 }
107
108 static inline libvlc_event_manager_t * mlist_em(libvlc_media_list_player_t * p_mlp)
109 {
110     assert_locked(p_mlp);
111     return libvlc_media_list_event_manager(p_mlp->p_mlist, NULL);
112 }
113
114 static inline libvlc_event_manager_t * mplayer_em(libvlc_media_list_player_t * p_mlp)
115 {
116     assert_locked(p_mlp);
117     return libvlc_media_player_event_manager(p_mlp->p_mi, NULL);
118 }
119
120
121 /**************************************************************************
122  *       get_next_path (private)
123  *
124  * Basic and dummy next item fetcher.
125  **************************************************************************/
126 static libvlc_media_list_path_t
127 get_next_path(libvlc_media_list_player_t * p_mlp)
128 {
129     assert_locked(p_mlp);
130
131     /* We are entered with libvlc_media_list_lock(p_mlp->p_list) */
132     libvlc_media_list_path_t ret;
133     libvlc_media_list_t * p_parent_of_playing_item;
134     libvlc_media_list_t * p_sublist_of_playing_item;
135
136     if (!p_mlp->current_playing_item_path)
137     {
138         if (!libvlc_media_list_count(p_mlp->p_mlist, NULL))
139             return NULL;
140         return libvlc_media_list_path_with_root_index(0);
141     }
142     
143     p_sublist_of_playing_item = libvlc_media_list_sublist_at_path(
144                             p_mlp->p_mlist,
145                             p_mlp->current_playing_item_path);
146  
147     /* If item just gained a sublist just play it */
148     if (p_sublist_of_playing_item)
149     {
150         libvlc_media_list_release(p_sublist_of_playing_item);
151         return libvlc_media_list_path_copy_by_appending(p_mlp->current_playing_item_path, 0);
152     }
153
154     /* Try to catch next element */
155     p_parent_of_playing_item = libvlc_media_list_parentlist_at_path(p_mlp->p_mlist,
156                             p_mlp->current_playing_item_path);
157
158     int depth = libvlc_media_list_path_depth(p_mlp->current_playing_item_path);
159     if (depth < 1 || !p_parent_of_playing_item)
160         return NULL;
161
162     ret = libvlc_media_list_path_copy(p_mlp->current_playing_item_path);
163
164     ret[depth-1]++; // Play next element
165
166     /* If this goes beyong the end of the list */
167     while(ret[depth-1] >= libvlc_media_list_count(p_parent_of_playing_item, NULL))
168     {
169         depth--;
170         if (depth <= 0)
171         {
172             free(ret);
173             libvlc_media_list_release(p_parent_of_playing_item);
174             return NULL;
175         }
176         ret[depth] = -1;
177         ret[depth-1]++;
178         p_parent_of_playing_item  = libvlc_media_list_parentlist_at_path(
179                                         p_mlp->p_mlist,
180                                         ret);
181     }
182     libvlc_media_list_release(p_parent_of_playing_item);
183     return ret;
184 }
185
186 /**************************************************************************
187  *       media_player_reached_end (private) (Event Callback)
188  **************************************************************************/
189 static void
190 media_player_reached_end(const libvlc_event_t * p_event, void * p_user_data)
191 {
192     VLC_UNUSED(p_event);
193     libvlc_media_list_player_t * p_mlp = p_user_data;
194     libvlc_exception_t e;
195     libvlc_exception_init(&e);
196
197     vlc_mutex_lock(&p_mlp->mp_callback_lock);
198     if (!p_mlp->are_mp_callback_cancelled)
199         next(p_mlp, &e);
200     vlc_mutex_unlock(&p_mlp->mp_callback_lock);
201
202     // There is no point in reporting an error from this callback
203     libvlc_exception_clear(&e);
204 }
205
206 /**************************************************************************
207  *       playlist_item_deleted (private) (Event Callback)
208  **************************************************************************/
209 static void
210 mlist_item_deleted(const libvlc_event_t * p_event, void * p_user_data)
211 {
212     libvlc_media_list_player_t * p_mlp = p_user_data;
213     libvlc_media_list_t * p_emitting_mlist = p_event->p_obj;
214     libvlc_media_t * p_current_md = NULL;
215
216     lock(p_mlp);
217     if (p_mlp->current_playing_item_path)
218         p_current_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, p_mlp->current_playing_item_path);
219
220     if (p_event->u.media_list_item_deleted.item == p_current_md &&
221         p_emitting_mlist == p_mlp->p_mlist)
222     {
223         /* We are playing this item, let's stop */
224         stop(p_mlp, NULL);
225     }
226     unlock(p_mlp);
227 }
228
229
230 /**************************************************************************
231  * install_playlist_observer (private)
232  **************************************************************************/
233 static void
234 install_playlist_observer(libvlc_media_list_player_t * p_mlp)
235 {
236     assert_locked(p_mlp);
237     libvlc_event_attach(mlist_em(p_mlp), libvlc_MediaListItemDeleted, mlist_item_deleted, p_mlp, NULL);
238 }
239
240 /**************************************************************************
241  * uninstall_playlist_observer (private)
242  **************************************************************************/
243 static void
244 uninstall_playlist_observer(libvlc_media_list_player_t * p_mlp)
245 {
246     assert_locked(p_mlp);
247     if (!p_mlp->p_mlist) return;
248     libvlc_event_detach(mlist_em(p_mlp), libvlc_MediaListItemDeleted, mlist_item_deleted, p_mlp, NULL);
249 }
250
251 /**************************************************************************
252  * install_media_player_observer (private)
253  **************************************************************************/
254 static void
255 install_media_player_observer(libvlc_media_list_player_t * p_mlp)
256 {
257     assert_locked(p_mlp);
258     libvlc_event_attach_async(mplayer_em(p_mlp), libvlc_MediaPlayerEndReached, media_player_reached_end, p_mlp, NULL);
259 }
260
261
262 /**************************************************************************
263  *       uninstall_media_player_observer (private)
264  **************************************************************************/
265 static void
266 uninstall_media_player_observer(libvlc_media_list_player_t * p_mlp)
267 {
268     assert_locked(p_mlp);
269     if (!p_mlp->p_mi) return;
270
271     // From now on, media_player callback won't be relevant.
272     p_mlp->are_mp_callback_cancelled = true;
273
274     // Allow callbacks to run, because detach() will wait until all callbacks are processed.
275     // This is safe because only callbacks are allowed, and there execution will be cancelled.
276     vlc_mutex_unlock(&p_mlp->mp_callback_lock);
277     libvlc_event_detach(mplayer_em(p_mlp), libvlc_MediaPlayerEndReached, media_player_reached_end, p_mlp, NULL);
278
279     // Now, lock back the callback lock. No more callback will be present from this point.
280     vlc_mutex_lock(&p_mlp->mp_callback_lock);
281     p_mlp->are_mp_callback_cancelled = true;
282
283     // What is here is safe, because we garantee that we won't be able to anything concurently,
284     // - except (cancelled) callbacks - thanks to the object_lock.
285 }
286
287 /**************************************************************************
288  *       set_current_playing_item (private)
289  *
290  * Playlist lock should be held
291  **************************************************************************/
292 static void
293 set_current_playing_item(libvlc_media_list_player_t * p_mlp, libvlc_media_list_path_t path)
294 {
295     assert_locked(p_mlp);
296
297     /* First, save the new path that we are going to play */
298     if (p_mlp->current_playing_item_path != path)
299     {
300         free(p_mlp->current_playing_item_path);
301         p_mlp->current_playing_item_path = path;
302     }
303
304     if (!path)
305         return;
306
307     libvlc_media_t * p_md;
308     p_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, path);
309     if (!p_md)
310         return;
311     
312     /* Make sure media_player_reached_end() won't get called */
313     uninstall_media_player_observer(p_mlp);
314
315     /* Create a new media_player if there is none */
316     if (!p_mlp->p_mi)
317         p_mlp->p_mi = libvlc_media_player_new_from_media(p_md, NULL);
318     install_media_player_observer(p_mlp);
319     libvlc_media_release(p_md); /* for libvlc_media_list_item_at_index */
320 }
321
322 /*
323  * Public libvlc functions
324  */
325
326 /**************************************************************************
327  *         new (Public)
328  **************************************************************************/
329 libvlc_media_list_player_t *
330 libvlc_media_list_player_new(libvlc_instance_t * p_instance, libvlc_exception_t * p_e)
331 {
332     (void)p_e;
333     libvlc_media_list_player_t * p_mlp;
334     p_mlp = calloc(sizeof(libvlc_media_list_player_t), 1);
335     if (!p_mlp)
336         return NULL;
337
338     libvlc_retain(p_instance);
339     p_mlp->p_libvlc_instance = p_instance;
340     p_mlp->i_refcount = 1;
341     vlc_mutex_init(&p_mlp->object_lock);
342     vlc_mutex_init(&p_mlp->mp_callback_lock);
343     p_mlp->p_event_manager = libvlc_event_manager_new(p_mlp, p_instance, p_e);
344     libvlc_event_manager_register_event_type(p_mlp->p_event_manager, libvlc_MediaListPlayerNextItemSet, p_e);
345
346     return p_mlp;
347 }
348
349 /**************************************************************************
350  *         release (Public)
351  **************************************************************************/
352 void libvlc_media_list_player_release(libvlc_media_list_player_t * p_mlp)
353 {
354     if (!p_mlp)
355         return;
356
357     lock(p_mlp);
358     p_mlp->i_refcount--;
359     if (p_mlp->i_refcount > 0)
360     {
361         unlock(p_mlp);
362         return;
363     }
364
365     assert(p_mlp->i_refcount == 0);
366
367     /* Keep the lock(), because the uninstall functions
368      * check for it. That's convenient. */
369
370     if (p_mlp->p_mi)
371     {
372         uninstall_media_player_observer(p_mlp);
373         libvlc_media_player_release(p_mlp->p_mi);
374     }    
375     if (p_mlp->p_mlist)
376     {
377         uninstall_playlist_observer(p_mlp);
378         libvlc_media_list_release(p_mlp->p_mlist);
379     }
380
381     unlock(p_mlp);
382     vlc_mutex_destroy(&p_mlp->object_lock);
383     vlc_mutex_destroy(&p_mlp->mp_callback_lock);
384
385     libvlc_event_manager_release(p_mlp->p_event_manager);
386     
387     free(p_mlp->current_playing_item_path);
388     libvlc_release(p_mlp->p_libvlc_instance);
389     free(p_mlp);
390 }
391
392 /**************************************************************************
393  *        set_media_player (Public)
394  **************************************************************************/
395 void libvlc_media_list_player_set_media_player(libvlc_media_list_player_t * p_mlp, libvlc_media_player_t * p_mi, libvlc_exception_t * p_e)
396 {
397     VLC_UNUSED(p_e);
398
399     lock(p_mlp);
400
401     if (p_mlp->p_mi)
402     {
403         uninstall_media_player_observer(p_mlp);
404         libvlc_media_player_release(p_mlp->p_mi);
405     }
406     libvlc_media_player_retain(p_mi);
407     p_mlp->p_mi = p_mi;
408
409     install_media_player_observer(p_mlp);
410
411     unlock(p_mlp);
412 }
413
414 /**************************************************************************
415  *       set_media_list (Public)
416  **************************************************************************/
417 void libvlc_media_list_player_set_media_list(libvlc_media_list_player_t * p_mlp, libvlc_media_list_t * p_mlist, libvlc_exception_t * p_e)
418 {
419     lock(p_mlp);
420
421     if (!p_mlist)
422     {
423         libvlc_exception_raise(p_e, "No media list provided");
424         unlock(p_mlp);
425         return;
426     }
427     if (p_mlp->p_mlist)
428     {
429         uninstall_playlist_observer(p_mlp);
430         libvlc_media_list_release(p_mlp->p_mlist);
431     }
432     libvlc_media_list_retain(p_mlist);
433     p_mlp->p_mlist = p_mlist;
434  
435     install_playlist_observer(p_mlp);
436
437     unlock(p_mlp);
438 }
439
440 /**************************************************************************
441  *        Play (Public)
442  **************************************************************************/
443 void libvlc_media_list_player_play(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
444 {
445     if (!p_mlp->current_playing_item_path)
446     {
447         libvlc_media_list_player_next(p_mlp, p_e);
448         return; /* Will set to play */
449     }
450
451     libvlc_media_player_play(p_mlp->p_mi, p_e);
452 }
453
454
455 /**************************************************************************
456  *        Pause (Public)
457  **************************************************************************/
458 void libvlc_media_list_player_pause(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
459 {
460     if (!p_mlp->p_mi)
461         return;
462     libvlc_media_player_pause(p_mlp->p_mi, p_e);
463 }
464
465 /**************************************************************************
466  *        is_playing (Public)
467  **************************************************************************/
468 int
469 libvlc_media_list_player_is_playing(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
470 {
471     libvlc_state_t state = libvlc_media_player_get_state(p_mlp->p_mi, p_e);
472     return (state == libvlc_Opening) || (state == libvlc_Buffering) ||
473            (state == libvlc_Playing);
474 }
475
476 /**************************************************************************
477  *        State (Public)
478  **************************************************************************/
479 libvlc_state_t
480 libvlc_media_list_player_get_state(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
481 {
482     if (!p_mlp->p_mi)
483         return libvlc_Ended;
484     return libvlc_media_player_get_state(p_mlp->p_mi, p_e);
485 }
486
487 /**************************************************************************
488  *        Play item at index (Public)
489  **************************************************************************/
490 void libvlc_media_list_player_play_item_at_index(libvlc_media_list_player_t * p_mlp, int i_index, libvlc_exception_t * p_e)
491 {
492     VLC_UNUSED(p_e);
493     set_current_playing_item(p_mlp, libvlc_media_list_path_with_root_index(i_index));
494
495     /* Send the next item event */
496     libvlc_event_t event;
497     event.type = libvlc_MediaListPlayerNextItemSet;
498     libvlc_event_send(p_mlp->p_event_manager, &event);
499
500     libvlc_media_player_play(p_mlp->p_mi, p_e);
501 }
502
503 /**************************************************************************
504  *        Play item (Public)
505  **************************************************************************/
506 void libvlc_media_list_player_play_item(libvlc_media_list_player_t * p_mlp, libvlc_media_t * p_md, libvlc_exception_t * p_e)
507 {
508     libvlc_media_list_path_t path = libvlc_media_list_path_of_item(p_mlp->p_mlist, p_md);
509     if (!path)
510     {
511         libvlc_exception_raise(p_e, "No such item in media list");
512         return;
513     }
514     set_current_playing_item(p_mlp, path);
515     libvlc_media_player_play(p_mlp->p_mi, p_e);
516 }
517
518 /**************************************************************************
519  *       Stop (Private)
520  *
521  * Lock must be held.
522  **************************************************************************/
523 static void stop(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
524 {
525     assert_locked(p_mlp);
526
527     if (p_mlp->p_mi && p_mlp->current_playing_item_path)
528     {
529         /* We are not interested in getting media stop event now */
530         uninstall_media_player_observer(p_mlp);
531         libvlc_media_player_stop(p_mlp->p_mi, p_e);
532         install_media_player_observer(p_mlp);
533     }
534
535     free(p_mlp->current_playing_item_path);
536     p_mlp->current_playing_item_path = NULL;
537 }
538
539 /**************************************************************************
540  *       Stop (Public)
541  **************************************************************************/
542 void libvlc_media_list_player_stop(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
543 {
544     lock(p_mlp);
545     stop(p_mlp, p_e);
546     unlock(p_mlp);
547 }
548
549 /**************************************************************************
550  *       Next (Private)
551  *
552  * Lock must be held.
553  **************************************************************************/
554 static void next(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
555 {
556     assert_locked(p_mlp);
557
558     if (!p_mlp->p_mlist)
559     {
560         libvlc_exception_raise(p_e, "No media list");
561         return;
562     }
563
564     libvlc_media_list_lock(p_mlp->p_mlist);
565
566     libvlc_media_list_path_t path = get_next_path(p_mlp);
567
568 #ifdef DEBUG_MEDIA_LIST_PLAYER
569     printf("Playing:");
570     libvlc_media_list_path_dump(path);
571 #endif
572
573     set_current_playing_item(p_mlp, path);
574
575     if (!path)
576     {
577         libvlc_media_list_unlock(p_mlp->p_mlist);
578         stop(p_mlp, p_e);
579         return;
580     }
581
582     libvlc_media_player_play(p_mlp->p_mi, p_e);
583
584     libvlc_media_list_unlock(p_mlp->p_mlist);
585
586     /* Send the next item event */
587     libvlc_event_t event;
588     event.type = libvlc_MediaListPlayerNextItemSet;
589     libvlc_event_send(p_mlp->p_event_manager, &event);
590 }
591
592 /**************************************************************************
593  *       Next (Public)
594  **************************************************************************/
595 void libvlc_media_list_player_next(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
596 {
597     lock(p_mlp);
598     next(p_mlp, p_e);
599     unlock(p_mlp);
600 }
601