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