From a81d916c922e4ebc7fa54cd4587e977867c2de06 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Sun, 15 Dec 2013 20:54:29 +0100 Subject: [PATCH] gstplayer: implement duration, position, and seek + correctly register an atexit callback to clean all the remaining worker. --- kivy/lib/gstplayer/_gstplayer.pyx | 75 +++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/kivy/lib/gstplayer/_gstplayer.pyx b/kivy/lib/gstplayer/_gstplayer.pyx index 1c3cba355..a64c2d36c 100644 --- a/kivy/lib/gstplayer/_gstplayer.pyx +++ b/kivy/lib/gstplayer/_gstplayer.pyx @@ -1,4 +1,6 @@ from libcpp cimport bool +from weakref import ref +import atexit cdef extern from 'gst/gst.h': ctypedef void *GstPipeline @@ -12,6 +14,8 @@ cdef extern from 'gst/gst.h': ctypedef unsigned long gulong ctypedef void *gpointer ctypedef char const_gchar 'const gchar' + ctypedef long int gint64 + ctypedef unsigned long long GstClockTime ctypedef enum GstState: GST_STATE_VOID_PENDING @@ -20,6 +24,13 @@ cdef extern from 'gst/gst.h': GST_STATE_PAUSED GST_STATE_PLAYING + ctypedef enum GstFormat: + GST_FORMAT_TIME + + ctypedef enum GstSeekFlags: + GST_SEEK_FLAG_KEY_UNIT + GST_SEEK_FLAG_FLUSH + ctypedef enum GstStateChangeReturn: pass @@ -38,11 +49,21 @@ cdef extern from 'gst/gst.h': GstElement *gst_pipeline_new(const_gchar *name) void gst_bus_enable_sync_message_emission(GstBus *bus) GstBus *gst_pipeline_get_bus(GstPipeline *pipeline) + GstStateChangeReturn gst_element_get_state( + GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout) nogil GstStateChangeReturn gst_element_set_state( GstElement *element, GstState state) nogil void g_signal_emit_by_name(gpointer instance, const_gchar *detailed_signal, void *retvalue) void g_error_free(GError *error) + bool gst_element_query_position( + GstElement *element, GstFormat format, gint64 *cur) + bool gst_element_query_duration( + GstElement *element, GstFormat format, gint64 *cur) + bool gst_element_seek_simple( + GstElement *element, GstFormat format, + GstSeekFlags seek_flags, gint64 seek_pos) cdef extern from '_gstplayer.h': void g_object_set_void(GstElement *element, char *name, void *value) @@ -54,6 +75,25 @@ cdef extern from '_gstplayer.h': void c_appsink_disconnect(GstElement *appsink, gulong handler_id) +# +# prevent gstreamer crash when some player are still working. +# + +cdef list _instances = [] + +def _on_player_deleted(wk): + if wk in _instances: + _instances.remove(wk) + +@atexit.register +def gst_exit_clean(): + for wk in _instances: + player = wk() + if player: + player.stop() + player.unload() + + class GstPlayerException(Exception): pass @@ -88,6 +128,7 @@ cdef class GstPlayer: cdef GstBus *bus cdef object uri, sample_cb cdef gulong handler_id + cdef object __weakref__ def __cinit__(self, uri, sample_cb): self.pipeline = self.playbin = self.appsink = NULL @@ -98,6 +139,7 @@ cdef class GstPlayer: super(GstPlayer, self).__init__() self.uri = uri self.sample_cb = sample_cb + _instances.append(ref(self, _on_player_deleted)) # ensure gstreamer is init _gst_init() @@ -135,7 +177,7 @@ cdef class GstPlayer: if self.appsink == NULL: raise GstPlayerException('Unable to create an appsink') - g_object_set_caps(self.appsink, 'video/x-raw,format=RGBA') + g_object_set_caps(self.appsink, 'video/x-raw,format=RGB') g_object_set_int(self.appsink, 'max-buffers', 5) g_object_set_int(self.appsink, 'drop', 1) g_object_set_int(self.appsink, 'sync', 1) @@ -168,12 +210,19 @@ cdef class GstPlayer: gst_element_set_state(self.pipeline, GST_STATE_PAUSED) def unload(self): + cdef GstState current_state, pending_state + if self.appsink != NULL and self.handler_id != 0: c_appsink_disconnect(self.appsink, self.handler_id) if self.pipeline != NULL: + # the state changes are async. if we want to guarantee that the + # state is set to NULL, we need to query it. We also put a 5s + # timeout for safety, but normally, nobody should hit it. with nogil: gst_element_set_state(self.pipeline, GST_STATE_NULL) + gst_element_get_state(self.pipeline, ¤t_state, + &pending_state, 5e9) gst_object_unref(self.pipeline) if self.bus != NULL: @@ -190,7 +239,27 @@ cdef class GstPlayer: g_object_set_double(self.playbin, 'volume', volume) def get_duration(self): - return -1 + cdef gint64 duration = 0 + if self.playbin == NULL: + return -1 + if not gst_element_query_duration( + self.playbin, GST_FORMAT_TIME, &duration): + return -1 + return duration / 1e9 def get_position(self): - return -1 + cdef gint64 position = 0 + if self.playbin == NULL: + return -1 + if not gst_element_query_position( + self.playbin, GST_FORMAT_TIME, &position): + return -1 + return position / 1e9 + + def seek(self, percent): + if self.playbin == NULL: + return + seek_t = percent * self.get_duration() * 1e9 + seek_flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT + gst_element_seek_simple(self.playbin, GST_FORMAT_TIME, + seek_flags, seek_t)