playlist.c 19.3 KB
Newer Older
1
2
3
/*****************************************************************************
 * playlist.c : Playlist management functions
 *****************************************************************************
4
 * Copyright (C) 1999-2004 VideoLAN
5
 * $Id: playlist.c,v 1.78 2004/01/26 23:30:18 fenrir Exp $
6
7
8
9
10
11
12
 *
 * Authors: Samuel Hocevar <sam@zoy.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.
13
 *
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/
#include <stdlib.h>                                      /* free(), strtol() */
#include <stdio.h>                                              /* sprintf() */
#include <string.h>                                            /* strerror() */

#include <vlc/vlc.h>
28
29
#include <vlc/vout.h>
#include <vlc/sout.h>
Sam Hocevar's avatar
Sam Hocevar committed
30
#include <vlc/input.h>
31
32
33
34

#include "stream_control.h"
#include "input_ext-intf.h"

35
#include "vlc_playlist.h"
36

37
38
#define PLAYLIST_FILE_HEADER_0_5  "# vlc playlist file version 0.5"

39
40
41
42
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static void RunThread ( playlist_t * );
43
44
45
static void SkipItem  ( playlist_t *, int );
static void PlayItem  ( playlist_t * );

sigmunau's avatar
sigmunau committed
46
47
48
/**
 * Create playlist
 *
49
 * Create a playlist structure.
sigmunau's avatar
sigmunau committed
50
51
52
 * \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
 */
53
playlist_t * __playlist_Create ( vlc_object_t *p_parent )
54
55
{
    playlist_t *p_playlist;
hartman's avatar
hartman committed
56
    vlc_value_t     val;
57
58
59
60
61
62
63
64
65

    /* Allocate structure */
    p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
    if( !p_playlist )
    {
        msg_Err( p_parent, "out of memory" );
        return NULL;
    }

hartman's avatar
hartman committed
66
67
68
69
    var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
    val.b_bool = VLC_TRUE;
    var_Set( p_playlist, "intf-change", val );

70
71
72
73
74
75
76
77
    var_Create( p_playlist, "item-change", VLC_VAR_INTEGER );
    val.i_int = -1;
    var_Set( p_playlist, "item-change", val );

    var_Create( p_playlist, "playlist-current", VLC_VAR_INTEGER );
    val.i_int = -1;
    var_Set( p_playlist, "playlist-current", val );

78
    var_Create( p_playlist, "intf-popupmenu", VLC_VAR_BOOL );
gbazin's avatar
   
gbazin committed
79

80
81
82
    var_Create( p_playlist, "intf-show", VLC_VAR_BOOL );
    val.b_bool = VLC_TRUE;
    var_Set( p_playlist, "intf-show", val );
83

84
85
86
87
88

    var_Create( p_playlist, "prevent-skip", VLC_VAR_BOOL );
    val.b_bool = VLC_FALSE;
    var_Set( p_playlist, "prevent-skip", val );

hartman's avatar
hartman committed
89
90
91
    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 );
92

93
    p_playlist->p_input = NULL;
94
    p_playlist->i_status = PLAYLIST_STOPPED;
95
96
97
    p_playlist->i_index = -1;
    p_playlist->i_size = 0;
    p_playlist->pp_items = NULL;
Sam Hocevar's avatar
Sam Hocevar committed
98

zorglub's avatar
zorglub committed
99
100
    p_playlist->i_groups = 0;
    p_playlist->pp_groups = NULL;
101
102
    p_playlist->i_last_group = 0;
    p_playlist->i_last_id = 0;
hartman's avatar
hartman committed
103
104
    p_playlist->i_sort = SORT_ID;
    p_playlist->i_order = ORDER_NORMAL;
zorglub's avatar
zorglub committed
105

zorglub's avatar
zorglub committed
106
    playlist_CreateGroup( p_playlist, _("Normal") );
zorglub's avatar
zorglub committed
107

