control.c 17.5 KB
Newer Older
1
/*****************************************************************************
zorglub's avatar
zorglub committed
2
 * control.c : Handle control of the playlist & running through it
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 *****************************************************************************
 * Copyright (C) 1999-2004 the VideoLAN team
 * $Id: /local/vlc/0.8.6-playlist-vlm/src/playlist/playlist.c 13741 2006-03-21T19:29:39.792444Z zorglub  $
 *
 * 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.
 *****************************************************************************/
#include <vlc/vlc.h>
#include <vlc/input.h>
#include "vlc_playlist.h"
27
#include "playlist_internal.h"
28
#include <assert.h>
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
int PlaylistVAControl( playlist_t * p_playlist, int i_query, va_list args );

void PreparseEnqueueItemSub( playlist_t *, playlist_item_t * );

playlist_item_t *playlist_RecursiveFindLast(playlist_t *p_playlist,
                                            playlist_item_t *p_node );

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

/**
 * Do a playlist action. Should be entered without playlist lock
 * \see playlist_Control
 */
int playlist_LockControl( playlist_t * p_playlist, int i_query, ... )
{
    va_list args;
    int i_result;
    va_start( args, i_query );
    vlc_mutex_lock( &p_playlist->object_lock );
    i_result = PlaylistVAControl( p_playlist, i_query, args );
    va_end( args );
    vlc_mutex_unlock( &p_playlist->object_lock );
    return i_result;
}

/**
 * Do a playlist action.
 * If there is something in the playlist then you can do playlist actions.
 * Should be entered with playlist lock. See include/vlc_playlist.h for
 * possible queries
 *
 * \param p_playlist the playlist to do the command on
 * \param i_query the command to do
 * \param variable number of arguments
 * \return VLC_SUCCESS or an error
 */
int playlist_Control( playlist_t * p_playlist, int i_query, ... )
{
    va_list args;
    int i_result;
    va_start( args, i_query );
    i_result = PlaylistVAControl( p_playlist, i_query, args );
    va_end( args );

    return i_result;
}

int PlaylistVAControl( playlist_t * p_playlist, int i_query, va_list args )
{
    playlist_item_t *p_item, *p_node;
    vlc_value_t val;

    if( p_playlist->i_size <= 0 )
    {
        return VLC_EGENERIC;
    }

    switch( i_query )
    {
    case PLAYLIST_STOP:
        p_playlist->request.i_status = PLAYLIST_STOPPED;
        p_playlist->request.b_request = VLC_TRUE;
        p_playlist->request.p_item = NULL;
        break;

100
101
    // Node can be null, it will keep the same. Use with care ...
    // Item null = take the first child of node
102
    case PLAYLIST_VIEWPLAY:
zorglub's avatar
zorglub committed
103
        p_playlist->b_reset_random = VLC_TRUE;
104
105
106
107
        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 )
        {
108
            p_node = p_playlist->status.p_node;
109
            assert( p_node );
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
        }
        p_playlist->request.i_status = PLAYLIST_RUNNING;
        p_playlist->request.i_skip = 0;
        p_playlist->request.b_request = VLC_TRUE;
        p_playlist->request.p_node = p_node;
        p_playlist->request.p_item = p_item;
        break;

    case PLAYLIST_PLAY:
        if( p_playlist->p_input )
        {
            val.i_int = PLAYING_S;
            var_Set( p_playlist->p_input, "state", val );
            break;
        }
125
126
127
128
129
130
131
132
        else
        {
            p_playlist->request.i_status = PLAYLIST_RUNNING;
            p_playlist->request.b_request = VLC_TRUE;
            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;
        }
133
134
135
        break;

    case PLAYLIST_AUTOPLAY:
136
137
        // AUTOPLAY is an ugly hack for initial status.
        // Hopefully it will disappear
138
        p_playlist->status.i_status = PLAYLIST_RUNNING;
139
        p_playlist->request.p_node = p_playlist->status.p_node;
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
        p_playlist->request.b_request = VLC_FALSE;
        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 );
