[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