1 /*****************************************************************************
2  * engine.c : Run the playlist and handle its control
3  *****************************************************************************
4  * Copyright (C) 1999-2008 VLC authors and VideoLAN
5  *
6  * Authors: Samuel Hocevar <sam@zoy.org>
7  *          Clément Stenac <zorglub@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 
28 #include <stddef.h>
29 #include <assert.h>
30 
31 #include <vlc_common.h>
32 #include <vlc_arrays.h>
33 #include <vlc_sout.h>
34 #include <vlc_playlist.h>
35 #include <vlc_interface.h>
36 #include <vlc_http.h>
37 #include <vlc_renderer_discovery.h>
38 #include "playlist_internal.h"
39 #include "input/resource.h"
40 
41 /*****************************************************************************
42  * Local prototypes
43  *****************************************************************************/
44 static void VariablesInit( playlist_t *p_playlist );
45 
RandomCallback(vlc_object_t * p_this,char const * psz_cmd,vlc_value_t oldval,vlc_value_t newval,void * a)46 static int RandomCallback( vlc_object_t *p_this, char const *psz_cmd,
47                            vlc_value_t oldval, vlc_value_t newval, void *a )
48 {
49     (void)psz_cmd; (void)oldval; (void)newval; (void)a;
50     playlist_t *p_playlist = (playlist_t*)p_this;
51     bool random = newval.b_bool;
52 
53     PL_LOCK;
54 
55     if( !random ) {
56         pl_priv(p_playlist)->b_reset_currently_playing = true;
57         vlc_cond_signal( &pl_priv(p_playlist)->signal );
58     } else {
59         /* Shuffle and sync the playlist on activation of random mode.
60          * This preserves the current playing item, so that the user
61          * can return to it if needed. (See #4472)
62          */
63         playlist_private_t *p_sys = pl_priv(p_playlist);
64         playlist_item_t *p_new = p_sys->status.p_item;
65         ResetCurrentlyPlaying( p_playlist, NULL );
66         if( p_new )
67             ResyncCurrentIndex( p_playlist, p_new );
68     }
69 
70     PL_UNLOCK;
71     return VLC_SUCCESS;
72 }
73 
74 /**
75  * When there are one or more pending corks, playback should be paused.
76  * This is used for audio policy.
77  * \warning Always add and remove a cork with var_IncInteger() and var_DecInteger().
78  * var_Get() and var_Set() are prone to race conditions.
79  */
CorksCallback(vlc_object_t * obj,char const * var,vlc_value_t old,vlc_value_t cur,void * dummy)80 static int CorksCallback( vlc_object_t *obj, char const *var,
81                           vlc_value_t old, vlc_value_t cur, void *dummy )
82 {
83     playlist_t *pl = (playlist_t *)obj;
84 
85     msg_Dbg( obj, "corks count: %"PRId64" -> %"PRId64, old.i_int, cur.i_int );
86     if( !old.i_int == !cur.i_int )
87         return VLC_SUCCESS; /* nothing to do */
88 
89     if( !var_InheritBool( obj, "playlist-cork" ) )
90         return VLC_SUCCESS;
91 
92     playlist_Lock(pl);
93 
94     if( cur.i_int )
95     {
96         bool effective = playlist_Status(pl) == PLAYLIST_RUNNING;
97 
98         msg_Dbg(obj, "corked (%seffective)", effective ? "" : "in");
99         pl_priv(pl)->cork_effective = effective;
100         playlist_Control(pl, PLAYLIST_PAUSE, pl_Locked);
101     }
102     else
103     {
104         bool effective = pl_priv(pl)->cork_effective;
105 
106         msg_Dbg(obj, "uncorked (%seffective)", effective ? "" : "in");
107 
108         if (effective)
109             playlist_Control(pl, PLAYLIST_RESUME, pl_Locked);
110     }
111 
112     playlist_Unlock(pl);
113     (void) var; (void) dummy;
114     return VLC_SUCCESS;
115 }
116 
RateCallback(vlc_object_t * p_this,char const * psz_cmd,vlc_value_t oldval,vlc_value_t newval,void * p)117 static int RateCallback( vlc_object_t *p_this, char const *psz_cmd,
118                          vlc_value_t oldval, vlc_value_t newval, void *p )
119 {
120     (void)psz_cmd; (void)oldval;(void)p;
121     playlist_t *p_playlist = (playlist_t*)p_this;
122 
123     PL_LOCK;
124 
125     if( pl_priv(p_playlist)->p_input )
126         var_SetFloat( pl_priv( p_playlist )->p_input, "rate", newval.f_float );
127 
128     PL_UNLOCK;
129     return VLC_SUCCESS;
130 }
131 
RateOffsetCallback(vlc_object_t * obj,char const * psz_cmd,vlc_value_t oldval,vlc_value_t newval,void * p_data)132 static int RateOffsetCallback( vlc_object_t *obj, char const *psz_cmd,
133                                vlc_value_t oldval, vlc_value_t newval, void *p_data )
134 {
135     playlist_t *p_playlist = (playlist_t *)obj;
136     VLC_UNUSED(oldval); VLC_UNUSED(p_data); VLC_UNUSED(newval);
137 
138     static const float rates[] = {
139         1.0/64, 1.0/32, 1.0/16, 1.0/8, 1.0/4, 1.0/3, 1.0/2, 2.0/3,
140         1.0/1,
141         3.0/2, 2.0/1, 3.0/1, 4.0/1, 8.0/1, 16.0/1, 32.0/1, 64.0/1,
142     };
143 
144     PL_LOCK;
145     input_thread_t *input = pl_priv( p_playlist )->p_input;
146     float current_rate = var_GetFloat( input ? VLC_OBJECT( input ) : obj, "rate" );
147     PL_UNLOCK;
148 
149     const bool faster = !strcmp( psz_cmd, "rate-faster" );
150     float rate = current_rate * ( faster ? 1.1f : 0.9f );
151 
152     /* find closest rate (if any) in the desired direction */
153     for( size_t i = 0; i < ARRAY_SIZE( rates ); ++i )
154     {
155         if( ( faster && rates[i] > rate ) ||
156             (!faster && rates[i] >= rate && i ) )
157         {
158             rate = faster ? rates[i] : rates[i-1];
159             break;
160         }
161     }
162 
163     msg_Dbg( p_playlist, "adjusting rate from %f to %f (%s)",
164         current_rate, rate, faster ? "faster" : "slower" );
165 
166     return var_SetFloat( p_playlist, "rate", rate );
167 }
168 
VideoSplitterCallback(vlc_object_t * p_this,char const * psz_cmd,vlc_value_t oldval,vlc_value_t newval,void * p_data)169 static int VideoSplitterCallback( vlc_object_t *p_this, char const *psz_cmd,
170                                   vlc_value_t oldval, vlc_value_t newval, void *p_data )
171 {
172     playlist_t *p_playlist = (playlist_t*)p_this;
173     VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED(p_data); VLC_UNUSED(newval);
174 
175     PL_LOCK;
176 
177     /* Force the input to restart the video ES to force a vout recreation */
178     input_thread_t *p_input = pl_priv( p_playlist )->p_input;
179     if( p_input )
180     {
181         const double f_position = var_GetFloat( p_input, "position" );
182         input_Control( p_input, INPUT_RESTART_ES, -VIDEO_ES );
183         var_SetFloat( p_input, "position", f_position );
184     }
185 
186     PL_UNLOCK;
187     return VLC_SUCCESS;
188 }
189 
190 /**
191  * Create playlist
192  *
193  * Create a playlist structure.
194  * \param p_parent the vlc object that is to be the parent of this playlist
195  * \return a pointer to the created playlist, or NULL on error
196  */
playlist_Create(vlc_object_t * p_parent)197 playlist_t *playlist_Create( vlc_object_t *p_parent )
198 {
199     playlist_t *p_playlist;
200     playlist_private_t *p;
201 
202     /* Allocate structure */
203     p = vlc_custom_create( p_parent, sizeof( *p ), "playlist" );
204     if( !p )
205         return NULL;
206 
207     p_playlist = &p->public_data;
208 
209     p->input_tree = NULL;
210     p->id_tree = NULL;
211 
212     TAB_INIT( pl_priv(p_playlist)->i_sds, pl_priv(p_playlist)->pp_sds );
213 
214     VariablesInit( p_playlist );
215     vlc_mutex_init( &p->lock );
216     vlc_cond_init( &p->signal );
217     p->killed = false;
218 
219     /* Initialise data structures */
220     pl_priv(p_playlist)->i_last_playlist_id = 0;
221     pl_priv(p_playlist)->p_input = NULL;
222 
223     ARRAY_INIT( p_playlist->items );
224     ARRAY_INIT( p_playlist->current );
225 
226     p_playlist->i_current_index = 0;
227     pl_priv(p_playlist)->b_reset_currently_playing = true;
228 
229     pl_priv(p_playlist)->b_tree = var_InheritBool( p_parent, "playlist-tree" );
230     pl_priv(p_playlist)->b_preparse = var_InheritBool( p_parent, "auto-preparse" );
231 
232     p_playlist->root.p_input = NULL;
233     p_playlist->root.pp_children = NULL;
234     p_playlist->root.i_children = 0;
235     p_playlist->root.i_nb_played = 0;
236     p_playlist->root.i_id = 0;
237     p_playlist->root.i_flags = 0;
238 
239     /* Create the root, playing items and meida library nodes */
240     playlist_item_t *playing, *ml;
241 
242     PL_LOCK;
243     playing = playlist_NodeCreate( p_playlist, _( "Playlist" ),
244                                    &p_playlist->root, PLAYLIST_END,
245                                    PLAYLIST_RO_FLAG|PLAYLIST_NO_INHERIT_FLAG );
246     if( var_InheritBool( p_parent, "media-library") )
247         ml = playlist_NodeCreate( p_playlist, _( "Media Library" ),
248                                   &p_playlist->root, PLAYLIST_END,
249                                   PLAYLIST_RO_FLAG|PLAYLIST_NO_INHERIT_FLAG );
250     else
251         ml = NULL;
252     PL_UNLOCK;
253 
254     if( unlikely(playing == NULL) )
255         abort();
256 
257     p_playlist->p_playing = playing;
258     p_playlist->p_media_library = ml;
259 
260     /* Initial status */
261     pl_priv(p_playlist)->status.p_item = NULL;
262     pl_priv(p_playlist)->status.p_node = p_playlist->p_playing;
263     pl_priv(p_playlist)->request.b_request = false;
264     pl_priv(p_playlist)->i_consecutive_errors = 0;
265     p->request.input_dead = false;
266 
267     if (ml != NULL)
268         playlist_MLLoad( p_playlist );
269 
270     /* Input resources */
271     p->p_input_resource = input_resource_New( VLC_OBJECT( p_playlist ) );
272     if( unlikely(p->p_input_resource == NULL) )
273         abort();
274 
275     /* Audio output (needed for volume and device controls). */
276     audio_output_t *aout = input_resource_GetAout( p->p_input_resource );
277     if( aout != NULL )
278         input_resource_PutAout( p->p_input_resource, aout );
279 
280     /* Initialize the shared HTTP cookie jar */
281     vlc_value_t cookies;
282     cookies.p_address = vlc_http_cookies_new();
283     if ( likely(cookies.p_address) )
284     {
285         var_Create( p_playlist, "http-cookies", VLC_VAR_ADDRESS );
286         var_SetChecked( p_playlist, "http-cookies", VLC_VAR_ADDRESS, cookies );
287     }
288 
289     /* Thread */
290     playlist_Activate (p_playlist);
291 
292     /* Add service discovery modules */
293     char *mods = var_InheritString( p_playlist, "services-discovery" );
294     if( mods != NULL )
295     {
296         char *s = mods, *m;
297         while( (m = strsep( &s, " :," )) != NULL )
298             playlist_ServicesDiscoveryAdd( p_playlist, m );
299         free( mods );
300     }
301 
302     return p_playlist;
303 }
304 
305 /**
306  * Destroy playlist.
307  * This is not thread-safe. Any reference to the playlist is assumed gone.
308  * (In particular, all interface and services threads must have been joined).
309  *
310  * \param p_playlist the playlist object
311  */
playlist_Destroy(playlist_t * p_playlist)312 void playlist_Destroy( playlist_t *p_playlist )
313 {
314     playlist_private_t *p_sys = pl_priv(p_playlist);
315 
316     /* Remove all services discovery */
317     playlist_ServicesDiscoveryKillAll( p_playlist );
318 
319     msg_Dbg( p_playlist, "destroying" );
320 
321     playlist_Deactivate( p_playlist );
322 
323     /* Release input resources */
324     assert( p_sys->p_input == NULL );
325     input_resource_Release( p_sys->p_input_resource );
326     if( p_sys->p_renderer )
327         vlc_renderer_item_release( p_sys->p_renderer );
328 
329     if( p_playlist->p_media_library != NULL )
330         playlist_MLDump( p_playlist );
331 
332     PL_LOCK;
333     /* Release the current node */
334     set_current_status_node( p_playlist, NULL );
335     /* Release the current item */
336     set_current_status_item( p_playlist, NULL );
337 
338     /* Destroy arrays completely - faster than one item at a time */
339     ARRAY_RESET( p_playlist->items );
340     ARRAY_RESET( p_playlist->current );
341 
342     /* Remove all remaining items */
343     if( p_playlist->p_media_library != NULL )
344     {
345         playlist_NodeDeleteExplicit( p_playlist, p_playlist->p_media_library,
346             PLAYLIST_DELETE_FORCE );
347     }
348 
349     playlist_NodeDeleteExplicit( p_playlist, p_playlist->p_playing,
350         PLAYLIST_DELETE_FORCE );
351 
352     assert( p_playlist->root.i_children <= 0 );
353     PL_UNLOCK;
354 
355     vlc_cond_destroy( &p_sys->signal );
356     vlc_mutex_destroy( &p_sys->lock );
357 
358     vlc_http_cookie_jar_t *cookies = var_GetAddress( p_playlist, "http-cookies" );
359     if ( cookies )
360     {
361         var_Destroy( p_playlist, "http-cookies" );
362         vlc_http_cookies_destroy( cookies );
363     }
364 
365     vlc_object_release( p_playlist );
366 }
367 
368 /** Get current playing input.
369  */
playlist_CurrentInputLocked(playlist_t * p_playlist)370 input_thread_t *playlist_CurrentInputLocked( playlist_t *p_playlist )
371 {
372     PL_ASSERT_LOCKED;
373 
374     input_thread_t *p_input = pl_priv(p_playlist)->p_input;
375     if( p_input != NULL )
376         vlc_object_hold( p_input );
377     return p_input;
378 }
379 
380 
381 /** Get current playing input.
382  */
playlist_CurrentInput(playlist_t * p_playlist)383 input_thread_t * playlist_CurrentInput( playlist_t * p_playlist )
384 {
385     input_thread_t * p_input;
386     PL_LOCK;
387     p_input = playlist_CurrentInputLocked( p_playlist );
388     PL_UNLOCK;
389     return p_input;
390 }
391 
392 /**
393  * @}
394  */
395 
396 /** Accessor for status item and status nodes.
397  */
get_current_status_item(playlist_t * p_playlist)398 playlist_item_t * get_current_status_item( playlist_t * p_playlist )
399 {
400     PL_ASSERT_LOCKED;
401 
402     return pl_priv(p_playlist)->status.p_item;
403 }
404 
get_current_status_node(playlist_t * p_playlist)405 playlist_item_t * get_current_status_node( playlist_t * p_playlist )
406 {
407     PL_ASSERT_LOCKED;
408 
409     return pl_priv(p_playlist)->status.p_node;
410 }
411 
set_current_status_item(playlist_t * p_playlist,playlist_item_t * p_item)412 void set_current_status_item( playlist_t * p_playlist,
413     playlist_item_t * p_item )
414 {
415     PL_ASSERT_LOCKED;
416 
417     pl_priv(p_playlist)->status.p_item = p_item;
418 }
419 
set_current_status_node(playlist_t * p_playlist,playlist_item_t * p_node)420 void set_current_status_node( playlist_t * p_playlist,
421     playlist_item_t * p_node )
422 {
423     PL_ASSERT_LOCKED;
424 
425     pl_priv(p_playlist)->status.p_node = p_node;
426 }
427 
VariablesInit(playlist_t * p_playlist)428 static void VariablesInit( playlist_t *p_playlist )
429 {
430     /* These variables control updates */
431     var_Create( p_playlist, "item-change", VLC_VAR_ADDRESS );
432     var_Create( p_playlist, "leaf-to-parent", VLC_VAR_INTEGER );
433 
434     var_Create( p_playlist, "playlist-item-append", VLC_VAR_ADDRESS );
435     var_Create( p_playlist, "playlist-item-deleted", VLC_VAR_ADDRESS );
436 
437     var_Create( p_playlist, "input-current", VLC_VAR_ADDRESS );
438 
439     /* Variables to control playback */
440     var_Create( p_playlist, "playlist-autostart", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
441     var_Create( p_playlist, "random", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
442     var_AddCallback( p_playlist, "random", RandomCallback, NULL );
443     var_Create( p_playlist, "repeat", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
444     var_Create( p_playlist, "loop", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
445     var_Create( p_playlist, "corks", VLC_VAR_INTEGER );
446     var_AddCallback( p_playlist, "corks", CorksCallback, NULL );
447 
448     var_Create( p_playlist, "rate", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
449     var_AddCallback( p_playlist, "rate", RateCallback, NULL );
450     var_Create( p_playlist, "rate-slower", VLC_VAR_VOID );
451     var_AddCallback( p_playlist, "rate-slower", RateOffsetCallback, NULL );
452     var_Create( p_playlist, "rate-faster", VLC_VAR_VOID );
453     var_AddCallback( p_playlist, "rate-faster", RateOffsetCallback, NULL );
454 
455     var_Create( p_playlist, "video-splitter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
456     var_AddCallback( p_playlist, "video-splitter", VideoSplitterCallback, NULL );
457 
458     var_Create( p_playlist, "video-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
459     var_Create( p_playlist, "sub-source", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
460     var_Create( p_playlist, "sub-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
461 
462     /* sout variables */
463     var_Create( p_playlist, "sout", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
464     var_Create( p_playlist, "demux-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
465 
466     /* */
467     var_Create( p_playlist, "metadata-network-access", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
468 
469     /* Variables to preserve video output parameters */
470     var_Create( p_playlist, "fullscreen", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
471     var_Create( p_playlist, "video-on-top", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
472     var_Create( p_playlist, "video-wallpaper", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
473 
474     /* Audio output parameters */
475     var_Create( p_playlist, "audio-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
476     var_Create( p_playlist, "audio-device", VLC_VAR_STRING );
477     var_Create( p_playlist, "mute", VLC_VAR_BOOL );
478     var_Create( p_playlist, "volume", VLC_VAR_FLOAT );
479     var_SetFloat( p_playlist, "volume", -1.f );
480 
481     var_Create( p_playlist, "sub-text-scale",
482                VLC_VAR_INTEGER | VLC_VAR_DOINHERIT | VLC_VAR_ISCOMMAND );
483 }
484 
playlist_CurrentPlayingItem(playlist_t * p_playlist)485 playlist_item_t * playlist_CurrentPlayingItem( playlist_t * p_playlist )
486 {
487     PL_ASSERT_LOCKED;
488 
489     return pl_priv(p_playlist)->status.p_item;
490 }
491 
playlist_Status(playlist_t * p_playlist)492 int playlist_Status( playlist_t * p_playlist )
493 {
494     input_thread_t *p_input = pl_priv(p_playlist)->p_input;
495 
496     PL_ASSERT_LOCKED;
497 
498     if( p_input == NULL )
499         return PLAYLIST_STOPPED;
500     if( var_GetInteger( p_input, "state" ) == PAUSE_S )
501         return PLAYLIST_PAUSED;
502     return PLAYLIST_RUNNING;
503 }
504 
505