Index: pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h	(revision 3892)
+++ pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev.h	(revision 3893)
@@ -752,17 +752,4 @@
 
 /**
- * Get the event publisher object for the video stream. Caller typically use
- * the returned object to subscribe or unsubscribe events from the video
- * stream.
- *
- * @param strm      The video stream.
- *
- * @return          The event publisher object.
- */
-PJ_DECL(pjmedia_event_publisher*)
-pjmedia_vid_dev_stream_get_event_publisher(pjmedia_vid_dev_stream *strm);
-
-
-/**
  * Request one frame from the stream. Application needs to call this function
  * periodically only if the stream doesn't support "active interface", i.e.
Index: pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev_imp.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev_imp.h	(revision 3892)
+++ pjproject/trunk/pjmedia/include/pjmedia-videodev/videodev_imp.h	(revision 3893)
@@ -189,7 +189,4 @@
     /** Operations */
     pjmedia_vid_dev_stream_op *op;
-
-    /** Event producer */
-    pjmedia_event_publisher	epub;
 };
 
Index: pjproject/trunk/pjmedia/include/pjmedia/event.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/event.h	(revision 3892)
+++ pjproject/trunk/pjmedia/include/pjmedia/event.h	(revision 3893)
@@ -26,5 +26,4 @@
 #include <pjmedia/format.h>
 #include <pjmedia/signatures.h>
-#include <pj/list.h>
 
 PJ_BEGIN_DECL
@@ -89,14 +88,4 @@
 
 /**
- * Forward declaration for event subscription.
- */
-typedef struct pjmedia_event_subscription pjmedia_event_subscription;
-
-/**
- * Forward declaration for event publisher.
- */
-typedef struct pjmedia_event_publisher pjmedia_event_publisher;
-
-/**
  * Additional data/parameters for media format changed event
  * (PJMEDIA_EVENT_FMT_CHANGED).
@@ -163,6 +152,6 @@
 /**
  * This structure describes a media event. It consists mainly of the event
- * type and additional data/parameters for the event. Event publishers need
- * to use #pjmedia_event_init() to initialize this event structure with
+ * type and additional data/parameters for the event. Applications can
+ * use #pjmedia_event_init() to initialize this event structure with
  * basic information about the event.
  */
@@ -180,25 +169,20 @@
 
     /**
-     * This keeps count on the number of subscribers that have
-     * processed this event.
-     */
-    unsigned				 proc_cnt;
-
-    /**
-     * The object signature of the event publisher. Application may use
-     * this to check which publisher published the event.
-     */
-    pjmedia_obj_sig			 epub_sig;
-
-    /**
      * Pointer information about the source of this event. This field
-     * is provided mainly so that the event subscribers can compare it
-     * against the publisher that it subscribed the events from initially,
-     * a publisher can republish events from other publisher. Event
-     * subscription must be careful when using this pointer other than for
-     * comparison purpose, since access to the publisher may require special
-     * care (e.g. mutex locking).
-     */
-    const pjmedia_event_publisher	*epub;
+     * is provided mainly for comparison purpose so that event subscribers
+     * can check which source the event originated from. Usage of this
+     * pointer for other purpose may require special care such as mutex
+     * locking or checking whether the object is already destroyed.
+     */
+    const void	                        *src;
+
+    /**
+     * Pointer information about the publisher of this event. This field
+     * is provided mainly for comparison purpose so that event subscribers
+     * can check which object published the event. Usage of this
+     * pointer for other purpose may require special care such as mutex
+     * locking or checking whether the object is already destroyed.
+     */
+    const void	                        *epub;
 
     /**
@@ -239,50 +223,90 @@
 
 /**
- * The callback to receive media events. The callback should increase
- * \a proc_cnt field of the event if it processes the event.
- *
- * @param esub		The subscription that was made initially to receive
- * 			this event.
- * @param event		The media event itself.
+ * The callback to receive media events.
+ *
+ * @param event		The media event.
+ * @param user_data	The user data associated with the callback.
  *
  * @return		If the callback returns non-PJ_SUCCESS, this return
- * 			code may be propagated back to the producer.
- */
-typedef pj_status_t pjmedia_event_cb(pjmedia_event_subscription *esub,
-				     pjmedia_event *event);
-
-/**
- * This structure keeps the data needed to maintain an event subscription.
- * This data is normally kept by event publishers.
- */
-struct pjmedia_event_subscription
-{
-    /** Standard list members */
-    PJ_DECL_LIST_MEMBER(pjmedia_event_subscription);
-
-    /** Callback that will be called by publisher to report events. */
-    pjmedia_event_cb	*cb;
-
-    /** User data for this subscription */
-    void		*user_data;
-
-    /** Current publisher it is subscribed to */
-    pjmedia_event_publisher *subscribe_to;
-};
-
-/**
- * This describes an event publisher. An event publisher is an object that
- * maintains event subscriptions. When an event is published on behalf of
- * a publisher with #pjmedia_event_publish(), that event will be propagated
- * to all of the subscribers registered to the publisher.
- */
-struct pjmedia_event_publisher
-{
-    /** The object signature of the publisher */
-    pjmedia_obj_sig		sig;
-
-    /** List of subscriptions for this event publisher */
-    pjmedia_event_subscription	subscription_list;
-};
+ * 			code may be propagated back to the caller.
+ */
+typedef pj_status_t pjmedia_event_cb(pjmedia_event *event,
+                                     void *user_data);
+
+/**
+ * This enumeration describes flags for event publication via
+ * #pjmedia_event_publish().
+ */
+typedef enum pjmedia_event_publish_flag
+{
+    /**
+     * Publisher will only post the event to the event manager. It is the
+     * event manager that will later notify all the publisher's subscribers.
+     */
+    PJMEDIA_EVENT_PUBLISH_POST_EVENT = 1
+
+} pjmedia_event_publish_flag;
+
+/**
+ * Event manager flag.
+ */
+typedef enum pjmedia_event_mgr_flag
+{
+    /**
+     * Tell the event manager not to create any event worker thread.
+     */
+    PJMEDIA_EVENT_MGR_NO_THREAD = 1
+
+} pjmedia_event_mgr_flag;
+
+/**
+ * Opaque data type for event manager. Typically, the event manager
+ * is a singleton instance, although application may instantiate more than one
+ * instances of this if required.
+ */
+typedef struct pjmedia_event_mgr pjmedia_event_mgr;
+
+/**
+ * Create a new event manager instance. This will also set the pointer
+ * to the singleton instance if the value is still NULL.
+ *
+ * @param pool		Pool to allocate memory from.
+ * @param options       Options. Bitmask flags from #pjmedia_event_mgr_flag
+ * @param mgr		Pointer to hold the created instance of the
+ * 			event manager.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_event_mgr_create(pj_pool_t *pool,
+                                              unsigned options,
+				              pjmedia_event_mgr **mgr);
+
+/**
+ * Get the singleton instance of the event manager.
+ *
+ * @return		The instance.
+ */
+PJ_DECL(pjmedia_event_mgr*) pjmedia_event_mgr_instance(void);
+
+/**
+ * Manually assign a specific event manager instance as the singleton
+ * instance. Normally this is not needed if only one instance is ever
+ * going to be created, as the library automatically assign the singleton
+ * instance.
+ *
+ * @param mgr		The instance to be used as the singleton instance.
+ * 			Application may specify NULL to clear the singleton
+ * 			singleton instance.
+ */
+PJ_DECL(void) pjmedia_event_mgr_set_instance(pjmedia_event_mgr *mgr);
+
+/**
+ * Destroy an event manager. If the manager happens to be the singleton
+ * instance, the singleton instance will be set to NULL.
+ *
+ * @param mgr		The eventmanager. Specify NULL to use
+ * 			the singleton instance.
+ */
+PJ_DECL(void) pjmedia_event_mgr_destroy(pjmedia_event_mgr *mgr);
 
 /**
@@ -293,108 +317,74 @@
  * @param ts		Event timestamp. May be set to NULL to set the event
  * 			timestamp to zero.
- * @param epub		Event publisher.
+ * @param src		Event source.
  */
 PJ_DECL(void) pjmedia_event_init(pjmedia_event *event,
                                  pjmedia_event_type type,
                                  const pj_timestamp *ts,
-                                 const pjmedia_event_publisher *epub);
-
-/**
- * Initialize an event publisher structure.
- *
- * @param epub		The event publisher.
- * @param sig		The object signature of the publisher.
- */
-PJ_DECL(void) pjmedia_event_publisher_init(pjmedia_event_publisher *epub,
-                                           pjmedia_obj_sig sig);
-
-/**
- * Initialize subscription data.
- *
- * @param esub		The event subscription.
- * @param cb		The callback to receive events.
- * @param user_data	Arbitrary user data to be associated with the
- * 			subscription.
- */
-PJ_DECL(void) pjmedia_event_subscription_init(pjmedia_event_subscription *esub,
-                                              pjmedia_event_cb *cb,
-                                              void *user_data);
-
-/**
- * Subscribe to events published by the specified publisher using the
- * specified subscription object. The callback and user data fields of
- * the subscription object must have been initialized prior to calling
- * this function, and the subscription object must be kept alive throughout
- * the duration of the subscription (e.g. it must not be allocated from
- * the stack).
- *
- * Note that the subscriber may receive not only events emitted by
+                                 const void *src);
+
+/**
+ * Subscribe a callback function to events published by the specified
+ * publisher. Note that the subscriber may receive not only events emitted by
  * the specific publisher specified in the argument, but also from other
  * publishers contained by the publisher, if the publisher is republishing
  * events from other publishers.
  *
+ * @param mgr		The event manager.
+ * @param pool          Pool to allocate memory from.
+ * @param cb            The callback function to receive the event.
+ * @param user_data     The user data to be associated with the callback
+ *                      function.
  * @param epub		The event publisher.
- * @param esub 		The event subscription object.
  *
  * @return		PJ_SUCCESS on success or the appropriate error code.
  */
