event.c 14.8 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
#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;
87
    vlc_mutex_t lock;
88 89 90 91 92 93 94 95 96 97 98 99 100
} 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;
}


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

108 109 110 111
/*
 * Private functions
 */

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

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

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

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

146 147
    p_em->p_obj = p_obj;

148
    vlc_array_init( &p_em->listeners_groups );
149
    vlc_mutex_init_recursive(&p_em->lock);
150
    return p_em;
151 152
}

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

163
    vlc_mutex_destroy(&p_em->lock);
164

165 166 167 168 169
    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
170
            free( vlc_array_item_at_index( &p_lg->listeners, j ) );
171

172 173 174 175
        vlc_array_clear( &p_lg->listeners );
        free( p_lg );
    }
    vlc_array_clear( &p_em->listeners_groups );
176
    free( p_em );
177 178
}

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

193
    vlc_mutex_lock(&p_em->lock);
194
    vlc_array_append( &p_em->listeners_groups, listeners_group );
195
    vlc_mutex_unlock(&p_em->lock);
196
}
197

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

212 213 214
    /* Fill event with the sending object now */
    p_event->p_obj = p_em->p_obj;

215
    vlc_mutex_lock(&p_em->lock);
216 217 218
    for( i = 0; i < vlc_array_count(&p_em->listeners_groups); i++)
    {
        listeners_group = vlc_array_item_at_index(&p_em->listeners_groups, i);
219 220
        if( listeners_group->event_type == p_event->type )
        {
221
            if( vlc_array_count( &listeners_group->listeners ) <= 0 )
222 223
                break;

224 225
            /* Cache a copy of the listener to avoid locking issues,
             * and allow that edition of listeners during callbacks will garantee immediate effect. */
226
            i_cached_listeners = vlc_array_count(&listeners_group->listeners);
227 228 229
            array_listeners_cached = malloc(sizeof(libvlc_event_listener_t)*(i_cached_listeners));
            if( !array_listeners_cached )
            {
230
                vlc_mutex_unlock(&p_em->lock);
231 232
                fprintf(stderr, "Can't alloc memory in libvlc_event_send" );
                return;
233 234 235
            }

            listener_cached = array_listeners_cached;
236 237 238 239
            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) );
240
                listener_cached++;
241
            }
242 243
            break;
        }
244
    }
245

246 247
    if( !listeners_group )
    {
248
        vlc_mutex_unlock(&p_em->lock);
249 250 251 252
        free( array_listeners_cached );
        return;
    }

Rafaël Carré's avatar
Rafaël Carré committed
253 254 255 256 257
    /* 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;

258 259 260
    listener_cached = array_listeners_cached;
    for( i = 0; i < i_cached_listeners; i++ )
    {
261 262 263
        /* The listener wants to block the emitter during event callback */
        listener_cached->pf_callback( p_event, listener_cached->p_user_data );
        listener_cached++;
264

265 266 267 268 269 270
        if( listeners_group->b_sublistener_removed )
        {
            /* If a callback was removed, this gets called */
            bool valid_listener;
            valid_listener = group_contains_listener( listeners_group, listener_cached );
            if( !valid_listener )
271
            {
272 273
                listener_cached++;
                continue;
274
            }
275
        }
276
    }
277
    vlc_mutex_unlock(&p_em->lock);
278

279
    free( array_listeners_cached );
280 281
}

282 283 284 285
/*
 * Public libvlc functions
 */

286 287 288 289 290 291 292 293 294 295 296 297
#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)
298
    DEF(MediaParsedChanged)
299 300
    DEF(MediaFreed)
    DEF(MediaStateChanged)
301
    DEF(MediaSubItemTreeAdded)
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

    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)
321
    DEF(MediaPlayerVout)
322
    DEF(MediaPlayerScrambledChanged)
323 324 325
    DEF(MediaPlayerESAdded)
    DEF(MediaPlayerESDeleted)
    DEF(MediaPlayerESSelected)
