playlist.c 16.8 KB
Newer Older
1
2
3
4
/*****************************************************************************
 * playlist.c : Playlist management functions
 *****************************************************************************
 * Copyright (C) 1999-2001 VideoLAN
zorglub's avatar
zorglub committed
5
 * $Id: playlist.c,v 1.63 2003/11/02 23:13:30 zorglub 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>
30
31
32
33

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

34
#include "vlc_playlist.h"
35

36
#define PLAYLIST_FILE_HEADER_0_5  "# vlc playlist file version 0.5"
zorglub's avatar
zorglub committed
37
#define PLAYLIST_FILE_HEADER_0_6  "# vlc playlist file version 0.6"
38

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

static void Poubellize ( playlist_t *, input_thread_t * );
47

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

    /* 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
68
69
70
71
    var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
    val.b_bool = VLC_TRUE;
    var_Set( p_playlist, "intf-change", val );

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

74
75
76
    var_Create( p_playlist, "intf-show", VLC_VAR_BOOL );
    val.b_bool = VLC_TRUE;
    var_Set( p_playlist, "intf-show", val );
77

hartman's avatar
hartman committed
78
79
80
    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 );
81

82
    p_playlist->p_input = NULL;
83
    p_playlist->i_status = PLAYLIST_STOPPED;
84
85
86
    p_playlist->i_index = -1;
    p_playlist->i_size = 0;
    p_playlist->pp_items = NULL;
Sam Hocevar's avatar
Sam Hocevar committed
87

zorglub's avatar
zorglub committed
88
89
90
91
92
93
    p_playlist->i_groups = 0;
    p_playlist->pp_groups = NULL;
    p_playlist->i_max_id = 0;

    playlist_CreateGroup( p_playlist, strdup("Normal") );

94
95
    if( vlc_thread_create( p_playlist, "playlist", RunThread,
                           VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
96
97
98
99
100
101
    {
        msg_Err( p_playlist, "cannot spawn playlist thread" );
        vlc_object_destroy( p_playlist );
        return NULL;
    }

102
103
104
    /* The object has been initialized, now attach it */
    vlc_object_attach( p_playlist, p_parent );

105
106
107
    return p_playlist;
}

sigmunau's avatar
sigmunau committed
108
109
110
/**
 * Destroy the playlist.
 *
111
 * Delete all items in the playlist and free the playlist structure.
sigmunau's avatar
sigmunau committed
112
113
 * \param p_playlist the playlist structure to destroy
 */
114
115
116
117
118
119
void playlist_Destroy( playlist_t * p_playlist )
{
    p_playlist->b_die = 1;

    vlc_thread_join( p_playlist );

hartman's avatar
hartman committed
120
121
    var_Destroy( p_playlist, "intf-change" );

122
123
124
125
    vlc_object_destroy( p_playlist );
}


sigmunau's avatar
sigmunau committed
126
127
/**
 * Do a playlist action
128
 *
sigmunau's avatar
sigmunau committed
129
130
131
132
133
134
 * \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 )
135
{
136
137
    vlc_value_t val;

138
    vlc_mutex_lock( &p_playlist->object_lock );
Sam Hocevar's avatar
Sam Hocevar committed
139

140
141
142
143
    switch( i_command )
    {
    case PLAYLIST_STOP:
        p_playlist->i_status = PLAYLIST_STOPPED;
144
145
146
147
        if( p_playlist->p_input )
        {
            input_StopThread( p_playlist->p_input );
        }
148
        break;
149

150
151
    case PLAYLIST_PLAY:
        p_playlist->i_status = PLAYLIST_RUNNING;
zorglub's avatar
zorglub committed
152
        if( !p_playlist->p_input && p_playlist->i_enabled != 0 )
153
        {
154
            PlayItem( p_playlist );
gbazin's avatar
   
gbazin committed
155
156
157
        }
        if( p_playlist->p_input )
        {
158
159
            val.i_int = PLAYING_S;
            var_Set( p_playlist->p_input, "state", val );
160
        }
161
        break;
162

163
164
165
166
    case PLAYLIST_PAUSE:
        p_playlist->i_status = PLAYLIST_PAUSED;
        if( p_playlist->p_input )
        {
167
168
            val.i_int = PAUSE_S;
            var_Set( p_playlist->p_input, "state", val );
169
170
171
        }
        break;

172
    case PLAYLIST_SKIP:
173
        p_playlist->i_status = PLAYLIST_STOPPED;
zorglub's avatar
zorglub committed
174
175
176
177
        if( p_playlist->i_enabled == 0)
        {
            break;
        }
178
179
        SkipItem( p_playlist, i_arg );
        if( p_playlist->p_input )
180
        {
181
182
183
184
185
186
            input_StopThread( p_playlist->p_input );
        }
        p_playlist->i_status = PLAYLIST_RUNNING;
        break;

    case PLAYLIST_GOTO:
zorglub's avatar
zorglub committed
187
188
        if( i_arg >= 0 && i_arg < p_playlist->i_size &&
            p_playlist->i_enabled != 0 )
189
190
191
192
193
194
        {
            p_playlist->i_index = i_arg;
            if( p_playlist->p_input )
            {
                input_StopThread( p_playlist->p_input );
            }
195
196
197
            p_playlist->i_status = PLAYLIST_RUNNING;
        }
        break;
198

199
    default:
200
        msg_Err( p_playlist, "unknown playlist command" );
201
202
203
        break;
    }

204
    vlc_mutex_unlock( &p_playlist->object_lock );
Sam Hocevar's avatar
Sam Hocevar committed
205

gbazin's avatar
   
gbazin committed
206
207
208
    val.b_bool = VLC_TRUE;
    var_Set( p_playlist, "intf-change", val );

209
210
211
212
    return;
}
/* Following functions are local */