-PJ_DECL(pj_status_t) pjmedia_event_subscribe(pjmedia_event_publisher *epub,
-                                             pjmedia_event_subscription *esub);
-
-/**
- * Unsubscribe the specified subscription object from publisher it is
- * currently subscribed to. If the subscription object is not currently
- * subscribed to anything, the function will do nothing.
- *
- * @param esub		The event subscription object, which must be the same
- * 			object that was given to #pjmedia_event_subscribe().
+PJ_DECL(pj_status_t) pjmedia_event_subscribe(pjmedia_event_mgr *mgr,
+                                             pj_pool_t *pool,
+                                             pjmedia_event_cb *cb,
+                                             void *user_data,
+                                             void *epub);
+
+/**
+ * Unsubscribe the callback associated with the user data from a publisher.
+ * If the user data is not specified, this function will do the
+ * unsubscription for all user data. If the publisher, epub, is not
+ * specified, this function will do the unsubscription from all publishers.
+ *
+ * @param mgr		The event manager.
+ * @param cb            The callback function.
+ * @param user_data     The user data associated with the callback
+ *                      function, can be NULL.
+ * @param epub		The event publisher, can be NULL.
  *
  * @return		PJ_SUCCESS on success or the appropriate error code.
  */
 PJ_DECL(pj_status_t)
-pjmedia_event_unsubscribe(pjmedia_event_subscription *esub);
-
-/**
- * Check if the specified publisher has subscribers.
- *
- * @param epub		The event publisher.
- *
- * @return		PJ_TRUE if the publisher has at least one subscriber.
- */
-PJ_DECL(pj_bool_t)
-pjmedia_event_publisher_has_sub(pjmedia_event_publisher *epub);
+pjmedia_event_unsubscribe(pjmedia_event_mgr *mgr,
+                          pjmedia_event_cb *cb,
+                          void *user_data,
+                          void *epub);
 
 /**
  * Publish the specified event to all subscribers of the specified event
- * publisher.
- *
+ * publisher. By default, the function will call all the subcribers'
+ * callbacks immediately. If the publisher uses the flag
+ * PJMEDIA_EVENT_PUBLISH_POST_EVENT, publisher will only post the event
+ * to the event manager and return immediately. It is the event manager
+ * that will later notify all the publisher's subscribers.
+ *
+ * @param mgr		The event manager.
  * @param epub		The event publisher.
  * @param event		The event to be published.
+ * @param flag          Publication flag.
  *
  * @return		PJ_SUCCESS only if all subscription callbacks returned
  * 			PJ_SUCCESS.
  */
-PJ_DECL(pj_status_t) pjmedia_event_publish(pjmedia_event_publisher *epub,
-                                           pjmedia_event *event);
-
-/**
- * Subscribe to events produced by the source publisher in \a esrc and
- * republish the events to all subscribers in \a epub publisher.
- *
- * @param esrc		The event source from which events will be
- * 			republished.
- * @param epub		Events from the event source above will be
- * 			republished to subscribers of this publisher.
- * @param esub		The subscription object to be used to subscribe
- * 			to \a esrc. This doesn't need to be initialized,
- * 			but it must be kept alive throughout the lifetime
- * 			of the subsciption.
- *
- * @return		PJ_SUCCESS only if all subscription callbacks returned
- * 			PJ_SUCCESS.
- */
-PJ_DECL(pj_status_t) pjmedia_event_republish(pjmedia_event_publisher *esrc,
-                                             pjmedia_event_publisher *epub,
-                                             pjmedia_event_subscription *esub);
+PJ_DECL(pj_status_t) pjmedia_event_publish(pjmedia_event_mgr *mgr,
+                                           void *epub,
+                                           pjmedia_event *event,
+                                           pjmedia_event_publish_flag flag);
+
 
 /**
Index: pjproject/trunk/pjmedia/include/pjmedia/port.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/port.h	(revision 3892)
+++ pjproject/trunk/pjmedia/include/pjmedia/port.h	(revision 3893)
@@ -400,9 +400,4 @@
     pj_status_t (*on_destroy)(struct pjmedia_port *this_port);
 
-    /**
-     * Get event publisher for this media port, if any.
-     */
-    pjmedia_event_publisher *(*get_event_pub)(struct pjmedia_port *this_port);
-
 } pjmedia_port;
 
@@ -485,19 +480,8 @@
 
 /**
- * Get the event publisher for the media port, if any.
+ * Destroy port (and subsequent downstream ports)
  *
  * @param port	    The media port.
  *
- * @return	    The event publisher, or NULL if the port does not publish
- * 		    events.
- */
-PJ_DECL(pjmedia_event_publisher*)
-pjmedia_port_get_event_publisher(pjmedia_port *port);
-
-/**
- * Destroy port (and subsequent downstream ports)
- *
- * @param port	    The media port.
- *
  * @return	    PJ_SUCCESS on success, or the appropriate error code.
  */
Index: pjproject/trunk/pjmedia/include/pjmedia/vid_codec.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/vid_codec.h	(revision 3892)
+++ pjproject/trunk/pjmedia/include/pjmedia/vid_codec.h	(revision 3893)
@@ -250,7 +250,4 @@
     /** Operations to codec. */
     pjmedia_vid_codec_op	*op;
-
-    /** Event publisher object */
-    pjmedia_event_publisher	 epub;
 };
 