zorglub's avatar
zorglub committed
172
173
174
        /* if already running, keep running */
        if( p_playlist->status.i_status != PLAYLIST_STOPPED )
            p_playlist->request.i_status = p_playlist->status.i_status;
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
        p_playlist->request.b_request = VLC_TRUE;
        break;

    default:
        msg_Err( p_playlist, "unknown playlist query" );
        return VLC_EBADVAR;
        break;
    }

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Preparse control
 *****************************************************************************/
/** Enqueue an item for preparsing */
int playlist_PreparseEnqueue( playlist_t *p_playlist,
                              input_item_t *p_item )
{
    vlc_mutex_lock( &p_playlist->p_preparse->object_lock );
    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 );
    vlc_mutex_unlock( &p_playlist->p_preparse->object_lock );
201
    vlc_cond_signal( &p_playlist->p_preparse->object_wait );
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    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 )
{
    vlc_mutex_lock( &p_playlist->object_lock );
    vlc_mutex_lock( &p_playlist->p_preparse->object_lock );
    PreparseEnqueueItemSub( p_playlist, p_item );
    vlc_mutex_unlock( &p_playlist->p_preparse->object_lock );
    vlc_mutex_unlock( &p_playlist->object_lock );
    return VLC_SUCCESS;
}

dionoea's avatar
dionoea committed
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
int playlist_AskForArtEnqueue( playlist_t *p_playlist,
                              input_item_t *p_item )
{
    int i;
    preparse_item_t p;
    p.p_item = p_item;
    p.b_fetch_art = VLC_TRUE;

    vlc_mutex_lock( &p_playlist->p_secondary_preparse->object_lock );
    for( i = 0; i < p_playlist->p_secondary_preparse->i_waiting &&
         p_playlist->p_secondary_preparse->p_waiting->b_fetch_art == VLC_TRUE;
         i++ );
    vlc_gc_incref( p_item );
    INSERT_ELEM( p_playlist->p_secondary_preparse->p_waiting,
                 p_playlist->p_secondary_preparse->i_waiting,
233
                 i, p );
dionoea's avatar
dionoea committed
234
    vlc_mutex_unlock( &p_playlist->p_secondary_preparse->object_lock );
zorglub's avatar
zorglub committed
235
    vlc_cond_signal( &p_playlist->p_secondary_preparse->object_wait );
dionoea's avatar
dionoea committed
236
237
238
    return VLC_SUCCESS;
}

239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
void PreparseEnqueueItemSub( playlist_t *p_playlist,
                             playlist_item_t *p_item )
{
    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++)
        {
255
            PreparseEnqueueItemSub( p_playlist, p_item->pp_children[i] );
256
257
258
259
260
261
262
263
264
265
266
267
268
        }
    }
}

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

/** This function calculates the next playlist item, depending
 *  on the playlist course mode (forward, backward, random, view,...). */
playlist_item_t * playlist_NextItem( playlist_t *p_playlist )
{
    playlist_item_t *p_new = NULL;
269
    int i_skip = 0, i;
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296

    vlc_bool_t b_loop = var_GetBool( p_playlist, "loop" );
    vlc_bool_t b_random = var_GetBool( p_playlist, "random" );
    vlc_bool_t b_repeat = var_GetBool( p_playlist, "repeat" );
    vlc_bool_t b_playstop = var_GetBool( p_playlist, "play-and-stop" );

    /* Handle quickly a few special cases */
    /* No items to play */
    if( p_playlist->i_size == 0 )
    {
        msg_Info( p_playlist, "playlist is empty" );
        return NULL;
    }

    /* Repeat and play/stop */
    if( !p_playlist->request.b_request && b_repeat == VLC_TRUE &&
         p_playlist->status.p_item )
    {
        msg_Dbg( p_playlist,"repeating item" );
        return p_playlist->status.p_item;
    }
    if( !p_playlist->request.b_request && b_playstop == VLC_TRUE )
    {
        msg_Dbg( p_playlist,"stopping (play and stop)");
        return NULL;
    }

zorglub's avatar
zorglub committed
297
    if( !p_playlist->request.b_request && p_playlist->status.p_item )
298
    {
zorglub's avatar
zorglub committed
299
300
301
302
303
304
305
306
307
308
        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;
        }
309
310
311
312
    }

    /* Random case. This is an exception: if request, but request is skip +- 1
     * we don't go to next item but select a new random one. */
