]> git.sesse.net Git - vlc/blob - src/playlist/control.c
Re-enable random.
[vlc] / src / playlist / control.c
1 /*****************************************************************************
2  * control.c : Handle control of the playlist & running through it
3  *****************************************************************************
4  * Copyright (C) 1999-2004 the VideoLAN team
5  * $Id: /local/vlc/0.8.6-playlist-vlm/src/playlist/playlist.c 13741 2006-03-21T19:29:39.792444Z zorglub  $
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *          ClĂ©ment Stenac <zorglub@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24 #include <vlc/vlc.h>
25 #include <vlc/input.h>
26 #include "vlc_playlist.h"
27 #include <assert.h>
28
29 /*****************************************************************************
30  * Local prototypes
31  *****************************************************************************/
32 int PlaylistVAControl( playlist_t * p_playlist, int i_query, va_list args );
33
34 void PreparseEnqueueItemSub( playlist_t *, playlist_item_t * );
35
36 playlist_item_t *playlist_RecursiveFindLast(playlist_t *p_playlist,
37                                             playlist_item_t *p_node );
38
39 /*****************************************************************************
40  * Playlist control
41  *****************************************************************************/
42
43 /**
44  * Do a playlist action. Should be entered without playlist lock
45  * \see playlist_Control
46  */
47 int playlist_LockControl( playlist_t * p_playlist, int i_query, ... )
48 {
49     va_list args;
50     int i_result;
51     va_start( args, i_query );
52     vlc_mutex_lock( &p_playlist->object_lock );
53     i_result = PlaylistVAControl( p_playlist, i_query, args );
54     va_end( args );
55     vlc_mutex_unlock( &p_playlist->object_lock );
56     return i_result;
57 }
58
59 /**
60  * Do a playlist action.
61  * If there is something in the playlist then you can do playlist actions.
62  * Should be entered with playlist lock. See include/vlc_playlist.h for
63  * possible queries
64  *
65  * \param p_playlist the playlist to do the command on
66  * \param i_query the command to do
67  * \param variable number of arguments
68  * \return VLC_SUCCESS or an error
69  */
70 int playlist_Control( playlist_t * p_playlist, int i_query, ... )
71 {
72     va_list args;
73     int i_result;
74     va_start( args, i_query );
75     i_result = PlaylistVAControl( p_playlist, i_query, args );
76     va_end( args );
77
78     return i_result;
79 }
80
81 int PlaylistVAControl( playlist_t * p_playlist, int i_query, va_list args )
82 {
83     playlist_item_t *p_item, *p_node;
84     vlc_value_t val;
85
86     if( p_playlist->i_size <= 0 )
87     {
88         return VLC_EGENERIC;
89     }
90
91     switch( i_query )
92     {
93     case PLAYLIST_STOP:
94         p_playlist->request.i_status = PLAYLIST_STOPPED;
95         p_playlist->request.b_request = VLC_TRUE;
96         p_playlist->request.p_item = NULL;
97         break;
98
99     // Node can be null, it will keep the same. Use with care ...
100     // Item null = take the first child of node
101     case PLAYLIST_VIEWPLAY:
102         p_playlist->b_reset_random = VLC_TRUE;
103         p_node = (playlist_item_t *)va_arg( args, playlist_item_t * );
104         p_item = (playlist_item_t *)va_arg( args, playlist_item_t * );
105         if ( p_node == NULL )
106         {
107             p_node = p_playlist->status.p_node;
108             assert( p_node );
109         }
110         p_playlist->request.i_status = PLAYLIST_RUNNING;
111         p_playlist->request.i_skip = 0;
112         p_playlist->request.b_request = VLC_TRUE;
113         p_playlist->request.p_node = p_node;
114         p_playlist->request.p_item = p_item;
115         break;
116
117     case PLAYLIST_PLAY:
118         if( p_playlist->p_input )
119         {
120             val.i_int = PLAYING_S;
121             var_Set( p_playlist->p_input, "state", val );
122             break;
123         }
124         else
125         {
126             p_playlist->request.i_status = PLAYLIST_RUNNING;
127             p_playlist->request.b_request = VLC_TRUE;
128             p_playlist->request.p_node = p_playlist->status.p_node;
129             p_playlist->request.p_item = p_playlist->status.p_item;
130             p_playlist->request.i_skip = 0;
131         }
132         break;
133
134     case PLAYLIST_AUTOPLAY:
135         // AUTOPLAY is an ugly hack for initial status.
136         // Hopefully it will disappear
137         p_playlist->status.i_status = PLAYLIST_RUNNING;
138         p_playlist->request.p_node = p_playlist->status.p_node;
139         p_playlist->request.b_request = VLC_FALSE;
140         break;
141
142     case PLAYLIST_PAUSE:
143         val.i_int = 0;
144         if( p_playlist->p_input )
145             var_Get( p_playlist->p_input, "state", &val );
146
147         if( val.i_int == PAUSE_S )
148         {
149             p_playlist->status.i_status = PLAYLIST_RUNNING;
150             if( p_playlist->p_input )
151             {
152                 val.i_int = PLAYING_S;
153                 var_Set( p_playlist->p_input, "state", val );
154             }
155         }
156         else
157         {
158             p_playlist->status.i_status = PLAYLIST_PAUSED;
159             if( p_playlist->p_input )
160             {
161                 val.i_int = PAUSE_S;
162                 var_Set( p_playlist->p_input, "state", val );
163             }
164         }
165         break;
166
167     case PLAYLIST_SKIP:
168         p_playlist->request.p_node = p_playlist->status.p_node;
169         p_playlist->request.p_item = p_playlist->status.p_item;
170         p_playlist->request.i_skip = (int) va_arg( args, int );
171         /* if already running, keep running */
172         if( p_playlist->status.i_status != PLAYLIST_STOPPED )
173             p_playlist->request.i_status = p_playlist->status.i_status;
174         p_playlist->request.b_request = VLC_TRUE;
175         break;
176
177     default:
178         msg_Err( p_playlist, "unknown playlist query" );
179         return VLC_EBADVAR;
180         break;
181     }
182
183     return VLC_SUCCESS;
184 }
185
186 /*****************************************************************************
187  * Preparse control
188  *****************************************************************************/
189 /** Enqueue an item for preparsing */
190 int playlist_PreparseEnqueue( playlist_t *p_playlist,
191                               input_item_t *p_item )
192 {
193     vlc_mutex_lock( &p_playlist->p_preparse->object_lock );
194     vlc_gc_incref( p_item );
195     INSERT_ELEM( p_playlist->p_preparse->pp_waiting,
196                  p_playlist->p_preparse->i_waiting,
197                  p_playlist->p_preparse->i_waiting,
198                  p_item );
199     vlc_mutex_unlock( &p_playlist->p_preparse->object_lock );
200     return VLC_SUCCESS;
201 }
202
203 /** Enqueue a playlist item or a node for peparsing.
204  *  This function should be entered without playlist and preparser locks */
205 int playlist_PreparseEnqueueItem( playlist_t *p_playlist,
206                                   playlist_item_t *p_item )
207 {
208     vlc_mutex_lock( &p_playlist->object_lock );
209     vlc_mutex_lock( &p_playlist->p_preparse->object_lock );
210     PreparseEnqueueItemSub( p_playlist, p_item );
211     vlc_mutex_unlock( &p_playlist->p_preparse->object_lock );
212     vlc_mutex_unlock( &p_playlist->object_lock );
213     return VLC_SUCCESS;
214 }
215
216 void PreparseEnqueueItemSub( playlist_t *p_playlist,
217                              playlist_item_t *p_item )
218 {
219     int i;
220     if( p_item->i_children == -1 )
221     {
222         vlc_gc_incref( p_item );
223         INSERT_ELEM( p_playlist->p_preparse->pp_waiting,
224                      p_playlist->p_preparse->i_waiting,
225                      p_playlist->p_preparse->i_waiting,
226                      p_item->p_input );
227     }
228     else
229     {
230         for( i = 0; i < p_item->i_children; i++)
231         {
232             PreparseEnqueueItemSub( p_playlist,
233                                              p_item->pp_children[i] );
234         }
235     }
236 }
237
238 /*****************************************************************************
239  * Playback logic
240  *****************************************************************************/
241
242 /** This function calculates the next playlist item, depending
243  *  on the playlist course mode (forward, backward, random, view,...). */
244 playlist_item_t * playlist_NextItem( playlist_t *p_playlist )
245 {
246     playlist_item_t *p_new = NULL;
247     int i_skip = 0, i;
248
249     vlc_bool_t b_loop = var_GetBool( p_playlist, "loop" );
250     vlc_bool_t b_random = var_GetBool( p_playlist, "random" );
251     vlc_bool_t b_repeat = var_GetBool( p_playlist, "repeat" );
252     vlc_bool_t b_playstop = var_GetBool( p_playlist, "play-and-stop" );
253
254     /* Handle quickly a few special cases */
255     /* No items to play */
256     if( p_playlist->i_size == 0 )
257     {
258         msg_Info( p_playlist, "playlist is empty" );
259         return NULL;
260     }
261
262     /* Repeat and play/stop */
263     if( !p_playlist->request.b_request && b_repeat == VLC_TRUE &&
264          p_playlist->status.p_item )
265     {
266         msg_Dbg( p_playlist,"repeating item" );
267         return p_playlist->status.p_item;
268     }
269     if( !p_playlist->request.b_request && b_playstop == VLC_TRUE )
270     {
271         msg_Dbg( p_playlist,"stopping (play and stop)");
272         return NULL;
273     }
274
275     if( !p_playlist->request.b_request && p_playlist->status.p_item &&
276          p_playlist->status.p_item->i_flags & PLAYLIST_SKIP_FLAG )
277     {
278         msg_Dbg( p_playlist, "blocking item, stopping") ;
279         return NULL;
280     }
281
282     /* Random case. This is an exception: if request, but request is skip +- 1
283      * we don't go to next item but select a new random one. */
284     if( b_random &&
285         ( !p_playlist->request.b_request ||
286         ( p_playlist->request.b_request &&
287             ( p_playlist->request.p_item == NULL ||
288               p_playlist->request.i_skip == 1    ||
289               p_playlist->request.i_skip == -1 ) ) ) )
290     {
291        PL_DEBUG( "doing random, have %i items, currently at %i, reset %i\n",
292                         p_playlist->i_random, p_playlist->i_random_index,
293                         p_playlist->b_reset_random );
294        if( p_playlist->b_reset_random )
295         {
296             int j;
297             FREE( p_playlist->pp_random );
298             if( !p_playlist->b_reset_random &&  !b_loop ) goto end;
299             p_playlist->i_random = 0;
300             p_playlist->i_random_index = 0;
301             p_playlist->i_random = playlist_GetAllEnabledChildren(
302                                                 p_playlist,
303                                                 p_playlist->status.p_node,
304                                                 &p_playlist->pp_random );
305             /* Shuffle the array */
306             srand( (unsigned int)mdate() );
307             int swap = 0;
308             for( j = p_playlist->i_random -1; j > 0; j-- )
309             {
310                 swap++;
311                 int i = rand() % (j+1); /* between 0 and j */
312                 playlist_item_t *p_tmp;
313                 p_tmp = p_playlist->pp_random[i];
314                 p_playlist->pp_random[i] = p_playlist->pp_random[j];
315                 p_playlist->pp_random[j] = p_tmp;
316             }
317             p_playlist->b_reset_random = VLC_FALSE;
318         }
319         else
320         {
321             /* Go backward or forward */
322             if( !p_playlist->request.b_request || !p_playlist->request.p_item ||
323                                                p_playlist->request.i_skip == 1 )
324                 p_playlist->i_random_index++;
325             else
326                 p_playlist->i_random_index--;
327             /* Handle bounds situations */
328             if( p_playlist->i_random_index == -1 )
329             {
330                 if( !b_loop || p_playlist->i_random == 0 ) goto end;
331                 p_playlist->i_random_index = p_playlist->i_random - 1;
332             }
333             else if( p_playlist->i_random_index == p_playlist->i_random )
334             {
335                 if( !b_loop || p_playlist->i_random == 0 ) goto end;
336                 p_playlist->i_random_index = 0;
337             }
338         }
339         PL_DEBUG( "using random item %i", p_playlist->i_random_index );
340         if ( p_playlist->i_random == 0 ) goto end; /* Can this happen ?? */
341         p_new = p_playlist->pp_random[p_playlist->i_random_index];
342 end:
343         if( !p_new ) p_playlist->b_reset_random = VLC_TRUE;
344         p_playlist->request.i_skip = 0;
345         p_playlist->request.b_request = VLC_FALSE;
346         return p_new;
347     }
348
349     /* Start the real work */
350     if( p_playlist->request.b_request )
351     {
352         PL_DEBUG( "processing request node %s item %s skip %i",
353                         PLI_NAME( p_playlist->request.p_item ),
354                         PLI_NAME( p_playlist->request.p_node ), i_skip );
355         p_new = p_playlist->request.p_item;
356         i_skip = p_playlist->request.i_skip;
357
358         if( p_playlist->request.p_node )
359             p_playlist->status.p_node = p_playlist->request.p_node;
360
361         /* If we are asked for a node, dont take it */
362         if( i_skip == 0 && ( p_new == NULL || p_new->i_children != -1 ) )
363             i_skip++;
364
365         if( i_skip > 0 )
366         {
367             for( i = i_skip; i > 0 ; i-- )
368             {
369                 p_new = playlist_GetNextLeaf( p_playlist,
370                                               p_playlist->request.p_node,
371                                               p_new, VLC_TRUE, VLC_FALSE );
372                 if( p_new == NULL )
373                 {
374                     PL_DEBUG( "looping - restarting at beginning of node" );
375                     p_new = playlist_GetNextLeaf( p_playlist,
376                                                   p_playlist->request.p_node,
377                                                   NULL, VLC_TRUE, VLC_FALSE);
378                     if( p_new == NULL ) break;
379                 }
380             }
381         }
382         else if( i_skip < 0 )
383         {
384             for( i = i_skip; i < 0 ; i++ )
385             {
386                 p_new = playlist_GetPrevLeaf( p_playlist,
387                                               p_playlist->request.p_node,
388                                               p_new, VLC_FALSE, VLC_FALSE );
389                 if( p_new == NULL )
390                 {
391                     PL_DEBUG( "looping - restarting at end of node" );
392                     /** \bug This is needed because GetPrevLeaf does not loop
393                       * by itself */
394                     p_new = playlist_GetLastLeaf( p_playlist,
395                                                  p_playlist->request.p_node );
396                 }
397                 if( p_new == NULL ) break;
398             }
399         }
400         /* Clear the request */
401         p_playlist->request.b_request = VLC_FALSE;
402     }
403     /* "Automatic" item change ( next ) */
404     else
405     {
406         PL_DEBUG( "changing item without a request" );
407         /* Cant go to next from current item */
408         if( p_playlist->status.p_item &&
409             p_playlist->status.p_item->i_flags & PLAYLIST_SKIP_FLAG )
410             return NULL;
411
412         p_new = playlist_GetNextLeaf( p_playlist,
413                                       p_playlist->status.p_node,
414                                       p_playlist->status.p_item,
415                                       VLC_TRUE, VLC_FALSE );
416         if( p_new == NULL && b_loop )
417         {
418             PL_DEBUG( "looping" );
419             p_new = playlist_GetNextLeaf( p_playlist,
420                                           p_playlist->status.p_node,
421                                           NULL, VLC_TRUE, VLC_FALSE );
422         }
423         /* The new item can't be autoselected  */
424         if( p_new != NULL && p_new->i_flags & PLAYLIST_SKIP_FLAG )
425             return NULL;
426     }
427     if( p_new == NULL )
428     {
429         msg_Dbg( p_playlist, "did not find something to play" );
430     }
431     return p_new;
432 }
433
434 /** Start the input for an item */
435 int playlist_PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
436 {
437     vlc_value_t val;
438     int i_activity = var_GetInteger( p_playlist, "activity") ;
439
440     msg_Dbg( p_playlist, "creating new input thread" );
441
442     p_item->p_input->i_nb_played++;
443     p_playlist->status.p_item = p_item;
444
445     p_playlist->status.i_status = PLAYLIST_RUNNING;
446
447     var_SetInteger( p_playlist, "activity", i_activity +
448                     DEFAULT_INPUT_ACTIVITY );
449     p_playlist->p_input = input_CreateThread( p_playlist, p_item->p_input );
450
451     val.i_int = p_item->p_input->i_id;
452     /* unlock the playlist to set the var...mmm */
453     vlc_mutex_unlock( &p_playlist->object_lock);
454     var_Set( p_playlist, "playlist-current", val);
455     vlc_mutex_lock( &p_playlist->object_lock);
456
457     return VLC_SUCCESS;
458 }