]> git.sesse.net Git - vlc/blob - lib/event.c
lib: add helper for playlist startup
[vlc] / lib / event.c
1 /*****************************************************************************
2  * event.c: New libvlc event control API
3  *****************************************************************************
4  * Copyright (C) 2007-2010 VLC authors and VideoLAN
5  * $Id $
6  *
7  * Authors: Filippo Carone <filippo@carone.org>
8  *          Pierre d'Herbemont <pdherbemont # videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #define  LIBVLC_EVENT_TYPES_KEEP_DEFINE
30 #include <vlc/libvlc.h>
31
32 #include "libvlc_internal.h"
33 #include "event_internal.h"
34 #include <assert.h>
35 #include <errno.h>
36
37 typedef struct libvlc_event_listeners_group_t
38 {
39     libvlc_event_type_t event_type;
40     vlc_array_t listeners;
41     bool b_sublistener_removed;
42 } libvlc_event_listeners_group_t;
43
44 /*
45  * Private functions
46  */
47
48 static bool
49 group_contains_listener( libvlc_event_listeners_group_t * group,
50                          libvlc_event_listener_t * searched_listener )
51 {
52     int i;
53     for( i = 0; i < vlc_array_count(&group->listeners); i++ )
54     {
55         if( listeners_are_equal(searched_listener, vlc_array_item_at_index(&group->listeners, i)) )
56             return true;
57     }
58     return false;
59 }
60
61 /*
62  * Internal libvlc functions
63  */
64
65 /**************************************************************************
66  *       libvlc_event_manager_new (internal) :
67  *
68  * Init an object's event manager.
69  **************************************************************************/
70 libvlc_event_manager_t *
71 libvlc_event_manager_new( void * p_obj, libvlc_instance_t * p_libvlc_inst )
72 {
73     libvlc_event_manager_t * p_em;
74
75     p_em = malloc(sizeof( libvlc_event_manager_t ));
76     if( !p_em )
77     {
78         libvlc_printerr( "Not enough memory" );
79         return NULL;
80     }
81
82     p_em->p_obj = p_obj;
83     p_em->p_obj = p_obj;
84     p_em->async_event_queue = NULL;
85     p_em->p_libvlc_instance = p_libvlc_inst;
86
87     libvlc_retain( p_libvlc_inst );
88     vlc_array_init( &p_em->listeners_groups );
89     vlc_mutex_init( &p_em->object_lock );
90     vlc_mutex_init_recursive( &p_em->event_sending_lock );
91     return p_em;
92 }
93
94 /**************************************************************************
95  *       libvlc_event_manager_release (internal) :
96  *
97  * Release an object's event manager.
98  **************************************************************************/
99 void libvlc_event_manager_release( libvlc_event_manager_t * p_em )
100 {
101     libvlc_event_listeners_group_t * p_lg;
102     int i,j ;
103
104     libvlc_event_async_fini(p_em);
105
106     vlc_mutex_destroy( &p_em->event_sending_lock );
107     vlc_mutex_destroy( &p_em->object_lock );
108
109     for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
110     {
111         p_lg = vlc_array_item_at_index( &p_em->listeners_groups, i );
112
113         for( j = 0; j < vlc_array_count(&p_lg->listeners); j++)
114             free( vlc_array_item_at_index( &p_lg->listeners, j ) );
115
116         vlc_array_clear( &p_lg->listeners );
117         free( p_lg );
118     }
119     vlc_array_clear( &p_em->listeners_groups );
120     libvlc_release( p_em->p_libvlc_instance );
121     free( p_em );
122 }
123
124 /**************************************************************************
125  *       libvlc_event_manager_register_event_type (internal) :
126  *
127  * Init an object's event manager.
128  **************************************************************************/
129 void libvlc_event_manager_register_event_type(
130         libvlc_event_manager_t * p_em,
131         libvlc_event_type_t event_type )
132 {
133     libvlc_event_listeners_group_t * listeners_group;
134     listeners_group = xmalloc(sizeof(libvlc_event_listeners_group_t));
135     listeners_group->event_type = event_type;
136     vlc_array_init( &listeners_group->listeners );
137
138     vlc_mutex_lock( &p_em->object_lock );
139     vlc_array_append( &p_em->listeners_groups, listeners_group );
140     vlc_mutex_unlock( &p_em->object_lock );
141 }
142
143 /**************************************************************************
144  *       libvlc_event_send (internal) :
145  *
146  * Send a callback.
147  **************************************************************************/
148 void libvlc_event_send( libvlc_event_manager_t * p_em,
149                         libvlc_event_t * p_event )
150 {
151     libvlc_event_listeners_group_t * listeners_group = NULL;
152     libvlc_event_listener_t * listener_cached;
153     libvlc_event_listener_t * listener;
154     libvlc_event_listener_t * array_listeners_cached = NULL;
155     int i, i_cached_listeners = 0;
156
157     /* Fill event with the sending object now */
158     p_event->p_obj = p_em->p_obj;
159
160     vlc_mutex_lock( &p_em->event_sending_lock );
161     vlc_mutex_lock( &p_em->object_lock );
162     for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
163     {
164         listeners_group = vlc_array_item_at_index(&p_em->listeners_groups, i);
165         if( listeners_group->event_type == p_event->type )
166         {
167             if( vlc_array_count( &listeners_group->listeners ) <= 0 )
168                 break;
169
170             /* Cache a copy of the listener to avoid locking issues,
171              * and allow that edition of listeners during callbacks will garantee immediate effect. */
172             i_cached_listeners = vlc_array_count(&listeners_group->listeners);
173             array_listeners_cached = malloc(sizeof(libvlc_event_listener_t)*(i_cached_listeners));
174             if( !array_listeners_cached )
175             {
176                 vlc_mutex_unlock( &p_em->object_lock );
177                 vlc_mutex_unlock( &p_em->event_sending_lock );
178                 fprintf(stderr, "Can't alloc memory in libvlc_event_send" );
179                 return;
180             }
181
182             listener_cached = array_listeners_cached;
183             for( i = 0; i < vlc_array_count(&listeners_group->listeners); i++)
184             {
185                 listener = vlc_array_item_at_index(&listeners_group->listeners, i);
186                 memcpy( listener_cached, listener, sizeof(libvlc_event_listener_t) );
187                 listener_cached++;
188             }
189             break;
190         }
191     }
192
193     if( !listeners_group )
194     {
195         free( array_listeners_cached );
196         vlc_mutex_unlock( &p_em->object_lock );
197         vlc_mutex_unlock( &p_em->event_sending_lock );
198         return;
199     }
200
201     /* Track item removed from *this* thread, with a simple flag. Indeed
202      * event_sending_lock is a recursive lock. This has the advantage of
203      * allowing to remove an event listener from within a callback */
204     listeners_group->b_sublistener_removed = false;
205
206     vlc_mutex_unlock( &p_em->object_lock );
207
208     listener_cached = array_listeners_cached;
209     for( i = 0; i < i_cached_listeners; i++ )
210     {
211         if(listener_cached->is_asynchronous)
212         {
213             /* The listener wants not to block the emitter during event callback */
214             libvlc_event_async_dispatch(p_em, listener_cached, p_event);
215         }
216         else
217         {
218             /* The listener wants to block the emitter during event callback */
219
220             listener_cached->pf_callback( p_event, listener_cached->p_user_data );
221             listener_cached++;
222
223             if( listeners_group->b_sublistener_removed )
224             {
225                 /* If a callback was removed, this gets called */
226                 bool valid_listener;
227                 vlc_mutex_lock( &p_em->object_lock );
228                 valid_listener = group_contains_listener( listeners_group, listener_cached );
229                 vlc_mutex_unlock( &p_em->object_lock );
230                 if( !valid_listener )
231                 {
232                     listener_cached++;
233                     continue;
234                 }
235             }
236         }
237     }
238     vlc_mutex_unlock( &p_em->event_sending_lock );
239
240     free( array_listeners_cached );
241 }
242
243 /*
244  * Public libvlc functions
245  */
246
247 #define DEF( a ) { libvlc_##a, #a, },
248
249 typedef struct
250 {
251     int type;
252     const char name[40];
253 } event_name_t;
254
255 static const event_name_t event_list[] = {
256     DEF(MediaMetaChanged)
257     DEF(MediaSubItemAdded)
258     DEF(MediaDurationChanged)
259     DEF(MediaParsedChanged)
260     DEF(MediaFreed)
261     DEF(MediaStateChanged)
262     DEF(MediaSubItemTreeAdded)
263
264     DEF(MediaPlayerMediaChanged)
265     DEF(MediaPlayerNothingSpecial)
266     DEF(MediaPlayerOpening)
267     DEF(MediaPlayerBuffering)
268     DEF(MediaPlayerPlaying)
269     DEF(MediaPlayerPaused)
270     DEF(MediaPlayerStopped)
271     DEF(MediaPlayerForward)
272     DEF(MediaPlayerBackward)
273     DEF(MediaPlayerEndReached)
274     DEF(MediaPlayerEncounteredError)
275     DEF(MediaPlayerTimeChanged)
276     DEF(MediaPlayerPositionChanged)
277     DEF(MediaPlayerSeekableChanged)
278     DEF(MediaPlayerPausableChanged)
279     DEF(MediaPlayerTitleChanged)
280     DEF(MediaPlayerSnapshotTaken)
281     DEF(MediaPlayerLengthChanged)
282     DEF(MediaPlayerVout)
283
284     DEF(MediaListItemAdded)
285     DEF(MediaListWillAddItem)
286     DEF(MediaListItemDeleted)
287     DEF(MediaListWillDeleteItem)
288
289     DEF(MediaListViewItemAdded)
290     DEF(MediaListViewWillAddItem)
291     DEF(MediaListViewItemDeleted)
292     DEF(MediaListViewWillDeleteItem)
293
294     DEF(MediaListPlayerPlayed)
295     DEF(MediaListPlayerNextItemSet)
296     DEF(MediaListPlayerStopped)
297
298     DEF(MediaDiscovererStarted)
299     DEF(MediaDiscovererEnded)
300
301     DEF(VlmMediaAdded)
302     DEF(VlmMediaRemoved)
303     DEF(VlmMediaChanged)
304     DEF(VlmMediaInstanceStarted)
305     DEF(VlmMediaInstanceStopped)
306     DEF(VlmMediaInstanceStatusInit)
307     DEF(VlmMediaInstanceStatusOpening)
308     DEF(VlmMediaInstanceStatusPlaying)
309     DEF(VlmMediaInstanceStatusPause)
310     DEF(VlmMediaInstanceStatusEnd)
311     DEF(VlmMediaInstanceStatusError)
312 };
313 #undef DEF
314
315 static const char unknown_event_name[] = "Unknown Event";
316
317 static int evcmp( const void *a, const void *b )
318 {
319     return (*(const int *)a) - ((event_name_t *)b)->type;
320 }
321
322 const char * libvlc_event_type_name( int event_type )
323 {
324     const event_name_t *p;
325
326     p = bsearch( &event_type, event_list,
327                  sizeof(event_list)/sizeof(event_list[0]), sizeof(*p),
328                  evcmp );
329     return p ? p->name : unknown_event_name;
330 }
331
332 /**************************************************************************
333  *       event_attach (internal) :
334  *
335  * Add a callback for an event.
336  **************************************************************************/
337 static
338 int event_attach( libvlc_event_manager_t * p_event_manager,
339                   libvlc_event_type_t event_type,
340                   libvlc_callback_t pf_callback, void *p_user_data,
341                   bool is_asynchronous )
342 {
343     libvlc_event_listeners_group_t * listeners_group;
344     libvlc_event_listener_t * listener;
345     int i;
346
347     listener = malloc(sizeof(libvlc_event_listener_t));
348     if( unlikely(listener == NULL) )
349         return ENOMEM;
350
351     listener->event_type = event_type;
352     listener->p_user_data = p_user_data;
353     listener->pf_callback = pf_callback;
354     listener->is_asynchronous = is_asynchronous;
355
356     vlc_mutex_lock( &p_event_manager->object_lock );
357     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++ )
358     {
359         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
360         if( listeners_group->event_type == listener->event_type )
361         {
362             vlc_array_append( &listeners_group->listeners, listener );
363             vlc_mutex_unlock( &p_event_manager->object_lock );
364             return 0;
365         }
366     }
367     vlc_mutex_unlock( &p_event_manager->object_lock );
368
369     free(listener);
370     fprintf( stderr, "This object event manager doesn't know about '%s' events",
371              libvlc_event_type_name(event_type) );
372     assert(0);
373     return -1;
374 }
375
376 /**************************************************************************
377  *       libvlc_event_attach (public) :
378  *
379  * Add a callback for an event.
380  **************************************************************************/
381 int libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
382                          libvlc_event_type_t event_type,
383                          libvlc_callback_t pf_callback,
384                          void *p_user_data )
385 {
386     return event_attach(p_event_manager, event_type, pf_callback, p_user_data,
387                         false /* synchronous */);
388 }
389
390 /**************************************************************************
391  *       libvlc_event_attach (public) :
392  *
393  * Add a callback for an event.
394  **************************************************************************/
395 void libvlc_event_attach_async( libvlc_event_manager_t * p_event_manager,
396                          libvlc_event_type_t event_type,
397                          libvlc_callback_t pf_callback,
398                          void *p_user_data )
399 {
400     event_attach(p_event_manager, event_type, pf_callback, p_user_data,
401                  true /* asynchronous */);
402 }
403
404 /**************************************************************************
405  *       libvlc_event_detach (public) :
406  *
407  * Remove a callback for an event.
408  **************************************************************************/
409 void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
410                                      libvlc_event_type_t event_type,
411                                      libvlc_callback_t pf_callback,
412                                      void *p_user_data )
413 {
414     libvlc_event_listeners_group_t * listeners_group;
415     libvlc_event_listener_t * listener;
416     int i, j;
417     bool found = false;
418
419     vlc_mutex_lock( &p_event_manager->event_sending_lock );
420     vlc_mutex_lock( &p_event_manager->object_lock );
421     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++)
422     {
423         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
424         if( listeners_group->event_type == event_type )
425         {
426             for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
427             {
428                 listener = vlc_array_item_at_index(&listeners_group->listeners, j);
429                 if( listener->event_type == event_type &&
430                     listener->pf_callback == pf_callback &&
431                     listener->p_user_data == p_user_data )
432                 {
433                     /* that's our listener */
434
435                     /* Mark this group as edited so that libvlc_event_send
436                      * will recheck what listener to call */
437                     listeners_group->b_sublistener_removed = true;
438
439                     free( listener );
440                     vlc_array_remove( &listeners_group->listeners, j );
441                     found = true;
442                     break;
443                 }
444             }
445         }
446     }
447     vlc_mutex_unlock( &p_event_manager->object_lock );
448     vlc_mutex_unlock( &p_event_manager->event_sending_lock );
449
450     /* Now make sure any pending async event won't get fired after that point */
451     libvlc_event_listener_t listener_to_remove;
452     listener_to_remove.event_type  = event_type;
453     listener_to_remove.pf_callback = pf_callback;
454     listener_to_remove.p_user_data = p_user_data;
455     listener_to_remove.is_asynchronous = true;
456
457     libvlc_event_async_ensure_listener_removal(p_event_manager, &listener_to_remove);
458
459     assert(found);
460 }