313
    if( b_random &&
314
        ( !p_playlist->request.b_request ||
315
316
317
318
        ( p_playlist->request.b_request &&
            ( p_playlist->request.p_item == NULL ||
              p_playlist->request.i_skip == 1    ||
              p_playlist->request.i_skip == -1 ) ) ) )
319
    {
zorglub's avatar
zorglub committed
320
321
322
323
       PL_DEBUG( "doing random, have %i items, currently at %i, reset %i\n",
                        p_playlist->i_random, p_playlist->i_random_index,
                        p_playlist->b_reset_random );
       if( p_playlist->b_reset_random )
324
        {
zorglub's avatar
zorglub committed
325
326
327
328
329
330
331
332
333
334
335
336
337
            int j;
            FREE( p_playlist->pp_random );
            if( !p_playlist->b_reset_random &&  !b_loop ) goto end;
            p_playlist->i_random = 0;
            p_playlist->i_random_index = 0;
            p_playlist->i_random = playlist_GetAllEnabledChildren(
                                                p_playlist,
                                                p_playlist->status.p_node,
                                                &p_playlist->pp_random );
            /* Shuffle the array */
            srand( (unsigned int)mdate() );
            int swap = 0;
            for( j = p_playlist->i_random -1; j > 0; j-- )
338
            {
zorglub's avatar
zorglub committed
339
340
341
342
343
344
                swap++;
                int i = rand() % (j+1); /* between 0 and j */
                playlist_item_t *p_tmp;
                p_tmp = p_playlist->pp_random[i];
                p_playlist->pp_random[i] = p_playlist->pp_random[j];
                p_playlist->pp_random[j] = p_tmp;
345
            }
zorglub's avatar
zorglub committed
346
            p_playlist->b_reset_random = VLC_FALSE;
zorglub's avatar
zorglub committed
347
            PL_DEBUG( "random rebuilt, have %i items", p_playlist->i_random );
348
        }
zorglub's avatar
zorglub committed
349
        else
350
        {
zorglub's avatar
zorglub committed
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
            /* Go backward or forward */
            if( !p_playlist->request.b_request || !p_playlist->request.p_item ||
                                               p_playlist->request.i_skip == 1 )
                p_playlist->i_random_index++;
            else
                p_playlist->i_random_index--;
            /* Handle bounds situations */
            if( p_playlist->i_random_index == -1 )
            {
                if( !b_loop || p_playlist->i_random == 0 ) goto end;
                p_playlist->i_random_index = p_playlist->i_random - 1;
            }
            else if( p_playlist->i_random_index == p_playlist->i_random )
            {
                if( !b_loop || p_playlist->i_random == 0 ) goto end;
                p_playlist->i_random_index = 0;
            }
368
        }
zorglub's avatar
zorglub committed
369
370
371
372
373
        PL_DEBUG( "using random item %i", p_playlist->i_random_index );
        if ( p_playlist->i_random == 0 ) goto end; /* Can this happen ?? */
        p_new = p_playlist->pp_random[p_playlist->i_random_index];
end:
        if( !p_new ) p_playlist->b_reset_random = VLC_TRUE;
374
375
        p_playlist->request.i_skip = 0;
        p_playlist->request.b_request = VLC_FALSE;
zorglub's avatar
zorglub committed
376
        return p_new;
377
    }
