]> git.sesse.net Git - vlc/blob - src/playlist/thread.c
playlist: simplify loop and do not expose already dead input
[vlc] / src / playlist / thread.c
1 /*****************************************************************************
2  * thread.c : Playlist management functions
3  *****************************************************************************
4  * Copyright © 1999-2008 VLC authors and VideoLAN
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 it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * 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 <assert.h>
29
30 #include <vlc_common.h>
31 #include <vlc_es.h>
32 #include <vlc_input.h>
33 #include <vlc_interface.h>
34 #include <vlc_playlist.h>
35 #include <vlc_rand.h>
36 #include "stream_output/stream_output.h"
37 #include "playlist_internal.h"
38
39 /*****************************************************************************
40  * Local prototypes
41  *****************************************************************************/
42 static void *Thread   ( void * );
43
44 /*****************************************************************************
45  * Main functions for the global thread
46  *****************************************************************************/
47
48 /**
49  * Create the main playlist threads.
50  * Additionally to the playlist, this thread controls :
51  *    - Statistics
52  *    - VLM
53  * \param p_parent
54  * \return an object with a started thread
55  */
56 void playlist_Activate( playlist_t *p_playlist )
57 {
58     /* */
59     playlist_private_t *p_sys = pl_priv(p_playlist);
60
61     p_sys->p_input_resource = input_resource_New( VLC_OBJECT( p_playlist ) );
62     if( unlikely(p_sys->p_input_resource == NULL) )
63         abort();
64
65     /* Start the playlist thread */
66     if( vlc_clone( &p_sys->thread, Thread, p_playlist,
67                    VLC_THREAD_PRIORITY_LOW ) )
68     {
69         msg_Err( p_playlist, "cannot spawn playlist thread" );
70     }
71     msg_Dbg( p_playlist, "playlist threads correctly activated" );
72 }
73
74 void playlist_Deactivate( playlist_t *p_playlist )
75 {
76     /* */
77     playlist_private_t *p_sys = pl_priv(p_playlist);
78
79     msg_Dbg( p_playlist, "deactivating the playlist" );
80
81     PL_LOCK;
82     p_sys->killed = true;
83     vlc_cond_signal( &p_sys->signal );
84     PL_UNLOCK;
85
86     vlc_join( p_sys->thread, NULL );
87     assert( !p_sys->p_input );
88
89     /* release input resources */
90     input_resource_Release( p_sys->p_input_resource );
91
92     if( var_InheritBool( p_playlist, "media-library" ) )
93         playlist_MLDump( p_playlist );
94
95     PL_LOCK;
96
97     /* Release the current node */
98     set_current_status_node( p_playlist, NULL );
99
100     /* Release the current item */
101     set_current_status_item( p_playlist, NULL );
102
103     PL_UNLOCK;
104
105     msg_Dbg( p_playlist, "playlist correctly deactivated" );
106 }
107
108 /* */
109
110 /* Input Callback */
111 static int InputEvent( vlc_object_t *p_this, char const *psz_cmd,
112                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
113 {
114     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
115     playlist_t *p_playlist = p_data;
116
117     if( newval.i_int != INPUT_EVENT_STATE &&
118         newval.i_int != INPUT_EVENT_DEAD )
119         return VLC_SUCCESS;
120
121     PL_LOCK;
122
123     /* XXX: signaling while not changing any parameter... suspicious... */
124     vlc_cond_signal( &pl_priv(p_playlist)->signal );
125
126     PL_UNLOCK;
127     return VLC_SUCCESS;
128 }
129
130 /**
131  * Synchronise the current index of the playlist
132  * to match the index of the current item.
133  *
134  * \param p_playlist the playlist structure
135  * \param p_cur the current playlist item
136  * \return nothing
137  */
138 void ResyncCurrentIndex( playlist_t *p_playlist, playlist_item_t *p_cur )
139 {
140     PL_ASSERT_LOCKED;
141
142     PL_DEBUG( "resyncing on %s", PLI_NAME( p_cur ) );
143     /* Simply resync index */
144     int i;
145     p_playlist->i_current_index = -1;
146     for( i = 0 ; i< p_playlist->current.i_size; i++ )
147     {
148         if( ARRAY_VAL( p_playlist->current, i ) == p_cur )
149         {
150             p_playlist->i_current_index = i;
151             break;
152         }
153     }
154     PL_DEBUG( "%s is at %i", PLI_NAME( p_cur ), p_playlist->i_current_index );
155 }
156
157 /**
158  * Reset the currently playing playlist.
159  *
160  * \param p_playlist the playlist structure
161  * \param p_cur the current playlist item
162  * \return nothing
163  */
164 void ResetCurrentlyPlaying( playlist_t *p_playlist,
165                                    playlist_item_t *p_cur )
166 {
167     playlist_private_t *p_sys = pl_priv(p_playlist);
168
169     PL_DEBUG( "rebuilding array of current - root %s",
170               PLI_NAME( p_sys->status.p_node ) );
171     ARRAY_RESET( p_playlist->current );
172     p_playlist->i_current_index = -1;
173     for( playlist_item_t *p_next = NULL; ; )
174     {
175         /** FIXME: this is *slow* */
176         p_next = playlist_GetNextLeaf( p_playlist,
177                                        p_sys->status.p_node,
178                                        p_next, true, false );
179         if( !p_next )
180             break;
181
182         if( p_next == p_cur )
183             p_playlist->i_current_index = p_playlist->current.i_size;
184         ARRAY_APPEND( p_playlist->current, p_next);
185     }
186     PL_DEBUG("rebuild done - %i items, index %i", p_playlist->current.i_size,
187                                                   p_playlist->i_current_index);
188
189     if( var_GetBool( p_playlist, "random" ) && ( p_playlist->current.i_size > 0 ) )
190     {
191         /* Shuffle the array */
192         for( unsigned j = p_playlist->current.i_size - 1; j > 0; j-- )
193         {
194             unsigned i = vlc_lrand48() % (j+1); /* between 0 and j */
195             playlist_item_t *p_tmp;
196             /* swap the two items */
197             p_tmp = ARRAY_VAL(p_playlist->current, i);
198             ARRAY_VAL(p_playlist->current,i) = ARRAY_VAL(p_playlist->current,j);
199             ARRAY_VAL(p_playlist->current,j) = p_tmp;
200         }
201     }
202     p_sys->b_reset_currently_playing = false;
203 }
204
205
206 /**
207  * Start the input for an item
208  *
209  * \param p_playlist the playlist object
210  * \param p_item the item to play
211  * \return nothing
212  */
213 static int PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
214 {
215     playlist_private_t *p_sys = pl_priv(p_playlist);
216     input_item_t *p_input = p_item->p_input;
217
218     PL_ASSERT_LOCKED;
219
220     msg_Dbg( p_playlist, "creating new input thread" );
221
222     p_input->i_nb_played++;
223     set_current_status_item( p_playlist, p_item );
224
225     p_sys->status.i_status = PLAYLIST_RUNNING;
226
227     var_TriggerCallback( p_playlist, "activity" );
228
229     assert( p_sys->p_input == NULL );
230
231     input_thread_t *p_input_thread = input_Create( p_playlist, p_input, NULL, p_sys->p_input_resource );
232     if( p_input_thread )
233     {
234         p_sys->p_input = p_input_thread;
235         var_AddCallback( p_input_thread, "intf-event", InputEvent, p_playlist );
236
237         var_SetAddress( p_playlist, "input-current", p_input_thread );
238
239         if( input_Start( p_sys->p_input ) )
240         {
241             vlc_object_release( p_input_thread );
242             p_sys->p_input = p_input_thread = NULL;
243         }
244     }
245
246     char *psz_uri = input_item_GetURI( p_item->p_input );
247     if( psz_uri && ( !strncmp( psz_uri, "directory:", 10 ) ||
248                      !strncmp( psz_uri, "vlc:", 4 ) ) )
249     {
250         free( psz_uri );
251         return VLC_SUCCESS;
252     }
253     free( psz_uri );
254
255     /* TODO store art policy in playlist private data */
256     if( var_GetInteger( p_playlist, "album-art" ) == ALBUM_ART_WHEN_PLAYED )
257     {
258         bool b_has_art;
259
260         char *psz_arturl, *psz_name;
261         psz_arturl = input_item_GetArtURL( p_input );
262         psz_name = input_item_GetName( p_input );
263
264         /* p_input->p_meta should not be null after a successful CreateThread */
265         b_has_art = !EMPTY_STR( psz_arturl );
266
267         if( !b_has_art || strncmp( psz_arturl, "attachment://", 13 ) )
268         {
269             PL_DEBUG( "requesting art for %s", psz_name );
270             playlist_AskForArtEnqueue( p_playlist, p_input );
271         }
272         free( psz_arturl );
273         free( psz_name );
274     }
275     /* FIXME: this is not safe !!*/
276     PL_UNLOCK;
277     var_SetAddress( p_playlist, "item-current", p_input );
278     PL_LOCK;
279
280     return VLC_SUCCESS;
281 }
282
283 /**
284  * Compute the next playlist item depending on
285  * the playlist course mode (forward, backward, random, view,...).
286  *
287  * \param p_playlist the playlist object
288  * \return nothing
289  */
290 static playlist_item_t *NextItem( playlist_t *p_playlist )
291 {
292     playlist_private_t *p_sys = pl_priv(p_playlist);
293     playlist_item_t *p_new = NULL;
294
295     /* Handle quickly a few special cases */
296     /* No items to play */
297     if( p_playlist->items.i_size == 0 )
298     {
299         msg_Info( p_playlist, "playlist is empty" );
300         return NULL;
301     }
302
303     /* Start the real work */
304     if( p_sys->request.b_request )
305     {
306         p_new = p_sys->request.p_item;
307         int i_skip = p_sys->request.i_skip;
308         PL_DEBUG( "processing request item: %s, node: %s, skip: %i",
309                         PLI_NAME( p_sys->request.p_item ),
310                         PLI_NAME( p_sys->request.p_node ), i_skip );
311
312         if( p_sys->request.p_node &&
313             p_sys->request.p_node != get_current_status_node( p_playlist ) )
314         {
315
316             set_current_status_node( p_playlist, p_sys->request.p_node );
317             p_sys->request.p_node = NULL;
318             p_sys->b_reset_currently_playing = true;
319         }
320
321         /* If we are asked for a node, go to it's first child */
322         if( i_skip == 0 && ( p_new == NULL || p_new->i_children != -1 ) )
323         {
324             i_skip++;
325             if( p_new != NULL )
326             {
327                 p_new = playlist_GetNextLeaf( p_playlist, p_new, NULL, true, false );
328                 for( int i = 0; i < p_playlist->current.i_size; i++ )
329                 {
330                     if( p_new == ARRAY_VAL( p_playlist->current, i ) )
331                     {
332                         p_playlist->i_current_index = i;
333                         i_skip = 0;
334                     }
335                 }
336             }
337         }
338
339         if( p_sys->b_reset_currently_playing )
340             /* A bit too bad to reset twice ... */
341             ResetCurrentlyPlaying( p_playlist, p_new );
342         else if( p_new )
343             ResyncCurrentIndex( p_playlist, p_new );
344         else
345             p_playlist->i_current_index = -1;
346
347         if( p_playlist->current.i_size && (i_skip > 0) )
348         {
349             if( p_playlist->i_current_index < -1 )
350                 p_playlist->i_current_index = -1;
351             for( int i = i_skip; i > 0 ; i-- )
352             {
353                 p_playlist->i_current_index++;
354                 if( p_playlist->i_current_index >= p_playlist->current.i_size )
355                 {
356                     PL_DEBUG( "looping - restarting at beginning of node" );
357                     p_playlist->i_current_index = 0;
358                 }
359             }
360             p_new = ARRAY_VAL( p_playlist->current,
361                                p_playlist->i_current_index );
362         }
363         else if( p_playlist->current.i_size && (i_skip < 0) )
364         {
365             for( int i = i_skip; i < 0 ; i++ )
366             {
367                 p_playlist->i_current_index--;
368                 if( p_playlist->i_current_index <= -1 )
369                 {
370                     PL_DEBUG( "looping - restarting at end of node" );
371                     p_playlist->i_current_index = p_playlist->current.i_size-1;
372                 }
373             }
374             p_new = ARRAY_VAL( p_playlist->current,
375                                p_playlist->i_current_index );
376         }
377         /* Clear the request */
378         p_sys->request.b_request = false;
379     }
380     /* "Automatic" item change ( next ) */
381     else
382     {
383         bool b_loop = var_GetBool( p_playlist, "loop" );
384         bool b_repeat = var_GetBool( p_playlist, "repeat" );
385         bool b_playstop = var_GetBool( p_playlist, "play-and-stop" );
386
387         /* Repeat and play/stop */
388         if( b_repeat && get_current_status_item( p_playlist ) )
389         {
390             msg_Dbg( p_playlist,"repeating item" );
391             return get_current_status_item( p_playlist );
392         }
393         if( b_playstop )
394         {
395             msg_Dbg( p_playlist,"stopping (play and stop)" );
396             return NULL;
397         }
398
399         /* */
400         if( get_current_status_item( p_playlist ) )
401         {
402             playlist_item_t *p_parent = get_current_status_item( p_playlist );
403             while( p_parent )
404             {
405                 if( p_parent->i_flags & PLAYLIST_SKIP_FLAG )
406                 {
407                     msg_Dbg( p_playlist, "blocking item, stopping") ;
408                     return NULL;
409                 }
410                 p_parent = p_parent->p_parent;
411             }
412         }
413
414         PL_DEBUG( "changing item without a request (current %i/%i)",
415                   p_playlist->i_current_index, p_playlist->current.i_size );
416         /* Cant go to next from current item */
417         if( get_current_status_item( p_playlist ) &&
418             get_current_status_item( p_playlist )->i_flags & PLAYLIST_SKIP_FLAG )
419             return NULL;
420
421         if( p_sys->b_reset_currently_playing )
422             ResetCurrentlyPlaying( p_playlist,
423                                    get_current_status_item( p_playlist ) );
424
425         p_playlist->i_current_index++;
426         assert( p_playlist->i_current_index <= p_playlist->current.i_size );
427         if( p_playlist->i_current_index == p_playlist->current.i_size )
428         {
429             if( !b_loop || p_playlist->current.i_size == 0 )
430                 return NULL;
431             p_playlist->i_current_index = 0;
432         }
433         PL_DEBUG( "using item %i", p_playlist->i_current_index );
434         if ( p_playlist->current.i_size == 0 )
435             return NULL;
436
437         p_new = ARRAY_VAL( p_playlist->current, p_playlist->i_current_index );
438         /* The new item can't be autoselected  */
439         if( p_new != NULL && p_new->i_flags & PLAYLIST_SKIP_FLAG )
440             return NULL;
441     }
442     return p_new;
443 }
444
445 static void LoopInput( playlist_t *p_playlist )
446 {
447     playlist_private_t *p_sys = pl_priv(p_playlist);
448     input_thread_t *p_input = p_sys->p_input;
449
450     assert( p_input != NULL );
451
452     if( ( p_sys->request.b_request || p_sys->killed ) && vlc_object_alive(p_input) )
453     {
454         PL_DEBUG( "incoming request - stopping current input" );
455         input_Stop( p_input, true );
456     }
457
458     /* This input is dead. Remove it ! */
459     if( p_input->b_dead )
460     {
461         p_sys->p_input = NULL;
462         PL_DEBUG( "dead input" );
463         PL_UNLOCK;
464
465         /* WARNING: Input resource manipulation and callback deletion are
466          * incompatible with the playlist lock. */
467         if( !var_InheritBool( p_input, "sout-keep" ) )
468             input_resource_TerminateSout( p_sys->p_input_resource );
469         var_DelCallback( p_input, "intf-event", InputEvent, p_playlist );
470
471         input_Close( p_input );
472         PL_LOCK;
473         var_TriggerCallback( p_playlist, "activity" );
474         return;
475     }
476     /* This input is dying, let it do */
477     else if( !vlc_object_alive(p_input) )
478     {
479         PL_DEBUG( "dying input" );
480     }
481     /* This input has finished, ask it to die ! */
482     else if( p_input->b_error || p_input->b_eof )
483     {
484         PL_DEBUG( "finished input" );
485         input_Stop( p_input, false );
486     }
487
488     vlc_cond_wait( &p_sys->signal, &p_sys->lock );
489 }
490
491 static void LoopRequest( playlist_t *p_playlist )
492 {
493     playlist_private_t *p_sys = pl_priv(p_playlist);
494     assert( !p_sys->p_input );
495
496     /* No input. Several cases
497      *  - No request, running status -> start new item
498      *  - No request, stopped status -> collect garbage
499      *  - Request, running requested -> start new item
500      *  - Request, stopped requested -> collect garbage
501     */
502     const int i_status = p_sys->request.b_request ?
503                          p_sys->request.i_status : p_sys->status.i_status;
504
505     if( i_status == PLAYLIST_STOPPED )
506     {
507         p_sys->status.i_status = PLAYLIST_STOPPED;
508         vlc_cond_wait( &p_sys->signal, &p_sys->lock );
509         return;
510     }
511
512     playlist_item_t *p_item = NextItem( p_playlist );
513     if( p_item )
514     {
515         msg_Dbg( p_playlist, "starting playback of the new playlist item" );
516         ResyncCurrentIndex( p_playlist, p_item );
517         PlayItem( p_playlist, p_item );
518         return;
519     }
520
521     msg_Dbg( p_playlist, "nothing to play" );
522     p_sys->status.i_status = PLAYLIST_STOPPED;
523
524     if( var_GetBool( p_playlist, "play-and-exit" ) )
525     {
526         msg_Info( p_playlist, "end of playlist, exiting" );
527         libvlc_Quit( p_playlist->p_libvlc );
528     }
529 }
530
531 /**
532  * Run the main control thread itself
533  */
534 static void *Thread ( void *data )
535 {
536     playlist_t *p_playlist = data;
537     playlist_private_t *p_sys = pl_priv(p_playlist);
538
539     playlist_Lock( p_playlist );
540     for( ;; )
541     {
542         while( p_sys->p_input != NULL )
543             LoopInput( p_playlist );
544
545         if( p_sys->killed )
546             break; /* THE END */
547
548         /* Destroy any video display if the playlist is stopped */
549         if( p_sys->status.i_status == PLAYLIST_STOPPED
550          && input_resource_HasVout( p_sys->p_input_resource ) )
551         {
552             PL_UNLOCK; /* Mind: NO LOCKS while manipulating input resources! */
553             input_resource_TerminateVout( p_sys->p_input_resource );
554             PL_LOCK;
555             continue; /* lost lock = lost state */
556         }
557
558         LoopRequest( p_playlist );
559     }
560     p_sys->status.i_status = PLAYLIST_STOPPED;
561     playlist_Unlock( p_playlist );
562
563     input_resource_Terminate( p_sys->p_input_resource );
564     return NULL;
565 }