]> git.sesse.net Git - vlc/blob - src/control/event.c
Rework error handling, with a fixed deallocation block at the end of Open().
[vlc] / src / control / event.c
1 /*****************************************************************************
2  * event.c: New libvlc event control API
3  *****************************************************************************
4  * Copyright (C) 2007-2010 the VideoLAN team
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
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 along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 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     /* Here a read/write lock would be nice */
161
162     vlc_mutex_lock( &p_em->object_lock );
163     for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
164     {
165         listeners_group = vlc_array_item_at_index(&p_em->listeners_groups, i);
166         if( listeners_group->event_type == p_event->type )
167         {
168             if( vlc_array_count( &listeners_group->listeners ) <= 0 )
169                 break;
170
171             /* Cache a copy of the listener to avoid locking issues,
172              * and allow that edition of listeners during callbacks will garantee immediate effect. */
173             i_cached_listeners = vlc_array_count(&listeners_group->listeners);
174             array_listeners_cached = malloc(sizeof(libvlc_event_listener_t)*(i_cached_listeners));
175             if( !array_listeners_cached )
176             {
177                 vlc_mutex_unlock( &p_em->object_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         return;
198     }
199
200     vlc_mutex_unlock( &p_em->object_lock );
201
202     vlc_mutex_lock( &p_em->event_sending_lock );
203     listener_cached = array_listeners_cached;
204     listeners_group->b_sublistener_removed = false;
205     for( i = 0; i < i_cached_listeners; i++ )
206     {
207         if(listener_cached->is_asynchronous)
208         {
209             /* The listener wants not to block the emitter during event callback */
210             libvlc_event_async_dispatch(p_em, listener_cached, p_event);
211         }
212         else
213         {
214             /* The listener wants to block the emitter during event callback */
215
216             listener_cached->pf_callback( p_event, listener_cached->p_user_data );
217             listener_cached++;
218
219             if( listeners_group->b_sublistener_removed )
220             {
221                 /* If a callback was removed, this gets called */
222                 bool valid_listener;
223                 vlc_mutex_lock( &p_em->object_lock );
224                 valid_listener = group_contains_listener( listeners_group, listener_cached );
225                 vlc_mutex_unlock( &p_em->object_lock );
226                 if( !valid_listener )
227                 {
228                     listener_cached++;
229                     continue;
230                 }
231             }
232         }
233     }
234     vlc_mutex_unlock( &p_em->event_sending_lock );
235
236     free( array_listeners_cached );
237 }
238
239 /*
240  * Public libvlc functions
241  */
242
243 #define DEF( a ) { libvlc_##a, #a, },
244
245 typedef struct
246 {
247     int type;
248     const char name[40];
249 } event_name_t;
250
251 static const event_name_t event_list[] = {
252     DEF(MediaMetaChanged)
253     DEF(MediaSubItemAdded)
254     DEF(MediaDurationChanged)
255     DEF(MediaParsedChanged)
256     DEF(MediaFreed)
257     DEF(MediaStateChanged)
258
259     DEF(MediaPlayerMediaChanged)
260     DEF(MediaPlayerNothingSpecial)
261     DEF(MediaPlayerOpening)
262     DEF(MediaPlayerBuffering)
263     DEF(MediaPlayerPlaying)
264     DEF(MediaPlayerPaused)
265     DEF(MediaPlayerStopped)
266     DEF(MediaPlayerForward)
267     DEF(MediaPlayerBackward)
268     DEF(MediaPlayerEndReached)
269     DEF(MediaPlayerEncounteredError)
270     DEF(MediaPlayerTimeChanged)
271     DEF(MediaPlayerPositionChanged)
272     DEF(MediaPlayerSeekableChanged)
273     DEF(MediaPlayerPausableChanged)
274     DEF(MediaPlayerTitleChanged)
275     DEF(MediaPlayerSnapshotTaken)
276     DEF(MediaPlayerLengthChanged)
277
278     DEF(MediaListItemAdded)
279     DEF(MediaListWillAddItem)
280     DEF(MediaListItemDeleted)
281     DEF(MediaListWillDeleteItem)
282
283     DEF(MediaListViewItemAdded)
284     DEF(MediaListViewWillAddItem)
285     DEF(MediaListViewItemDeleted)
286     DEF(MediaListViewWillDeleteItem)
287
288     DEF(MediaListPlayerPlayed)
289     DEF(MediaListPlayerNextItemSet)
290     DEF(MediaListPlayerStopped)
291
292     DEF(MediaDiscovererStarted)
293     DEF(MediaDiscovererEnded)
294
295     DEF(VlmMediaAdded)
296     DEF(VlmMediaRemoved)
297     DEF(VlmMediaChanged)
298     DEF(VlmMediaInstanceStarted)
299     DEF(VlmMediaInstanceStopped)
300     DEF(VlmMediaInstanceStatusInit)
301     DEF(VlmMediaInstanceStatusOpening)
302 };
303 #undef DEF
304
305 static const char unknown_event_name[] = "Unknown Event";
306
307 static int evcmp( const void *a, const void *b )
308 {
309     return (*(const int *)a) - ((event_name_t *)b)->type;
310 }
311
312 const char * libvlc_event_type_name( int event_type )
313 {
314     const event_name_t *p;
315
316     p = bsearch( &event_type, event_list,
317                  sizeof(event_list)/sizeof(event_list[0]), sizeof(*p),
318                  evcmp );
319     return p ? p->name : unknown_event_name;
320 }
321
322 /**************************************************************************
323  *       event_attach (internal) :
324  *
325  * Add a callback for an event.
326  **************************************************************************/
327 static
328 int event_attach( libvlc_event_manager_t * p_event_manager,
329                   libvlc_event_type_t event_type,
330                   libvlc_callback_t pf_callback, void *p_user_data,
331                   bool is_asynchronous )
332 {
333     libvlc_event_listeners_group_t * listeners_group;
334     libvlc_event_listener_t * listener;
335     int i;
336
337     listener = malloc(sizeof(libvlc_event_listener_t));
338     if( unlikely(listener == NULL) )
339         return ENOMEM;
340
341     listener->event_type = event_type;
342     listener->p_user_data = p_user_data;
343     listener->pf_callback = pf_callback;
344     listener->is_asynchronous = is_asynchronous;
345
346     vlc_mutex_lock( &p_event_manager->object_lock );
347     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++ )
348     {
349         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
350         if( listeners_group->event_type == listener->event_type )
351         {
352             vlc_array_append( &listeners_group->listeners, listener );
353             vlc_mutex_unlock( &p_event_manager->object_lock );
354             return 0;
355         }
356     }
357     vlc_mutex_unlock( &p_event_manager->object_lock );
358
359     free(listener);
360     fprintf( stderr, "This object event manager doesn't know about '%s' events",
361              libvlc_event_type_name(event_type) );
362     assert(0);
363     return -1;
364 }
365
366 /**************************************************************************
367  *       libvlc_event_attach (public) :
368  *
369  * Add a callback for an event.
370  **************************************************************************/
371 int libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
372                          libvlc_event_type_t event_type,
373                          libvlc_callback_t pf_callback,
374                          void *p_user_data )
375 {
376     return event_attach(p_event_manager, event_type, pf_callback, p_user_data,
377                         false /* synchronous */);
378 }
379
380 /**************************************************************************
381  *       libvlc_event_attach (public) :
382  *
383  * Add a callback for an event.
384  **************************************************************************/
385 void libvlc_event_attach_async( libvlc_event_manager_t * p_event_manager,
386                          libvlc_event_type_t event_type,
387                          libvlc_callback_t pf_callback,
388                          void *p_user_data )
389 {
390     event_attach(p_event_manager, event_type, pf_callback, p_user_data,
391                  true /* asynchronous */);
392 }
393
394 /**************************************************************************
395  *       libvlc_event_detach (public) :
396  *
397  * Remove a callback for an event.
398  **************************************************************************/
399 void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
400                                      libvlc_event_type_t event_type,
401                                      libvlc_callback_t pf_callback,
402                                      void *p_user_data )
403 {
404     libvlc_event_listeners_group_t * listeners_group;
405     libvlc_event_listener_t * listener;
406     int i, j;
407     bool found = false;
408
409     vlc_mutex_lock( &p_event_manager->event_sending_lock );
410     vlc_mutex_lock( &p_event_manager->object_lock );
411     for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++)
412     {
413         listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
414         if( listeners_group->event_type == event_type )
415         {
416             for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
417             {
418                 listener = vlc_array_item_at_index(&listeners_group->listeners, j);
419                 if( listener->event_type == event_type &&
420                     listener->pf_callback == pf_callback &&
421                     listener->p_user_data == p_user_data )
422                 {
423                     /* that's our listener */
424
425                     /* Mark this group as edited so that libvlc_event_send
426                      * will recheck what listener to call */
427                     listeners_group->b_sublistener_removed = false;
428
429                     free( listener );
430                     vlc_array_remove( &listeners_group->listeners, j );
431                     found = true;
432                     break;
433                 }
434             }
435         }
436     }
437     vlc_mutex_unlock( &p_event_manager->object_lock );
438     vlc_mutex_unlock( &p_event_manager->event_sending_lock );
439
440     /* Now make sure any pending async event won't get fired after that point */
441     libvlc_event_listener_t listener_to_remove;
442     listener_to_remove.event_type  = event_type;
443     listener_to_remove.pf_callback = pf_callback;
444     listener_to_remove.p_user_data = p_user_data;
445     listener_to_remove.is_asynchronous = true;
446
447     libvlc_event_async_ensure_listener_removal(p_event_manager, &listener_to_remove);
448
449     assert(found);
450 }