2005-11-19 10:37:58 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- Mode: Python -*-
|
|
|
|
# vi:si:et:sw=4:sts=4:ts=4
|
|
|
|
|
2004-10-11 09:55:44 +00:00
|
|
|
import pygtk
|
|
|
|
pygtk.require('2.0')
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
import gobject
|
2009-05-06 22:38:08 +00:00
|
|
|
gobject.threads_init()
|
2005-11-04 10:14:22 +00:00
|
|
|
|
|
|
|
import pygst
|
2005-12-01 19:15:26 +00:00
|
|
|
pygst.require('0.10')
|
2004-10-11 09:55:44 +00:00
|
|
|
import gst
|
|
|
|
import gst.interfaces
|
|
|
|
import gtk
|
2009-05-06 22:38:08 +00:00
|
|
|
gtk.gdk.threads_init()
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
class GstPlayer:
|
2006-02-10 17:49:47 +00:00
|
|
|
def __init__(self, videowidget):
|
|
|
|
self.playing = False
|
2004-10-11 09:55:44 +00:00
|
|
|
self.player = gst.element_factory_make("playbin", "player")
|
2006-02-10 17:49:47 +00:00
|
|
|
self.videowidget = videowidget
|
|
|
|
self.on_eos = False
|
|
|
|
|
|
|
|
bus = self.player.get_bus()
|
|
|
|
bus.enable_sync_message_emission()
|
|
|
|
bus.add_signal_watch()
|
|
|
|
bus.connect('sync-message::element', self.on_sync_message)
|
|
|
|
bus.connect('message', self.on_message)
|
|
|
|
|
|
|
|
def on_sync_message(self, bus, message):
|
|
|
|
if message.structure is None:
|
|
|
|
return
|
|
|
|
if message.structure.get_name() == 'prepare-xwindow-id':
|
2008-04-24 11:35:38 +00:00
|
|
|
# Sync with the X server before giving the X-id to the sink
|
2009-05-06 22:38:08 +00:00
|
|
|
gtk.gdk.threads_enter()
|
2008-04-24 11:35:38 +00:00
|
|
|
gtk.gdk.display_get_default().sync()
|
2006-02-10 17:49:47 +00:00
|
|
|
self.videowidget.set_sink(message.src)
|
|
|
|
message.src.set_property('force-aspect-ratio', True)
|
2009-05-06 22:38:08 +00:00
|
|
|
gtk.gdk.threads_leave()
|
2006-02-10 17:49:47 +00:00
|
|
|
|
|
|
|
def on_message(self, bus, message):
|
|
|
|
t = message.type
|
|
|
|
if t == gst.MESSAGE_ERROR:
|
|
|
|
err, debug = message.parse_error()
|
|
|
|
print "Error: %s" % err, debug
|
|
|
|
if self.on_eos:
|
|
|
|
self.on_eos()
|
|
|
|
self.playing = False
|
|
|
|
elif t == gst.MESSAGE_EOS:
|
|
|
|
if self.on_eos:
|
|
|
|
self.on_eos()
|
|
|
|
self.playing = False
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
def set_location(self, location):
|
|
|
|
self.player.set_property('uri', location)
|
|
|
|
|
2005-10-09 17:53:58 +00:00
|
|
|
def query_position(self):
|
|
|
|
"Returns a (position, duration) tuple"
|
2005-11-04 10:14:22 +00:00
|
|
|
try:
|
2006-02-10 10:53:22 +00:00
|
|
|
position, format = self.player.query_position(gst.FORMAT_TIME)
|
2005-11-04 10:14:22 +00:00
|
|
|
except:
|
|
|
|
position = gst.CLOCK_TIME_NONE
|
|
|
|
|
|
|
|
try:
|
2006-02-10 10:53:22 +00:00
|
|
|
duration, format = self.player.query_duration(gst.FORMAT_TIME)
|
2005-11-04 10:14:22 +00:00
|
|
|
except:
|
|
|
|
duration = gst.CLOCK_TIME_NONE
|
2004-10-11 09:55:44 +00:00
|
|
|
|
2006-02-10 10:53:22 +00:00
|
|
|
return (position, duration)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
def seek(self, location):
|
2005-10-09 17:53:58 +00:00
|
|
|
"""
|
|
|
|
@param location: time to seek to, in nanoseconds
|
|
|
|
"""
|
|
|
|
gst.debug("seeking to %r" % location)
|
|
|
|
event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
|
2006-02-10 17:49:47 +00:00
|
|
|
gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
|
2005-10-09 17:53:58 +00:00
|
|
|
gst.SEEK_TYPE_SET, location,
|
|
|
|
gst.SEEK_TYPE_NONE, 0)
|
|
|
|
|
|
|
|
res = self.player.send_event(event)
|
|
|
|
if res:
|
|
|
|
gst.info("setting new stream time to 0")
|
|
|
|
self.player.set_new_stream_time(0L)
|
|
|
|
else:
|
|
|
|
gst.error("seek to %r failed" % location)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
def pause(self):
|
2005-10-09 17:53:58 +00:00
|
|
|
gst.info("pausing player")
|
2004-10-11 09:55:44 +00:00
|
|
|
self.player.set_state(gst.STATE_PAUSED)
|
2006-02-10 17:49:47 +00:00
|
|
|
self.playing = False
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
def play(self):
|
2005-10-09 17:53:58 +00:00
|
|
|
gst.info("playing player")
|
2004-10-11 09:55:44 +00:00
|
|
|
self.player.set_state(gst.STATE_PLAYING)
|
2006-02-10 17:49:47 +00:00
|
|
|
self.playing = True
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
def stop(self):
|
2006-02-10 17:49:47 +00:00
|
|
|
self.player.set_state(gst.STATE_NULL)
|
2005-10-09 17:53:58 +00:00
|
|
|
gst.info("stopped player")
|
|
|
|
|
2005-11-04 10:14:22 +00:00
|
|
|
def get_state(self, timeout=1):
|
2005-10-09 17:53:58 +00:00
|
|
|
return self.player.get_state(timeout=timeout)
|
|
|
|
|
2006-02-10 17:49:47 +00:00
|
|
|
def is_playing(self):
|
|
|
|
return self.playing
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
class VideoWidget(gtk.DrawingArea):
|
2006-02-10 17:49:47 +00:00
|
|
|
def __init__(self):
|
2004-10-11 09:55:44 +00:00
|
|
|
gtk.DrawingArea.__init__(self)
|
2006-02-10 17:49:47 +00:00
|
|
|
self.imagesink = None
|
|
|
|
self.unset_flags(gtk.DOUBLE_BUFFERED)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
2006-02-10 17:49:47 +00:00
|
|
|
def do_expose_event(self, event):
|
|
|
|
if self.imagesink:
|
|
|
|
self.imagesink.expose()
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
2005-10-09 17:53:58 +00:00
|
|
|
|
2006-02-10 17:49:47 +00:00
|
|
|
def set_sink(self, sink):
|
|
|
|
assert self.window.xid
|
|
|
|
self.imagesink = sink
|
|
|
|
self.imagesink.set_xwindow_id(self.window.xid)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
class PlayerWindow(gtk.Window):
|
|
|
|
UPDATE_INTERVAL = 500
|
|
|
|
def __init__(self):
|
|
|
|
gtk.Window.__init__(self)
|
2006-02-10 17:49:47 +00:00
|
|
|
self.set_default_size(410, 325)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
self.create_ui()
|
|
|
|
|
2006-02-10 17:49:47 +00:00
|
|
|
self.player = GstPlayer(self.videowidget)
|
|
|
|
|
|
|
|
def on_eos():
|
|
|
|
self.player.seek(0L)
|
|
|
|
self.play_toggled()
|
|
|
|
self.player.on_eos = lambda *x: on_eos()
|
|
|
|
|
2004-10-11 09:55:44 +00:00
|
|
|
self.update_id = -1
|
2005-10-09 17:53:58 +00:00
|
|
|
self.changed_id = -1
|
|
|
|
self.seek_timeout_id = -1
|
|
|
|
|
|
|
|
self.p_position = gst.CLOCK_TIME_NONE
|
|
|
|
self.p_duration = gst.CLOCK_TIME_NONE
|
2006-02-10 17:49:47 +00:00
|
|
|
|
|
|
|
def on_delete_event():
|
|
|
|
self.player.stop()
|
|
|
|
gtk.main_quit()
|
|
|
|
self.connect('delete-event', lambda *x: on_delete_event())
|
|
|
|
|
2004-10-11 09:55:44 +00:00
|
|
|
def load_file(self, location):
|
|
|
|
self.player.set_location(location)
|
|
|
|
|
|
|
|
def create_ui(self):
|
|
|
|
vbox = gtk.VBox()
|
2006-02-10 17:49:47 +00:00
|
|
|
self.add(vbox)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
2006-02-10 17:49:47 +00:00
|
|
|
self.videowidget = VideoWidget()
|
2005-10-09 17:53:58 +00:00
|
|
|
vbox.pack_start(self.videowidget)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
hbox = gtk.HBox()
|
2005-11-04 10:14:22 +00:00
|
|
|
vbox.pack_start(hbox, fill=False, expand=False)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
2006-02-10 17:49:47 +00:00
|
|
|
self.pause_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE,
|
|
|
|
gtk.ICON_SIZE_BUTTON)
|
|
|
|
self.pause_image.show()
|
|
|
|
self.play_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,
|
|
|
|
gtk.ICON_SIZE_BUTTON)
|
|
|
|
self.play_image.show()
|
|
|
|
self.button = button = gtk.Button()
|
|
|
|
button.add(self.play_image)
|
|
|
|
button.set_property('can-default', True)
|
|
|
|
button.set_focus_on_click(False)
|
|
|
|
button.show()
|
2004-10-11 09:55:44 +00:00
|
|
|
hbox.pack_start(button, False)
|
2006-02-10 17:49:47 +00:00
|
|
|
button.set_property('has-default', True)
|
|
|
|
button.connect('clicked', lambda *args: self.play_toggled())
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
|
|
|
|
hscale = gtk.HScale(self.adjustment)
|
|
|
|
hscale.set_digits(2)
|
|
|
|
hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
|
|
|
|
hscale.connect('button-press-event', self.scale_button_press_cb)
|
|
|
|
hscale.connect('button-release-event', self.scale_button_release_cb)
|
|
|
|
hscale.connect('format-value', self.scale_format_value_cb)
|
|
|
|
hbox.pack_start(hscale)
|
2005-10-09 17:53:58 +00:00
|
|
|
self.hscale = hscale
|
2004-10-11 09:55:44 +00:00
|
|
|
|
2006-02-10 17:49:47 +00:00
|
|
|
self.videowidget.connect_after('realize',
|
|
|
|
lambda *x: self.play_toggled())
|
|
|
|
|
|
|
|
def play_toggled(self):
|
|
|
|
self.button.remove(self.button.child)
|
|
|
|
if self.player.is_playing():
|
|
|
|
self.player.pause()
|
|
|
|
self.button.add(self.play_image)
|
|
|
|
else:
|
|
|
|
self.player.play()
|
|
|
|
if self.update_id == -1:
|
|
|
|
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
|
|
|
|
self.update_scale_cb)
|
|
|
|
self.button.add(self.pause_image)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
def scale_format_value_cb(self, scale, value):
|
2005-10-09 17:53:58 +00:00
|
|
|
if self.p_duration == -1:
|
2004-10-11 09:55:44 +00:00
|
|
|
real = 0
|
|
|
|
else:
|
2005-10-09 17:53:58 +00:00
|
|
|
real = value * self.p_duration / 100
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
seconds = real / gst.SECOND
|
|
|
|
|
|
|
|
return "%02d:%02d" % (seconds / 60, seconds % 60)
|
|
|
|
|
|
|
|
def scale_button_press_cb(self, widget, event):
|
2005-10-09 17:53:58 +00:00
|
|
|
# see seek.c:start_seek
|
|
|
|
gst.debug('starting seek')
|
2006-02-10 17:49:47 +00:00
|
|
|
|
|
|
|
self.button.set_sensitive(False)
|
|
|
|
self.was_playing = self.player.is_playing()
|
|
|
|
if self.was_playing:
|
|
|
|
self.player.pause()
|
2005-10-09 17:53:58 +00:00
|
|
|
|
|
|
|
# don't timeout-update position during seek
|
2004-10-11 09:55:44 +00:00
|
|
|
if self.update_id != -1:
|
2005-10-09 17:53:58 +00:00
|
|
|
gobject.source_remove(self.update_id)
|
2004-10-11 09:55:44 +00:00
|
|
|
self.update_id = -1
|
2005-10-09 17:53:58 +00:00
|
|
|
|
|
|
|
# make sure we get changed notifies
|
|
|
|
if self.changed_id == -1:
|
|
|
|
self.changed_id = self.hscale.connect('value-changed',
|
|
|
|
self.scale_value_changed_cb)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
2005-10-09 17:53:58 +00:00
|
|
|
def scale_value_changed_cb(self, scale):
|
|
|
|
# see seek.c:seek_cb
|
|
|
|
real = long(scale.get_value() * self.p_duration / 100) # in ns
|
|
|
|
gst.debug('value changed, perform seek to %r' % real)
|
2004-10-11 09:55:44 +00:00
|
|
|
self.player.seek(real)
|
2005-10-09 17:53:58 +00:00
|
|
|
# allow for a preroll
|
2006-02-10 17:49:47 +00:00
|
|
|
self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
|
2005-10-09 17:53:58 +00:00
|
|
|
|
|
|
|
def scale_button_release_cb(self, widget, event):
|
|
|
|
# see seek.cstop_seek
|
|
|
|
widget.disconnect(self.changed_id)
|
|
|
|
self.changed_id = -1
|
|
|
|
|
2006-02-10 17:49:47 +00:00
|
|
|
self.button.set_sensitive(True)
|
2005-10-09 17:53:58 +00:00
|
|
|
if self.seek_timeout_id != -1:
|
|
|
|
gobject.source_remove(self.seek_timeout_id)
|
|
|
|
self.seek_timeout_id = -1
|
|
|
|
else:
|
|
|
|
gst.debug('released slider, setting back to playing')
|
2006-02-10 17:49:47 +00:00
|
|
|
if self.was_playing:
|
|
|
|
self.player.play()
|
2005-10-09 17:53:58 +00:00
|
|
|
|
|
|
|
if self.update_id != -1:
|
|
|
|
self.error('Had a previous update timeout id')
|
|
|
|
else:
|
|
|
|
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
|
|
|
|
self.update_scale_cb)
|
2004-10-11 09:55:44 +00:00
|
|
|
|
|
|
|
def update_scale_cb(self):
|
2006-02-10 10:53:22 +00:00
|
|
|
self.p_position, self.p_duration = self.player.query_position()
|
2005-10-09 17:53:58 +00:00
|
|
|
if self.p_position != gst.CLOCK_TIME_NONE:
|
|
|
|
value = self.p_position * 100.0 / self.p_duration
|
2004-10-11 09:55:44 +00:00
|
|
|
self.adjustment.set_value(value)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def main(args):
|
2006-02-10 10:53:22 +00:00
|
|
|
def usage():
|
|
|
|
sys.stderr.write("usage: %s URI-OF-MEDIA-FILE\n" % args[0])
|
|
|
|
sys.exit(1)
|
|
|
|
|
2007-10-18 15:10:44 +00:00
|
|
|
# Need to register our derived widget types for implicit event
|
|
|
|
# handlers to get called.
|
|
|
|
gobject.type_register(PlayerWindow)
|
|
|
|
gobject.type_register(VideoWidget)
|
|
|
|
|
2004-10-11 09:55:44 +00:00
|
|
|
w = PlayerWindow()
|
2006-02-10 10:53:22 +00:00
|
|
|
|
|
|
|
if len(args) != 2:
|
|
|
|
usage()
|
|
|
|
|
|
|
|
if not gst.uri_is_valid(args[1]):
|
|
|
|
sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
|
|
|
|
sys.exit(1)
|
|
|
|
|
2004-10-11 09:55:44 +00:00
|
|
|
w.load_file(args[1])
|
|
|
|
w.show_all()
|
|
|
|
|
|
|
|
gtk.main()
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
sys.exit(main(sys.argv))
|