From 1fa9c49ffa865f609a9c9ae93d15dfff7193e76b Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 18 Jul 2007 17:07:51 -0400 Subject: Initial import --- NEWS | 0 activity/activity.info | 9 ++ jukeboxactivity.py | 330 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 21 ++++ 4 files changed, 360 insertions(+) create mode 100755 NEWS create mode 100755 activity/activity.info create mode 100755 jukeboxactivity.py create mode 100755 setup.py diff --git a/NEWS b/NEWS new file mode 100755 index 0000000..e69de29 diff --git a/activity/activity.info b/activity/activity.info new file mode 100755 index 0000000..53580e9 --- /dev/null +++ b/activity/activity.info @@ -0,0 +1,9 @@ +[Activity] +name = Jukebox +service_name = org.laptop.sugar.Jukebox +icon = activity-jukebox +class = jukeboxactivity.JukeboxActivity +show_launcher = yes +activity_version = 1 +mime_types = video/x-theora;audio/x-vorbis;audio/x-flac;audio/x-speex;application/x-ogm-video;application/x-ogm-audio;video/x-mng;audio/x-aiff;audio/x-wav;audio/x-m4a;video/mpeg4;video/mpeg-stream;video/mpeg;application/ogg;video/mpegts;video/mpeg2;video/mpeg1;audio/mpeg;audio/x-ac3;video/x-cdxa;audio/x-au;audio/mpegurl;audio/x-mpegurl;audio/x-vorbis+ogg + diff --git a/jukeboxactivity.py b/jukeboxactivity.py new file mode 100755 index 0000000..a609915 --- /dev/null +++ b/jukeboxactivity.py @@ -0,0 +1,330 @@ +""" + jukeboxactivity.py + Activity that plays media. + Copyright (C) 2007 Andy Wingo + Copyright (C) 2007 Red Hat, Inc. +""" + +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + + +import logging +from gettext import gettext as _ +import os + +from sugar.activity import activity + +import pygtk +pygtk.require('2.0') + +import sys + +import gobject + +import pygst +pygst.require('0.10') +import gst +import gst.interfaces +import gtk + +import urllib + +class JukeboxActivity(activity.Activity): + UPDATE_INTERVAL = 500 + + def __init__(self, handle): + activity.Activity.__init__(self, handle) + self.set_title(_('Jukebox Activity')) + + toolbox = activity.ActivityToolbox(self) + self.set_toolbox(toolbox) + toolbox.show() + + self.connect("shared", self._shared_cb) + + if handle.uri: + pass + elif self._shared_activity: + if self.get_shared(): + pass + else: + # Wait for a successful join before trying to get the document + self.connect("joined", self._joined_cb) + + 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 + + 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.show_all() + + if handle.uri: + gobject.idle_add(self._start, handle.uri) + + def _joined_cb(self, activity): + pass + + def _shared_cb(self, activity): + pass + + def read_file(self, file_path): + uri = "file://" + urllib.quote(os.path.abspath(file_path)) + gobject.idle_add(self._start, uri) + + def write_file(self, file_path): + pass + + def _start(self, uri=None): + if not uri: + return False + # FIXME: parse m3u files and extract actual URL + self.player.set_uri(uri) + self.play_toggled() + return False + + def create_ui(self): + vbox = gtk.VBox() + self.set_canvas(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 + + 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 + +class GstPlayer: + def __init__(self, videowidget): + self.playing = False + self.player = gst.element_factory_make("playbin", "player") + # FIXME: hook up to the 'error' signal of the playbin + + xvsink = gst.element_factory_make("xvimagesink", "xvimagesink") + self.player.set_property("video-sink", xvsink) + + 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 set_uri(self, uri): + self.player.set_property('uri', uri) + + 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() + logging.debug("Error: %s - %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 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) + + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..318c786 --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +# Copyright (C) 2006, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from sugar.activity import bundlebuilder + +bundlebuilder.start('Jukebox') -- cgit