Changeset - 8d87a3528369
[Not reviewed]
default
0 4 1
drewp@bigasterisk.com - 13 years ago 2012-06-18 01:13:18
drewp@bigasterisk.com
go button support
Ignore-this: e32b66cb39bf05a2fec5c91fdb7a7093
5 files changed with 48 insertions and 27 deletions:
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
#!bin/python
 
import web, thread, gobject, 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
 
from light9.ascoltami.webapp import makeWebApp, songUri, songLocation
 
from light9 import networking, showconfig
 

	
 

	
 
import BaseHTTPServer
 
BaseHTTPServer.BaseHTTPRequestHandler.log_message = \
 
                     lambda self, format, *args: None
 

	
 
class App:
 
    def __init__(self, graph, show):
 
        self.graph = graph
 
        self.player = Player(onEOS=self.onEOS)
 
        self.show = show
 
@@ -24,32 +24,32 @@ class App:
 
        # one seems to run
 
        thread.start_new(web.httpserver.runbasic,
 
                         (makeWebApp(self).wsgifunc(),
 
                          ('0.0.0.0', musicPort)))
 

	
 
        mainloop = gobject.MainLoop()
 
        mainloop.run()
 

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

	
 
        # stop here for now- no go-button behavior
 
        return
 
        thisSongUri = songUri(graph, URIRef(song))
 

	
 
        try:
 
            nextSong = self.playlist.nextSong(song)
 
            nextSong = self.playlist.nextSong(thisSongUri)
 
        except NoSuchSong: # we're at the end of the playlist
 
            return
 

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

	
 
if __name__ == "__main__":
 
    logging.basicConfig()
 
    log = logging.getLogger()
 
    gobject.threads_init()
 

	
 
    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")
 
    (options, args) = parser.parse_args()
bin/gobutton
Show inline comments
 
new file 100644
 
#!/bin/sh
 
# uri should be set from $LIGHT9_SHOW/config.n3
 
exec curl --silent -d '' http://localhost:8040/go
light9/ascoltami/index.html
Show inline comments
 
@@ -110,25 +110,25 @@
 
	return true;
 
    });
 

	
 
    $("#cmd-stop").click(function () { $.post("time", tojs({pause: true})); });
 
    $("#cmd-play").click(function () { $.post("time", tojs({resume: true})); });
 
    $("#cmd-intro").click(function () { 
 
	$.post("time", tojs({t: times.intro, resume: true}))
 
    });
 
    $("#cmd-post").click(function () { 
 
	$.post("time", tojs({t: currentDuration - times.post, resume: true}))
 
    });
 
    $("#cmd-go").click(function () {
 
	// todo
 
	$.post("go");
 
    });
 
    $("#cmd-out0").click(function () { $.post("output", tojs({sink: "0"})); })
 
    $("#cmd-out1").click(function () { $.post("output", tojs({sink: "1"})); })
 

	
 
    var pendingSlide = false;
 
    $("#timeSlider").slider({
 
	step: .01,
 
	slide: function (event, ui) {
 
	    if (pendingSlide) {
 
		return;
 
	    }
 
	    pendingSlide = true;
 
@@ -150,13 +150,13 @@
 
	    }, 50);
 
	};
 
	updateCurrent(whenDone);
 
    }
 
    updateLoop();
 

	
 
});
 
// ]]>
 
</script>
 

	
 

	
 
  </body>
 
</html>
 
\ No newline at end of file
 
</html>
light9/ascoltami/player.py
Show inline comments
 
@@ -12,24 +12,25 @@ 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.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)
 

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

	
 
        def on_any(bus, msg):
 
            #print bus, msg, msg.type
 
