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