engine.c 14.4 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 143 144 145 146 147
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 )
        input_Control( p_input, INPUT_RESTART_ES, -VIDEO_ES );

    PL_UNLOCK;
    return VLC_SUCCESS;
}
148

149 150 151 152 153 154 155 156 157
/**
 * 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 )
{
158
    static const char playlist_name[] = "playlist";
159
    playlist_t *p_playlist;
160
    playlist_private_t *p;
161 162

    /* Allocate structure */
163 164 165
    p = vlc_custom_create( p_parent, sizeof( *p ),
                           VLC_OBJECT_GENERIC, playlist_name );
    if( !p )
166
        return NULL;
167

168 169
    assert( offsetof( playlist_private_t, public_data ) == 0 );
    p_playlist = &p->public_data;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
170
    vlc_object_attach( p_playlist, p_parent );
171
    TAB_INIT( pl_priv(p_playlist)->i_sds, pl_priv(p_playlist)->pp_sds );
172

173
    libvlc_priv(p_parent->p_libvlc)->p_playlist = p_playlist;
174 175

    VariablesInit( p_playlist );
176
    vlc_mutex_init( &p->lock );
177
    vlc_cond_init( &p->signal );
178 179

    /* Initialise data structures */
180
    pl_priv(p_playlist)->i_last_playlist_id = 0;
181
    pl_priv(p_playlist)->p_input = NULL;
182

zorglub's avatar
zorglub committed
183 184
    ARRAY_INIT( p_playlist->items );
    ARRAY_INIT( p_playlist->all_items );
185
    ARRAY_INIT( pl_priv(p_playlist)->items_to_delete );
zorglub's avatar
zorglub committed
186
    ARRAY_INIT( p_playlist->current );
187

zorglub's avatar
zorglub committed
188
    p_playlist->i_current_index = 0;
189
    pl_priv(p_playlist)->b_reset_currently_playing = true;
190
    pl_priv(p_playlist)->last_rebuild_date = 0;
zorglub's avatar
zorglub committed
191

192
    pl_priv(p_playlist)->b_tree = var_InheritBool( p_parent, "playlist-tree" );
193

194
    pl_priv(p_playlist)->b_doing_ml = false;
195

196 197
    pl_priv(p_playlist)->b_auto_preparse =
        var_InheritBool( p_parent, "auto-preparse" );
198

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

jpd's avatar
jpd committed
220 221 222 223
    /* 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
224
        PLAYLIST_END, PLAYLIST_RO_FLAG, NULL );
225

226
    PL_UNLOCK;
227

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

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

jpd's avatar
jpd committed
240
        if(!p_playlist->p_media_library ) return NULL;
241 242 243
    }
    else
    {
jpd's avatar
jpd committed
244
        p_playlist->p_media_library = NULL;
245
    }
246

jpd's avatar
jpd committed
247 248 249 250 251 252 253
    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;;

254
    /* Initial status */
255
    pl_priv(p_playlist)->status.p_item = NULL;
jpd's avatar
jpd committed
256
    pl_priv(p_playlist)->status.p_node = p_playlist->p_playing;
257 258
    pl_priv(p_playlist)->request.b_request = false;
    pl_priv(p_playlist)->status.i_status = PLAYLIST_STOPPED;
259

260 261 262 263 264 265 266
    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;
    }
267

268 269 270
    return p_playlist;
}

271 272 273 274 275 276 277
/**
 * 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
 */
278 279 280 281 282 283 284 285 286
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 );
287

288
    /* Already cleared when deactivating (if activated anyway) */
Laurent Aimar's avatar
Laurent Aimar committed
289
    assert( !p_sys->p_input );
290
    assert( !p_sys->p_input_resource );
291

292
    vlc_cond_destroy( &p_sys->signal );
293
    vlc_mutex_destroy( &p_sys->lock );
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311

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

312
    vlc_object_release( p_playlist );
313 314
}

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

327 328 329 330
/**
 * @}
 */

331 332 333 334 335 336
/** Accessor for status item and status nodes.
 */
playlist_item_t * get_current_status_item( playlist_t * p_playlist )
{
    PL_ASSERT_LOCKED;

337
    return pl_priv(p_playlist)->status.p_item;
338 339 340 341 342 343
}

playlist_item_t * get_current_status_node( playlist_t * p_playlist )
{
    PL_ASSERT_LOCKED;

344
    return pl_priv(p_playlist)->status.p_node;
345 346 347 348 349 350 351
}

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

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

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

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

377 378 379 380 381 382
static input_thread_t *playlist_FindInput( vlc_object_t *object )
{
    assert( object == VLC_OBJECT(pl_Get(object)) );
    return playlist_CurrentInput( (playlist_t *)object );
}

383 384 385 386
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
387
    var_SetBool( p_playlist, "intf-change", true );
388

389
    var_Create( p_playlist, "item-change", VLC_VAR_ADDRESS );
390
    var_Create( p_playlist, "leaf-to-parent", VLC_VAR_ADDRESS );
391

392 393
    var_Create( p_playlist, "playlist-item-deleted", VLC_VAR_INTEGER );
    var_SetInteger( p_playlist, "playlist-item-deleted", -1 );
394

395
    var_Create( p_playlist, "playlist-item-append", VLC_VAR_ADDRESS );
396

397
    var_Create( p_playlist, "item-current", VLC_VAR_ADDRESS );
398
    var_Create( p_playlist, "input-current", VLC_VAR_ADDRESS );
399 400 401 402 403

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

    /* Variables to control playback */
404 405 406 407 408
    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
409

410 411 412 413 414 415 416
    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 );

417 418 419
    var_Create( p_playlist, "vout-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
    var_AddCallback( p_playlist, "vout-filter", VideoSplitterCallback, NULL );

zorglub's avatar
zorglub committed
420
    var_AddCallback( p_playlist, "random", RandomCallback, NULL );
421 422 423

    /* */
    var_Create( p_playlist, "album-art", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
424 425 426

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

429 430 431 432
    /* 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 );
433 434 435
    /* 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 );
436
}
437 438 439

playlist_item_t * playlist_CurrentPlayingItem( playlist_t * p_playlist )
{
440 441
    PL_ASSERT_LOCKED;

442 443 444 445 446
    return pl_priv(p_playlist)->status.p_item;
}

int playlist_Status( playlist_t * p_playlist )
{
447 448
    PL_ASSERT_LOCKED;

449 450
    return pl_priv(p_playlist)->status.i_status;
}
451