engine.c 13.8 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 133
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;
}


134 135 136 137 138 139 140 141 142
/**
 * 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 )
{
143
    static const char playlist_name[] = "playlist";
144
    playlist_t *p_playlist;
145
    playlist_private_t *p;
146 147

    /* Allocate structure */
148 149 150
    p = vlc_custom_create( p_parent, sizeof( *p ),
                           VLC_OBJECT_GENERIC, playlist_name );
    if( !p )
151
        return NULL;
152

153 154
    assert( offsetof( playlist_private_t, public_data ) == 0 );
    p_playlist = &p->public_data;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
155
    vlc_object_attach( p_playlist, p_parent );
156
    TAB_INIT( pl_priv(p_playlist)->i_sds, pl_priv(p_playlist)->pp_sds );
157

158
    libvlc_priv(p_parent->p_libvlc)->p_playlist = p_playlist;
159 160

    VariablesInit( p_playlist );
161
    vlc_mutex_init( &p->lock );
162
    vlc_cond_init( &p->signal );
163 164

    /* Initialise data structures */
165
    pl_priv(p_playlist)->i_last_playlist_id = 0;
166
    pl_priv(p_playlist)->p_input = NULL;
167

zorglub's avatar
zorglub committed
168 169
    ARRAY_INIT( p_playlist->items );
    ARRAY_INIT( p_playlist->all_items );
170
    ARRAY_INIT( pl_priv(p_playlist)->items_to_delete );
zorglub's avatar
zorglub committed
171
    ARRAY_INIT( p_playlist->current );
172

zorglub's avatar
zorglub committed
173
    p_playlist->i_current_index = 0;
174
    pl_priv(p_playlist)->b_reset_currently_playing = true;
175
    pl_priv(p_playlist)->last_rebuild_date = 0;
zorglub's avatar
zorglub committed
176

177
    pl_priv(p_playlist)->b_tree = var_InheritBool( p_parent, "playlist-tree" );
178

179
    pl_priv(p_playlist)->b_doing_ml = false;
180

181 182
    pl_priv(p_playlist)->b_auto_preparse =
        var_InheritBool( p_parent, "auto-preparse" );
183

184 185 186 187 188 189 190 191 192 193 194 195 196 197
    /* 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
198 199 200
    /* Create the root node */
    PL_LOCK;
    p_playlist->p_root = playlist_NodeCreate( p_playlist, NULL, NULL,
jpd's avatar
jpd committed
201
                                    PLAYLIST_END, 0, NULL );
202
    PL_UNLOCK;
jpd's avatar
jpd committed
203
    if( !p_playlist->p_root ) return NULL;
204

jpd's avatar
jpd committed
205 206 207 208
    /* 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
209
        PLAYLIST_END, PLAYLIST_RO_FLAG, NULL );
210

211
    PL_UNLOCK;
212

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

jpd's avatar
jpd committed
215
    /* Create media library node */
216 217
    const bool b_ml = var_InheritBool( p_parent, "media-library");
    if( b_ml )
218
    {
jpd's avatar
jpd committed
219 220 221
        PL_LOCK;
        p_playlist->p_media_library = playlist_NodeCreate(
            p_playlist, _( "Media Library" ), p_playlist->p_root,
jpd's avatar
jpd committed
222
            PLAYLIST_END, PLAYLIST_RO_FLAG, NULL );
223
        PL_UNLOCK;
224

jpd's avatar
jpd committed
225
        if(!p_playlist->p_media_library ) return NULL;
226 227 228
    }
    else
    {
jpd's avatar
jpd committed
229
        p_playlist->p_media_library = NULL;
230
    }
231

jpd's avatar
jpd committed
232 233 234 235 236 237 238
    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;;

239
    /* Initial status */
240
    pl_priv(p_playlist)->status.p_item = NULL;
jpd's avatar
jpd committed
241
    pl_priv(p_playlist)->status.p_node = p_playlist->p_playing;
