]> git.sesse.net Git - vlc/blob - src/playlist/playlist.c
1209e9647543f81ed130410af82ac89b1465bec0
[vlc] / src / playlist / playlist.c
1 /*****************************************************************************
2  * playlist.c : Playlist management functions
3  *****************************************************************************
4  * Copyright (C) 1999-2004 VideoLAN
5  * $Id: playlist.c,v 1.72 2004/01/06 08:50:20 zorglub 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 #include <vlc/vout.h>
29 #include <vlc/sout.h>
30 #include <vlc/input.h>
31
32 #include "stream_control.h"
33 #include "input_ext-intf.h"
34
35 #include "vlc_playlist.h"
36
37 #define PLAYLIST_FILE_HEADER_0_5  "# vlc playlist file version 0.5"
38 #define PLAYLIST_FILE_HEADER_0_6  "# vlc playlist file version 0.6"
39
40 /*****************************************************************************
41  * Local prototypes
42  *****************************************************************************/
43 static void RunThread ( playlist_t * );
44 static void SkipItem  ( playlist_t *, int );
45 static void PlayItem  ( playlist_t * );
46
47 /**
48  * Create playlist
49  *
50  * Create a playlist structure.
51  * \param p_parent the vlc object that is to be the parent of this playlist
52  * \return a pointer to the created playlist, or NULL on error
53  */
54 playlist_t * __playlist_Create ( vlc_object_t *p_parent )
55 {
56     playlist_t *p_playlist;
57     vlc_value_t     val;
58
59     /* Allocate structure */
60     p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
61     if( !p_playlist )
62     {
63         msg_Err( p_parent, "out of memory" );
64         return NULL;
65     }
66
67     var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
68     val.b_bool = VLC_TRUE;
69     var_Set( p_playlist, "intf-change", val );
70
71     var_Create( p_playlist, "item-change", VLC_VAR_INTEGER );
72     val.i_int = -1;
73     var_Set( p_playlist, "item-change", val );
74
75     var_Create( p_playlist, "playlist-current", VLC_VAR_INTEGER );
76     val.i_int = -1;
77     var_Set( p_playlist, "playlist-current", val );
78
79     var_Create( p_playlist, "intf-popupmenu", VLC_VAR_BOOL );
80
81     var_Create( p_playlist, "intf-show", VLC_VAR_BOOL );
82     val.b_bool = VLC_TRUE;
83     var_Set( p_playlist, "intf-show", val );
84
85
86     var_Create( p_playlist, "prevent-skip", VLC_VAR_BOOL );
87     val.b_bool = VLC_FALSE;
88     var_Set( p_playlist, "prevent-skip", val );
89
90     var_Create( p_playlist, "random", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
91     var_Create( p_playlist, "repeat", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
92     var_Create( p_playlist, "loop", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
93
94     p_playlist->p_input = NULL;
95     p_playlist->i_status = PLAYLIST_STOPPED;
96     p_playlist->i_index = -1;
97     p_playlist->i_size = 0;
98     p_playlist->pp_items = NULL;
99
100     p_playlist->i_groups = 0;
101     p_playlist->pp_groups = NULL;
102     p_playlist->i_last_group = 0;
103     p_playlist->i_last_id = 0;
104
105     playlist_CreateGroup( p_playlist, "Normal" );
106
107     if( vlc_thread_create( p_playlist, "playlist", RunThread,
108                            VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
109     {
110         msg_Err( p_playlist, "cannot spawn playlist thread" );
111         vlc_object_destroy( p_playlist );
112         return NULL;
113     }
114
115     /* The object has been initialized, now attach it */
116     vlc_object_attach( p_playlist, p_parent );
117
118     return p_playlist;
119 }
120
121 /**
122  * Destroy the playlist.
123  *
124  * Delete all items in the playlist and free the playlist structure.
125  * \param p_playlist the playlist structure to destroy
126  */
127 void playlist_Destroy( playlist_t * p_playlist )
128 {
129     p_playlist->b_die = 1;
130
131     vlc_thread_join( p_playlist );
132
133     var_Destroy( p_playlist, "intf-change" );
134     var_Destroy( p_playlist, "item-change" );
135     var_Destroy( p_playlist, "playlist-current" );
136     var_Destroy( p_playlist, "intf-popmenu" );
137     var_Destroy( p_playlist, "intf-show" );
138     var_Destroy( p_playlist, "prevent-skip" );
139     var_Destroy( p_playlist, "random" );
140     var_Destroy( p_playlist, "repeat" );
141     var_Destroy( p_playlist, "loop" );
142
143     while( p_playlist->i_groups > 0 )
144     {
145         playlist_DeleteGroup( p_playlist, p_playlist->pp_groups[0]->i_id );
146     }
147
148     while( p_playlist->i_size > 0 )
149     {
150         playlist_Delete( p_playlist, 0 );
151     }
152
153     vlc_object_destroy( p_playlist );
154 }
155
156
157 /**
158  * Do a playlist action
159  *
160  * \param p_playlist the playlist to do the command on
161  * \param i_command the command to do
162  * \param i_arg the argument to the command. See playlist_command_t for details
163  */
164  void playlist_Command( playlist_t * p_playlist, playlist_command_t i_command,
165                        int i_arg )
166 {
167     vlc_value_t val;
168
169     vlc_mutex_lock( &p_playlist->object_lock );
170
171     switch( i_command )
172     {
173     case PLAYLIST_STOP:
174         p_playlist->i_status = PLAYLIST_STOPPED;
175         if( p_playlist->p_input )
176         {
177             input_StopThread( p_playlist->p_input );
178             val.i_int = p_playlist->i_index;
179             var_Set( p_playlist, "item-change",val );
180         }
181         break;
182
183     case PLAYLIST_PLAY:
184         p_playlist->i_status = PLAYLIST_RUNNING;
185         if( !p_playlist->p_input && p_playlist->i_enabled != 0 )
186         {
187             PlayItem( p_playlist );
188         }
189         if( p_playlist->p_input )
190         {
191             val.i_int = PLAYING_S;
192             var_Set( p_playlist->p_input, "state", val );
193         }
194         break;
195
196     case PLAYLIST_PAUSE:
197         val.i_int = 0;
198         if( p_playlist->p_input )
199             var_Get( p_playlist->p_input, "state", &val );
200
201         if( val.i_int == PAUSE_S )
202         {
203             p_playlist->i_status = PLAYLIST_RUNNING;
204             if( p_playlist->p_input )
205             {
206                 val.i_int = PLAYING_S;
207                 var_Set( p_playlist->p_input, "state", val );
208             }
209         }
210         else
211         {
212             p_playlist->i_status = PLAYLIST_PAUSED;
213             if( p_playlist->p_input )
214             {
215                 val.i_int = PAUSE_S;
216                 var_Set( p_playlist->p_input, "state", val );
217             }
218         }
219         break;
220
221     case PLAYLIST_SKIP:
222         p_playlist->i_status = PLAYLIST_STOPPED;
223         if( p_playlist->i_enabled == 0)
224         {
225             break;
226         }
227         SkipItem( p_playlist, i_arg );
228         if( p_playlist->p_input )
229         {
230             input_StopThread( p_playlist->p_input );
231         }
232         p_playlist->i_status = PLAYLIST_RUNNING;
233         break;
234
235     case PLAYLIST_GOTO:
236         if( i_arg >= 0 && i_arg < p_playlist->i_size &&
237             p_playlist->i_enabled != 0 )
238         {
239             p_playlist->i_index = i_arg;
240             if( p_playlist->p_input )
241             {
242                 input_StopThread( p_playlist->p_input );
243             }
244             val.b_bool = VLC_TRUE;
245             var_Set( p_playlist, "prevent-skip", val );
246             p_playlist->i_status = PLAYLIST_RUNNING;
247         }
248         break;
249
250     default:
251         msg_Err( p_playlist, "unknown playlist command" );
252         break;
253     }
254
255     vlc_mutex_unlock( &p_playlist->object_lock );
256 #if 0
257     val.b_bool = VLC_TRUE;
258     var_Set( p_playlist, "intf-change", val );
259 #endif
260     return;
261 }
262
263
264 static void ObjectGarbageCollector( playlist_t *p_playlist,
265                                     int i_type,
266                                     mtime_t *pi_obj_destroyed_date )
267 {
268     vlc_object_t *p_obj;
269     if( *pi_obj_destroyed_date > mdate() )
270     {
271         return;
272     }
273
274     if( *pi_obj_destroyed_date == 0 )
275     {
276         /* give a little time */
277         *pi_obj_destroyed_date = mdate() + I64C(300000);
278     }
279     else
280     {
281         while( ( p_obj = vlc_object_find( p_playlist,
282                                            i_type,
283                                            FIND_CHILD ) ) )
284         {
285             if( p_obj->p_parent != (vlc_object_t*)p_playlist )
286             {
287                 /* only first chiled (ie unused) */
288                 vlc_object_release( p_obj );
289                 break;
290             }
291             if( i_type == VLC_OBJECT_VOUT )
292             {
293                 msg_Dbg( p_playlist, "vout garbage collector destroying 1 vout" );
294                 vlc_object_detach( p_obj );
295                 vlc_object_release( p_obj );
296                 vout_Destroy( (vout_thread_t *)p_obj );
297             }
298             else if( i_type == VLC_OBJECT_SOUT )
299             {
300                 vlc_object_release( p_obj );
301                 sout_DeleteInstance( (sout_instance_t*)p_obj );
302             }
303         }
304         *pi_obj_destroyed_date = 0;
305     }
306 }
307
308 /*****************************************************************************
309  * RunThread: main playlist thread
310  *****************************************************************************/
311 static void RunThread ( playlist_t *p_playlist )
312 {
313     vlc_object_t *p_obj;
314     vlc_value_t val;
315
316     mtime_t    i_vout_destroyed_date = 0;
317     mtime_t    i_sout_destroyed_date = 0;
318
319     /* Tell above that we're ready */
320     vlc_thread_ready( p_playlist );
321
322     while( !p_playlist->b_die )
323     {
324         vlc_mutex_lock( &p_playlist->object_lock );
325
326         /* If there is an input, check that it doesn't need to die. */
327         if( p_playlist->p_input )
328         {
329             /* This input is dead. Remove it ! */
330             if( p_playlist->p_input->b_dead )
331             {
332                 input_thread_t *p_input;
333
334                 p_input = p_playlist->p_input;
335                 p_playlist->p_input = NULL;
336
337                 /* Release the playlist lock, because we may get stuck
338                  * in input_DestroyThread() for some time. */
339                 vlc_mutex_unlock( &p_playlist->object_lock );
340
341                 /* Destroy input */
342                 input_DestroyThread( p_input );
343
344                 /* Unlink current input
345                  * (_after_ input_DestroyThread for vout garbage collector) */
346                 vlc_object_detach( p_input );
347
348                 /* Destroy object */
349                 vlc_object_destroy( p_input );
350
351                 i_vout_destroyed_date = 0;
352                 i_sout_destroyed_date = 0;
353                 continue;
354             }
355             /* This input is dying, let him do */
356             else if( p_playlist->p_input->b_die )
357             {
358                 ;
359             }
360             /* This input has finished, ask him to die ! */
361             else if( p_playlist->p_input->b_error
362                       || p_playlist->p_input->b_eof )
363             {
364                 /* Check for autodeletion */
365                 if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
366                 {
367                     vlc_mutex_unlock( &p_playlist->object_lock );
368                     playlist_Delete( p_playlist, p_playlist->i_index );
369                     p_playlist->i_index++;
370                     p_playlist->i_status = PLAYLIST_RUNNING;
371                 }
372                 else
373                 {
374                     /* Select the next playlist item */
375                     SkipItem( p_playlist, 1 );
376                     input_StopThread( p_playlist->p_input );
377                     vlc_mutex_unlock( &p_playlist->object_lock );
378                 }
379
380                 val.i_int = p_playlist->i_index;
381                 var_Set( p_playlist, "playlist-current", val);
382 #if 0
383                 val.b_bool = VLC_TRUE;
384                 var_Set( p_playlist, "intf-change", val );
385 #endif
386                 continue;
387             }
388             else if( p_playlist->p_input->stream.control.i_status != INIT_S )
389             {
390                 vlc_mutex_unlock( &p_playlist->object_lock );
391                 ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
392                                         &i_vout_destroyed_date );
393                 ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
394                                         &i_sout_destroyed_date );
395                 vlc_mutex_lock( &p_playlist->object_lock );
396             }
397         }
398         else if( p_playlist->i_status != PLAYLIST_STOPPED )
399         {
400             var_Get( p_playlist, "prevent-skip", &val);
401             if( val.b_bool == VLC_FALSE)
402             {
403                 SkipItem( p_playlist, 0 );
404             }
405             val.b_bool = VLC_TRUE;
406             var_Set( p_playlist, "prevent-skip", val);
407             PlayItem( p_playlist );
408         }
409         else if( p_playlist->i_status == PLAYLIST_STOPPED )
410         {
411             vlc_mutex_unlock( &p_playlist->object_lock );
412             ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
413                                     &i_sout_destroyed_date );
414             ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
415                                     &i_vout_destroyed_date );
416             vlc_mutex_lock( &p_playlist->object_lock );
417         }
418         vlc_mutex_unlock( &p_playlist->object_lock );
419
420         msleep( INTF_IDLE_SLEEP );
421     }
422
423     /* If there is an input, kill it */
424     while( 1 )
425     {
426         vlc_mutex_lock( &p_playlist->object_lock );
427
428         if( p_playlist->p_input == NULL )
429         {
430             vlc_mutex_unlock( &p_playlist->object_lock );
431             break;
432         }
433
434         if( p_playlist->p_input->b_dead )
435         {
436             input_thread_t *p_input;
437
438             /* Unlink current input */
439             p_input = p_playlist->p_input;
440             p_playlist->p_input = NULL;
441             vlc_mutex_unlock( &p_playlist->object_lock );
442
443             /* Destroy input */
444             input_DestroyThread( p_input );
445             /* Unlink current input (_after_ input_DestroyThread for vout
446              * garbage collector)*/
447             vlc_object_detach( p_input );
448
449             /* Destroy object */
450             vlc_object_destroy( p_input );
451             continue;
452         }
453         else if( p_playlist->p_input->b_die )
454         {
455             /* This input is dying, leave him alone */
456             ;
457         }
458         else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
459         {
460             input_StopThread( p_playlist->p_input );
461             vlc_mutex_unlock( &p_playlist->object_lock );
462             continue;
463         }
464         else
465         {
466             p_playlist->p_input->b_eof = 1;
467         }
468
469         vlc_mutex_unlock( &p_playlist->object_lock );
470
471         msleep( INTF_IDLE_SLEEP );
472     }
473
474     /* close all remaining sout */
475     while( ( p_obj = vlc_object_find( p_playlist,
476                                       VLC_OBJECT_SOUT, FIND_CHILD ) ) )
477     {
478         vlc_object_release( p_obj );
479         sout_DeleteInstance( (sout_instance_t*)p_obj );
480     }
481
482     /* close all remaining vout */
483     while( ( p_obj = vlc_object_find( p_playlist,
484                                       VLC_OBJECT_VOUT, FIND_CHILD ) ) )
485     {
486         vlc_object_detach( p_obj );
487         vlc_object_release( p_obj );
488         vout_Destroy( (vout_thread_t *)p_obj );
489     }
490 }
491
492 /*****************************************************************************
493  * SkipItem: go to Xth playlist item
494  *****************************************************************************
495  * This function calculates the position of the next playlist item, depending
496  * on the playlist course mode (forward, backward, random...).
497  *****************************************************************************/
498 static void SkipItem( playlist_t *p_playlist, int i_arg )
499 {
500     int i_oldindex = p_playlist->i_index;
501     vlc_bool_t b_random, b_repeat, b_loop;
502     vlc_value_t val;
503
504     /* If the playlist is empty, there is no current item */
505     if( p_playlist->i_size == 0 )
506     {
507         p_playlist->i_index = -1;
508         return;
509     }
510
511     var_Get( p_playlist, "random", &val );
512     b_random = val.b_bool;
513     var_Get( p_playlist, "repeat", &val );
514     b_repeat = val.b_bool;
515     var_Get( p_playlist, "loop", &val );
516     b_loop = val.b_bool;
517
518     /* Increment */
519     if( b_random )
520     {
521         srand( (unsigned int)mdate() );
522
523         /* Simple random stuff - we cheat a bit to minimize the chances to
524          * get the same index again. */
525         i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
526         if( i_arg == 0 )
527         {
528             i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
529         }
530     }
531     if( b_repeat )
532     {
533         i_arg = 0;
534     }
535     p_playlist->i_index += i_arg;
536
537     /* Boundary check */
538     if( p_playlist->i_index >= p_playlist->i_size )
539     {
540         if( p_playlist->i_status == PLAYLIST_STOPPED
541              || b_random
542              || b_loop )
543         {
544             p_playlist->i_index -= p_playlist->i_size
545                          * ( p_playlist->i_index / p_playlist->i_size );
546         }
547         else
548         {
549             /* Don't loop by default: stop at playlist end */
550             p_playlist->i_index = i_oldindex;
551             p_playlist->i_status = PLAYLIST_STOPPED;
552         }
553     }
554     else if( p_playlist->i_index < 0 )
555     {
556         p_playlist->i_index = p_playlist->i_size - 1;
557     }
558
559     /* Check that the item is enabled */
560    if( p_playlist->pp_items[p_playlist->i_index]->b_enabled == VLC_FALSE &&
561        p_playlist->i_enabled != 0)
562     {
563         SkipItem( p_playlist , 1 );
564     }
565 }
566
567 /*****************************************************************************
568  * PlayItem: play current playlist item
569  *****************************************************************************
570  * This function calculates the position of the next playlist item, depending
571  * on the playlist course mode (forward, backward, random...).
572  *****************************************************************************/
573 static void PlayItem( playlist_t *p_playlist )
574 {
575     vlc_value_t val;
576     if( p_playlist->i_index == -1 )
577     {
578         if( p_playlist->i_size == 0 || p_playlist->i_enabled == 0)
579         {
580             return;
581         }
582
583         SkipItem( p_playlist, 1 );
584     }
585
586     if( p_playlist->i_enabled == 0)
587     {
588         return;
589     }
590
591     msg_Dbg( p_playlist, "creating new input thread" );
592     p_playlist->p_input = input_CreateThread( p_playlist,
593                                   p_playlist->pp_items[p_playlist->i_index] );
594
595     val.i_int = p_playlist->i_index;
596     var_Set( p_playlist, "item-change", val );
597 }