Line 1: |
Line 1: |
− | {{Sugar Almanac}} | + | {{Almanac}} |
| | | |
| === Where can I get additional resources and sample code on using the camera? === | | === Where can I get additional resources and sample code on using the camera? === |
Line 5: |
Line 5: |
| | | |
| | | |
− | === How can I play back video files in my activity? === | + | === How can I embed video files in my activity? === |
| Sugar, by default, can play back audio (encoded in Vorbis) and video (encoded in Theora) files in ogg containers. | | Sugar, by default, can play back audio (encoded in Vorbis) and video (encoded in Theora) files in ogg containers. |
| + | |
| + | The following file will be helpful for embedding video or audio files into your activity. After the code posting is a code snippet showing how to use this. |
| + | |
| + | <pre> |
| + | import gtk |
| + | import pygtk |
| + | pygtk.require('2.0') |
| + | import sys |
| + | import pygst |
| + | pygst.require('0.10') |
| + | import gst |
| + | import gst.interfaces |
| + | import gobject |
| + | import time |
| + | gobject.threads_init() |
| + | |
| + | |
| + | class Gplay: |
| + | |
| + | def __init__(self): |
| + | self.window = None |
| + | self.playing = False |
| + | |
| + | self.player = gst.element_factory_make("playbin", "playbin") |
| + | xis = gst.element_factory_make("xvimagesink", "xvimagesink") |
| + | self.player.set_property("video-sink", xis) |
| + | bus = self.player.get_bus() |
| + | bus.enable_sync_message_emission() |
| + | bus.add_signal_watch() |
| + | self.SYNC_ID = bus.connect('sync-message::element', self._onSyncMessageCb) |
| + | |
| + | |
| + | def _onSyncMessageCb(self, bus, message): |
| + | if message.structure is None: |
| + | return True |
| + | if message.structure.get_name() == 'prepare-xwindow-id': |
| + | self.window.set_sink(message.src) |
| + | message.src.set_property('force-aspect-ratio', True) |
| + | return True |
| + | |
| + | |
| + | def setFile(self, path): |
| + | uri = "file://" + str( path ) |
| + | if self.player.get_property('uri') == uri: |
| + | self.seek(gst.SECOND*0) |
| + | return |
| + | |
| + | self.player.set_state(gst.STATE_READY) |
| + | self.player.set_property('uri', uri) |
| + | ext = uri[len(uri)-3:] |
| + | if ext == "jpg": |
| + | self.pause() |
| + | else: |
| + | self.play() |
| + | |
| + | |
| + | def queryPosition(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, time): |
| + | event = gst.event_new_seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, time, gst.SEEK_TYPE_NONE, 0) |
| + | res = self.player.send_event(event) |
| + | if res: |
| + | self.player.set_new_stream_time(0L) |
| + | |
| + | |
| + | def pause(self): |
| + | self.playing = False |
| + | self.player.set_state(gst.STATE_PAUSED) |
| + | |
| + | |
| + | def play(self): |
| + | self.playing = True |
| + | self.player.set_state(gst.STATE_PLAYING) |
| + | |
| + | |
| + | def stop(self): |
| + | self.playing = False |
| + | self.player.set_state(gst.STATE_NULL) |
| + | self.nextMovie() |
| + | |
| + | |
| + | def get_state(self, timeout=1): |
| + | return self.player.get_state(timeout=timeout) |
| + | |
| + | |
| + | def is_playing(self): |
| + | return self.playing |
| + | |
| + | |
| + | class PlayVideoWindow(gtk.Window): |
| + | def __init__(self): |
| + | gtk.Window.__init__(self) |
| + | |
| + | self.imagesink = None |
| + | self.unset_flags(gtk.DOUBLE_BUFFERED) |
| + | self.set_flags(gtk.APP_PAINTABLE) |
| + | |
| + | |
| + | def set_sink(self, sink): |
| + | if self.imagesink != None: |
| + | assert self.window.xid |
| + | self.imagesink = None |
| + | del self.imagesink |
| + | |
| + | self.imagesink = sink |
| + | self.imagesink.set_xwindow_id(self.window.xid) |
| + | |
| + | </pre> |
| + | |
| + | And here is a code snippet showing how you would use this file to embed media playback in your activity: |
| + | |
| + | <pre> |
| + | #this is our video playback window |
| + | self.gplay = Gplay( ) |
| + | self.gplayWin = PlayVideoWindow( ) |
| + | self.gplay.window = self.gplayWin |
| + | |
| + | self.gplayWin.set_type_hint( gtk.gdk.WINDOW_TYPE_HINT_DIALOG ) |
| + | self.gplayWin.set_decorated( False ) |
| + | self.gplayWin.set_transient_for( self ) #activity subclass |
| + | self.gplayWin.move( 100, 100 ) |
| + | self.gplayWin.resize( 300, 400 ) |
| + | self.gplayWin.show_all( ) |
| + | </pre> |
| + | |
| + | Alternatively, you could embed your video into a gtk.DrawingArea: |
| + | |
| + | <pre> |
| + | class VideoWidget(gtk.DrawingArea): |
| + | def __init__(self): |
| + | gtk.DrawingArea.__init__(self) |
| + | self.set_events(gtk.gdk.POINTER_MOTION_MASK | |
| + | gtk.gdk.POINTER_MOTION_HINT_MASK | |
| + | gtk.gdk.EXPOSURE_MASK | |
| + | gtk.gdk.KEY_PRESS_MASK | |
| + | gtk.gdk.KEY_RELEASE_MASK) |
| + | self.imagesink = None |
| + | self.unset_flags(gtk.DOUBLE_BUFFERED) |
| + | self.set_flags(gtk.APP_PAINTABLE) |
| + | |
| + | 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) |
| + | |
| + | </pre> |
| + | |
| + | === How can I add play/pause buttons and a scrubber to my audio or video? === |
| + | |
| + | The widget below is pre-configured to work with the gplay class above. This widget takes two gtk.Images in its constructor. |
| + | |
| + | <pre> |
| + | import gtk |
| + | import pygtk |
| + | pygtk.require('2.0') |
| + | import pygst |
| + | pygst.require('0.10') |
| + | import gst |
| + | import gst.interfaces |
| + | import gobject |
| + | |
| + | import sugar.graphics.style |
| + | |
| + | class GScrub(gtk.Window): |
| + | |
| + | def __init__(self, gplay, playImg, pauseImg): |
| + | gtk.Window.__init__(self) |
| + | self._gplay = gplay |
| + | self._playImg = playImg |
| + | self._pauseImg = pauseImg |
| + | |
| + | self.UPDATE_INTERVAL = 500 |
| + | self.UPDATE_SCALE_ID = 0 |
| + | self.CHANGED_ID = 0 |
| + | self.was_playing = False |
| + | self.p_position = gst.CLOCK_TIME_NONE |
| + | self.p_duration = gst.CLOCK_TIME_NONE |
| + | |
| + | self.hbox = gtk.HBox() |
| + | self.hbox.modify_bg( gtk.STATE_NORMAL, sugar.graphics.style.COLOR_BLACK.get_gdk_color() ) |
| + | self.hbox.modify_bg( gtk.STATE_INSENSITIVE, sugar.graphics.style.COLOR_BLACK.get_gdk_color() ) |
| + | self.add( self.hbox ) |
| + | |
| + | self.button = gtk.Button() |
| + | buttBox = gtk.EventBox() |
| + | buttBox.add(self.button) |
| + | buttBox.modify_bg( gtk.STATE_NORMAL, sugar.graphics.style.COLOR_BLACK.get_gdk_color() ) |
| + | self.button.set_image( self._playImg ) |
| + | self.button.set_property('can-default', True) |
| + | self.button.set_relief(gtk.RELIEF_NONE) |
| + | self.button.set_size_request( 55, 55 ) |
| + | buttBox.set_size_request( 55, 55 ) |
| + | self.button.show() |
| + | |
| + | buttBox.modify_bg( gtk.STATE_NORMAL, sugar.graphics.style.COLOR_BLACK.get_gdk_color() ) |
| + | self.button.modify_bg( gtk.STATE_ACTIVE, sugar.graphics.style.COLOR_BLACK.get_gdk_color() ) |
| + | |
| + | self.button.connect('clicked', self._buttonClickedCb) |
| + | self.hbox.pack_start(buttBox, expand=False) |
| + | |
| + | self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0) |
| + | self.hscale = gtk.HScale(self.adjustment) |
| + | self.hscale.set_draw_value(False) |
| + | self.hscale.set_update_policy(gtk.UPDATE_CONTINUOUS) |
| + | hscaleBox = gtk.EventBox() |
| + | hscaleBox.modify_bg( gtk.STATE_NORMAL, sugar.graphics.style.COLOR_BLACK.get_gdk_color() ) |
| + | hscaleBox.add( self.hscale ) |
| + | self.hscale.connect('button-press-event', self._scaleButtonPressCb) |
| + | self.hscale.connect('button-release-event', self._scaleButtonReleaseCb) |
| + | self.hbox.pack_start(hscaleBox, expand=True) |
| + | |
| + | |
| + | def removeCallbacks( self ): |
| + | if self.UPDATE_SCALE_ID != 0: |
| + | gobject.source_remove(self.UPDATE_SCALE_ID) |
| + | self.UPDATE_SCALE_ID = 0 |
| + | if self.CHANGED_ID != 0: |
| + | gobject.source_remove(self.CHANGED_ID) |
| + | self.CHANGED_ID = 0 |
| + | |
| + | |
| + | def reset(self): |
| + | self.adjustment.set_value(0) |
| + | |
| + | |
| + | def _buttonClickedCb(self, widget): |
| + | self.play_toggled() |
| + | |
| + | |
| + | def set_button_play(self): |
| + | self.button.set_image(self._playImg) |
| + | |
| + | |
| + | def set_button_pause(self): |
| + | self.button.set_image(self._pauseImg) |
| + | |
| + | |
| + | def play_toggled(self): |
| + | self.p_position, self.p_duration = self._gplay.queryPosition() |
| + | if self.p_position == self.p_duration: |
| + | self._gplay.seek(0) |
| + | self._gplay.pause() |
| + | |
| + | if self._gplay.is_playing(): |
| + | self._gplay.pause() |
| + | self.set_button_play() |
| + | else: |
| + | #if self._gplay.error: |
| + | # #todo: check if we have "error", and also to disable everything |
| + | # self.button.set_disabled() |
| + | #else: |
| + | self.doPlay() |
| + | |
| + | |
| + | def doPlay(self): |
| + | self._gplay.play() |
| + | if self.UPDATE_SCALE_ID == 0: |
| + | self.UPDATE_SCALE_ID = gobject.timeout_add(self.UPDATE_INTERVAL, self._updateScaleCb) |
| + | self.set_button_pause() |
| + | |
| + | |
| + | def _scaleButtonPressCb(self, widget, event): |
| + | #self.button.set_sensitive(False) |
| + | self.was_playing = self._gplay.is_playing() |
| + | if self.was_playing: |
| + | self._gplay.pause() |
| + | |
| + | # don't timeout-update position during seek |
| + | if self.UPDATE_SCALE_ID != 0: |
| + | gobject.source_remove(self.UPDATE_SCALE_ID) |
| + | self.UPDATE_SCALE_ID = 0 |
| + | |
| + | # make sure we get changed notifies |
| + | if self.CHANGED_ID == 0: |
| + | self.CHANGED_ID = self.hscale.connect('value-changed', self._scaleValueChangedCb) |
| + | |
| + | |
| + | def _scaleButtonReleaseCb(self, widget, event): |
| + | # see seek.cstop_seek |
| + | widget.disconnect(self.CHANGED_ID) |
| + | self.CHANGED_ID = 0 |
| + | |
| + | #self.button.set_sensitive(True) |
| + | if self.was_playing: |
| + | self._gplay.play() |
| + | |
| + | if self.UPDATE_SCALE_ID != 0: |
| + | pass |
| + | #print('Had a previous update timeout id') |
| + | else: |
| + | self.UPDATE_SCALE_ID = gobject.timeout_add(self.UPDATE_INTERVAL, self._updateScaleCb) |
| + | |
| + | |
| + | def _scaleValueChangedCb(self, scale): |
| + | real = long(scale.get_value() * self.p_duration / 100) # in ns |
| + | self._gplay.seek(real) |
| + | # allow for a preroll |
| + | self._gplay.get_state(timeout=50*gst.MSECOND) # 50 ms |
| + | |
| + | |
| + | def _updateScaleCb(self): |
| + | self.p_position, self.p_duration = self._gplay.queryPosition() |
| + | if self.p_position != gst.CLOCK_TIME_NONE: |
| + | value = self.p_position * 100.0 / self.p_duration |
| + | if value > 99: |
| + | value = 99 |
| + | elif value < 0: |
| + | value = 0 |
| + | |
| + | self.adjustment.set_value(value) |
| + | |
| + | if self._gplay.is_playing() and self.p_position == self.p_duration: |
| + | self._gplay.pause() |
| + | self.set_button_play() |
| + | |
| + | return True |
| + | </pre> |