242 243
    pl_priv(p_playlist)->request.b_request = false;
    pl_priv(p_playlist)->status.i_status = PLAYLIST_STOPPED;
244

245 246 247 248 249 250 251
    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;
    }
252

253 254 255
    return p_playlist;
}

256 257 258 259 260 261 262
/**
 * 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
 */
263 264 265 266 267 268 269 270 271
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 );
272

273
    /* Already cleared when deactivating (if activated anyway) */
Laurent Aimar's avatar
Laurent Aimar committed
274
    assert( !p_sys->p_input );
275
    assert( !p_sys->p_input_resource );
276

277
    vlc_cond_destroy( &p_sys->signal );
278
    vlc_mutex_destroy( &p_sys->lock );
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

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

297
    vlc_object_release( p_playlist );
298 299
}

300 301 302 303 304 305
/** Get current playing input.
 */
input_thread_t * playlist_CurrentInput( playlist_t * p_playlist )
{
    input_thread_t * p_input;
    PL_LOCK;
306
    p_input = pl_priv(p_playlist)->p_input;
307
    if( p_input ) vlc_object_hold( p_input );
308 309 310 311
    PL_UNLOCK;
    return p_input;
}

312 313 314 315
/**
 * @}
 */

316 317 318 319 320 321
/** Accessor for status item and status nodes.
 */
playlist_item_t * get_current_status_item( playlist_t * p_playlist )
{
    PL_ASSERT_LOCKED;

322
    return pl_priv(p_playlist)->status.p_item;
323 324 325 326 327 328
}

playlist_item_t * get_current_status_node( playlist_t * p_playlist )
{
    PL_ASSERT_LOCKED;

329
    return pl_priv(p_playlist)->status.p_node;
330 331 332 333 334 335 336
}

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

337 338 339
    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 )
340
    {
341
        /* It's unsafe given current design to delete a playlist item :(
342
        playlist_ItemDelete( pl_priv(p_playlist)->status.p_item ); */
343
    }
344
    pl_priv(p_playlist)->status.p_item = p_item;
345 346 347 348 349 350 351
}

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

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

362 363 364 365 366 367
static input_thread_t *playlist_FindInput( vlc_object_t *object )
{
    assert( object == VLC_OBJECT(pl_Get(object)) );
    return playlist_CurrentInput( (playlist_t *)object );
}

368 369 370 371
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
372
    var_SetBool( p_playlist, "intf-change", true );
373

374
    var_Create( p_playlist, "item-change", VLC_VAR_ADDRESS );
375
    var_Create( p_playlist, "leaf-to-parent", VLC_VAR_ADDRESS );
376

377 378
    var_Create( p_playlist, "playlist-item-deleted", VLC_VAR_INTEGER );
    var_SetInteger( p_playlist, "playlist-item-deleted", -1 );
379

380
    var_Create( p_playlist, "playlist-item-append", VLC_VAR_ADDRESS );
381

382
    var_Create( p_playlist, "item-current", VLC_VAR_ADDRESS );
383
    var_Create( p_playlist, "input-current", VLC_VAR_ADDRESS );
384 385 386 387 388

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

    /* Variables to control playback */
389 390 391 392 393
    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
394

395 396 397 398 399 400 401
    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 );

zorglub's avatar
zorglub committed
402
    var_AddCallback( p_playlist, "random", RandomCallback, NULL );
403 404 405

    /* */
    var_Create( p_playlist, "album-art", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
406 407 408

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

411 412 413 414
    /* 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 );
415 416 417
    /* 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 );
418
}
419 420 421

playlist_item_t * playlist_CurrentPlayingItem( playlist_t * p_playlist )
{
422 423
    PL_ASSERT_LOCKED;

424 425 426 427 428
    return pl_priv(p_playlist)->status.p_item;
}

int playlist_Status( playlist_t * p_playlist )
{
429 430
    PL_ASSERT_LOCKED;

431 432
    return pl_priv(p_playlist)->status.i_status;
}
433