[vlc-commits] commit: Improve event callback handling. (Olivier Aubert )

git at videolan.org git at videolan.org
Tue Nov 16 14:29:27 CET 2010


vlc/python | branch: master | Olivier Aubert <olivier.aubert at liris.cnrs.fr> | Tue Nov 16 10:41:20 2010 +0100| [31a59582dee1fce9dcbe0bce3ea2027f14c68073] | committer: Olivier Aubert 

Improve event callback handling.

With the extended EventManager, callback methods do not have to be decorated anymore. They are properly wrapped by the EventManager upon call.

Signed-off-by: Olivier Aubert <olivier.aubert at liris.cnrs.fr>

> http://git.videolan.org/gitweb.cgi/vlc/python.git/?a=commit;h=31a59582dee1fce9dcbe0bce3ea2027f14c68073
---

 footer.py   |   68 ++++++++++++++++++++++++++++++++++++++++++++++------------
 override.py |   68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 122 insertions(+), 14 deletions(-)

diff --git a/footer.py b/footer.py
index 313bedd..7fb40c9 100644
--- a/footer.py
+++ b/footer.py
@@ -33,15 +33,38 @@ class Event(ctypes.Structure):
         ('u', EventUnion),
         ]
 
-# Decorator for callback methods
-callbackmethod=ctypes.CFUNCTYPE(None, ctypes.POINTER(Event), ctypes.c_void_p)
-
-# Example callback method
- at callbackmethod
-def debug_callback(event, data):
-    print "Debug callback method"
-    print "Event:", event.contents.type
-    print "Data", data
+_EventManagers = {}
+
+# FIXME: the EventManager global dict could be removed if
+# _callback_handler was made a method of EventManager.
+_called_from_ctypes = ctypes.CFUNCTYPE(None, ctypes.POINTER(Event), ctypes.c_void_p)
+ at _called_from_ctypes
+def _callback_handler(event, key):
+    '''(INTERNAL) handle callback call from ctypes.
+    '''
+    try: # retrieve Python callback and arguments
+        call, args, kwds = _EventManagers[key]._callbacks_[event.contents.type.value]
+        # FIXME: event could be dereferenced here to event.contents,
+        # this would simplify the callback code.
+        call(event, *args, **kwds)
+    except KeyError:  # detached?
+        pass
+
+def callbackmethod(f):
+    """Backward compatibility with the now useless @callbackmethod decorator.
+    
+    This method will be removed after a transition period.
+    """
+    return f
+
+# Example callback, useful for debugging
+def debug_callback(event, *args, **kwds):
+    l = ["event %s" % (event.contents.type,)]
+    if args:
+       l.extend(map(str, args))
+    if kwds:
+       l.extend(sorted( "%s=%s" % t for t in kwds.iteritems() ))
+    print "Debug callback (%s)" % ", ".join(l)
 
 if __name__ == '__main__':
     try:
@@ -59,11 +82,17 @@ if __name__ == '__main__':
                 termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
             return ch
 
-    @callbackmethod
-    def end_callback(event, data):
+    def end_callback(event):
         print "End of stream"
         sys.exit(0)
 
+    echo_position = False
+    def pos_callback(event, player):
+        if echo_position:
+            print "%s to %.2f%% (%.2f%%)" % (event.contents.type,
+                   event.contents.u.new_position * 100,
+                   player.get_position() * 100)
+
     if sys.argv[1:]:
         instance=Instance()
         media=instance.media_new(sys.argv[1])
@@ -71,12 +100,17 @@ if __name__ == '__main__':
         player.set_media(media)
         player.play()
 
