* ./src/playlist/playlist.c: lots of playlist behaviour enhancements.
[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.6 2002/06/07 14:30:41 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 "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     vlc_mutex_lock( &p_playlist->object_lock );
111
112     /* Create the new playlist item */
113     p_item = malloc( sizeof( playlist_item_t ) );
114     if( p_item == NULL )
115     {
116         msg_Err( p_playlist, "out of memory" );
117         vlc_mutex_unlock( &p_playlist->object_lock );
118     }
119
120     p_item->psz_name = strdup( psz_target );
121     p_item->i_type = 0;
122     p_item->i_status = 0;
123
124     /* Do a few boundary checks and allocate space for the item */
125     if( i_pos == PLAYLIST_END )
126     {
127         i_pos = p_playlist->i_size - 1;
128     }
129
130     if( !(i_mode & PLAYLIST_REPLACE)
131          || i_pos < 0 || i_pos >= p_playlist->i_size )
132     {
133         int i_index;
134
135         p_playlist->i_size++;
136         p_playlist->pp_items = realloc( p_playlist->pp_items,
137                                         p_playlist->i_size * sizeof(void*) );
138         if( p_playlist->pp_items == NULL )
139         {
140             msg_Err( p_playlist, "out of memory" );
141             free( p_item->psz_name );
142             free( p_item );
143             vlc_mutex_unlock( &p_playlist->object_lock );
144             return -1;
145         }
146
147         /* Additional boundary checks */
148         if( i_mode & PLAYLIST_APPEND )
149         {
150             i_pos++;
151         }
152
153         if( i_pos < 0 )
154         {
155             i_pos = 0;
156         }
157         else if( i_pos > p_playlist->i_size - 1 )
158         {
159             i_pos = p_playlist->i_size - 1;
160         }
161
162         /* Now we know exactly where it goes. Just renumber the playlist */
163         for( i_index = p_playlist->i_size - 2; i_index > i_pos ; i_index-- )
164         {
165             p_playlist->pp_items[i_index + 1] = p_playlist->pp_items[i_index];
166         }
167
168         if( p_playlist->i_index >= i_pos )
169         {
170             i_index++;
171         }
172     }
173     else
174     {
175         /* i_mode == PLAYLIST_REPLACE and 0 <= i_pos < p_playlist->i_size */
176         free( p_playlist->pp_items[i_pos]->psz_name );
177         free( p_playlist->pp_items[i_pos] );
178         /* XXX: what if the item is still in use? */
179     }
180
181     p_playlist->pp_items[i_pos] = p_item;
182     p_playlist->i_status = PLAYLIST_RUNNING;
183
184     vlc_mutex_unlock( &p_playlist->object_lock );
185
186     return 0;
187 }
188
189 /*****************************************************************************
190  * playlist_Delete: delete an item from the playlist
191  *****************************************************************************
192  * Delete the item in the playlist with position i_pos.
193  *****************************************************************************/
194 int playlist_Delete( playlist_t * p_playlist, int i_pos )
195 {
196     vlc_mutex_lock( &p_playlist->object_lock );
197
198     vlc_mutex_unlock( &p_playlist->object_lock );
199
200     return 0;
201 }
202
203 /*****************************************************************************
204  * playlist_Command: do a playlist action
205  *****************************************************************************
206  *
207  *****************************************************************************/
208 void playlist_Command( playlist_t * p_playlist, int i_command, int i_arg )
209 {
210     vlc_mutex_lock( &p_playlist->object_lock );
211
212     switch( i_command )
213     {
214     case PLAYLIST_STOP:
215         p_playlist->i_status = PLAYLIST_STOPPED;
216         if( p_playlist->p_input )
217         {
218             input_StopThread( p_playlist->p_input );
219         }
220         break;
221
222     case PLAYLIST_PLAY:
223         p_playlist->i_status = PLAYLIST_RUNNING;
224         break;
225
226     case PLAYLIST_SKIP:
227         p_playlist->i_status = PLAYLIST_STOPPED;
228         SkipItem( p_playlist, i_arg );
229         if( p_playlist->p_input )
230         {
231             input_StopThread( p_playlist->p_input );
232         }
233         p_playlist->i_status = PLAYLIST_RUNNING;
234         break;
235
236     case PLAYLIST_GOTO:
237         if( i_arg >= 0 && i_arg < p_playlist->i_size )
238         {
239             p_playlist->i_index = i_arg;
240             if( p_playlist->p_input )
241             {
242                 input_StopThread( p_playlist->p_input );
243             }
244             p_playlist->i_status = PLAYLIST_RUNNING;
245         }
246         break;
247
248     default:
249         msg_Err( p_playlist, "unknown playlist command" );
250         break;
251     }
252
253     vlc_mutex_unlock( &p_playlist->object_lock );
254
255     return;
256 }
257
258 /* Following functions are local */
259
260 /*****************************************************************************
261  * RunThread: main playlist thread
262  *****************************************************************************/
263 static void RunThread ( playlist_t *p_playlist )
264 {
265     /* Tell above that we're ready */
266     vlc_thread_ready( p_playlist );
267
268     while( !p_playlist->b_die )
269     {
270         vlc_mutex_lock( &p_playlist->object_lock );
271
272         /* If there is an input, check that it doesn't need to die. */
273         if( p_playlist->p_input )
274         {
275             /* This input is dead. Remove it ! */
276             if( p_playlist->p_input->b_dead )
277             {
278                 input_thread_t *p_input;
279
280                 /* Unlink current input */
281                 p_input = p_playlist->p_input;
282                 p_playlist->p_input = NULL;
283                 vlc_object_detach_all( p_input );
284
285                 /* Release the playlist lock, because we may get stuck
286                  * in input_DestroyThread() for some time. */
287                 vlc_mutex_unlock( &p_playlist->object_lock );
288
289                 /* Destroy input */
290                 input_DestroyThread( p_input );
291                 vlc_object_destroy( p_input );
292                 continue;
293             }
294             /* This input is dying, let him do */
295             else if( p_playlist->p_input->b_die )
296             {
297                 ;
298             }
299             /* This input has finished, ask him to die ! */
300             else if( p_playlist->p_input->b_error
301                       || p_playlist->p_input->b_eof )
302             {
303                 /* Select the next playlist item */
304                 SkipItem( p_playlist, 1 );
305
306                 /* Release the playlist lock, because we may get stuck
307                  * in input_StopThread() for some time. */
308                 vlc_mutex_unlock( &p_playlist->object_lock );
309                 input_StopThread( p_playlist->p_input );
310                 continue;
311             }
312         }
313         else if( p_playlist->i_status != PLAYLIST_STOPPED )
314         {
315             PlayItem( p_playlist );
316         }
317
318         vlc_mutex_unlock( &p_playlist->object_lock );
319
320         msleep( INTF_IDLE_SLEEP );
321     }
322
323     /* If there is an input, kill it */
324     while( 1 )
325     {
326         vlc_mutex_lock( &p_playlist->object_lock );
327
328         if( p_playlist->p_input == NULL )
329         {
330             vlc_mutex_unlock( &p_playlist->object_lock );
331             break;
332         }
333
334         if( p_playlist->p_input->b_dead )
335         {
336             input_thread_t *p_input;
337
338             /* Unlink current input */
339             p_input = p_playlist->p_input;
340             p_playlist->p_input = NULL;
341             vlc_object_detach_all( p_input );
342             vlc_mutex_unlock( &p_playlist->object_lock );
343
344             /* Destroy input */
345             input_DestroyThread( p_input );
346             vlc_object_destroy( p_input );
347             continue;
348         }
349         else if( p_playlist->p_input->b_die )
350         {
351             /* This input is dying, leave him alone */
352             ;
353         }
354         else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
355         {
356             vlc_mutex_unlock( &p_playlist->object_lock );
357             input_StopThread( p_playlist->p_input );
358             continue;
359         }
360         else
361         {
362             p_playlist->p_input->b_eof = 1;
363         }
364
365         vlc_mutex_unlock( &p_playlist->object_lock );
366
367         msleep( INTF_IDLE_SLEEP );
368     }
369 }
370
371 /*****************************************************************************
372  * SkipItem: go to Xth playlist item
373  *****************************************************************************
374  * This function calculates the position of the next playlist item, depending
375  * on the playlist course mode (forward, backward, random...).
376  *****************************************************************************/
377 static void SkipItem( playlist_t *p_playlist, int i_arg )
378 {
379     int i_oldindex = p_playlist->i_index;
380
381     /* If the playlist is empty, there is no current item */
382     if( p_playlist->i_size == 0 )
383     {
384         p_playlist->i_index = -1;
385         return;
386     }
387
388     /* Increment */
389     p_playlist->i_index += i_arg;
390
391     /* Boundary check */
392     if( p_playlist->i_index >= p_playlist->i_size )
393     {
394         if( p_playlist->i_status == PLAYLIST_STOPPED
395              || config_GetInt( p_playlist, "loop" ) )
396         {
397             p_playlist->i_index = 0;
398         }
399         else
400         {
401             /* Don't loop by default: stop at playlist end */
402             p_playlist->i_index = i_oldindex;
403             p_playlist->i_status = PLAYLIST_STOPPED;
404         }
405     }
406     else if( p_playlist->i_index < 0 )
407     {
408         p_playlist->i_index = p_playlist->i_size - 1;
409     }
410 }
411
412 /*****************************************************************************
413  * PlayItem: play current playlist item
414  *****************************************************************************
415  * This function calculates the position of the next playlist item, depending
416  * on the playlist course mode (forward, backward, random...).
417  *****************************************************************************/
418 static void PlayItem( playlist_t *p_playlist )
419 {
420     if( p_playlist->i_index == -1 )
421     {
422         if( p_playlist->i_size == 0 )
423         {
424             return;
425         }
426
427         SkipItem( p_playlist, 1 );
428     }
429
430     msg_Dbg( p_playlist, "creating new input thread" );
431     p_playlist->p_input = input_CreateThread( p_playlist,
432                 p_playlist->pp_items[p_playlist->i_index], NULL );
433 }
434
435 /*****************************************************************************
436  * Poubellize: put an input thread in the trashcan
437  *****************************************************************************
438  * XXX: unused
439  *****************************************************************************/
440 static void Poubellize ( playlist_t *p_playlist, input_thread_t *p_input )
441 {
442     
443 }
444