Index: pjproject/trunk/pjmedia/include/pjmedia/vid_port.h
===================================================================
--- pjproject/trunk/pjmedia/include/pjmedia/vid_port.h	(revision 3892)
+++ pjproject/trunk/pjmedia/include/pjmedia/vid_port.h	(revision 3893)
@@ -106,14 +106,4 @@
 
 /**
- * Get the event publisher instance of the video port.
- *
- * @param vid_port	The video port.
- *
- * @return		The event publisher of the video port.
- */
-PJ_DECL(pjmedia_event_publisher*)
-pjmedia_vid_port_get_event_publisher(pjmedia_vid_port *vid_port);
-
-/**
  * Return the underlying video stream of the video port.
  *
Index: pjproject/trunk/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c	(revision 3893)
@@ -1610,13 +1610,13 @@
 
 	    /* Broadcast event */
-	    if (pjmedia_event_publisher_has_sub(&codec->epub)) {
+	    {
 		pjmedia_event event;
 
 		pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED,
-				   &input->timestamp, &codec->epub);
+				   &input->timestamp, codec);
 		event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING;
 		pj_memcpy(&event.data.fmt_changed.new_fmt, &ff->param.dec_fmt,
 			  sizeof(ff->param.dec_fmt));
-		pjmedia_event_publish(&codec->epub, &event);
+		pjmedia_event_publish(NULL, codec, &event, 0);
 	    }
 	}
@@ -1652,11 +1652,11 @@
 
         /* Check if we got key frame */
-        if (avframe.key_frame && pjmedia_event_publisher_has_sub(&codec->epub))
+        if (avframe.key_frame)
         {
             pjmedia_event event;
 
             pjmedia_event_init(&event, PJMEDIA_EVENT_KEY_FRAME_FOUND,
-                               &output->timestamp, &codec->epub);
-            pjmedia_event_publish(&codec->epub, &event);
+                               &output->timestamp, codec);
+            pjmedia_event_publish(NULL, codec, &event, 0);
         }
     } else {
Index: pjproject/trunk/pjmedia/src/pjmedia-videodev/colorbar_dev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-videodev/colorbar_dev.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia-videodev/colorbar_dev.c	(revision 3893)
@@ -417,5 +417,4 @@
     pj_memcpy(&strm->vafp, &vafp, sizeof(vafp));
     strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1);
-    pjmedia_event_publisher_init(&strm->base.epub, PJMEDIA_SIG_VID_DEV_COLORBAR);
 
     for (i = 0; i < vfi->plane_cnt; ++i) {
Index: pjproject/trunk/pjmedia/src/pjmedia-videodev/dshow_dev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-videodev/dshow_dev.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia-videodev/dshow_dev.c	(revision 3893)
@@ -870,5 +870,4 @@
     pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
     strm->user_data = user_data;
-    pjmedia_event_publisher_init(&strm->base.epub, PJMEDIA_SIG_VID_DEV_DSHOW);
 
     if (param->dir & PJMEDIA_DIR_CAPTURE) {
Index: pjproject/trunk/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c	(revision 3893)
@@ -384,5 +384,4 @@
     strm->pool = pool;
     pj_memcpy(&strm->param, param, sizeof(*param));
-    pjmedia_event_publisher_init(&strm->base.epub);
 
     /* Done */
Index: pjproject/trunk/pjmedia/src/pjmedia-videodev/sdl_dev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-videodev/sdl_dev.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia-videodev/sdl_dev.c	(revision 3893)
@@ -309,5 +309,5 @@
     if (strm)
         pjmedia_event_init(pevent, PJMEDIA_EVENT_NONE, &strm->last_ts,
-		           &strm->base.epub);
+		           strm);
 
     return strm;
@@ -355,5 +355,5 @@
             pj_status_t status;
 
-	    pjmedia_event_publish(&strm->base.epub, &pevent);
+	    pjmedia_event_publish(NULL, strm, &pevent, 0);
 
 	    switch (pevent.type) {
@@ -376,7 +376,6 @@
                 sdl_destroy_all(strm);
                 pjmedia_event_init(&pevent, PJMEDIA_EVENT_WND_CLOSED,
-                                   &strm->last_ts,
-                                   &strm->base.epub);
-                pjmedia_event_publish(&strm->base.epub, &pevent);
+                                   &strm->last_ts, strm);
+                pjmedia_event_publish(NULL, strm, &pevent, 0);
 
                 /*
@@ -917,5 +916,4 @@
     pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
     strm->user_data = user_data;
-    pjmedia_event_publisher_init(&strm->base.epub, PJMEDIA_SIG_VID_DEV_SDL);
 
     /* Create render stream here */
Index: pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia-videodev/videodev.c	(revision 3893)
@@ -786,10 +786,4 @@
 }
 
-PJ_DEF(pjmedia_event_publisher*)
-pjmedia_vid_dev_stream_get_event_publisher(pjmedia_vid_dev_stream *strm)
-{
-    return &strm->epub;
-}
-
 /* API: Start the stream. */
 PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_start(pjmedia_vid_dev_stream *strm)
Index: pjproject/trunk/pjmedia/src/pjmedia/event.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/event.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia/event.c	(revision 3893)
@@ -20,19 +20,205 @@
 #include <pjmedia/errno.h>
 #include <pj/assert.h>
+#include <pj/list.h>
 #include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
 #include <pj/string.h>
 
 #define THIS_FILE	"event.c"
 
