control.c 17.2 KB
Newer Older
1
/*****************************************************************************
Clément Stenac's avatar
Clément Stenac committed
2
 * control.c : Handle control of the playlist & running through it
3 4
 *****************************************************************************
 * Copyright (C) 1999-2004 the VideoLAN team
Jean-Paul Saman's avatar
Jean-Paul Saman committed
5
 * $Id$
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 * 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.
 *****************************************************************************/
24 25 26 27
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

28
#include <vlc_common.h>
29
#include "vlc_playlist.h"
30
#include "playlist_internal.h"
31
#include <assert.h>
32 33 34 35

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
36
static int PlaylistVAControl( playlist_t * p_playlist, int i_query, va_list args );
37 38 39 40 41

/*****************************************************************************
 * Playlist control
 *****************************************************************************/

Antoine Cellerier's avatar
Antoine Cellerier committed
42
playlist_t *__pl_Hold( vlc_object_t *p_this )
43
{
44
    playlist_t *pl;
45

46
    barrier();
47
    pl = libvlc_priv (p_this->p_libvlc)->p_playlist;
48

49
    assert( VLC_OBJECT(pl) != p_this /* This does not make sense to hold the playlist
Antoine Cellerier's avatar
Antoine Cellerier committed
50
    using pl_Hold. use vlc_object_hold in this case */ );
51

52
    if (pl)
53
        vlc_object_hold (pl);
54 55 56 57 58
    return pl;
}

void __pl_Release( vlc_object_t *p_this )
{
59
    playlist_t *pl = libvlc_priv (p_this->p_libvlc)->p_playlist;
60
    assert( pl != NULL );
61

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
62 63 64
    /* The rule is that pl_Release() should act on
       the same object than pl_Hold() */
    assert( VLC_OBJECT(pl) != p_this);
65

66 67 68 69
    vlc_object_release( pl );
}

int playlist_Control( playlist_t * p_playlist, int i_query,
70
                      bool b_locked, ... )
71 72 73
{
    va_list args;
    int i_result;
74
    PL_LOCK_IF( !b_locked );
75
    va_start( args, b_locked );
76 77
    i_result = PlaylistVAControl( p_playlist, i_query, args );
    va_end( args );
78
    PL_UNLOCK_IF( !b_locked );
79 80 81 82

    return i_result;
}

