1 /*****************************************************************************
2 * thread.c : Playlist management functions
3 *****************************************************************************
4 * Copyright © 1999-2008 the VideoLAN team
7 * Authors: Samuel Hocevar <sam@zoy.org>
8 * Clément Stenac <zorglub@videolan.org>
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.
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.
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 *****************************************************************************/
30 #include <vlc_common.h>
32 #include <vlc_input.h>
33 #include <vlc_interface.h>
34 #include <vlc_playlist.h>
36 #include "stream_output/stream_output.h"
37 #include "playlist_internal.h"
39 /*****************************************************************************
41 *****************************************************************************/
42 static void *Thread ( void * );
44 /*****************************************************************************
45 * Main functions for the global thread
46 *****************************************************************************/
49 * Create the main playlist threads.
50 * Additionally to the playlist, this thread controls :
54 * \return an object with a started thread
56 void playlist_Activate( playlist_t
*p_playlist
)
59 playlist_private_t
*p_sys
= pl_priv(p_playlist
);
61 /* Start the playlist thread */
62 if( vlc_clone( &p_sys
->thread
, Thread
, p_playlist
,
63 VLC_THREAD_PRIORITY_LOW
) )
65 msg_Err( p_playlist
, "cannot spawn playlist thread" );
67 msg_Dbg( p_playlist
, "playlist threads correctly activated" );
70 void playlist_Deactivate( playlist_t
*p_playlist
)
73 playlist_private_t
*p_sys
= pl_priv(p_playlist
);
75 msg_Dbg( p_playlist
, "deactivating the playlist" );
78 vlc_object_kill( p_playlist
);
79 vlc_cond_signal( &p_sys
->signal
);
82 vlc_join( p_sys
->thread
, NULL
);
83 assert( !p_sys
->p_input
);
85 /* release input resources */
86 if( p_sys
->p_input_resource
)
88 input_resource_Terminate( p_sys
->p_input_resource
);
89 input_resource_Release( p_sys
->p_input_resource
);
91 p_sys
->p_input_resource
= NULL
;
93 if( var_InheritBool( p_playlist
, "media-library" ) )
94 playlist_MLDump( p_playlist
);
98 /* Release the current node */
99 set_current_status_node( p_playlist
, NULL
);
101 /* Release the current item */
102 set_current_status_item( p_playlist
, NULL
);
106 msg_Dbg( p_playlist
, "playlist correctly deactivated" );
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
)
115 VLC_UNUSED(p_this
); VLC_UNUSED(psz_cmd
); VLC_UNUSED(oldval
);
116 playlist_t
*p_playlist
= p_data
;
118 if( newval
.i_int
!= INPUT_EVENT_STATE
&&
119 newval
.i_int
!= INPUT_EVENT_DEAD
)
124 /* XXX: signaling while not changing any parameter... suspicious... */
125 vlc_cond_signal( &pl_priv(p_playlist
)->signal
);
131 static void UpdateActivity( playlist_t
*p_playlist
, int i_delta
)
135 const int i_activity
= var_GetInteger( p_playlist
, "activity" ) ;
136 var_SetInteger( p_playlist
, "activity", i_activity
+ i_delta
);
140 * Synchronise the current index of the playlist
141 * to match the index of the current item.
143 * \param p_playlist the playlist structure
144 * \param p_cur the current playlist item
147 static void ResyncCurrentIndex( playlist_t
*p_playlist
, playlist_item_t
*p_cur
)
151 PL_DEBUG( "resyncing on %s", PLI_NAME( p_cur
) );
152 /* Simply resync index */
154 p_playlist
->i_current_index
= -1;
155 for( i
= 0 ; i
< p_playlist
->current
.i_size
; i
++ )
157 if( ARRAY_VAL( p_playlist
->current
, i
) == p_cur
)
159 p_playlist
->i_current_index
= i
;
163 PL_DEBUG( "%s is at %i", PLI_NAME( p_cur
), p_playlist
->i_current_index
);
166 static void ResetCurrentlyPlaying( playlist_t
*p_playlist
,
167 playlist_item_t
*p_cur
)
169 playlist_private_t
*p_sys
= pl_priv(p_playlist
);
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
; ; )
179 /** FIXME: this is *slow* */
180 p_next
= playlist_GetNextLeaf( p_playlist
,
181 p_sys
->status
.p_node
,
182 p_next
, true, false );
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
);
190 PL_DEBUG("rebuild done - %i items, index %i", p_playlist
->current
.i_size
,
191 p_playlist
->i_current_index
);
193 if( var_GetBool( p_playlist
, "random" ) && ( p_playlist
->current
.i_size
> 0 ) )
195 /* Shuffle the array */
196 for( unsigned j
= p_playlist
->current
.i_size
- 1; j
> 0; j
-- )
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
;
206 p_sys
->b_reset_currently_playing
= false;
207 stats_TimerStop( p_playlist
, STATS_TIMER_PLAYLIST_BUILD
);
212 * Start the input for an item
214 * \param p_playlist the playlist object
215 * \param p_item the item to play
218 static int PlayItem( playlist_t
*p_playlist
, playlist_item_t
*p_item
)
220 playlist_private_t
*p_sys
= pl_priv(p_playlist
);
221 input_item_t
*p_input
= p_item
->p_input
;
225 msg_Dbg( p_playlist
, "creating new input thread" );
227 p_input
->i_nb_played
++;
228 set_current_status_item( p_playlist
, p_item
);
230 p_sys
->status
.i_status
= PLAYLIST_RUNNING
;
232 UpdateActivity( p_playlist
, DEFAULT_INPUT_ACTIVITY
);
234 assert( p_sys
->p_input
== NULL
);
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
);
241 p_sys
->p_input
= p_input_thread
;
242 var_AddCallback( p_input_thread
, "intf-event", InputEvent
, p_playlist
);
244 var_SetAddress( p_playlist
, "input-current", p_input_thread
);
246 if( input_Start( p_sys
->p_input
) )
248 vlc_object_release( p_input_thread
);
249 p_sys
->p_input
= p_input_thread
= NULL
;
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 ) ) )
262 /* TODO store art policy in playlist private data */
263 if( var_GetInteger( p_playlist
, "album-art" ) == ALBUM_ART_WHEN_PLAYED
)
267 char *psz_arturl
, *psz_name
;
268 psz_arturl
= input_item_GetArtURL( p_input
);
269 psz_name
= input_item_GetName( p_input
);
271 /* p_input->p_meta should not be null after a successfull CreateThread */
272 b_has_art
= !EMPTY_STR( psz_arturl
);
274 if( !b_has_art
|| strncmp( psz_arturl
, "attachment://", 13 ) )
276 PL_DEBUG( "requesting art for %s", psz_name
);
277 playlist_AskForArtEnqueue( p_playlist
, p_input
);
282 /* FIXME: this is not safe !!*/
284 var_SetAddress( p_playlist
, "item-current", p_input
);
291 * Compute the next playlist item depending on
292 * the playlist course mode (forward, backward, random, view,...).
294 * \param p_playlist the playlist object
297 static playlist_item_t
*NextItem( playlist_t
*p_playlist
)
299 playlist_private_t
*p_sys
= pl_priv(p_playlist
);
300 playlist_item_t
*p_new
= NULL
;
302 /* Handle quickly a few special cases */
303 /* No items to play */
304 if( p_playlist
->items
.i_size
== 0 )
306 msg_Info( p_playlist
, "playlist is empty" );
310 /* Start the real work */
311 if( p_sys
->request
.b_request
)
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
);
319 if( p_sys
->request
.p_node
&&
320 p_sys
->request
.p_node
!= get_current_status_node( p_playlist
) )
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;
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 ) )
334 p_new
= playlist_GetNextLeaf( p_playlist
, p_new
, NULL
, true, false );
335 for( int i
= 0; i
< p_playlist
->current
.i_size
; i
++ )
337 if( p_new
== ARRAY_VAL( p_playlist
->current
, i
) )
339 p_playlist
->i_current_index
= i
;
346 if( p_sys
->b_reset_currently_playing
)
347 /* A bit too bad to reset twice ... */
348 ResetCurrentlyPlaying( p_playlist
, p_new
);
350 ResyncCurrentIndex( p_playlist
, p_new
);
352 p_playlist
->i_current_index
= -1;
354 if( p_playlist
->current
.i_size
&& (i_skip
> 0) )
356 if( p_playlist
->i_current_index
< -1 )
357 p_playlist
->i_current_index
= -1;
358 for( int i
= i_skip
; i
> 0 ; i
-- )
360 p_playlist
->i_current_index
++;
361 if( p_playlist
->i_current_index
>= p_playlist
->current
.i_size
)
363 PL_DEBUG( "looping - restarting at beginning of node" );
364 p_playlist
->i_current_index
= 0;
367 p_new
= ARRAY_VAL( p_playlist
->current
,
368 p_playlist
->i_current_index
);
370 else if( p_playlist
->current
.i_size
&& (i_skip
< 0) )
372 for( int i
= i_skip
; i
< 0 ; i
++ )
374 p_playlist
->i_current_index
--;
375 if( p_playlist
->i_current_index
<= -1 )
377 PL_DEBUG( "looping - restarting at end of node" );
378 p_playlist
->i_current_index
= p_playlist
->current
.i_size
-1;
381 p_new
= ARRAY_VAL( p_playlist
->current
,
382 p_playlist
->i_current_index
);
384 /* Clear the request */
385 p_sys
->request
.b_request
= false;
387 /* "Automatic" item change ( next ) */
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" );
394 /* Repeat and play/stop */
395 if( b_repeat
&& get_current_status_item( p_playlist
) )
397 msg_Dbg( p_playlist
,"repeating item" );
398 return get_current_status_item( p_playlist
);
402 msg_Dbg( p_playlist
,"stopping (play and stop)" );
407 if( get_current_status_item( p_playlist
) )
409 playlist_item_t
*p_parent
= get_current_status_item( p_playlist
);
412 if( p_parent
->i_flags
& PLAYLIST_SKIP_FLAG
)
414 msg_Dbg( p_playlist
, "blocking item, stopping") ;
417 p_parent
= p_parent
->p_parent
;
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
)
428 if( p_sys
->b_reset_currently_playing
)
429 ResetCurrentlyPlaying( p_playlist
,
430 get_current_status_item( p_playlist
) );
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
)
436 if( !b_loop
|| p_playlist
->current
.i_size
== 0 )
438 p_playlist
->i_current_index
= 0;
440 PL_DEBUG( "using item %i", p_playlist
->i_current_index
);
441 if ( p_playlist
->current
.i_size
== 0 )
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
)
452 static int LoopInput( playlist_t
*p_playlist
)
454 playlist_private_t
*p_sys
= pl_priv(p_playlist
);
455 input_thread_t
*p_input
= p_sys
->p_input
;
460 if( ( p_sys
->request
.b_request
|| !vlc_object_alive( p_playlist
) ) && !p_input
->b_die
)
462 PL_DEBUG( "incoming request - stopping current input" );
463 input_Stop( p_input
, true );
466 /* This input is dead. Remove it ! */
467 if( p_input
->b_dead
)
469 PL_DEBUG( "dead input" );
472 /* We can unlock as we return VLC_EGENERIC (no event will be lost) */
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
);
478 /* The DelCallback must be issued without playlist lock */
479 var_DelCallback( p_input
, "intf-event", InputEvent
, p_playlist
);
483 p_sys
->p_input
= NULL
;
484 input_Close( p_input
);
486 UpdateActivity( p_playlist
, -DEFAULT_INPUT_ACTIVITY
);
490 /* This input is dying, let it do */
491 else if( p_input
->b_die
)
493 PL_DEBUG( "dying input" );
495 /* This input has finished, ask it to die ! */
496 else if( p_input
->b_error
|| p_input
->b_eof
)
498 PL_DEBUG( "finished input" );
499 input_Stop( p_input
, false );
504 static void LoopRequest( playlist_t
*p_playlist
)
506 playlist_private_t
*p_sys
= pl_priv(p_playlist
);
507 assert( !p_sys
->p_input
);
509 /* No input. Several cases
510 * - No request, running status -> start new item
511 * - No request, stopped status -> collect garbage
512 * - Request, running requested -> start new item
513 * - Request, stopped requested -> collect garbage
515 const int i_status
= p_sys
->request
.b_request
?
516 p_sys
->request
.i_status
: p_sys
->status
.i_status
;
518 if( i_status
== PLAYLIST_STOPPED
|| !vlc_object_alive( p_playlist
) )
520 p_sys
->status
.i_status
= PLAYLIST_STOPPED
;
522 if( p_sys
->p_input_resource
&&
523 input_resource_HasVout( p_sys
->p_input_resource
) )
525 /* XXX We can unlock if we don't issue the wait as we will be
526 * call again without anything else done between the calls */
529 /* input_resource_t must be manipulated without playlist lock */
530 input_resource_TerminateVout( p_sys
->p_input_resource
);
536 if( vlc_object_alive( p_playlist
) )
537 vlc_cond_wait( &p_sys
->signal
, &p_sys
->lock
);
542 playlist_item_t
*p_item
= NextItem( p_playlist
);
545 msg_Dbg( p_playlist
, "starting playback of the new playlist item" );
546 PlayItem( p_playlist
, p_item
);
550 msg_Dbg( p_playlist
, "nothing to play" );
551 p_sys
->status
.i_status
= PLAYLIST_STOPPED
;
553 if( var_GetBool( p_playlist
, "play-and-exit" ) )
555 msg_Info( p_playlist
, "end of playlist, exiting" );
556 libvlc_Quit( p_playlist
->p_libvlc
);
561 * Run the main control thread itself
563 static void *Thread ( void *data
)
565 playlist_t
*p_playlist
= data
;
566 playlist_private_t
*p_sys
= pl_priv(p_playlist
);
568 playlist_Lock( p_playlist
);
569 while( vlc_object_alive( p_playlist
) || p_sys
->p_input
)
571 /* FIXME: what's that ! */
572 if( p_sys
->b_reset_currently_playing
&&
573 mdate() - p_sys
->last_rebuild_date
> 30000 ) // 30 ms
575 ResetCurrentlyPlaying( p_playlist
,
576 get_current_status_item( p_playlist
) );
577 p_sys
->last_rebuild_date
= mdate();
580 /* If there is an input, check that it doesn't need to die. */
581 while( !LoopInput( p_playlist
) )
582 vlc_cond_wait( &p_sys
->signal
, &p_sys
->lock
);
584 LoopRequest( p_playlist
);
586 playlist_Unlock( p_playlist
);