1 /*****************************************************************************
2 * playlist.c : Playlist management functions
3 *****************************************************************************
4 * Copyright (C) 1999-2004 VideoLAN
5 * $Id: playlist.c,v 1.72 2004/01/06 08:50:20 zorglub Exp $
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 "stream_control.h"
33 #include "input_ext-intf.h"
35 #include "vlc_playlist.h"
37 #define PLAYLIST_FILE_HEADER_0_5 "# vlc playlist file version 0.5"
38 #define PLAYLIST_FILE_HEADER_0_6 "# vlc playlist file version 0.6"
40 /*****************************************************************************
42 *****************************************************************************/
43 static void RunThread ( playlist_t * );
44 static void SkipItem ( playlist_t *, int );
45 static void PlayItem ( playlist_t * );
50 * Create a playlist structure.
51 * \param p_parent the vlc object that is to be the parent of this playlist
52 * \return a pointer to the created playlist, or NULL on error
54 playlist_t * __playlist_Create ( vlc_object_t *p_parent )
56 playlist_t *p_playlist;
59 /* Allocate structure */
60 p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
63 msg_Err( p_parent, "out of memory" );
67 var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
68 val.b_bool = VLC_TRUE;
69 var_Set( p_playlist, "intf-change", val );
71 var_Create( p_playlist, "item-change", VLC_VAR_INTEGER );
73 var_Set( p_playlist, "item-change", val );
75 var_Create( p_playlist, "playlist-current", VLC_VAR_INTEGER );
77 var_Set( p_playlist, "playlist-current", val );
79 var_Create( p_playlist, "intf-popupmenu", VLC_VAR_BOOL );
81 var_Create( p_playlist, "intf-show", VLC_VAR_BOOL );
82 val.b_bool = VLC_TRUE;
83 var_Set( p_playlist, "intf-show", val );
86 var_Create( p_playlist, "prevent-skip", VLC_VAR_BOOL );
87 val.b_bool = VLC_FALSE;
88 var_Set( p_playlist, "prevent-skip", val );
90 var_Create( p_playlist, "random", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
91 var_Create( p_playlist, "repeat", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
92 var_Create( p_playlist, "loop", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
94 p_playlist->p_input = NULL;
95 p_playlist->i_status = PLAYLIST_STOPPED;
96 p_playlist->i_index = -1;
97 p_playlist->i_size = 0;
98 p_playlist->pp_items = NULL;
100 p_playlist->i_groups = 0;
101 p_playlist->pp_groups = NULL;
102 p_playlist->i_last_group = 0;
103 p_playlist->i_last_id = 0;
105 playlist_CreateGroup( p_playlist, "Normal" );
107 if( vlc_thread_create( p_playlist, "playlist", RunThread,
108 VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
110 msg_Err( p_playlist, "cannot spawn playlist thread" );
111 vlc_object_destroy( p_playlist );
115 /* The object has been initialized, now attach it */
116 vlc_object_attach( p_playlist, p_parent );
122 * Destroy the playlist.
124 * Delete all items in the playlist and free the playlist structure.
125 * \param p_playlist the playlist structure to destroy
127 void playlist_Destroy( playlist_t * p_playlist )
129 p_playlist->b_die = 1;
131 vlc_thread_join( p_playlist );
133 var_Destroy( p_playlist, "intf-change" );
134 var_Destroy( p_playlist, "item-change" );
135 var_Destroy( p_playlist, "playlist-current" );
136 var_Destroy( p_playlist, "intf-popmenu" );
137 var_Destroy( p_playlist, "intf-show" );
138 var_Destroy( p_playlist, "prevent-skip" );
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 * \param p_playlist the playlist to do the command on
161 * \param i_command the command to do
162 * \param i_arg the argument to the command. See playlist_command_t for details
164 void playlist_Command( playlist_t * p_playlist, playlist_command_t i_command,
169 vlc_mutex_lock( &p_playlist->object_lock );
174 p_playlist->i_status = PLAYLIST_STOPPED;
175 if( p_playlist->p_input )
177 input_StopThread( p_playlist->p_input );
178 val.i_int = p_playlist->i_index;
179 var_Set( p_playlist, "item-change",val );
184 p_playlist->i_status = PLAYLIST_RUNNING;
185 if( !p_playlist->p_input && p_playlist->i_enabled != 0 )
187 PlayItem( p_playlist );
189 if( p_playlist->p_input )
191 val.i_int = PLAYING_S;
192 var_Set( p_playlist->p_input, "state", val );
198 if( p_playlist->p_input )
199 var_Get( p_playlist->p_input, "state", &val );
201 if( val.i_int == PAUSE_S )
203 p_playlist->i_status = PLAYLIST_RUNNING;
204 if( p_playlist->p_input )
206 val.i_int = PLAYING_S;
207 var_Set( p_playlist->p_input, "state", val );
212 p_playlist->i_status = PLAYLIST_PAUSED;
213 if( p_playlist->p_input )
216 var_Set( p_playlist->p_input, "state", val );
222 p_playlist->i_status = PLAYLIST_STOPPED;
223 if( p_playlist->i_enabled == 0)
227 SkipItem( p_playlist, i_arg );
228 if( p_playlist->p_input )
230 input_StopThread( p_playlist->p_input );
232 p_playlist->i_status = PLAYLIST_RUNNING;
236 if( i_arg >= 0 && i_arg < p_playlist->i_size &&
237 p_playlist->i_enabled != 0 )
239 p_playlist->i_index = i_arg;
240 if( p_playlist->p_input )
242 input_StopThread( p_playlist->p_input );
244 val.b_bool = VLC_TRUE;
245 var_Set( p_playlist, "prevent-skip", val );
246 p_playlist->i_status = PLAYLIST_RUNNING;
251 msg_Err( p_playlist, "unknown playlist command" );
255 vlc_mutex_unlock( &p_playlist->object_lock );
257 val.b_bool = VLC_TRUE;
258 var_Set( p_playlist, "intf-change", val );
264 static void ObjectGarbageCollector( playlist_t *p_playlist,
266 mtime_t *pi_obj_destroyed_date )
269 if( *pi_obj_destroyed_date > mdate() )
274 if( *pi_obj_destroyed_date == 0 )
276 /* give a little time */
277 *pi_obj_destroyed_date = mdate() + I64C(300000);
281 while( ( p_obj = vlc_object_find( p_playlist,
285 if( p_obj->p_parent != (vlc_object_t*)p_playlist )
287 /* only first chiled (ie unused) */
288 vlc_object_release( p_obj );
291 if( i_type == VLC_OBJECT_VOUT )
293 msg_Dbg( p_playlist, "vout garbage collector destroying 1 vout" );
294 vlc_object_detach( p_obj );
295 vlc_object_release( p_obj );
296 vout_Destroy( (vout_thread_t *)p_obj );
298 else if( i_type == VLC_OBJECT_SOUT )
300 vlc_object_release( p_obj );
301 sout_DeleteInstance( (sout_instance_t*)p_obj );
304 *pi_obj_destroyed_date = 0;
308 /*****************************************************************************
309 * RunThread: main playlist thread
310 *****************************************************************************/
311 static void RunThread ( playlist_t *p_playlist )
316 mtime_t i_vout_destroyed_date = 0;
317 mtime_t i_sout_destroyed_date = 0;
319 /* Tell above that we're ready */
320 vlc_thread_ready( p_playlist );
322 while( !p_playlist->b_die )
324 vlc_mutex_lock( &p_playlist->object_lock );
326 /* If there is an input, check that it doesn't need to die. */
327 if( p_playlist->p_input )
329 /* This input is dead. Remove it ! */
330 if( p_playlist->p_input->b_dead )
332 input_thread_t *p_input;
334 p_input = p_playlist->p_input;
335 p_playlist->p_input = NULL;
337 /* Release the playlist lock, because we may get stuck
338 * in input_DestroyThread() for some time. */
339 vlc_mutex_unlock( &p_playlist->object_lock );
342 input_DestroyThread( p_input );
344 /* Unlink current input
345 * (_after_ input_DestroyThread for vout garbage collector) */
346 vlc_object_detach( p_input );
349 vlc_object_destroy( p_input );
351 i_vout_destroyed_date = 0;
352 i_sout_destroyed_date = 0;
355 /* This input is dying, let him do */
356 else if( p_playlist->p_input->b_die )
360 /* This input has finished, ask him to die ! */
361 else if( p_playlist->p_input->b_error
362 || p_playlist->p_input->b_eof )
364 /* Check for autodeletion */
365 if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
367 vlc_mutex_unlock( &p_playlist->object_lock );
368 playlist_Delete( p_playlist, p_playlist->i_index );
369 p_playlist->i_index++;
370 p_playlist->i_status = PLAYLIST_RUNNING;
374 /* Select the next playlist item */
375 SkipItem( p_playlist, 1 );
376 input_StopThread( p_playlist->p_input );
377 vlc_mutex_unlock( &p_playlist->object_lock );
380 val.i_int = p_playlist->i_index;
381 var_Set( p_playlist, "playlist-current", val);
383 val.b_bool = VLC_TRUE;
384 var_Set( p_playlist, "intf-change", val );
388 else if( p_playlist->p_input->stream.control.i_status != INIT_S )
390 vlc_mutex_unlock( &p_playlist->object_lock );
391 ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
392 &i_vout_destroyed_date );
393 ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
394 &i_sout_destroyed_date );
395 vlc_mutex_lock( &p_playlist->object_lock );
398 else if( p_playlist->i_status != PLAYLIST_STOPPED )
400 var_Get( p_playlist, "prevent-skip", &val);
401 if( val.b_bool == VLC_FALSE)
403 SkipItem( p_playlist, 0 );
405 val.b_bool = VLC_TRUE;
406 var_Set( p_playlist, "prevent-skip", val);
407 PlayItem( p_playlist );
409 else if( p_playlist->i_status == PLAYLIST_STOPPED )
411 vlc_mutex_unlock( &p_playlist->object_lock );
412 ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
413 &i_sout_destroyed_date );
414 ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
415 &i_vout_destroyed_date );
416 vlc_mutex_lock( &p_playlist->object_lock );
418 vlc_mutex_unlock( &p_playlist->object_lock );
420 msleep( INTF_IDLE_SLEEP );
423 /* If there is an input, kill it */
426 vlc_mutex_lock( &p_playlist->object_lock );
428 if( p_playlist->p_input == NULL )
430 vlc_mutex_unlock( &p_playlist->object_lock );
434 if( p_playlist->p_input->b_dead )
436 input_thread_t *p_input;
438 /* Unlink current input */
439 p_input = p_playlist->p_input;
440 p_playlist->p_input = NULL;
441 vlc_mutex_unlock( &p_playlist->object_lock );
444 input_DestroyThread( p_input );
445 /* Unlink current input (_after_ input_DestroyThread for vout
446 * garbage collector)*/
447 vlc_object_detach( p_input );
450 vlc_object_destroy( p_input );
453 else if( p_playlist->p_input->b_die )
455 /* This input is dying, leave him alone */
458 else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
460 input_StopThread( p_playlist->p_input );
461 vlc_mutex_unlock( &p_playlist->object_lock );
466 p_playlist->p_input->b_eof = 1;
469 vlc_mutex_unlock( &p_playlist->object_lock );
471 msleep( INTF_IDLE_SLEEP );
474 /* close all remaining sout */
475 while( ( p_obj = vlc_object_find( p_playlist,
476 VLC_OBJECT_SOUT, FIND_CHILD ) ) )
478 vlc_object_release( p_obj );
479 sout_DeleteInstance( (sout_instance_t*)p_obj );
482 /* close all remaining vout */
483 while( ( p_obj = vlc_object_find( p_playlist,
484 VLC_OBJECT_VOUT, FIND_CHILD ) ) )
486 vlc_object_detach( p_obj );
487 vlc_object_release( p_obj );
488 vout_Destroy( (vout_thread_t *)p_obj );
492 /*****************************************************************************
493 * SkipItem: go to Xth playlist item
494 *****************************************************************************
495 * This function calculates the position of the next playlist item, depending
496 * on the playlist course mode (forward, backward, random...).
497 *****************************************************************************/
498 static void SkipItem( playlist_t *p_playlist, int i_arg )
500 int i_oldindex = p_playlist->i_index;
501 vlc_bool_t b_random, b_repeat, b_loop;
504 /* If the playlist is empty, there is no current item */
505 if( p_playlist->i_size == 0 )
507 p_playlist->i_index = -1;
511 var_Get( p_playlist, "random", &val );
512 b_random = val.b_bool;
513 var_Get( p_playlist, "repeat", &val );
514 b_repeat = val.b_bool;
515 var_Get( p_playlist, "loop", &val );
521 srand( (unsigned int)mdate() );
523 /* Simple random stuff - we cheat a bit to minimize the chances to
524 * get the same index again. */
525 i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
528 i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
535 p_playlist->i_index += i_arg;
538 if( p_playlist->i_index >= p_playlist->i_size )
540 if( p_playlist->i_status == PLAYLIST_STOPPED
544 p_playlist->i_index -= p_playlist->i_size
545 * ( p_playlist->i_index / p_playlist->i_size );
549 /* Don't loop by default: stop at playlist end */
550 p_playlist->i_index = i_oldindex;
551 p_playlist->i_status = PLAYLIST_STOPPED;
554 else if( p_playlist->i_index < 0 )
556 p_playlist->i_index = p_playlist->i_size - 1;
559 /* Check that the item is enabled */
560 if( p_playlist->pp_items[p_playlist->i_index]->b_enabled == VLC_FALSE &&
561 p_playlist->i_enabled != 0)
563 SkipItem( p_playlist , 1 );
567 /*****************************************************************************
568 * PlayItem: play current playlist item
569 *****************************************************************************
570 * This function calculates the position of the next playlist item, depending
571 * on the playlist course mode (forward, backward, random...).
572 *****************************************************************************/
573 static void PlayItem( playlist_t *p_playlist )
576 if( p_playlist->i_index == -1 )
578 if( p_playlist->i_size == 0 || p_playlist->i_enabled == 0)
583 SkipItem( p_playlist, 1 );
586 if( p_playlist->i_enabled == 0)
591 msg_Dbg( p_playlist, "creating new input thread" );
592 p_playlist->p_input = input_CreateThread( p_playlist,
593 p_playlist->pp_items[p_playlist->i_index] );
595 val.i_int = p_playlist->i_index;
596 var_Set( p_playlist, "item-change", val );