engine.c 14.6 KB
Newer Older
1 2 3
/*****************************************************************************
 * engine.c : Run the playlist and handle its control
 *****************************************************************************
4
 * Copyright (C) 1999-2008 the VideoLAN team
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * Authors: Samuel Hocevar <sam@zoy.org>
 *          Clément Stenac <zorglub@videolan.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
23

24 25 26 27
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

28
#include <stddef.h>
29
#include <assert.h>
30
#include <vlc_common.h>
zorglub's avatar
zorglub committed
31 32 33
#include <vlc_sout.h>
#include <vlc_playlist.h>
#include <vlc_interface.h>
34
#include "playlist_internal.h"
zorglub's avatar
zorglub committed
35
#include "stream_output/stream_output.h" /* sout_DeleteInstance */
36
#include <math.h> /* for fabs() */
37 38 39 40 41 42

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static void VariablesInit( playlist_t *p_playlist );

zorglub's avatar
zorglub committed
43 44 45
static int RandomCallback( vlc_object_t *p_this, char const *psz_cmd,
                           vlc_value_t oldval, vlc_value_t newval, void *a )
{
46
    (void)psz_cmd; (void)oldval; (void)newval; (void)a;
47
    playlist_t *p_playlist = (playlist_t*)p_this;
48

49 50 51
    PL_LOCK;

    pl_priv(p_playlist)->b_reset_currently_playing = true;
52
    vlc_cond_signal( &pl_priv(p_playlist)->signal );
53 54

    PL_UNLOCK;
zorglub's avatar
zorglub committed
55 56 57
    return VLC_SUCCESS;
}

58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
static int RateCallback( vlc_object_t *p_this, char const *psz_cmd,
                         vlc_value_t oldval, vlc_value_t newval, void *p )
{
    (void)psz_cmd; (void)oldval;(void)p;
    playlist_t *p_playlist = (playlist_t*)p_this;

    PL_LOCK;

    if( pl_priv(p_playlist)->p_input == NULL )
    {
        PL_UNLOCK;
        return VLC_SUCCESS;
    }

    var_SetFloat( pl_priv( p_playlist )->p_input, "rate", newval.f_float );
    PL_UNLOCK;
    return VLC_SUCCESS;
}

static int RateOffsetCallback( vlc_object_t *p_this, char const *psz_cmd,
                               vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
    playlist_t *p_playlist = (playlist_t*)p_this;
    VLC_UNUSED(oldval); VLC_UNUSED(p_data); VLC_UNUSED(newval);

    static const float pf_rate[] = {
        1.0/64, 1.0/32, 1.0/16, 1.0/8, 1.0/4, 1.0/3, 1.0/2, 2.0/3,
        1.0/1,
        3.0/2, 2.0/1, 3.0/1, 4.0/1, 8.0/1, 16.0/1, 32.0/1, 64.0/1,
    };
    const unsigned i_rate_count = sizeof(pf_rate)/sizeof(*pf_rate);

    PL_LOCK;
    float f_rate = 1.;
    if( pl_priv( p_playlist )->p_input )
    {
       f_rate = var_GetFloat( pl_priv(p_playlist)->p_input, "rate" );
    }
    else
    {
       f_rate = var_GetFloat( p_playlist, "rate" );
    }
    PL_UNLOCK;

    /* Determine the factor closest to the current rate */
    float f_error;
    int i_idx;
    for( unsigned i = 0; i < i_rate_count; i++ )
    {
        const float f_test_e = fabs( fabs( f_rate ) - pf_rate[i] );
        if( i == 0 || f_test_e < f_error )
        {
            i_idx = i;
            f_error = f_test_e;
        }
    }
    assert( i_idx < (int)i_rate_count );

    /* */
    i_idx += strcmp( psz_cmd, "rate-faster" ) == 0 ? 1 : -1;
    if( i_idx >= 0 && i_idx < (int)i_rate_count )
    {
        const float f_rate_min = (float)INPUT_RATE_DEFAULT / INPUT_RATE_MAX;
        const float f_rate_max = (float)INPUT_RATE_DEFAULT / INPUT_RATE_MIN;
        const float f_sign = f_rate >= 0 ? +1. : -1.;

        var_SetFloat( p_playlist, "rate",
                      f_sign * __MAX( __MIN( pf_rate[i_idx],
                                             f_rate_max ),
                                      f_rate_min ) );

    }
    return VLC_SUCCESS;
}