326 327
    DEF(MediaPlayerCorked)
    DEF(MediaPlayerUncorked)
328 329
    DEF(MediaPlayerMuted)
    DEF(MediaPlayerUnmuted)
330
    DEF(MediaPlayerAudioVolume)
331
    DEF(MediaPlayerAudioDevice)
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356

    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)
357 358 359 360
    DEF(VlmMediaInstanceStatusPlaying)
    DEF(VlmMediaInstanceStatusPause)
    DEF(VlmMediaInstanceStatusEnd)
    DEF(VlmMediaInstanceStatusError)
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
};
#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;
}

381
/**************************************************************************
382
 *       event_attach (internal) :
383 384 385
 *
 * Add a callback for an event.
 **************************************************************************/
386
static
387 388
int event_attach( libvlc_event_manager_t * p_event_manager,
                  libvlc_event_type_t event_type,
389
                  libvlc_callback_t pf_callback, void *p_user_data )
Filippo Carone's avatar
Filippo Carone committed
390
{
391 392
    libvlc_event_listeners_group_t * listeners_group;
    libvlc_event_listener_t * listener;
393
    int i;
394

395
    listener = malloc(sizeof(libvlc_event_listener_t));
396 397
    if( unlikely(listener == NULL) )
        return ENOMEM;
398

399 400 401
    listener->event_type = event_type;
    listener->p_user_data = p_user_data;
    listener->pf_callback = pf_callback;
402

403
    vlc_mutex_lock(&p_event_manager->lock);
404 405 406
    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);
407 408
        if( listeners_group->event_type == listener->event_type )
        {
409
            vlc_array_append( &listeners_group->listeners, listener );
410
            vlc_mutex_unlock(&p_event_manager->lock);
411
            return 0;
412
        }
413
    }
414
    vlc_mutex_unlock(&p_event_manager->lock);
415

416
    free(listener);
417 418
    fprintf( stderr, "This object event manager doesn't know about '%s' events",
             libvlc_event_type_name(event_type) );
419
    vlc_assert_unreachable();
420
    return -1;
421 422 423 424 425 426 427
}

/**************************************************************************
 *       libvlc_event_attach (public) :
 *
 * Add a callback for an event.
 **************************************************************************/
428
int libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
429 430
                         libvlc_event_type_t event_type,
                         libvlc_callback_t pf_callback,
431
                         void *p_user_data )
432
{
433
    return event_attach(p_event_manager, event_type, pf_callback, p_user_data);
Filippo Carone's avatar
Filippo Carone committed
434 435
}

436
/**************************************************************************
437
 *       libvlc_event_detach (public) :
438 439 440
 *
 * Remove a callback for an event.
 **************************************************************************/
441
void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
442 443
                                     libvlc_event_type_t event_type,
                                     libvlc_callback_t pf_callback,
444
                                     void *p_user_data )
Filippo Carone's avatar
Filippo Carone committed
445
{
446 447
    libvlc_event_listeners_group_t * listeners_group;
    libvlc_event_listener_t * listener;
448
    int i, j;
449
    bool found = false;
450

451
    vlc_mutex_lock(&p_event_manager->lock);
452 453 454
    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);
455 456
        if( listeners_group->event_type == event_type )
        {
457
            for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
458
            {
459
                listener = vlc_array_item_at_index(&listeners_group->listeners, j);
460 461 462 463 464
                if( listener->event_type == event_type &&
                    listener->pf_callback == pf_callback &&
                    listener->p_user_data == p_user_data )
                {
                    /* that's our listener */
465

466 467
                    /* 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
468
                    listeners_group->b_sublistener_removed = true;
469

470
                    free( listener );
471
                    vlc_array_remove( &listeners_group->listeners, j );
472 473
                    found = true;
                    break;
474
                }
475
            }
476
        }
477
    }
478
    vlc_mutex_unlock(&p_event_manager->lock);
479

480
    assert(found);
Filippo Carone's avatar
Filippo Carone committed
481
}