108
109
    if( vlc_thread_create( p_playlist, "playlist", RunThread,
                           VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
110
111
112
113
114
115
    {
        msg_Err( p_playlist, "cannot spawn playlist thread" );
        vlc_object_destroy( p_playlist );
        return NULL;
    }

116
117
118
    /* The object has been initialized, now attach it */
    vlc_object_attach( p_playlist, p_parent );

119
120
121
    return p_playlist;
}

sigmunau's avatar
sigmunau committed
122
123
124
/**
 * Destroy the playlist.
 *
125
 * Delete all items in the playlist and free the playlist structure.
sigmunau's avatar
sigmunau committed
126
127
 * \param p_playlist the playlist structure to destroy
 */
128
129
130
131
132
133
void playlist_Destroy( playlist_t * p_playlist )
{
    p_playlist->b_die = 1;

    vlc_thread_join( p_playlist );

hartman's avatar
hartman committed
134
    var_Destroy( p_playlist, "intf-change" );
135
    var_Destroy( p_playlist, "item-change" );
136
137
138
139
140
141
142
    var_Destroy( p_playlist, "playlist-current" );
    var_Destroy( p_playlist, "intf-popmenu" );
    var_Destroy( p_playlist, "intf-show" );
    var_Destroy( p_playlist, "prevent-skip" );
    var_Destroy( p_playlist, "random" );
    var_Destroy( p_playlist, "repeat" );
    var_Destroy( p_playlist, "loop" );
hartman's avatar
hartman committed
143

144
145
146
147
148
149
150
151
152
153
    while( p_playlist->i_groups > 0 )
    {
        playlist_DeleteGroup( p_playlist, p_playlist->pp_groups[0]->i_id );
    }

    while( p_playlist->i_size > 0 )
    {
        playlist_Delete( p_playlist, 0 );
    }

154
155
156
157
    vlc_object_destroy( p_playlist );
}


sigmunau's avatar
sigmunau committed
158
159
/**
 * Do a playlist action
160
 *
sigmunau's avatar
sigmunau committed
161
162
163
164
165
166
 * \param p_playlist the playlist to do the command on
 * \param i_command the command to do
 * \param i_arg the argument to the command. See playlist_command_t for details
 */
 void playlist_Command( playlist_t * p_playlist, playlist_command_t i_command,
                       int i_arg )
167
{
168
169
    vlc_value_t val;

170
    vlc_mutex_lock( &p_playlist->object_lock );
Sam Hocevar's avatar
Sam Hocevar committed
171

172
173
174
175
    switch( i_command )
    {
    case PLAYLIST_STOP:
        p_playlist->i_status = PLAYLIST_STOPPED;
176
177
178
        if( p_playlist->p_input )
        {
            input_StopThread( p_playlist->p_input );
179
180
            val.i_int = p_playlist->i_index;
            var_Set( p_playlist, "item-change",val );
181
        }
182
        break;
183

184
185
    case PLAYLIST_PLAY:
        p_playlist->i_status = PLAYLIST_RUNNING;
zorglub's avatar
zorglub committed
186
        if( !p_playlist->p_input && p_playlist->i_enabled != 0 )
187
        {
188
            PlayItem( p_playlist );
gbazin's avatar
   
gbazin committed
189
190
191
        }
        if( p_playlist->p_input )
        {
192
193
            val.i_int = PLAYING_S;
            var_Set( p_playlist->p_input, "state", val );
194
        }
195
        break;
196

197
    case PLAYLIST_PAUSE:
hartman's avatar
hartman committed
198
        val.i_int = 0;
199
        if( p_playlist->p_input )
hartman's avatar
hartman committed
200
201
202
            var_Get( p_playlist->p_input, "state", &val );

        if( val.i_int == PAUSE_S )
203
        {
hartman's avatar
hartman committed
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
            p_playlist->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->i_status = PLAYLIST_PAUSED;
            if( p_playlist->p_input )
            {
                val.i_int = PAUSE_S;
                var_Set( p_playlist->p_input, "state", val );
            }
219
220
221
        }
        break;

222
    case PLAYLIST_SKIP:
223
        p_playlist->i_status = PLAYLIST_STOPPED;
zorglub's avatar
zorglub committed
224
225
226
227
        if( p_playlist->i_enabled == 0)
        {
            break;
        }
228
229
        SkipItem( p_playlist, i_arg );
        if( p_playlist->p_input )
230
        {
231
232
233
234
235
236
            input_StopThread( p_playlist->p_input );
        }
        p_playlist->i_status = PLAYLIST_RUNNING;
        break;

    case PLAYLIST_GOTO:
zorglub's avatar
zorglub committed
237
238
        if( i_arg >= 0 && i_arg < p_playlist->i_size &&
            p_playlist->i_enabled != 0 )
239
240
241
242
243
244
        {
            p_playlist->i_index = i_arg;
            if( p_playlist->p_input )
            {
                input_StopThread( p_playlist->p_input );
            }
245
246
            val.b_bool = VLC_TRUE;
            var_Set( p_playlist, "prevent-skip", val );
247
248
249
            p_playlist->i_status = PLAYLIST_RUNNING;
        }
        break;
250

251
    default:
252
        msg_Err( p_playlist, "unknown playlist command" );
253
254
255
        break;
    }

256
    vlc_mutex_unlock( &p_playlist->object_lock );
257
#if 0
gbazin's avatar
   
gbazin committed
258
259
    val.b_bool = VLC_TRUE;
    var_Set( p_playlist, "intf-change", val );
260
#endif
261
262
    return;
}
263

264

265
266
267
268
269
static void ObjectGarbageCollector( playlist_t *p_playlist,
                                    int i_type,
                                    mtime_t *pi_obj_destroyed_date )
{
    vlc_object_t *p_obj;
270
    if( *pi_obj_destroyed_date > mdate() )
271
272
273
274
275
276
277
    {
        return;
    }

    if( *pi_obj_destroyed_date == 0 )
    {
        /* give a little time */
278
        *pi_obj_destroyed_date = mdate() + I64C(300000);
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
    }
    else
    {
        while( ( p_obj = vlc_object_find( p_playlist,
                                           i_type,
                                           FIND_CHILD ) ) )
        {
            if( p_obj->p_parent != (vlc_object_t*)p_playlist )
            {
                /* only first chiled (ie unused) */
                vlc_object_release( p_obj );
                break;
            }
            if( i_type == VLC_OBJECT_VOUT )
            {
                msg_Dbg( p_playlist, "vout garbage collector destroying 1 vout" );
                vlc_object_detach( p_obj );
                vlc_object_release( p_obj );
                vout_Destroy( (vout_thread_t *)p_obj );
            }
            else if( i_type == VLC_OBJECT_SOUT )
            {
                vlc_object_release( p_obj );
                sout_DeleteInstance( (sout_instance_t*)p_obj );
            }
        }
305
        *pi_obj_destroyed_date = 0;
306
307
308
    }
}

309
310
311
312
313
/*****************************************************************************
 * RunThread: main playlist thread
 *****************************************************************************/
static void RunThread ( playlist_t *p_playlist )
{
314
    vlc_object_t *p_obj;
gbazin's avatar
   
gbazin committed
315
316
    vlc_value_t val;

317
318
319
    mtime_t    i_vout_destroyed_date = 0;
    mtime_t    i_sout_destroyed_date = 0;

320
321
322
    /* Tell above that we're ready */
    vlc_thread_ready( p_playlist );

323
324
    while( !p_playlist->b_die )
    {
325
326
        vlc_mutex_lock( &p_playlist->object_lock );

327
328
329
        /* If there is an input, check that it doesn't need to die. */
        if( p_playlist->p_input )
        {
Sam Hocevar's avatar
Sam Hocevar committed
330
            /* This input is dead. Remove it ! */
Sam Hocevar's avatar
Sam Hocevar committed
331
            if( p_playlist->p_input->b_dead )
332
333
334
335
336
            {
                input_thread_t *p_input;

                p_input = p_playlist->p_input;
                p_playlist->p_input = NULL;
337
338
339
340

                /* Release the playlist lock, because we may get stuck
                 * in input_DestroyThread() for some time. */
                vlc_mutex_unlock( &p_playlist->object_lock );
341
342
343

                /* Destroy input */
                input_DestroyThread( p_input );
344

gbazin's avatar
   
gbazin committed
345
346
                /* Unlink current input
                 * (_after_ input_DestroyThread for vout garbage collector) */
347
348
349
                vlc_object_detach( p_input );

                /* Destroy object */
Sam Hocevar's avatar
Sam Hocevar committed
350
                vlc_object_destroy( p_input );
351
352
353

                i_vout_destroyed_date = 0;
                i_sout_destroyed_date = 0;
354
                continue;
355
            }
Sam Hocevar's avatar
Sam Hocevar committed
356
357
358
359
360
361
            /* This input is dying, let him do */
            else if( p_playlist->p_input->b_die )
            {
                ;
            }
            /* This input has finished, ask him to die ! */
Sam Hocevar's avatar
Sam Hocevar committed
362
363
            else if( p_playlist->p_input->b_error
                      || p_playlist->p_input->b_eof )
364
            {
gbazin's avatar
   
gbazin committed
365
366
367
                /* Check for autodeletion */
                if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
                {
gbazin's avatar
   
gbazin committed
368
                    vlc_mutex_unlock( &p_playlist->object_lock );
gbazin's avatar
   
gbazin committed
369
                    playlist_Delete( p_playlist, p_playlist->i_index );
370
371
                    p_playlist->i_index++;
                    p_playlist->i_status = PLAYLIST_RUNNING;
gbazin's avatar
   
gbazin committed
372
                }
gbazin's avatar
   
gbazin committed
373
374
375
376
377
378
379
                else
                {
                    /* Select the next playlist item */
                    SkipItem( p_playlist, 1 );
                    input_StopThread( p_playlist->p_input );
                    vlc_mutex_unlock( &p_playlist->object_lock );
                }
380
                continue;
381
            }
382
383
            else if( p_playlist->p_input->stream.control.i_status != INIT_S )
            {
384
                vlc_mutex_unlock( &p_playlist->object_lock );
385
                ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
386
                                        &i_vout_destroyed_date );
387
                ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
388
389
                                        &i_sout_destroyed_date );
                vlc_mutex_lock( &p_playlist->object_lock );
390
            }
391
392
393
        }
        else if( p_playlist->i_status != PLAYLIST_STOPPED )
        {
394
395
396
397
398
399
400
            var_Get( p_playlist, "prevent-skip", &val);
            if( val.b_bool == VLC_FALSE)
            {
                SkipItem( p_playlist, 0 );
            }
            val.b_bool = VLC_TRUE;
            var_Set( p_playlist, "prevent-skip", val);
401
            PlayItem( p_playlist );
402
        }
403
404
        else if( p_playlist->i_status == PLAYLIST_STOPPED )
        {
405
            vlc_mutex_unlock( &p_playlist->object_lock );
406
            ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
407
                                    &i_sout_destroyed_date );
408
            ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
409
                                    &i_vout_destroyed_date );
