Changeset - cca75951554a
[Not reviewed]
default
0 3 0
Drew Perttula - 12 years ago 2013-06-10 08:28:06
drewp@bigasterisk.com
port ascoltami to Gst 1.0, work around the missing message signals
Ignore-this: c11f3e788d38fad6928e136035f3331a
3 files changed with 76 insertions and 57 deletions:
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
#!bin/python
 
from run_local import log
 
from twisted.internet import gtk2reactor, reactor
 
import web, thread, gobject, sys, optparse, logging
 
from twisted.internet import gireactor, reactor
 
import web, thread, sys, optparse, logging
 
from rdflib import URIRef
 
sys.path.append(".")
 
from light9.ascoltami.player import Player
 
from light9.ascoltami.playlist import Playlist, NoSuchSong
 
from light9.ascoltami.webapp import makeWebApp, songUri, songLocation
 
from light9 import networking, showconfig
 

	
 
import gi
 
gi.require_version('Gst', '1.0')
 
from gi.repository import GObject, Gst, Gtk
 

	
 
class App:
 
class App(object):
 
    def __init__(self, graph, show):
 
        self.graph = graph
 
        self.player = Player(onEOS=self.onEOS)
 
        self.show = show
 
        self.playlist = Playlist.fromShow(graph, show)
 

	
 
    def run(self, musicPort):
 
        # the cherrypy server would wedge when vidref pounds on it; this
 
        # one seems to run
 
        #gtk2reactor.install(useGtk=False)
 
        reactor.listenTCP(musicPort, makeWebApp(self))
 
        log.info("listening on %s" % musicPort)
 
        reactor.run()
 

	
 
    def onEOS(self, song):
 
        self.player.pause()
 
        self.player.seek(0)
 

	
 
        thisSongUri = songUri(graph, URIRef(song))
 

	
 
@@ -36,13 +31,14 @@ class App:
 
        except NoSuchSong: # we're at the end of the playlist
 
            return
 

	
 
        self.player.setSong(songLocation(graph, nextSong), play=False)
 

	
 
if __name__ == "__main__":
 
    gobject.threads_init() # this is in gtk2reactor too
 
    GObject.threads_init()
 
    Gst.init(None)
 

	
 
    parser = optparse.OptionParser()
 
    parser.add_option('--show',
 
        help='show URI, like http://light9.bigasterisk.com/show/dance2008', default=showconfig.showUri())
 
    parser.add_option("-v", "--verbose", action="store_true",
 
                      help="logging.DEBUG")
 
@@ -52,7 +48,9 @@ if __name__ == "__main__":
 

	
 
    if not options.show:
 
        raise ValueError("missing --show http://...")
 
            
 
    graph = showconfig.getGraph()
 
    app = App(graph, URIRef(options.show))
 
    app.run(networking.musicPlayer.port)
 
    reactor.listenTCP(networking.musicPlayer.port, makeWebApp(app))
 
    log.info("listening on %s" % networking.musicPlayer.port)
 
    reactor.run()
light9/ascoltami/player.py
Show inline comments
 
@@ -2,89 +2,110 @@
 

	
 
"""
 
alternate to the mpd music player, for ascoltami
 
"""
 
from __future__ import division
 
import time, logging, traceback
 
import gst, gobject
 
from gi.repository import GObject, Gst
 
from twisted.internet import reactor, task
 

	
 

	
 
log = logging.getLogger()
 

	
 
