control.c 18 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
static void PreparseEnqueueItemSub( playlist_t *, playlist_item_t * );
39 40 41 42 43

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

44 45
playlist_t *__pl_Yield( vlc_object_t *p_this )
{
46
    playlist_t *pl;
47

48 49 50 51
    barrier ();
    pl = libvlc_priv (p_this->p_libvlc)->p_playlist;
    if (pl)
        vlc_object_yield (pl);
52 53 54 55 56
    return pl;
}

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

int playlist_Control( playlist_t * p_playlist, int i_query,
63
                      bool b_locked, ... )
64 65 66
{
    va_list args;
    int i_result;
67 68
    va_start( args, b_locked );
    if( !b_locked ) PL_LOCK;
69 70
    i_result = PlaylistVAControl( p_playlist, i_query, args );
    va_end( args );
71
    if( !b_locked ) PL_UNLOCK;
72 73 74 75

    return i_result;
}

76
static int PlaylistVAControl( playlist_t * p_playlist, int i_query, va_list args )
77 78 79 80
{
    playlist_item_t *p_item, *p_node;
    vlc_value_t val;

81
    if( playlist_IsEmpty( p_playlist ) )
82 83 84 85 86 87
        return VLC_EGENERIC;

    switch( i_query )
    {
    case PLAYLIST_STOP:
        p_playlist->request.i_status = PLAYLIST_STOPPED;
88
        p_playlist->request.b_request = true;
89 90 91
        p_playlist->request.p_item = NULL;
        break;

92 93
    // Node can be null, it will keep the same. Use with care ...
    // Item null = take the first child of node
94 95 96 97 98
    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 )
        {
99
            p_node = p_playlist->status.p_node;
100
            assert( p_node );
101 102 103
        }
        p_playlist->request.i_status = PLAYLIST_RUNNING;
        p_playlist->request.i_skip = 0;
104
        p_playlist->request.b_request = true;
105 106
        p_playlist->request.p_node = p_node;
        p_playlist->request.p_item = p_item;
Clément Stenac's avatar
Clément Stenac committed
107
        if( p_item && var_GetBool( p_playlist, "random" ) )
108
            p_playlist->b_reset_currently_playing = true;
109 110 111 112 113 114 115 116 117
        break;

    case PLAYLIST_PLAY:
        if( p_playlist->p_input )
        {
            val.i_int = PLAYING_S;
            var_Set( p_playlist->p_input, "state", val );
            break;
        }
118 119 120
        else
        {
            p_playlist->request.i_status = PLAYLIST_RUNNING;
121
            p_playlist->request.b_request = true;
122 123 124 125
            p_playlist->request.p_node = p_playlist->status.p_node;
            p_playlist->request.p_item = p_playlist->status.p_item;
            p_playlist->request.i_skip = 0;
        }
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
        break;

    case PLAYLIST_PAUSE:
        val.i_int = 0;
        if( p_playlist->p_input )
            var_Get( p_playlist->p_input, "state", &val );

        if( val.i_int == PAUSE_S )
        {
            p_playlist->status.i_status = PLAYLIST_RUNNING;
            if( p_playlist->p_input )
            {
                val.i_int = PLAYING_S;
                var_Set( p_playlist->p_input, "state", val );
            }
        }
        else
        {
            p_playlist->status.i_status = PLAYLIST_PAUSED;
            if( p_playlist->p_input )
            {
                val.i_int = PAUSE_S;
                var_Set( p_playlist->p_input, "state", val );
            }
        }
        break;

    case PLAYLIST_SKIP:
        p_playlist->request.p_node = p_playlist->status.p_node;
        p_playlist->request.p_item = p_playlist->status.p_item;
        p_playlist->request.i_skip = (int) va_arg( args, int );
157 158 159
        /* if already running, keep running */
        if( p_playlist->status.i_status != PLAYLIST_STOPPED )
            p_playlist->request.i_status = p_playlist->status.i_status;
160
        p_playlist->request.b_request = true;
161 162 163 164 165 166 167
        break;

    default:
        msg_Err( p_playlist, "unknown playlist query" );
        return VLC_EBADVAR;
        break;
    }
168
    vlc_object_signal_maybe( VLC_OBJECT(p_playlist) );
169 170 171 172 173 174 175 176 177 178 179

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Preparse control
 *****************************************************************************/
