event.c 15.4 KB
Newer Older
Filippo Carone's avatar
Filippo Carone committed
1
/*****************************************************************************
2
 * event.c: New libvlc event control API
Filippo Carone's avatar
Filippo Carone committed
3
 *****************************************************************************
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2007-2010 VLC authors and VideoLAN
Filippo Carone's avatar
Filippo Carone committed
5 6 7
 * $Id $
 *
 * Authors: Filippo Carone <filippo@carone.org>
8
 *          Pierre d'Herbemont <pdherbemont # videolan.org>
Filippo Carone's avatar
Filippo Carone committed
9
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
10 11 12
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
Filippo Carone's avatar
Filippo Carone committed
13 14 15 16
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
17 18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
Filippo Carone's avatar
Filippo Carone committed
19
 *
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
20 21 22
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Filippo Carone's avatar
Filippo Carone committed
23 24
 *****************************************************************************/

25 26 27 28
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

29
#define  LIBVLC_EVENT_TYPES_KEEP_DEFINE
Filippo Carone's avatar
Filippo Carone committed
30
#include <vlc/libvlc.h>
31 32 33

#include "libvlc_internal.h"
#include "event_internal.h"
34
#include <assert.h>
35
#include <errno.h>
36 37 38 39 40 41 42

typedef struct libvlc_event_listeners_group_t
{
    libvlc_event_type_t event_type;
    vlc_array_t listeners;
    bool b_sublistener_removed;
} libvlc_event_listeners_group_t;
Filippo Carone's avatar
Filippo Carone committed
43

44 45 46 47
/*
 * Private functions
 */

