]> git.sesse.net Git - vlc/blob - src/playlist/engine.c
Rebuild the array of currently playing items as a background task.
[vlc] / src / playlist / engine.c
1 /*****************************************************************************
2  * engine.c : Run the playlist and handle its control
3  *****************************************************************************
4  * Copyright (C) 1999-2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *          ClĂ©ment Stenac <zorglub@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24 #include <vlc/vlc.h>
25 #include <vlc/vout.h>
26 #include <vlc/sout.h>
27 #include <vlc/input.h>
28 #include "vlc_playlist.h"
29 #include "vlc_interaction.h"
30 #include "playlist_internal.h"
31
32 /*****************************************************************************
33  * Local prototypes
34  *****************************************************************************/
35 static void VariablesInit( playlist_t *p_playlist );
36
37 static int RandomCallback( vlc_object_t *p_this, char const *psz_cmd,
38                            vlc_value_t oldval, vlc_value_t newval, void *a )
39 {
40     ((playlist_t*)p_this)->b_reset_currently_playing = VLC_TRUE;
41     playlist_Signal( ((playlist_t*)p_this) );
42     return VLC_SUCCESS;
43 }
44
45 /**
46  * Create playlist
47  *
48  * Create a playlist structure.
49  * \param p_parent the vlc object that is to be the parent of this playlist
50  * \return a pointer to the created playlist, or NULL on error
51  */
52 playlist_t * playlist_Create( vlc_object_t *p_parent )
53 {
54     playlist_t *p_playlist;
55     int i_tree;
56
57     /* Allocate structure */
58     p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
59     if( !p_playlist )
60     {
61         msg_Err( p_parent, "out of memory" );
62         return NULL;
63     }
64     p_parent->p_libvlc->p_playlist = p_playlist;
65
66     VariablesInit( p_playlist );
67
68     /* Initialise data structures */
69     vlc_mutex_init( p_playlist, &p_playlist->gc_lock );
70     p_playlist->i_last_playlist_id = 0;
71     p_playlist->i_last_input_id = 0;
72     p_playlist->p_input = NULL;
73
74     p_playlist->gc_date = 0;
75     p_playlist->b_cant_sleep = VLC_FALSE;
76
77     ARRAY_INIT( p_playlist->items );
78     ARRAY_INIT( p_playlist->all_items );
79     ARRAY_INIT( p_playlist->input_items );
80     ARRAY_INIT( p_playlist->current );
81
82     p_playlist->i_current_index = 0;
83     p_playlist->b_reset_currently_playing = VLC_TRUE;
84     p_playlist->last_rebuild_date = 0;
85
86     i_tree = var_CreateGetBool( p_playlist, "playlist-tree" );
87     p_playlist->b_always_tree = (i_tree == 1);
88     p_playlist->b_never_tree = (i_tree == 2);
89
90     p_playlist->b_doing_ml = VLC_FALSE;
91
92     p_playlist->p_root_category = playlist_NodeCreate( p_playlist, NULL, NULL);
93     p_playlist->p_root_onelevel = playlist_NodeCreate( p_playlist, NULL, NULL);
94
95     /* Create playlist and media library */
96     p_playlist->p_local_category = playlist_NodeCreate( p_playlist,
97                                  _( "Playlist" ),p_playlist->p_root_category );
98     p_playlist->p_local_onelevel =  playlist_NodeCreate( p_playlist,
99                                 _( "Playlist" ), p_playlist->p_root_onelevel );
100     p_playlist->p_local_category->i_flags |= PLAYLIST_RO_FLAG;
101     p_playlist->p_local_onelevel->i_flags |= PLAYLIST_RO_FLAG;
102
103     /* Link the nodes together. Todo: actually create them from the same input*/
104     p_playlist->p_local_onelevel->p_input->i_id =
105         p_playlist->p_local_category->p_input->i_id;
106
107     if( config_GetInt( p_playlist, "media-library") )
108     {
109         p_playlist->p_ml_category =   playlist_NodeCreate( p_playlist,
110                            _( "Media Library" ), p_playlist->p_root_category );
111         p_playlist->p_ml_onelevel =  playlist_NodeCreate( p_playlist,
112                            _( "Media Library" ), p_playlist->p_root_onelevel );
113         p_playlist->p_ml_category->i_flags |= PLAYLIST_RO_FLAG;
114         p_playlist->p_ml_onelevel->i_flags |= PLAYLIST_RO_FLAG;
115         p_playlist->p_ml_onelevel->p_input->i_id =
116              p_playlist->p_ml_category->p_input->i_id;
117
118     }
119     else
120     {
121         p_playlist->p_ml_category = p_playlist->p_ml_onelevel = NULL;
122     }
123
124     /* Initial status */
125     p_playlist->status.p_item = NULL;
126     p_playlist->status.p_node = p_playlist->p_local_onelevel;
127     p_playlist->request.b_request = VLC_FALSE;
128     p_playlist->status.i_status = PLAYLIST_STOPPED;
129
130     p_playlist->i_sort = SORT_ID;
131     p_playlist->i_order = ORDER_NORMAL;
132
133     vlc_object_attach( p_playlist, p_parent );
134
135     playlist_MLLoad( p_playlist );
136     return p_playlist;
137 }
138
139 void playlist_Destroy( playlist_t *p_playlist )
140 {
141     while( p_playlist->i_sds )
142     {
143         playlist_ServicesDiscoveryRemove( p_playlist,
144                                           p_playlist->pp_sds[0]->psz_module );
145     }
146
147     playlist_MLDump( p_playlist );
148
149     vlc_thread_join( p_playlist->p_preparse );
150     vlc_thread_join( p_playlist->p_secondary_preparse );
151     vlc_thread_join( p_playlist );
152
153     vlc_object_detach( p_playlist->p_preparse );
154     vlc_object_detach( p_playlist->p_secondary_preparse );
155
156     var_Destroy( p_playlist, "intf-change" );
157     var_Destroy( p_playlist, "item-change" );
158     var_Destroy( p_playlist, "playlist-current" );
159     var_Destroy( p_playlist, "intf-popmenu" );
160     var_Destroy( p_playlist, "intf-show" );
161     var_Destroy( p_playlist, "play-and-stop" );
162     var_Destroy( p_playlist, "play-and-exit" );
163     var_Destroy( p_playlist, "random" );
164     var_Destroy( p_playlist, "repeat" );
165     var_Destroy( p_playlist, "loop" );
166     var_Destroy( p_playlist, "activity" );
167
168     PL_LOCK;
169     /* Go through all items, and simply free everything without caring
170      * about the tree structure. Do not decref, it will be done by doing
171      * the same thing on the input items array */
172     FOREACH_ARRAY( playlist_item_t *p_del, p_playlist->all_items )
173         free( p_del->pp_children );
174         free( p_del );
175     FOREACH_END();
176     ARRAY_RESET( p_playlist->all_items );
177
178     FOREACH_ARRAY( input_item_t *p_del, p_playlist->input_items )
179         input_ItemClean( p_del );
180         free( p_del );
181     FOREACH_END();
182     ARRAY_RESET( p_playlist->input_items );
183
184     ARRAY_RESET( p_playlist->items );
185     ARRAY_RESET( p_playlist->current );
186
187     PL_UNLOCK;
188
189     if( p_playlist->p_stats )
190         free( p_playlist->p_stats );
191
192     vlc_mutex_destroy( &p_playlist->gc_lock );
193     vlc_object_destroy( p_playlist->p_preparse );
194     vlc_object_destroy( p_playlist->p_secondary_preparse );
195     vlc_object_detach( p_playlist );
196     vlc_object_destroy( p_playlist );
197
198 }
199
200 /* Destroy remaining objects */
201 static void ObjectGarbageCollector( playlist_t *p_playlist )
202 {
203     vlc_object_t *p_obj;
204
205     if( mdate() - p_playlist->gc_date < 1000000 )
206     {
207         p_playlist->b_cant_sleep = VLC_TRUE;
208         return;
209     }
210     else if( p_playlist->gc_date == 0 )
211         return;
212
213     vlc_mutex_lock( &p_playlist->gc_lock );
214     while( ( p_obj = vlc_object_find( p_playlist, VLC_OBJECT_VOUT,
215                                                   FIND_CHILD ) ) )
216     {
217         if( p_obj->p_parent != (vlc_object_t*)p_playlist )
218         {
219             vlc_object_release( p_obj );
220             break;
221         }
222         msg_Dbg( p_playlist, "garbage collector destroying 1 vout" );
223         vlc_object_detach( p_obj );
224         vlc_object_release( p_obj );
225         vout_Destroy( (vout_thread_t *)p_obj );
226     }
227     while( ( p_obj = vlc_object_find( p_playlist, VLC_OBJECT_SOUT,
228                                                   FIND_CHILD ) ) )
229     {
230         if( p_obj->p_parent != (vlc_object_t*)p_playlist )
231         {
232             vlc_object_release( p_obj );
233             break;
234         }
235         vlc_object_release( p_obj );
236         sout_DeleteInstance( (sout_instance_t*)p_obj );
237     }
238     p_playlist->b_cant_sleep = VLC_FALSE;
239     vlc_mutex_unlock( &p_playlist->gc_lock );
240 }
241
242 /** Main loop for the playlist */
243 void playlist_MainLoop( playlist_t *p_playlist )
244 {
245     playlist_item_t *p_item = NULL;
246     vlc_bool_t b_playexit = var_GetBool( p_playlist, "play-and-exit" );
247     PL_LOCK;
248
249     if( p_playlist->b_reset_currently_playing &&
250         mdate() - p_playlist->last_rebuild_date > 30000 ) // 30 ms
251     {
252         ResetCurrentlyPlaying( p_playlist, var_GetBool( p_playlist, "random"),
253                              p_playlist->status.p_item );
254         p_playlist->last_rebuild_date = mdate();
255     }
256
257 check_input:
258     /* If there is an input, check that it doesn't need to die. */
259     if( p_playlist->p_input )
260     {
261         if( p_playlist->request.b_request && !p_playlist->p_input->b_die )
262         {
263             PL_DEBUG( "incoming request - stopping current input" );
264             input_StopThread( p_playlist->p_input );
265         }
266
267         /* This input is dead. Remove it ! */
268         if( p_playlist->p_input->b_dead )
269         {
270             int i_activity;
271             input_thread_t *p_input;
272             PL_DEBUG( "dead input" );
273
274             p_input = p_playlist->p_input;
275             p_playlist->p_input = NULL;
276
277             /* Release the playlist lock, because we may get stuck
278              * in input_DestroyThread() for some time. */
279             PL_UNLOCK
280
281             /* Destroy input */
282             input_DestroyThread( p_input );
283
284             /* Unlink current input
285              * (_after_ input_DestroyThread for vout garbage collector) */
286             vlc_object_detach( p_input );
287
288             /* Destroy object */
289             vlc_object_destroy( p_input );
290
291             PL_LOCK;
292
293             p_playlist->gc_date = mdate();
294             p_playlist->b_cant_sleep = VLC_TRUE;
295
296             if( p_playlist->status.p_item->i_flags
297                 & PLAYLIST_REMOVE_FLAG )
298             {
299                  PL_DEBUG( "%s was marked for deletion, deleting",
300                                  PLI_NAME( p_playlist->status.p_item  ) );
301                  playlist_ItemDelete( p_playlist->status.p_item );
302                  if( p_playlist->request.p_item == p_playlist->status.p_item )
303                      p_playlist->request.p_item = NULL;
304                  p_playlist->status.p_item = NULL;
305             }
306
307             i_activity= var_GetInteger( p_playlist, "activity") ;
308             var_SetInteger( p_playlist, "activity", i_activity -
309                             DEFAULT_INPUT_ACTIVITY );
310             goto check_input;
311         }
312         /* This input is dying, let it do */
313         else if( p_playlist->p_input->b_die )
314         {
315             PL_DEBUG( "dying input" );
316             msleep( 25000 ); // 25 ms
317             goto check_input;
318         }
319         /* This input has finished, ask it to die ! */
320         else if( p_playlist->p_input->b_error
321                   || p_playlist->p_input->b_eof )
322         {
323             PL_DEBUG( "finished input" );
324             input_StopThread( p_playlist->p_input );
325             /* No need to wait here, we'll wait in the p_input->b_die case */
326             goto check_input;
327         }
328         else if( p_playlist->p_input->i_state != INIT_S )
329         {
330             PL_UNLOCK;
331             ObjectGarbageCollector( p_playlist );
332             PL_LOCK;
333         }
334     }
335     else
336     {
337         /* No input. Several cases
338          *  - No request, running status -> start new item
339          *  - No request, stopped status -> collect garbage
340          *  - Request, running requested -> start new item
341          *  - Request, stopped requested -> collect garbage
342          */
343          if( (!p_playlist->request.b_request &&
344               p_playlist->status.i_status != PLAYLIST_STOPPED) ||
345               ( p_playlist->request.b_request &&
346                 p_playlist->request.i_status != PLAYLIST_STOPPED ) )
347          {
348              msg_Dbg( p_playlist, "starting new item" );
349              p_item = playlist_NextItem( p_playlist );
350
351              if( p_item == NULL )
352              {
353                 msg_Dbg( p_playlist, "nothing to play" );
354                 if( b_playexit == VLC_TRUE )
355                 {
356                     msg_Info( p_playlist, "end of playlist, exiting" );
357                     p_playlist->p_libvlc->b_die = VLC_TRUE;
358                 }
359                 p_playlist->status.i_status = PLAYLIST_STOPPED;
360                 PL_UNLOCK
361                 return;
362              }
363              playlist_PlayItem( p_playlist, p_item );
364          }
365          else
366          {
367             p_playlist->status.i_status = PLAYLIST_STOPPED;
368             if( p_playlist->status.p_item &&
369                 p_playlist->status.p_item->i_flags & PLAYLIST_REMOVE_FLAG )
370             {
371                 PL_DEBUG( "deleting item marked for deletion" );
372                 playlist_ItemDelete( p_playlist->status.p_item );
373                 p_playlist->status.p_item = NULL;
374             }
375
376             /* Collect garbage */
377             PL_UNLOCK;
378             ObjectGarbageCollector( p_playlist );
379             PL_LOCK;
380         }
381     }
382     PL_UNLOCK
383 }
384
385 /** Playlist dying last loop */
386 void playlist_LastLoop( playlist_t *p_playlist )
387 {
388     vlc_object_t *p_obj;
389
390     /* If there is an input, kill it */
391     while( 1 )
392     {
393         PL_LOCK
394
395         if( p_playlist->p_input == NULL )
396         {
397             PL_UNLOCK
398             break;
399         }
400
401         if( p_playlist->p_input->b_dead )
402         {
403             input_thread_t *p_input;
404
405             /* Unlink current input */
406             p_input = p_playlist->p_input;
407             p_playlist->p_input = NULL;
408             PL_UNLOCK
409
410             /* Destroy input */
411             input_DestroyThread( p_input );
412             /* Unlink current input (_after_ input_DestroyThread for vout
413              * garbage collector)*/
414             vlc_object_detach( p_input );
415
416             /* Destroy object */
417             vlc_object_destroy( p_input );
418             continue;
419         }
420         else if( p_playlist->p_input->b_die )
421         {
422             /* This input is dying, leave it alone */
423             ;
424         }
425         else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
426         {
427             input_StopThread( p_playlist->p_input );
428             PL_UNLOCK
429             continue;
430         }
431         else
432         {
433             p_playlist->p_input->b_eof = 1;
434         }
435
436         PL_UNLOCK
437
438         msleep( INTF_IDLE_SLEEP );
439     }
440
441     /* close all remaining sout */
442     while( ( p_obj = vlc_object_find( p_playlist,
443                                       VLC_OBJECT_SOUT, FIND_CHILD ) ) )
444     {
445         vlc_object_release( p_obj );
446         sout_DeleteInstance( (sout_instance_t*)p_obj );
447     }
448
449     /* close all remaining vout */
450     while( ( p_obj = vlc_object_find( p_playlist,
451                                       VLC_OBJECT_VOUT, FIND_CHILD ) ) )
452     {
453         vlc_object_detach( p_obj );
454         vlc_object_release( p_obj );
455         vout_Destroy( (vout_thread_t *)p_obj );
456     }
457 }
458
459 /** Main loop for preparser queue */
460 void playlist_PreparseLoop( playlist_preparse_t *p_obj )
461 {
462     playlist_t *p_playlist = (playlist_t *)p_obj->p_parent;
463     input_item_t *p_current;
464     int i_activity;
465     uint32_t i_m, i_o;
466
467     while( !p_playlist->b_die )
468     {
469         vlc_mutex_lock( &p_obj->object_lock );
470         while( p_obj->i_waiting == 0 )
471         {
472             vlc_cond_wait( &p_obj->object_wait, &p_obj->object_lock );
473             if( p_playlist->b_die )
474             {
475                 vlc_mutex_unlock( &p_obj->object_lock );
476                 return;
477             }
478         }
479
480         p_current = p_obj->pp_waiting[0];
481         REMOVE_ELEM( p_obj->pp_waiting, p_obj->i_waiting, 0 );
482         vlc_mutex_unlock( &p_obj->object_lock );
483
484         PL_LOCK;
485         if( p_current )
486         {
487             vlc_bool_t b_preparsed = VLC_FALSE;
488             if( strncmp( p_current->psz_uri, "http:", 5 ) &&
489                 strncmp( p_current->psz_uri, "rtsp:", 5 ) &&
490                 strncmp( p_current->psz_uri, "udp:", 4 ) &&
491                 strncmp( p_current->psz_uri, "mms:", 4 ) &&
492                 strncmp( p_current->psz_uri, "cdda:", 4 ) &&
493                 strncmp( p_current->psz_uri, "dvd:", 4 ) &&
494                 strncmp( p_current->psz_uri, "v4l:", 4 ) &&
495                 strncmp( p_current->psz_uri, "dshow:", 6 ) )
496             {
497                 b_preparsed = VLC_TRUE;
498                 stats_TimerStart( p_playlist, "Preparse run",
499                                   STATS_TIMER_PREPARSE );
500                 PL_UNLOCK;
501                 input_Preparse( p_playlist, p_current );
502                 PL_LOCK;
503                 stats_TimerStop( p_playlist, STATS_TIMER_PREPARSE );
504             }
505             PL_UNLOCK;
506             if( b_preparsed )
507             {
508                 p_current->p_meta->i_status |= ITEM_PREPARSED;
509                 var_SetInteger( p_playlist, "item-change", p_current->i_id );
510             }
511             PL_LOCK;
512
513             /* If we haven't retrieved enough meta, add to secondary queue
514              * which will run the "meta fetchers"
515              * TODO:
516              *  don't do this for things we won't get meta for, like
517              *  videos
518              */
519             if( !input_MetaSatisfied( p_playlist, p_current, &i_m, &i_o,
520                                       VLC_TRUE ) )
521             {
522                 preparse_item_t p;
523                 p.p_item = p_current;
524                 p.b_fetch_art = VLC_FALSE;
525                 vlc_mutex_lock( &p_playlist->p_secondary_preparse->object_lock);
526                 INSERT_ELEM( p_playlist->p_secondary_preparse->p_waiting,
527                              p_playlist->p_secondary_preparse->i_waiting,
528                              p_playlist->p_secondary_preparse->i_waiting,
529                              p );
530                 vlc_mutex_unlock(
531                             &p_playlist->p_secondary_preparse->object_lock);
532                 vlc_cond_signal(
533                             &p_playlist->p_secondary_preparse->object_wait );
534             }
535             else
536                 vlc_gc_decref( p_current );
537             PL_UNLOCK;
538         }
539         else
540             PL_UNLOCK;
541
542         vlc_mutex_lock( &p_obj->object_lock );
543         i_activity = var_GetInteger( p_playlist, "activity" );
544         if( i_activity < 0 ) i_activity = 0;
545         vlc_mutex_unlock( &p_obj->object_lock );
546         /* Sleep at least 1ms */
547         msleep( (i_activity+1) * 1000 );
548     }
549 }
550
551 /** Main loop for secondary preparser queue */
552 void playlist_SecondaryPreparseLoop( playlist_secondary_preparse_t *p_obj )
553 {
554     playlist_t *p_playlist = (playlist_t *)p_obj->p_parent;
555     vlc_bool_t b_fetch_art;
556     input_item_t *p_item;
557     int i_activity;
558
559     while( !p_playlist->b_die )
560     {
561         vlc_mutex_lock( &p_obj->object_lock );
562         while( p_obj->i_waiting == 0 )
563         {
564             vlc_cond_wait( &p_obj->object_wait, &p_obj->object_lock );
565             if( p_playlist->b_die )
566             {
567                 vlc_mutex_unlock( &p_obj->object_lock );
568                 return;
569             }
570         }
571
572         b_fetch_art = p_obj->p_waiting->b_fetch_art;
573         p_item = p_obj->p_waiting->p_item;
574         REMOVE_ELEM( p_obj->p_waiting, p_obj->i_waiting, 0 );
575         vlc_mutex_unlock( &p_obj->object_lock );
576         if( p_item )
577         {
578             if( !b_fetch_art )
579             {
580                 input_MetaFetch( p_playlist, p_item );
581                 p_item->p_meta->i_status |= ITEM_META_FETCHED;
582                 var_SetInteger( p_playlist, "item-change", p_item->i_id );
583                 /*  Fetch right now */
584                 if( var_GetInteger( p_playlist, "album-art" ) == ALBUM_ART_ALL )
585                 {
586                     vlc_mutex_lock( &p_obj->object_lock );
587                     preparse_item_t p;
588                     p.p_item = p_item;
589                     p.b_fetch_art = VLC_TRUE;
590                     INSERT_ELEM( p_playlist->p_secondary_preparse->p_waiting,
591                                  p_playlist->p_secondary_preparse->i_waiting,
592                                  0, p );
593                     vlc_mutex_unlock( &p_obj->object_lock );
594                 }
595                 else
596                     vlc_gc_decref( p_item );
597             }
598             else
599             {
600                 input_ArtFetch( p_playlist, p_item );
601                 p_item->p_meta->i_status |= ITEM_ART_FETCHED;
602                 vlc_gc_decref( p_item );
603            }
604         }
605         vlc_mutex_lock( &p_obj->object_lock );
606         i_activity = var_GetInteger( p_playlist, "activity" );
607         if( i_activity < 0 ) i_activity = 0;
608         vlc_mutex_unlock( &p_obj->object_lock );
609         /* Sleep at least 1ms */
610         msleep( (i_activity+1) * 1000 );
611     }
612 }
613
614 static void VariablesInit( playlist_t *p_playlist )
615 {
616     vlc_value_t val;
617     /* These variables control updates */
618     var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
619     val.b_bool = VLC_TRUE;
620     var_Set( p_playlist, "intf-change", val );
621
622     var_Create( p_playlist, "item-change", VLC_VAR_INTEGER );
623     val.i_int = -1;
624     var_Set( p_playlist, "item-change", val );
625
626     var_Create( p_playlist, "item-deleted", VLC_VAR_INTEGER );
627     val.i_int = -1;
628     var_Set( p_playlist, "item-deleted", val );
629
630     var_Create( p_playlist, "item-append", VLC_VAR_ADDRESS );
631
632     var_Create( p_playlist, "playlist-current", VLC_VAR_INTEGER );
633     val.i_int = -1;
634     var_Set( p_playlist, "playlist-current", val );
635
636     var_Create( p_playlist, "intf-popupmenu", VLC_VAR_BOOL );
637
638     var_Create( p_playlist, "intf-show", VLC_VAR_BOOL );
639     val.b_bool = VLC_TRUE;
640     var_Set( p_playlist, "intf-show", val );
641
642     var_Create( p_playlist, "activity", VLC_VAR_INTEGER );
643     var_SetInteger( p_playlist, "activity", 0 );
644
645     /* Variables to control playback */
646     var_CreateGetBool( p_playlist, "play-and-stop" );
647     var_CreateGetBool( p_playlist, "play-and-exit" );
648     var_CreateGetBool( p_playlist, "random" );
649     var_CreateGetBool( p_playlist, "repeat" );
650     var_CreateGetBool( p_playlist, "loop" );
651
652     var_AddCallback( p_playlist, "random", RandomCallback, NULL );
653 }