378
379
380
381

    /* Start the real work */
    if( p_playlist->request.b_request )
    {
382
383
        p_new = p_playlist->request.p_item;
        i_skip = p_playlist->request.i_skip;
zorglub's avatar
zorglub committed
384
        PL_DEBUG( "processing request item %s node %s skip %i",
zorglub's avatar
zorglub committed
385
386
                        PLI_NAME( p_playlist->request.p_item ),
                        PLI_NAME( p_playlist->request.p_node ), i_skip );
387

388
389
        if( p_playlist->request.p_node )
            p_playlist->status.p_node = p_playlist->request.p_node;
390

391
392
        /* If we are asked for a node, dont take it */
        if( i_skip == 0 && ( p_new == NULL || p_new->i_children != -1 ) )
393
394
395
396
397
398
            i_skip++;

        if( i_skip > 0 )
        {
            for( i = i_skip; i > 0 ; i-- )
            {
399
400
401
                p_new = playlist_GetNextLeaf( p_playlist,
                                              p_playlist->request.p_node,
                                              p_new, VLC_TRUE, VLC_FALSE );
402
403
                if( p_new == NULL )
                {
zorglub's avatar
zorglub committed
404
                    PL_DEBUG( "looping - restarting at beginning of node" );
405
406
                    p_new = playlist_GetNextLeaf( p_playlist,
                                                  p_playlist->request.p_node,
407
                                                  NULL, VLC_TRUE, VLC_FALSE);
408
409
410
411
412
413
414
415
416
417
                    if( p_new == NULL ) break;
                }
            }
        }
        else if( i_skip < 0 )
        {
            for( i = i_skip; i < 0 ; i++ )
            {
                p_new = playlist_GetPrevLeaf( p_playlist,
                                              p_playlist->request.p_node,
418
                                              p_new, VLC_FALSE, VLC_FALSE );
419
420
                if( p_new == NULL )
                {
zorglub's avatar
zorglub committed
421
                    PL_DEBUG( "looping - restarting at end of node" );
422
423
424
425
426
427
428
429
430
431
432
433
434
435
                    /** \bug This is needed because GetPrevLeaf does not loop
                      * by itself */
                    p_new = playlist_GetLastLeaf( p_playlist,
                                                 p_playlist->request.p_node );
                }
                if( p_new == NULL ) break;
            }
        }
        /* Clear the request */
        p_playlist->request.b_request = VLC_FALSE;
    }
    /* "Automatic" item change ( next ) */
    else
    {
zorglub's avatar
zorglub committed
436
        PL_DEBUG( "changing item without a request" );
437
438
439
440
441
442
443
        /* 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;

        p_new = playlist_GetNextLeaf( p_playlist,
                                      p_playlist->status.p_node,
444
445
                                      p_playlist->status.p_item,
                                      VLC_TRUE, VLC_FALSE );
446
447
        if( p_new == NULL && b_loop )
        {
zorglub's avatar
zorglub committed
448
            PL_DEBUG( "looping" );
449
450
            p_new = playlist_GetNextLeaf( p_playlist,
                                          p_playlist->status.p_node,
451
                                          NULL, VLC_TRUE, VLC_FALSE );
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
        }
        /* The new item can't be autoselected  */
        if( p_new != NULL && p_new->i_flags & PLAYLIST_SKIP_FLAG )
            return NULL;
    }
    if( p_new == NULL )
    {
        msg_Dbg( p_playlist, "did not find something to play" );
    }
    return p_new;
}

/** Start the input for an item */
int playlist_PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
{
    vlc_value_t val;
    int i_activity = var_GetInteger( p_playlist, "activity") ;

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

    p_item->p_input->i_nb_played++;
    p_playlist->status.p_item = p_item;

    p_playlist->status.i_status = PLAYLIST_RUNNING;

    var_SetInteger( p_playlist, "activity", i_activity +
                    DEFAULT_INPUT_ACTIVITY );
    p_playlist->p_input = input_CreateThread( p_playlist, p_item->p_input );

    val.i_int = p_item->p_input->i_id;
    /* unlock the playlist to set the var...mmm */
    vlc_mutex_unlock( &p_playlist->object_lock);
    var_Set( p_playlist, "playlist-current", val);
    vlc_mutex_lock( &p_playlist->object_lock);

    return VLC_SUCCESS;
}