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