3
# vi:si:et:sw=4:sts=4:ts=4
19
def __init__(self, videowidget):
21
self.player = gst.element_factory_make("playbin", "player")
22
self.videowidget = videowidget
25
bus = self.player.get_bus()
26
bus.enable_sync_message_emission()
27
bus.add_signal_watch()
28
bus.connect('sync-message::element', self.on_sync_message)
29
bus.connect('message', self.on_message)
31
def on_sync_message(self, bus, message):
32
if message.structure is None:
34
if message.structure.get_name() == 'prepare-xwindow-id':
35
self.videowidget.set_sink(message.src)
36
message.src.set_property('force-aspect-ratio', True)
38
def on_message(self, bus, message):
40
if t == gst.MESSAGE_ERROR:
41
err, debug = message.parse_error()
42
print "Error: %s" % err, debug
46
elif t == gst.MESSAGE_EOS:
51
def set_location(self, location):
52
self.player.set_property('uri', location)
54
def query_position(self):
55
"Returns a (position, duration) tuple"
57
position, format = self.player.query_position(gst.FORMAT_TIME)
59
position = gst.CLOCK_TIME_NONE
62
duration, format = self.player.query_duration(gst.FORMAT_TIME)
64
duration = gst.CLOCK_TIME_NONE
66
return (position, duration)
68
def seek(self, location):
70
@param location: time to seek to, in nanoseconds
72
gst.debug("seeking to %r" % location)
73
event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
74
gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
75
gst.SEEK_TYPE_SET, location,
76
gst.SEEK_TYPE_NONE, 0)
78
res = self.player.send_event(event)
80
gst.info("setting new stream time to 0")
81
self.player.set_new_stream_time(0L)
83
gst.error("seek to %r failed" % location)
86
gst.info("pausing player")
87
self.player.set_state(gst.STATE_PAUSED)
91
gst.info("playing player")
92
self.player.set_state(gst.STATE_PLAYING)
96
self.player.set_state(gst.STATE_NULL)
97
gst.info("stopped player")
99
def get_state(self, timeout=1):
100
return self.player.get_state(timeout=timeout)
102
def is_playing(self):
105
class VideoWidget(gtk.DrawingArea):
107
gtk.DrawingArea.__init__(self)
108
self.imagesink = None
109
self.unset_flags(gtk.DOUBLE_BUFFERED)
111
def do_expose_event(self, event):
113
self.imagesink.expose()
118
def set_sink(self, sink):
119
assert self.window.xid
120
self.imagesink = sink
121
self.imagesink.set_xwindow_id(self.window.xid)
123
class PlayerWindow(gtk.Window):
124
UPDATE_INTERVAL = 500
126
gtk.Window.__init__(self)
127
self.set_default_size(410, 325)
131
self.player = GstPlayer(self.videowidget)
136
self.player.on_eos = lambda *x: on_eos()
140
self.seek_timeout_id = -1
142
self.p_position = gst.CLOCK_TIME_NONE
143
self.p_duration = gst.CLOCK_TIME_NONE
145
def on_delete_event():
148
self.connect('delete-event', lambda *x: on_delete_event())
150
def load_file(self, location):
151
self.player.set_location(location)
157
self.videowidget = VideoWidget()
158
vbox.pack_start(self.videowidget)
161
vbox.pack_start(hbox, fill=False, expand=False)
163
self.pause_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE,
164
gtk.ICON_SIZE_BUTTON)
165
self.pause_image.show()
166
self.play_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,
167
gtk.ICON_SIZE_BUTTON)
168
self.play_image.show()
169
self.button = button = gtk.Button()
170
button.add(self.play_image)
171
button.set_property('can-default', True)
172
button.set_focus_on_click(False)
174
hbox.pack_start(button, False)
175
button.set_property('has-default', True)
176
button.connect('clicked', lambda *args: self.play_toggled())
178
self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
179
hscale = gtk.HScale(self.adjustment)
181
hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
182
hscale.connect('button-press-event', self.scale_button_press_cb)
183
hscale.connect('button-release-event', self.scale_button_release_cb)
184
hscale.connect('format-value', self.scale_format_value_cb)
185
hbox.pack_start(hscale)
188
self.videowidget.connect_after('realize',
189
lambda *x: self.play_toggled())
191
def play_toggled(self):
192
self.button.remove(self.button.child)
193
if self.player.is_playing():
195
self.button.add(self.play_image)
198
if self.update_id == -1:
199
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
200
self.update_scale_cb)
201
self.button.add(self.pause_image)
203
def scale_format_value_cb(self, scale, value):
204
if self.p_duration == -1:
207
real = value * self.p_duration / 100
209
seconds = real / gst.SECOND
211
return "%02d:%02d" % (seconds / 60, seconds % 60)
213
def scale_button_press_cb(self, widget, event):
214
# see seek.c:start_seek
215
gst.debug('starting seek')
217
self.button.set_sensitive(False)
218
self.was_playing = self.player.is_playing()
222
# don't timeout-update position during seek
223
if self.update_id != -1:
224
gobject.source_remove(self.update_id)
227
# make sure we get changed notifies
228
if self.changed_id == -1:
229
self.changed_id = self.hscale.connect('value-changed',
230
self.scale_value_changed_cb)
232
def scale_value_changed_cb(self, scale):
234
real = long(scale.get_value() * self.p_duration / 100) # in ns
235
gst.debug('value changed, perform seek to %r' % real)
236
self.player.seek(real)
237
# allow for a preroll
238
self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
240
def scale_button_release_cb(self, widget, event):
241
# see seek.cstop_seek
242
widget.disconnect(self.changed_id)
245
self.button.set_sensitive(True)
246
if self.seek_timeout_id != -1:
247
gobject.source_remove(self.seek_timeout_id)
248
self.seek_timeout_id = -1
250
gst.debug('released slider, setting back to playing')
254
if self.update_id != -1:
255
self.error('Had a previous update timeout id')
257
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
258
self.update_scale_cb)
260
def update_scale_cb(self):
261
self.p_position, self.p_duration = self.player.query_position()
262
if self.p_position != gst.CLOCK_TIME_NONE:
263
value = self.p_position * 100.0 / self.p_duration
264
self.adjustment.set_value(value)
270
sys.stderr.write("usage: %s URI-OF-MEDIA-FILE\n" % args[0])
278
if not gst.uri_is_valid(args[1]):
279
sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
287
if __name__ == '__main__':
288
sys.exit(main(sys.argv))