410
            vlc_mutex_lock( &p_playlist->object_lock );
411
        }
412
413
        vlc_mutex_unlock( &p_playlist->object_lock );

414
415
416
417
        msleep( INTF_IDLE_SLEEP );
    }

    /* If there is an input, kill it */
418
    while( 1 )
419
    {
420
421
422
423
424
425
426
427
        vlc_mutex_lock( &p_playlist->object_lock );

        if( p_playlist->p_input == NULL )
        {
            vlc_mutex_unlock( &p_playlist->object_lock );
            break;
        }

Sam Hocevar's avatar
Sam Hocevar committed
428
        if( p_playlist->p_input->b_dead )
429
430
431
432
433
434
        {
            input_thread_t *p_input;

            /* Unlink current input */
            p_input = p_playlist->p_input;
            p_playlist->p_input = NULL;
435
            vlc_mutex_unlock( &p_playlist->object_lock );
436
437
438

            /* Destroy input */
            input_DestroyThread( p_input );
439
440
441
442
443
            /* Unlink current input (_after_ input_DestroyThread for vout
             * garbage collector)*/
            vlc_object_detach( p_input );

            /* Destroy object */
Sam Hocevar's avatar
Sam Hocevar committed
444
            vlc_object_destroy( p_input );
445
            continue;
446
        }
Sam Hocevar's avatar
Sam Hocevar committed
447
448
        else if( p_playlist->p_input->b_die )
        {
449
            /* This input is dying, leave him alone */
Sam Hocevar's avatar
Sam Hocevar committed
450
451
            ;
        }
Sam Hocevar's avatar
Sam Hocevar committed
452
        else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
453
        {
Sam Hocevar's avatar
Sam Hocevar committed
454
            input_StopThread( p_playlist->p_input );
455
            vlc_mutex_unlock( &p_playlist->object_lock );
456
            continue;
457
458
459
460
461
462
        }
        else
        {
            p_playlist->p_input->b_eof = 1;
        }

463
464
        vlc_mutex_unlock( &p_playlist->object_lock );

465
466
        msleep( INTF_IDLE_SLEEP );
    }
