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