class Player(object):
 
    def __init__(self, autoStopOffset=4, onEOS=None):
 
        """autoStopOffset is the number of seconds before the end of
 
        song before automatically stopping (which is really pausing).
 
        onEOS is an optional function to be called when we reach the
 
        end of a stream (for example, can be used to advance the song).
 
        It is called with one argument which is the URI of the song that
 
        just finished."""
 
        self.autoStopOffset = autoStopOffset
 
        self.playbin = self.pipeline = gst.parse_launch("playbin2")
 
        self.playbin = self.pipeline = Gst.ElementFactory.make('playbin',None)
 

	
 
        self.playStartTime = 0
 
        self.lastWatchTime = 0
 
        self.autoStopTime = 0
 
        self.lastSetSongUri = None
 
        self.onEOS = onEOS
 
        
 
        # before playbin2:
 
        #self.pipeline = gst.parse_launch("filesrc name=file location=%s ! wavparse name=src ! audioconvert ! alsasink name=out" % songFile)
 

	
 
        gobject.timeout_add(50, self.watchTime)
 
        task.LoopingCall(self.watchTime).start(.050)
 

	
 
        bus = self.pipeline.get_bus()
 
        bus.add_signal_watch()
 

	
 
        def on_any(bus, msg):
 
            #print bus, msg, msg.type
 
            if msg.type == gst.MESSAGE_EOS:
 
                if self.onEOS is not None:
 
                    self.onEOS(self.getSong())
 
        bus.connect('message', on_any)
 
        # not working- see notes in pollForMessages
 
        #self.watchForMessages(bus)
 
      
 
    def watchTime(self):
 

	
 
        def onStreamStatus(bus, message):
 
            print "streamstatus", bus, message
 
            (statusType, _elem) = message.parse_stream_status()
 
            if statusType == gst.STREAM_STATUS_TYPE_ENTER:
 
                self.setupAutostop()
 
        bus.connect('message::stream-status', onStreamStatus)
 

	
 
    def watchTime(self):
 
        self.pollForMessages()
 
        
 
        try:
 
            try:
 
                t = self.currentTime()
 
            except gst.QueryError:
 
                return True
 
            t = self.currentTime()
 
            log.debug("watch %s < %s < %s",
 
                      self.lastWatchTime, self.autoStopTime, t)
 
            if self.lastWatchTime < self.autoStopTime < t:
 
                log.info("autostop")
 
                self.pause()
 

	
 
            self.lastWatchTime = t
 
        except:
 
            traceback.print_exc()
 
        return True
 

	
 
    def watchForMessages(self, bus):
 
        """this would be nicer than pollForMessages but it's not working for
 
        me. It's like add_signal_watch isn't running."""
 
        bus.add_signal_watch()
 

	
 
        def onEos(*args):
 
            print "onEos", args
 
            if self.onEOS is not None:
 
                self.onEOS(self.getSong())
 
        bus.connect('message::eos', onEos)
 

	
 
        def onStreamStatus(bus, message):
 
            print "streamstatus", bus, message
 
            (statusType, _elem) = message.parse_stream_status()
 
            if statusType == Gst.StreamStatusType.ENTER:
 
                self.setupAutostop()
 
        bus.connect('message::stream-status', onStreamStatus)
 
            
 
    def pollForMessages(self):
 
        """bus.add_signal_watch seems to be having no effect, but this works"""
 
        bus = self.pipeline.get_bus()
 
        mt = Gst.MessageType
 
        msg = bus.poll(mt.EOS | mt.STREAM_STATUS, 0)
 
        if msg is not None:
 
            if msg.type == Gst.MessageType.EOS:
 
                if self.onEOS is not None:
 
                    self.onEOS(self.getSong())
 
            if msg.type == Gst.MessageType.STREAM_STATUS:
 
                (statusType, _elem) = msg.parse_stream_status()
 
                if statusType == Gst.StreamStatusType.ENTER:
 
                    self.setupAutostop()
 
            
 
    def seek(self, t):
 
        assert self.playbin.seek_simple(
 
            gst.FORMAT_TIME,
 
            gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE | gst.SEEK_FLAG_SKIP,
 
            t * gst.SECOND)
 
        isSeekable = self.playbin.seek_simple(
 
            Gst.Format.TIME,
 
            Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE | Gst.SeekFlags.SKIP,
 
            t * Gst.SECOND)
 
        if not isSeekable:
 
            raise ValueError('seek_simple failed')
 
        self.playStartTime = time.time()
 

	
 
    def setSong(self, songLoc, play=True):
 
        """
 
        uri like file:///my/proj/light9/show/dance2010/music/07.wav
 
        """
 
        log.info("set song to %r" % songLoc)
 
        self.pipeline.set_state(gst.STATE_READY)
 
        self.pipeline.set_state(Gst.State.READY)
 
        self.preload(songLoc)
 
        self.pipeline.set_property("uri", songLoc)
 
        self.lastSetSongUri = songLoc
 
        # todo: don't have any error report yet if the uri can't be read
 
        if play:
 
            self.pipeline.set_state(gst.STATE_PLAYING)
 
            self.pipeline.set_state(Gst.State.PLAYING)
 
            self.playStartTime = time.time()
 

	
 
    def getSong(self):
 
        """Returns the URI of the current song."""
 
        # even the 'uri' that I just set isn't readable yet
 
        return self.playbin.get_property("uri") or self.lastSetSongUri
 