/** Enqueue an item for preparsing */
int playlist_PreparseEnqueue( playlist_t *p_playlist,
                              input_item_t *p_item )
{
180
    vlc_object_lock( p_playlist->p_preparse );
181 182 183 184 185
    vlc_gc_incref( p_item );
    INSERT_ELEM( p_playlist->p_preparse->pp_waiting,
                 p_playlist->p_preparse->i_waiting,
                 p_playlist->p_preparse->i_waiting,
                 p_item );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
186
    vlc_object_signal_unlocked( p_playlist->p_preparse );
187
    vlc_object_unlock( p_playlist->p_preparse );
188 189 190 191 192 193 194 195
    return VLC_SUCCESS;
}

/** Enqueue a playlist item or a node for peparsing.
 *  This function should be entered without playlist and preparser locks */
int playlist_PreparseEnqueueItem( playlist_t *p_playlist,
                                  playlist_item_t *p_item )
{
196 197
    vlc_object_lock( p_playlist );
    vlc_object_lock( p_playlist->p_preparse );
198
    PreparseEnqueueItemSub( p_playlist, p_item );
199 200
    vlc_object_unlock( p_playlist->p_preparse );
    vlc_object_unlock( p_playlist );
201 202 203
    return VLC_SUCCESS;
}

204
int playlist_AskForArtEnqueue( playlist_t *p_playlist,
205
                               input_item_t *p_item )
206 207 208
{
    int i;

209
    vlc_object_lock( p_playlist->p_fetcher );
Rafaël Carré's avatar
Rafaël Carré committed
210
    for( i = 0; i < p_playlist->p_fetcher->i_waiting ; i++ );
211
    vlc_gc_incref( p_item );
Rafaël Carré's avatar
Rafaël Carré committed
212
    INSERT_ELEM( p_playlist->p_fetcher->pp_waiting,
213
                 p_playlist->p_fetcher->i_waiting,
Rafaël Carré's avatar
Rafaël Carré committed
214
                 i, p_item );
215
    vlc_object_signal_unlocked( p_playlist->p_fetcher );
Rafaël Carré's avatar
typo  
Rafaël Carré committed
216
    vlc_object_unlock( p_playlist->p_fetcher );
217 218 219
    return VLC_SUCCESS;
}

220 221
static void PreparseEnqueueItemSub( playlist_t *p_playlist,
                                    playlist_item_t *p_item )
222 223 224 225 226 227 228 229 230 231 232 233 234 235
{
    int i;
    if( p_item->i_children == -1 )
    {
        vlc_gc_incref( p_item );
        INSERT_ELEM( p_playlist->p_preparse->pp_waiting,
                     p_playlist->p_preparse->i_waiting,
                     p_playlist->p_preparse->i_waiting,
                     p_item->p_input );
    }
    else
    {
        for( i = 0; i < p_item->i_children; i++)
        {
236
            PreparseEnqueueItemSub( p_playlist, p_item->pp_children[i] );
237 238 239 240 241 242 243 244
        }
    }
}

/*****************************************************************************
 * Playback logic
 *****************************************************************************/

Rémi Duraffort's avatar
Rémi Duraffort committed
245 246 247 248 249 250 251 252 253
/**
 * 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
254
{
Rémi Duraffort's avatar
Rémi Duraffort committed
255
     PL_DEBUG( "resyncing on %s", PLI_NAME( p_cur ) );
Clément Stenac's avatar
Clément Stenac committed
256 257 258 259 260
     /* 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
261
          if( ARRAY_VAL( p_playlist->current, i ) == p_cur )
Clément Stenac's avatar
Clément Stenac committed
262 263 264 265 266
          {
              p_playlist->i_current_index = i;
              break;
          }
     }
Rémi Duraffort's avatar
Rémi Duraffort committed
267
     PL_DEBUG( "%s is at %i", PLI_NAME( p_cur ), p_playlist->i_current_index );
Clément Stenac's avatar
Clément Stenac committed
268 269
}

270
void ResetCurrentlyPlaying( playlist_t *p_playlist, bool b_random,
271
                            playlist_item_t *p_cur )
Clément Stenac's avatar
Clément Stenac committed
272 273
{
    playlist_item_t *p_next = NULL;
274 275
    stats_TimerStart( p_playlist, "Items array build",
                      STATS_TIMER_PLAYLIST_BUILD );
Rémi Duraffort's avatar
Rémi Duraffort committed
276 277 278
    PL_DEBUG( "rebuilding array of current - root %s",
              PLI_NAME( p_playlist->status.p_node ) );
    ARRAY_RESET( p_playlist->current );
Clément Stenac's avatar
Clément Stenac committed
279 280 281 282 283 284
    p_playlist->i_current_index = -1;
    while( 1 )
    {
        /** FIXME: this is *slow* */
        p_next = playlist_GetNextLeaf( p_playlist,
                                       p_playlist->status.p_node,
285
                                       p_next, true, false );
Clément Stenac's avatar
Clément Stenac committed
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
        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