213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
static void ObjectGarbageCollector( playlist_t *p_playlist,
                                    int i_type,
                                    vlc_bool_t *pb_obj_destroyed,
                                    mtime_t *pi_obj_destroyed_date )
{
    vlc_object_t *p_obj;
    if( *pb_obj_destroyed || *pi_obj_destroyed_date > mdate() )
    {
        return;
    }

    if( *pi_obj_destroyed_date == 0 )
    {
        /* give a little time */
        *pi_obj_destroyed_date = mdate() + 300000LL;
    }
    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 );
            }
        }
        *pb_obj_destroyed = VLC_TRUE;
    }
}

258
259
260
261
262
/*****************************************************************************
 * RunThread: main playlist thread
 *****************************************************************************/
static void RunThread ( playlist_t *p_playlist )
{
263
    vlc_object_t *p_obj;
gbazin's avatar
   
gbazin committed
264
265
    vlc_value_t val;

266
267
268
269
270
271
    vlc_bool_t b_vout_destroyed = VLC_FALSE; /*we do vout garbage collector */
    mtime_t    i_vout_destroyed_date = 0;

    vlc_bool_t b_sout_destroyed = VLC_FALSE; /*we do vout garbage collector */
    mtime_t    i_sout_destroyed_date = 0;

272
273
274
    /* Tell above that we're ready */
    vlc_thread_ready( p_playlist );

275
276
    while( !p_playlist->b_die )
    {
277
278
        vlc_mutex_lock( &p_playlist->object_lock );

279
280
281
        /* 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
282
            /* This input is dead. Remove it ! */
Sam Hocevar's avatar
Sam Hocevar committed
283
            if( p_playlist->p_input->b_dead )
284
285
286
287
288
            {
                input_thread_t *p_input;

                p_input = p_playlist->p_input;
                p_playlist->p_input = NULL;
289
290
291
292

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

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

gbazin's avatar
   
gbazin committed
297
298
                /* Unlink current input
                 * (_after_ input_DestroyThread for vout garbage collector) */
299
300
301
                vlc_object_detach( p_input );

                /* Destroy object */
Sam Hocevar's avatar
Sam Hocevar committed
302
                vlc_object_destroy( p_input );
303
304
305
306
307

                b_vout_destroyed = VLC_FALSE;
                i_vout_destroyed_date = 0;
                b_sout_destroyed = VLC_FALSE;
                i_sout_destroyed_date = 0;
308
                continue;
309
            }
Sam Hocevar's avatar
Sam Hocevar committed
310
311
312
313
314
315
            /* 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
316
317
            else if( p_playlist->p_input->b_error
                      || p_playlist->p_input->b_eof )
318
            {
gbazin's avatar
   
gbazin committed
319
320
321
                /* Check for autodeletion */
                if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
                {
gbazin's avatar
   
gbazin committed
322
                    vlc_mutex_unlock( &p_playlist->object_lock );
gbazin's avatar
   
gbazin committed
323
                    playlist_Delete( p_playlist, p_playlist->i_index );
gbazin's avatar
   
gbazin committed
324
                    vlc_mutex_lock( &p_playlist->object_lock );
gbazin's avatar
   
gbazin committed
325
326
327
328
329
                }

                /* Select the next playlist item */
                SkipItem( p_playlist, 1 );

Sam Hocevar's avatar
Sam Hocevar committed
330
                input_StopThread( p_playlist->p_input );
331
                vlc_mutex_unlock( &p_playlist->object_lock );
gbazin's avatar
   
gbazin committed
332
333
334

                val.b_bool = VLC_TRUE;
                var_Set( p_playlist, "intf-change", val );
335
                continue;
336
            }
337
338
            else if( p_playlist->p_input->stream.control.i_status != INIT_S )
            {
339
                vlc_mutex_unlock( &p_playlist->object_lock );
340
                ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
341
342
                                        &b_vout_destroyed,
                                        &i_vout_destroyed_date );
343
                ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
344
345
346
                                        &b_sout_destroyed,
                                        &i_sout_destroyed_date );
                vlc_mutex_lock( &p_playlist->object_lock );
347
            }
348
349
350
        }
        else if( p_playlist->i_status != PLAYLIST_STOPPED )
        {
351
            SkipItem( p_playlist, 0 );
352
            PlayItem( p_playlist );
353
        }
