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