48
static bool
49 50 51 52 53 54 55
group_contains_listener( libvlc_event_listeners_group_t * group,
                         libvlc_event_listener_t * searched_listener )
{
    int i;
    for( i = 0; i < vlc_array_count(&group->listeners); i++ )
    {
        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 63
/*
 * Internal libvlc functions
 */
Filippo Carone's avatar
Filippo Carone committed
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 76 77
    p_em = malloc(sizeof( libvlc_event_manager_t ));
    if( !p_em )
    {
78
        libvlc_printerr( "Not enough memory" );
79 80
        return NULL;
    }
81

82
    p_em->p_obj = p_obj;
83 84
    p_em->p_obj = p_obj;
    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 102
    libvlc_event_listeners_group_t * p_lg;
    int i,j ;
103

104 105
    libvlc_event_async_fini(p_em);

106 107
    vlc_mutex_destroy( &p_em->event_sending_lock );
    vlc_mutex_destroy( &p_em->object_lock );
108

109 110 111 112 113
    for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
    {
        p_lg = vlc_array_item_at_index( &p_em->listeners_groups, i );

        for( j = 0; j < vlc_array_count(&p_lg->listeners); j++)
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
114
            free( vlc_array_item_at_index( &p_lg->listeners, j ) );
115

116 117 118 119
        vlc_array_clear( &p_lg->listeners );
        free( p_lg );
    }
    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 149
void libvlc_event_send( libvlc_event_manager_t * p_em,
                        libvlc_event_t * p_event )
150
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
151
    libvlc_event_listeners_group_t * listeners_group = NULL;
152
    libvlc_event_listener_t * listener_cached;
153
    libvlc_event_listener_t * listener;
154 155 156
    libvlc_event_listener_t * array_listeners_cached = NULL;
    int i, i_cached_listeners = 0;

157 158 159
    /* Fill event with the sending object now */
    p_event->p_obj = p_em->p_obj;

Rafaël Carré's avatar
Rafaël Carré committed
160
    vlc_mutex_lock( &p_em->event_sending_lock );
161
    vlc_mutex_lock( &p_em->object_lock );
162 163 164
    for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
    {
        listeners_group = vlc_array_item_at_index(&p_em->listeners_groups, i);
165 166
        if( listeners_group->event_type == p_event->type )
        {
167
            if( vlc_array_count( &listeners_group->listeners ) <= 0 )
168 169
                break;

170 171
            /* Cache a copy of the listener to avoid locking issues,
             * and allow that edition of listeners during callbacks will garantee immediate effect. */
172
            i_cached_listeners = vlc_array_count(&listeners_group->listeners);
173 174 175
            array_listeners_cached = malloc(sizeof(libvlc_event_listener_t)*(i_cached_listeners));
            if( !array_listeners_cached )
            {
176
                vlc_mutex_unlock( &p_em->object_lock );
Rafaël Carré's avatar
Rafaël Carré committed
177
                vlc_mutex_unlock( &p_em->event_sending_lock );
178 179
                fprintf(stderr, "Can't alloc memory in libvlc_event_send" );
                return;
180 181 182
            }

            listener_cached = array_listeners_cached;
183 184 185 186
            for( i = 0; i < vlc_array_count(&listeners_group->listeners); i++)
            {
                listener = vlc_array_item_at_index(&listeners_group->listeners, i);
                memcpy( listener_cached, listener, sizeof(libvlc_event_listener_t) );
187
                listener_cached++;
188
            }
189 190
            break;
        }
191
    }
192

193 194 195
    if( !listeners_group )
    {
        free( array_listeners_cached );
196
        vlc_mutex_unlock( &p_em->object_lock );
Rafaël Carré's avatar
Rafaël Carré committed
197
        vlc_mutex_unlock( &p_em->event_sending_lock );
198 199 200
        return;
    }

Rafaël Carré's avatar
Rafaël Carré committed
201 202 203 204 205
    /* Track item removed from *this* thread, with a simple flag. Indeed
     * event_sending_lock is a recursive lock. This has the advantage of
     * allowing to remove an event listener from within a callback */
    listeners_group->b_sublistener_removed = false;

206 207 208 209 210
    vlc_mutex_unlock( &p_em->object_lock );

    listener_cached = array_listeners_cached;
    for( i = 0; i < i_cached_listeners; i++ )
    {
211
        if(listener_cached->is_asynchronous)
212
        {
213 214 215 216 217 218
            /* The listener wants not to block the emitter during event callback */
            libvlc_event_async_dispatch(p_em, listener_cached, p_event);
        }
        else
        {
            /* The listener wants to block the emitter during event callback */
219

220 221
            listener_cached->pf_callback( p_event, listener_cached->p_user_data );
            listener_cached++;
222

223
            if( listeners_group->b_sublistener_removed )
224
            {
225 226 227 228 229 230 231 232 233 234
                /* If a callback was removed, this gets called */
                bool valid_listener;
                vlc_mutex_lock( &p_em->object_lock );
                valid_listener = group_contains_listener( listeners_group, listener_cached );
                vlc_mutex_unlock( &p_em->object_lock );
                if( !valid_listener )
                {
                    listener_cached++;
                    continue;
                }
235
            }
236
        }
237
    }
238
    vlc_mutex_unlock( &p_em->event_sending_lock );
239

240
    free( array_listeners_cached );
241 242
}

243 244 245 246
/*
 * Public libvlc functions
 */

247 248 249 250 251 252 253 254 255 256 257 258
#define DEF( a ) { libvlc_##a, #a, },

typedef struct
{
    int type;
    const char name[40];
} event_name_t;

static const event_name_t event_list[] = {
    DEF(MediaMetaChanged)
    DEF(MediaSubItemAdded)
    DEF(MediaDurationChanged)
259
    DEF(MediaParsedChanged)
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
    DEF(MediaFreed)
    DEF(MediaStateChanged)

    DEF(MediaPlayerMediaChanged)
    DEF(MediaPlayerNothingSpecial)
    DEF(MediaPlayerOpening)
    DEF(MediaPlayerBuffering)
    DEF(MediaPlayerPlaying)
    DEF(MediaPlayerPaused)
    DEF(MediaPlayerStopped)
    DEF(MediaPlayerForward)
    DEF(MediaPlayerBackward)
    DEF(MediaPlayerEndReached)
    DEF(MediaPlayerEncounteredError)
    DEF(MediaPlayerTimeChanged)
    DEF(MediaPlayerPositionChanged)
    DEF(MediaPlayerSeekableChanged)
    DEF(MediaPlayerPausableChanged)
    DEF(MediaPlayerTitleChanged)
    DEF(MediaPlayerSnapshotTaken)
    DEF(MediaPlayerLengthChanged)
281
    DEF(MediaPlayerVout)
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306

    DEF(MediaListItemAdded)
    DEF(MediaListWillAddItem)
    DEF(MediaListItemDeleted)
    DEF(MediaListWillDeleteItem)

    DEF(MediaListViewItemAdded)
    DEF(MediaListViewWillAddItem)
    DEF(MediaListViewItemDeleted)
    DEF(MediaListViewWillDeleteItem)

    DEF(MediaListPlayerPlayed)
    DEF(MediaListPlayerNextItemSet)
    DEF(MediaListPlayerStopped)

    DEF(MediaDiscovererStarted)
    DEF(MediaDiscovererEnded)

    DEF(VlmMediaAdded)
    DEF(VlmMediaRemoved)
    DEF(VlmMediaChanged)
    DEF(VlmMediaInstanceStarted)
    DEF(VlmMediaInstanceStopped)
    DEF(VlmMediaInstanceStatusInit)
    DEF(VlmMediaInstanceStatusOpening)
307 308 309 310
    DEF(VlmMediaInstanceStatusPlaying)
    DEF(VlmMediaInstanceStatusPause)
    DEF(VlmMediaInstanceStatusEnd)
    DEF(VlmMediaInstanceStatusError)
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
};
#undef DEF

static const char unknown_event_name[] = "Unknown Event";

static int evcmp( const void *a, const void *b )
{
    return (*(const int *)a) - ((event_name_t *)b)->type;
}

const char * libvlc_event_type_name( int event_type )
{
    const event_name_t *p;

    p = bsearch( &event_type, event_list,
                 sizeof(event_list)/sizeof(event_list[0]), sizeof(*p),
                 evcmp );
    return p ? p->name : unknown_event_name;
}

331
/**************************************************************************
332
 *       event_attach (internal) :
333 334 335
 *
 * Add a callback for an event.
 **************************************************************************/
336
static
337 338 339 340
int event_attach( libvlc_event_manager_t * p_event_manager,
                  libvlc_event_type_t event_type,
                  libvlc_callback_t pf_callback, void *p_user_data,
                  bool is_asynchronous )
Filippo Carone's avatar
Filippo Carone committed
341
{
342 343
    libvlc_event_listeners_group_t * listeners_group;
    libvlc_event_listener_t * listener;
344
    int i;
345

346
    listener = malloc(sizeof(libvlc_event_listener_t));
347 348
    if( unlikely(listener == NULL) )
        return ENOMEM;
349

350 351 352
    listener->event_type = event_type;
    listener->p_user_data = p_user_data;
    listener->pf_callback = pf_callback;
353
    listener->is_asynchronous = is_asynchronous;
354

355
    vlc_mutex_lock( &p_event_manager->object_lock );
356 357 358
    for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++ )
    {
        listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
359 360
        if( listeners_group->event_type == listener->event_type )
        {
361
            vlc_array_append( &listeners_group->listeners, listener );
362
            vlc_mutex_unlock( &p_event_manager->object_lock );
363
            return 0;
364
        }
365
    }
366
    vlc_mutex_unlock( &p_event_manager->object_lock );
367

368
    free(listener);
369 370
    fprintf( stderr, "This object event manager doesn't know about '%s' events",
             libvlc_event_type_name(event_type) );
371
    assert(0);
372
    return -1;
373 374 375 376 377 378 379
}

