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