* src/playlist/playlist.c: fixed misplaced variable declaration.
[vlc.git] / src / playlist / playlist.c
1 /*****************************************************************************
2  * playlist.c : Playlist management functions
3  *****************************************************************************
4  * Copyright (C) 1999-2001 VideoLAN
5  * $Id: playlist.c,v 1.34 2003/03/18 00:49:14 gbazin Exp $
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *
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.
13  *
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.
18  *
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() */
26
27 #include <vlc/vlc.h>
28
29 #include "stream_control.h"
30 #include "input_ext-intf.h"
31
32 #include "vlc_playlist.h"
33
34 #define PLAYLIST_FILE_HEADER_0_5  "# vlc playlist file version 0.5"
35 #ifdef WIN32
36 #   define PLAYLIST_FILE_EOL "\r\n"
37 #else
38 #   define PLAYLIST_FILE_EOL "\n"
39 #endif
40
41 /*****************************************************************************
42  * Local prototypes
43  *****************************************************************************/
44 static void RunThread ( playlist_t * );
45 static void SkipItem  ( playlist_t *, int );
46 static void PlayItem  ( playlist_t * );
47
48 static void Poubellize ( playlist_t *, input_thread_t * );
49
50 /*****************************************************************************
51  * playlist_Create: create playlist
52  *****************************************************************************
53  * Create a playlist structure.
54  *****************************************************************************/
55 playlist_t * __playlist_Create ( vlc_object_t *p_parent )
56 {
57     playlist_t *p_playlist;
58     vlc_value_t     val;
59
60     /* Allocate structure */
61     p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
62     if( !p_playlist )
63     {
64         msg_Err( p_parent, "out of memory" );
65         return NULL;
66     }
67
68     var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
69     val.b_bool = VLC_TRUE;
70     var_Set( p_playlist, "intf-change", val );
71
72     p_playlist->p_input = NULL;
73     p_playlist->i_status = PLAYLIST_STOPPED;
74     p_playlist->i_index = -1;
75     p_playlist->i_size = 0;
76     p_playlist->pp_items = NULL;
77
78     if( vlc_thread_create( p_playlist, "playlist", RunThread,
79                            VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
80     {
81         msg_Err( p_playlist, "cannot spawn playlist thread" );
82         vlc_object_destroy( p_playlist );
83         return NULL;
84     }
85
86     /* The object has been initialized, now attach it */
87     vlc_object_attach( p_playlist, p_parent );
88
89     return p_playlist;
90 }
91
92 /*****************************************************************************
93  * playlist_Destroy: destroy the playlist
94  *****************************************************************************
95  * Delete all items in the playlist and free the playlist structure.
96  *****************************************************************************/
97 void playlist_Destroy( playlist_t * p_playlist )
98 {
99     p_playlist->b_die = 1;
100
101     vlc_thread_join( p_playlist );
102
103     var_Destroy( p_playlist, "intf-change" );
104
105     vlc_object_destroy( p_playlist );
106 }
107
108 /*****************************************************************************
109  * playlist_Add: add an item to the playlist
110  *****************************************************************************
111  * Add an item to the playlist at position i_pos. If i_pos is PLAYLIST_END,
112  * add it at the end regardless of the playlist current size.
113  *****************************************************************************/
114 int playlist_Add( playlist_t *p_playlist, const char * psz_target,
115                                           int i_mode, int i_pos )
116 {
117     playlist_item_t * p_item;
118
119     p_item = malloc( sizeof( playlist_item_t ) );
120     if( p_item == NULL )
121     {
122         msg_Err( p_playlist, "out of memory" );
123     }
124
125     p_item->psz_name = strdup( psz_target );
126     p_item->psz_uri  = strdup( psz_target );
127     p_item->i_type = 0;
128     p_item->i_status = 0;
129     p_item->b_autodeletion = VLC_FALSE;
130
131     return playlist_AddItem( p_playlist, p_item, i_mode, i_pos );
132 }
133
134
135 int playlist_AddItem( playlist_t *p_playlist, playlist_item_t * p_item,
136                 int i_mode, int i_pos)
137 {
138     vlc_value_t     val;
139
140     vlc_mutex_lock( &p_playlist->object_lock );
141
142     /*
143      * CHECK_INSERT : checks if the item is already enqued before
144      * enqueing it
145      */
146     if ( i_mode & PLAYLIST_CHECK_INSERT )
147     {
148          int j;
149
150          if ( p_playlist->pp_items )
151          {
152              for ( j = 0; j < p_playlist->i_size; j++ )
153              {
154                  if ( !strcmp( p_playlist->pp_items[j]->psz_uri, p_item->psz_uri ) )
155                  {
156                       if( p_item->psz_name )
157                       {
158                           free( p_item->psz_name );
159                       }
160                       if( p_item->psz_uri )
161                       {
162                           free( p_item->psz_uri );
163                       }
164                       free( p_item );
165                       vlc_mutex_unlock( &p_playlist->object_lock );
166                       return 0;
167                  }
168              }
169          }
170          i_mode &= ~PLAYLIST_CHECK_INSERT;
171          i_mode |= PLAYLIST_APPEND;
172     }
173
174
175     msg_Dbg( p_playlist, "adding playlist item « %s »", p_item->psz_name );
176
177     /* Create the new playlist item */
178
179
180     /* Do a few boundary checks and allocate space for the item */
181     if( i_pos == PLAYLIST_END )
182     {
183         if( i_mode & PLAYLIST_INSERT )
184         {
185             i_mode &= ~PLAYLIST_INSERT;
186             i_mode |= PLAYLIST_APPEND;
187         }
188
189         i_pos = p_playlist->i_size - 1;
190     }
191
192     if( !(i_mode & PLAYLIST_REPLACE)
193          || i_pos < 0 || i_pos >= p_playlist->i_size )
194     {
195         /* Additional boundary checks */
196         if( i_mode & PLAYLIST_APPEND )
197         {
198             i_pos++;
199         }
200
201         if( i_pos < 0 )
202         {
203             i_pos = 0;
204         }
205         else if( i_pos > p_playlist->i_size )
206         {
207             i_pos = p_playlist->i_size;
208         }
209
210         INSERT_ELEM( p_playlist->pp_items,
211                      p_playlist->i_size,
212                      i_pos,
213                      p_item );
214
215         if( p_playlist->i_index >= i_pos )
216         {
217             p_playlist->i_index++;
218         }
219     }
220     else
221     {
222         /* i_mode == PLAYLIST_REPLACE and 0 <= i_pos < p_playlist->i_size */
223         if( p_playlist->pp_items[i_pos]->psz_name )
224         {
225             free( p_playlist->pp_items[i_pos]->psz_name );
226         }
227         if( p_playlist->pp_items[i_pos]->psz_uri )
228         {
229             free( p_playlist->pp_items[i_pos]->psz_uri );
230         }
231         /* XXX: what if the item is still in use? */
232         free( p_playlist->pp_items[i_pos] );
233         p_playlist->pp_items[i_pos] = p_item;
234     }
235
236     if( i_mode & PLAYLIST_GO )
237     {
238         p_playlist->i_index = i_pos;
239         if( p_playlist->p_input )
240         {
241             input_StopThread( p_playlist->p_input );
242         }
243         p_playlist->i_status = PLAYLIST_RUNNING;
244     }
245
246     vlc_mutex_unlock( &p_playlist->object_lock );
247
248     val.b_bool = VLC_TRUE;
249     var_Set( p_playlist, "intf-change", val );
250
251     return 0;
252 }
253
254 /*****************************************************************************
255  * playlist_Delete: delete an item from the playlist
256  *****************************************************************************
257  * Delete the item in the playlist with position i_pos.
258  *****************************************************************************/
259 int playlist_Delete( playlist_t * p_playlist, int i_pos )
260 {
261     vlc_value_t     val;
262     vlc_mutex_lock( &p_playlist->object_lock );
263
264     if( i_pos >= 0 && i_pos < p_playlist->i_size )
265     {
266         msg_Dbg( p_playlist, "deleting playlist item « %s »",
267                              p_playlist->pp_items[i_pos]->psz_name );
268
269         if( p_playlist->pp_items[i_pos]->psz_name )
270         {
271             free( p_playlist->pp_items[i_pos]->psz_name );
272         }
273         if( p_playlist->pp_items[i_pos]->psz_uri )
274         {
275             free( p_playlist->pp_items[i_pos]->psz_uri );
276         }
277
278         /* XXX: what if the item is still in use? */
279         free( p_playlist->pp_items[i_pos] );
280
281         if( i_pos <= p_playlist->i_index )
282         {
283             p_playlist->i_index--;
284         }
285
286         /* Renumber the playlist */
287         REMOVE_ELEM( p_playlist->pp_items,
288                      p_playlist->i_size,
289                      i_pos );
290     }
291
292     vlc_mutex_unlock( &p_playlist->object_lock );
293
294     val.b_bool = VLC_TRUE;
295     var_Set( p_playlist, "intf-change", val );
296
297     return 0;
298 }
299
300 /*****************************************************************************
301  * playlist_Move: move an item in the playlist
302  *****************************************************************************
303  * Move the item in the playlist with position i_pos before the current item
304  * at position i_newpos.
305  *****************************************************************************/
306 int playlist_Move( playlist_t * p_playlist, int i_pos, int i_newpos)
307 {
308     vlc_value_t     val;
309     vlc_mutex_lock( &p_playlist->object_lock );
310
311     /* take into account that our own row disappears. */
312     if ( i_pos < i_newpos ) i_newpos--;
313
314     if( i_pos >= 0 && i_newpos >=0 && i_pos <= p_playlist->i_size 
315                      && i_newpos <= p_playlist->i_size )
316     {
317         playlist_item_t * temp;
318
319         msg_Dbg( p_playlist, "moving playlist item « %s »",
320                              p_playlist->pp_items[i_pos]->psz_name );
321
322         if( i_pos == p_playlist->i_index )
323         {
324             p_playlist->i_index = i_newpos;
325         }
326         else if( i_pos > p_playlist->i_index && i_newpos <= p_playlist->i_index )
327         {
328             p_playlist->i_index++;
329         }
330         else if( i_pos < p_playlist->i_index && i_newpos >= p_playlist->i_index )
331         {
332             p_playlist->i_index--;
333         }
334
335         if ( i_pos < i_newpos )
336         {
337             temp = p_playlist->pp_items[i_pos];
338             while ( i_pos < i_newpos )
339             {
340                 p_playlist->pp_items[i_pos] = p_playlist->pp_items[i_pos+1];
341                 i_pos++;
342             }
343             p_playlist->pp_items[i_newpos] = temp;
344         }
345         else if ( i_pos > i_newpos )
346         {
347             temp = p_playlist->pp_items[i_pos];
348             while ( i_pos > i_newpos )
349             {
350                 p_playlist->pp_items[i_pos] = p_playlist->pp_items[i_pos-1];
351                 i_pos--;
352             }
353             p_playlist->pp_items[i_newpos] = temp;
354         }
355     }
356
357     vlc_mutex_unlock( &p_playlist->object_lock );
358
359     val.b_bool = VLC_TRUE;
360     var_Set( p_playlist, "intf-change", val );
361
362     return 0;
363 }
364
365 /*****************************************************************************
366  * playlist_Command: do a playlist action
367  *****************************************************************************
368  *
369  *****************************************************************************/
370 void playlist_Command( playlist_t * p_playlist, int i_command, int i_arg )
371 {
372     vlc_mutex_lock( &p_playlist->object_lock );
373
374     switch( i_command )
375     {
376     case PLAYLIST_STOP:
377         p_playlist->i_status = PLAYLIST_STOPPED;
378         if( p_playlist->p_input )
379         {
380             input_StopThread( p_playlist->p_input );
381         }
382         break;
383
384     case PLAYLIST_PLAY:
385         p_playlist->i_status = PLAYLIST_RUNNING;
386         if( p_playlist->p_input )
387         {
388             input_SetStatus( p_playlist->p_input, INPUT_STATUS_PLAY );
389         }
390         break;
391
392     case PLAYLIST_PAUSE:
393         p_playlist->i_status = PLAYLIST_PAUSED;
394         if( p_playlist->p_input )
395         {
396             input_SetStatus( p_playlist->p_input, INPUT_STATUS_PAUSE );
397         }
398         break;
399
400     case PLAYLIST_SKIP:
401         p_playlist->i_status = PLAYLIST_STOPPED;
402         SkipItem( p_playlist, i_arg );
403         if( p_playlist->p_input )
404         {
405             input_StopThread( p_playlist->p_input );
406         }
407         p_playlist->i_status = PLAYLIST_RUNNING;
408         break;
409
410     case PLAYLIST_GOTO:
411         if( i_arg >= 0 && i_arg < p_playlist->i_size )
412         {
413             p_playlist->i_index = i_arg;
414             if( p_playlist->p_input )
415             {
416                 input_StopThread( p_playlist->p_input );
417             }
418             p_playlist->i_status = PLAYLIST_RUNNING;
419         }
420         break;
421
422     default:
423         msg_Err( p_playlist, "unknown playlist command" );
424         break;
425     }
426
427     vlc_mutex_unlock( &p_playlist->object_lock );
428
429     return;
430 }
431
432 /* Following functions are local */
433
434 /*****************************************************************************
435  * RunThread: main playlist thread
436  *****************************************************************************/
437 static void RunThread ( playlist_t *p_playlist )
438 {
439     /* Tell above that we're ready */
440     vlc_thread_ready( p_playlist );
441
442     while( !p_playlist->b_die )
443     {
444         vlc_mutex_lock( &p_playlist->object_lock );
445
446         /* If there is an input, check that it doesn't need to die. */
447         if( p_playlist->p_input )
448         {
449             /* This input is dead. Remove it ! */
450             if( p_playlist->p_input->b_dead )
451             {
452                 input_thread_t *p_input;
453
454                 /* Unlink current input */
455                 p_input = p_playlist->p_input;
456                 p_playlist->p_input = NULL;
457                 vlc_object_detach( p_input );
458
459                 /* Release the playlist lock, because we may get stuck
460                  * in input_DestroyThread() for some time. */
461                 vlc_mutex_unlock( &p_playlist->object_lock );
462
463                 /* Destroy input */
464                 input_DestroyThread( p_input );
465                 vlc_object_destroy( p_input );
466                 continue;
467             }
468             /* This input is dying, let him do */
469             else if( p_playlist->p_input->b_die )
470             {
471                 ;
472             }
473             /* This input has finished, ask him to die ! */
474             else if( p_playlist->p_input->b_error
475                       || p_playlist->p_input->b_eof )
476             {
477                 /* Check for autodeletion */
478                 if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
479                 {
480                     vlc_mutex_unlock( &p_playlist->object_lock );
481                     playlist_Delete( p_playlist, p_playlist->i_index );
482                     vlc_mutex_lock( &p_playlist->object_lock );
483                 }
484
485                 /* Select the next playlist item */
486                 SkipItem( p_playlist, 1 );
487
488                 /* Release the playlist lock, because we may get stuck
489                  * in input_StopThread() for some time. */
490                 vlc_mutex_unlock( &p_playlist->object_lock );
491                 input_StopThread( p_playlist->p_input );
492                 continue;
493             }
494         }
495         else if( p_playlist->i_status != PLAYLIST_STOPPED )
496         {
497             PlayItem( p_playlist );
498         }
499
500         vlc_mutex_unlock( &p_playlist->object_lock );
501
502         msleep( INTF_IDLE_SLEEP );
503     }
504
505     /* If there is an input, kill it */
506     while( 1 )
507     {
508         vlc_mutex_lock( &p_playlist->object_lock );
509
510         if( p_playlist->p_input == NULL )
511         {
512             vlc_mutex_unlock( &p_playlist->object_lock );
513             break;
514         }
515
516         if( p_playlist->p_input->b_dead )
517         {
518             input_thread_t *p_input;
519
520             /* Unlink current input */
521             p_input = p_playlist->p_input;
522             p_playlist->p_input = NULL;
523             vlc_object_detach( p_input );
524             vlc_mutex_unlock( &p_playlist->object_lock );
525
526             /* Destroy input */
527             input_DestroyThread( p_input );
528             vlc_object_destroy( p_input );
529             continue;
530         }
531         else if( p_playlist->p_input->b_die )
532         {
533             /* This input is dying, leave him alone */
534             ;
535         }
536         else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
537         {
538             vlc_mutex_unlock( &p_playlist->object_lock );
539             input_StopThread( p_playlist->p_input );
540             continue;
541         }
542         else
543         {
544             p_playlist->p_input->b_eof = 1;
545         }
546
547         vlc_mutex_unlock( &p_playlist->object_lock );
548
549         msleep( INTF_IDLE_SLEEP );
550     }
551 }
552
553 /*****************************************************************************
554  * SkipItem: go to Xth playlist item
555  *****************************************************************************
556  * This function calculates the position of the next playlist item, depending
557  * on the playlist course mode (forward, backward, random...).
558  *****************************************************************************/
559 static void SkipItem( playlist_t *p_playlist, int i_arg )
560 {
561     int i_oldindex = p_playlist->i_index;
562     vlc_bool_t b_random;
563
564     /* If the playlist is empty, there is no current item */
565     if( p_playlist->i_size == 0 )
566     {
567         p_playlist->i_index = -1;
568         return;
569     }
570
571     b_random = config_GetInt( p_playlist, "random" );
572
573     /* Increment */
574     if( b_random )
575     {
576         srand( (unsigned int)mdate() );
577
578         /* Simple random stuff - we cheat a bit to minimize the chances to
579          * get the same index again. */
580         i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
581         if( i_arg == 0 )
582         {
583             i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
584         }
585     }
586
587     p_playlist->i_index += i_arg;
588
589     /* Boundary check */
590     if( p_playlist->i_index >= p_playlist->i_size )
591     {
592         if( p_playlist->i_status == PLAYLIST_STOPPED
593              || b_random
594              || config_GetInt( p_playlist, "loop" ) )
595         {
596             p_playlist->i_index -= p_playlist->i_size
597                          * ( p_playlist->i_index / p_playlist->i_size );
598         }
599         else
600         {
601             /* Don't loop by default: stop at playlist end */
602             p_playlist->i_index = i_oldindex;
603             p_playlist->i_status = PLAYLIST_STOPPED;
604         }
605     }
606     else if( p_playlist->i_index < 0 )
607     {
608         p_playlist->i_index = p_playlist->i_size - 1;
609     }
610 }
611
612 /*****************************************************************************
613  * PlayItem: play current playlist item
614  *****************************************************************************
615  * This function calculates the position of the next playlist item, depending
616  * on the playlist course mode (forward, backward, random...).
617  *****************************************************************************/
618 static void PlayItem( playlist_t *p_playlist )
619 {
620     if( p_playlist->i_index == -1 )
621     {
622         if( p_playlist->i_size == 0 )
623         {
624             return;
625         }
626
627         SkipItem( p_playlist, 1 );
628     }
629
630     msg_Dbg( p_playlist, "creating new input thread" );
631     p_playlist->p_input = input_CreateThread( p_playlist,
632                                   p_playlist->pp_items[p_playlist->i_index] );
633 }
634
635 /*****************************************************************************
636  * Poubellize: put an input thread in the trashcan
637  *****************************************************************************
638  * XXX: unused
639  *****************************************************************************/
640 static void Poubellize ( playlist_t *p_playlist, input_thread_t *p_input )
641 {
642     msg_Dbg( p_playlist, "poubellizing input %i\n", p_input->i_object_id );
643 }
644
645 /*****************************************************************************
646  * playlist_LoadFile: load a playlist file.
647  ****************************************************************************/
648 int playlist_LoadFile( playlist_t * p_playlist, const char *psz_filename )
649 {
650     FILE *file;
651     char line[1024];
652     int i_current_status;
653     int i;
654
655     msg_Dbg( p_playlist, "opening playlist file %s", psz_filename );
656
657     file = fopen( psz_filename, "rt" );
658     if( !file )
659     {
660         msg_Err( p_playlist, "playlist file %s does not exist", psz_filename );
661         return -1;
662     }
663     fseek( file, 0L, SEEK_SET );
664
665     /* check the file is not empty */
666     if ( ! fgets( line, 1024, file ) )
667     {
668         msg_Err( p_playlist, "playlist file %s is empty", psz_filename );
669         fclose( file );
670         return -1;
671     }
672
673     /* get rid of line feed */
674     if( line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r' )
675     {
676        line[strlen(line)-1] = (char)0;
677        if( line[strlen(line)-1] == '\r' ) line[strlen(line)-1] = (char)0;
678     }
679     /* check the file format is valid */
680     if ( strcmp ( line , PLAYLIST_FILE_HEADER_0_5 ) )
681     {
682         msg_Err( p_playlist, "playlist file %s format is unsupported"
683                 , psz_filename );
684         fclose( file );
685         return -1;
686     }
687
688     /* stop playing */
689     i_current_status = p_playlist->i_status;
690     if ( p_playlist->i_status != PLAYLIST_STOPPED )
691     {
692         playlist_Stop ( p_playlist );
693     }
694
695     /* delete current content of the playlist */
696     for( i = p_playlist->i_size - 1; i >= 0; i-- )
697     {
698         playlist_Delete ( p_playlist , i );
699     }
700
701     /* simply add each line */
702     while( fgets( line, 1024, file ) )
703     {
704        /* ignore comments or empty lines */
705        if( (line[0] == '#') || (line[0] == '\r') || (line[0] == '\n')
706                || (line[0] == (char)0) )
707            continue;
708
709        /* get rid of line feed */
710        if( line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r' )
711        {
712            line[strlen(line)-1] = (char)0;
713            if( line[strlen(line)-1] == '\r' ) line[strlen(line)-1] = (char)0;
714        }
715
716        playlist_Add ( p_playlist , (char*) &line , PLAYLIST_APPEND , PLAYLIST_END );
717     }
718
719     /* start playing */
720     if ( i_current_status != PLAYLIST_STOPPED )
721     {
722         playlist_Play ( p_playlist );
723     }
724
725     fclose( file );
726
727     return 0;
728 }
729
730 /*****************************************************************************
731  * playlist_SaveFile: Save a playlist in a file.
732  *****************************************************************************/
733 int playlist_SaveFile( playlist_t * p_playlist, const char * psz_filename )
734 {
735     FILE *file;
736     int i;
737
738     vlc_mutex_lock( &p_playlist->object_lock );
739
740     msg_Dbg( p_playlist, "saving playlist file %s", psz_filename );
741
742     file = fopen( psz_filename, "wt" );
743     if( !file )
744     {
745         msg_Err( p_playlist , "could not create playlist file %s"
746                 , psz_filename );
747         return -1;
748     }
749
750     fprintf( file , PLAYLIST_FILE_HEADER_0_5 PLAYLIST_FILE_EOL );
751
752     for ( i = 0 ; i < p_playlist->i_size ; i++ )
753     {
754         fprintf( file , p_playlist->pp_items[i]->psz_uri );
755         fprintf( file , PLAYLIST_FILE_EOL );
756     }
757
758     fclose( file );
759
760     vlc_mutex_unlock( &p_playlist->object_lock );
761
762     return 0;
763 }