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