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