]> git.sesse.net Git - vlc/blob - src/playlist/control.c
Fixes for random + live search
[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             PL_DEBUG( "random rebuilt, have %i items", p_playlist->i_random );
319         }
320         else
321         {
322             /* Go backward or forward */
323             if( !p_playlist->request.b_request || !p_playlist->request.p_item ||
324                                                p_playlist->request.i_skip == 1 )
325                 p_playlist->i_random_index++;
326             else
327                 p_playlist->i_random_index--;
328             /* Handle bounds situations */
329             if( p_playlist->i_random_index == -1 )
330             {
331                 if( !b_loop || p_playlist->i_random == 0 ) goto end;
332                 p_playlist->i_random_index = p_playlist->i_random - 1;
333             }
334             else if( p_playlist->i_random_index == p_playlist->i_random )
335             {
336                 if( !b_loop || p_playlist->i_random == 0 ) goto end;
337                 p_playlist->i_random_index = 0;
338             }
339         }
340         PL_DEBUG( "using random item %i", p_playlist->i_random_index );
341         if ( p_playlist->i_random == 0 ) goto end; /* Can this happen ?? */
342         p_new = p_playlist->pp_random[p_playlist->i_random_index];
343 end:
344         if( !p_new ) p_playlist->b_reset_random = VLC_TRUE;
345         p_playlist->request.i_skip = 0;
346         p_playlist->request.b_request = VLC_FALSE;
347         return p_new;
348     }
349
350     /* Start the real work */
351     if( p_playlist->request.b_request )
352     {
353         PL_DEBUG( "processing request node %s item %s skip %i",
354                         PLI_NAME( p_playlist->request.p_item ),
355                         PLI_NAME( p_playlist->request.p_node ), i_skip );
356         p_new = p_playlist->request.p_item;
357         i_skip = p_playlist->request.i_skip;
358
359         if( p_playlist->request.p_node )
360             p_playlist->status.p_node = p_playlist->request.p_node;
361
362         /* If we are asked for a node, dont take it */
363         if( i_skip == 0 && ( p_new == NULL || p_new->i_children != -1 ) )
364             i_skip++;
365
366         if( i_skip > 0 )
367         {
368             for( i = i_skip; i > 0 ; i-- )
369             {
370                 p_new = playlist_GetNextLeaf( p_playlist,
371                                               p_playlist->request.p_node,
372                                               p_new, VLC_TRUE, VLC_FALSE );
373                 if( p_new == NULL )
374                 {
375                     PL_DEBUG( "looping - restarting at beginning of node" );
376                     p_new = playlist_GetNextLeaf( p_playlist,
377                                                   p_playlist->request.p_node,
378                                                   NULL, VLC_TRUE, VLC_FALSE);
379                     if( p_new == NULL ) break;
380                 }
381             }
382         }
383         else if( i_skip < 0 )
384         {
385             for( i = i_skip; i < 0 ; i++ )
386             {
387                 p_new = playlist_GetPrevLeaf( p_playlist,
388                                               p_playlist->request.p_node,
389                                               p_new, VLC_FALSE, VLC_FALSE );
390                 if( p_new == NULL )
391                 {
392                     PL_DEBUG( "looping - restarting at end of node" );
393                     /** \bug This is needed because GetPrevLeaf does not loop
394                       * by itself */
395                     p_new = playlist_GetLastLeaf( p_playlist,
396                                                  p_playlist->request.p_node );
397                 }
398                 if( p_new == NULL ) break;
399             }
400         }
401         /* Clear the request */
402         p_playlist->request.b_request = VLC_FALSE;
403     }
404     /* "Automatic" item change ( next ) */
405     else
406     {
407         PL_DEBUG( "changing item without a request" );
408         /* Cant go to next from current item */
409         if( p_playlist->status.p_item &&
410             p_playlist->status.p_item->i_flags & PLAYLIST_SKIP_FLAG )
411             return NULL;
412
413         p_new = playlist_GetNextLeaf( p_playlist,
414                                       p_playlist->status.p_node,
415                                       p_playlist->status.p_item,
416                                       VLC_TRUE, VLC_FALSE );
417         if( p_new == NULL && b_loop )
418         {
419             PL_DEBUG( "looping" );
420             p_new = playlist_GetNextLeaf( p_playlist,
421                                           p_playlist->status.p_node,
422                                           NULL, VLC_TRUE, VLC_FALSE );
423         }
424         /* The new item can't be autoselected  */
425         if( p_new != NULL && p_new->i_flags & PLAYLIST_SKIP_FLAG )
426             return NULL;
427     }
428     if( p_new == NULL )
429     {
430         msg_Dbg( p_playlist, "did not find something to play" );
431     }
432     return p_new;
433 }
434
435 /** Start the input for an item */
436 int playlist_PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
437 {
438     vlc_value_t val;
439     int i_activity = var_GetInteger( p_playlist, "activity") ;
440
441     msg_Dbg( p_playlist, "creating new input thread" );
442
443     p_item->p_input->i_nb_played++;
444     p_playlist->status.p_item = p_item;
445
446     p_playlist->status.i_status = PLAYLIST_RUNNING;
447
448     var_SetInteger( p_playlist, "activity", i_activity +
449                     DEFAULT_INPUT_ACTIVITY );
450     p_playlist->p_input = input_CreateThread( p_playlist, p_item->p_input );
451
452     val.i_int = p_item->p_input->i_id;
453     /* unlock the playlist to set the var...mmm */
454     vlc_mutex_unlock( &p_playlist->object_lock);
455     var_Set( p_playlist, "playlist-current", val);
456     vlc_mutex_lock( &p_playlist->object_lock);
457
458     return VLC_SUCCESS;
459 }