467

468
    /* close all remaining sout */
469
    while( ( p_obj = vlc_object_find( p_playlist,
470
                                      VLC_OBJECT_SOUT, FIND_CHILD ) ) )
471
472
    {
        vlc_object_release( p_obj );
473
        sout_DeleteInstance( (sout_instance_t*)p_obj );
474
    }
475
476

    /* close all remaining vout */
477
    while( ( p_obj = vlc_object_find( p_playlist,
478
                                      VLC_OBJECT_VOUT, FIND_CHILD ) ) )
479
    {
480
        vlc_object_detach( p_obj );
481
        vlc_object_release( p_obj );
482
        vout_Destroy( (vout_thread_t *)p_obj );
483
    }
484
485
}

486
487
488
489
490
491
492
493
494
/*****************************************************************************
 * SkipItem: go to Xth playlist item
 *****************************************************************************
 * This function calculates the position of the next playlist item, depending
 * on the playlist course mode (forward, backward, random...).
 *****************************************************************************/
static void SkipItem( playlist_t *p_playlist, int i_arg )
{
    int i_oldindex = p_playlist->i_index;
hartman's avatar
hartman committed
495
    vlc_bool_t b_random, b_repeat, b_loop;
496
    vlc_value_t val;
497
498
499
500
501
502
503
504

    /* If the playlist is empty, there is no current item */
    if( p_playlist->i_size == 0 )
    {
        p_playlist->i_index = -1;
        return;
    }

hartman's avatar
hartman committed
505
506
507
508
509
510
    var_Get( p_playlist, "random", &val );
    b_random = val.b_bool;
    var_Get( p_playlist, "repeat", &val );
    b_repeat = val.b_bool;
    var_Get( p_playlist, "loop", &val );
    b_loop = val.b_bool;
511

512
    /* Increment */
513
    if( b_random )
514
    {
515
        srand( (unsigned int)mdate() );
516
517
518
519
520
521
522
523

        /* Simple random stuff - we cheat a bit to minimize the chances to
         * get the same index again. */
        i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
        if( i_arg == 0 )
        {
            i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
        }
524
    }
hartman's avatar
hartman committed
525
526
527
528
    if( b_repeat )
    {
        i_arg = 0;
    }
529
530
    p_playlist->i_index += i_arg;

531
532
533
534
    /* Boundary check */
    if( p_playlist->i_index >= p_playlist->i_size )
    {
        if( p_playlist->i_status == PLAYLIST_STOPPED
535
             || b_random
hartman's avatar
hartman committed
536
             || b_loop )
537
        {
538
539
            p_playlist->i_index -= p_playlist->i_size
                         * ( p_playlist->i_index / p_playlist->i_size );
540
541
542
543
544
545
546
547
548
549
550
551
        }
        else
        {
            /* Don't loop by default: stop at playlist end */
            p_playlist->i_index = i_oldindex;
            p_playlist->i_status = PLAYLIST_STOPPED;
        }
    }
    else if( p_playlist->i_index < 0 )
    {
        p_playlist->i_index = p_playlist->i_size - 1;
    }
552

zorglub's avatar
zorglub committed
553
554
555
    /* Check that the item is enabled */
   if( p_playlist->pp_items[p_playlist->i_index]->b_enabled == VLC_FALSE &&
       p_playlist->i_enabled != 0)
556
    {
zorglub's avatar
zorglub committed
557
558
        SkipItem( p_playlist , 1 );
    }
559
560
561
562
563
564
565
566
567
568
}

