b43ac5e8ef907945faa9b40458294b121f6e3018
[vlc.git] / src / playlist / playlist.c
1 /*****************************************************************************
2  * playlist.c : Playlist management functions
3  *****************************************************************************
4  * Copyright (C) 1999-2001 VideoLAN
5  * $Id: playlist.c,v 1.10 2002/08/12 09:34:15 sam Exp $
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23 #include <stdlib.h>                                      /* free(), strtol() */
24 #include <stdio.h>                                              /* sprintf() */
25 #include <string.h>                                            /* strerror() */
26 #include <errno.h>                                                 /* ENOMEM */
27
28 #include <vlc/vlc.h>
29
30 #include "stream_control.h"
31 #include "input_ext-intf.h"
32
33 #include "vlc_playlist.h"
34
35 #define PLAYLIST_STOPPED 0
36 #define PLAYLIST_RUNNING 1
37
38 /*****************************************************************************
39  * Local prototypes
40  *****************************************************************************/
41 static void RunThread ( playlist_t * );
42 static void SkipItem  ( playlist_t *, int );
43 static void PlayItem  ( playlist_t * );
44
45 static void Poubellize ( playlist_t *, input_thread_t * );
46
47 /*****************************************************************************
48  * playlist_Create: create playlist
49  *****************************************************************************
50  * Create a playlist structure.
51  *****************************************************************************/
52 playlist_t * __playlist_Create ( vlc_object_t *p_parent )
53 {
54     playlist_t *p_playlist;
55
56     /* Allocate structure */
57     p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
58     if( !p_playlist )
59     {
60         msg_Err( p_parent, "out of memory" );
61         return NULL;
62     }
63
64     p_playlist->p_input = NULL;
65     p_playlist->i_status = PLAYLIST_RUNNING;
66     p_playlist->i_index = -1;
67     p_playlist->i_size = 0;
68     p_playlist->pp_items = NULL;
69
70     if( vlc_thread_create( p_playlist, "playlist", RunThread, VLC_TRUE ) )
71     {
72         msg_Err( p_playlist, "cannot spawn playlist thread" );
73         vlc_object_destroy( p_playlist );
74         return NULL;
75     }
76
77     /* The object has been initialized, now attach it */
78     vlc_object_attach( p_playlist, p_parent );
79
80     return p_playlist;
81 }
82
83 /*****************************************************************************
84  * playlist_Destroy: destroy the playlist
85  *****************************************************************************
86  * Delete all items in the playlist and free the playlist structure.
87  *****************************************************************************/
88 void playlist_Destroy( playlist_t * p_playlist )
89 {
90     p_playlist->b_die = 1;
91
92     vlc_thread_join( p_playlist );
93
94     vlc_object_destroy( p_playlist );
95 }
96
97 /*****************************************************************************
98  * playlist_Add: add an item to the playlist
99  *****************************************************************************
100  * Add an item to the playlist at position i_pos. If i_pos is PLAYLIST_END,
101  * add it at the end regardless of the playlist current size.
102  *****************************************************************************/
103 int playlist_Add( playlist_t *p_playlist, const char * psz_target,
104                                           int i_mode, int i_pos )
105 {
106     playlist_item_t *p_item;
107
108     msg_Warn( p_playlist, "adding playlist item « %s »", psz_target );
109
110     /* Create the new playlist item */
111     p_item = malloc( sizeof( playlist_item_t ) );
112     if( p_item == NULL )
113     {
114         msg_Err( p_playlist, "out of memory" );
115     }
116
117     p_item->psz_name = strdup( psz_target );
118     p_item->i_type = 0;
119     p_item->i_status = 0;
120
121     vlc_mutex_lock( &p_playlist->object_lock );
122
123     /* Do a few boundary checks and allocate space for the item */
124     if( i_pos == PLAYLIST_END )
125     {
126         if( i_mode & PLAYLIST_INSERT )
127         {
128             i_mode &= ~PLAYLIST_INSERT;
129             i_mode |= PLAYLIST_APPEND;
130         }
131
132         i_pos = p_playlist->i_size - 1;
133     }
134
135     if( !(i_mode & PLAYLIST_REPLACE)
136          || i_pos < 0 || i_pos >= p_playlist->i_size )
137     {
138         int i_index;
139
140         p_playlist->i_size++;
141         p_playlist->pp_items = realloc( p_playlist->pp_items,
142                                         p_playlist->i_size * sizeof(void*) );
143         if( p_playlist->pp_items == NULL )
144         {
145             msg_Err( p_playlist, "out of memory" );
146             free( p_item->psz_name );
147             free( p_item );
148             vlc_mutex_unlock( &p_playlist->object_lock );
149             return -1;
150         }
151
152         /* Additional boundary checks */
153         if( i_mode & PLAYLIST_APPEND )
154         {
155             i_pos++;
156         }
157
158         if( i_pos < 0 )
159         {
160             i_pos = 0;
161         }
162         else if( i_pos > p_playlist->i_size - 1 )
163         {
164             i_pos = p_playlist->i_size - 1;
165         }
166
167         /* Now we know exactly where it goes. Just renumber the playlist */
168         for( i_index = p_playlist->i_size - 1; i_index > i_pos ; i_index-- )
169         {
170             p_playlist->pp_items[i_index] = p_playlist->pp_items[i_index - 1];
171         }
172
173         if( p_playlist->i_index >= i_pos )
174         {
175             p_playlist->i_index++;
176         }
177     }
178     else
179     {
180         /* i_mode == PLAYLIST_REPLACE and 0 <= i_pos < p_playlist->i_size */
181         free( p_playlist->pp_items[i_pos]->psz_name );
182         free( p_playlist->pp_items[i_pos] );
183         /* XXX: what if the item is still in use? */
184     }
185
186     p_playlist->pp_items[i_pos] = p_item;
187
188     if( i_mode & PLAYLIST_GO )
189     {
190         p_playlist->i_index = i_pos;
191         if( p_playlist->p_input )
192         {
193             input_StopThread( p_playlist->p_input );
194         }
195         p_playlist->i_status = PLAYLIST_RUNNING;
196     }
197
198     vlc_mutex_unlock( &p_playlist->object_lock );
199
200     return 0;
201 }
202
203 /*****************************************************************************
204  * playlist_Delete: delete an item from the playlist
205  *****************************************************************************
206  * Delete the item in the playlist with position i_pos.
207  *****************************************************************************/
208 int playlist_Delete( playlist_t * p_playlist, int i_pos )
209 {
210     int i_index;
211
212     vlc_mutex_lock( &p_playlist->object_lock );
213
214     if( i_pos >= 0 && i_pos < p_playlist->i_size )
215     {
216         msg_Warn( p_playlist, "deleting playlist item « %s »",
217                               p_playlist->pp_items[i_pos]->psz_name );
218
219         free( p_playlist->pp_items[i_pos]->psz_name );
220         free( p_playlist->pp_items[i_pos] );
221         /* XXX: what if the item is still in use? */
222
223         if( i_pos < p_playlist->i_index )
224         {
225             p_playlist->i_index--;
226         }
227
228         /* Renumber the playlist */
229         for( i_index = i_pos + 1; i_index < p_playlist->i_size; i_index++ )
230         {
231             p_playlist->pp_items[i_index - 1] = p_playlist->pp_items[i_index];
232         }
233
234         p_playlist->i_size--;
235         if( p_playlist->i_size )
236         {
237             p_playlist->pp_items = realloc( p_playlist->pp_items,
238                                         p_playlist->i_size * sizeof(void*) );
239         }
240         else
241         {
242             free( p_playlist->pp_items );
243             p_playlist->pp_items = NULL;
244         }
245     }
246
247     vlc_mutex_unlock( &p_playlist->object_lock );
248
249     return 0;
250 }
251
252 /*****************************************************************************
253  * playlist_Command: do a playlist action
254  *****************************************************************************
255  *
256  *****************************************************************************/
257 void playlist_Command( playlist_t * p_playlist, int i_command, int i_arg )
258 {
259     vlc_mutex_lock( &p_playlist->object_lock );
260
261     switch( i_command )
262     {
263     case PLAYLIST_STOP:
264         p_playlist->i_status = PLAYLIST_STOPPED;
265         if( p_playlist->p_input )
266         {
267             input_StopThread( p_playlist->p_input );
268         }
269         break;
270
271     case PLAYLIST_PLAY:
272         p_playlist->i_status = PLAYLIST_RUNNING;
273         break;
274
275     case PLAYLIST_SKIP:
276         p_playlist->i_status = PLAYLIST_STOPPED;
277         SkipItem( p_playlist, i_arg );
278         if( p_playlist->p_input )
279         {
280             input_StopThread( p_playlist->p_input );
281         }
282         p_playlist->i_status = PLAYLIST_RUNNING;
283         break;
284
285     case PLAYLIST_GOTO:
286         if( i_arg >= 0 && i_arg < p_playlist->i_size )
287         {
288             p_playlist->i_index = i_arg;
289             if( p_playlist->p_input )
290             {
291                 input_StopThread( p_playlist->p_input );
292             }
293             p_playlist->i_status = PLAYLIST_RUNNING;
294         }
295         break;
296
297     default:
298         msg_Err( p_playlist, "unknown playlist command" );
299         break;
300     }
301
302     vlc_mutex_unlock( &p_playlist->object_lock );
303
304     return;
305 }
306
307 /* Following functions are local */
308
309 /*****************************************************************************
310  * RunThread: main playlist thread
311  *****************************************************************************/
312 static void RunThread ( playlist_t *p_playlist )
313 {
314     /* Tell above that we're ready */
315     vlc_thread_ready( p_playlist );
316
317     while( !p_playlist->b_die )
318     {
319         vlc_mutex_lock( &p_playlist->object_lock );
320
321         /* If there is an input, check that it doesn't need to die. */
322         if( p_playlist->p_input )
323         {
324             /* This input is dead. Remove it ! */
325             if( p_playlist->p_input->b_dead )
326             {
327                 input_thread_t *p_input;
328
329                 /* Unlink current input */
330                 p_input = p_playlist->p_input;
331                 p_playlist->p_input = NULL;
332                 vlc_object_detach( p_input );
333
334                 /* Release the playlist lock, because we may get stuck
335                  * in input_DestroyThread() for some time. */
336                 vlc_mutex_unlock( &p_playlist->object_lock );
337
338                 /* Destroy input */
339                 input_DestroyThread( p_input );
340                 vlc_object_destroy( p_input );
341                 continue;
342             }
343             /* This input is dying, let him do */
344             else if( p_playlist->p_input->b_die )
345             {
346                 ;
347             }
348             /* This input has finished, ask him to die ! */
349             else if( p_playlist->p_input->b_error
350                       || p_playlist->p_input->b_eof )
351             {
352                 /* Select the next playlist item */
353                 SkipItem( p_playlist, 1 );
354
355                 /* Release the playlist lock, because we may get stuck
356                  * in input_StopThread() for some time. */
357                 vlc_mutex_unlock( &p_playlist->object_lock );
358                 input_StopThread( p_playlist->p_input );
359                 continue;
360             }
361         }
362         else if( p_playlist->i_status != PLAYLIST_STOPPED )
363         {
364             PlayItem( p_playlist );
365         }
366
367         vlc_mutex_unlock( &p_playlist->object_lock );
368
369         msleep( INTF_IDLE_SLEEP );
370     }
371
372     /* If there is an input, kill it */
373     while( 1 )
374     {
375         vlc_mutex_lock( &p_playlist->object_lock );
376
377         if( p_playlist->p_input == NULL )
378         {
379             vlc_mutex_unlock( &p_playlist->object_lock );
380             break;
381         }
382
383         if( p_playlist->p_input->b_dead )
384         {
385             input_thread_t *p_input;
386
387             /* Unlink current input */
388             p_input = p_playlist->p_input;
389             p_playlist->p_input = NULL;
390             vlc_object_detach( p_input );
391             vlc_mutex_unlock( &p_playlist->object_lock );
392
393             /* Destroy input */
394             input_DestroyThread( p_input );
395             vlc_object_destroy( p_input );
396             continue;
397         }
398         else if( p_playlist->p_input->b_die )
399         {
400             /* This input is dying, leave him alone */
401             ;
402         }
403         else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
404         {
405             vlc_mutex_unlock( &p_playlist->object_lock );
406             input_StopThread( p_playlist->p_input );
407             continue;
408         }
409         else
410         {
411             p_playlist->p_input->b_eof = 1;
412         }
413
414         vlc_mutex_unlock( &p_playlist->object_lock );
415
416         msleep( INTF_IDLE_SLEEP );
417     }
418 }
419
420 /*****************************************************************************
421  * SkipItem: go to Xth playlist item
422  *****************************************************************************
423  * This function calculates the position of the next playlist item, depending
424  * on the playlist course mode (forward, backward, random...).
425  *****************************************************************************/
426 static void SkipItem( playlist_t *p_playlist, int i_arg )
427 {
428     int i_oldindex = p_playlist->i_index;
429
430     /* If the playlist is empty, there is no current item */
431     if( p_playlist->i_size == 0 )
432     {
433         p_playlist->i_index = -1;
434         return;
435     }
436
437     /* Increment */
438     p_playlist->i_index += i_arg;
439
440     /* Boundary check */
441     if( p_playlist->i_index >= p_playlist->i_size )
442     {
443         if( p_playlist->i_status == PLAYLIST_STOPPED
444              || config_GetInt( p_playlist, "loop" ) )
445         {
446             p_playlist->i_index = 0;
447         }
448         else
449         {
450             /* Don't loop by default: stop at playlist end */
451             p_playlist->i_index = i_oldindex;
452             p_playlist->i_status = PLAYLIST_STOPPED;
453         }
454     }
455     else if( p_playlist->i_index < 0 )
456     {
457         p_playlist->i_index = p_playlist->i_size - 1;
458     }
459 }
460
461 /*****************************************************************************
462  * PlayItem: play current playlist item
463  *****************************************************************************
464  * This function calculates the position of the next playlist item, depending
465  * on the playlist course mode (forward, backward, random...).
466  *****************************************************************************/
467 static void PlayItem( playlist_t *p_playlist )
468 {
469     if( p_playlist->i_index == -1 )
470     {
471         if( p_playlist->i_size == 0 )
472         {
473             return;
474         }
475
476         SkipItem( p_playlist, 1 );
477     }
478
479     msg_Dbg( p_playlist, "creating new input thread" );
480     p_playlist->p_input = input_CreateThread( p_playlist,
481                 p_playlist->pp_items[p_playlist->i_index], NULL );
482 }
483
484 /*****************************************************************************
485  * Poubellize: put an input thread in the trashcan
486  *****************************************************************************
487  * XXX: unused
488  *****************************************************************************/
489 static void Poubellize ( playlist_t *p_playlist, input_thread_t *p_input )
490 {
491     
492 }
493