home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/env python
- # -*- Mode: Python -*-
- # vi:si:et:sw=4:sts=4:ts=4
-
- import pygtk
- pygtk.require('2.0')
-
- import sys
-
- import gobject
-
- import pygst
- pygst.require('0.10')
- import gst
- import gst.interfaces
- import gtk
-
- class GstPlayer:
- def __init__(self, videowidget):
- self.playing = False
- self.player = gst.element_factory_make("playbin", "player")
- 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':
- self.videowidget.set_sink(message.src)
- message.src.set_property('force-aspect-ratio', True)
-
- 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
-
- def set_location(self, location):
- self.player.set_property('uri', location)
-
- def query_position(self):
- "Returns a (position, duration) tuple"
- try:
- position, format = self.player.query_position(gst.FORMAT_TIME)
- except:
- position = gst.CLOCK_TIME_NONE
-
- try:
- duration, format = self.player.query_duration(gst.FORMAT_TIME)
- except:
- duration = gst.CLOCK_TIME_NONE
-
- return (position, duration)
-
- def seek(self, location):
- """
- @param location: time to seek to, in nanoseconds
- """
- gst.debug("seeking to %r" % location)
- event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
- gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
- 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)
-
- def pause(self):
- gst.info("pausing player")
- self.player.set_state(gst.STATE_PAUSED)
- self.playing = False
-
- def play(self):
- gst.info("playing player")
- self.player.set_state(gst.STATE_PLAYING)
- self.playing = True
-
- def stop(self):
- self.player.set_state(gst.STATE_NULL)
- gst.info("stopped player")
-
- def get_state(self, timeout=1):
- return self.player.get_state(timeout=timeout)
-
- def is_playing(self):
- return self.playing
-
- class VideoWidget(gtk.DrawingArea):
- def __init__(self):
- gtk.DrawingArea.__init__(self)
- self.imagesink = None
- self.unset_flags(gtk.DOUBLE_BUFFERED)
-
- def do_expose_event(self, event):
- if self.imagesink:
- self.imagesink.expose()
- return False
- else:
- return True
-
- def set_sink(self, sink):
- assert self.window.xid
- self.imagesink = sink
- self.imagesink.set_xwindow_id(self.window.xid)
-
- class PlayerWindow(gtk.Window):
- UPDATE_INTERVAL = 500
- def __init__(self):
- gtk.Window.__init__(self)
- self.set_default_size(410, 325)
-
- self.create_ui()
-
- self.player = GstPlayer(self.videowidget)
-
- def on_eos():
- self.player.seek(0L)
- self.play_toggled()
- self.player.on_eos = lambda *x: on_eos()
-
- self.update_id = -1
- self.changed_id = -1
- self.seek_timeout_id = -1
-
- self.p_position = gst.CLOCK_TIME_NONE
- self.p_duration = gst.CLOCK_TIME_NONE
-
- def on_delete_event():
- self.player.stop()
- gtk.main_quit()
- self.connect('delete-event', lambda *x: on_delete_event())
-
- def load_file(self, location):
- self.player.set_location(location)
-
- def create_ui(self):
- vbox = gtk.VBox()
- self.add(vbox)
-
- self.videowidget = VideoWidget()
- vbox.pack_start(self.videowidget)
-
- hbox = gtk.HBox()
- vbox.pack_start(hbox, fill=False, expand=False)
-
- 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()
- hbox.pack_start(button, False)
- button.set_property('has-default', True)
- button.connect('clicked', lambda *args: self.play_toggled())
-
- 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)
- self.hscale = hscale
-
- 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)
-
- def scale_format_value_cb(self, scale, value):
- if self.p_duration == -1:
- real = 0
- else:
- real = value * self.p_duration / 100
-
- seconds = real / gst.SECOND
-
- return "%02d:%02d" % (seconds / 60, seconds % 60)
-
- def scale_button_press_cb(self, widget, event):
- # see seek.c:start_seek
- gst.debug('starting seek')
-
- self.button.set_sensitive(False)
- self.was_playing = self.player.is_playing()
- if self.was_playing:
- self.player.pause()
-
- # don't timeout-update position during seek
- if self.update_id != -1:
- gobject.source_remove(self.update_id)
- self.update_id = -1
-
- # make sure we get changed notifies
- if self.changed_id == -1:
- self.changed_id = self.hscale.connect('value-changed',
- self.scale_value_changed_cb)
-
- 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)
- self.player.seek(real)
- # allow for a preroll
- self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
-
- def scale_button_release_cb(self, widget, event):
- # see seek.cstop_seek
- widget.disconnect(self.changed_id)
- self.changed_id = -1
-
- self.button.set_sensitive(True)
- 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')
- if self.was_playing:
- self.player.play()
-
- 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)
-
- def update_scale_cb(self):
- self.p_position, self.p_duration = self.player.query_position()
- if self.p_position != gst.CLOCK_TIME_NONE:
- value = self.p_position * 100.0 / self.p_duration
- self.adjustment.set_value(value)
-
- return True
-
- def main(args):
- def usage():
- sys.stderr.write("usage: %s URI-OF-MEDIA-FILE\n" % args[0])
- sys.exit(1)
-
- w = PlayerWindow()
-
- 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)
-
- w.load_file(args[1])
- w.show_all()
-
- gtk.main()
-
- if __name__ == '__main__':
- sys.exit(main(sys.argv))
-