]> git.sesse.net Git - vlc/blob - src/playlist/thread.c
Use a custom condition variable for the playlist engine
[vlc] / src / playlist / thread.c
1 /*****************************************************************************
2  * thread.c : Playlist management functions
3  *****************************************************************************
4  * Copyright © 1999-2008 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 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <vlc_common.h>
29 #include <vlc_es.h>
30 #include <vlc_input.h>
31 #include <vlc_interface.h>
32 #include <vlc_playlist.h>
33 #include "stream_output/stream_output.h"
34 #include "playlist_internal.h"
35
36 /*****************************************************************************
37  * Local prototypes
38  *****************************************************************************/
39 static void *Thread   ( vlc_object_t * );
40
41 /*****************************************************************************
42  * Main functions for the global thread
43  *****************************************************************************/
44
45 /**
46  * Create the main playlist threads.
47  * Additionally to the playlist, this thread controls :
48  *    - Statistics
49  *    - VLM
50  * \param p_parent
51  * \return an object with a started thread
52  */
53 void playlist_Activate( playlist_t *p_playlist )
54 {
55     /* */
56     playlist_private_t *p_sys = pl_priv(p_playlist);
57
58     /* Fetcher */
59     p_sys->p_fetcher = playlist_fetcher_New( p_playlist );
60     if( !p_sys->p_fetcher )
61         msg_Err( p_playlist, "cannot create playlist fetcher" );
62
63     /* Preparse */
64     p_sys->p_preparser = playlist_preparser_New( p_playlist, p_sys->p_fetcher );
65     if( !p_sys->p_preparser )
66         msg_Err( p_playlist, "cannot create playlist preparser" );
67
68     /* Start the playlist thread */
69     if( vlc_thread_create( p_playlist, "playlist", Thread,
70                            VLC_THREAD_PRIORITY_LOW, false ) )
71     {
72         msg_Err( p_playlist, "cannot spawn playlist thread" );
73     }
74     msg_Dbg( p_playlist, "Activated" );
75 }
76
77 void playlist_Deactivate( playlist_t *p_playlist )
78 {
79     /* */
80     playlist_private_t *p_sys = pl_priv(p_playlist);
81
82     msg_Dbg( p_playlist, "Deactivate" );
83
84     vlc_object_kill( p_playlist );
85     PL_LOCK;
86     vlc_cond_signal( &p_sys->signal );
87     PL_UNLOCK;
88
89     vlc_thread_join( p_playlist );
90     assert( !p_sys->p_input );
91
92     PL_LOCK;
93     playlist_preparser_t *p_preparser = p_sys->p_preparser;
94     playlist_fetcher_t *p_fetcher = p_sys->p_fetcher;
95
96     p_sys->p_preparser = NULL;
97     p_sys->p_fetcher = NULL;
98     PL_UNLOCK;
99
100     if( p_preparser )
101         playlist_preparser_Delete( p_preparser );
102     if( p_fetcher )
103         playlist_fetcher_Delete( p_fetcher );
104
105     /* release input ressources */
106     if( p_sys->p_input_ressource )
107         input_ressource_Delete( p_sys->p_input_ressource );
108     p_sys->p_input_ressource = NULL;
109
110     /* */
111     playlist_MLDump( p_playlist );
112
113     PL_LOCK;
114
115     /* Release the current node */
116     set_current_status_node( p_playlist, NULL );
117
118     /* Release the current item */
119     set_current_status_item( p_playlist, NULL );
120
121     FOREACH_ARRAY( playlist_item_t *p_del, p_playlist->all_items )
122         free( p_del->pp_children );
123         vlc_gc_decref( p_del->p_input );
124         free( p_del );
125     FOREACH_END();
126     ARRAY_RESET( p_playlist->all_items );
127     FOREACH_ARRAY( playlist_item_t *p_del, p_sys->items_to_delete )
128         free( p_del->pp_children );
129         vlc_gc_decref( p_del->p_input );
130         free( p_del );
131     FOREACH_END();
132     ARRAY_RESET( p_sys->items_to_delete );
133
134     ARRAY_RESET( p_playlist->items );
135     ARRAY_RESET( p_playlist->current );
136
137     PL_UNLOCK;
138
139     msg_Dbg( p_playlist, "Deactivated" );
140 }
141
142 /* */
143
144 /* Input Callback */
145 static int InputEvent( vlc_object_t *p_this, char const *psz_cmd,
146                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
147 {
148     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
149     playlist_t *p_playlist = p_data;
150
151     if( newval.i_int != INPUT_EVENT_STATE &&
152         newval.i_int != INPUT_EVENT_DEAD )
153         return VLC_SUCCESS;
154
155     PL_LOCK;
156
157     /* XXX: signaling while not changing any parameter... suspicious... */
158     vlc_cond_signal( &pl_priv(p_playlist)->signal );
159
160     PL_UNLOCK;
161     return VLC_SUCCESS;
162 }
163
164 static void UpdateActivity( playlist_t *p_playlist, int i_delta )
165 {
166     PL_ASSERT_LOCKED;
167
168     const int i_activity = var_GetInteger( p_playlist, "activity" ) ;
169     var_SetInteger( p_playlist, "activity", i_activity + i_delta );
170 }
171
172 /**
173  * Synchronise the current index of the playlist
174  * to match the index of the current item.
175  *
176  * \param p_playlist the playlist structure
177  * \param p_cur the current playlist item
178  * \return nothing
179  */
180 static void ResyncCurrentIndex( playlist_t *p_playlist, playlist_item_t *p_cur )
181 {
182     PL_ASSERT_LOCKED;
183
184     PL_DEBUG( "resyncing on %s", PLI_NAME( p_cur ) );
185     /* Simply resync index */
186     int i;
187     p_playlist->i_current_index = -1;
188     for( i = 0 ; i< p_playlist->current.i_size; i++ )
189     {
190         if( ARRAY_VAL( p_playlist->current, i ) == p_cur )
191         {
192             p_playlist->i_current_index = i;
193             break;
194         }
195     }
196     PL_DEBUG( "%s is at %i", PLI_NAME( p_cur ), p_playlist->i_current_index );
197 }
198
199 static void ResetCurrentlyPlaying( playlist_t *p_playlist,
200                                    playlist_item_t *p_cur )
201 {
202     playlist_private_t *p_sys = pl_priv(p_playlist);
203
204     stats_TimerStart( p_playlist, "Items array build",
205                       STATS_TIMER_PLAYLIST_BUILD );
206     PL_DEBUG( "rebuilding array of current - root %s",
207               PLI_NAME( p_sys->status.p_node ) );
208     ARRAY_RESET( p_playlist->current );
209     p_playlist->i_current_index = -1;
210     for( playlist_item_t *p_next = NULL; ; )
211     {
212         /** FIXME: this is *slow* */
213         p_next = playlist_GetNextLeaf( p_playlist,
214                                        p_sys->status.p_node,
215                                        p_next, true, false );
216         if( !p_next )
217             break;
218
219         if( p_next == p_cur )
220             p_playlist->i_current_index = p_playlist->current.i_size;
221         ARRAY_APPEND( p_playlist->current, p_next);
222     }
223     PL_DEBUG("rebuild done - %i items, index %i", p_playlist->current.i_size,
224                                                   p_playlist->i_current_index);
225
226     if( var_GetBool( p_playlist, "random" ) )
227     {
228         /* Shuffle the array */
229         srand( (unsigned int)mdate() );
230         for( int j = p_playlist->current.i_size - 1; j > 0; j-- )
231         {
232             int i = rand() % (j+1); /* between 0 and j */
233             playlist_item_t *p_tmp;
234             /* swap the two items */
235             p_tmp = ARRAY_VAL(p_playlist->current, i);
236             ARRAY_VAL(p_playlist->current,i) = ARRAY_VAL(p_playlist->current,j);
237             ARRAY_VAL(p_playlist->current,j) = p_tmp;
238         }
239     }
240     p_sys->b_reset_currently_playing = false;
241     stats_TimerStop( p_playlist, STATS_TIMER_PLAYLIST_BUILD );
242 }
243
244
245 /**
246  * Start the input for an item
247  *
248  * \param p_playlist the playlist object
249  * \param p_item the item to play
250  * \return nothing
251  */
252 static int PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
253 {
254     playlist_private_t *p_sys = pl_priv(p_playlist);
255     input_item_t *p_input = p_item->p_input;
256
257     PL_ASSERT_LOCKED;
258
259     msg_Dbg( p_playlist, "creating new input thread" );
260
261     p_input->i_nb_played++;
262     set_current_status_item( p_playlist, p_item );
263
264     p_sys->status.i_status = PLAYLIST_RUNNING;
265
266     UpdateActivity( p_playlist, DEFAULT_INPUT_ACTIVITY );
267
268     assert( p_sys->p_input == NULL );
269
270     input_thread_t *p_input_thread =
271         input_CreateThreadExtended( p_playlist, p_input, NULL, p_sys->p_input_ressource );
272
273     if( p_input_thread )
274     {
275         p_sys->p_input = p_input_thread;
276
277         var_AddCallback( p_input_thread, "intf-event", InputEvent, p_playlist );
278     }
279
280     p_sys->p_input_ressource = NULL;
281
282     char *psz_uri = input_item_GetURI( p_item->p_input );
283     if( psz_uri && ( !strncmp( psz_uri, "directory:", 10 ) ||
284                      !strncmp( psz_uri, "vlc:", 4 ) ) )
285     {
286         free( psz_uri );
287         return VLC_SUCCESS;
288     }
289     free( psz_uri );
290
291     /* TODO store art policy in playlist private data */
292     if( var_GetInteger( p_playlist, "album-art" ) == ALBUM_ART_WHEN_PLAYED )
293     {
294         bool b_has_art;
295
296         char *psz_arturl, *psz_name;
297         psz_arturl = input_item_GetArtURL( p_input );
298         psz_name = input_item_GetName( p_input );
299
300         /* p_input->p_meta should not be null after a successfull CreateThread */
301         b_has_art = !EMPTY_STR( psz_arturl );
302
303         if( !b_has_art || strncmp( psz_arturl, "attachment://", 13 ) )
304         {
305             PL_DEBUG( "requesting art for %s", psz_name );
306             playlist_AskForArtEnqueue( p_playlist, p_input, pl_Locked );
307         }
308         free( psz_arturl );
309         free( psz_name );
310     }
311
312     PL_UNLOCK;
313     var_SetInteger( p_playlist, "playlist-current", p_input->i_id );
314     PL_LOCK;
315
316     return VLC_SUCCESS;
317 }
318
319 /**
320  * Compute the next playlist item depending on
321  * the playlist course mode (forward, backward, random, view,...).
322  *
323  * \param p_playlist the playlist object
324  * \return nothing
325  */
326 static playlist_item_t *NextItem( playlist_t *p_playlist )
327 {
328     playlist_private_t *p_sys = pl_priv(p_playlist);
329     playlist_item_t *p_new = NULL;
330
331     /* Handle quickly a few special cases */
332     /* No items to play */
333     if( p_playlist->items.i_size == 0 )
334     {
335         msg_Info( p_playlist, "playlist is empty" );
336         return NULL;
337     }
338
339     /* Start the real work */
340     if( p_sys->request.b_request )
341     {
342         p_new = p_sys->request.p_item;
343         int i_skip = p_sys->request.i_skip;
344         PL_DEBUG( "processing request item %s node %s skip %i",
345                         PLI_NAME( p_sys->request.p_item ),
346                         PLI_NAME( p_sys->request.p_node ), i_skip );
347
348         if( p_sys->request.p_node &&
349             p_sys->request.p_node != get_current_status_node( p_playlist ) )
350         {
351
352             set_current_status_node( p_playlist, p_sys->request.p_node );
353             p_sys->request.p_node = NULL;
354             p_sys->b_reset_currently_playing = true;
355         }
356
357         /* If we are asked for a node, go to it's first child */
358         if( i_skip == 0 && ( p_new == NULL || p_new->i_children != -1 ) )
359         {
360             i_skip++;
361             if( p_new != NULL )
362             {
363                 p_new = playlist_GetNextLeaf( p_playlist, p_new, NULL, true, false );
364                 for( int i = 0; i < p_playlist->current.i_size; i++ )
365                 {
366                     if( p_new == ARRAY_VAL( p_playlist->current, i ) )
367                     {
368                         p_playlist->i_current_index = i;
369                         i_skip = 0;
370                     }
371                 }
372             }
373         }
374
375         if( p_sys->b_reset_currently_playing )
376             /* A bit too bad to reset twice ... */
377             ResetCurrentlyPlaying( p_playlist, p_new );
378         else if( p_new )
379             ResyncCurrentIndex( p_playlist, p_new );
380         else
381             p_playlist->i_current_index = -1;
382
383         if( p_playlist->current.i_size && (i_skip > 0) )
384         {
385             if( p_playlist->i_current_index < -1 )
386                 p_playlist->i_current_index = -1;
387             for( int i = i_skip; i > 0 ; i-- )
388             {
389                 p_playlist->i_current_index++;
390                 if( p_playlist->i_current_index >= p_playlist->current.i_size )
391                 {
392                     PL_DEBUG( "looping - restarting at beginning of node" );
393                     p_playlist->i_current_index = 0;
394                 }
395             }
396             p_new = ARRAY_VAL( p_playlist->current,
397                                p_playlist->i_current_index );
398         }
399         else if( p_playlist->current.i_size && (i_skip < 0) )
400         {
401             for( int i = i_skip; i < 0 ; i++ )
402             {
403                 p_playlist->i_current_index--;
404                 if( p_playlist->i_current_index <= -1 )
405                 {
406                     PL_DEBUG( "looping - restarting at end of node" );
407                     p_playlist->i_current_index = p_playlist->current.i_size-1;
408                 }
409             }
410             p_new = ARRAY_VAL( p_playlist->current,
411                                p_playlist->i_current_index );
412         }
413         /* Clear the request */
414         p_sys->request.b_request = false;
415     }
416     /* "Automatic" item change ( next ) */
417     else
418     {
419         bool b_loop = var_GetBool( p_playlist, "loop" );
420         bool b_repeat = var_GetBool( p_playlist, "repeat" );
421         bool b_playstop = var_GetBool( p_playlist, "play-and-stop" );
422
423         /* Repeat and play/stop */
424         if( b_repeat && get_current_status_item( p_playlist ) )
425         {
426             msg_Dbg( p_playlist,"repeating item" );
427             return get_current_status_item( p_playlist );
428         }
429         if( b_playstop )
430         {
431             msg_Dbg( p_playlist,"stopping (play and stop)" );
432             return NULL;
433         }
434
435         /* */
436         if( get_current_status_item( p_playlist ) )
437         {
438             playlist_item_t *p_parent = get_current_status_item( p_playlist );
439             while( p_parent )
440             {
441                 if( p_parent->i_flags & PLAYLIST_SKIP_FLAG )
442                 {
443                     msg_Dbg( p_playlist, "blocking item, stopping") ;
444                     return NULL;
445                 }
446                 p_parent = p_parent->p_parent;
447             }
448         }
449
450         PL_DEBUG( "changing item without a request (current %i/%i)",
451                   p_playlist->i_current_index, p_playlist->current.i_size );
452         /* Cant go to next from current item */
453         if( get_current_status_item( p_playlist ) &&
454             get_current_status_item( p_playlist )->i_flags & PLAYLIST_SKIP_FLAG )
455             return NULL;
456
457         if( p_sys->b_reset_currently_playing )
458             ResetCurrentlyPlaying( p_playlist,
459                                    get_current_status_item( p_playlist ) );
460
461         p_playlist->i_current_index++;
462         assert( p_playlist->i_current_index <= p_playlist->current.i_size );
463         if( p_playlist->i_current_index == p_playlist->current.i_size )
464         {
465             if( !b_loop || p_playlist->current.i_size == 0 )
466                 return NULL;
467             p_playlist->i_current_index = 0;
468         }
469         PL_DEBUG( "using item %i", p_playlist->i_current_index );
470         if ( p_playlist->current.i_size == 0 )
471             return NULL;
472
473         p_new = ARRAY_VAL( p_playlist->current, p_playlist->i_current_index );
474         /* The new item can't be autoselected  */
475         if( p_new != NULL && p_new->i_flags & PLAYLIST_SKIP_FLAG )
476             return NULL;
477     }
478     return p_new;
479 }
480
481 static int LoopInput( playlist_t *p_playlist )
482 {
483     playlist_private_t *p_sys = pl_priv(p_playlist);
484     input_thread_t *p_input = p_sys->p_input;
485
486     if( !p_input )
487         return VLC_EGENERIC;
488
489     if( ( p_sys->request.b_request || !vlc_object_alive( p_playlist ) ) && !p_input->b_die )
490     {
491         PL_DEBUG( "incoming request - stopping current input" );
492         input_StopThread( p_input );
493     }
494
495     /* This input is dead. Remove it ! */
496     if( p_input->b_dead )
497     {
498         PL_DEBUG( "dead input" );
499
500         assert( p_sys->p_input_ressource == NULL );
501
502         p_sys->p_input_ressource = input_DetachRessource( p_input );
503         if( !var_CreateGetBool( p_input, "sout-keep" ) )
504             input_ressource_TerminateSout( p_sys->p_input_ressource );
505
506         /* The DelCallback must be issued without playlist lock
507          * It is not a problem as we return VLC_EGENERIC */
508         PL_UNLOCK;
509         var_DelCallback( p_input, "intf-event", InputEvent, p_playlist );
510         PL_LOCK;
511
512         p_sys->p_input = NULL;
513         vlc_thread_join( p_input );
514         vlc_object_release( p_input );
515
516         UpdateActivity( p_playlist, -DEFAULT_INPUT_ACTIVITY );
517
518         return VLC_EGENERIC;
519     }
520     /* This input is dying, let it do */
521     else if( p_input->b_die )
522     {
523         PL_DEBUG( "dying input" );
524     }
525     /* This input has finished, ask it to die ! */
526     else if( p_input->b_error || p_input->b_eof )
527     {
528         PL_DEBUG( "finished input" );
529         input_StopThread( p_input );
530     }
531     return VLC_SUCCESS;
532 }
533
534 static void LoopRequest( playlist_t *p_playlist )
535 {
536     playlist_private_t *p_sys = pl_priv(p_playlist);
537     assert( !p_sys->p_input );
538
539     /* No input. Several cases
540      *  - No request, running status -> start new item
541      *  - No request, stopped status -> collect garbage
542      *  - Request, running requested -> start new item
543      *  - Request, stopped requested -> collect garbage
544     */
545     const int i_status = p_sys->request.b_request ?
546                          p_sys->request.i_status : p_sys->status.i_status;
547
548     if( i_status == PLAYLIST_STOPPED )
549     {
550         p_sys->status.i_status = PLAYLIST_STOPPED;
551
552         if( p_sys->p_input_ressource )
553             input_ressource_TerminateVout( p_sys->p_input_ressource );
554
555         if( vlc_object_alive( p_playlist ) )
556             vlc_cond_wait( &pl_priv(p_playlist)->signal,
557                            &vlc_internals(p_playlist)->lock );
558         return;
559     }
560
561     playlist_item_t *p_item = NextItem( p_playlist );
562     if( p_item )
563     {
564         msg_Dbg( p_playlist, "starting new item" );
565         PlayItem( p_playlist, p_item );
566         return;
567     }
568
569     msg_Dbg( p_playlist, "nothing to play" );
570     p_sys->status.i_status = PLAYLIST_STOPPED;
571
572     if( var_GetBool( p_playlist, "play-and-exit" ) )
573     {
574         msg_Info( p_playlist, "end of playlist, exiting" );
575         vlc_object_kill( p_playlist->p_libvlc );
576     }
577 }
578
579 /**
580  * Run the main control thread itself
581  */
582 static void *Thread ( vlc_object_t *p_this )
583 {
584     playlist_t *p_playlist = (playlist_t*)p_this;
585     playlist_private_t *p_sys = pl_priv(p_playlist);
586     int canc = vlc_savecancel();
587
588     vlc_object_lock( p_playlist );
589     while( vlc_object_alive( p_playlist ) || p_sys->p_input )
590     {
591         /* FIXME: what's that ! */
592         if( p_sys->b_reset_currently_playing &&
593             mdate() - p_sys->last_rebuild_date > 30000 ) // 30 ms
594         {
595             ResetCurrentlyPlaying( p_playlist,
596                                    get_current_status_item( p_playlist ) );
597             p_sys->last_rebuild_date = mdate();
598         }
599
600         /* If there is an input, check that it doesn't need to die. */
601         while( !LoopInput( p_playlist ) )
602             vlc_cond_wait( &pl_priv(p_playlist)->signal,
603                            &vlc_internals(p_playlist)->lock );
604
605         LoopRequest( p_playlist );
606     }
607     vlc_object_unlock( p_playlist );
608
609     vlc_restorecancel (canc);
610     return NULL;
611 }
612