engine.c 14.7 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
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;
137
    VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED(p_data); VLC_UNUSED(newval);
138 139 140 141 142 143

    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 )
144 145
    {
        const double f_position = var_GetFloat( p_input, "position" );
146
        input_Control( p_input, INPUT_RESTART_ES, -VIDEO_ES );
147 148
        var_SetFloat( p_input, "position", f_position );
    }
149 150 151 152

    PL_UNLOCK;
    return VLC_SUCCESS;
}
153

154 155 156 157 158 159 160 161 162
/**
 * 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 )
{
163
    static const char playlist_name[] = "playlist";
164
    playlist_t *p_playlist;
165
    playlist_private_t *p;
166 167

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

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

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

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

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

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

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

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

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

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

204 205 206 207 208 209 210 211 212 213 214 215 216 217
    /* 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
218 219 220
    /* Create the root node */
    PL_LOCK;
    p_playlist->p_root = playlist_NodeCreate( p_playlist, NULL, NULL,
jpd's avatar
jpd committed
221
                                    PLAYLIST_END, 0, NULL );
222
    PL_UNLOCK;
jpd's avatar
jpd committed
223
    if( !p_playlist->p_root ) return NULL;
224

jpd's avatar
jpd committed
225 226 227 228
    /* 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
229
        PLAYLIST_END, PLAYLIST_RO_FLAG, NULL );
230

231
    PL_UNLOCK;
232

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

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

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

jpd's avatar
jpd committed
252 253 254 255 256 257 258
    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;;

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

265 266 267 268 269 270 271
    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;
    }
272

273 274 275
    return p_playlist;
}

276 277 278 279 280 281 282
/**
 * 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
 */
283 284 285 286 287 288 289 290 291
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 );
292

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

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

    /* 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 );

317
    vlc_object_release( p_playlist );
318 319
}

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

332 333 334 335
/**
 * @}
 */

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

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

playlist_item_t * get_current_status_node( playlist_t * p_playlist )
{
    PL_ASSERT_LOCKED;

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

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

357 358 359
    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 )
360
    {
361
        /* It's unsafe given current design to delete a playlist item :(
362
        playlist_ItemDelete( pl_priv(p_playlist)->status.p_item ); */
363
    }
364
    pl_priv(p_playlist)->status.p_item = p_item;
365 366 367 368 369 370 371
}

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

372 373 374
    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 )
375
    {
376
        /* It's unsafe given current design to delete a playlist item :(
377
        playlist_ItemDelete( pl_priv(p_playlist)->status.p_node ); */
378
    }
379
    pl_priv(p_playlist)->status.p_node = p_node;
380 381
}

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

388 389 390 391
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
392
    var_SetBool( p_playlist, "intf-change", true );
393

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

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

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

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

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

    /* Variables to control playback */
409 410 411 412 413
    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
414

415 416 417 418 419 420 421
    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 );

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

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

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

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

434 435 436 437
    /* 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 );
438 439 440
    /* 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 );
441
}
442 443 444

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

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

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

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