1 /*****************************************************************************
2 * media_list_player.c: libvlc new API media_list player functions
3 *****************************************************************************
4 * Copyright (C) 2007 the VideoLAN team
7 * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
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.
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.
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 *****************************************************************************/
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>
32 #include "libvlc_internal.h"
34 #include "media_internal.h" // Abuse, could and should be removed
35 #include "media_list_path.h"
37 //#define DEBUG_MEDIA_LIST_PLAYER
39 /* This is a very dummy implementation of playlist on top of
40 * media_list and media_player.
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().
47 * This is thread safe, and we use a two keys (locks) scheme
48 * to discriminate between callbacks and regular uses.
51 struct libvlc_media_list_player_t
53 libvlc_event_manager_t * p_event_manager;
54 libvlc_instance_t * p_libvlc_instance;
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 libvlc_playback_mode_t e_playback_mode;
69 /* This is not yet exported by libvlccore */
70 static inline void vlc_assert_locked(vlc_mutex_t *mutex)
79 static void set_relative_playlist_position_and_play(
80 libvlc_media_list_player_t * p_mlp,
81 int i_relative_position,
82 libvlc_exception_t * p_e);
83 static void stop(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e);
89 /**************************************************************************
91 **************************************************************************/
92 static inline void lock(libvlc_media_list_player_t * p_mlp)
94 // Obtain an access to this structure
95 vlc_mutex_lock(&p_mlp->object_lock);
97 // Make sure no callback will occurs at the same time
98 vlc_mutex_lock(&p_mlp->mp_callback_lock);
101 static inline void unlock(libvlc_media_list_player_t * p_mlp)
103 vlc_mutex_unlock(&p_mlp->mp_callback_lock);
104 vlc_mutex_unlock(&p_mlp->object_lock);
107 static inline void assert_locked(libvlc_media_list_player_t * p_mlp)
109 vlc_assert_locked(&p_mlp->mp_callback_lock);
112 static inline libvlc_event_manager_t * mlist_em(libvlc_media_list_player_t * p_mlp)
114 assert_locked(p_mlp);
115 return libvlc_media_list_event_manager(p_mlp->p_mlist, NULL);
118 static inline libvlc_event_manager_t * mplayer_em(libvlc_media_list_player_t * p_mlp)
120 assert_locked(p_mlp);
121 return libvlc_media_player_event_manager(p_mlp->p_mi, NULL);
124 /**************************************************************************
125 * get_next_path (private)
127 * Returns the path to the next item in the list.
128 * If looping is specified and the current item is the last list item in
129 * the list it will return the first item in the list.
130 **************************************************************************/
131 static libvlc_media_list_path_t
132 get_next_path(libvlc_media_list_player_t * p_mlp, bool b_loop)
134 assert_locked(p_mlp);
136 /* We are entered with libvlc_media_list_lock(p_mlp->p_list) */
137 libvlc_media_list_path_t ret;
138 libvlc_media_list_t * p_parent_of_playing_item;
139 libvlc_media_list_t * p_sublist_of_playing_item;
141 if (!p_mlp->current_playing_item_path)
143 if (!libvlc_media_list_count(p_mlp->p_mlist, NULL))
145 return libvlc_media_list_path_with_root_index(0);
148 p_sublist_of_playing_item = libvlc_media_list_sublist_at_path(
150 p_mlp->current_playing_item_path);
152 /* If item just gained a sublist just play it */
153 if (p_sublist_of_playing_item)
155 libvlc_media_list_release(p_sublist_of_playing_item);
156 return libvlc_media_list_path_copy_by_appending(p_mlp->current_playing_item_path, 0);
159 /* Try to catch parent element */
160 p_parent_of_playing_item = libvlc_media_list_parentlist_at_path(p_mlp->p_mlist,
161 p_mlp->current_playing_item_path);
163 int depth = libvlc_media_list_path_depth(p_mlp->current_playing_item_path);
164 if (depth < 1 || !p_parent_of_playing_item)
167 ret = libvlc_media_list_path_copy(p_mlp->current_playing_item_path);
168 ret[depth - 1]++; /* set to next element */
170 /* If this goes beyond the end of the list */
171 while(ret[depth-1] >= libvlc_media_list_count(p_parent_of_playing_item, NULL))
185 libvlc_media_list_release(p_parent_of_playing_item);
191 p_parent_of_playing_item = libvlc_media_list_parentlist_at_path(
196 libvlc_media_list_release(p_parent_of_playing_item);
200 /**************************************************************************
201 * find_last_item (private)
203 * Returns the path of the last descendant of a given item path.
204 * Note: Due to the recursive nature of the function and the need to free
205 * media list paths, paths passed in may be freed if they are replaced.
206 Recommended usage is to set return value to the same path that was
207 passed to the function (i.e. item = find_last_item(list, item); )
208 **************************************************************************/
209 static libvlc_media_list_path_t
210 find_last_item( libvlc_media_list_t * p_mlist, libvlc_media_list_path_t current_item )
212 libvlc_media_list_t * p_sublist = libvlc_media_list_sublist_at_path(p_mlist, current_item);
213 libvlc_media_list_path_t last_item_path = current_item;
217 int i_count = libvlc_media_list_count(p_sublist, NULL);
220 /* Add the last sublist item to the path. */
221 last_item_path = libvlc_media_list_path_copy_by_appending(current_item, i_count - 1);
223 /* Check that sublist item for more descendants. */
224 last_item_path = find_last_item(p_mlist, last_item_path);
227 libvlc_media_list_release(p_sublist);
230 return last_item_path;
233 /**************************************************************************
234 * get_previous_path (private)
236 * Returns the path to the preceding item in the list.
237 * If looping is specified and the current item is the first list item in
238 * the list it will return the last descendant of the last item in the list.
239 **************************************************************************/
240 static libvlc_media_list_path_t
241 get_previous_path(libvlc_media_list_player_t * p_mlp, bool b_loop)
243 assert_locked(p_mlp);
245 /* We are entered with libvlc_media_list_lock(p_mlp->p_list) */
246 libvlc_media_list_path_t ret;
247 libvlc_media_list_t * p_parent_of_playing_item;
249 if (!p_mlp->current_playing_item_path)
251 if (!libvlc_media_list_count(p_mlp->p_mlist, NULL))
253 return libvlc_media_list_path_with_root_index(0);
256 /* Try to catch parent element */
257 p_parent_of_playing_item = libvlc_media_list_parentlist_at_path(
259 p_mlp->current_playing_item_path);
261 int depth = libvlc_media_list_path_depth(p_mlp->current_playing_item_path);
262 if (depth < 1 || !p_parent_of_playing_item)
265 /* Set the return path to the current path */
266 ret = libvlc_media_list_path_copy(p_mlp->current_playing_item_path);
268 /* Change the return path to the previous list entry */
269 ret[depth - 1]--; /* set to previous element */
272 /* Is the return path is beyond the start of the current list? */
273 if(ret[depth - 1] < 0)
275 /* Move to parent of current item */
278 /* Are we at the root level of the tree? */
281 // Is looping enabled?
284 int i_count = libvlc_media_list_count(p_parent_of_playing_item, NULL);
286 /* Set current play item to the last element in the list */
287 ret[0] = i_count - 1;
290 /* Set the return path to the last descendant item of the current item */
291 ret = find_last_item(p_mlp->p_mlist, ret);
295 /* No looping so return empty path. */
302 /* This is the case of moving backward from the beginning of the
303 * subitem list to its parent item.
304 * This ensures that current path is properly terminated to
312 ret = find_last_item(p_mlp->p_mlist, ret);
315 libvlc_media_list_release(p_parent_of_playing_item);
319 /**************************************************************************
320 * media_player_reached_end (private) (Event Callback)
321 **************************************************************************/
323 media_player_reached_end(const libvlc_event_t * p_event, void * p_user_data)
326 libvlc_media_list_player_t * p_mlp = p_user_data;
327 libvlc_exception_t e;
328 libvlc_exception_init(&e);
330 vlc_mutex_lock(&p_mlp->mp_callback_lock);
331 if (!p_mlp->are_mp_callback_cancelled)
332 set_relative_playlist_position_and_play(p_mlp, 1, &e);
333 vlc_mutex_unlock(&p_mlp->mp_callback_lock);
335 // There is no point in reporting an error from this callback
336 libvlc_exception_clear(&e);
339 /**************************************************************************
340 * playlist_item_deleted (private) (Event Callback)
341 **************************************************************************/
343 mlist_item_deleted(const libvlc_event_t * p_event, void * p_user_data)
345 libvlc_media_list_player_t * p_mlp = p_user_data;
346 libvlc_media_list_t * p_emitting_mlist = p_event->p_obj;
347 libvlc_media_t * p_current_md = NULL;
350 if (p_mlp->current_playing_item_path)
351 p_current_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, p_mlp->current_playing_item_path);
353 if (p_event->u.media_list_item_deleted.item == p_current_md &&
354 p_emitting_mlist == p_mlp->p_mlist)
356 /* We are playing this item, let's stop */
363 /**************************************************************************
364 * install_playlist_observer (private)
365 **************************************************************************/
367 install_playlist_observer(libvlc_media_list_player_t * p_mlp)
369 assert_locked(p_mlp);
370 libvlc_event_attach(mlist_em(p_mlp), libvlc_MediaListItemDeleted, mlist_item_deleted, p_mlp, NULL);
373 /**************************************************************************
374 * uninstall_playlist_observer (private)
375 **************************************************************************/
377 uninstall_playlist_observer(libvlc_media_list_player_t * p_mlp)
379 assert_locked(p_mlp);
380 if (!p_mlp->p_mlist) return;
381 libvlc_event_detach(mlist_em(p_mlp), libvlc_MediaListItemDeleted, mlist_item_deleted, p_mlp, NULL);
384 /**************************************************************************
385 * install_media_player_observer (private)
386 **************************************************************************/
388 install_media_player_observer(libvlc_media_list_player_t * p_mlp)
390 assert_locked(p_mlp);
391 libvlc_event_attach_async(mplayer_em(p_mlp), libvlc_MediaPlayerEndReached, media_player_reached_end, p_mlp, NULL);
395 /**************************************************************************
396 * uninstall_media_player_observer (private)
397 **************************************************************************/
399 uninstall_media_player_observer(libvlc_media_list_player_t * p_mlp)
401 assert_locked(p_mlp);
402 if (!p_mlp->p_mi) return;
404 // From now on, media_player callback won't be relevant.
405 p_mlp->are_mp_callback_cancelled = true;
407 // Allow callbacks to run, because detach() will wait until all callbacks are processed.
408 // This is safe because only callbacks are allowed, and there execution will be cancelled.
409 vlc_mutex_unlock(&p_mlp->mp_callback_lock);
410 libvlc_event_detach(mplayer_em(p_mlp), libvlc_MediaPlayerEndReached, media_player_reached_end, p_mlp, NULL);
412 // Now, lock back the callback lock. No more callback will be present from this point.
413 vlc_mutex_lock(&p_mlp->mp_callback_lock);
414 p_mlp->are_mp_callback_cancelled = false;
416 // What is here is safe, because we guarantee that we won't be able to anything concurrently,
417 // - except (cancelled) callbacks - thanks to the object_lock.
420 /**************************************************************************
421 * set_current_playing_item (private)
423 * Playlist lock should be held
424 **************************************************************************/
426 set_current_playing_item(libvlc_media_list_player_t * p_mlp, libvlc_media_list_path_t path)
428 assert_locked(p_mlp);
430 /* First, save the new path that we are going to play */
431 if (p_mlp->current_playing_item_path != path)
433 free(p_mlp->current_playing_item_path);
434 p_mlp->current_playing_item_path = path;
440 libvlc_media_t * p_md;
441 p_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, path);
445 /* Make sure media_player_reached_end() won't get called */
446 uninstall_media_player_observer(p_mlp);
448 /* Create a new media_player if there is none */
450 p_mlp->p_mi = libvlc_media_player_new_from_media(p_md, NULL);
452 libvlc_media_player_set_media(p_mlp->p_mi, p_md, NULL);
454 install_media_player_observer(p_mlp);
455 libvlc_media_release(p_md); /* for libvlc_media_list_item_at_index */
459 * Public libvlc functions
462 /**************************************************************************
464 **************************************************************************/
465 libvlc_media_list_player_t *
466 libvlc_media_list_player_new(libvlc_instance_t * p_instance, libvlc_exception_t * p_e)
469 libvlc_media_list_player_t * p_mlp;
470 p_mlp = calloc( 1, sizeof(libvlc_media_list_player_t) );
474 libvlc_retain(p_instance);
475 p_mlp->p_libvlc_instance = p_instance;
476 p_mlp->i_refcount = 1;
477 vlc_mutex_init(&p_mlp->object_lock);
478 vlc_mutex_init(&p_mlp->mp_callback_lock);
479 p_mlp->p_event_manager = libvlc_event_manager_new(p_mlp, p_instance, p_e);
480 libvlc_event_manager_register_event_type(p_mlp->p_event_manager, libvlc_MediaListPlayerNextItemSet, p_e);
481 p_mlp->e_playback_mode = libvlc_playback_mode_default;
486 /**************************************************************************
488 **************************************************************************/
489 void libvlc_media_list_player_release(libvlc_media_list_player_t * p_mlp)
496 if (p_mlp->i_refcount > 0)
502 assert(p_mlp->i_refcount == 0);
504 /* Keep the lock(), because the uninstall functions
505 * check for it. That's convenient. */
509 uninstall_media_player_observer(p_mlp);
510 libvlc_media_player_release(p_mlp->p_mi);
514 uninstall_playlist_observer(p_mlp);
515 libvlc_media_list_release(p_mlp->p_mlist);
519 vlc_mutex_destroy(&p_mlp->object_lock);
520 vlc_mutex_destroy(&p_mlp->mp_callback_lock);
522 libvlc_event_manager_release(p_mlp->p_event_manager);
524 free(p_mlp->current_playing_item_path);
525 libvlc_release(p_mlp->p_libvlc_instance);
529 /**************************************************************************
530 * event_manager (Public)
531 **************************************************************************/
532 libvlc_event_manager_t *
533 libvlc_media_list_player_event_manager(libvlc_media_list_player_t * p_mlp)
535 return p_mlp->p_event_manager;
538 /**************************************************************************
539 * set_media_player (Public)
540 **************************************************************************/
541 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)
549 uninstall_media_player_observer(p_mlp);
550 libvlc_media_player_release(p_mlp->p_mi);
552 libvlc_media_player_retain(p_mi);
555 install_media_player_observer(p_mlp);
560 /**************************************************************************
561 * set_media_list (Public)
562 **************************************************************************/
563 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)
569 libvlc_exception_raise(p_e, "No media list provided");
575 uninstall_playlist_observer(p_mlp);
576 libvlc_media_list_release(p_mlp->p_mlist);
578 libvlc_media_list_retain(p_mlist);
579 p_mlp->p_mlist = p_mlist;
581 install_playlist_observer(p_mlp);
586 /**************************************************************************
588 **************************************************************************/
589 void libvlc_media_list_player_play(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
592 if (!p_mlp->current_playing_item_path)
594 set_relative_playlist_position_and_play(p_mlp, 1, p_e);
596 return; /* Will set to play */
598 libvlc_media_player_play(p_mlp->p_mi, p_e);
603 /**************************************************************************
605 **************************************************************************/
606 void libvlc_media_list_player_pause(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
614 libvlc_media_player_pause(p_mlp->p_mi, p_e);
618 /**************************************************************************
619 * is_playing (Public)
620 **************************************************************************/
622 libvlc_media_list_player_is_playing(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
624 libvlc_state_t state = libvlc_media_player_get_state(p_mlp->p_mi, p_e);
625 return (state == libvlc_Opening) || (state == libvlc_Buffering) ||
626 (state == libvlc_Playing);
629 /**************************************************************************
631 **************************************************************************/
633 libvlc_media_list_player_get_state(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
637 return libvlc_media_player_get_state(p_mlp->p_mi, p_e);
640 /**************************************************************************
641 * Play item at index (Public)
642 **************************************************************************/
643 void libvlc_media_list_player_play_item_at_index(libvlc_media_list_player_t * p_mlp, int i_index, libvlc_exception_t * p_e)
648 set_current_playing_item(p_mlp, libvlc_media_list_path_with_root_index(i_index));
649 libvlc_media_player_play(p_mlp->p_mi, p_e);
652 /* Send the next item event */
653 libvlc_event_t event;
654 event.type = libvlc_MediaListPlayerNextItemSet;
655 libvlc_event_send(p_mlp->p_event_manager, &event);
658 /**************************************************************************
660 **************************************************************************/
661 void libvlc_media_list_player_play_item(libvlc_media_list_player_t * p_mlp, libvlc_media_t * p_md, libvlc_exception_t * p_e)
664 libvlc_media_list_path_t path = libvlc_media_list_path_of_item(p_mlp->p_mlist, p_md);
667 libvlc_exception_raise(p_e, "No such item in media list");
672 set_current_playing_item(p_mlp, path);
673 libvlc_media_player_play(p_mlp->p_mi, p_e);
677 /**************************************************************************
681 **************************************************************************/
682 static void stop(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
684 assert_locked(p_mlp);
686 if (p_mlp->p_mi && p_mlp->current_playing_item_path)
688 /* We are not interested in getting media stop event now */
689 uninstall_media_player_observer(p_mlp);
690 libvlc_media_player_stop(p_mlp->p_mi, p_e);
691 install_media_player_observer(p_mlp);
694 free(p_mlp->current_playing_item_path);
695 p_mlp->current_playing_item_path = NULL;
698 /**************************************************************************
700 **************************************************************************/
701 void libvlc_media_list_player_stop(libvlc_media_list_player_t * p_mlp,
702 libvlc_exception_t * p_e)
709 /**************************************************************************
710 * Set relative playlist position and play (Private)
712 * Sets the currently played item to the given relative play item position
713 * (based on the currently playing item) and then begins the new item playback.
715 **************************************************************************/
716 static void set_relative_playlist_position_and_play(
717 libvlc_media_list_player_t * p_mlp,
718 int i_relative_position,
719 libvlc_exception_t * p_e)
721 assert_locked(p_mlp);
725 libvlc_exception_raise(p_e, "No media list");
729 libvlc_media_list_lock(p_mlp->p_mlist);
731 libvlc_media_list_path_t path = p_mlp->current_playing_item_path;
733 if(p_mlp->e_playback_mode != libvlc_playback_mode_repeat)
735 bool b_loop = (p_mlp->e_playback_mode == libvlc_playback_mode_loop);
737 if(i_relative_position > 0)
741 path = get_next_path(p_mlp, b_loop);
742 set_current_playing_item(p_mlp, path);
743 --i_relative_position;
745 while(i_relative_position > 0);
747 else if(i_relative_position < 0)
751 path = get_previous_path(p_mlp, b_loop);
752 set_current_playing_item(p_mlp, path);
753 ++i_relative_position;
755 while (i_relative_position < 0);
760 set_current_playing_item(p_mlp, path);
763 #ifdef DEBUG_MEDIA_LIST_PLAYER
765 libvlc_media_list_path_dump(path);
770 libvlc_media_list_unlock(p_mlp->p_mlist);
775 libvlc_media_player_play(p_mlp->p_mi, p_e);
777 libvlc_media_list_unlock(p_mlp->p_mlist);
779 /* Send the next item event */
780 libvlc_event_t event;
781 event.type = libvlc_MediaListPlayerNextItemSet;
782 libvlc_media_t * p_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, path);
783 event.u.media_list_player_next_item_set.item = p_md;
784 libvlc_event_send(p_mlp->p_event_manager, &event);
785 libvlc_media_release(p_md);
788 /**************************************************************************
790 **************************************************************************/
791 void libvlc_media_list_player_next(libvlc_media_list_player_t * p_mlp,
792 libvlc_exception_t * p_e)
795 set_relative_playlist_position_and_play(p_mlp, 1, p_e);
799 /**************************************************************************
801 **************************************************************************/
802 void libvlc_media_list_player_previous(libvlc_media_list_player_t * p_mlp,
803 libvlc_exception_t * p_e)
806 set_relative_playlist_position_and_play(p_mlp, -1, p_e);
810 /**************************************************************************
811 * Set Playback Mode (Public)
812 **************************************************************************/
813 void libvlc_media_list_player_set_playback_mode(
814 libvlc_media_list_player_t * p_mlp,
815 libvlc_playback_mode_t e_mode,
816 libvlc_exception_t * p_e )
821 p_mlp->e_playback_mode = e_mode;