1 /*****************************************************************************
2 * playlist.c : Playlist management functions
3 *****************************************************************************
4 * Copyright (C) 1999-2001 VideoLAN
5 * $Id: playlist.c,v 1.35 2003/05/12 17:33:20 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 p_playlist->p_input = NULL;
68 p_playlist->i_status = PLAYLIST_STOPPED;
69 p_playlist->i_index = -1;
70 p_playlist->i_size = 0;
71 p_playlist->pp_items = NULL;
73 if( vlc_thread_create( p_playlist, "playlist", RunThread,
74 VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
76 msg_Err( p_playlist, "cannot spawn playlist thread" );
77 vlc_object_destroy( p_playlist );
81 /* The object has been initialized, now attach it */
82 vlc_object_attach( p_playlist, p_parent );
87 /*****************************************************************************
88 * playlist_Destroy: destroy the playlist
89 *****************************************************************************
90 * Delete all items in the playlist and free the playlist structure.
91 *****************************************************************************/
92 void playlist_Destroy( playlist_t * p_playlist )
94 p_playlist->b_die = 1;
96 vlc_thread_join( p_playlist );
98 var_Destroy( p_playlist, "intf-change" );
100 vlc_object_destroy( p_playlist );
103 /*****************************************************************************
104 * playlist_Add: add an item to the playlist
105 *****************************************************************************
106 * Add an item to the playlist at position i_pos. If i_pos is PLAYLIST_END,
107 * add it at the end regardless of the playlist current size.
108 *****************************************************************************/
109 int playlist_Add( playlist_t *p_playlist, const char * psz_target,
110 int i_mode, int i_pos )
112 playlist_item_t * p_item;
114 p_item = malloc( sizeof( playlist_item_t ) );
117 msg_Err( p_playlist, "out of memory" );
120 p_item->psz_name = strdup( psz_target );
121 p_item->psz_uri = strdup( psz_target );
123 p_item->i_status = 0;
124 p_item->b_autodeletion = VLC_FALSE;
126 return playlist_AddItem( p_playlist, p_item, i_mode, i_pos );
130 int playlist_AddItem( playlist_t *p_playlist, playlist_item_t * p_item,
131 int i_mode, int i_pos)
135 vlc_mutex_lock( &p_playlist->object_lock );
138 * CHECK_INSERT : checks if the item is already enqued before
141 if ( i_mode & PLAYLIST_CHECK_INSERT )
145 if ( p_playlist->pp_items )
147 for ( j = 0; j < p_playlist->i_size; j++ )
149 if ( !strcmp( p_playlist->pp_items[j]->psz_uri, p_item->psz_uri ) )
151 if( p_item->psz_name )
153 free( p_item->psz_name );
155 if( p_item->psz_uri )
157 free( p_item->psz_uri );
160 vlc_mutex_unlock( &p_playlist->object_lock );
165 i_mode &= ~PLAYLIST_CHECK_INSERT;
166 i_mode |= PLAYLIST_APPEND;
170 msg_Dbg( p_playlist, "adding playlist item « %s »", p_item->psz_name );
172 /* Create the new playlist item */
175 /* Do a few boundary checks and allocate space for the item */
176 if( i_pos == PLAYLIST_END )
178 if( i_mode & PLAYLIST_INSERT )
180 i_mode &= ~PLAYLIST_INSERT;
181 i_mode |= PLAYLIST_APPEND;
184 i_pos = p_playlist->i_size - 1;
187 if( !(i_mode & PLAYLIST_REPLACE)
188 || i_pos < 0 || i_pos >= p_playlist->i_size )
190 /* Additional boundary checks */
191 if( i_mode & PLAYLIST_APPEND )
200 else if( i_pos > p_playlist->i_size )
202 i_pos = p_playlist->i_size;
205 INSERT_ELEM( p_playlist->pp_items,
210 if( p_playlist->i_index >= i_pos )
212 p_playlist->i_index++;
217 /* i_mode == PLAYLIST_REPLACE and 0 <= i_pos < p_playlist->i_size */
218 if( p_playlist->pp_items[i_pos]->psz_name )
220 free( p_playlist->pp_items[i_pos]->psz_name );
222 if( p_playlist->pp_items[i_pos]->psz_uri )
224 free( p_playlist->pp_items[i_pos]->psz_uri );
226 /* XXX: what if the item is still in use? */
227 free( p_playlist->pp_items[i_pos] );
228 p_playlist->pp_items[i_pos] = p_item;
231 if( i_mode & PLAYLIST_GO )
233 p_playlist->i_index = i_pos;
234 if( p_playlist->p_input )
236 input_StopThread( p_playlist->p_input );
238 p_playlist->i_status = PLAYLIST_RUNNING;
241 vlc_mutex_unlock( &p_playlist->object_lock );
243 val.b_bool = VLC_TRUE;
244 var_Set( p_playlist, "intf-change", val );
249 /*****************************************************************************
250 * playlist_Delete: delete an item from the playlist
251 *****************************************************************************
252 * Delete the item in the playlist with position i_pos.
253 *****************************************************************************/
254 int playlist_Delete( playlist_t * p_playlist, int i_pos )
257 vlc_mutex_lock( &p_playlist->object_lock );
259 if( i_pos >= 0 && i_pos < p_playlist->i_size )
261 msg_Dbg( p_playlist, "deleting playlist item « %s »",
262 p_playlist->pp_items[i_pos]->psz_name );
264 if( p_playlist->pp_items[i_pos]->psz_name )
266 free( p_playlist->pp_items[i_pos]->psz_name );
268 if( p_playlist->pp_items[i_pos]->psz_uri )
270 free( p_playlist->pp_items[i_pos]->psz_uri );
273 /* XXX: what if the item is still in use? */
274 free( p_playlist->pp_items[i_pos] );
276 if( i_pos <= p_playlist->i_index )
278 p_playlist->i_index--;
281 /* Renumber the playlist */
282 REMOVE_ELEM( p_playlist->pp_items,
287 vlc_mutex_unlock( &p_playlist->object_lock );
289 val.b_bool = VLC_TRUE;
290 var_Set( p_playlist, "intf-change", val );
295 /*****************************************************************************
296 * playlist_Move: move an item in the playlist
297 *****************************************************************************
298 * Move the item in the playlist with position i_pos before the current item
299 * at position i_newpos.
300 *****************************************************************************/
301 int playlist_Move( playlist_t * p_playlist, int i_pos, int i_newpos)
304 vlc_mutex_lock( &p_playlist->object_lock );
306 /* take into account that our own row disappears. */
307 if ( i_pos < i_newpos ) i_newpos--;
309 if( i_pos >= 0 && i_newpos >=0 && i_pos <= p_playlist->i_size
310 && i_newpos <= p_playlist->i_size )
312 playlist_item_t * temp;
314 msg_Dbg( p_playlist, "moving playlist item « %s »",
315 p_playlist->pp_items[i_pos]->psz_name );
317 if( i_pos == p_playlist->i_index )
319 p_playlist->i_index = i_newpos;
321 else if( i_pos > p_playlist->i_index && i_newpos <= p_playlist->i_index )
323 p_playlist->i_index++;
325 else if( i_pos < p_playlist->i_index && i_newpos >= p_playlist->i_index )
327 p_playlist->i_index--;
330 if ( i_pos < i_newpos )
332 temp = p_playlist->pp_items[i_pos];
333 while ( i_pos < i_newpos )
335 p_playlist->pp_items[i_pos] = p_playlist->pp_items[i_pos+1];
338 p_playlist->pp_items[i_newpos] = temp;
340 else if ( i_pos > i_newpos )
342 temp = p_playlist->pp_items[i_pos];
343 while ( i_pos > i_newpos )
345 p_playlist->pp_items[i_pos] = p_playlist->pp_items[i_pos-1];
348 p_playlist->pp_items[i_newpos] = temp;
352 vlc_mutex_unlock( &p_playlist->object_lock );
354 val.b_bool = VLC_TRUE;
355 var_Set( p_playlist, "intf-change", val );
360 /*****************************************************************************
361 * playlist_Command: do a playlist action
362 *****************************************************************************
364 *****************************************************************************/
365 void playlist_Command( playlist_t * p_playlist, int i_command, int i_arg )
367 vlc_mutex_lock( &p_playlist->object_lock );
372 p_playlist->i_status = PLAYLIST_STOPPED;
373 if( p_playlist->p_input )
375 input_StopThread( p_playlist->p_input );
380 p_playlist->i_status = PLAYLIST_RUNNING;
381 if( p_playlist->p_input )
383 input_SetStatus( p_playlist->p_input, INPUT_STATUS_PLAY );
388 p_playlist->i_status = PLAYLIST_PAUSED;
389 if( p_playlist->p_input )
391 input_SetStatus( p_playlist->p_input, INPUT_STATUS_PAUSE );
396 p_playlist->i_status = PLAYLIST_STOPPED;
397 SkipItem( p_playlist, i_arg );
398 if( p_playlist->p_input )
400 input_StopThread( p_playlist->p_input );
402 p_playlist->i_status = PLAYLIST_RUNNING;
406 if( i_arg >= 0 && i_arg < p_playlist->i_size )
408 p_playlist->i_index = i_arg;
409 if( p_playlist->p_input )
411 input_StopThread( p_playlist->p_input );
413 p_playlist->i_status = PLAYLIST_RUNNING;
418 msg_Err( p_playlist, "unknown playlist command" );
422 vlc_mutex_unlock( &p_playlist->object_lock );
427 /* Following functions are local */
429 /*****************************************************************************
430 * RunThread: main playlist thread
431 *****************************************************************************/
432 static void RunThread ( playlist_t *p_playlist )
434 /* Tell above that we're ready */
435 vlc_thread_ready( p_playlist );
437 while( !p_playlist->b_die )
439 vlc_mutex_lock( &p_playlist->object_lock );
441 /* If there is an input, check that it doesn't need to die. */
442 if( p_playlist->p_input )
444 /* This input is dead. Remove it ! */
445 if( p_playlist->p_input->b_dead )
447 input_thread_t *p_input;
449 /* Unlink current input */
450 p_input = p_playlist->p_input;
451 p_playlist->p_input = NULL;
452 vlc_object_detach( p_input );
454 /* Release the playlist lock, because we may get stuck
455 * in input_DestroyThread() for some time. */
456 vlc_mutex_unlock( &p_playlist->object_lock );
459 input_DestroyThread( p_input );
460 vlc_object_destroy( p_input );
463 /* This input is dying, let him do */
464 else if( p_playlist->p_input->b_die )
468 /* This input has finished, ask him to die ! */
469 else if( p_playlist->p_input->b_error
470 || p_playlist->p_input->b_eof )
472 /* Check for autodeletion */
473 if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
475 vlc_mutex_unlock( &p_playlist->object_lock );
476 playlist_Delete( p_playlist, p_playlist->i_index );
477 vlc_mutex_lock( &p_playlist->object_lock );
480 /* Select the next playlist item */
481 SkipItem( p_playlist, 1 );
483 /* Release the playlist lock, because we may get stuck
484 * in input_StopThread() for some time. */
485 vlc_mutex_unlock( &p_playlist->object_lock );
486 input_StopThread( p_playlist->p_input );
490 else if( p_playlist->i_status != PLAYLIST_STOPPED )
492 PlayItem( p_playlist );
495 vlc_mutex_unlock( &p_playlist->object_lock );
497 msleep( INTF_IDLE_SLEEP );
500 /* If there is an input, kill it */
503 vlc_mutex_lock( &p_playlist->object_lock );
505 if( p_playlist->p_input == NULL )
507 vlc_mutex_unlock( &p_playlist->object_lock );
511 if( p_playlist->p_input->b_dead )
513 input_thread_t *p_input;
515 /* Unlink current input */
516 p_input = p_playlist->p_input;
517 p_playlist->p_input = NULL;
518 vlc_object_detach( p_input );
519 vlc_mutex_unlock( &p_playlist->object_lock );
522 input_DestroyThread( p_input );
523 vlc_object_destroy( p_input );
526 else if( p_playlist->p_input->b_die )
528 /* This input is dying, leave him alone */
531 else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
533 vlc_mutex_unlock( &p_playlist->object_lock );
534 input_StopThread( p_playlist->p_input );
539 p_playlist->p_input->b_eof = 1;
542 vlc_mutex_unlock( &p_playlist->object_lock );
544 msleep( INTF_IDLE_SLEEP );
548 /*****************************************************************************
549 * SkipItem: go to Xth playlist item
550 *****************************************************************************
551 * This function calculates the position of the next playlist item, depending
552 * on the playlist course mode (forward, backward, random...).
553 *****************************************************************************/
554 static void SkipItem( playlist_t *p_playlist, int i_arg )
556 int i_oldindex = p_playlist->i_index;
559 /* If the playlist is empty, there is no current item */
560 if( p_playlist->i_size == 0 )
562 p_playlist->i_index = -1;
566 b_random = config_GetInt( p_playlist, "random" );
571 srand( (unsigned int)mdate() );
573 /* Simple random stuff - we cheat a bit to minimize the chances to
574 * get the same index again. */
575 i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
578 i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
582 p_playlist->i_index += i_arg;
585 if( p_playlist->i_index >= p_playlist->i_size )
587 if( p_playlist->i_status == PLAYLIST_STOPPED
589 || config_GetInt( p_playlist, "loop" ) )
591 p_playlist->i_index -= p_playlist->i_size
592 * ( p_playlist->i_index / p_playlist->i_size );
596 /* Don't loop by default: stop at playlist end */
597 p_playlist->i_index = i_oldindex;
598 p_playlist->i_status = PLAYLIST_STOPPED;
601 else if( p_playlist->i_index < 0 )
603 p_playlist->i_index = p_playlist->i_size - 1;
607 /*****************************************************************************
608 * PlayItem: play current playlist item
609 *****************************************************************************
610 * This function calculates the position of the next playlist item, depending
611 * on the playlist course mode (forward, backward, random...).
612 *****************************************************************************/
613 static void PlayItem( playlist_t *p_playlist )
615 if( p_playlist->i_index == -1 )
617 if( p_playlist->i_size == 0 )
622 SkipItem( p_playlist, 1 );
625 msg_Dbg( p_playlist, "creating new input thread" );
626 p_playlist->p_input = input_CreateThread( p_playlist,
627 p_playlist->pp_items[p_playlist->i_index] );
630 /*****************************************************************************
631 * Poubellize: put an input thread in the trashcan
632 *****************************************************************************
634 *****************************************************************************/
635 static void Poubellize ( playlist_t *p_playlist, input_thread_t *p_input )
637 msg_Dbg( p_playlist, "poubellizing input %i\n", p_input->i_object_id );
640 /*****************************************************************************
641 * playlist_LoadFile: load a playlist file.
642 ****************************************************************************/
643 int playlist_LoadFile( playlist_t * p_playlist, const char *psz_filename )
647 int i_current_status;
650 msg_Dbg( p_playlist, "opening playlist file %s", psz_filename );
652 file = fopen( psz_filename, "rt" );
655 msg_Err( p_playlist, "playlist file %s does not exist", psz_filename );
658 fseek( file, 0L, SEEK_SET );
660 /* check the file is not empty */
661 if ( ! fgets( line, 1024, file ) )
663 msg_Err( p_playlist, "playlist file %s is empty", psz_filename );
668 /* get rid of line feed */
669 if( line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r' )
671 line[strlen(line)-1] = (char)0;
672 if( line[strlen(line)-1] == '\r' ) line[strlen(line)-1] = (char)0;
674 /* check the file format is valid */
675 if ( strcmp ( line , PLAYLIST_FILE_HEADER_0_5 ) )
677 msg_Err( p_playlist, "playlist file %s format is unsupported"
684 i_current_status = p_playlist->i_status;
685 if ( p_playlist->i_status != PLAYLIST_STOPPED )
687 playlist_Stop ( p_playlist );
690 /* delete current content of the playlist */
691 for( i = p_playlist->i_size - 1; i >= 0; i-- )
693 playlist_Delete ( p_playlist , i );
696 /* simply add each line */
697 while( fgets( line, 1024, file ) )
699 /* ignore comments or empty lines */
700 if( (line[0] == '#') || (line[0] == '\r') || (line[0] == '\n')
701 || (line[0] == (char)0) )
704 /* get rid of line feed */
705 if( line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r' )
707 line[strlen(line)-1] = (char)0;
708 if( line[strlen(line)-1] == '\r' ) line[strlen(line)-1] = (char)0;
711 playlist_Add ( p_playlist , (char*) &line , PLAYLIST_APPEND , PLAYLIST_END );
715 if ( i_current_status != PLAYLIST_STOPPED )
717 playlist_Play ( p_playlist );
725 /*****************************************************************************
726 * playlist_SaveFile: Save a playlist in a file.
727 *****************************************************************************/
728 int playlist_SaveFile( playlist_t * p_playlist, const char * psz_filename )
733 vlc_mutex_lock( &p_playlist->object_lock );
735 msg_Dbg( p_playlist, "saving playlist file %s", psz_filename );
737 file = fopen( psz_filename, "wt" );
740 msg_Err( p_playlist , "could not create playlist file %s"
745 fprintf( file , PLAYLIST_FILE_HEADER_0_5 "\n" );
747 for ( i = 0 ; i < p_playlist->i_size ; i++ )
749 fprintf( file , p_playlist->pp_items[i]->psz_uri );
750 fprintf( file , "\n" );
755 vlc_mutex_unlock( &p_playlist->object_lock );