]> git.sesse.net Git - vlc/blob - src/playlist/playlist.c
bd4270597306a72de55271e0959bc41f12efdbfc
[vlc] / src / playlist / playlist.c
1 /*****************************************************************************
2  * playlist.c : Playlist management functions
3  *****************************************************************************
4  * Copyright (C) 1999-2001 VideoLAN
5  * $Id: playlist.c,v 1.29 2002/12/07 22:15:10 gitan 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
27 #include <vlc/vlc.h>
28
29 #include "stream_control.h"
30 #include "input_ext-intf.h"
31
32 #include "vlc_playlist.h"
33
34 #define PLAYLIST_STOPPED 0
35 #define PLAYLIST_RUNNING 1
36
37 /*****************************************************************************
38  * Local prototypes
39  *****************************************************************************/
40 static void RunThread ( playlist_t * );
41 static void SkipItem  ( playlist_t *, int );
42 static void PlayItem  ( playlist_t * );
43
44 static void Poubellize ( playlist_t *, input_thread_t * );
45
46 /*****************************************************************************
47  * playlist_Create: create playlist
48  *****************************************************************************
49  * Create a playlist structure.
50  *****************************************************************************/
51 playlist_t * __playlist_Create ( vlc_object_t *p_parent )
52 {
53     playlist_t *p_playlist;
54
55     /* Allocate structure */
56     p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
57     if( !p_playlist )
58     {
59         msg_Err( p_parent, "out of memory" );
60         return NULL;
61     }
62
63     p_playlist->p_input = NULL;
64     p_playlist->i_status = PLAYLIST_STOPPED;
65     p_playlist->i_index = -1;
66     p_playlist->i_size = 0;
67     p_playlist->pp_items = NULL;
68
69     if( vlc_thread_create( p_playlist, "playlist", RunThread,
70                            VLC_THREAD_PRIORITY_LOW, 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     p_item = malloc( sizeof( playlist_item_t ) );
109     if( p_item == NULL )
110     {
111         msg_Err( p_playlist, "out of memory" );
112     }
113
114     p_item->psz_name = strdup( psz_target );
115     p_item->psz_uri  = strdup( psz_target );
116     p_item->i_type = 0;
117     p_item->i_status = 0;
118     p_item->b_autodeletion = VLC_FALSE;
119
120     return playlist_AddItem( p_playlist, p_item, i_mode, i_pos );
121 }
122
123
124 int playlist_AddItem( playlist_t *p_playlist, playlist_item_t * p_item,
125                 int i_mode, int i_pos)
126 {
127
128     vlc_mutex_lock( &p_playlist->object_lock );
129
130     /*
131      * CHECK_INSERT : checks if the item is already enqued before 
132      * enqueing it
133      */
134     if ( i_mode & PLAYLIST_CHECK_INSERT )
135     {
136          int j;
137        
138          if ( p_playlist->pp_items )
139          {
140              for ( j = 0; j < p_playlist->i_size; j++ )
141              {
142                  if ( !strcmp( p_playlist->pp_items[j]->psz_uri, p_item->psz_uri ) )
143                  {
144                       if( p_item->psz_name ) 
145                       {
146                           free( p_item->psz_name );
147                       }
148                       if( p_item->psz_uri )
149                       {
150                           free( p_item->psz_uri );
151                       }
152                       free( p_item );
153                       vlc_mutex_unlock( &p_playlist->object_lock );
154                       return 0;   
155                  }
156              }
157          }
158          i_mode &= ~PLAYLIST_CHECK_INSERT;
159          i_mode |= PLAYLIST_APPEND;
160     }
161
162     
163     msg_Dbg( p_playlist, "adding playlist item « %s »", p_item->psz_name );
164     
165     /* Create the new playlist item */
166
167
168     /* Do a few boundary checks and allocate space for the item */
169     if( i_pos == PLAYLIST_END )
170     {
171         if( i_mode & PLAYLIST_INSERT )
172         {
173             i_mode &= ~PLAYLIST_INSERT;
174             i_mode |= PLAYLIST_APPEND;
175         }
176
177         i_pos = p_playlist->i_size - 1;
178     }
179
180     if( !(i_mode & PLAYLIST_REPLACE)
181          || i_pos < 0 || i_pos >= p_playlist->i_size )
182     {
183         /* Additional boundary checks */
184         if( i_mode & PLAYLIST_APPEND )
185         {
186             i_pos++;
187         }
188
189         if( i_pos < 0 )
190         {
191             i_pos = 0;
192         }
193         else if( i_pos > p_playlist->i_size )
194         {
195             i_pos = p_playlist->i_size;
196         }
197
198         INSERT_ELEM( p_playlist->pp_items,
199                      p_playlist->i_size,
200                      i_pos,
201                      p_item );
202
203         if( p_playlist->i_index >= i_pos )
204         {
205             p_playlist->i_index++;
206         }
207     }
208     else
209     {
210         /* i_mode == PLAYLIST_REPLACE and 0 <= i_pos < p_playlist->i_size */
211         if( p_playlist->pp_items[i_pos]->psz_name )
212         {
213             free( p_playlist->pp_items[i_pos]->psz_name );
214         }
215         if( p_playlist->pp_items[i_pos]->psz_uri )
216         {
217             free( p_playlist->pp_items[i_pos]->psz_uri );
218         }
219         /* XXX: what if the item is still in use? */
220         free( p_playlist->pp_items[i_pos] );
221         p_playlist->pp_items[i_pos] = p_item;
222     }
223
224     if( i_mode & PLAYLIST_GO )
225     {
226         p_playlist->i_index = i_pos;
227         if( p_playlist->p_input )
228         {
229             input_StopThread( p_playlist->p_input );
230         }
231         p_playlist->i_status = PLAYLIST_RUNNING;
232     }
233
234     vlc_mutex_unlock( &p_playlist->object_lock );
235
236     return 0;
237 }
238
239 /*****************************************************************************
240  * playlist_Delete: delete an item from the playlist
241  *****************************************************************************
242  * Delete the item in the playlist with position i_pos.
243  *****************************************************************************/
244 int playlist_Delete( playlist_t * p_playlist, int i_pos )
245 {
246     vlc_mutex_lock( &p_playlist->object_lock );
247
248     if( i_pos >= 0 && i_pos < p_playlist->i_size )
249     {
250         msg_Dbg( p_playlist, "deleting playlist item « %s »",
251                              p_playlist->pp_items[i_pos]->psz_name );
252
253         if( p_playlist->pp_items[i_pos]->psz_name )
254         {
255             free( p_playlist->pp_items[i_pos]->psz_name );
256         }
257         if( p_playlist->pp_items[i_pos]->psz_uri )
258         {
259             free( p_playlist->pp_items[i_pos]->psz_uri );
260         }
261
262         /* XXX: what if the item is still in use? */
263         free( p_playlist->pp_items[i_pos] );
264
265         if( i_pos <= p_playlist->i_index )
266         {
267             p_playlist->i_index--;
268         }
269
270         /* Renumber the playlist */
271         REMOVE_ELEM( p_playlist->pp_items,
272                      p_playlist->i_size,
273                      i_pos );
274     }
275
276     vlc_mutex_unlock( &p_playlist->object_lock );
277
278     return 0;
279 }
280
281 /*****************************************************************************
282  * playlist_Command: do a playlist action
283  *****************************************************************************
284  *
285  *****************************************************************************/
286 void playlist_Command( playlist_t * p_playlist, int i_command, int i_arg )
287 {
288     vlc_mutex_lock( &p_playlist->object_lock );
289
290     switch( i_command )
291     {
292     case PLAYLIST_STOP:
293         p_playlist->i_status = PLAYLIST_STOPPED;
294         if( p_playlist->p_input )
295         {
296             input_StopThread( p_playlist->p_input );
297         }
298         break;
299
300     case PLAYLIST_PLAY:
301         p_playlist->i_status = PLAYLIST_RUNNING;
302         if ( p_playlist->p_input )
303         {
304             input_SetStatus( p_playlist->p_input, INPUT_STATUS_PLAY );
305         }
306         break;
307
308     case PLAYLIST_SKIP:
309         p_playlist->i_status = PLAYLIST_STOPPED;
310         SkipItem( p_playlist, i_arg );
311         if( p_playlist->p_input )
312         {
313             input_StopThread( p_playlist->p_input );
314         }
315         p_playlist->i_status = PLAYLIST_RUNNING;
316         break;
317
318     case PLAYLIST_GOTO:
319         if( i_arg >= 0 && i_arg < p_playlist->i_size )
320         {
321             p_playlist->i_index = i_arg;
322             if( p_playlist->p_input )
323             {
324                 input_StopThread( p_playlist->p_input );
325             }
326             p_playlist->i_status = PLAYLIST_RUNNING;
327         }
328         break;
329
330     default:
331         msg_Err( p_playlist, "unknown playlist command" );
332         break;
333     }
334
335     vlc_mutex_unlock( &p_playlist->object_lock );
336
337     return;
338 }
339
340 /* Following functions are local */
341
342 /*****************************************************************************
343  * RunThread: main playlist thread
344  *****************************************************************************/
345 static void RunThread ( playlist_t *p_playlist )
346 {
347     /* Tell above that we're ready */
348     vlc_thread_ready( p_playlist );
349
350     while( !p_playlist->b_die )
351     {
352         vlc_mutex_lock( &p_playlist->object_lock );
353
354         /* If there is an input, check that it doesn't need to die. */
355         if( p_playlist->p_input )
356         {
357             /* This input is dead. Remove it ! */
358             if( p_playlist->p_input->b_dead )
359             {
360                 input_thread_t *p_input;
361
362                 /* Unlink current input */
363                 p_input = p_playlist->p_input;
364                 p_playlist->p_input = NULL;
365                 vlc_object_detach( p_input );
366
367                 /* Release the playlist lock, because we may get stuck
368                  * in input_DestroyThread() for some time. */
369                 vlc_mutex_unlock( &p_playlist->object_lock );
370
371                 /* Destroy input */
372                 input_DestroyThread( p_input );
373                 vlc_object_destroy( p_input );
374                 continue;
375             }
376             /* This input is dying, let him do */
377             else if( p_playlist->p_input->b_die )
378             {
379                 ;
380             }
381             /* This input has finished, ask him to die ! */
382             else if( p_playlist->p_input->b_error
383                       || p_playlist->p_input->b_eof )
384             {
385                 /* Check for autodeletion */
386                 if( p_playlist->pp_items[p_playlist->i_index]->b_autodeletion )
387                 {
388                     vlc_mutex_unlock( &p_playlist->object_lock );
389                     playlist_Delete( p_playlist, p_playlist->i_index );
390                     vlc_mutex_lock( &p_playlist->object_lock );
391                 }
392
393                 /* Select the next playlist item */
394                 SkipItem( p_playlist, 1 );
395
396                 /* Release the playlist lock, because we may get stuck
397                  * in input_StopThread() for some time. */
398                 vlc_mutex_unlock( &p_playlist->object_lock );
399                 input_StopThread( p_playlist->p_input );
400                 continue;
401             }
402         }
403         else if( p_playlist->i_status != PLAYLIST_STOPPED )
404         {
405             PlayItem( p_playlist );
406         }
407
408         vlc_mutex_unlock( &p_playlist->object_lock );
409
410         msleep( INTF_IDLE_SLEEP );
411     }
412
413     /* If there is an input, kill it */
414     while( 1 )
415     {
416         vlc_mutex_lock( &p_playlist->object_lock );
417
418         if( p_playlist->p_input == NULL )
419         {
420             vlc_mutex_unlock( &p_playlist->object_lock );
421             break;
422         }
423
424         if( p_playlist->p_input->b_dead )
425         {
426             input_thread_t *p_input;
427
428             /* Unlink current input */
429             p_input = p_playlist->p_input;
430             p_playlist->p_input = NULL;
431             vlc_object_detach( p_input );
432             vlc_mutex_unlock( &p_playlist->object_lock );
433
434             /* Destroy input */
435             input_DestroyThread( p_input );
436             vlc_object_destroy( p_input );
437             continue;
438         }
439         else if( p_playlist->p_input->b_die )
440         {
441             /* This input is dying, leave him alone */
442             ;
443         }
444         else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
445         {
446             vlc_mutex_unlock( &p_playlist->object_lock );
447             input_StopThread( p_playlist->p_input );
448             continue;
449         }
450         else
451         {
452             p_playlist->p_input->b_eof = 1;
453         }
454
455         vlc_mutex_unlock( &p_playlist->object_lock );
456
457         msleep( INTF_IDLE_SLEEP );
458     }
459 }
460
461 /*****************************************************************************
462  * SkipItem: go to Xth 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 SkipItem( playlist_t *p_playlist, int i_arg )
468 {
469     int i_oldindex = p_playlist->i_index;
470     vlc_bool_t b_random;
471
472     /* If the playlist is empty, there is no current item */
473     if( p_playlist->i_size == 0 )
474     {
475         p_playlist->i_index = -1;
476         return;
477     }
478
479     b_random = config_GetInt( p_playlist, "random" );
480
481     /* Increment */
482     if( b_random )
483     {
484         srand( (unsigned int)mdate() );
485
486         /* Simple random stuff - we cheat a bit to minimize the chances to
487          * get the same index again. */
488         i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
489         if( i_arg == 0 )
490         {
491             i_arg = (int)((float)p_playlist->i_size * rand() / (RAND_MAX+1.0));
492         }
493     }
494
495     p_playlist->i_index += i_arg;
496
497     /* Boundary check */
498     if( p_playlist->i_index >= p_playlist->i_size )
499     {
500         if( p_playlist->i_status == PLAYLIST_STOPPED
501              || b_random
502              || config_GetInt( p_playlist, "loop" ) )
503         {
504             p_playlist->i_index -= p_playlist->i_size
505                          * ( p_playlist->i_index / p_playlist->i_size );
506         }
507         else
508         {
509             /* Don't loop by default: stop at playlist end */
510             p_playlist->i_index = i_oldindex;
511             p_playlist->i_status = PLAYLIST_STOPPED;
512         }
513     }
514     else if( p_playlist->i_index < 0 )
515     {
516         p_playlist->i_index = p_playlist->i_size - 1;
517     }
518 }
519
520 /*****************************************************************************
521  * PlayItem: play current playlist item
522  *****************************************************************************
523  * This function calculates the position of the next playlist item, depending
524  * on the playlist course mode (forward, backward, random...).
525  *****************************************************************************/
526 static void PlayItem( playlist_t *p_playlist )
527 {
528     if( p_playlist->i_index == -1 )
529     {
530         if( p_playlist->i_size == 0 )
531         {
532             return;
533         }
534
535         SkipItem( p_playlist, 1 );
536     }
537
538     msg_Dbg( p_playlist, "creating new input thread" );
539     p_playlist->p_input = input_CreateThread( p_playlist,
540                                   p_playlist->pp_items[p_playlist->i_index] );
541 }
542
543 /*****************************************************************************
544  * Poubellize: put an input thread in the trashcan
545  *****************************************************************************
546  * XXX: unused
547  *****************************************************************************/
548 static void Poubellize ( playlist_t *p_playlist, input_thread_t *p_input )
549 {
550     msg_Dbg( p_playlist, "poubellizing input %i\n", p_input->i_object_id );
551 }
552