media_list_player: Strictier lock in libvlc_media_list_player_play*().
[vlc.git] / 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 = false;
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
319     libvlc_media_player_set_media(p_mlp->p_mi, p_md, NULL);
320
321     install_media_player_observer(p_mlp);
322     libvlc_media_release(p_md); /* for libvlc_media_list_item_at_index */
323 }
324
325 /*
326  * Public libvlc functions
327  */
328
329 /**************************************************************************
330  *         new (Public)
331  **************************************************************************/
332 libvlc_media_list_player_t *
333 libvlc_media_list_player_new(libvlc_instance_t * p_instance, libvlc_exception_t * p_e)
334 {
335     (void)p_e;
336     libvlc_media_list_player_t * p_mlp;
337     p_mlp = calloc(sizeof(libvlc_media_list_player_t), 1);
338     if (!p_mlp)
339         return NULL;
340
341     libvlc_retain(p_instance);
342     p_mlp->p_libvlc_instance = p_instance;
343     p_mlp->i_refcount = 1;
344     vlc_mutex_init(&p_mlp->object_lock);
345     vlc_mutex_init(&p_mlp->mp_callback_lock);
346     p_mlp->p_event_manager = libvlc_event_manager_new(p_mlp, p_instance, p_e);
347     libvlc_event_manager_register_event_type(p_mlp->p_event_manager, libvlc_MediaListPlayerNextItemSet, p_e);
348
349     return p_mlp;
350 }
351
352 /**************************************************************************
353  *         release (Public)
354  **************************************************************************/
355 void libvlc_media_list_player_release(libvlc_media_list_player_t * p_mlp)
356 {
357     if (!p_mlp)
358         return;
359
360     lock(p_mlp);
361     p_mlp->i_refcount--;
362     if (p_mlp->i_refcount > 0)
363     {
364         unlock(p_mlp);
365         return;
366     }
367
368     assert(p_mlp->i_refcount == 0);
369
370     /* Keep the lock(), because the uninstall functions
371      * check for it. That's convenient. */
372
373     if (p_mlp->p_mi)
374     {
375         uninstall_media_player_observer(p_mlp);
376         libvlc_media_player_release(p_mlp->p_mi);
377     }    
378     if (p_mlp->p_mlist)
379     {
380         uninstall_playlist_observer(p_mlp);
381         libvlc_media_list_release(p_mlp->p_mlist);
382     }
383
384     unlock(p_mlp);
385     vlc_mutex_destroy(&p_mlp->object_lock);
386     vlc_mutex_destroy(&p_mlp->mp_callback_lock);
387
388     libvlc_event_manager_release(p_mlp->p_event_manager);
389     
390     free(p_mlp->current_playing_item_path);
391     libvlc_release(p_mlp->p_libvlc_instance);
392     free(p_mlp);
393 }
394
395 /**************************************************************************
396  *        event_manager (Public)
397  **************************************************************************/
398 libvlc_event_manager_t *
399 libvlc_media_list_player_event_manager(libvlc_media_list_player_t * p_mlp)
400 {
401     return p_mlp->p_event_manager;
402 }
403
404 /**************************************************************************
405  *        set_media_player (Public)
406  **************************************************************************/
407 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)
408 {
409     VLC_UNUSED(p_e);
410
411     lock(p_mlp);
412
413     if (p_mlp->p_mi)
414     {
415         uninstall_media_player_observer(p_mlp);
416         libvlc_media_player_release(p_mlp->p_mi);
417     }
418     libvlc_media_player_retain(p_mi);
419     p_mlp->p_mi = p_mi;
420
421     install_media_player_observer(p_mlp);
422
423     unlock(p_mlp);
424 }
425
426 /**************************************************************************
427  *       set_media_list (Public)
428  **************************************************************************/
429 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)
430 {
431     lock(p_mlp);
432
433     if (!p_mlist)
434     {
435         libvlc_exception_raise(p_e, "No media list provided");
436         unlock(p_mlp);
437         return;
438     }
439     if (p_mlp->p_mlist)
440     {
441         uninstall_playlist_observer(p_mlp);
442         libvlc_media_list_release(p_mlp->p_mlist);
443     }
444     libvlc_media_list_retain(p_mlist);
445     p_mlp->p_mlist = p_mlist;
446  
447     install_playlist_observer(p_mlp);
448
449     unlock(p_mlp);
450 }
451
452 /**************************************************************************
453  *        Play (Public)
454  **************************************************************************/
455 void libvlc_media_list_player_play(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
456 {
457     if (!p_mlp->current_playing_item_path)
458     {
459         libvlc_media_list_player_next(p_mlp, p_e);
460         return; /* Will set to play */
461     }
462
463     libvlc_media_player_play(p_mlp->p_mi, p_e);
464 }
465
466
467 /**************************************************************************
468  *        Pause (Public)
469  **************************************************************************/
470 void libvlc_media_list_player_pause(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
471 {
472     if (!p_mlp->p_mi)
473         return;
474     libvlc_media_player_pause(p_mlp->p_mi, p_e);
475 }
476
477 /**************************************************************************
478  *        is_playing (Public)
479  **************************************************************************/
480 int
481 libvlc_media_list_player_is_playing(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
482 {
483     libvlc_state_t state = libvlc_media_player_get_state(p_mlp->p_mi, p_e);
484     return (state == libvlc_Opening) || (state == libvlc_Buffering) ||
485            (state == libvlc_Playing);
486 }
487
488 /**************************************************************************
489  *        State (Public)
490  **************************************************************************/
491 libvlc_state_t
492 libvlc_media_list_player_get_state(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
493 {
494     if (!p_mlp->p_mi)
495         return libvlc_Ended;
496     return libvlc_media_player_get_state(p_mlp->p_mi, p_e);
497 }
498
499 /**************************************************************************
500  *        Play item at index (Public)
501  **************************************************************************/
502 void libvlc_media_list_player_play_item_at_index(libvlc_media_list_player_t * p_mlp, int i_index, libvlc_exception_t * p_e)
503 {
504     VLC_UNUSED(p_e);
505
506     lock(p_mlp);
507     set_current_playing_item(p_mlp, libvlc_media_list_path_with_root_index(i_index));
508     libvlc_media_player_play(p_mlp->p_mi, p_e);
509     unlock(p_mlp);
510
511     /* Send the next item event */
512     libvlc_event_t event;
513     event.type = libvlc_MediaListPlayerNextItemSet;
514     libvlc_event_send(p_mlp->p_event_manager, &event);
515 }
516
517 /**************************************************************************
518  *        Play item (Public)
519  **************************************************************************/
520 void libvlc_media_list_player_play_item(libvlc_media_list_player_t * p_mlp, libvlc_media_t * p_md, libvlc_exception_t * p_e)
521 {
522     lock(p_mlp);
523     libvlc_media_list_path_t path = libvlc_media_list_path_of_item(p_mlp->p_mlist, p_md);
524     if (!path)
525     {
526         libvlc_exception_raise(p_e, "No such item in media list");
527         unlock(p_mlp);
528         return;
529     }
530     
531     set_current_playing_item(p_mlp, path);    
532     libvlc_media_player_play(p_mlp->p_mi, p_e);
533     unlock(p_mlp);
534 }
535
536 /**************************************************************************
537  *       Stop (Private)
538  *
539  * Lock must be held.
540  **************************************************************************/
541 static void stop(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
542 {
543     assert_locked(p_mlp);
544
545     if (p_mlp->p_mi && p_mlp->current_playing_item_path)
546     {
547         /* We are not interested in getting media stop event now */
548         uninstall_media_player_observer(p_mlp);
549         libvlc_media_player_stop(p_mlp->p_mi, p_e);
550         install_media_player_observer(p_mlp);
551     }
552
553     free(p_mlp->current_playing_item_path);
554     p_mlp->current_playing_item_path = NULL;
555 }
556
557 /**************************************************************************
558  *       Stop (Public)
559  **************************************************************************/
560 void libvlc_media_list_player_stop(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
561 {
562     lock(p_mlp);
563     stop(p_mlp, p_e);
564     unlock(p_mlp);
565 }
566
567 /**************************************************************************
568  *       Next (Private)
569  *
570  * Lock must be held.
571  **************************************************************************/
572 static void next(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
573 {
574     assert_locked(p_mlp);
575
576     if (!p_mlp->p_mlist)
577     {
578         libvlc_exception_raise(p_e, "No media list");
579         return;
580     }
581
582     libvlc_media_list_lock(p_mlp->p_mlist);
583
584     libvlc_media_list_path_t path = get_next_path(p_mlp);
585
586 #ifdef DEBUG_MEDIA_LIST_PLAYER
587     printf("Playing:");
588     libvlc_media_list_path_dump(path);
589 #endif
590
591     set_current_playing_item(p_mlp, path);
592
593     if (!path)
594     {
595         libvlc_media_list_unlock(p_mlp->p_mlist);
596         stop(p_mlp, p_e);
597         return;
598     }
599
600     libvlc_media_player_play(p_mlp->p_mi, p_e);
601
602     libvlc_media_list_unlock(p_mlp->p_mlist);
603
604     /* Send the next item event */
605     libvlc_event_t event;
606     event.type = libvlc_MediaListPlayerNextItemSet;
607     libvlc_media_t * p_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, path);
608     event.u.media_list_player_next_item_set.item = p_md;
609     libvlc_event_send(p_mlp->p_event_manager, &event);
610     libvlc_media_release(p_md);
611 }
612
613 /**************************************************************************
614  *       Next (Public)
615  **************************************************************************/
616 void libvlc_media_list_player_next(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
617 {
618     lock(p_mlp);
619     next(p_mlp, p_e);
620     unlock(p_mlp);
621 }
622