1 /*****************************************************************************
2 * playlist.c : Playlist management functions
3 *****************************************************************************
4 * Copyright (C) 1999-2004 VideoLAN
7 * Authors: Samuel Hocevar <sam@zoy.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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
22 *****************************************************************************/
23 #include <stdlib.h> /* free(), strtol() */
24 #include <stdio.h> /* sprintf() */
25 #include <string.h> /* strerror() */
30 #include <vlc/input.h>
32 #include "vlc_playlist.h"
34 #define PLAYLIST_FILE_HEADER_0_5 "# vlc playlist file version 0.5"
36 /*****************************************************************************
38 *****************************************************************************/
39 static void RunThread ( playlist_t * );
40 static void SkipItem ( playlist_t *, int );
41 static void PlayItem ( playlist_t * );
46 * Create a playlist structure.
47 * \param p_parent the vlc object that is to be the parent of this playlist
48 * \return a pointer to the created playlist, or NULL on error
50 playlist_t * __playlist_Create ( vlc_object_t *p_parent )
52 playlist_t *p_playlist;
55 /* Allocate structure */
56 p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
59 msg_Err( p_parent, "out of memory" );
63 var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
64 val.b_bool = VLC_TRUE;
65 var_Set( p_playlist, "intf-change", val );
67 var_Create( p_playlist, "item-change", VLC_VAR_INTEGER );
69 var_Set( p_playlist, "item-change", val );
71 var_Create( p_playlist, "playlist-current", VLC_VAR_INTEGER );
73 var_Set( p_playlist, "playlist-current", val );
75 var_Create( p_playlist, "intf-popupmenu", VLC_VAR_BOOL );
77 var_Create( p_playlist, "intf-show", VLC_VAR_BOOL );
78 val.b_bool = VLC_TRUE;
79 var_Set( p_playlist, "intf-show", val );
82 var_Create( p_playlist, "prevent-skip", VLC_VAR_BOOL );
83 val.b_bool = VLC_FALSE;
84 var_Set( p_playlist, "prevent-skip", val );
86 var_Create( p_playlist, "play-and-stop", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
87 var_Create( p_playlist, "random", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
88 var_Create( p_playlist, "repeat", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
89 var_Create( p_playlist, "loop", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
91 p_playlist->p_input = NULL;
92 p_playlist->i_status = PLAYLIST_STOPPED;
93 p_playlist->i_index = -1;
94 p_playlist->i_size = 0;
95 p_playlist->pp_items = NULL;
97 p_playlist->i_groups = 0;
98 p_playlist->pp_groups = NULL;
99 p_playlist->i_last_group = 0;
100 p_playlist->i_last_id = 0;
101 p_playlist->i_sort = SORT_ID;
102 p_playlist->i_order = ORDER_NORMAL;
104 playlist_CreateGroup( p_playlist, _("Normal") );
106 if( vlc_thread_create( p_playlist, "playlist", RunThread,
107 VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
109 msg_Err( p_playlist, "cannot spawn playlist thread" );
110 vlc_object_destroy( p_playlist );
114 /* The object has been initialized, now attach it */
115 vlc_object_attach( p_playlist, p_parent );
121 * Destroy the playlist.
123 * Delete all items in the playlist and free the playlist structure.
124 * \param p_playlist the playlist structure to destroy
126 void playlist_Destroy( playlist_t * p_playlist )
128 p_playlist->b_die = 1;
130 vlc_thread_join( p_playlist );
132 var_Destroy( p_playlist, "intf-change" );
133 var_Destroy( p_playlist, "item-change" );
134 var_Destroy( p_playlist, "playlist-current" );
135 var_Destroy( p_playlist, "intf-popmenu" );
136 var_Destroy( p_playlist, "intf-show" );
137 var_Destroy( p_playlist, "prevent-skip" );
138 var_Destroy( p_playlist, "play-and-stop" );
139 var_Destroy( p_playlist, "random" );
140 var_Destroy( p_playlist, "repeat" );
141 var_Destroy( p_playlist, "loop" );
143 while( p_playlist->i_groups > 0 )
145 playlist_DeleteGroup( p_playlist, p_playlist->pp_groups[0]->i_id );
148 while( p_playlist->i_size > 0 )
150 playlist_Delete( p_playlist, 0 );
153 vlc_object_destroy( p_playlist );
158 * Do a playlist action.
160 * If there is something in the playlist then you can do playlist actions.
161 * \param p_playlist the playlist to do the command on
162 * \param i_command the command to do
163 * \param i_arg the argument to the command. See playlist_command_t for details
165 void playlist_Command( playlist_t * p_playlist, playlist_command_t i_command,
170 vlc_mutex_lock( &p_playlist->object_lock );
172 if( p_playlist->i_size <= 0 )
174 vlc_mutex_unlock( &p_playlist->object_lock );
181 p_playlist->i_status = PLAYLIST_STOPPED;
182 if( p_playlist->p_input )
184 input_StopThread( p_playlist->p_input );
185 val.i_int = p_playlist->i_index;
186 /* Does not matter if we unlock here */
187 vlc_mutex_unlock( &p_playlist->object_lock );
188 var_Set( p_playlist, "item-change",val );
189 vlc_mutex_lock( &p_playlist->object_lock );
194 p_playlist->i_status = PLAYLIST_RUNNING;
195 if( !p_playlist->p_input && p_playlist->i_enabled != 0 )
197 PlayItem( p_playlist );
199 if( p_playlist->p_input )
201 val.i_int = PLAYING_S;
202 var_Set( p_playlist->p_input, "state", val );
208 if( p_playlist->p_input )
209 var_Get( p_playlist->p_input, "state", &val );
211 if( val.i_int == PAUSE_S )
213 p_playlist->i_status = PLAYLIST_RUNNING;
214 if( p_playlist->p_input )
216 val.i_int = PLAYING_S;
217 var_Set( p_playlist->p_input, "state", val );
222 p_playlist->i_status = PLAYLIST_PAUSED;
223 if( p_playlist->p_input )
226 var_Set( p_playlist->p_input, "state", val );
232 p_playlist->i_status = PLAYLIST_STOPPED;
233 if( p_playlist->i_enabled == 0)
237 SkipItem( p_playlist, i_arg );
238 if( p_playlist->p_input )
240 input_StopThread( p_playlist->p_input );
242 p_playlist->i_status = PLAYLIST_RUNNING;
246 if( i_arg >= 0 && i_arg < p_playlist->i_size &&
247 p_playlist->i_enabled != 0 )
249 p_playlist->i_index = i_arg;
250 if( p_playlist->p_input )
252 input_StopThread( p_playlist->p_input );
254 val.b_bool = VLC_TRUE;
255 var_Set( p_playlist, "prevent-skip", val );
256 p_playlist->i_status = PLAYLIST_RUNNING;
261 msg_Err( p_playlist, "unknown playlist command" );
265 vlc_mutex_unlock( &p_playlist->object_lock );
267 val.b_bool = VLC_TRUE;
268 var_Set( p_playlist, "intf-change", val );
274 static mtime_t ObjectGarbageCollector( playlist_t *p_playlist, int i_type,
275 mtime_t destroy_date )
279 if( destroy_date > mdate() ) return destroy_date;
281 if( destroy_date == 0 )
283 /* give a little time */
284 return mdate() + I64C(1000000);
288 while( ( p_obj = vlc_object_find( p_playlist, i_type, FIND_CHILD ) ) )
290 if( p_obj->p_parent != (vlc_object_t*)p_playlist )
292 /* only first child (ie unused) */
293 vlc_object_release( p_obj );
296 if( i_type == VLC_OBJECT_VOUT )
298 msg_Dbg( p_playlist, "garbage collector destroying 1 vout" );
299 vlc_object_detach( p_obj );
300 vlc_object_release( p_obj );
301 vout_Destroy( (vout_thread_t *)p_obj );
303 else if( i_type == VLC_OBJECT_SOUT )
305 vlc_object_release( p_obj );
306 sout_DeleteInstance( (sout_instance_t*)p_obj );
313 /*****************************************************************************
314 * RunThread: main playlist thread
315 *****************************************************************************/
316 static void RunThread ( playlist_t *p_playlist )
321 mtime_t i_vout_destroyed_date = 0;
322 mtime_t i_sout_destroyed_date = 0;
324 playlist_item_t *p_autodelete_item = 0;
326 /* Tell above that we're ready */
327 vlc_thread_ready( p_playlist );
329 while( !p_playlist->b_die )
331 vlc_mutex_lock( &p_playlist->object_lock );
333 /* If there is an input, check that it doesn't need to die. */
334 if( p_playlist->p_input )
336 /* This input is dead. Remove it ! */
337 if( p_playlist->p_input->b_dead )
339 input_thread_t *p_input;
341 p_input = p_playlist->p_input;
342 p_playlist->p_input = NULL;
344 /* Release the playlist lock, because we may get stuck
345 * in input_DestroyThread() for some time. */
346 vlc_mutex_unlock( &p_playlist->object_lock );
349 input_DestroyThread( p_input );
351 /* Unlink current input
352 * (_after_ input_DestroyThread for vout garbage collector) */
353 vlc_object_detach( p_input );
356 vlc_object_destroy( p_input );
358 i_vout_destroyed_date = 0;
359 i_sout_destroyed_date = 0;
361 /* Check for autodeletion */
362 if( p_autodelete_item )
364 playlist_ItemDelete( p_autodelete_item );
365 p_autodelete_item = 0;
370 /* This input is dying, let him do */
371 else if( p_playlist->p_input->b_die )
375 /* This input has finished, ask him to die ! */
376 else if( p_playlist->p_input->b_error
377 || p_playlist->p_input->b_eof )
379 input_StopThread( p_playlist->p_input );
381 if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
383 /* This ain't pretty but hey it works */
385 p_playlist->pp_items[p_playlist->i_index];
386 p_playlist->pp_items[p_playlist->i_index] =
387 playlist_ItemNew( p_playlist,
388 p_autodelete_item->input.psz_uri, 0);
390 vlc_mutex_unlock( &p_playlist->object_lock );
391 p_playlist->i_status = PLAYLIST_STOPPED;
392 playlist_Delete( p_playlist, p_playlist->i_index );
393 p_playlist->i_status = PLAYLIST_RUNNING;
394 vlc_mutex_lock( &p_playlist->object_lock );
397 SkipItem( p_playlist, 1 );
398 vlc_mutex_unlock( &p_playlist->object_lock );
401 else if( p_playlist->p_input->i_state != INIT_S )
403 vlc_mutex_unlock( &p_playlist->object_lock );
404 i_vout_destroyed_date =
405 ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
406 i_vout_destroyed_date );
407 i_sout_destroyed_date =
408 ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
409 i_sout_destroyed_date );
410 vlc_mutex_lock( &p_playlist->object_lock );
413 else if( p_playlist->i_status != PLAYLIST_STOPPED )
415 /* Start another input. Let's check if that item has
416 * been forced. In that case, we override random (by not skipping)
417 * and play-and-stop */
419 var_Get( p_playlist, "prevent-skip", &val );
420 b_forced = val.b_bool;
421 if( val.b_bool == VLC_FALSE )
423 SkipItem( p_playlist, 0 );
425 /* Reset forced status */
426 val.b_bool = VLC_FALSE;
427 var_Set( p_playlist, "prevent-skip", val );
428 /* Check for play-and-stop */
429 var_Get( p_playlist, "play-and-stop", &val );
430 if( val.b_bool == VLC_FALSE || b_forced == VLC_TRUE )
432 PlayItem( p_playlist );
435 else if( p_playlist->i_status == PLAYLIST_STOPPED )
437 vlc_mutex_unlock( &p_playlist->object_lock );
438 i_sout_destroyed_date =
439 ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT, mdate() );
440 i_vout_destroyed_date =
441 ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT, mdate() );
442 vlc_mutex_lock( &p_playlist->object_lock );
444 vlc_mutex_unlock( &p_playlist->object_lock );
446 msleep( INTF_IDLE_SLEEP );
449 /* If there is an input, kill it */
452 vlc_mutex_lock( &p_playlist->object_lock );
454 if( p_playlist->p_input == NULL )
456 vlc_mutex_unlock( &p_playlist->object_lock );
460 if( p_playlist->p_input->b_dead )
462 input_thread_t *p_input;
464 /* Unlink current input */
465 p_input = p_playlist->p_input;
466 p_playlist->p_input = NULL;
467 vlc_mutex_unlock( &p_playlist->object_lock );
470 input_DestroyThread( p_input );
471 /* Unlink current input (_after_ input_DestroyThread for vout
472 * garbage collector)*/
473 vlc_object_detach( p_input );
476 vlc_object_destroy( p_input );
479 else if( p_playlist->p_input->b_die )
481 /* This input is dying, leave him alone */
484 else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
486 input_StopThread( p_playlist->p_input );
487 vlc_mutex_unlock( &p_playlist->object_lock );
492 p_playlist->p_input->b_eof = 1;
495 vlc_mutex_unlock( &p_playlist->object_lock );
497 msleep( INTF_IDLE_SLEEP );
500 /* close all remaining sout */
501 while( ( p_obj = vlc_object_find( p_playlist,
502 VLC_OBJECT_SOUT, FIND_CHILD ) ) )
504 vlc_object_release( p_obj );
505 sout_DeleteInstance( (sout_instance_t*)p_obj );
508 /* close all remaining vout */
509 while( ( p_obj = vlc_object_find( p_playlist,
510 VLC_OBJECT_VOUT, FIND_CHILD ) ) )
512 vlc_object_detach( p_obj );
513 vlc_object_release( p_obj );
514 vout_Destroy( (vout_thread_t *)p_obj );
518 /*****************************************************************************
519 * SkipItem: go to Xth playlist item
520 *****************************************************************************
521 * This function calculates the position of the next playlist item, depending
522 * on the playlist course mode (forward, backward, random...).
523 *****************************************************************************/
524 static void SkipItem( playlist_t *p_playlist, int i_arg )
526 int i_oldindex = p_playlist->i_index;
527 vlc_bool_t b_random, b_repeat, b_loop;
531 /* If the playlist is empty, there is no current item */
532 if( p_playlist->i_size == 0 )
534 p_playlist->i_index = -1;
538 var_Get( p_playlist, "random", &val );
539 b_random = val.b_bool;
540 var_Get( p_playlist, "repeat", &val );
541 b_repeat = val.b_bool;
542 var_Get( p_playlist, "loop", &val );
548 srand( (unsigned int)mdate() );
550 while( i_count < p_playlist->i_size )
552 p_playlist->i_index =
553 (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
554 /* Check if the item has not already been played */
555 if( p_playlist->pp_items[p_playlist->i_index]->i_nb_played == 0 )
559 if( i_count == p_playlist->i_size )
561 /* The whole playlist has been played: reset the counters */
564 p_playlist->pp_items[--i_count]->i_nb_played = 0;
568 p_playlist->i_status = PLAYLIST_STOPPED;
574 p_playlist->i_index += i_arg;
578 if( p_playlist->i_index >= p_playlist->i_size )
580 if( p_playlist->i_status == PLAYLIST_STOPPED || b_random || b_loop )
582 p_playlist->i_index -= p_playlist->i_size
583 * ( p_playlist->i_index / p_playlist->i_size );
587 /* Don't loop by default: stop at playlist end */
588 p_playlist->i_index = i_oldindex;
589 p_playlist->i_status = PLAYLIST_STOPPED;
592 else if( p_playlist->i_index < 0 )
594 p_playlist->i_index = p_playlist->i_size - 1;
597 /* Check that the item is enabled */
598 if( p_playlist->pp_items[p_playlist->i_index]->b_enabled == VLC_FALSE &&
599 p_playlist->i_enabled != 0)
601 SkipItem( p_playlist , 1 );
605 /*****************************************************************************
606 * PlayItem: play current playlist item
607 *****************************************************************************
608 * This function calculates the position of the next playlist item, depending
609 * on the playlist course mode (forward, backward, random...).
610 *****************************************************************************/
611 static void PlayItem( playlist_t *p_playlist )
613 playlist_item_t *p_item;
615 if( p_playlist->i_index == -1 )
617 if( p_playlist->i_size == 0 || p_playlist->i_enabled == 0)
621 SkipItem( p_playlist, 1 );
623 if( p_playlist->i_enabled == 0)
628 msg_Dbg( p_playlist, "creating new input thread" );
629 p_item = p_playlist->pp_items[p_playlist->i_index];
631 p_item->i_nb_played++;
632 p_playlist->p_input = input_CreateThread( p_playlist, &p_item->input );
634 val.i_int = p_playlist->i_index;
635 /* unlock the playlist to set the var...mmm */
636 vlc_mutex_unlock( &p_playlist->object_lock);
637 var_Set( p_playlist, "playlist-current", val);
638 vlc_mutex_lock( &p_playlist->object_lock);