vlc/python | branch: master | Olivier Aubert <olivier.aub...@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.aub...@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 -...@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) +...@_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)) _______________________________________________ vlc-commits mailing list vlc-commits@videolan.org http://mailman.videolan.org/listinfo/vlc-commits