305
            /* swap the two items */
Clément Stenac's avatar
Clément Stenac committed
306 307 308 309 310
            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;
        }
    }
311
    p_playlist->b_reset_currently_playing = false;
312
    stats_TimerStop( p_playlist, STATS_TIMER_PLAYLIST_BUILD );
Clément Stenac's avatar
Clément Stenac committed
313 314
}

Rémi Duraffort's avatar
Rémi Duraffort committed
315 316 317 318 319 320 321
/**
 * Compute the next playlist item depending on
 * the playlist course mode (forward, backward, random, view,...).
 *
 * \param p_playlist the playlist object
 * \return nothing
 */
322 323 324
playlist_item_t * playlist_NextItem( playlist_t *p_playlist )
{
    playlist_item_t *p_new = NULL;
325
    int i_skip = 0, i;
326

327 328 329 330
    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" );
331 332 333

    /* Handle quickly a few special cases */
    /* No items to play */
Clément Stenac's avatar
Clément Stenac committed
334
    if( p_playlist->items.i_size == 0 )
335 336 337 338 339 340
    {
        msg_Info( p_playlist, "playlist is empty" );
        return NULL;
    }

    /* Repeat and play/stop */
341
    if( !p_playlist->request.b_request && b_repeat == true &&
342 343 344 345 346
         p_playlist->status.p_item )
    {
        msg_Dbg( p_playlist,"repeating item" );
        return p_playlist->status.p_item;
    }
347
    if( !p_playlist->request.b_request && b_playstop == true )
348
    {
Rémi Duraffort's avatar
Rémi Duraffort committed
349
        msg_Dbg( p_playlist,"stopping (play and stop)" );
350 351 352
        return NULL;
    }

353
    if( !p_playlist->request.b_request && p_playlist->status.p_item )
354
    {
355 356 357 358 359 360 361 362 363 364
        playlist_item_t *p_parent = p_playlist->status.p_item;
        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;
        }
365 366 367 368 369
    }

    /* Start the real work */
    if( p_playlist->request.b_request )
    {
370 371
        p_new = p_playlist->request.p_item;
        i_skip = p_playlist->request.i_skip;
Clément Stenac's avatar
Clément Stenac committed
372
        PL_DEBUG( "processing request item %s node %s skip %i",
373 374
                        PLI_NAME( p_playlist->request.p_item ),
                        PLI_NAME( p_playlist->request.p_node ), i_skip );
375

376 377 378 379 380 381 382 383 384 385 386 387 388
        /* Make sure the node wasn't deleted */
        if( p_playlist->status.p_node &&
            p_playlist->status.p_node->i_flags & PLAYLIST_REMOVE_FLAG )
        {
             PL_DEBUG( "%s was marked for deletion, deleting",
                             PLI_NAME( p_playlist->status.p_node  ) );
             playlist_ItemDelete( p_playlist->status.p_node );
             /* Don't attempt to reuse that node */
             if( p_playlist->status.p_node == p_playlist->request.p_node )
                p_playlist->request.p_node = NULL;
             p_playlist->status.p_node = NULL;
        }

Clément Stenac's avatar
Clément Stenac committed
389 390 391
        if( p_playlist->request.p_node &&
            p_playlist->request.p_node != p_playlist->status.p_node )
        {
392

393
            p_playlist->status.p_node = p_playlist->request.p_node;
394
            p_playlist->b_reset_currently_playing = true;
Clément Stenac's avatar
Clément Stenac committed
395
        }
396

Rémi Duraffort's avatar
Rémi Duraffort committed
397
        /* If we are asked for a node, don't take it */
398
        if( i_skip == 0 && ( p_new == NULL || p_new->i_children != -1 ) )
399 400
            i_skip++;

Clément Stenac's avatar
Clément Stenac committed
401
        if( p_playlist->b_reset_currently_playing )
402
            /* A bit too bad to reset twice ... */
Clément Stenac's avatar
Clément Stenac committed
403 404 405 406 407 408
            ResetCurrentlyPlaying( p_playlist, b_random, p_new );
        else if( p_new )
            ResyncCurrentIndex( p_playlist, p_new );
        else
            p_playlist->i_current_index = -1;

409
        if( p_playlist->current.i_size && (i_skip > 0) )
410
        {
411 412
            if( p_playlist->i_current_index < -1 )
                p_playlist->i_current_index = -1;
413 414
            for( i = i_skip; i > 0 ; i-- )
            {
Clément Stenac's avatar
Clément Stenac committed
415
                p_playlist->i_current_index++;
416
                if( p_playlist->i_current_index >= p_playlist->current.i_size )
417
                {
418
                    PL_DEBUG( "looping - restarting at beginning of node" );
Clément Stenac's avatar
Clément Stenac committed
419
                    p_playlist->i_current_index = 0;
420 421
                }
            }
Clément Stenac's avatar
Clément Stenac committed
422 423
            p_new = ARRAY_VAL( p_playlist->current,
                               p_playlist->i_current_index );
424
        }
425
        else if( p_playlist->current.i_size && (i_skip < 0) )
426 427 428
        {
            for( i = i_skip; i < 0 ; i++ )
            {
Clément Stenac's avatar
Clément Stenac committed
429
                p_playlist->i_current_index--;
430
                if( p_playlist->i_current_index <= -1 )
431
                {
432
                    PL_DEBUG( "looping - restarting at end of node" );
Clément Stenac's avatar
Clément Stenac committed
433
                    p_playlist->i_current_index = p_playlist->current.i_size-1;
434 435
                }
            }
Clément Stenac's avatar
Clément Stenac committed
436 437
            p_new = ARRAY_VAL( p_playlist->current,
                               p_playlist->i_current_index );
438 439
        }
        /* Clear the request */
440
        p_playlist->request.b_request = false;
441 442 443 444
    }
    /* "Automatic" item change ( next ) */
    else
    {
Clément Stenac's avatar
Clément Stenac committed
445 446
        PL_DEBUG( "changing item without a request (current %i/%i)",
                  p_playlist->i_current_index, p_playlist->current.i_size );
447 448 449 450 451
        /* Cant go to next from current item */
        if( p_playlist->status.p_item &&
            p_playlist->status.p_item->i_flags & PLAYLIST_SKIP_FLAG )
            return NULL;

Clément Stenac's avatar
Clément Stenac committed
452 453 454 455 456
        if( p_playlist->b_reset_currently_playing )
            ResetCurrentlyPlaying( p_playlist, b_random,
                                   p_playlist->status.p_item );

        p_playlist->i_current_index++;
Rafaël Carré's avatar
Rafaël Carré committed
457
        assert( p_playlist->i_current_index <= p_playlist->current.i_size );
Clément Stenac's avatar
Clément Stenac committed
458
        if( p_playlist->i_current_index == p_playlist->current.i_size )
459
        {
Clément Stenac's avatar
Clément Stenac committed
460 461
            if( !b_loop || p_playlist->current.i_size == 0 ) return NULL;
            p_playlist->i_current_index = 0;
462
        }
Clément Stenac's avatar
Clément Stenac committed
463 464 465 466
        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 );
