]> git.sesse.net Git - vlc/blob - src/playlist/engine.c
Auto load and save media library (Closes:#433)
[vlc] / src / playlist / engine.c
1 /*****************************************************************************
2  * engine.c : Run the playlist and handle its control
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/vout.h>
26 #include <vlc/sout.h>
27 #include <vlc/input.h>
28 #include "vlc_playlist.h"
29 #include "vlc_interaction.h"
30
31 /*****************************************************************************
32  * Local prototypes
33  *****************************************************************************/
34 static void VariablesInit( playlist_t *p_playlist );
35
36 /**
37  * Create playlist
38  *
39  * Create a playlist structure.
40  * \param p_parent the vlc object that is to be the parent of this playlist
41  * \return a pointer to the created playlist, or NULL on error
42  */
43 playlist_t * playlist_Create( vlc_object_t *p_parent )
44 {
45     playlist_t *p_playlist;
46     int i_tree;
47
48     /* Allocate structure */
49     p_playlist = vlc_object_create( p_parent, VLC_OBJECT_PLAYLIST );
50     if( !p_playlist )
51     {
52         msg_Err( p_parent, "out of memory" );
53         return NULL;
54     }
55
56     VariablesInit( p_playlist );
57
58     /* Initialise data structures */
59     vlc_mutex_init( p_playlist, &p_playlist->gc_lock );
60     p_playlist->i_last_playlist_id = 0;
61     p_playlist->i_last_input_id = 0;
62     p_playlist->p_input = NULL;
63
64     p_playlist->i_vout_destroyed_date = 0;
65     p_playlist->i_sout_destroyed_date = 0;
66
67     p_playlist->i_size = 0;
68     p_playlist->pp_items = NULL;
69     p_playlist->i_all_size = 0;
70     p_playlist->pp_all_items = NULL;
71
72     p_playlist->i_input_items = 0;
73     p_playlist->pp_input_items = NULL;
74
75     p_playlist->i_random = 0;
76     p_playlist->pp_random = NULL;
77     p_playlist->i_random_index = 0;
78     p_playlist->b_reset_random = VLC_TRUE;
79
80     i_tree = var_CreateGetBool( p_playlist, "playlist-tree" );
81     p_playlist->b_always_tree = (i_tree == 1);
82     p_playlist->b_never_tree = (i_tree == 2);
83
84     p_playlist->b_doing_ml = VLC_FALSE;
85
86     p_playlist->p_root_category = playlist_NodeCreate( p_playlist, NULL, NULL);
87     p_playlist->p_root_onelevel = playlist_NodeCreate( p_playlist, NULL, NULL);
88
89     /* Create playlist and media library */
90     p_playlist->p_local_category = playlist_NodeCreate( p_playlist,
91                                  _( "Playlist" ),p_playlist->p_root_category );
92     p_playlist->p_local_onelevel =  playlist_NodeCreate( p_playlist,
93                                 _( "Playlist" ), p_playlist->p_root_onelevel );
94     p_playlist->p_local_category->i_flags |= PLAYLIST_RO_FLAG;
95     p_playlist->p_local_onelevel->i_flags |= PLAYLIST_RO_FLAG;
96
97     /* Link the nodes together. Todo: actually create them from the same input*/
98     p_playlist->p_local_onelevel->p_input->i_id =
99         p_playlist->p_local_category->p_input->i_id;
100
101     if( config_GetInt( p_playlist, "media-library") )
102     {
103         p_playlist->p_ml_category =   playlist_NodeCreate( p_playlist,
104                            _( "Media Library" ), p_playlist->p_root_category );
105         p_playlist->p_ml_onelevel =  playlist_NodeCreate( p_playlist,
106                            _( "Media Library" ), p_playlist->p_root_onelevel );
107         p_playlist->p_ml_category->i_flags |= PLAYLIST_RO_FLAG;
108         p_playlist->p_ml_onelevel->i_flags |= PLAYLIST_RO_FLAG;
109         p_playlist->p_ml_onelevel->p_input->i_id =
110              p_playlist->p_ml_category->p_input->i_id;
111
112     }
113     else
114     {
115         p_playlist->p_ml_category = p_playlist->p_ml_onelevel = NULL;
116     }
117
118     /* Initial status */
119     p_playlist->status.p_item = NULL;
120     p_playlist->status.p_node = p_playlist->p_local_onelevel;
121     p_playlist->request.b_request = VLC_FALSE;
122     p_playlist->status.i_status = PLAYLIST_STOPPED;
123
124     p_playlist->i_sort = SORT_ID;
125     p_playlist->i_order = ORDER_NORMAL;
126
127     vlc_object_attach( p_playlist, p_parent );
128
129     playlist_MLLoad( p_playlist );
130     return p_playlist;
131 }
132
133 void playlist_Destroy( playlist_t *p_playlist )
134 {
135     while( p_playlist->i_sds )
136     {
137         playlist_ServicesDiscoveryRemove( p_playlist,
138                                           p_playlist->pp_sds[0]->psz_module );
139     }
140
141     playlist_MLDump( p_playlist );
142
143     vlc_thread_join( p_playlist->p_preparse );
144     vlc_thread_join( p_playlist );
145
146     vlc_object_detach( p_playlist->p_preparse );
147
148     var_Destroy( p_playlist, "intf-change" );
149     var_Destroy( p_playlist, "item-change" );
150     var_Destroy( p_playlist, "playlist-current" );
151     var_Destroy( p_playlist, "intf-popmenu" );
152     var_Destroy( p_playlist, "intf-show" );
153     var_Destroy( p_playlist, "play-and-stop" );
154     var_Destroy( p_playlist, "play-and-exit" );
155     var_Destroy( p_playlist, "random" );
156     var_Destroy( p_playlist, "repeat" );
157     var_Destroy( p_playlist, "loop" );
158     var_Destroy( p_playlist, "activity" );
159
160     PL_LOCK;
161     playlist_NodeDelete( p_playlist, p_playlist->p_root_category, VLC_TRUE,
162                          VLC_TRUE );
163     playlist_NodeDelete( p_playlist, p_playlist->p_root_onelevel, VLC_TRUE,
164                          VLC_TRUE );
165     PL_UNLOCK;
166
167     if( p_playlist->p_stats )
168         free( p_playlist->p_stats );
169
170     vlc_mutex_destroy( &p_playlist->gc_lock );
171     vlc_object_destroy( p_playlist->p_preparse );
172     vlc_object_destroy( p_playlist );
173
174 }
175 /* Destroy remaining objects */
176 static mtime_t ObjectGarbageCollector( playlist_t *p_playlist, int i_type,
177                                        mtime_t destroy_date )
178 {
179     vlc_object_t *p_obj;
180
181     if( destroy_date > mdate() ) return destroy_date;
182
183     if( destroy_date == 0 )
184     {
185         /* give a little time */
186         return mdate() + I64C(1000000);
187     }
188     else
189     {
190         vlc_mutex_lock( &p_playlist->gc_lock );
191         while( ( p_obj = vlc_object_find( p_playlist, i_type, FIND_CHILD ) ) )
192         {
193             if( p_obj->p_parent != (vlc_object_t*)p_playlist )
194             {
195                 /* only first child (ie unused) */
196                 vlc_object_release( p_obj );
197                 break;
198             }
199             if( i_type == VLC_OBJECT_VOUT )
200             {
201                 msg_Dbg( p_playlist, "garbage collector destroying 1 vout" );
202                 vlc_object_detach( p_obj );
203                 vlc_object_release( p_obj );
204                 vout_Destroy( (vout_thread_t *)p_obj );
205             }
206             else if( i_type == VLC_OBJECT_SOUT )
207             {
208                 vlc_object_release( p_obj );
209                 sout_DeleteInstance( (sout_instance_t*)p_obj );
210             }
211         }
212         vlc_mutex_unlock( &p_playlist->gc_lock );
213         return 0;
214     }
215 }
216
217 /** Main loop for the playlist */
218 void playlist_MainLoop( playlist_t *p_playlist )
219 {
220     playlist_item_t *p_item = NULL;
221     vlc_bool_t b_playexit = var_GetBool( p_playlist, "play-and-exit" );
222     PL_LOCK
223
224     /* First, check if we have something to do */
225     /* FIXME : this can be called several times */
226     if( p_playlist->request.b_request )
227     {
228         /* Stop the existing input */
229         if( p_playlist->p_input && !p_playlist->p_input->b_die )
230         {
231             PL_DEBUG( "incoming request - stopping current input" );
232             input_StopThread( p_playlist->p_input );
233         }
234     }
235
236     /* If there is an input, check that it doesn't need to die. */
237     if( p_playlist->p_input )
238     {
239         /* This input is dead. Remove it ! */
240         if( p_playlist->p_input->b_dead )
241         {
242             int i_activity;
243             input_thread_t *p_input;
244             PL_DEBUG( "dead input" );
245
246             p_input = p_playlist->p_input;
247             p_playlist->p_input = NULL;
248
249             /* Release the playlist lock, because we may get stuck
250              * in input_DestroyThread() for some time. */
251             PL_UNLOCK
252
253             /* Destroy input */
254             input_DestroyThread( p_input );
255
256             /* Unlink current input
257              * (_after_ input_DestroyThread for vout garbage collector) */
258             vlc_object_detach( p_input );
259
260             /* Destroy object */
261             vlc_object_destroy( p_input );
262
263             p_playlist->i_vout_destroyed_date = 0;
264             p_playlist->i_sout_destroyed_date = 0;
265
266             if( p_playlist->status.p_item->i_flags
267                 & PLAYLIST_REMOVE_FLAG )
268             {
269                  PL_DEBUG( "%s was marked for deletion, deleting",
270                                  PLI_NAME( p_playlist->status.p_item  ) );
271                  playlist_ItemDelete( p_playlist->status.p_item );
272                  if( p_playlist->request.p_item == p_playlist->status.p_item )
273                      p_playlist->request.p_item = NULL;
274                  p_playlist->status.p_item = NULL;
275             }
276
277             i_activity= var_GetInteger( p_playlist, "activity") ;
278             var_SetInteger( p_playlist, "activity", i_activity -
279                             DEFAULT_INPUT_ACTIVITY );
280
281             return;
282         }
283         /* This input is dying, let it do */
284         else if( p_playlist->p_input->b_die )
285         {
286             PL_DEBUG( "dying input" );
287         }
288         /* This input has finished, ask it to die ! */
289         else if( p_playlist->p_input->b_error
290                   || p_playlist->p_input->b_eof )
291         {
292             PL_DEBUG( "finished input" );
293             input_StopThread( p_playlist->p_input );
294             /* Select the next playlist item */
295             PL_UNLOCK
296             return;
297         }
298         else if( p_playlist->p_input->i_state != INIT_S )
299         {
300             PL_UNLOCK;
301             p_playlist->i_vout_destroyed_date =
302                 ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT,
303                                         p_playlist->i_vout_destroyed_date );
304             p_playlist->i_sout_destroyed_date =
305                 ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT,
306                                         p_playlist->i_sout_destroyed_date );
307             PL_LOCK
308         }
309     }
310     else
311     {
312         /* No input. Several cases
313          *  - No request, running status -> start new item
314          *  - No request, stopped status -> collect garbage
315          *  - Request, running requested -> start new item
316          *  - Request, stopped requested -> collect garbage
317          */
318          if( (!p_playlist->request.b_request &&
319               p_playlist->status.i_status != PLAYLIST_STOPPED) ||
320               ( p_playlist->request.b_request &&
321                 p_playlist->request.i_status != PLAYLIST_STOPPED ) )
322          {
323              msg_Dbg( p_playlist, "starting new item" );
324              stats_TimerStart( p_playlist, "Playlist walk",
325                                   STATS_TIMER_PLAYLIST_WALK );
326              p_item = playlist_NextItem( p_playlist );
327              stats_TimerStop( p_playlist, STATS_TIMER_PLAYLIST_WALK );
328
329              if( p_item == NULL )
330              {
331                 msg_Dbg( p_playlist, "nothing to play" );
332                 if( b_playexit == VLC_TRUE )
333                 {
334                     msg_Info( p_playlist, "end of playlist, exiting" );
335                     p_playlist->p_vlc->b_die = VLC_TRUE;
336                 }
337                 p_playlist->status.i_status = PLAYLIST_STOPPED;
338                 PL_UNLOCK
339                 return;
340              }
341              playlist_PlayItem( p_playlist, p_item );
342          }
343          else
344          {
345              if( p_playlist->status.p_item &&
346                  p_playlist->status.p_item->i_flags & PLAYLIST_REMOVE_FLAG )
347              {
348                  PL_DEBUG( "deleting item marked for deletion" );
349                  playlist_ItemDelete( p_playlist->status.p_item );
350                  p_playlist->status.p_item = NULL;
351              }
352
353              /* Collect garbage */
354              PL_UNLOCK
355              p_playlist->i_sout_destroyed_date =
356              ObjectGarbageCollector( p_playlist, VLC_OBJECT_SOUT, mdate() );
357              p_playlist->i_vout_destroyed_date =
358              ObjectGarbageCollector( p_playlist, VLC_OBJECT_VOUT, mdate() );
359              PL_LOCK
360          }
361     }
362     PL_UNLOCK
363 }
364
365 /** Playlist dying last loop */
366 void playlist_LastLoop( playlist_t *p_playlist )
367 {
368     vlc_object_t *p_obj;
369
370     /* If there is an input, kill it */
371     while( 1 )
372     {
373         PL_LOCK
374
375         if( p_playlist->p_input == NULL )
376         {
377             PL_UNLOCK
378             break;
379         }
380
381         if( p_playlist->p_input->b_dead )
382         {
383             input_thread_t *p_input;
384
385             /* Unlink current input */
386             p_input = p_playlist->p_input;
387             p_playlist->p_input = NULL;
388             PL_UNLOCK
389
390             /* Destroy input */
391             input_DestroyThread( p_input );
392             /* Unlink current input (_after_ input_DestroyThread for vout
393              * garbage collector)*/
394             vlc_object_detach( p_input );
395
396             /* Destroy object */
397             vlc_object_destroy( p_input );
398             continue;
399         }
400         else if( p_playlist->p_input->b_die )
401         {
402             /* This input is dying, leave it alone */
403             ;
404         }
405         else if( p_playlist->p_input->b_error || p_playlist->p_input->b_eof )
406         {
407             input_StopThread( p_playlist->p_input );
408             PL_UNLOCK
409             continue;
410         }
411         else
412         {
413             p_playlist->p_input->b_eof = 1;
414         }
415
416         PL_UNLOCK
417
418         msleep( INTF_IDLE_SLEEP );
419     }
420
421     /* close all remaining sout */
422     while( ( p_obj = vlc_object_find( p_playlist,
423                                       VLC_OBJECT_SOUT, FIND_CHILD ) ) )
424     {
425         vlc_object_release( p_obj );
426         sout_DeleteInstance( (sout_instance_t*)p_obj );
427     }
428
429     /* close all remaining vout */
430     while( ( p_obj = vlc_object_find( p_playlist,
431                                       VLC_OBJECT_VOUT, FIND_CHILD ) ) )
432     {
433         vlc_object_detach( p_obj );
434         vlc_object_release( p_obj );
435         vout_Destroy( (vout_thread_t *)p_obj );
436     }
437 }
438
439 /** Main loop for preparser queue */
440 void playlist_PreparseLoop( playlist_preparse_t *p_obj )
441 {
442     playlist_t *p_playlist = (playlist_t *)p_obj->p_parent;
443     int i_activity;
444
445     vlc_mutex_lock( &p_obj->object_lock );
446
447     if( p_obj->i_waiting > 0 )
448     {
449         input_item_t *p_current = p_playlist->p_preparse->pp_waiting[0];
450         REMOVE_ELEM( p_obj->pp_waiting, p_obj->i_waiting, 0 );
451         vlc_mutex_unlock( &p_obj->object_lock );
452         vlc_mutex_lock( &p_playlist->object_lock );
453         if( p_current )
454         {
455             vlc_bool_t b_preparsed = VLC_FALSE;
456             if( strncmp( p_current->psz_uri, "http:", 5 ) &&
457                 strncmp( p_current->psz_uri, "rtsp:", 5 ) &&
458                 strncmp( p_current->psz_uri, "udp:", 4 ) &&
459                 strncmp( p_current->psz_uri, "mms:", 4 ) &&
460                 strncmp( p_current->psz_uri, "cdda:", 4 ) &&
461                 strncmp( p_current->psz_uri, "dvd:", 4 ) &&
462                 strncmp( p_current->psz_uri, "v4l:", 4 ) &&
463                 strncmp( p_current->psz_uri, "dshow:", 6 ) )
464             {
465                 b_preparsed = VLC_TRUE;
466                 stats_TimerStart( p_playlist, "Preparse run",
467                                   STATS_TIMER_PREPARSE );
468                 input_Preparse( p_playlist, p_current );
469                 stats_TimerStop( p_playlist, STATS_TIMER_PREPARSE );
470             }
471             vlc_mutex_unlock( &p_playlist->object_lock );
472             if( b_preparsed )
473             {
474                 var_SetInteger( p_playlist, "item-change",
475                                 p_current->i_id );
476             }
477             vlc_gc_decref( p_current );
478         }
479         else
480         {
481             vlc_mutex_unlock( &p_playlist->object_lock );
482         }
483         vlc_mutex_lock( &p_obj->object_lock );
484         i_activity=  var_GetInteger( p_playlist, "activity" );
485         if( i_activity < 0 ) i_activity = 0;
486         vlc_mutex_unlock( &p_obj->object_lock );
487         msleep( (i_activity+1) * 1000 );
488         return;
489     }
490     vlc_mutex_unlock( &p_obj->object_lock );
491 }
492
493 static void VariablesInit( playlist_t *p_playlist )
494 {
495     vlc_value_t val;
496     /* These variables control updates */
497     var_Create( p_playlist, "intf-change", VLC_VAR_BOOL );
498     val.b_bool = VLC_TRUE;
499     var_Set( p_playlist, "intf-change", val );
500
501     var_Create( p_playlist, "item-change", VLC_VAR_INTEGER );
502     val.i_int = -1;
503     var_Set( p_playlist, "item-change", val );
504
505     var_Create( p_playlist, "item-deleted", VLC_VAR_INTEGER );
506     val.i_int = -1;
507     var_Set( p_playlist, "item-deleted", val );
508
509     var_Create( p_playlist, "item-append", VLC_VAR_ADDRESS );
510
511     var_Create( p_playlist, "playlist-current", VLC_VAR_INTEGER );
512     val.i_int = -1;
513     var_Set( p_playlist, "playlist-current", val );
514
515     var_Create( p_playlist, "intf-popupmenu", VLC_VAR_BOOL );
516
517     var_Create( p_playlist, "intf-show", VLC_VAR_BOOL );
518     val.b_bool = VLC_TRUE;
519     var_Set( p_playlist, "intf-show", val );
520
521     var_Create( p_playlist, "activity", VLC_VAR_INTEGER );
522     var_SetInteger( p_playlist, "activity", 0 );
523
524     /* Variables to control playback */
525     var_CreateGetBool( p_playlist, "play-and-stop" );
526     var_CreateGetBool( p_playlist, "play-and-exit" );
527     var_CreateGetBool( p_playlist, "random" );
528     var_CreateGetBool( p_playlist, "repeat" );
529     var_CreateGetBool( p_playlist, "loop" );
530 }