133 134 135 136 137 138 139 140 141 142
static int VideoSplitterCallback( vlc_object_t *p_this, char const *psz_cmd,
                                  vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
    playlist_t *p_playlist = (playlist_t*)p_this;

    PL_LOCK;

    /* Force the input to restart the video ES to force a vout recreation */
    input_thread_t *p_input = pl_priv( p_playlist )->p_input;
    if( p_input )
143 144
    {
        const double f_position = var_GetFloat( p_input, "position" );
145
        input_Control( p_input, INPUT_RESTART_ES, -VIDEO_ES );
146 147
        var_SetFloat( p_input, "position", f_position );
    }
148 149 150 151

    PL_UNLOCK;
    return VLC_SUCCESS;
}
152

153 154 155 156 157 158 159 160 161
/**
 * Create playlist
 *
 * Create a playlist structure.
 * \param p_parent the vlc object that is to be the parent of this playlist
 * \return a pointer to the created playlist, or NULL on error
 */
playlist_t * playlist_Create( vlc_object_t *p_parent )
{
162
    static const char playlist_name[] = "playlist";
163
    playlist_t *p_playlist;
164
    playlist_private_t *p;
165 166

    /* Allocate structure */
167 168 169
    p = vlc_custom_create( p_parent, sizeof( *p ),
                           VLC_OBJECT_GENERIC, playlist_name );
    if( !p )
170
        return NULL;
171

172 173
    assert( offsetof( playlist_private_t, public_data ) == 0 );
    p_playlist = &p->public_data;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
174
    vlc_object_attach( p_playlist, p_parent );
175
    TAB_INIT( pl_priv(p_playlist)->i_sds, pl_priv(p_playlist)->pp_sds );
176

177
    libvlc_priv(p_parent->p_libvlc)->p_playlist = p_playlist;
178 179

    VariablesInit( p_playlist );
180
    vlc_mutex_init( &p->lock );
181
    vlc_cond_init( &p->signal );
182 183

    /* Initialise data structures */
184
    pl_priv(p_playlist)->i_last_playlist_id = 0;
185
    pl_priv(p_playlist)->p_input = NULL;
186

zorglub's avatar
zorglub committed
187 188
    ARRAY_INIT( p_playlist->items );
    ARRAY_INIT( p_playlist->all_items );
189
    ARRAY_INIT( pl_priv(p_playlist)->items_to_delete );
zorglub's avatar
zorglub committed
190
    ARRAY_INIT( p_playlist->current );
191

zorglub's avatar
zorglub committed
192
    p_playlist->i_current_index = 0;
193
    pl_priv(p_playlist)->b_reset_currently_playing = true;
194
    pl_priv(p_playlist)->last_rebuild_date = 0;
zorglub's avatar
zorglub committed
195

196
    pl_priv(p_playlist)->b_tree = var_InheritBool( p_parent, "playlist-tree" );
197

198
    pl_priv(p_playlist)->b_doing_ml = false;
199

200 201
    pl_priv(p_playlist)->b_auto_preparse =
        var_InheritBool( p_parent, "auto-preparse" );
202

203 204 205 206 207 208 209 210 211 212 213 214 215 216
    /* Fetcher */
    p->p_fetcher = playlist_fetcher_New( p_playlist );
    if( unlikely(p->p_fetcher == NULL) )
    {
        msg_Err( p_playlist, "cannot create fetcher" );
        p->p_preparser = NULL;
    }
    else
    {   /* Preparse */
        p->p_preparser = playlist_preparser_New( p_playlist, p->p_fetcher );
        if( unlikely(p->p_preparser == NULL) )
            msg_Err( p_playlist, "cannot create preparser" );
    }

jpd's avatar
jpd committed
217 218 219
    /* Create the root node */
    PL_LOCK;
    p_playlist->p_root = playlist_NodeCreate( p_playlist, NULL, NULL,
jpd's avatar
jpd committed
220
                                    PLAYLIST_END, 0, NULL );
221
    PL_UNLOCK;
jpd's avatar
jpd committed
222
    if( !p_playlist->p_root ) return NULL;
223

jpd's avatar
jpd committed
224 225 226 227
    /* Create currently playing items node */
    PL_LOCK;
    p_playlist->p_playing = playlist_NodeCreate(
        p_playlist, _( "Playlist" ), p_playlist->p_root,
jpd's avatar
jpd committed
228
        PLAYLIST_END, PLAYLIST_RO_FLAG, NULL );
229

230
    PL_UNLOCK;
231

jpd's avatar
jpd committed
232
    if( !p_playlist->p_playing ) return NULL;
233

jpd's avatar
jpd committed
234
    /* Create media library node */
235 236
    const bool b_ml = var_InheritBool( p_parent, "media-library");
    if( b_ml )
237
    {
jpd's avatar
jpd committed
238 239 240
        PL_LOCK;
        p_playlist->p_media_library = playlist_NodeCreate(
            p_playlist, _( "Media Library" ), p_playlist->p_root,
jpd's avatar
jpd committed
241
            PLAYLIST_END, PLAYLIST_RO_FLAG, NULL );
242
        PL_UNLOCK;
243

jpd's avatar
jpd committed
244
        if(!p_playlist->p_media_library ) return NULL;
245 246 247
    }
    else
    {
jpd's avatar
jpd committed
248
        p_playlist->p_media_library = NULL;
249
    }
250

jpd's avatar
jpd committed
251 252 253 254 255 256 257
    p_playlist->p_root_category = p_playlist->p_root;
    p_playlist->p_root_onelevel = p_playlist->p_root;
    p_playlist->p_local_category = p_playlist->p_playing;
    p_playlist->p_local_onelevel = p_playlist->p_playing;
    p_playlist->p_ml_category = p_playlist->p_media_library;
    p_playlist->p_ml_onelevel = p_playlist->p_media_library;;

258
    /* Initial status */
259
    pl_priv(p_playlist)->status.p_item = NULL;
jpd's avatar
jpd committed
260
    pl_priv(p_playlist)->status.p_node = p_playlist->p_playing;
261 262
    pl_priv(p_playlist)->request.b_request = false;
    pl_priv(p_playlist)->status.i_status = PLAYLIST_STOPPED;
263

264 265 266 267 268 269 270
    if(b_ml)
    {
        const bool b_auto_preparse = pl_priv(p_playlist)->b_auto_preparse;
        pl_priv(p_playlist)->b_auto_preparse = false;
        playlist_MLLoad( p_playlist );
        pl_priv(p_playlist)->b_auto_preparse = b_auto_preparse;
    }
271

272 273 274
    return p_playlist;
}

