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
#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 )
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 83 84
    p_em->p_obj = p_obj;
    p_em->async_event_queue = NULL;

85
    vlc_array_init( &p_em->listeners_groups );
86
    vlc_mutex_init( &p_em->object_lock );
87
    vlc_mutex_init_recursive( &p_em->event_sending_lock );
88
    return p_em;
89 90
}

91
/**************************************************************************
92
 *       libvlc_event_manager_release (internal) :
93
 *
94
 * Release an object's event manager.
95
 **************************************************************************/
96
void libvlc_event_manager_release( libvlc_event_manager_t * p_em )
97
{
98 99
    libvlc_event_listeners_group_t * p_lg;
    int i,j ;
100

101 102
    libvlc_event_async_fini(p_em);

103 104
    vlc_mutex_destroy( &p_em->event_sending_lock );
    vlc_mutex_destroy( &p_em->object_lock );
105

106 107 108 109 110
    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
111
            free( vlc_array_item_at_index( &p_lg->listeners, j ) );
112

113 114 115 116
        vlc_array_clear( &p_lg->listeners );
        free( p_lg );
    }
    vlc_array_clear( &p_em->listeners_groups );
117
    free( p_em );
118 119
}

120
/**************************************************************************
121
 *       libvlc_event_manager_register_event_type (internal) :
122
 *
123
 * Init an object's event manager.
124
 **************************************************************************/
125
void libvlc_event_manager_register_event_type(
126
        libvlc_event_manager_t * p_em,
127
        libvlc_event_type_t event_type )
128
{
129
    libvlc_event_listeners_group_t * listeners_group;
130
    listeners_group = xmalloc(sizeof(libvlc_event_listeners_group_t));
131
    listeners_group->event_type = event_type;
132
    vlc_array_init( &listeners_group->listeners );
133

134
    vlc_mutex_lock( &p_em->object_lock );
135
    vlc_array_append( &p_em->listeners_groups, listeners_group );
136
    vlc_mutex_unlock( &p_em->object_lock );
137
}
138

139
/**************************************************************************
140
 *       libvlc_event_send (internal) :
141
 *
142
 * Send a callback.
143
 **************************************************************************/
144 145
void libvlc_event_send( libvlc_event_manager_t * p_em,
                        libvlc_event_t * p_event )
146
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
147
    libvlc_event_listeners_group_t * listeners_group = NULL;
148
    libvlc_event_listener_t * listener_cached;
149
    libvlc_event_listener_t * listener;
150 151 152
    libvlc_event_listener_t * array_listeners_cached = NULL;
    int i, i_cached_listeners = 0;

153 154 155
    /* Fill event with the sending object now */
    p_event->p_obj = p_em->p_obj;

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

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

            listener_cached = array_listeners_cached;
179 180 181 182
            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) );
183
                listener_cached++;
184
            }
185 186
            break;
        }
187
    }
188

189 190 191
    if( !listeners_group )
    {
        free( array_listeners_cached );
192
        vlc_mutex_unlock( &p_em->object_lock );
Rafaël Carré's avatar
Rafaël Carré committed
193
        vlc_mutex_unlock( &p_em->event_sending_lock );
194 195 196
        return;
    }

Rafaël Carré's avatar
Rafaël Carré committed
197 198 199 200 201
    /* 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;

202 203 204 205 206
    vlc_mutex_unlock( &p_em->object_lock );

    listener_cached = array_listeners_cached;
    for( i = 0; i < i_cached_listeners; i++ )
    {
207
        if(listener_cached->is_asynchronous)
208
        {
209 210 211 212 213 214
            /* 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 */
215

216 217
            listener_cached->pf_callback( p_event, listener_cached->p_user_data );
            listener_cached++;
218

219
            if( listeners_group->b_sublistener_removed )
220
            {
221 222 223 224 225 226 227 228 229 230
                /* 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;
                }
231
            }
232
        }
233
    }
234
    vlc_mutex_unlock( &p_em->event_sending_lock );
235

236
    free( array_listeners_cached );
237 238
}

239 240 241 242
/*
 * Public libvlc functions
 */

243 244 245 246 247 248 249 250 251 252 253 254
#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)
255
    DEF(MediaParsedChanged)
256 257
    DEF(MediaFreed)
    DEF(MediaStateChanged)
258
    DEF(MediaSubItemTreeAdded)
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277

    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)
278
    DEF(MediaPlayerVout)
279
    DEF(MediaPlayerScrambledChanged)
280 281 282
    DEF(MediaPlayerESAdded)
    DEF(MediaPlayerESDeleted)
    DEF(MediaPlayerESSelected)
