1 /*****************************************************************************
2 * playlist.c : Playlist management functions
3 *****************************************************************************
4 * Copyright (C) 1999-2001 VideoLAN
5 * $Id: playlist.c,v 1.36 2003/05/26 19:06:47 gbazin 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() */
29 #include "stream_control.h"
30 #include "input_ext-intf.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 * );
43 static void Poubellize ( playlist_t *, input_thread_t * );
45 /*****************************************************************************
46 * playlist_Create: create playlist
47 *****************************************************************************
48 * Create a playlist structure.
49 *****************************************************************************/
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, "intf-popupmenu", VLC_VAR_VOID );
69 p_playlist->p_input = NULL;
70 p_playlist->i_status = PLAYLIST_STOPPED;
71 p_playlist->i_index = -1;
72 p_playlist->i_size = 0;
73 p_playlist->pp_items = NULL;
75 if( vlc_thread_create( p_playlist, "playlist", RunThread,
76 VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
78 msg_Err( p_playlist, "cannot spawn playlist thread" );
79 vlc_object_destroy( p_playlist );
83 /* The object has been initialized, now attach it */
84 vlc_object_attach( p_playlist, p_parent );
89 /*****************************************************************************
90 * playlist_Destroy: destroy the playlist
91 *****************************************************************************
92 * Delete all items in the playlist and free the playlist structure.
93 *****************************************************************************/
94 void playlist_Destroy( playlist_t * p_playlist )
96 p_playlist->b_die = 1;
98 vlc_thread_join( p_playlist );
100 var_Destroy( p_playlist, "intf-change" );
102 vlc_object_destroy( p_playlist );
105 /*****************************************************************************
106 * playlist_Add: add an item to the playlist
107 *****************************************************************************
108 * Add an item to the playlist at position i_pos. If i_pos is PLAYLIST_END,
109 * add it at the end regardless of the playlist current size.
110 *****************************************************************************/
111 int playlist_Add( playlist_t *p_playlist, const char * psz_target,
112 int i_mode, int i_pos )
114 playlist_item_t * p_item;
116 p_item = malloc( sizeof( playlist_item_t ) );
119 msg_Err( p_playlist, "out of memory" );
122 p_item->psz_name = strdup( psz_target );
123 p_item->psz_uri = strdup( psz_target );
125 p_item->i_status = 0;
126 p_item->b_autodeletion = VLC_FALSE;
128 return playlist_AddItem( p_playlist, p_item, i_mode, i_pos );
132 int playlist_AddItem( playlist_t *p_playlist, playlist_item_t * p_item,
133 int i_mode, int i_pos)
137 vlc_mutex_lock( &p_playlist->object_lock );
140 * CHECK_INSERT : checks if the item is already enqued before
143 if ( i_mode & PLAYLIST_CHECK_INSERT )
147 if ( p_playlist->pp_items )
149 for ( j = 0; j < p_playlist->i_size; j++ )
151 if ( !strcmp( p_playlist->pp_items[j]->psz_uri, p_item->psz_uri ) )
153 if( p_item->psz_name )
155 free( p_item->psz_name );
157 if( p_item->psz_uri )
159 free( p_item->psz_uri );
162 vlc_mutex_unlock( &p_playlist->object_lock );
167 i_mode &= ~PLAYLIST_CHECK_INSERT;
168 i_mode |= PLAYLIST_APPEND;
172 msg_Dbg( p_playlist, "adding playlist item « %s »", p_item->psz_name );
174 /* Create the new playlist item */
177 /* Do a few boundary checks and allocate space for the item */
178 if( i_pos == PLAYLIST_END )
180 if( i_mode & PLAYLIST_INSERT )
182 i_mode &= ~PLAYLIST_INSERT;
183 i_mode |= PLAYLIST_APPEND;
186 i_pos = p_playlist->i_size - 1;
189 if( !(i_mode & PLAYLIST_REPLACE)
190 || i_pos < 0 || i_pos >= p_playlist->i_size )
192 /* Additional boundary checks */
193 if( i_mode & PLAYLIST_APPEND )
202 else if( i_pos > p_playlist->i_size )
204 i_pos = p_playlist->i_size;
207 INSERT_ELEM( p_playlist->pp_items,
212 if( p_playlist->i_index >= i_pos )
214 p_playlist->i_index++;
219 /* i_mode == PLAYLIST_REPLACE and 0 <= i_pos < p_playlist->i_size */
220 if( p_playlist->pp_items[i_pos]->psz_name )
222 free( p_playlist->pp_items[i_pos]->psz_name );
224 if( p_playlist->pp_items[i_pos]->psz_uri )
226 free( p_playlist->pp_items[i_pos]->psz_uri );
228 /* XXX: what if the item is still in use? */
229 free( p_playlist->pp_items[i_pos] );
230 p_playlist->pp_items[i_pos] = p_item;
233 if( i_mode & PLAYLIST_GO )
235 p_playlist->i_index = i_pos;
236 if( p_playlist->p_input )
238 input_StopThread( p_playlist->p_input );
240 p_playlist->i_status = PLAYLIST_RUNNING;
243 vlc_mutex_unlock( &p_playlist->object_lock );
245 val.b_bool = VLC_TRUE;
246 var_Set( p_playlist, "intf-change", val );
251 /*****************************************************************************
252 * playlist_Delete: delete an item from the playlist
253 *****************************************************************************
254 * Delete the item in the playlist with position i_pos.
255 *****************************************************************************/
256 int playlist_Delete( playlist_t * p_playlist, int i_pos )
259 vlc_mutex_lock( &p_playlist->object_lock );
261 if( i_pos >= 0 && i_pos < p_playlist->i_size )
263 msg_Dbg( p_playlist, "deleting playlist item « %s »",
264 p_playlist->pp_items[i_pos]->psz_name );
266 if( p_playlist->pp_items[i_pos]->psz_name )
268 free( p_playlist->pp_items[i_pos]->psz_name );
270 if( p_playlist->pp_items[i_pos]->psz_uri )
272 free( p_playlist->pp_items[i_pos]->psz_uri );
275 /* XXX: what if the item is still in use? */
276 free( p_playlist->pp_items[i_pos] );
278 if( i_pos <= p_playlist->i_index )
280 p_playlist->i_index--;
283 /* Renumber the playlist */
284 REMOVE_ELEM( p_playlist->pp_items,
289 vlc_mutex_unlock( &p_playlist->object_lock );
291 val.b_bool = VLC_TRUE;
292 var_Set( p_playlist, "intf-change", val );
297 /*****************************************************************************
298 * playlist_Move: move an item in the playlist
299 *****************************************************************************
300 * Move the item in the playlist with position i_pos before the current item
301 * at position i_newpos.
302 *****************************************************************************/
303 int playlist_Move( playlist_t * p_playlist, int i_pos, int i_newpos)
306 vlc_mutex_lock( &p_playlist->object_lock );
308 /* take into account that our own row disappears. */
309 if ( i_pos < i_newpos ) i_newpos--;
311 if( i_pos >= 0 && i_newpos >=0 && i_pos <= p_playlist->i_size
312 && i_newpos <= p_playlist->i_size )
314 playlist_item_t * temp;
316 msg_Dbg( p_playlist, "moving playlist item « %s »",
317 p_playlist->pp_items[i_pos]->psz_name );
319 if( i_pos == p_playlist->i_index )
321 p_playlist->i_index = i_newpos;
323 else if( i_pos > p_playlist->i_index && i_newpos <= p_playlist->i_index )
325 p_playlist->i_index++;
327 else if( i_pos < p_playlist->i_index && i_newpos >= p_playlist->i_index )
329 p_playlist->i_index--;
332 if ( i_pos < i_newpos )
334 temp = p_playlist->pp_items[i_pos];
335 while ( i_pos < i_newpos )
337 p_playlist->pp_items[i_pos] = p_playlist->pp_items[i_pos+1];
340 p_playlist->pp_items[i_newpos] = temp;
342 else if ( i_pos > i_newpos )
344 temp = p_playlist->pp_items[i_pos];
345 while ( i_pos > i_newpos )
347 p_playlist->pp_items[i_pos] = p_playlist->pp_items[i_pos-1];
350 p_playlist->pp_items[i_newpos] = temp;
354 vlc_mutex_unlock( &p_playlist->object_lock );
356 val.b_bool = VLC_TRUE;
357 var_Set( p_playlist, "intf-change", val );
362 /*****************************************************************************
363 * playlist_Command: do a playlist action
364 *****************************************************************************
366 *****************************************************************************/
367 void playlist_Command( playlist_t * p_playlist, int i_command, int i_arg )
369 vlc_mutex_lock( &p_playlist->object_lock );
374 p_playlist->i_status = PLAYLIST_STOPPED;
375 if( p_playlist->p_input )
377 input_StopThread( p_playlist->p_input );
382 p_playlist->i_status = PLAYLIST_RUNNING;
383 if( p_playlist->p_input )
385 input_SetStatus( p_playlist->p_input, INPUT_STATUS_PLAY );
390 p_playlist->i_status = PLAYLIST_PAUSED;
391 if( p_playlist->p_input )
393 input_SetStatus( p_playlist->p_input, INPUT_STATUS_PAUSE );
398 p_playlist->i_status = PLAYLIST_STOPPED;
399 SkipItem( p_playlist, i_arg );
400 if( p_playlist->p_input )
402 input_StopThread( p_playlist->p_input );
404 p_playlist->i_status = PLAYLIST_RUNNING;
408 if( i_arg >= 0 && i_arg < p_playlist->i_size )
410 p_playlist->i_index = i_arg;
411 if( p_playlist->p_input )
413 input_StopThread( p_playlist->p_input );
415 p_playlist->i_status = PLAYLIST_RUNNING;
420 msg_Err( p_playlist, "unknown playlist command" );
424 vlc_mutex_unlock( &p_playlist->object_lock );
429 /* Following functions are local */
431 /*****************************************************************************
432 * RunThread: main playlist thread
433 *****************************************************************************/
434 static void RunThread ( playlist_t *p_playlist )
436 /* Tell above that we're ready */
437 vlc_thread_ready( p_playlist );
439 while( !p_playlist->b_die )
441 vlc_mutex_lock( &p_playlist->object_lock );
443 /* If there is an input, check that it doesn't need to die. */
444 if( p_playlist->p_input )
446 /* This input is dead. Remove it ! */
447 if( p_playlist->p_input->b_dead )
449 input_thread_t *p_input;
451 /* Unlink current input */
452 p_input = p_playlist->p_input;
453 p_playlist->p_input = NULL;
454 vlc_object_detach( p_input );
456 /* Release the playlist lock, because we may get stuck
457 * in input_DestroyThread() for some time. */
458 vlc_mutex_unlock( &p_playlist->object_lock );
461 input_DestroyThread( p_input );
462 vlc_object_destroy( p_input );
465 /* This input is dying, let him do */
466 else if( p_playlist->p_input->b_die )
470 /* This input has finished, ask him to die ! */
471 else if( p_playlist->p_input->b_error
472 || p_playlist->p_input->b_eof )
474 /* Check for autodeletion */
475 if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
477 vlc_mutex_unlock( &p_playlist->object_lock );
478 playlist_Delete( p_playlist, p_playlist->i_index );
479 vlc_mutex_lock( &p_playlist->object_lock );
482 /* Select the next playlist item */
483 SkipItem( p_playlist, 1 );
485 /* Release the playlist lock, because we may get stuck
486 * in input_StopThread() for some time. */
487 vlc_mutex_unlock( &p_playlist->object_lock );
488 input_StopThread( p_playlist->p_input );
492 else if( p_playlist->i_status != PLAYLIST_STOPPED )
494 PlayItem( p_playlist );
497 vlc_mutex_unlock( &p_playlist->object_lock );
499 msleep( INTF_IDLE_SLEEP );
502 /* If there is an input, kill it */
505 vlc_mutex_lock( &p_playlist->object_lock );
507 if( p_playlist->p_input == NULL )
509 vlc_mutex_unlock( &p_playlist->object_lock );
513 if( p_playlist->p_input->b_dead )
515 input_thread_t *p_input;
517 /* Unlink current input */
518 p_input = p_playlist->p_input;
519 p_playlist->p_input = NULL;
520 vlc_object_detach( p_input );
521 vlc_mutex_unlock( &p_playlist->object_lock );
524 input_DestroyThread( p_input );
525 vlc_object_destroy( p_input );
528 else if( p_playlist->p_input->b_die )
530 /* This input is dying, leave him alone */
533 else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
535 vlc_mutex_unlock( &p_playlist->object_lock );
536 input_StopThread( p_playlist->p_input );
541 p_playlist->p_input->b_eof = 1;
544 vlc_mutex_unlock( &p_playlist->object_lock );
546 msleep( INTF_IDLE_SLEEP );
550 /*****************************************************************************
551 * SkipItem: go to Xth playlist item
552 *****************************************************************************
553 * This function calculates the position of the next playlist item, depending
554 * on the playlist course mode (forward, backward, random...).
555 *****************************************************************************/
556 static void SkipItem( playlist_t *p_playlist, int i_arg )
558 int i_oldindex = p_playlist->i_index;
561 /* If the playlist is empty, there is no current item */
562 if( p_playlist->i_size == 0 )
564 p_playlist->i_index = -1;
568 b_random = config_GetInt( p_playlist, "random" );
573 srand( (unsigned int)mdate() );
575 /* Simple random stuff - we cheat a bit to minimize the chances to
576 * get the same index again. */
577 i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
580 i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
584 p_playlist->i_index += i_arg;
587 if( p_playlist->i_index >= p_playlist->i_size )
589 if( p_playlist->i_status == PLAYLIST_STOPPED
591 || config_GetInt( p_playlist, "loop" ) )
593 p_playlist->i_index -= p_playlist->i_size
594 * ( p_playlist->i_index / p_playlist->i_size );
598 /* Don't loop by default: stop at playlist end */
599 p_playlist->i_index = i_oldindex;
600 p_playlist->i_status = PLAYLIST_STOPPED;
603 else if( p_playlist->i_index < 0 )
605 p_playlist->i_index = p_playlist->i_size - 1;
609 /*****************************************************************************
610 * PlayItem: play current playlist item
611 *****************************************************************************
612 * This function calculates the position of the next playlist item, depending
613 * on the playlist course mode (forward, backward, random...).
614 *****************************************************************************/
615 static void PlayItem( playlist_t *p_playlist )
617 if( p_playlist->i_index == -1 )
619 if( p_playlist->i_size == 0 )
624 SkipItem( p_playlist, 1 );
627 msg_Dbg( p_playlist, "creating new input thread" );
628 p_playlist->p_input = input_CreateThread( p_playlist,
629 p_playlist->pp_items[p_playlist->i_index] );
632 /*****************************************************************************
633 * Poubellize: put an input thread in the trashcan
634 *****************************************************************************
636 *****************************************************************************/
637 static void Poubellize ( playlist_t *p_playlist, input_thread_t *p_input )
639 msg_Dbg( p_playlist, "poubellizing input %i\n", p_input->i_object_id );
642 /*****************************************************************************
643 * playlist_LoadFile: load a playlist file.
644 ****************************************************************************/
645 int playlist_LoadFile( playlist_t * p_playlist, const char *psz_filename )
649 int i_current_status;
652 msg_Dbg( p_playlist, "opening playlist file %s", psz_filename );
654 file = fopen( psz_filename, "rt" );
657 msg_Err( p_playlist, "playlist file %s does not exist", psz_filename );
660 fseek( file, 0L, SEEK_SET );
662 /* check the file is not empty */
663 if ( ! fgets( line, 1024, file ) )
665 msg_Err( p_playlist, "playlist file %s is empty", psz_filename );
670 /* get rid of line feed */
671 if( line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r' )
673 line[strlen(line)-1] = (char)0;
674 if( line[strlen(line)-1] == '\r' ) line[strlen(line)-1] = (char)0;
676 /* check the file format is valid */
677 if ( strcmp ( line , PLAYLIST_FILE_HEADER_0_5 ) )
679 msg_Err( p_playlist, "playlist file %s format is unsupported"
686 i_current_status = p_playlist->i_status;
687 if ( p_playlist->i_status != PLAYLIST_STOPPED )
689 playlist_Stop ( p_playlist );
692 /* delete current content of the playlist */
693 for( i = p_playlist->i_size - 1; i >= 0; i-- )
695 playlist_Delete ( p_playlist , i );
698 /* simply add each line */
699 while( fgets( line, 1024, file ) )
701 /* ignore comments or empty lines */
702 if( (line[0] == '#') || (line[0] == '\r') || (line[0] == '\n')
703 || (line[0] == (char)0) )
706 /* get rid of line feed */
707 if( line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r' )
709 line[strlen(line)-1] = (char)0;
710 if( line[strlen(line)-1] == '\r' ) line[strlen(line)-1] = (char)0;
713 playlist_Add ( p_playlist , (char*) &line , PLAYLIST_APPEND , PLAYLIST_END );
717 if ( i_current_status != PLAYLIST_STOPPED )
719 playlist_Play ( p_playlist );
727 /*****************************************************************************
728 * playlist_SaveFile: Save a playlist in a file.
729 *****************************************************************************/
730 int playlist_SaveFile( playlist_t * p_playlist, const char * psz_filename )
735 vlc_mutex_lock( &p_playlist->object_lock );
737 msg_Dbg( p_playlist, "saving playlist file %s", psz_filename );
739 file = fopen( psz_filename, "wt" );
742 msg_Err( p_playlist , "could not create playlist file %s"
747 fprintf( file , PLAYLIST_FILE_HEADER_0_5 "\n" );
749 for ( i = 0 ; i < p_playlist->i_size ; i++ )
751 fprintf( file , p_playlist->pp_items[i]->psz_uri );
752 fprintf( file , "\n" );
757 vlc_mutex_unlock( &p_playlist->object_lock );