event.c 15.5 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
#include <assert.h>
30
#include <errno.h>
31

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
#include <vlc/libvlc.h>
#include "libvlc_internal.h"

#include <vlc_common.h>

/*
 * Event Handling
 */

/* Example usage
 *
 * struct libvlc_cool_object_t
 * {
 *        ...
 *        libvlc_event_manager_t * p_event_manager;
 *        ...
 * }
 *
 * libvlc_my_cool_object_new()
 * {
 *        ...
 *        p_self->p_event_manager = libvlc_event_manager_new( p_self )
 *        libvlc_event_manager_register_event_type(p_self->p_event_manager,
 *                libvlc_MyCoolObjectDidSomething, p_e)
 *        ...
 * }
 *
 * libvlc_my_cool_object_release()
 * {
 *         ...
 *         libvlc_event_manager_release( p_self->p_event_manager );
 *         ...
 * }
 *
 * libvlc_my_cool_object_do_something()
 * {
 *        ...
 *        libvlc_event_t event;
 *        event.type = libvlc_MyCoolObjectDidSomething;
 *        event.u.my_cool_object_did_something.what_it_did = kSomething;
 *        libvlc_event_send( p_self->p_event_manager, &event );
 * }
 * */

typedef struct libvlc_event_listener_t
{
    libvlc_event_type_t event_type;
    void *              p_user_data;
    libvlc_callback_t   pf_callback;
} libvlc_event_listener_t;

typedef struct libvlc_event_manager_t
{
    void * p_obj;
    vlc_array_t listeners_groups;
    vlc_mutex_t object_lock;
    vlc_mutex_t event_sending_lock;
} libvlc_event_sender_t;


static inline bool
listeners_are_equal( libvlc_event_listener_t * listener1,
                    libvlc_event_listener_t * listener2 )
{
    return listener1->event_type  == listener2->event_type &&
    listener1->pf_callback == listener2->pf_callback &&
    listener1->p_user_data == listener2->p_user_data;
}


102 103 104 105 106 107
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
108

109 110 111 112
/*
 * Private functions
 */

113
static bool
114 115 116 117 118 119 120
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)) )
121
            return true;
122
    }
123
    return false;
124 125
}

126 127 128
/*
 * Internal libvlc functions
 */
Filippo Carone's avatar
Filippo Carone committed
129

130
/**************************************************************************
131
 *       libvlc_event_manager_new (internal) :
132
 *
133
 * Init an object's event manager.
134
 **************************************************************************/
135
libvlc_event_manager_t *
136
libvlc_event_manager_new( void * p_obj )
137
{
138
    libvlc_event_manager_t * p_em;
139

140 141 142
    p_em = malloc(sizeof( libvlc_event_manager_t ));
    if( !p_em )
    {
143
        libvlc_printerr( "Not enough memory" );
144 145
        return NULL;
    }
146

147 148
    p_em->p_obj = p_obj;

149
    vlc_array_init( &p_em->listeners_groups );
150
    vlc_mutex_init( &p_em->object_lock );
151
    vlc_mutex_init_recursive( &p_em->event_sending_lock );
152
    return p_em;
153 154
}

155
/**************************************************************************
156
 *       libvlc_event_manager_release (internal) :
157
 *
158
 * Release an object's event manager.
159
 **************************************************************************/
160
void libvlc_event_manager_release( libvlc_event_manager_t * p_em )
161
{
162 163
    libvlc_event_listeners_group_t * p_lg;
    int i,j ;
164

165 166
    vlc_mutex_destroy( &p_em->event_sending_lock );
    vlc_mutex_destroy( &p_em->object_lock );
167

168 169 170 171 172
    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
173
            free( vlc_array_item_at_index( &p_lg->listeners, j ) );
174

175 176 177 178
        vlc_array_clear( &p_lg->listeners );
        free( p_lg );
    }
    vlc_array_clear( &p_em->listeners_groups );
179
    free( p_em );
180 181
}

182
/**************************************************************************
183
 *       libvlc_event_manager_register_event_type (internal) :
184
 *
185
 * Init an object's event manager.
186
 **************************************************************************/
187
void libvlc_event_manager_register_event_type(
188
        libvlc_event_manager_t * p_em,
189
        libvlc_event_type_t event_type )
