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 *****************************************************************************/
28 #include <vlc/libvlc.h>
29 #include <vlc/libvlc_media.h>
30 #include <vlc/libvlc_media_list.h>
31 #include <vlc/libvlc_media_player.h>
32 #include <vlc/libvlc_media_list_player.h>
33 #include <vlc/libvlc_events.h>
36 #include "libvlc_internal.h"
38 #include "media_internal.h" // Abuse, could and should be removed
39 #include "media_list_path.h"
41 //#define DEBUG_MEDIA_LIST_PLAYER
43 /* This is a very dummy implementation of playlist on top of
44 * media_list and media_player.
46 * All this code is doing is simply computing the next item
47 * of a tree of media_list (see get_next_index()), and play
48 * the next item when the current is over. This is happening
49 * via the event callback media_player_reached_end().
51 * This is thread safe, and we use a two keys (locks) scheme
52 * to discriminate between callbacks and regular uses.
55 struct libvlc_media_list_player_t
57 libvlc_event_manager_t * p_event_manager;
58 libvlc_instance_t * p_libvlc_instance;
60 /* Protect access to this structure. */
61 vlc_mutex_t object_lock;
62 /* Protect access to this structure and from callback execution. */
63 vlc_mutex_t mp_callback_lock;
64 /* Indicate to media player callbacks that they are cancelled. */
65 bool are_mp_callback_cancelled;
66 libvlc_media_list_path_t current_playing_item_path;
67 libvlc_media_t * p_current_playing_item;
68 libvlc_media_list_t * p_mlist;
69 libvlc_media_player_t * p_mi;
70 libvlc_playback_mode_t e_playback_mode;
73 /* This is not yet exported by libvlccore */
74 static inline void vlc_assert_locked(vlc_mutex_t *mutex)
83 static void set_relative_playlist_position_and_play(
84 libvlc_media_list_player_t * p_mlp,
85 int i_relative_position,
86 libvlc_exception_t * p_e);
87 static void stop(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e);
93 /**************************************************************************
95 **************************************************************************/
96 static inline void lock(libvlc_media_list_player_t * p_mlp)
98 // Obtain an access to this structure
99 vlc_mutex_lock(&p_mlp->object_lock);
101 // Make sure no callback will occurs at the same time
102 vlc_mutex_lock(&p_mlp->mp_callback_lock);
105 static inline void unlock(libvlc_media_list_player_t * p_mlp)
107 vlc_mutex_unlock(&p_mlp->mp_callback_lock);
108 vlc_mutex_unlock(&p_mlp->object_lock);
111 static inline void assert_locked(libvlc_media_list_player_t * p_mlp)
113 vlc_assert_locked(&p_mlp->mp_callback_lock);
116 static inline libvlc_event_manager_t * mlist_em(libvlc_media_list_player_t * p_mlp)
118 assert_locked(p_mlp);
119 return libvlc_media_list_event_manager(p_mlp->p_mlist, NULL);
122 static inline libvlc_event_manager_t * mplayer_em(libvlc_media_list_player_t * p_mlp)
124 assert_locked(p_mlp);
125 return libvlc_media_player_event_manager(p_mlp->p_mi, NULL);
128 /**************************************************************************
129 * get_next_path (private)
131 * Returns the path to the next item in the list.
132 * If looping is specified and the current item is the last list item in
133 * the list it will return the first item in the list.
134 **************************************************************************/
135 static libvlc_media_list_path_t
136 get_next_path(libvlc_media_list_player_t * p_mlp, bool b_loop)
138 assert_locked(p_mlp);
140 /* We are entered with libvlc_media_list_lock(p_mlp->p_list) */
141 libvlc_media_list_path_t ret;
142 libvlc_media_list_t * p_parent_of_playing_item;
143 libvlc_media_list_t * p_sublist_of_playing_item;
145 if (!p_mlp->current_playing_item_path)
147 if (!libvlc_media_list_count(p_mlp->p_mlist, NULL))
149 return libvlc_media_list_path_with_root_index(0);
152 p_sublist_of_playing_item = libvlc_media_list_sublist_at_path(
154 p_mlp->current_playing_item_path);
156 /* If item just gained a sublist just play it */
157 if (p_sublist_of_playing_item)
159 libvlc_media_list_release(p_sublist_of_playing_item);
160 return libvlc_media_list_path_copy_by_appending(p_mlp->current_playing_item_path, 0);
163 /* Try to catch parent element */
164 p_parent_of_playing_item = libvlc_media_list_parentlist_at_path(p_mlp->p_mlist,
165 p_mlp->current_playing_item_path);
167 int depth = libvlc_media_list_path_depth(p_mlp->current_playing_item_path);
168 if (depth < 1 || !p_parent_of_playing_item)
171 ret = libvlc_media_list_path_copy(p_mlp->current_playing_item_path);
172 ret[depth - 1]++; /* set to next element */
174 /* If this goes beyond the end of the list */
175 while(ret[depth-1] >= libvlc_media_list_count(p_parent_of_playing_item, NULL))
189 libvlc_media_list_release(p_parent_of_playing_item);
195 p_parent_of_playing_item = libvlc_media_list_parentlist_at_path(
200 libvlc_media_list_release(p_parent_of_playing_item);
204 /**************************************************************************
205 * find_last_item (private)
207 * Returns the path of the last descendant of a given item path.
208 * Note: Due to the recursive nature of the function and the need to free
209 * media list paths, paths passed in may be freed if they are replaced.
210 Recommended usage is to set return value to the same path that was
211 passed to the function (i.e. item = find_last_item(list, item); )
212 **************************************************************************/
213 static libvlc_media_list_path_t
214 find_last_item( libvlc_media_list_t * p_mlist, libvlc_media_list_path_t current_item )
216 libvlc_media_list_t * p_sublist = libvlc_media_list_sublist_at_path(p_mlist, current_item);
217 libvlc_media_list_path_t last_item_path = current_item;
221 int i_count = libvlc_media_list_count(p_sublist, NULL);
224 /* Add the last sublist item to the path. */
225 last_item_path = libvlc_media_list_path_copy_by_appending(current_item, i_count - 1);
227 /* Check that sublist item for more descendants. */
228 last_item_path = find_last_item(p_mlist, last_item_path);
231 libvlc_media_list_release(p_sublist);
234 return last_item_path;
237 /**************************************************************************
238 * get_previous_path (private)
240 * Returns the path to the preceding item in the list.
241 * If looping is specified and the current item is the first list item in
242 * the list it will return the last descendant of the last item in the list.
243 **************************************************************************/
244 static libvlc_media_list_path_t
245 get_previous_path(libvlc_media_list_player_t * p_mlp, bool b_loop)
247 assert_locked(p_mlp);
249 /* We are entered with libvlc_media_list_lock(p_mlp->p_list) */
250 libvlc_media_list_path_t ret;
251 libvlc_media_list_t * p_parent_of_playing_item;
253 if (!p_mlp->current_playing_item_path)
255 if (!libvlc_media_list_count(p_mlp->p_mlist, NULL))
257 return libvlc_media_list_path_with_root_index(0);
260 /* Try to catch parent element */
261 p_parent_of_playing_item = libvlc_media_list_parentlist_at_path(
263 p_mlp->current_playing_item_path);
265 int depth = libvlc_media_list_path_depth(p_mlp->current_playing_item_path);
266 if (depth < 1 || !p_parent_of_playing_item)
269 /* Set the return path to the current path */
270 ret = libvlc_media_list_path_copy(p_mlp->current_playing_item_path);
272 /* Change the return path to the previous list entry */
273 ret[depth - 1]--; /* set to previous element */
276 /* Is the return path is beyond the start of the current list? */
277 if(ret[depth - 1] < 0)
279 /* Move to parent of current item */
282 /* Are we at the root level of the tree? */
285 // Is looping enabled?
288 int i_count = libvlc_media_list_count(p_parent_of_playing_item, NULL);
290 /* Set current play item to the last element in the list */
291 ret[0] = i_count - 1;
294 /* Set the return path to the last descendant item of the current item */
295 ret = find_last_item(p_mlp->p_mlist, ret);
299 /* No looping so return empty path. */
306 /* This is the case of moving backward from the beginning of the
307 * subitem list to its parent item.
308 * This ensures that current path is properly terminated to
316 ret = find_last_item(p_mlp->p_mlist, ret);
319 libvlc_media_list_release(p_parent_of_playing_item);
323 /**************************************************************************
324 * media_player_reached_end (private) (Event Callback)
325 **************************************************************************/
327 media_player_reached_end(const libvlc_event_t * p_event, void * p_user_data)
330 libvlc_media_list_player_t * p_mlp = p_user_data;
331 libvlc_exception_t e;
332 libvlc_exception_init(&e);
334 vlc_mutex_lock(&p_mlp->mp_callback_lock);
335 if (!p_mlp->are_mp_callback_cancelled)
336 set_relative_playlist_position_and_play(p_mlp, 1, &e);
337 vlc_mutex_unlock(&p_mlp->mp_callback_lock);
339 // There is no point in reporting an error from this callback
340 libvlc_exception_clear(&e);
343 /**************************************************************************
344 * playlist_item_deleted (private) (Event Callback)
345 **************************************************************************/
347 mlist_item_deleted(const libvlc_event_t * p_event, void * p_user_data)
349 libvlc_media_list_player_t * p_mlp = p_user_data;
350 libvlc_media_list_t * p_emitting_mlist = p_event->p_obj;
351 libvlc_media_t * p_current_md = NULL;
354 if (p_mlp->current_playing_item_path)
355 p_current_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, p_mlp->current_playing_item_path);
357 if (p_event->u.media_list_item_deleted.item == p_current_md &&
358 p_emitting_mlist == p_mlp->p_mlist)
360 /* We are playing this item, let's stop */
367 /**************************************************************************
368 * install_playlist_observer (private)
369 **************************************************************************/
371 install_playlist_observer(libvlc_media_list_player_t * p_mlp)
373 assert_locked(p_mlp);
374 libvlc_event_attach(mlist_em(p_mlp), libvlc_MediaListItemDeleted, mlist_item_deleted, p_mlp, NULL);
377 /**************************************************************************
378 * uninstall_playlist_observer (private)
379 **************************************************************************/
381 uninstall_playlist_observer(libvlc_media_list_player_t * p_mlp)
383 assert_locked(p_mlp);
384 if (!p_mlp->p_mlist) return;
385 libvlc_event_detach(mlist_em(p_mlp), libvlc_MediaListItemDeleted, mlist_item_deleted, p_mlp, NULL);
388 /**************************************************************************
389 * install_media_player_observer (private)
390 **************************************************************************/
392 install_media_player_observer(libvlc_media_list_player_t * p_mlp)
394 assert_locked(p_mlp);
395 libvlc_event_attach_async(mplayer_em(p_mlp), libvlc_MediaPlayerEndReached, media_player_reached_end, p_mlp, NULL);
399 /**************************************************************************
400 * uninstall_media_player_observer (private)
401 **************************************************************************/
403 uninstall_media_player_observer(libvlc_media_list_player_t * p_mlp)
405 assert_locked(p_mlp);
406 if (!p_mlp->p_mi) return;
408 // From now on, media_player callback won't be relevant.
409 p_mlp->are_mp_callback_cancelled = true;
411 // Allow callbacks to run, because detach() will wait until all callbacks are processed.
412 // This is safe because only callbacks are allowed, and there execution will be cancelled.
413 vlc_mutex_unlock(&p_mlp->mp_callback_lock);
414 libvlc_event_detach(mplayer_em(p_mlp), libvlc_MediaPlayerEndReached, media_player_reached_end, p_mlp, NULL);
416 // Now, lock back the callback lock. No more callback will be present from this point.
417 vlc_mutex_lock(&p_mlp->mp_callback_lock);
418 p_mlp->are_mp_callback_cancelled = false;
420 // What is here is safe, because we guarantee that we won't be able to anything concurrently,
421 // - except (cancelled) callbacks - thanks to the object_lock.
424 /**************************************************************************
425 * set_current_playing_item (private)
427 * Playlist lock should be held
428 **************************************************************************/
430 set_current_playing_item(libvlc_media_list_player_t * p_mlp, libvlc_media_list_path_t path)
432 assert_locked(p_mlp);
434 /* First, save the new path that we are going to play */
435 if (p_mlp->current_playing_item_path != path)
437 free(p_mlp->current_playing_item_path);
438 p_mlp->current_playing_item_path = path;
444 libvlc_media_t * p_md;
445 p_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, path);
449 /* Make sure media_player_reached_end() won't get called */
450 uninstall_media_player_observer(p_mlp);
452 /* Create a new media_player if there is none */
454 p_mlp->p_mi = libvlc_media_player_new_from_media(p_md, NULL);
456 libvlc_media_player_set_media(p_mlp->p_mi, p_md, NULL);
458 install_media_player_observer(p_mlp);
459 libvlc_media_release(p_md); /* for libvlc_media_list_item_at_index */
463 * Public libvlc functions
466 /**************************************************************************
468 **************************************************************************/
469 libvlc_media_list_player_t *
470 libvlc_media_list_player_new(libvlc_instance_t * p_instance, libvlc_exception_t * p_e)
473 libvlc_media_list_player_t * p_mlp;
474 p_mlp = calloc( 1, sizeof(libvlc_media_list_player_t) );
478 libvlc_retain(p_instance);
479 p_mlp->p_libvlc_instance = p_instance;
480 p_mlp->i_refcount = 1;
481 vlc_mutex_init(&p_mlp->object_lock);
482 vlc_mutex_init(&p_mlp->mp_callback_lock);
483 p_mlp->p_event_manager = libvlc_event_manager_new(p_mlp, p_instance, p_e);
484 libvlc_event_manager_register_event_type(p_mlp->p_event_manager, libvlc_MediaListPlayerNextItemSet, p_e);
485 p_mlp->e_playback_mode = libvlc_playback_mode_default;
490 /**************************************************************************
492 **************************************************************************/
493 void libvlc_media_list_player_release(libvlc_media_list_player_t * p_mlp)
500 if (p_mlp->i_refcount > 0)
506 assert(p_mlp->i_refcount == 0);
508 /* Keep the lock(), because the uninstall functions
509 * check for it. That's convenient. */
513 uninstall_media_player_observer(p_mlp);
514 libvlc_media_player_release(p_mlp->p_mi);
518 uninstall_playlist_observer(p_mlp);
519 libvlc_media_list_release(p_mlp->p_mlist);
523 vlc_mutex_destroy(&p_mlp->object_lock);
524 vlc_mutex_destroy(&p_mlp->mp_callback_lock);
526 libvlc_event_manager_release(p_mlp->p_event_manager);
528 free(p_mlp->current_playing_item_path);
529 libvlc_release(p_mlp->p_libvlc_instance);
533 /**************************************************************************
534 * event_manager (Public)
535 **************************************************************************/
536 libvlc_event_manager_t *
537 libvlc_media_list_player_event_manager(libvlc_media_list_player_t * p_mlp)
539 return p_mlp->p_event_manager;
542 /**************************************************************************
543 * set_media_player (Public)
544 **************************************************************************/
545 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)
553 uninstall_media_player_observer(p_mlp);
554 libvlc_media_player_release(p_mlp->p_mi);
556 libvlc_media_player_retain(p_mi);
559 install_media_player_observer(p_mlp);
564 /**************************************************************************
565 * set_media_list (Public)
566 **************************************************************************/
567 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)
574 uninstall_playlist_observer(p_mlp);
575 libvlc_media_list_release(p_mlp->p_mlist);
577 libvlc_media_list_retain(p_mlist);
578 p_mlp->p_mlist = p_mlist;
580 install_playlist_observer(p_mlp);
585 /**************************************************************************
587 **************************************************************************/
588 void libvlc_media_list_player_play(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
591 if (!p_mlp->current_playing_item_path)
593 set_relative_playlist_position_and_play(p_mlp, 1, p_e);
595 return; /* Will set to play */
597 libvlc_media_player_play(p_mlp->p_mi, p_e);
602 /**************************************************************************
604 **************************************************************************/
605 void libvlc_media_list_player_pause(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
613 libvlc_media_player_pause(p_mlp->p_mi, p_e);
617 /**************************************************************************
618 * is_playing (Public)
619 **************************************************************************/
621 libvlc_media_list_player_is_playing(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
623 libvlc_state_t state = libvlc_media_player_get_state(p_mlp->p_mi, p_e);
624 return (state == libvlc_Opening) || (state == libvlc_Buffering) ||
625 (state == libvlc_Playing);
628 /**************************************************************************
630 **************************************************************************/
632 libvlc_media_list_player_get_state(libvlc_media_list_player_t * p_mlp, libvlc_exception_t * p_e)
636 return libvlc_media_player_get_state(p_mlp->p_mi, p_e);
639 /**************************************************************************
640 * Play item at index (Public)
641 **************************************************************************/
642 void libvlc_media_list_player_play_item_at_index(libvlc_media_list_player_t * p_mlp, int i_index, libvlc_exception_t * p_e)
647 set_current_playing_item(p_mlp, libvlc_media_list_path_with_root_index(i_index));
648 libvlc_media_player_play(p_mlp->p_mi, p_e);
651 /* Send the next item event */
652 libvlc_event_t event;
653 event.type = libvlc_MediaListPlayerNextItemSet;
654 libvlc_event_send(p_mlp->p_event_manager, &event);
657 /**************************************************************************
659 **************************************************************************/
660 void libvlc_media_list_player_play_item(libvlc_media_list_player_t * p_mlp, libvlc_media_t * p_md, libvlc_exception_t * p_e)
663 libvlc_media_list_path_t path = libvlc_media_list_path_of_item(p_mlp->p_mlist, p_md);
666 libvlc_exception_raise(p_e);
667 libvlc_printerr("Item not found 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);
726 libvlc_printerr("No media list");
730 libvlc_media_list_lock(p_mlp->p_mlist);
732 libvlc_media_list_path_t path = p_mlp->current_playing_item_path;
734 if(p_mlp->e_playback_mode != libvlc_playback_mode_repeat)
736 bool b_loop = (p_mlp->e_playback_mode == libvlc_playback_mode_loop);
738 if(i_relative_position > 0)
742 path = get_next_path(p_mlp, b_loop);
743 set_current_playing_item(p_mlp, path);
744 --i_relative_position;
746 while(i_relative_position > 0);
748 else if(i_relative_position < 0)
752 path = get_previous_path(p_mlp, b_loop);
753 set_current_playing_item(p_mlp, path);
754 ++i_relative_position;
756 while (i_relative_position < 0);
761 set_current_playing_item(p_mlp, path);
764 #ifdef DEBUG_MEDIA_LIST_PLAYER
766 libvlc_media_list_path_dump(path);
771 libvlc_media_list_unlock(p_mlp->p_mlist);
776 libvlc_media_player_play(p_mlp->p_mi, p_e);
778 libvlc_media_list_unlock(p_mlp->p_mlist);
780 /* Send the next item event */
781 libvlc_event_t event;
782 event.type = libvlc_MediaListPlayerNextItemSet;
783 libvlc_media_t * p_md = libvlc_media_list_item_at_path(p_mlp->p_mlist, path);
784 event.u.media_list_player_next_item_set.item = p_md;
785 libvlc_event_send(p_mlp->p_event_manager, &event);
786 libvlc_media_release(p_md);
789 /**************************************************************************
791 **************************************************************************/
792 void libvlc_media_list_player_next(libvlc_media_list_player_t * p_mlp,
793 libvlc_exception_t * p_e)
796 set_relative_playlist_position_and_play(p_mlp, 1, p_e);
800 /**************************************************************************
802 **************************************************************************/
803 void libvlc_media_list_player_previous(libvlc_media_list_player_t * p_mlp,
804 libvlc_exception_t * p_e)
807 set_relative_playlist_position_and_play(p_mlp, -1, p_e);
811 /**************************************************************************
812 * Set Playback Mode (Public)
813 **************************************************************************/
814 void libvlc_media_list_player_set_playback_mode(
815 libvlc_media_list_player_t * p_mlp,
816 libvlc_playback_mode_t e_mode,
817 libvlc_exception_t * p_e )
822 p_mlp->e_playback_mode = e_mode;