@@ -48,60 +49,54 @@ class Player(object):
 
    def watchTime(self):
 
        try:
 
            try:
 
                t = self.currentTime()
 
            except gst.QueryError:
 
                return True
 
            log.debug("watch %s < %s < %s",
 
                      self.lastWatchTime, self.autoStopTime, t)
 
            if self.lastWatchTime < self.autoStopTime < t:
 
                log.info("autostop")
 
                self.pause()
 

	
 
                # new EOS logic above should be better
 
            ## if not self.onEOS:
 
            ##     if self.isPlaying() and t >= self.duration() - .2:
 
            ##         # i don't expect to hit dur exactly with this
 
            ##         # polling. What would be better would be to watch for
 
            ##         # the EOS signal and react to that
 
            ##         self.onEOS(self.getSong())
 

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

	
 
    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)
 
        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.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.playStartTime = time.time()
 

	
 
    def getSong(self):
 
        """Returns the URI of the current song."""
 
        return self.playbin.get_property("uri")
 
        # even the 'uri' that I just set isn't readable yet
 
        return self.playbin.get_property("uri") or self.lastSetSongUri
 

	
 
    def preload(self, songPath):
 
        """
 
        to avoid disk seek stutters, which happened sometimes (in 2007) with the
 
        non-gst version of this program, we read the whole file to get
 
        more OS caching.
 

	
 
        i don't care that it's blocking.
 
        """
 
        log.info("preloading %s", songPath)
 
        assert songPath.startswith("file://"), songPath
 
        open(songPath[len("file://"):]).read()
 
@@ -120,30 +115,34 @@ class Player(object):
 
            return 0
 

	
 
    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)
 

	
 
    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)
 
        pos = self.currentTime()
 
        autoStop = self.duration() - self.autoStopOffset
 
        if abs(pos - autoStop) < .01:
 
            self.releaseAutostop()
 

	
 
    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
light9/ascoltami/webapp.py
Show inline comments
 
@@ -15,37 +15,40 @@ def songLocation(graph, songUri):
 
    return loc
 
    
 
def songUri(graph, locationUri):
 
    return _songUris[locationUri]
 

	
 
class root(object):
 
    def GET(self):
 
        web.header("Content-type", "application/xhtml+xml")
 
        # todo: use a template; embed the show name and the intro/post
 
        # times into the page
 
        return render.index(host=socket.gethostname())
 

	
 
def playerSongUri(graph, player):
 
    """or None"""
 
    
 
    playingLocation = player.getSong()
 
    if playingLocation:
 
        return songUri(graph, URIRef(playingLocation))
 
    else:
 
        return None
 

	
 
class timeResource(object):
 
    def GET(self):
 
        player = app.player
 
        graph = app.graph
 

	
 
        playingLocation = player.getSong()
 
        if playingLocation:
 
            song = songUri(graph, URIRef(playingLocation))
 
        else:
 
            song = None
 
        web.header("content-type", "application/json")
 
        return json.dumps({
 
            "song" : song,
 
            "song" : playerSongUri(graph, player),
 
            "started" : player.playStartTime,
 
            "duration" : player.duration(),
 
            "playing" : player.isPlaying(),
 
            "t" : player.currentTime(),
 
            "state" : player.states(),
 
            })
 

	
 
    def POST(self):
 
        """
 
        post a json object with {pause: true} or {resume: true} if you
 
        want those actions. Use {t: <seconds>} to seek, optionally
 
        with a pause/resume command too.
 
@@ -89,25 +92,41 @@ class seekPlayOrPause(object):
 
        data = json.loads(web.data())
 
        if player.isPlaying():
 
            player.pause()
 
        else:
 
            player.seek(data['t'])
 
            player.resume()
 

	
 
class output(object):
 
    def POST(self):
 
        d = json.loads(web.data())
 
        subprocess.check_call(["bin/movesinks", str(d['sink'])])
 

	
 
class goButton(object):
 
    def POST(self):
 
        """
 
        if music is playing, this silently does nothing.
 
        """
 
        graph, player = app.graph, app.player
 

	
 
        if player.isPlaying():
 
            pass
 
        else:
 
            player.resume()
 
            
 
        web.header("content-type", "text/plain")
 
        return "ok"
 

	
 
def makeWebApp(theApp):
 
    global app
 
    app = theApp
 

	
 
    urls = (r"/", "root",
 
            r"/time", "timeResource",
 
            r"/song", "songResource",
 
            r"/songs", "songs",
 
            r"/seekPlayOrPause", "seekPlayOrPause",
 
            r"/output", "output",
 
            r"/go", "goButton",
 
            )
 

	
 
    return web.application(urls, globals(), autoreload=False)
0 comments (0 inline, 0 general)