190
{
191
    libvlc_event_listeners_group_t * listeners_group;
192
    listeners_group = xmalloc(sizeof(libvlc_event_listeners_group_t));
193
    listeners_group->event_type = event_type;
194
    vlc_array_init( &listeners_group->listeners );
195

196
    vlc_mutex_lock( &p_em->object_lock );
197
    vlc_array_append( &p_em->listeners_groups, listeners_group );
198
    vlc_mutex_unlock( &p_em->object_lock );
199
}
200

201
/**************************************************************************
202
 *       libvlc_event_send (internal) :
203
 *
204
 * Send a callback.
205
 **************************************************************************/
206 207
void libvlc_event_send( libvlc_event_manager_t * p_em,
                        libvlc_event_t * p_event )
208
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
209
    libvlc_event_listeners_group_t * listeners_group = NULL;
210
    libvlc_event_listener_t * listener_cached;
211
    libvlc_event_listener_t * listener;
212 213 214
    libvlc_event_listener_t * array_listeners_cached = NULL;
    int i, i_cached_listeners = 0;

215 216 217
    /* Fill event with the sending object now */
    p_event->p_obj = p_em->p_obj;

Rafaël Carré's avatar
Rafaël Carré committed
218
    vlc_mutex_lock( &p_em->event_sending_lock );
219
    vlc_mutex_lock( &p_em->object_lock );
220 221 222
    for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
    {
        listeners_group = vlc_array_item_at_index(&p_em->listeners_groups, i);
223 224
        if( listeners_group->event_type == p_event->type )
        {
225
            if( vlc_array_count( &listeners_group->listeners ) <= 0 )
226 227
                break;

228 229
            /* Cache a copy of the listener to avoid locking issues,
             * and allow that edition of listeners during callbacks will garantee immediate effect. */
230
            i_cached_listeners = vlc_array_count(&listeners_group->listeners);
231 232 233
            array_listeners_cached = malloc(sizeof(libvlc_event_listener_t)*(i_cached_listeners));
            if( !array_listeners_cached )
            {
234
                vlc_mutex_unlock( &p_em->object_lock );
Rafaël Carré's avatar
Rafaël Carré committed
235
                vlc_mutex_unlock( &p_em->event_sending_lock );
236 237
                fprintf(stderr, "Can't alloc memory in libvlc_event_send" );
                return;
238 239 240
            }

            listener_cached = array_listeners_cached;
241 242 243 244
            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) );
245
                listener_cached++;
246
            }
247 248
            break;
        }
249
    }
250

251 252 253
    if( !listeners_group )
    {
        free( array_listeners_cached );
254
        vlc_mutex_unlock( &p_em->object_lock );
Rafaël Carré's avatar
Rafaël Carré committed
255
        vlc_mutex_unlock( &p_em->event_sending_lock );
256 257 258
        return;
    }

Rafaël Carré's avatar
Rafaël Carré committed
259 260 261 262 263
    /* 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;

264 265 266 267 268
    vlc_mutex_unlock( &p_em->object_lock );

    listener_cached = array_listeners_cached;
    for( i = 0; i < i_cached_listeners; i++ )
    {
269 270 271
        /* The listener wants to block the emitter during event callback */
        listener_cached->pf_callback( p_event, listener_cached->p_user_data );
        listener_cached++;
272

273 274 275 276 277 278 279 280
        if( listeners_group->b_sublistener_removed )
        {
            /* 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 )
281
            {
282 283
                listener_cached++;
                continue;
284
            }
285
        }
286
    }
287
    vlc_mutex_unlock( &p_em->event_sending_lock );
288

289
    free( array_listeners_cached );
290 291
}

292 293 294 295
/*
 * Public libvlc functions
 */

296 297 298 299 300 301 302 303 304 305 306 307
#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)
308
    DEF(MediaParsedChanged)
309 310
    DEF(MediaFreed)
    DEF(MediaStateChanged)
311
    DEF(MediaSubItemTreeAdded)
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330

    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)
331
    DEF(MediaPlayerVout)
332
    DEF(MediaPlayerScrambledChanged)
333 334 335
    DEF(MediaPlayerESAdded)
    DEF(MediaPlayerESDeleted)
    DEF(MediaPlayerESSelected)