-#if 1
-#   define TRACE_(x)	PJ_LOG(6,x)
-#else
-#   define TRACE_(x)
-#endif
+#define MAX_EVENTS 16
+
+typedef struct esub esub;
+
+struct esub
+{
+    PJ_DECL_LIST_MEMBER(esub);
+
+    pjmedia_event_cb    *cb;
+    void                *user_data;
+    void                *epub;
+};
+
+typedef struct event_queue
+{
+    pjmedia_event   events[MAX_EVENTS]; /**< array of events.           */
+    int             head, tail;
+    pj_bool_t       is_full;
+} event_queue;
+
+struct pjmedia_event_mgr
+{
+    pj_thread_t    *thread;             /**< worker thread.             */
+    pj_bool_t       is_quitting;
+    pj_sem_t       *sem;
+    pj_mutex_t     *mutex;
+    event_queue     ev_queue;
+    event_queue    *pub_ev_queue;       /**< publish() event queue.     */
+    esub            esub_list;          /**< list of subscribers.       */
+    esub           *th_next_sub,        /**< worker thread's next sub.  */
+                   *pub_next_sub;       /**< publish() next sub.        */
+};
+
+static pjmedia_event_mgr *event_manager_instance;
+
+static pj_status_t event_queue_add_event(event_queue* ev_queue,
+                                         pjmedia_event *event)
+{
+    if (ev_queue->is_full) {
+        char ev_name[5];
+
+        /* This event will be ignored. */
+        PJ_LOG(4, (THIS_FILE, "Lost event %s from publisher [0x%p] "
+                              "due to full queue.",
+                              pjmedia_fourcc_name(event->type, ev_name),
+                              event->epub));
+
+        return PJ_ETOOMANY;
+    }
+
+    pj_memcpy(&ev_queue->events[ev_queue->tail], event, sizeof(*event));
+    ev_queue->tail = (ev_queue->tail + 1) % MAX_EVENTS;
+    if (ev_queue->tail == ev_queue->head)
+        ev_queue->is_full = PJ_TRUE;
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t event_mgr_distribute_events(pjmedia_event_mgr *mgr,
+                                               event_queue *ev_queue,
+                                               esub **next_sub)
+{
+    pj_status_t err = PJ_SUCCESS;
+    esub * sub = mgr->esub_list.next;
+    pjmedia_event *ev = &ev_queue->events[ev_queue->head];
+
+    while (sub != &mgr->esub_list) {
+        *next_sub = sub->next;
+
+        /* Check if the subscriber is interested in 
+         * receiving the event from the publisher.
+         */
+        if (sub->epub == ev->epub || !sub->epub) {
+            pj_status_t status = (*sub->cb)(ev, sub->user_data);
+            if (status != PJ_SUCCESS && err == PJ_SUCCESS)
+	        err = status;
+        }
+	sub = *next_sub;
+    }
+    *next_sub = NULL;
+
+    ev_queue->head = (ev_queue->head + 1) % MAX_EVENTS;
+    ev_queue->is_full = PJ_FALSE;
+
+    return err;
+}
+
+/* Event worker thread function. */
+static int event_worker_thread(void *arg)
+{
+    pjmedia_event_mgr *mgr = (pjmedia_event_mgr *)arg;
+
+    while (1) {
+	/* Wait until there is an event. */
+        pj_sem_wait(mgr->sem);
+
+        if (mgr->is_quitting)
+            break;
+
+        pj_mutex_lock(mgr->mutex);
+        event_mgr_distribute_events(mgr, &mgr->ev_queue, &mgr->th_next_sub);
+        pj_mutex_unlock(mgr->mutex);
+    }
+
+    return 0;
+}
+
+PJ_DEF(pj_status_t) pjmedia_event_mgr_create(pj_pool_t *pool,
+                                             unsigned options,
+				             pjmedia_event_mgr **p_mgr)
+{
+    pjmedia_event_mgr *mgr;
+    pj_status_t status;
+
+    mgr = PJ_POOL_ZALLOC_T(pool, pjmedia_event_mgr);
+    pj_list_init(&mgr->esub_list);
+
+    if (!(options & PJMEDIA_EVENT_MGR_NO_THREAD)) {
+        status = pj_sem_create(pool, "ev_sem", 0, MAX_EVENTS + 1, &mgr->sem);
+        if (status != PJ_SUCCESS)
+            return status;
+
+        status = pj_thread_create(pool, "ev_thread", &event_worker_thread,
+                                  mgr, 0, 0, &mgr->thread);
+        if (status != PJ_SUCCESS) {
+            pjmedia_event_mgr_destroy(mgr);
+            return status;
+        }
+    }
+
+    status = pj_mutex_create_recursive(pool, "ev_mutex", &mgr->mutex);
+    if (status != PJ_SUCCESS) {
+        pjmedia_event_mgr_destroy(mgr);
+        return status;
+    }
+
+    if (!event_manager_instance)
+	event_manager_instance = mgr;
+
+    if (p_mgr)
+	*p_mgr = mgr;
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pjmedia_event_mgr*) pjmedia_event_mgr_instance(void)
+{
+    return event_manager_instance;
+}
+
+PJ_DEF(void) pjmedia_event_mgr_set_instance(pjmedia_event_mgr *mgr)
+{
+    event_manager_instance = mgr;
+}
+
+PJ_DEF(void) pjmedia_event_mgr_destroy(pjmedia_event_mgr *mgr)
+{
+    esub *sub;
+
+    if (!mgr) mgr = pjmedia_event_mgr_instance();
+    PJ_ASSERT_ON_FAIL(mgr != NULL, return);
+
+    if (mgr->thread) {
+        mgr->is_quitting = PJ_TRUE;
+        pj_sem_post(mgr->sem);
+        pj_thread_join(mgr->thread);
+    }
+
+    if (mgr->sem) {
+        pj_sem_destroy(mgr->sem);
+        mgr->sem = NULL;
+    }
+
+    if (mgr->mutex) {
+        pj_mutex_destroy(mgr->mutex);
+        mgr->mutex = NULL;
+    }
+
+    sub = mgr->esub_list.next;
+    while (sub != &mgr->esub_list) {
+	esub *next = sub->next;
+	pj_list_erase(sub);
+	sub = next;
+    }
+
+    if (event_manager_instance == mgr)
+	event_manager_instance = NULL;
+}
 
 PJ_DEF(void) pjmedia_event_init( pjmedia_event *event,
                                  pjmedia_event_type type,
                                  const pj_timestamp *ts,
-                                 const pjmedia_event_publisher *epub)
+                                 const void *src)
 {
     pj_bzero(event, sizeof(*event));
@@ -40,129 +226,135 @@
     if (ts)
 	event->timestamp.u64 = ts->u64;
+    event->epub = event->src = src;
+}
+
+PJ_DEF(pj_status_t) pjmedia_event_subscribe( pjmedia_event_mgr *mgr,
+                                             pj_pool_t *pool,
+                                             pjmedia_event_cb *cb,
+                                             void *user_data,
+                                             void *epub)
+{
+    esub *sub;
+
+    PJ_ASSERT_RETURN(pool && cb, PJ_EINVAL);
+
+    if (!mgr) mgr = pjmedia_event_mgr_instance();
+    PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+    pj_mutex_lock(mgr->mutex);
+    /* Check whether callback function with the same user data is already
+     * subscribed to the publisher. This is to prevent the callback function
+     * receiving the same event from the same publisher more than once.
+     */
+    sub = mgr->esub_list.next;
+    while (sub != &mgr->esub_list) {
+	esub *next = sub->next;
+        if (sub->cb == cb && sub->user_data == user_data &&
+            sub->epub == epub)
+        {
+            pj_mutex_unlock(mgr->mutex);
+            return PJ_SUCCESS;
+        }
+	sub = next;
+    }
+
+    sub = PJ_POOL_ZALLOC_T(pool, esub);
+    sub->cb = cb;
+    sub->user_data = user_data;
+    sub->epub = epub;
+    pj_list_push_back(&mgr->esub_list, sub);
+    pj_mutex_unlock(mgr->mutex);
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t)
+pjmedia_event_unsubscribe(pjmedia_event_mgr *mgr,
+                          pjmedia_event_cb *cb,
+                          void *user_data,
+                          void *epub)
+{
+    esub *sub;
+
+    PJ_ASSERT_RETURN(cb, PJ_EINVAL);
+
+    if (!mgr) mgr = pjmedia_event_mgr_instance();
+    PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+    pj_mutex_lock(mgr->mutex);
+    sub = mgr->esub_list.next;
+    while (sub != &mgr->esub_list) {
+	esub *next = sub->next;
+        if (sub->cb == cb && (sub->user_data == user_data || !user_data) &&
+            (sub->epub == epub || !epub))
+        {
+            /* If the worker thread or pjmedia_event_publish() API is
+             * in the process of distributing events, make sure that
+             * its pointer to the next subscriber stays valid.
+             */
+            if (mgr->th_next_sub == sub)
+                mgr->th_next_sub = sub->next;
+            if (mgr->pub_next_sub == sub)
+                mgr->pub_next_sub = sub->next;
+            pj_list_erase(sub);
+            if (user_data && epub)
+                break;
+        }
+	sub = next;
+    }
+    pj_mutex_unlock(mgr->mutex);
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_event_publish( pjmedia_event_mgr *mgr,
+                                           void *epub,
+                                           pjmedia_event *event,
+                                           pjmedia_event_publish_flag flag)
+{
+    pj_status_t err = PJ_SUCCESS;
+
+    PJ_ASSERT_RETURN(epub && event, PJ_EINVAL);
+
+    if (!mgr) mgr = pjmedia_event_mgr_instance();
+    PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
     event->epub = epub;
-    if (epub)
-	event->epub_sig = epub->sig;
-}
-
-PJ_DEF(void) pjmedia_event_publisher_init(pjmedia_event_publisher *epub,
-                                          pjmedia_obj_sig sig)
-{
-    pj_bzero(epub, sizeof(*epub));
-    pj_list_init(&epub->subscription_list);
-    epub->sig = sig;
-}
-
-PJ_DEF(void) pjmedia_event_subscription_init( pjmedia_event_subscription *esub,
-                                              pjmedia_event_cb *cb,
-                                              void *user_data)
-{
-    pj_bzero(esub, sizeof(*esub));
-    esub->cb = cb;
-    esub->user_data = user_data;
-}
-
-PJ_DEF(pj_bool_t)
-pjmedia_event_publisher_has_sub(pjmedia_event_publisher *epub)
-{
-    PJ_ASSERT_RETURN(epub, PJ_FALSE);
-    return epub->subscription_list.next &&
-	    (!pj_list_empty(&epub->subscription_list));
-}
-
-PJ_DEF(pj_status_t) pjmedia_event_subscribe( pjmedia_event_publisher *epub,
-                                             pjmedia_event_subscription *esub)
-{
-    char epub_name[5];
-
-    PJ_ASSERT_RETURN(epub && esub && esub->cb, PJ_EINVAL);
-    /* Must not currently subscribe to anything */
-    PJ_ASSERT_RETURN(esub->subscribe_to == NULL, PJ_EINVALIDOP);
-
-    TRACE_((THIS_FILE, "Subscription [0x%p] to publisher %s",
-		       esub,
-		       pjmedia_fourcc_name(epub->sig, epub_name)));
-    /* Suppress compiler warning if trace is disabled */
-    PJ_UNUSED_ARG(epub_name);
-
-    pj_list_push_back(&epub->subscription_list, esub);
-    esub->subscribe_to = epub;
-    return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_event_unsubscribe(pjmedia_event_subscription *esub)
-{
-    PJ_ASSERT_RETURN(esub, PJ_EINVAL);
-
-    if (esub->subscribe_to) {
-	char epub_name[5];
-	TRACE_((THIS_FILE, "Unsubscription [0x%p] to publisher %s",
-			   esub,
-			   pjmedia_fourcc_name(esub->subscribe_to->sig,
-					       epub_name)));
-	/* Suppress compiler warning if trace is disabled */
-	PJ_UNUSED_ARG(epub_name);
-
-	PJ_ASSERT_RETURN(
-	    pj_list_find_node(&esub->subscribe_to->subscription_list,
-	                      esub)==esub, PJ_ENOTFOUND);
-	pj_list_erase(esub);
-	esub->subscribe_to = NULL;
-    }
-    return PJ_SUCCESS;
-}
-
-PJ_DEF(pj_status_t) pjmedia_event_publish( pjmedia_event_publisher *epub,
-                                           pjmedia_event *event)
-{
-    pjmedia_event_subscription *esub;
-    char event_name[5];
-    char epub_name[5];
-    pj_status_t err = PJ_SUCCESS;
-
-    PJ_ASSERT_RETURN(epub && event, PJ_EINVAL);
-
-    TRACE_((THIS_FILE, "Event %s is published by publisher %s",
-		       pjmedia_fourcc_name(event->type, event_name),
-		       pjmedia_fourcc_name(epub->sig, epub_name)));
-    /* Suppress compiler warning if trace is disabled */
-    PJ_UNUSED_ARG(event_name);
-    PJ_UNUSED_ARG(epub_name);
-
-    esub = epub->subscription_list.next;
-    if (!esub)
-	return err;
-
-    while (esub != &epub->subscription_list) {
-	pjmedia_event_subscription *next;
-	pj_status_t status;
-
-	/* just in case esub is destroyed in the callback */
-	next = esub->next;
-
-	status = (*esub->cb)(esub, event);
-	if (status != PJ_SUCCESS && err == PJ_SUCCESS)
-	    err = status;
-
-	esub = next;
-    }
+
+    pj_mutex_lock(mgr->mutex);
+    if (flag & PJMEDIA_EVENT_PUBLISH_POST_EVENT) {
+        if (event_queue_add_event(&mgr->ev_queue, event) == PJ_SUCCESS)
+            pj_sem_post(mgr->sem);
+    } else {
+        /* For nested pjmedia_event_publish() calls, i.e. calling publish()
+         * inside the subscriber's callback, the function will only add
+         * the event to the event queue of the first publish() call. It
+         * is the first publish() call that will be responsible to
+         * distribute the events.
+         */
+        if (mgr->pub_ev_queue) {
+            event_queue_add_event(mgr->pub_ev_queue, event);
+        } else {
+            static event_queue ev_queue;
+            pj_status_t status;
+
+            ev_queue.head = ev_queue.tail = 0;
+            ev_queue.is_full = PJ_FALSE;
+            mgr->pub_ev_queue = &ev_queue;
+
+            event_queue_add_event(mgr->pub_ev_queue, event);
+
+            do {
+                status = event_mgr_distribute_events(mgr, mgr->pub_ev_queue,
+                                                     &mgr->pub_next_sub);
+                if (status != PJ_SUCCESS && err == PJ_SUCCESS)
+	            err = status;
+            } while(ev_queue.head != ev_queue.tail || ev_queue.is_full);
+
+            mgr->pub_ev_queue = NULL;
+        }
+    }
+    pj_mutex_unlock(mgr->mutex);
 
     return err;
 }
-
-static pj_status_t republisher_cb(pjmedia_event_subscription *esub,
-       				  pjmedia_event *event)
-{
-    return pjmedia_event_publish((pjmedia_event_publisher*)esub->user_data,
-                                 event);
-}
-
-PJ_DEF(pj_status_t) pjmedia_event_republish(pjmedia_event_publisher *esrc,
-                                             pjmedia_event_publisher *epub,
-                                             pjmedia_event_subscription *esub)
-{
-    PJ_ASSERT_RETURN(esrc && epub && esub, PJ_EINVAL);
-
-    pjmedia_event_subscription_init(esub, &republisher_cb, epub);
-    return pjmedia_event_subscribe(esrc, esub);
-}
-
Index: pjproject/trunk/pjmedia/src/pjmedia/port.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/port.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia/port.c	(revision 3893)
@@ -118,18 +118,4 @@
 }
 
-/*
- * Get event publisher
- */
-PJ_DEF(pjmedia_event_publisher*)
-pjmedia_port_get_event_publisher(pjmedia_port *port)
-{
-    PJ_ASSERT_RETURN(port, NULL);
-
-    if (port->get_event_pub)
-	return (*port->get_event_pub)(port);
-
-    return NULL;
-}
-
 /**
  * Destroy port (and subsequent downstream ports)
Index: pjproject/trunk/pjmedia/src/pjmedia/vid_codec.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/vid_codec.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia/vid_codec.c	(revision 3893)
@@ -78,5 +78,4 @@
 {
     pj_bzero(codec, sizeof(*codec));
-    pjmedia_event_publisher_init(&codec->epub, sig);
 }
 
Index: pjproject/trunk/pjmedia/src/pjmedia/vid_port.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/vid_port.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia/vid_port.c	(revision 3893)
@@ -62,8 +62,4 @@
     pjmedia_conversion_param conv_param;
 
-    pjmedia_event_publisher    epub;
-    pjmedia_event_subscription esub_dev;
-    pjmedia_event_subscription esub_client_port;
-
     pjmedia_clock           *clock;
     pjmedia_clock_src        clocksrc;
@@ -95,8 +91,8 @@
 				       void *user_data,
 				       pjmedia_frame *frame);
-static pj_status_t vidstream_event_cb(pjmedia_event_subscription *esub,
-			              pjmedia_event *event);
-static pj_status_t client_port_event_cb(pjmedia_event_subscription *esub,
-                                        pjmedia_event *event);
+static pj_status_t vidstream_event_cb(pjmedia_event *event,
+                                      void *user_data);
+static pj_status_t client_port_event_cb(pjmedia_event *event,
+                                        void *user_data);
 
 static void enc_clock_cb(const pj_timestamp *ts, void *user_data);
@@ -207,5 +203,4 @@
     vp->dir = prm->vidparam.dir;
 //    vp->cap_size = vfd->size;
-    pjmedia_event_publisher_init(&vp->epub, SIGNATURE);
 
     vparam = prm->vidparam;
@@ -273,8 +268,6 @@
 
     /* Subscribe to device's events */
-    pjmedia_event_subscription_init(&vp->esub_dev, vidstream_event_cb, vp);
-    pjmedia_event_subscribe(
-	    pjmedia_vid_dev_stream_get_event_publisher(vp->strm),
-	    &vp->esub_dev);
+    pjmedia_event_subscribe(NULL, vp->pool, &vidstream_event_cb,
+                            vp, vp->strm);
 
     if (vp->dir & PJMEDIA_DIR_CAPTURE) {
@@ -371,11 +364,4 @@
 }
 
-PJ_DEF(pjmedia_event_publisher*)
-pjmedia_vid_port_get_event_publisher(pjmedia_vid_port *vid_port)
-{
-    PJ_ASSERT_RETURN(vid_port, NULL);
-    return &vid_port->epub;
-}
-
 PJ_DEF(pjmedia_vid_dev_stream*)
 pjmedia_vid_port_get_stream(pjmedia_vid_port *vp)
@@ -420,6 +406,4 @@
 					      pj_bool_t destroy)
 {
-    pjmedia_event_publisher *epub;
-
     PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL);
     vp->destroy_client_port = destroy;