275 276 277 278 279 280 281
/**
 * Destroy playlist.
 * This is not thread-safe. Any reference to the playlist is assumed gone.
 * (In particular, all interface and services threads must have been joined).
 *
 * \param p_playlist the playlist object
 */
282 283 284 285 286 287 288 289 290
void playlist_Destroy( playlist_t *p_playlist )
{
    playlist_private_t *p_sys = pl_priv(p_playlist);

    msg_Dbg( p_playlist, "destroying" );
    if( p_sys->p_preparser )
        playlist_preparser_Delete( p_sys->p_preparser );
    if( p_sys->p_fetcher )
        playlist_fetcher_Delete( p_sys->p_fetcher );
291

292
    /* Already cleared when deactivating (if activated anyway) */
Laurent Aimar's avatar
Laurent Aimar committed
293
    assert( !p_sys->p_input );
294
    assert( !p_sys->p_input_resource );
295

296
    vlc_cond_destroy( &p_sys->signal );
297
    vlc_mutex_destroy( &p_sys->lock );
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315

    /* Remove all remaining items */
    FOREACH_ARRAY( playlist_item_t *p_del, p_playlist->all_items )
        free( p_del->pp_children );
        vlc_gc_decref( p_del->p_input );
        free( p_del );
    FOREACH_END();
    ARRAY_RESET( p_playlist->all_items );
    FOREACH_ARRAY( playlist_item_t *p_del, p_sys->items_to_delete )
        free( p_del->pp_children );
        vlc_gc_decref( p_del->p_input );
        free( p_del );
    FOREACH_END();
    ARRAY_RESET( p_sys->items_to_delete );

    ARRAY_RESET( p_playlist->items );
    ARRAY_RESET( p_playlist->current );

316
    vlc_object_release( p_playlist );
317 318
}

319 320 321 322 323 324
/** Get current playing input.
 */
input_thread_t * playlist_CurrentInput( playlist_t * p_playlist )
{
    input_thread_t * p_input;
    PL_LOCK;
325
    p_input = pl_priv(p_playlist)->p_input;
326
    if( p_input ) vlc_object_hold( p_input );
327 328 329 330
    PL_UNLOCK;
    return p_input;
}

331 332 333 334
/**
 * @}
 */

335 336 337 338 339 340
/** Accessor for status item and status nodes.
 */
playlist_item_t * get_current_status_item( playlist_t * p_playlist )
{
    PL_ASSERT_LOCKED;

341
    return pl_priv(p_playlist)->status.p_item;
342 343 344 345 346 347
}

playlist_item_t * get_current_status_node( playlist_t * p_playlist )
{
    PL_ASSERT_LOCKED;

348
    return pl_priv(p_playlist)->status.p_node;
349 350 351 352 353 354 355
}