-        event_manager=player.event_manager()
-        event_manager.event_attach(EventType.MediaPlayerEndReached, end_callback, None)
+         # Some event manager examples.  Note, the callback can be any Python
+         # callable and does not need to be decorated.  Optionally, specify
+         # any number of positional and/or keyword arguments to be passed
+         # to the callback (in addition to the first one, an Event instance).
+        event_manager = player.event_manager()
+        event_manager.event_attach(EventType.MediaPlayerEndReached, end_callback)
+        event_manager.event_attach(EventType.MediaPlayerPositionChanged, pos_callback, player)
 
         def print_info():
             """Print information about the media."""
-            media=player.get_media()
+            media = player.get_media()
             print "State:", player.get_state()
             print "Media:", media.get_mrl()
             try:
@@ -116,6 +150,11 @@ if __name__ == '__main__':
             """Exit."""
             sys.exit(0)
 
+        def toggle_echo_position():
+            """Toggle echoing of media position"""
+            global echo_position
+            echo_position = not echo_position
+
         keybindings={
             'f': player.toggle_fullscreen,
             ' ': player.pause,
@@ -125,6 +164,7 @@ if __name__ == '__main__':
             ',': one_frame_backward,
             '?': print_help,
             'i': print_info,
+            'p': toggle_echo_position,
             'q': quit_app,
             }
 
diff --git a/override.py b/override.py
index 297e6b5..aabae9d 100644
--- a/override.py
+++ b/override.py
@@ -199,3 +199,71 @@ class Log:
 
     def dump(self):
         return [ str(m) for m in self ]
+
+class EventManager:
+    """Create an event manager and handler.
+
+       This class interposes the registration and handling of
+       event notifications in order to (a) allow any number of
+       positional and/or keyword arguments to the callback (in
+       addition to the Event instance), (b) preserve the Python
+       argument objects and (c) remove the need for decorating
+       each callback with decorator '@callbackmethod'.
+
+       Calls from ctypes to Python callbacks are handled by
+       function _callback_handler.
+
+       A side benefit of this scheme is that the callback and
+       all argument objects remain alive (i.e. are not garbage
+       collected) until *after* the event notification has
+       been unregistered.
+
+       NOTE: only a single notification can be registered for
+       each event type in an EventManager instance.
+    """
+    def __new__(cls, ptr=None):
+        if ptr is None:
+            raise LibVLCException("(INTERNAL) ctypes class.")
+        if ptr == 0:
+            return None
+        o = object.__new__(cls)
+        o._as_parameter_ = ptr  # was ctypes.c_void_p(ptr)
+        o._callbacks_ = {}  # 3-tuples of Python objs
+        _EventManagers[id(o)] = o  # map id to instance
+        return o
+
+    def event_attach(self, eventtype, callback, *args, **kwds):
+        """Register an event notification.
+
+        @param eventtype: the desired event type to be notified about
+        @param callback: the function to call when the event occurs
+        @param args: optional positional arguments for the callback
+        @param kwds: optional keyword arguments for the callback
+        @return: 0 on success, ENOMEM on error
+
+        NOTE: The callback must have at least one argument, the Event
+        instance.  The optional positional and keyword arguments are
+        in addition to the first one.
+        """
+        if not isinstance(eventtype, EventType):
+            raise LibVLCException("%s required: %r" % ('EventType', eventtype))
+        if not hasattr(callback, '__call__'):  # callable()
+            raise LibVLCException("%s required: %r" % ('callable', callback))
+
+        r = libvlc_event_attach(self, eventtype, _callback_handler, id(self))
+        if not r:
+            self._callbacks_[eventtype.value] = (callback, args, kwds)
+        return r
+
+    def event_detach(self, eventtype):
+        """Unregister an event notification.
+
+        @param eventtype: the event type notification to be removed
+        """
+        if not isinstance(eventtype, EventType):
+            raise LibVLCException("%s required: %r" % ('EventType', eventtype))
+
+        t = eventtype.value
+        if t in self._callbacks_:
+            del self._callbacks_[t] # remove, regardless of libvlc return value
+            libvlc_event_detach(self, eventtype, _callback_handler, id(self))



More information about the vlc-commits mailing list