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