467 468 469 470 471 472 473
        /* 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
474 475 476 477 478 479 480
/**
 * Start the input for an item
 *
 * \param p_playlist the playlist objetc
 * \param p_item the item to play
 * \return nothing
 */
481 482
int playlist_PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
{
483
    input_item_t *p_input = p_item->p_input;
484
    sout_instance_t **pp_sout = &libvlc_priv(p_playlist->p_libvlc)->p_sout;
Rémi Duraffort's avatar
Rémi Duraffort committed
485
    int i_activity = var_GetInteger( p_playlist, "activity" ) ;
486 487 488

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

489
    p_input->i_nb_played++;
490 491 492 493 494 495
    p_playlist->status.p_item = p_item;

    p_playlist->status.i_status = PLAYLIST_RUNNING;

    var_SetInteger( p_playlist, "activity", i_activity +
                    DEFAULT_INPUT_ACTIVITY );
496 497

    input_thread_t * p_input_thread =
498
        input_CreateThreadExtended( p_playlist, p_input, NULL, *pp_sout );
499 500 501
    playlist_set_current_input( p_playlist, p_input_thread );
    vlc_object_release( p_input_thread );

502
    *pp_sout = NULL;
503

504 505 506 507 508 509 510 511 512
    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 );

513 514
    if( p_playlist->p_fetcher &&
            p_playlist->p_fetcher->i_art_policy == ALBUM_ART_WHEN_PLAYED )
515
    {
516
        bool b_has_art;
517

518 519 520 521
        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
522
        /* p_input->p_meta should not be null after a successfull CreateThread */
523
        b_has_art = !EMPTY_STR( psz_arturl );
524

525
        if( !b_has_art || strncmp( psz_arturl, "attachment://", 13 ) )
526
        {
527
            PL_DEBUG( "requesting art for %s", psz_name );
528 529
            playlist_AskForArtEnqueue( p_playlist, p_input );
        }
530 531
        free( psz_arturl );
        free( psz_name );
532
    }
533

534
    PL_UNLOCK;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
535
    var_SetInteger( p_playlist, "playlist-current", p_input->i_id );
536
    PL_LOCK;
537 538 539

    return VLC_SUCCESS;
}