/*****************************************************************************
 * PlayItem: play current playlist item
 *****************************************************************************
 * This function calculates the position of the next playlist item, depending
 * on the playlist course mode (forward, backward, random...).
 *****************************************************************************/
static void PlayItem( playlist_t *p_playlist )
{
569
570
571
572
    playlist_item_t *p_item;
    char            **ppsz_options;
    int             i_options;
    int             i, j;
573
    vlc_value_t val;
574
575
    if( p_playlist->i_index == -1 )
    {
zorglub's avatar
zorglub committed
576
        if( p_playlist->i_size == 0 || p_playlist->i_enabled == 0)
577
578
579
580
581
        {
            return;
        }
        SkipItem( p_playlist, 1 );
    }
zorglub's avatar
zorglub committed
582
583
584
585
586
    if( p_playlist->i_enabled == 0)
    {
        return;
    }

587
    msg_Dbg( p_playlist, "creating new input thread" );
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
    p_item = p_playlist->pp_items[p_playlist->i_index];

    i_options    = 0;
    ppsz_options = NULL;

    /* Beurk, who the hell have done that ???????, why moving options
     * to playlist in a such *bad* way ? --fenrir_is_asking ...*/
    /* Parse input options */
    for( i = 0 ; i < p_item->i_categories ; i++ )
    {
        if( !strcmp( p_item->pp_categories[i]->psz_name, _("Options") ) )
        {
            msg_Dbg( p_playlist, "Parsing %i options for item", p_item->pp_categories[i]->i_infos );
            for( j = 0; j< p_item->pp_categories[i]->i_infos ; j++ )
            {
                msg_Dbg( p_playlist, "Option : %s",
604
                         p_item->pp_categories[i]->pp_infos[j]->psz_value );
605
                TAB_APPEND( i_options, ppsz_options,
606
                            p_item->pp_categories[i]->pp_infos[j]->psz_value );
607
608
609
610
611
612
613
614
615
616
617
618
            }
            break;
        }
    }

    p_playlist->p_input = input_CreateThread( p_playlist, p_item->psz_uri,
                                              ppsz_options, i_options );

    if( ppsz_options )
    {
        free( ppsz_options );
    }
619
620

    val.i_int = p_playlist->i_index;
621
    var_Set( p_playlist, "playlist-current", val);
622
}
623