@@ -103,50 +124,49 @@ class Player(object):
 
            open(songPath[len("file://"):]).read()
 
        except IOError as e:
 
            log.error("couldn't preload %s, %r", songPath, e)
 
            raise
 

	
 
    def currentTime(self):
 
        try:
 
            cur, _format = self.playbin.query_position(gst.FORMAT_TIME)
 
        except gst.QueryError:
 
        success, cur = self.playbin.query_position(Gst.Format.TIME)
 
        if not success:
 
            return 0
 
        return cur / gst.SECOND
 
        return cur / Gst.SECOND
 

	
 
    def duration(self):
 
        try:
 
            return self.playbin.query_duration(gst.FORMAT_TIME)[0] / gst.SECOND
 
        except gst.QueryError:
 
        success, dur = self.playbin.query_duration(Gst.Format.TIME)
 
        if not success:
 
            return 0
 
        return dur / Gst.SECOND
 

	
 
    def states(self):
 
        """json-friendly object describing the interesting states of
 
        the player nodes"""
 
        success, state, pending = self.playbin.get_state(timeout=0)
 
        return {"current": {"name":state.value_nick},
 
                "pending": {"name":state.value_nick}}
 
        
 
    def pause(self):
 
        self.pipeline.set_state(gst.STATE_PAUSED)
 
        self.pipeline.set_state(Gst.State.PAUSED)
 

	
 
    def isAutostopped(self):
 
        """
 
        are we stopped at the autostop time?
 
        """
 
        pos = self.currentTime()
 
        autoStop = self.duration() - self.autoStopOffset
 
        return not self.isPlaying() and abs(pos - autoStop) < 1 # i've seen .4 difference here
 

	
 
    def resume(self):
 
        self.pipeline.set_state(gst.STATE_PLAYING)
 
        self.pipeline.set_state(Gst.State.PLAYING)
 

	
 
    def setupAutostop(self):
 
        dur = self.duration()
 
        self.autoStopTime = (dur - self.autoStopOffset)
 
        log.info("autostop will be at %s", self.autoStopTime)
 
        # pipeline.seek can take a stop time, but using that wasn't
 
        # working out well. I'd get pauses at other times that were
 
        # hard to remove.
 

	
 
    def isPlaying(self):
 
        _, state, _ = self.pipeline.get_state()
 
        return state == gst.STATE_PLAYING
 
        _, state, _ = self.pipeline.get_state(timeout=0)
 
        return state == Gst.State.PLAYING
makefile
Show inline comments
 
@@ -15,12 +15,13 @@ install_python_deps: link_to_sys_package
 
DP=/usr/lib/python2.7/dist-packages
 
SP=env/lib/python2.7/site-packages
 

	
 
link_to_sys_packages:
 
	# http://stackoverflow.com/questions/249283/virtualenv-on-ubuntu-with-no-site-packages
 
	ln -sf $(DP)/glib $(SP)/
 
	ln -sf $(DP)/gi $(SP)/
 
	ln -sf $(DP)/gobject $(SP)/
 
	ln -sf $(DP)/cairo $(SP)/
 
	ln -sf $(DP)/gtk-2.0 $(SP)/
 
	ln -sf $(DP)/pygtk.py $(SP)/
 
	ln -sf $(DP)/pygtk.pth $(SP)/
 
	ln -sf $(DP)/pygst.pth $(SP)/
0 comments (0 inline, 0 general)