@@ -427,11 +411,7 @@
 
     /* Subscribe to client port's events */
-    epub = pjmedia_port_get_event_publisher(port);
-    if (epub) {
-	pjmedia_event_subscription_init(&vp->esub_client_port,
-	                                &client_port_event_cb,
-	                                vp);
-	pjmedia_event_subscribe(epub, &vp->esub_client_port);
-    }
+    pjmedia_event_subscribe(NULL, vp->pool, &client_port_event_cb, vp,
+                            vp->client_port);
+
     return PJ_SUCCESS;
 }
@@ -442,8 +422,8 @@
     PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL);
 
-    if (vp->client_port) {
-	pjmedia_event_unsubscribe(&vp->esub_client_port);
-	vp->client_port = NULL;
-    }
+    pjmedia_event_unsubscribe(NULL, &client_port_event_cb, vp,
+                              vp->client_port);
+    vp->client_port = NULL;
+
     return PJ_SUCCESS;
 }
@@ -511,8 +491,11 @@
     }
     if (vp->strm) {
+        pjmedia_event_unsubscribe(NULL, &vidstream_event_cb, vp, vp->strm);
 	pjmedia_vid_dev_stream_destroy(vp->strm);
 	vp->strm = NULL;
     }
     if (vp->client_port) {
+        pjmedia_event_unsubscribe(NULL, &client_port_event_cb, vp,
+                                  vp->client_port);
 	if (vp->destroy_client_port)
 	    pjmedia_port_destroy(vp->client_port);
@@ -561,17 +544,17 @@
 
 /* Handle event from vidstream */
-static pj_status_t vidstream_event_cb(pjmedia_event_subscription *esub,
-				      pjmedia_event *event)
-{
-    pjmedia_vid_port *vp = (pjmedia_vid_port*)esub->user_data;
+static pj_status_t vidstream_event_cb(pjmedia_event *event,
+                                      void *user_data)
+{
+    pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
     
     /* Just republish the event to our client */
-    return pjmedia_event_publish(&vp->epub, event);
-}
-
-static pj_status_t client_port_event_cb(pjmedia_event_subscription *esub,
-                                        pjmedia_event *event)
-{
-    pjmedia_vid_port *vp = (pjmedia_vid_port*)esub->user_data;
+    return pjmedia_event_publish(NULL, vp, event, 0);
+}
+
+static pj_status_t client_port_event_cb(pjmedia_event *event,
+                                        void *user_data)
+{
+    pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
 
     if (event->type == PJMEDIA_EVENT_FMT_CHANGED) {
@@ -579,6 +562,4 @@
         pj_status_t status;
         
-        ++event->proc_cnt;
-
 	pjmedia_vid_port_stop(vp);
         
@@ -634,6 +615,9 @@
     }
     
-    /* Republish the event */
-    return pjmedia_event_publish(&vp->epub, event);
+    /* Republish the event, post the event to the event manager
+     * to avoid deadlock if vidport is trying to stop the clock.
+     */
+    return pjmedia_event_publish(NULL, vp, event,
+                                 PJMEDIA_EVENT_PUBLISH_POST_EVENT);
 }
 
Index: pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/pjmedia/vid_stream.c	(revision 3893)
@@ -148,7 +148,4 @@
     pj_uint32_t		     last_dec_ts;    /**< Last decoded timestamp.   */
     int			     last_dec_seq;   /**< Last decoded sequence.    */
-
-    pjmedia_event_subscription esub_codec;   /**< To subscribe codec events */
-    pjmedia_event_publisher    epub;	     /**< To publish events	    */
 };
 
@@ -340,16 +337,13 @@
  * Handle events from stream components.
  */
-static pj_status_t stream_event_cb(pjmedia_event_subscription *esub,
-                                   pjmedia_event *event)
-{
-    pjmedia_vid_stream *stream = (pjmedia_vid_stream*)esub->user_data;
-
-    if (esub == &stream->esub_codec) {
+static pj_status_t stream_event_cb(pjmedia_event *event,
+                                   void *user_data)
+{
+    pjmedia_vid_stream *stream = (pjmedia_vid_stream*)user_data;
+
+    if (event->epub == stream->codec) {
 	/* This is codec event */
 	switch (event->type) {
 	case PJMEDIA_EVENT_FMT_CHANGED:
-	    /* we process the event */
-	    ++event->proc_cnt;
-
 	    /* Copy the event to avoid deadlock if we publish the event
 	     * now. This happens because fmt_event may trigger restart
@@ -363,11 +357,5 @@
     }
 
-    return pjmedia_event_publish(&stream->epub, event);
-}
-
-static pjmedia_event_publisher *port_get_epub(pjmedia_port *port)
-{
-    pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata;
-    return &stream->epub;
+    return pjmedia_event_publish(NULL, stream, event, 0);
 }
 
@@ -1032,5 +1020,5 @@
 
 		/* Publish PJMEDIA_EVENT_FMT_CHANGED event */
-		if (pjmedia_event_publisher_has_sub(&stream->epub)) {
+		{
 		    pjmedia_event event;
 
@@ -1038,10 +1026,10 @@
 
 		    pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED,
-		                       &frame->timestamp, &stream->epub);
+		                       &frame->timestamp, &stream);
 		    event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING;
 		    pj_memcpy(&event.data.fmt_changed.new_fmt,
 		              &stream->info.codec_param->dec_fmt,
 		              sizeof(pjmedia_format));
-		    pjmedia_event_publish(&stream->epub, &event);
+		    pjmedia_event_publish(NULL, stream, &event, 0);
 		}
 	    }
@@ -1087,5 +1075,5 @@
 		      "changed");
 
-	pjmedia_event_publish(&stream->epub, &stream->fmt_event);
+	pjmedia_event_publish(NULL, stream, &stream->fmt_event, 0);
 
 	stream->fmt_event.type = PJMEDIA_EVENT_NONE;
@@ -1212,5 +1200,4 @@
     /* Init port. */
     channel->port.port_data.pdata = stream;
-    channel->port.get_event_pub = &port_get_epub;
 
     PJ_LOG(5, (name.ptr,
@@ -1346,9 +1333,7 @@
 	return status;
 
-    /* Init event publisher and subscribe to codec events */
-    pjmedia_event_publisher_init(&stream->epub, SIGNATURE);
-    pjmedia_event_subscription_init(&stream->esub_codec, &stream_event_cb,
-                                    stream);
-    pjmedia_event_subscribe(&stream->codec->epub, &stream->esub_codec);
+    /* Subscribe to codec events */
+    pjmedia_event_subscribe(NULL, pool, &stream_event_cb, stream,
+                            stream->codec);
 
     /* Estimate the maximum frame size */
@@ -1557,4 +1542,6 @@
     /* Free codec. */
     if (stream->codec) {
+        pjmedia_event_unsubscribe(NULL, &stream_event_cb, stream,
+                                  stream->codec);
 	pjmedia_vid_codec_close(stream->codec);
 	pjmedia_vid_codec_mgr_dealloc_codec(stream->codec_mgr, stream->codec);
Index: pjproject/trunk/pjmedia/src/test/test.c
===================================================================
--- pjproject/trunk/pjmedia/src/test/test.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/test/test.c	(revision 3893)
@@ -73,4 +73,5 @@
     pjmedia_video_format_mgr_create(pool, 64, 0, NULL);
     pjmedia_converter_mgr_create(pool, NULL);
+    pjmedia_event_mgr_create(pool, 0, NULL);
     pjmedia_vid_codec_mgr_create(pool, NULL);
 #endif
@@ -116,4 +117,5 @@
     pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr_instance());
     pjmedia_converter_mgr_destroy(pjmedia_converter_mgr_instance());
+    pjmedia_event_mgr_destroy(pjmedia_event_mgr_instance());
     pjmedia_vid_codec_mgr_destroy(pjmedia_vid_codec_mgr_instance());
 #endif
Index: pjproject/trunk/pjmedia/src/test/vid_codec_test.c
===================================================================
--- pjproject/trunk/pjmedia/src/test/vid_codec_test.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/test/vid_codec_test.c	(revision 3893)
@@ -48,8 +48,8 @@
 } codec_port_data_t;
 
-static pj_status_t codec_on_event(pjmedia_event_subscription *esub,
-                                  pjmedia_event *event)
-{
-    codec_port_data_t *port_data = (codec_port_data_t*)esub->user_data;
+static pj_status_t codec_on_event(pjmedia_event *event,
+                                  void *user_data)
+{
+    codec_port_data_t *port_data = (codec_port_data_t*)user_data;
 
     if (event->type == PJMEDIA_EVENT_FMT_CHANGED) {
@@ -57,6 +57,4 @@
 	pjmedia_vid_codec_param codec_param;
 	pj_status_t status;
-
-	++event->proc_cnt;
 
 	status = pjmedia_vid_codec_get_param(codec, &codec_param);
@@ -201,5 +199,4 @@
     pjmedia_vid_port_param vport_param;
     pjmedia_video_format_detail *vfd;
-    pjmedia_event_subscription esub;
     char codec_name[5];
     pj_status_t status;
@@ -324,7 +321,6 @@
 
 	/* Subscribe to codec events */
-	pjmedia_event_subscription_init(&esub, &codec_on_event,
-	                                &codec_port_data);
-	pjmedia_event_subscribe(&codec->epub, &esub);
+	pjmedia_event_subscribe(NULL, pool, &codec_on_event, &codec_port_data,
+                                codec);
     }
 
@@ -431,4 +427,6 @@
     }
     if (codec) {
+	pjmedia_event_unsubscribe(NULL, &codec_on_event, &codec_port_data,
+                                  codec);
         pjmedia_vid_codec_close(codec);
         pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec);
Index: pjproject/trunk/pjmedia/src/test/vid_dev_test.c
===================================================================
--- pjproject/trunk/pjmedia/src/test/vid_dev_test.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/test/vid_dev_test.c	(revision 3893)
@@ -77,8 +77,8 @@
 }
 
-static pj_status_t vid_event_cb(pjmedia_event_subscription *esub,
-				pjmedia_event *event)
-{
-    PJ_UNUSED_ARG(esub);
+static pj_status_t vid_event_cb(pjmedia_event *event,
+                                void *user_data)
+{
+    PJ_UNUSED_ARG(user_data);
 
     if (event->type == PJMEDIA_EVENT_WND_CLOSED)
@@ -96,5 +96,4 @@
     pjmedia_vid_port_param param;
     pjmedia_video_format_detail *vfd;
-    pjmedia_event_subscription esub;
     pj_status_t status;
     int rc = 0, i;
@@ -162,8 +161,5 @@
 
     /* Set event handler */
-    pjmedia_event_subscription_init(&esub, &vid_event_cb, NULL);
-    pjmedia_event_subscribe(
-	    pjmedia_vid_port_get_event_publisher(renderer),
-	    &esub);
+    pjmedia_event_subscribe(NULL, pool, &vid_event_cb, NULL, renderer);
 
     /* Connect capture to renderer */
@@ -197,6 +193,8 @@
     if (capture)
 	pjmedia_vid_port_destroy(capture);
-    if (renderer)
+    if (renderer) {
+        pjmedia_event_unsubscribe(NULL, &vid_event_cb, NULL, renderer);
 	pjmedia_vid_port_destroy(renderer);
+    }
 
     pj_pool_release(pool);
Index: pjproject/trunk/pjmedia/src/test/vid_port_test.c
===================================================================
--- pjproject/trunk/pjmedia/src/test/vid_port_test.c	(revision 3892)
+++ pjproject/trunk/pjmedia/src/test/vid_port_test.c	(revision 3893)
@@ -33,8 +33,8 @@
 static pj_bool_t is_quitting = PJ_FALSE;
 
-static pj_status_t vid_event_cb(pjmedia_event_subscription *esub,
-				pjmedia_event *event)
-{
-    PJ_UNUSED_ARG(esub);
+static pj_status_t vid_event_cb(pjmedia_event *event,
+                                void *user_data)
+{
+    PJ_UNUSED_ARG(user_data);
 
     if (event->type == PJMEDIA_EVENT_WND_CLOSED)
@@ -53,5 +53,4 @@
     pjmedia_vid_port_param param;
     pjmedia_video_format_detail *vfd;
-    pjmedia_event_subscription esub;
     pj_status_t status;
     int rc = 0, i;
@@ -119,8 +118,5 @@
 
     /* Set event handler */
-    pjmedia_event_subscription_init(&esub, &vid_event_cb, NULL);
-    pjmedia_event_subscribe(
-	    pjmedia_vid_port_get_event_publisher(renderer),
-	    &esub);
+    pjmedia_event_subscribe(NULL, pool, &vid_event_cb, NULL, renderer);
 
     /* Connect capture to renderer */
@@ -154,6 +150,8 @@
     if (capture)
 	pjmedia_vid_port_destroy(capture);
-    if (renderer)
+    if (renderer) {
+        pjmedia_event_unsubscribe(NULL, &vid_event_cb, NULL, renderer);
 	pjmedia_vid_port_destroy(renderer);
+    }
 
     pj_pool_release(pool);
Index: pjproject/trunk/pjsip-apps/src/samples/aviplay.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/aviplay.c	(revision 3892)
+++ pjproject/trunk/pjsip-apps/src/samples/aviplay.c	(revision 3893)
@@ -106,8 +106,8 @@
 } codec_port_data_t;
 
-static pj_status_t avi_event_cb(pjmedia_event_subscription *esub,
-				pjmedia_event *event)
-{
-    avi_port_t *ap = (avi_port_t *)esub->user_data;
+static pj_status_t avi_event_cb(pjmedia_event *event,
+                                void *user_data)
+{
+    avi_port_t *ap = (avi_port_t *)user_data;
     
     switch (event->type) {
@@ -192,5 +192,4 @@
     pjmedia_port *vid_port = NULL, *aud_port = NULL;
     pjmedia_vid_codec *codec=NULL;
-    pjmedia_event_subscription esub;
     avi_port_t avi_port;
     
@@ -403,8 +402,6 @@
 
         /* subscribe events */
-        pjmedia_event_subscription_init(&esub, &avi_event_cb, &avi_port);
-        pjmedia_event_subscribe(
-        	pjmedia_vid_port_get_event_publisher(renderer),
-        	&esub);
+        pjmedia_event_subscribe(NULL, pool, &avi_event_cb, &avi_port,
+                                renderer);
 
         if (snd_port) {
@@ -437,6 +434,9 @@
         pjmedia_snd_port_destroy(snd_port);
     }
-    if (renderer)
+    if (renderer) {
+        pjmedia_event_unsubscribe(NULL, &avi_event_cb, &avi_port,
+                                  renderer);
         pjmedia_vid_port_destroy(renderer);
+    }
     if (aud_port)
         pjmedia_port_destroy(aud_port);
@@ -483,4 +483,5 @@
     pjmedia_video_format_mgr_create(pool, 64, 0, NULL);
     pjmedia_converter_mgr_create(pool, NULL);
+    pjmedia_event_mgr_create(pool, 0, NULL);
     pjmedia_vid_codec_mgr_create(pool, NULL);
     
@@ -520,4 +521,5 @@
     pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr_instance());
     pjmedia_converter_mgr_destroy(pjmedia_converter_mgr_instance());
+    pjmedia_event_mgr_destroy(pjmedia_event_mgr_instance());
     pjmedia_vid_codec_mgr_destroy(pjmedia_vid_codec_mgr_instance());    
     
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 3892)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 3893)
@@ -86,7 +86,4 @@
     pjmedia_srtp_use	 rem_srtp_use; /**< Remote's SRTP usage policy.	    */
 
-    pjmedia_event_subscription esub_rend;/**< Subscribe renderer events.    */
-    pjmedia_event_subscription esub_cap;/**< Subscribe capture events.      */
-
     pjsua_med_tp_state_cb      med_init_cb;/**< Media transport
                                                 initialization callback.    */
@@ -566,4 +563,7 @@
 void set_media_tp_state(pjsua_call_media *call_med, pjsua_med_tp_st tp_st);
 
+/* Callback to receive media events */
+pj_status_t call_media_on_event(pjmedia_event *event,
+                                void *user_data);
 
 /**
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 3892)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 3893)
@@ -1254,12 +1254,11 @@
 
 /* Callback to receive media events */
-static pj_status_t call_media_on_event(pjmedia_event_subscription *esub,
-                                       pjmedia_event *event)
-{
-    pjsua_call_media *call_med = (pjsua_call_media*)esub->user_data;
+pj_status_t call_media_on_event(pjmedia_event *event,
+                                void *user_data)
+{
+    pjsua_call_media *call_med = (pjsua_call_media*)user_data;
     pjsua_call *call = call_med->call;
 
     if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
-	++event->proc_cnt;
 	(*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
 						   call_med->idx, event);
@@ -1425,10 +1424,4 @@
 		call_med->strm.v.cap_dev = info.id;
 	    }
-
-	    /* Init event subscribtion */
-	    pjmedia_event_subscription_init(&call_med->esub_rend, &call_media_on_event,
-					    call_med);
-	    pjmedia_event_subscription_init(&call_med->esub_cap, &call_media_on_event,
-					    call_med);
 	}
 #endif
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c	(revision 3892)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c	(revision 3893)
@@ -58,4 +58,11 @@
     }
 
+    status = pjmedia_event_mgr_create(pjsua_var.pool, 0, NULL);
+    if (status != PJ_SUCCESS) {
+	PJ_PERROR(1,(THIS_FILE, status,
+		     "Error creating PJMEDIA event manager"));
+	goto on_error;
+    }
+
     status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
     if (status != PJ_SUCCESS) {
@@ -130,4 +137,7 @@
     if (pjmedia_converter_mgr_instance())
 	pjmedia_converter_mgr_destroy(NULL);
+
+    if (pjmedia_event_mgr_instance())
+	pjmedia_event_mgr_destroy(NULL);
 
     if (pjmedia_video_format_mgr_instance())
@@ -614,4 +624,6 @@
 
     if (w->vp_cap) {
+        pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
+                                  w->vp_cap);
 	pjmedia_vid_port_stop(w->vp_cap);
 	pjmedia_vid_port_disconnect(w->vp_cap);
@@ -619,4 +631,6 @@
     }
     if (w->vp_rend) {
+        pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
+                                  w->vp_rend);
 	pjmedia_vid_port_stop(w->vp_rend);
 	pjmedia_vid_port_destroy(w->vp_rend);
@@ -816,7 +830,6 @@
 #if ENABLE_EVENT
 	    /* Register to video events */
-	    pjmedia_event_subscribe(
-		    pjmedia_vid_port_get_event_publisher(w->vp_rend),
-		    &call_med->esub_rend);
+	    pjmedia_event_subscribe(NULL, w->pool, &call_media_on_event,
+                                    call_med, w->vp_rend);
 #endif
 	    
@@ -887,7 +900,6 @@
 	    w = &pjsua_var.win[wid];
 #if ENABLE_EVENT
-	    pjmedia_event_subscribe(
-		    pjmedia_vid_port_get_event_publisher(w->vp_cap),
-		    &call_med->esub_cap);
+            pjmedia_event_subscribe(NULL, w->pool, &call_media_on_event,
+                                    call_med, w->vp_cap);
 #endif
 	    
@@ -1007,5 +1019,6 @@
 
         /* Unsubscribe event */
-	pjmedia_event_unsubscribe(&call_med->esub_cap);
+	pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
+                                  w->vp_cap);
 
 	/* Re-start capture again, if it is used by other stream */
@@ -1022,5 +1035,6 @@
 	/* Stop the render before unsubscribing event */
 	pjmedia_vid_port_stop(w->vp_rend);
-	pjmedia_event_unsubscribe(&call_med->esub_rend);
+	pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
+                                  w->vp_rend);
 
 	dec_vid_win(call_med->strm.v.rdr_win_id);
@@ -1836,5 +1850,6 @@
 	return status;
 
-    pjmedia_event_unsubscribe(&call_med->esub_cap);
+    pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
+                              w->vp_cap);
     
     /* temporarily disconnect while we operate on the tee. */
@@ -1894,7 +1909,6 @@
 
 #if ENABLE_EVENT
-    pjmedia_event_subscribe(
-	    pjmedia_vid_port_get_event_publisher(new_w->vp_cap),
-	    &call_med->esub_cap);
+    pjmedia_event_subscribe(NULL, new_w->pool, &call_media_on_event,
+                            call_med, new_w->vp_cap);
 #endif
 
@@ -1920,5 +1934,6 @@
     if (new_w) {
 	/* Unsubscribe, just in case */
-	pjmedia_event_unsubscribe(&call_med->esub_cap);
+        pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
+                                  new_w->vp_cap);
 	/* Disconnect media port from the new capturer */
 	pjmedia_vid_tee_remove_dst_port(new_w->tee, media_port);
@@ -1936,7 +1951,6 @@
 #if ENABLE_EVENT
     /* Resubscribe */
-    pjmedia_event_subscribe(
-	    pjmedia_vid_port_get_event_publisher(w->vp_cap),
-	    &call_med->esub_cap);
+    pjmedia_event_subscribe(NULL, w->pool, &call_media_on_event,
+                            call_med, w->vp_cap);
 #endif
 