83
static int PlaylistVAControl( playlist_t * p_playlist, int i_query, va_list args )
84 85 86 87
{
    playlist_item_t *p_item, *p_node;
    vlc_value_t val;

88 89
    PL_ASSERT_LOCKED;

90 91 92
    if( !vlc_object_alive( p_playlist ) )
        return VLC_EGENERIC;

93
    if( playlist_IsEmpty( p_playlist ) )
94 95 96 97 98
        return VLC_EGENERIC;

    switch( i_query )
    {
    case PLAYLIST_STOP:
99 100 101
        pl_priv(p_playlist)->request.i_status = PLAYLIST_STOPPED;
        pl_priv(p_playlist)->request.b_request = true;
        pl_priv(p_playlist)->request.p_item = NULL;
102 103
        break;

104 105
    // Node can be null, it will keep the same. Use with care ...
    // Item null = take the first child of node
106 107 108 109 110
    case PLAYLIST_VIEWPLAY:
        p_node = (playlist_item_t *)va_arg( args, playlist_item_t * );
        p_item = (playlist_item_t *)va_arg( args, playlist_item_t * );
        if ( p_node == NULL )
        {
111
            p_node = get_current_status_node( p_playlist );
112
            assert( p_node );
113
        }
114 115 116 117 118
        pl_priv(p_playlist)->request.i_status = PLAYLIST_RUNNING;
        pl_priv(p_playlist)->request.i_skip = 0;
        pl_priv(p_playlist)->request.b_request = true;
        pl_priv(p_playlist)->request.p_node = p_node;
        pl_priv(p_playlist)->request.p_item = p_item;
Clément Stenac's avatar
Clément Stenac committed
119
        if( p_item && var_GetBool( p_playlist, "random" ) )
120
            pl_priv(p_playlist)->b_reset_currently_playing = true;
121 122 123
        break;

    case PLAYLIST_PLAY:
124
        if( pl_priv(p_playlist)->p_input )
125 126
        {
            val.i_int = PLAYING_S;
127
            var_Set( pl_priv(p_playlist)->p_input, "state", val );
128 129
            break;
        }
130 131
        else
        {
132 133 134 135 136
            pl_priv(p_playlist)->request.i_status = PLAYLIST_RUNNING;
            pl_priv(p_playlist)->request.b_request = true;
            pl_priv(p_playlist)->request.p_node = get_current_status_node( p_playlist );
            pl_priv(p_playlist)->request.p_item = get_current_status_item( p_playlist );
            pl_priv(p_playlist)->request.i_skip = 0;
137
        }
138 139 140 141
        break;

    case PLAYLIST_PAUSE:
        val.i_int = 0;
142 143
        if( pl_priv(p_playlist)->p_input )
            var_Get( pl_priv(p_playlist)->p_input, "state", &val );
144 145 146

        if( val.i_int == PAUSE_S )
        {
147 148
            pl_priv(p_playlist)->status.i_status = PLAYLIST_RUNNING;
            if( pl_priv(p_playlist)->p_input )
149 150
            {
                val.i_int = PLAYING_S;
151
                var_Set( pl_priv(p_playlist)->p_input, "state", val );
152 153 154 155
            }
        }
        else
        {
156 157
            pl_priv(p_playlist)->status.i_status = PLAYLIST_PAUSED;
            if( pl_priv(p_playlist)->p_input )
158 159
            {
                val.i_int = PAUSE_S;
160
                var_Set( pl_priv(p_playlist)->p_input, "state", val );
161 162 163 164 165
            }
        }
        break;

    case PLAYLIST_SKIP:
166 167 168
        pl_priv(p_playlist)->request.p_node = get_current_status_node( p_playlist );
        pl_priv(p_playlist)->request.p_item = get_current_status_item( p_playlist );
        pl_priv(p_playlist)->request.i_skip = (int) va_arg( args, int );
169
        /* if already running, keep running */
170 171 172
        if( pl_priv(p_playlist)->status.i_status != PLAYLIST_STOPPED )
            pl_priv(p_playlist)->request.i_status = pl_priv(p_playlist)->status.i_status;
        pl_priv(p_playlist)->request.b_request = true;
173 174 175 176 177 178
        break;

    default:
        msg_Err( p_playlist, "unknown playlist query" );
        return VLC_EBADVAR;
    }
179
    vlc_object_signal_unlocked( p_playlist );
180 181 182 183 184 185 186 187 188 189 190

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Preparse control
 *****************************************************************************/
/** Enqueue an item for preparsing */
int playlist_PreparseEnqueue( playlist_t *p_playlist,
                              input_item_t *p_item )
{
191
    playlist_private_t *p_sys = pl_priv(p_playlist);
192

193
    playlist_preparser_Push( p_sys->p_preparser, p_item );
194

195 196 197
    return VLC_SUCCESS;
}

198
int playlist_AskForArtEnqueue( playlist_t *p_playlist,
199
                               input_item_t *p_item )
200
{
201
    playlist_private_t *p_sys = pl_priv(p_playlist);
202

203
    playlist_fetcher_Push( p_sys->p_fetcher, p_item );
204

205 206 207
    return VLC_SUCCESS;
}

208 209 210 211
/*****************************************************************************
 * Playback logic
 *****************************************************************************/

Rémi Duraffort's avatar
Rémi Duraffort committed
212 213 214 215 216 217 218 219 220
/**
 * Synchronise the current index of the playlist
 * to match the index of the current item.
 *
 * \param p_playlist the playlist structure
 * \param p_cur the current playlist item
 * \return nothing
 */
static void ResyncCurrentIndex( playlist_t *p_playlist, playlist_item_t *p_cur )
Clément Stenac's avatar
Clément Stenac committed
221
{
Rémi Duraffort's avatar
Rémi Duraffort committed
222
     PL_DEBUG( "resyncing on %s", PLI_NAME( p_cur ) );
Clément Stenac's avatar
Clément Stenac committed
223 224 225 226 227
     /* Simply resync index */
     int i;
     p_playlist->i_current_index = -1;
     for( i = 0 ; i< p_playlist->current.i_size; i++ )
     {
Rémi Duraffort's avatar
Rémi Duraffort committed
228
          if( ARRAY_VAL( p_playlist->current, i ) == p_cur )
Clément Stenac's avatar
Clément Stenac committed
229 230 231 232 233
          {
              p_playlist->i_current_index = i;
              break;
          }
     }
Rémi Duraffort's avatar
Rémi Duraffort committed
234
     PL_DEBUG( "%s is at %i", PLI_NAME( p_cur ), p_playlist->i_current_index );
Clément Stenac's avatar
Clément Stenac committed
235 236
}

237
void ResetCurrentlyPlaying( playlist_t *p_playlist, bool b_random,
238
                            playlist_item_t *p_cur )
Clément Stenac's avatar
Clément Stenac committed
239 240
{
    playlist_item_t *p_next = NULL;
241 242
    stats_TimerStart( p_playlist, "Items array build",
                      STATS_TIMER_PLAYLIST_BUILD );
Rémi Duraffort's avatar
Rémi Duraffort committed
243
    PL_DEBUG( "rebuilding array of current - root %s",
244
              PLI_NAME( pl_priv(p_playlist)->status.p_node ) );
Rémi Duraffort's avatar
Rémi Duraffort committed
245
    ARRAY_RESET( p_playlist->current );
Clément Stenac's avatar
Clément Stenac committed
246 247 248 249 250
    p_playlist->i_current_index = -1;
    while( 1 )
    {
        /** FIXME: this is *slow* */
        p_next = playlist_GetNextLeaf( p_playlist,
251
                                       pl_priv(p_playlist)->status.p_node,
252
                                       p_next, true, false );
Clément Stenac's avatar
Clément Stenac committed
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
        if( p_next )
        {
            if( p_next == p_cur )
                p_playlist->i_current_index = p_playlist->current.i_size;
            ARRAY_APPEND( p_playlist->current, p_next);
        }
        else break;
    }
    PL_DEBUG("rebuild done - %i items, index %i", p_playlist->current.i_size,
                                                  p_playlist->i_current_index);
    if( b_random )
    {
        /* Shuffle the array */
        srand( (unsigned int)mdate() );
        int j;
        for( j = p_playlist->current.i_size - 1; j > 0; j-- )
        {
            int i = rand() % (j+1); /* between 0 and j */
            playlist_item_t *p_tmp;
Rémi Duraffort's avatar
Rémi Duraffort committed
272
            /* swap the two items */
Clément Stenac's avatar
Clément Stenac committed
273 274 275 276 277
            p_tmp = ARRAY_VAL(p_playlist->current, i);
            ARRAY_VAL(p_playlist->current,i) = ARRAY_VAL(p_playlist->current,j);
            ARRAY_VAL(p_playlist->current,j) = p_tmp;
        }
    }
278
    pl_priv(p_playlist)->b_reset_currently_playing = false;
279
    stats_TimerStop( p_playlist, STATS_TIMER_PLAYLIST_BUILD );
Clément Stenac's avatar
Clément Stenac committed
280 281
}

Rémi Duraffort's avatar
Rémi Duraffort committed
282 283 284 285 286 287 288
/**
 * Compute the next playlist item depending on
 * the playlist course mode (forward, backward, random, view,...).
 *
 * \param p_playlist the playlist object
 * \return nothing
 */
289 290 291
playlist_item_t * playlist_NextItem( playlist_t *p_playlist )
{
    playlist_item_t *p_new = NULL;
292
    int i_skip = 0, i;
293

294 295 296 297
    bool b_loop = var_GetBool( p_playlist, "loop" );
    bool b_random = var_GetBool( p_playlist, "random" );
    bool b_repeat = var_GetBool( p_playlist, "repeat" );
    bool b_playstop = var_GetBool( p_playlist, "play-and-stop" );
298 299 300

    /* Handle quickly a few special cases */
    /* No items to play */
Clément Stenac's avatar
Clément Stenac committed
301
    if( p_playlist->items.i_size == 0 )
302 303 304 305 306 307
    {
        msg_Info( p_playlist, "playlist is empty" );
        return NULL;
    }

    /* Repeat and play/stop */
308
    if( !pl_priv(p_playlist)->request.b_request && b_repeat == true &&
309
         get_current_status_item( p_playlist ) )
310 311
    {
        msg_Dbg( p_playlist,"repeating item" );
312
        return get_current_status_item( p_playlist );
313
    }
314
    if( !pl_priv(p_playlist)->request.b_request && b_playstop == true )
315
    {
Rémi Duraffort's avatar
Rémi Duraffort committed
316
        msg_Dbg( p_playlist,"stopping (play and stop)" );
317 318 319
        return NULL;
    }

320
    if( !pl_priv(p_playlist)->request.b_request &&
321
        get_current_status_item( p_playlist ) )
322
    {
323
        playlist_item_t *p_parent = get_current_status_item( p_playlist );
324 325 326 327 328 329 330 331 332
        while( p_parent )
        {
            if( p_parent->i_flags & PLAYLIST_SKIP_FLAG )
            {
                msg_Dbg( p_playlist, "blocking item, stopping") ;
                return NULL;
            }
            p_parent = p_parent->p_parent;
        }
333 334 335
    }

    /* Start the real work */
336
    if( pl_priv(p_playlist)->request.b_request )
337
    {
338 339
        p_new = pl_priv(p_playlist)->request.p_item;
        i_skip = pl_priv(p_playlist)->request.i_skip;
Clément Stenac's avatar
Clément Stenac committed
340
        PL_DEBUG( "processing request item %s node %s skip %i",
341 342
                        PLI_NAME( pl_priv(p_playlist)->request.p_item ),
                        PLI_NAME( pl_priv(p_playlist)->request.p_node ), i_skip );
343

344 345
        if( pl_priv(p_playlist)->request.p_node &&
            pl_priv(p_playlist)->request.p_node != get_current_status_node( p_playlist ) )
Clément Stenac's avatar
Clément Stenac committed
346
        {
347

348 349
            set_current_status_node( p_playlist, pl_priv(p_playlist)->request.p_node );
            pl_priv(p_playlist)->request.p_node = NULL;
350
            pl_priv(p_playlist)->b_reset_currently_playing = true;
Clément Stenac's avatar
Clément Stenac committed
351
        }
352

353
        /* If we are asked for a node, go to it's first child */
354
        if( i_skip == 0 && ( p_new == NULL || p_new->i_children != -1 ) )
355
        {
356
            i_skip++;
357 358 359 360 361 362 363 364 365 366 367 368 369
            if( p_new != NULL )
            {
                p_new = playlist_GetNextLeaf( p_playlist, p_new, NULL, true, false );
                for( i = 0; i < p_playlist->current.i_size; i++ )
                {
                    if( p_new == ARRAY_VAL( p_playlist->current, i ) )
                    {
                        p_playlist->i_current_index = i;
                        i_skip = 0;
                    }
                }
            }
        }
370

371
        if( pl_priv(p_playlist)->b_reset_currently_playing )
372
            /* A bit too bad to reset twice ... */
Clément Stenac's avatar
Clément Stenac committed
373 374 375 376 377 378
            ResetCurrentlyPlaying( p_playlist, b_random, p_new );
        else if( p_new )
            ResyncCurrentIndex( p_playlist, p_new );
        else
            p_playlist->i_current_index = -1;

379
        if( p_playlist->current.i_size && (i_skip > 0) )
380
        {
381 382
            if( p_playlist->i_current_index < -1 )
                p_playlist->i_current_index = -1;
383 384
            for( i = i_skip; i > 0 ; i-- )
            {
Clément Stenac's avatar
Clément Stenac committed
385
                p_playlist->i_current_index++;
386
                if( p_playlist->i_current_index >= p_playlist->current.i_size )
387
                {
388
                    PL_DEBUG( "looping - restarting at beginning of node" );
Clément Stenac's avatar
Clément Stenac committed
389
                    p_playlist->i_current_index = 0;
390 391
                }
            }
Clément Stenac's avatar
Clément Stenac committed
392 393
            p_new = ARRAY_VAL( p_playlist->current,
                               p_playlist->i_current_index );
394
        }
395
        else if( p_playlist->current.i_size && (i_skip < 0) )
396 397 398
        {
            for( i = i_skip; i < 0 ; i++ )
            {
Clément Stenac's avatar
Clément Stenac committed
399
                p_playlist->i_current_index--;
400
                if( p_playlist->i_current_index <= -1 )
401
                {
402
                    PL_DEBUG( "looping - restarting at end of node" );
Clément Stenac's avatar
Clément Stenac committed
403
                    p_playlist->i_current_index = p_playlist->current.i_size-1;
404 405
                }
            }
Clément Stenac's avatar
Clément Stenac committed
406 407
            p_new = ARRAY_VAL( p_playlist->current,
                               p_playlist->i_current_index );
408 409
        }
        /* Clear the request */
410
        pl_priv(p_playlist)->request.b_request = false;
411 412 413 414
    }
    /* "Automatic" item change ( next ) */
    else
    {
Clément Stenac's avatar
Clément Stenac committed
415 416
        PL_DEBUG( "changing item without a request (current %i/%i)",
                  p_playlist->i_current_index, p_playlist->current.i_size );
417
        /* Cant go to next from current item */
418 419
        if( get_current_status_item( p_playlist ) &&
            get_current_status_item( p_playlist )->i_flags & PLAYLIST_SKIP_FLAG )
420 421
            return NULL;

422
        if( pl_priv(p_playlist)->b_reset_currently_playing )
Clément Stenac's avatar
Clément Stenac committed
423
            ResetCurrentlyPlaying( p_playlist, b_random,
424
                                   get_current_status_item( p_playlist ) );
Clément Stenac's avatar
Clément Stenac committed
425 426

        p_playlist->i_current_index++;
Rafaël Carré's avatar
Rafaël Carré committed
427
        assert( p_playlist->i_current_index <= p_playlist->current.i_size );
Clément Stenac's avatar
Clément Stenac committed
428
        if( p_playlist->i_current_index == p_playlist->current.i_size )
429
        {
Clément Stenac's avatar
Clément Stenac committed
430 431
            if( !b_loop || p_playlist->current.i_size == 0 ) return NULL;
            p_playlist->i_current_index = 0;
432
        }
Clément Stenac's avatar
Clément Stenac committed
433 434 435 436
        PL_DEBUG( "using item %i", p_playlist->i_current_index );
        if ( p_playlist->current.i_size == 0 ) return NULL;

        p_new = ARRAY_VAL( p_playlist->current, p_playlist->i_current_index );
437 438 439 440 441 442 443
        /* The new item can't be autoselected  */
        if( p_new != NULL && p_new->i_flags & PLAYLIST_SKIP_FLAG )
            return NULL;
    }
    return p_new;
}

Rémi Duraffort's avatar
Rémi Duraffort committed
444 445 446
/**
 * Start the input for an item
 *
Antoine Cellerier's avatar
Antoine Cellerier committed
447
 * \param p_playlist the playlist object
Rémi Duraffort's avatar
Rémi Duraffort committed
448 449 450
 * \param p_item the item to play
 * \return nothing
 */
451 452
int playlist_PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
{
453
    input_item_t *p_input = p_item->p_input;
454
    sout_instance_t **pp_sout = &pl_priv(p_playlist)->p_sout;
Rémi Duraffort's avatar
Rémi Duraffort committed
455
    int i_activity = var_GetInteger( p_playlist, "activity" ) ;
456 457 458

    msg_Dbg( p_playlist, "creating new input thread" );

459
    p_input->i_nb_played++;
460
    set_current_status_item( p_playlist, p_item );
461

462
    pl_priv(p_playlist)->status.i_status = PLAYLIST_RUNNING;
463 464 465

    var_SetInteger( p_playlist, "activity", i_activity +
                    DEFAULT_INPUT_ACTIVITY );
466 467

    input_thread_t * p_input_thread =
468
        input_CreateThreadExtended( p_playlist, p_input, NULL, *pp_sout );
469 470 471
    playlist_set_current_input( p_playlist, p_input_thread );
    vlc_object_release( p_input_thread );

472
    *pp_sout = NULL;
473

474 475 476 477 478 479 480 481 482
    char *psz_uri = input_item_GetURI( p_item->p_input );
    if( psz_uri && ( !strncmp( psz_uri, "directory:", 10 ) ||
                     !strncmp( psz_uri, "vlc:", 4 ) ) )
    {
        free( psz_uri );
        return VLC_SUCCESS;
    }
    free( psz_uri );

483 484
    /* FIXME remove access to fetcher private data */
    if( pl_priv(p_playlist)->p_fetcher->i_art_policy == ALBUM_ART_WHEN_PLAYED )
485
    {
486
        bool b_has_art;
487

488 489 490 491
        char *psz_arturl, *psz_name;
        psz_arturl = input_item_GetArtURL( p_input );
        psz_name = input_item_GetName( p_input );

Rémi Duraffort's avatar
Rémi Duraffort committed
492
        /* p_input->p_meta should not be null after a successfull CreateThread */
493
        b_has_art = !EMPTY_STR( psz_arturl );
494

495
        if( !b_has_art || strncmp( psz_arturl, "attachment://", 13 ) )
496
        {
497
            PL_DEBUG( "requesting art for %s", psz_name );
498 499
            playlist_AskForArtEnqueue( p_playlist, p_input );
        }
500 501
        free( psz_arturl );
        free( psz_name );
502
    }
503

504
    PL_UNLOCK;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
505
    var_SetInteger( p_playlist, "playlist-current", p_input->i_id );
506
    PL_LOCK;
507 508 509

    return VLC_SUCCESS;
}