3 # vi:si:et:sw=4:sts=4:ts=4
11 gobject.threads_init()
18 gtk.gdk.threads_init()
21 def __init__(self, videowidget):
23 self.player = gst.element_factory_make("playbin", "player")
24 self.videowidget = videowidget
27 bus = self.player.get_bus()
28 bus.enable_sync_message_emission()
29 bus.add_signal_watch()
30 bus.connect('sync-message::element', self.on_sync_message)
31 bus.connect('message', self.on_message)
33 def on_sync_message(self, bus, message):
34 if message.structure is None:
36 if message.structure.get_name() == 'prepare-xwindow-id':
37 # Sync with the X server before giving the X-id to the sink
38 gtk.gdk.threads_enter()
39 gtk.gdk.display_get_default().sync()
40 self.videowidget.set_sink(message.src)
41 message.src.set_property('force-aspect-ratio', True)
42 gtk.gdk.threads_leave()
44 def on_message(self, bus, message):
46 if t == gst.MESSAGE_ERROR:
47 err, debug = message.parse_error()
48 print "Error: %s" % err, debug
52 elif t == gst.MESSAGE_EOS:
57 def set_location(self, location):
58 self.player.set_property('uri', location)
60 def query_position(self):
61 "Returns a (position, duration) tuple"
63 position, format = self.player.query_position(gst.FORMAT_TIME)
65 position = gst.CLOCK_TIME_NONE
68 duration, format = self.player.query_duration(gst.FORMAT_TIME)
70 duration = gst.CLOCK_TIME_NONE
72 return (position, duration)
74 def seek(self, location):
76 @param location: time to seek to, in nanoseconds
78 gst.debug("seeking to %r" % location)
79 event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
80 gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
81 gst.SEEK_TYPE_SET, location,
82 gst.SEEK_TYPE_NONE, 0)
84 res = self.player.send_event(event)
86 gst.info("setting new stream time to 0")
87 self.player.set_new_stream_time(0L)
89 gst.error("seek to %r failed" % location)
92 gst.info("pausing player")
93 self.player.set_state(gst.STATE_PAUSED)
97 gst.info("playing player")
98 self.player.set_state(gst.STATE_PLAYING)
102 self.player.set_state(gst.STATE_NULL)
103 gst.info("stopped player")
105 def get_state(self, timeout=1):
106 return self.player.get_state(timeout=timeout)
108 def is_playing(self):
111 class VideoWidget(gtk.DrawingArea):
113 gtk.DrawingArea.__init__(self)
114 self.imagesink = None
115 self.unset_flags(gtk.DOUBLE_BUFFERED)
117 def do_expose_event(self, event):
119 self.imagesink.expose()
124 def set_sink(self, sink):
125 assert self.window.xid
126 self.imagesink = sink
127 self.imagesink.set_xwindow_id(self.window.xid)
129 class PlayerWindow(gtk.Window):
130 UPDATE_INTERVAL = 500
132 gtk.Window.__init__(self)
133 self.set_default_size(410, 325)
137 self.player = GstPlayer(self.videowidget)
142 self.player.on_eos = lambda *x: on_eos()
146 self.seek_timeout_id = -1
148 self.p_position = gst.CLOCK_TIME_NONE
149 self.p_duration = gst.CLOCK_TIME_NONE
151 def on_delete_event():
154 self.connect('delete-event', lambda *x: on_delete_event())
156 def load_file(self, location):
157 self.player.set_location(location)
163 self.videowidget = VideoWidget()
164 vbox.pack_start(self.videowidget)
167 vbox.pack_start(hbox, fill=False, expand=False)
169 self.pause_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE,
170 gtk.ICON_SIZE_BUTTON)
171 self.pause_image.show()
172 self.play_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,
173 gtk.ICON_SIZE_BUTTON)
174 self.play_image.show()
175 self.button = button = gtk.Button()
176 button.add(self.play_image)
177 button.set_property('can-default', True)
178 button.set_focus_on_click(False)
180 hbox.pack_start(button, False)
181 button.set_property('has-default', True)
182 button.connect('clicked', lambda *args: self.play_toggled())
184 self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
185 hscale = gtk.HScale(self.adjustment)
187 hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
188 hscale.connect('button-press-event', self.scale_button_press_cb)
189 hscale.connect('button-release-event', self.scale_button_release_cb)
190 hscale.connect('format-value', self.scale_format_value_cb)
191 hbox.pack_start(hscale)
194 self.videowidget.connect_after('realize',
195 lambda *x: self.play_toggled())
197 def play_toggled(self):
198 self.button.remove(self.button.child)
199 if self.player.is_playing():
201 self.button.add(self.play_image)
204 if self.update_id == -1:
205 self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
206 self.update_scale_cb)
207 self.button.add(self.pause_image)
209 def scale_format_value_cb(self, scale, value):
210 if self.p_duration == -1:
213 real = value * self.p_duration / 100
215 seconds = real / gst.SECOND
217 return "%02d:%02d" % (seconds / 60, seconds % 60)
219 def scale_button_press_cb(self, widget, event):
220 # see seek.c:start_seek
221 gst.debug('starting seek')
223 self.button.set_sensitive(False)
224 self.was_playing = self.player.is_playing()
228 # don't timeout-update position during seek
229 if self.update_id != -1:
230 gobject.source_remove(self.update_id)
233 # make sure we get changed notifies
234 if self.changed_id == -1:
235 self.changed_id = self.hscale.connect('value-changed',
236 self.scale_value_changed_cb)
238 def scale_value_changed_cb(self, scale):
240 real = long(scale.get_value() * self.p_duration / 100) # in ns
241 gst.debug('value changed, perform seek to %r' % real)
242 self.player.seek(real)
243 # allow for a preroll
244 self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
246 def scale_button_release_cb(self, widget, event):
247 # see seek.cstop_seek
248 widget.disconnect(self.changed_id)
251 self.button.set_sensitive(True)
252 if self.seek_timeout_id != -1:
253 gobject.source_remove(self.seek_timeout_id)
254 self.seek_timeout_id = -1
256 gst.debug('released slider, setting back to playing')
260 if self.update_id != -1:
261 self.error('Had a previous update timeout id')
263 self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
264 self.update_scale_cb)
266 def update_scale_cb(self):
267 self.p_position, self.p_duration = self.player.query_position()
268 if self.p_position != gst.CLOCK_TIME_NONE:
269 value = self.p_position * 100.0 / self.p_duration
270 self.adjustment.set_value(value)
276 sys.stderr.write("usage: %s URI-OF-MEDIA-FILE\n" % args[0])
279 # Need to register our derived widget types for implicit event
280 # handlers to get called.
281 gobject.type_register(PlayerWindow)
282 gobject.type_register(VideoWidget)
289 if not gst.uri_is_valid(args[1]):
290 sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
298 if __name__ == '__main__':
299 sys.exit(main(sys.argv))