* src/playlist/playlist.c:
[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.32 2003/02/13 00:09:51 hartman 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_Command: do a playlist action
302  *****************************************************************************
303  *
304  *****************************************************************************/
305 void playlist_Command( playlist_t * p_playlist, int i_command, int i_arg )
306 {
307     vlc_mutex_lock( &p_playlist->object_lock );
308
309     switch( i_command )
310     {
311     case PLAYLIST_STOP:
312         p_playlist->i_status = PLAYLIST_STOPPED;
313         if( p_playlist->p_input )
314         {
315             input_StopThread( p_playlist->p_input );
316         }
317         break;
318
319     case PLAYLIST_PLAY:
320         p_playlist->i_status = PLAYLIST_RUNNING;
321         if( p_playlist->p_input )
322         {
323             input_SetStatus( p_playlist->p_input, INPUT_STATUS_PLAY );
324         }
325         break;
326
327     case PLAYLIST_PAUSE:
328         p_playlist->i_status = PLAYLIST_PAUSED;
329         if( p_playlist->p_input )
330         {
331             input_SetStatus( p_playlist->p_input, INPUT_STATUS_PAUSE );
332         }
333         break;
334
335     case PLAYLIST_SKIP:
336         p_playlist->i_status = PLAYLIST_STOPPED;
337         SkipItem( p_playlist, i_arg );
338         if( p_playlist->p_input )
339         {
340             input_StopThread( p_playlist->p_input );
341         }
342         p_playlist->i_status = PLAYLIST_RUNNING;
343         break;
344
345     case PLAYLIST_GOTO:
346         if( i_arg >= 0 && i_arg < p_playlist->i_size )
347         {
348             p_playlist->i_index = i_arg;
349             if( p_playlist->p_input )
350             {
351                 input_StopThread( p_playlist->p_input );
352             }
353             p_playlist->i_status = PLAYLIST_RUNNING;
354         }
355         break;
356
357     default:
358         msg_Err( p_playlist, "unknown playlist command" );
359         break;
360     }
361
362     vlc_mutex_unlock( &p_playlist->object_lock );
363
364     return;
365 }
366
367 /* Following functions are local */
368
369 /*****************************************************************************
370  * RunThread: main playlist thread
371  *****************************************************************************/
372 static void RunThread ( playlist_t *p_playlist )
373 {
374     /* Tell above that we're ready */
375     vlc_thread_ready( p_playlist );
376
377     while( !p_playlist->b_die )
378     {
379         vlc_mutex_lock( &p_playlist->object_lock );
380
381         /* If there is an input, check that it doesn't need to die. */
382         if( p_playlist->p_input )
383         {
384             /* This input is dead. Remove it ! */
385             if( p_playlist->p_input->b_dead )
386             {
387                 input_thread_t *p_input;
388
389                 /* Unlink current input */
390                 p_input = p_playlist->p_input;
391                 p_playlist->p_input = NULL;
392                 vlc_object_detach( p_input );
393
394                 /* Release the playlist lock, because we may get stuck
395                  * in input_DestroyThread() for some time. */
396                 vlc_mutex_unlock( &p_playlist->object_lock );
397
398                 /* Destroy input */
399                 input_DestroyThread( p_input );
400                 vlc_object_destroy( p_input );
401                 continue;
402             }
403             /* This input is dying, let him do */
404             else if( p_playlist->p_input->b_die )
405             {
406                 ;
407             }
408             /* This input has finished, ask him to die ! */
409             else if( p_playlist->p_input->b_error
410                       || p_playlist->p_input->b_eof )
411             {
412                 /* Check for autodeletion */
413                 if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
414                 {
415                     vlc_mutex_unlock( &p_playlist->object_lock );
416                     playlist_Delete( p_playlist, p_playlist->i_index );
417                     vlc_mutex_lock( &p_playlist->object_lock );
418                 }
419
420                 /* Select the next playlist item */
421                 SkipItem( p_playlist, 1 );
422
423                 /* Release the playlist lock, because we may get stuck
424                  * in input_StopThread() for some time. */
425                 vlc_mutex_unlock( &p_playlist->object_lock );
426                 input_StopThread( p_playlist->p_input );
427                 continue;
428             }
429         }
430         else if( p_playlist->i_status != PLAYLIST_STOPPED )
431         {
432             PlayItem( p_playlist );
433         }
434
435         vlc_mutex_unlock( &p_playlist->object_lock );
436
437         msleep( INTF_IDLE_SLEEP );
438     }
439
440     /* If there is an input, kill it */
441     while( 1 )
442     {
443         vlc_mutex_lock( &p_playlist->object_lock );
444
445         if( p_playlist->p_input == NULL )
446         {
447             vlc_mutex_unlock( &p_playlist->object_lock );
448             break;
449         }
450
451         if( p_playlist->p_input->b_dead )
452         {
453             input_thread_t *p_input;
454
455             /* Unlink current input */
456             p_input = p_playlist->p_input;
457             p_playlist->p_input = NULL;
458             vlc_object_detach( p_input );
459             vlc_mutex_unlock( &p_playlist->object_lock );
460
461             /* Destroy input */
462             input_DestroyThread( p_input );
463             vlc_object_destroy( p_input );
464             continue;
465         }
466         else if( p_playlist->p_input->b_die )
467         {
468             /* This input is dying, leave him alone */
469             ;
470         }
471         else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
472         {
473             vlc_mutex_unlock( &p_playlist->object_lock );
474             input_StopThread( p_playlist->p_input );
475             continue;
476         }
477         else
478         {
479             p_playlist->p_input->b_eof = 1;
480         }
481
482         vlc_mutex_unlock( &p_playlist->object_lock );
483
484         msleep( INTF_IDLE_SLEEP );
485     }
486 }
487
488 /*****************************************************************************
489  * SkipItem: go to Xth playlist item
490  *****************************************************************************
491  * This function calculates the position of the next playlist item, depending
492  * on the playlist course mode (forward, backward, random...).
493  *****************************************************************************/
494 static void SkipItem( playlist_t *p_playlist, int i_arg )
495 {
496     int i_oldindex = p_playlist->i_index;
497     vlc_bool_t b_random;
498
499     /* If the playlist is empty, there is no current item */
500     if( p_playlist->i_size == 0 )
501     {
502         p_playlist->i_index = -1;
503         return;
504     }
505
506     b_random = config_GetInt( p_playlist, "random" );
507
508     /* Increment */
509     if( b_random )
510     {
511         srand( (unsigned int)mdate() );
512
513         /* Simple random stuff - we cheat a bit to minimize the chances to
514          * get the same index again. */
515         i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
516         if( i_arg == 0 )
517         {
518             i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
519         }
520     }
521
522     p_playlist->i_index += i_arg;
523
524     /* Boundary check */
525     if( p_playlist->i_index >= p_playlist->i_size )
526     {
527         if( p_playlist->i_status == PLAYLIST_STOPPED
528              || b_random
529              || config_GetInt( p_playlist, "loop" ) )
530         {
531             p_playlist->i_index -= p_playlist->i_size
532                          * ( p_playlist->i_index / p_playlist->i_size );
533         }
534         else
535         {
536             /* Don't loop by default: stop at playlist end */
537             p_playlist->i_index = i_oldindex;
538             p_playlist->i_status = PLAYLIST_STOPPED;
539         }
540     }
541     else if( p_playlist->i_index < 0 )
542     {
543         p_playlist->i_index = p_playlist->i_size - 1;
544     }
545 }
546
547 /*****************************************************************************
548  * PlayItem: play current playlist item
549  *****************************************************************************
550  * This function calculates the position of the next playlist item, depending
551  * on the playlist course mode (forward, backward, random...).
552  *****************************************************************************/
553 static void PlayItem( playlist_t *p_playlist )
554 {
555     if( p_playlist->i_index == -1 )
556     {
557         if( p_playlist->i_size == 0 )
558         {
559             return;
560         }
561
562         SkipItem( p_playlist, 1 );
563     }
564
565     msg_Dbg( p_playlist, "creating new input thread" );
566     p_playlist->p_input = input_CreateThread( p_playlist,
567                                   p_playlist->pp_items[p_playlist->i_index] );
568 }
569
570 /*****************************************************************************
571  * Poubellize: put an input thread in the trashcan
572  *****************************************************************************
573  * XXX: unused
574  *****************************************************************************/
575 static void Poubellize ( playlist_t *p_playlist, input_thread_t *p_input )
576 {
577     msg_Dbg( p_playlist, "poubellizing input %i\n", p_input->i_object_id );
578 }
579
580 /*****************************************************************************
581  * playlist_LoadFile: load a playlist file.
582  ****************************************************************************/
583 int playlist_LoadFile( playlist_t * p_playlist, const char *psz_filename )
584 {
585     FILE *file;
586     char line[1024];
587     int i_current_status;
588     int i;
589
590     msg_Dbg( p_playlist, "opening playlist file %s", psz_filename );
591
592     file = fopen( psz_filename, "rt" );
593     if( !file )
594     {
595         msg_Err( p_playlist, "playlist file %s does not exist", psz_filename );
596         return -1;
597     }
598     fseek( file, 0L, SEEK_SET );
599
600     /* check the file is not empty */
601     if ( ! fgets( line, 1024, file ) )
602     {
603         msg_Err( p_playlist, "playlist file %s is empty", psz_filename );
604         fclose( file );
605         return -1;
606     }
607
608     /* get rid of line feed */
609     if( line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r' )
610     {
611        line[strlen(line)-1] = (char)0;
612        if( line[strlen(line)-1] == '\r' ) line[strlen(line)-1] = (char)0;
613     }
614     /* check the file format is valid */
615     if ( strcmp ( line , PLAYLIST_FILE_HEADER_0_5 ) )
616     {
617         msg_Err( p_playlist, "playlist file %s format is unsupported"
618                 , psz_filename );
619         fclose( file );
620         return -1;
621     }
622
623     /* stop playing */
624     i_current_status = p_playlist->i_status;
625     if ( p_playlist->i_status != PLAYLIST_STOPPED )
626     {
627         playlist_Stop ( p_playlist );
628     }
629
630     /* delete current content of the playlist */
631     for( i = p_playlist->i_size - 1; i >= 0; i-- )
632     {
633         playlist_Delete ( p_playlist , i );
634     }
635
636     /* simply add each line */
637     while( fgets( line, 1024, file ) )
638     {
639        /* ignore comments or empty lines */
640        if( (line[0] == '#') || (line[0] == '\r') || (line[0] == '\n')
641                || (line[0] == (char)0) )
642            continue;
643
644        /* get rid of line feed */
645        if( line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r' )
646        {
647            line[strlen(line)-1] = (char)0;
648            if( line[strlen(line)-1] == '\r' ) line[strlen(line)-1] = (char)0;
649        }
650
651        playlist_Add ( p_playlist , (char*) &line , PLAYLIST_APPEND , PLAYLIST_END );
652     }
653
654     /* start playing */
655     if ( i_current_status != PLAYLIST_STOPPED )
656     {
657         playlist_Play ( p_playlist );
658     }
659
660     fclose( file );
661
662     return 0;
663 }
664
665 /*****************************************************************************
666  * playlist_SaveFile: Save a playlist in a file.
667  *****************************************************************************/
668 int playlist_SaveFile( playlist_t * p_playlist, const char * psz_filename )
669 {
670     FILE *file;
671     int i;
672
673     vlc_mutex_lock( &p_playlist->object_lock );
674
675     msg_Dbg( p_playlist, "saving playlist file %s", psz_filename );
676
677     file = fopen( psz_filename, "wt" );
678     if( !file )
679     {
680         msg_Err( p_playlist , "could not create playlist file %s"
681                 , psz_filename );
682         return -1;
683     }
684
685     fprintf( file , PLAYLIST_FILE_HEADER_0_5 PLAYLIST_FILE_EOL );
686
687     for ( i = 0 ; i < p_playlist->i_size ; i++ )
688     {
689         fprintf( file , p_playlist->pp_items[i]->psz_uri );
690         fprintf( file , PLAYLIST_FILE_EOL );
691     }
692
693     fclose( file );
694
695     vlc_mutex_unlock( &p_playlist->object_lock );
696
697     return 0;
698 }