354
355
        else if( p_playlist->i_status == PLAYLIST_STOPPED )
        {
356
            vlc_mutex_unlock( &p_playlist->object_lock );
357
358
            ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
                                    &b_sout_destroyed, &i_sout_destroyed_date );
359
360
            ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
                                    &b_vout_destroyed, &i_vout_destroyed_date );
361
            vlc_mutex_lock( &p_playlist->object_lock );
362
        }
363
364
        vlc_mutex_unlock( &p_playlist->object_lock );

365
366
367
368
        msleep( INTF_IDLE_SLEEP );
    }

    /* If there is an input, kill it */
369
    while( 1 )
370
    {
371
372
373
374
375
376
377
378
        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
379
        if( p_playlist->p_input->b_dead )
380
381
382
383
384
385
        {
            input_thread_t *p_input;

            /* Unlink current input */
            p_input = p_playlist->p_input;
            p_playlist->p_input = NULL;
386
            vlc_mutex_unlock( &p_playlist->object_lock );
387
388
389

            /* Destroy input */
            input_DestroyThread( p_input );
390
391
392
393
394
            /* Unlink current input (_after_ input_DestroyThread for vout
             * garbage collector)*/
            vlc_object_detach( p_input );

            /* Destroy object */
Sam Hocevar's avatar
Sam Hocevar committed
395
            vlc_object_destroy( p_input );
396
            continue;
397
        }
Sam Hocevar's avatar
Sam Hocevar committed
398
399
        else if( p_playlist->p_input->b_die )
        {
400
            /* This input is dying, leave him alone */
Sam Hocevar's avatar
Sam Hocevar committed
401
402
            ;
        }
Sam Hocevar's avatar
Sam Hocevar committed
403
        else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
404
        {
Sam Hocevar's avatar
Sam Hocevar committed
405
            input_StopThread( p_playlist->p_input );
406
            vlc_mutex_unlock( &p_playlist->object_lock );
407
            continue;
408
409
410
411
412
413
        }
        else
        {
            p_playlist->p_input->b_eof = 1;
        }

414
415
        vlc_mutex_unlock( &p_playlist->object_lock );

416
417
        msleep( INTF_IDLE_SLEEP );
    }
418

419
    /* close all remaining sout */
420
    while( ( p_obj = vlc_object_find( p_playlist,
421
                                      VLC_OBJECT_SOUT, FIND_CHILD ) ) )
422
423
    {
        vlc_object_release( p_obj );
424
        sout_DeleteInstance( (sout_instance_t*)p_obj );
425
    }
426
427

    /* close all remaining vout */
428
    while( ( p_obj = vlc_object_find( p_playlist,
429
                                      VLC_OBJECT_VOUT, FIND_CHILD ) ) )
430
    {
431
        vlc_object_detach( p_obj );
432
        vlc_object_release( p_obj );
433
        vout_Destroy( (vout_thread_t *)p_obj );
434
    }
435
436
}

437
438
439
440
441
442
443
444
445
/*****************************************************************************
 * 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
446
    vlc_bool_t b_random, b_repeat, b_loop;
447
    vlc_value_t val;
448
449
450
451
452
453
454
455

    /* 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
456
457
458
459
460
461
    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;
462

463
    /* Increment */
464
    if( b_random )
465
    {
466
        srand( (unsigned int)mdate() );
467
468
469
470
471
472
473
474

        /* 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));
        }
475
    }
hartman's avatar
hartman committed
476
477
478
479
    if( b_repeat )
    {
        i_arg = 0;
    }
480
481
    p_playlist->i_index += i_arg;

482
483
484
485
    /* Boundary check */
    if( p_playlist->i_index >= p_playlist->i_size )
    {
        if( p_playlist->i_status == PLAYLIST_STOPPED
486
             || b_random
hartman's avatar
hartman committed
487
             || b_loop )
488
        {
489
490
            p_playlist->i_index -= p_playlist->i_size
                         * ( p_playlist->i_index / p_playlist->i_size );
491
492
493
494
495
496
497
498
499
500
501
502
        }
        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;
    }
503

zorglub's avatar
zorglub committed
504
505
506
507
508
509
    /* Check that the item is enabled */
   if( p_playlist->pp_items[p_playlist->i_index]->b_enabled == VLC_FALSE &&
       p_playlist->i_enabled != 0)
   {
        SkipItem( p_playlist , 1 );
    }
510
511
512
513
514
515
516
517
518
519
520
521
}

/*****************************************************************************
 * 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 )
{
    if( p_playlist->i_index == -1 )
    {
zorglub's avatar
zorglub committed
522
        if( p_playlist->i_size == 0 || p_playlist->i_enabled == 0)
523
524
525
526
527
528
529
        {
            return;
        }

        SkipItem( p_playlist, 1 );
    }

zorglub's avatar
zorglub committed
530
531
532
533
534
    if( p_playlist->i_enabled == 0)
    {
        return;
    }

535
536
    msg_Dbg( p_playlist, "creating new input thread" );
    p_playlist->p_input = input_CreateThread( p_playlist,
537
                                  p_playlist->pp_items[p_playlist->i_index] );
538
}