283 284
    DEF(MediaPlayerCorked)
    DEF(MediaPlayerUncorked)
285 286
    DEF(MediaPlayerMuted)
    DEF(MediaPlayerUnmuted)
287
    DEF(MediaPlayerAudioVolume)
288
    DEF(MediaPlayerAudioDevice)
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313

    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)
314 315 316 317
    DEF(VlmMediaInstanceStatusPlaying)
    DEF(VlmMediaInstanceStatusPause)
    DEF(VlmMediaInstanceStatusEnd)
    DEF(VlmMediaInstanceStatusError)
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
};
#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;
}

338
/**************************************************************************
339
 *       event_attach (internal) :
340 341 342
 *
 * Add a callback for an event.
 **************************************************************************/
343
static
344 345 346 347
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
348
{
349 350
    libvlc_event_listeners_group_t * listeners_group;
    libvlc_event_listener_t * listener;
351
    int i;
352

353
    listener = malloc(sizeof(libvlc_event_listener_t));
354 355
    if( unlikely(listener == NULL) )
        return ENOMEM;
356

357 358 359
    listener->event_type = event_type;
    listener->p_user_data = p_user_data;
    listener->pf_callback = pf_callback;
360
    listener->is_asynchronous = is_asynchronous;
361

362
    vlc_mutex_lock( &p_event_manager->object_lock );
363 364 365
    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);
366 367
        if( listeners_group->event_type == listener->event_type )
        {
368
            vlc_array_append( &listeners_group->listeners, listener );
369
            vlc_mutex_unlock( &p_event_manager->object_lock );
370
            return 0;
371
        }
372
    }
373
    vlc_mutex_unlock( &p_event_manager->object_lock );
374

375
    free(listener);
376 377
    fprintf( stderr, "This object event manager doesn't know about '%s' events",
             libvlc_event_type_name(event_type) );
378
    vlc_assert_unreachable();
379
    return -1;
380 381 382 383 384 385 386
}

/**************************************************************************
 *       libvlc_event_attach (public) :
 *
 * Add a callback for an event.
 **************************************************************************/
387
int libvlc_event_attach( libvlc_event_manager_t * p_event_manager,
388 389
                         libvlc_event_type_t event_type,
                         libvlc_callback_t pf_callback,
390
                         void *p_user_data )
391
{
392 393
    return event_attach(p_event_manager, event_type, pf_callback, p_user_data,
                        false /* synchronous */);
394 395 396 397 398 399 400 401 402 403
}

/**************************************************************************
 *       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,
404
                         void *p_user_data )
405
{
406 407
    event_attach(p_event_manager, event_type, pf_callback, p_user_data,
                 true /* asynchronous */);
Filippo Carone's avatar
Filippo Carone committed
408 409
}

410
/**************************************************************************
411
 *       libvlc_event_detach (public) :
412 413 414
 *
 * Remove a callback for an event.
 **************************************************************************/
415
void libvlc_event_detach( libvlc_event_manager_t *p_event_manager,
416 417
                                     libvlc_event_type_t event_type,
                                     libvlc_callback_t pf_callback,
418
                                     void *p_user_data )
Filippo Carone's avatar
Filippo Carone committed
419
{
420 421
    libvlc_event_listeners_group_t * listeners_group;
    libvlc_event_listener_t * listener;
422
    int i, j;
423
    bool found = false;
424

425
    vlc_mutex_lock( &p_event_manager->event_sending_lock );
426
    vlc_mutex_lock( &p_event_manager->object_lock );
427 428 429
    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);
430 431
        if( listeners_group->event_type == event_type )
        {
432
            for( j = 0; j < vlc_array_count(&listeners_group->listeners); j++)
433
            {
434
                listener = vlc_array_item_at_index(&listeners_group->listeners, j);
435 436 437 438 439
                if( listener->event_type == event_type &&
                    listener->pf_callback == pf_callback &&
                    listener->p_user_data == p_user_data )
                {
                    /* that's our listener */
440

441 442
                    /* 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
443
                    listeners_group->b_sublistener_removed = true;
444

445
                    free( listener );
446
                    vlc_array_remove( &listeners_group->listeners, j );
447 448
                    found = true;
                    break;
449
                }
450
            }
451
        }
452
    }
453
    vlc_mutex_unlock( &p_event_manager->object_lock );
454
    vlc_mutex_unlock( &p_event_manager->event_sending_lock );
455

456 457 458 459 460 461 462 463 464
    /* 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);

465
    assert(found);
Filippo Carone's avatar
Filippo Carone committed
466
}