336 337
    DEF(MediaPlayerCorked)
    DEF(MediaPlayerUncorked)
338 339
    DEF(MediaPlayerMuted)
    DEF(MediaPlayerUnmuted)
340
    DEF(MediaPlayerAudioVolume)
341
    DEF(MediaPlayerAudioDevice)
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366

    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)
367 368 369 370
    DEF(VlmMediaInstanceStatusPlaying)
    DEF(VlmMediaInstanceStatusPause)
    DEF(VlmMediaInstanceStatusEnd)
    DEF(VlmMediaInstanceStatusError)
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
};
#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;
}

391
/**************************************************************************
392
 *       event_attach (internal) :
393 394 395
 *
 * Add a callback for an event.
 **************************************************************************/
396
static
397 398
int event_attach( libvlc_event_manager_t * p_event_manager,
                  libvlc_event_type_t event_type,
399
                  libvlc_callback_t pf_callback, void *p_user_data )
Filippo Carone's avatar
Filippo Carone committed
400
{
401 402
    libvlc_event_listeners_group_t * listeners_group;
    libvlc_event_listener_t * listener;
403
    int i;
404

405
    listener = malloc(sizeof(libvlc_event_listener_t));
406 407
    if( unlikely(listener == NULL) )
        return ENOMEM;
408

409 410 411
    listener->event_type = event_type;
    listener->p_user_data = p_user_data;
    listener->pf_callback = pf_callback;
412

413
    vlc_mutex_lock( &p_event_manager->object_lock );
414 415 416
    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);
417 418
        if( listeners_group->event_type == listener->event_type )
        {
419
            vlc_array_append( &listeners_group->listeners, listener );
420
            vlc_mutex_unlock( &p_event_manager->object_lock );
421
            return 0;
422
        }
423
    }
424
    vlc_mutex_unlock( &p_event_manager->object_lock );
425

426
    free(listener);
427 428
    fprintf( stderr, "This object event manager doesn't know about '%s' events",
             libvlc_event_type_name(event_type) );
429
    vlc_assert_unreachable();
430
    return -1;
431 432 433 434 435 436 437
}

/**************************************************************************
 *       libvlc_event_attach (public) :
 *
 * Add a callback for an event.
 **************************************************************************/
438
int libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
439 440
                         libvlc_event_type_t event_type,
                         libvlc_callback_t pf_callback,
441
                         void *p_user_data )
442
{
443
    return event_attach(p_event_manager, event_type, pf_callback, p_user_data);
Filippo Carone's avatar
Filippo Carone committed
444 445
}

446
/**************************************************************************
447
 *       libvlc_event_detach (public) :
448 449 450
 *
 * Remove a callback for an event.
 **************************************************************************/
451
void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
452 453
                                     libvlc_event_type_t event_type,
                                     libvlc_callback_t pf_callback,
454
                                     void *p_user_data )
Filippo Carone's avatar
Filippo Carone committed
455
{
456 457
    libvlc_event_listeners_group_t * listeners_group;
    libvlc_event_listener_t * listener;
458
    int i, j;
459
    bool found = false;
460

461
    vlc_mutex_lock( &p_event_manager->event_sending_lock );
462
    vlc_mutex_lock( &p_event_manager->object_lock );
463 464 465
    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);
466 467
        if( listeners_group->event_type == event_type )
        {
468
            for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
469
            {
470
                listener = vlc_array_item_at_index(&listeners_group->listeners, j);
471 472 473 474 475
                if( listener->event_type == event_type &&
                    listener->pf_callback == pf_callback &&
                    listener->p_user_data == p_user_data )
                {
                    /* that's our listener */
476

477 478
                    /* 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
479
                    listeners_group->b_sublistener_removed = true;
480

481
                    free( listener );
482
                    vlc_array_remove( &listeners_group->listeners, j );
483 484
                    found = true;
                    break;
485
                }
486
            }
487
        }
488
    }
489
    vlc_mutex_unlock( &p_event_manager->object_lock );
490
    vlc_mutex_unlock( &p_event_manager->event_sending_lock );
491

492
    assert(found);
Filippo Carone's avatar
Filippo Carone committed
493
}