/**************************************************************************
 *       libvlc_event_attach (public) :
 *
 * Add a callback for an event.
 **************************************************************************/
380
int libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
381 382
                         libvlc_event_type_t event_type,
                         libvlc_callback_t pf_callback,
383
                         void *p_user_data )
384
{
385 386
    return event_attach(p_event_manager, event_type, pf_callback, p_user_data,
                        false /* synchronous */);
387 388 389 390 391 392 393 394 395 396
}

/**************************************************************************
 *       libvlc_event_attach (public) :
 *
 * Add a callback for an event.
 **************************************************************************/
void libvlc_event_attach_async( libvlc_event_manager_t * p_event_manager,
                         libvlc_event_type_t event_type,
                         libvlc_callback_t pf_callback,
397
                         void *p_user_data )
398
{
399 400
    event_attach(p_event_manager, event_type, pf_callback, p_user_data,
                 true /* asynchronous */);
Filippo Carone's avatar
Filippo Carone committed
401 402
}

403
/**************************************************************************
404
 *       libvlc_event_detach (public) :
405 406 407
 *
 * Remove a callback for an event.
 **************************************************************************/
408
void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
409 410
                                     libvlc_event_type_t event_type,
                                     libvlc_callback_t pf_callback,
411
                                     void *p_user_data )
Filippo Carone's avatar
Filippo Carone committed
412
{
413 414
    libvlc_event_listeners_group_t * listeners_group;
    libvlc_event_listener_t * listener;
415
    int i, j;
416
    bool found = false;
417

418
    vlc_mutex_lock( &p_event_manager->event_sending_lock );
419
    vlc_mutex_lock( &p_event_manager->object_lock );
420 421 422
    for( i = 0; i < vlc_array_count(&p_event_manager->listeners_groups); i++)
    {
        listeners_group = vlc_array_item_at_index(&p_event_manager->listeners_groups, i);
423 424
        if( listeners_group->event_type == event_type )
        {
425
            for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
426
            {
427
                listener = vlc_array_item_at_index(&listeners_group->listeners, j);
428 429 430 431 432
                if( listener->event_type == event_type &&
                    listener->pf_callback == pf_callback &&
                    listener->p_user_data == p_user_data )
                {
                    /* that's our listener */
433

434 435
                    /* Mark this group as edited so that libvlc_event_send
                     * will recheck what listener to call */
Rafaël Carré's avatar
Rafaël Carré committed
436
                    listeners_group->b_sublistener_removed = true;
437

438
                    free( listener );
439
                    vlc_array_remove( &listeners_group->listeners, j );
440 441
                    found = true;
                    break;
442
                }
443
            }
444
        }
445
    }
446
    vlc_mutex_unlock( &p_event_manager->object_lock );
447
    vlc_mutex_unlock( &p_event_manager->event_sending_lock );
448

449 450 451 452 453 454 455 456 457
    /* Now make sure any pending async event won't get fired after that point */
    libvlc_event_listener_t listener_to_remove;
    listener_to_remove.event_type  = event_type;
    listener_to_remove.pf_callback = pf_callback;
    listener_to_remove.p_user_data = p_user_data;
    listener_to_remove.is_asynchronous = true;

    libvlc_event_async_ensure_listener_removal(p_event_manager, &listener_to_remove);

458
    assert(found);
Filippo Carone's avatar
Filippo Carone committed
459
}