void set_current_status_item( playlist_t * p_playlist,
    playlist_item_t * p_item )
{
    PL_ASSERT_LOCKED;

356 357 358
    if( pl_priv(p_playlist)->status.p_item &&
        pl_priv(p_playlist)->status.p_item->i_flags & PLAYLIST_REMOVE_FLAG &&
        pl_priv(p_playlist)->status.p_item != p_item )
359
    {
360
        /* It's unsafe given current design to delete a playlist item :(
361
        playlist_ItemDelete( pl_priv(p_playlist)->status.p_item ); */
362
    }
363
    pl_priv(p_playlist)->status.p_item = p_item;
364 365 366 367 368 369 370
}

void set_current_status_node( playlist_t * p_playlist,
    playlist_item_t * p_node )
{
    PL_ASSERT_LOCKED;

371 372 373
    if( pl_priv(p_playlist)->status.p_node &&
        pl_priv(p_playlist)->status.p_node->i_flags & PLAYLIST_REMOVE_FLAG &&
        pl_priv(p_playlist)->status.p_node != p_node )
374
    {
375
        /* It's unsafe given current design to delete a playlist item :(
376
        playlist_ItemDelete( pl_priv(p_playlist)->status.p_node ); */
377
    }
378
    pl_priv(p_playlist)->status.p_node = p_node;
379 380
}

381 382 383 384 385 386
static input_thread_t *playlist_FindInput( vlc_object_t *object )
{
    assert( object == VLC_OBJECT(pl_Get(object)) );
    return playlist_CurrentInput( (playlist_t *)object );
}

387 388 389 390
static void VariablesInit( playlist_t *p_playlist )
{
    /* These variables control updates */
    var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
Laurent Aimar's avatar
Laurent Aimar committed
391
    var_SetBool( p_playlist, "intf-change", true );
392

393
    var_Create( p_playlist, "item-change", VLC_VAR_ADDRESS );
394
    var_Create( p_playlist, "leaf-to-parent", VLC_VAR_ADDRESS );
395

396 397
    var_Create( p_playlist, "playlist-item-deleted", VLC_VAR_INTEGER );
    var_SetInteger( p_playlist, "playlist-item-deleted", -1 );
398

399
    var_Create( p_playlist, "playlist-item-append", VLC_VAR_ADDRESS );
400

401
    var_Create( p_playlist, "item-current", VLC_VAR_ADDRESS );
402
    var_Create( p_playlist, "input-current", VLC_VAR_ADDRESS );
403 404 405 406 407

    var_Create( p_playlist, "activity", VLC_VAR_INTEGER );
    var_SetInteger( p_playlist, "activity", 0 );

    /* Variables to control playback */
408 409 410 411 412
    var_Create( p_playlist, "play-and-stop", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
    var_Create( p_playlist, "play-and-exit", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
    var_Create( p_playlist, "random", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
    var_Create( p_playlist, "repeat", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
    var_Create( p_playlist, "loop", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
zorglub's avatar
zorglub committed
413

414 415 416 417 418 419 420
    var_Create( p_playlist, "rate", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
    var_Create( p_playlist, "rate-slower", VLC_VAR_VOID );
    var_Create( p_playlist, "rate-faster", VLC_VAR_VOID );
    var_AddCallback( p_playlist, "rate", RateCallback, NULL );
    var_AddCallback( p_playlist, "rate-slower", RateOffsetCallback, NULL );
    var_AddCallback( p_playlist, "rate-faster", RateOffsetCallback, NULL );

421 422
    var_Create( p_playlist, "video-splitter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
    var_AddCallback( p_playlist, "video-splitter", VideoSplitterCallback, NULL );
423

zorglub's avatar
zorglub committed
424
    var_AddCallback( p_playlist, "random", RandomCallback, NULL );
425 426 427

    /* */
    var_Create( p_playlist, "album-art", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
428 429 430

    /* Variables to preserve video output parameters */
    var_Create( p_playlist, "fullscreen", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
431
    var_Create( p_playlist, "video-on-top", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
432

433 434 435 436
    /* Audio output parameters */
    var_Create( p_playlist, "volume-muted", VLC_VAR_BOOL );
    var_Create( p_playlist, "saved-volume", VLC_VAR_INTEGER );
    var_Create( p_playlist, "volume-change", VLC_VAR_VOID );
437 438 439
    /* FIXME: horrible hack for audio output interface code */
    var_Create( p_playlist, "find-input-callback", VLC_VAR_ADDRESS );
    var_SetAddress( p_playlist, "find-input-callback", playlist_FindInput );
440
}
441 442 443

playlist_item_t * playlist_CurrentPlayingItem( playlist_t * p_playlist )
{
444 445
    PL_ASSERT_LOCKED;

446 447 448 449 450
    return pl_priv(p_playlist)->status.p_item;
}

int playlist_Status( playlist_t * p_playlist )
{
451 452
    PL_ASSERT_LOCKED;

453 454
    return pl_priv(p_playlist)->status.i_status;
}
455