Files
@ 7e66643b3f54
Branch filter:
Location: light9/light9/vidref/musictime.py
7e66643b3f54
4.5 KiB
text/x-python
quieter graph logs
Ignore-this: f24071530e1ecc5c2b7a7de184f438ac
Ignore-this: f24071530e1ecc5c2b7a7de184f438ac
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | import restkit, time, json, logging
from light9 import networking
from twisted.internet import reactor
from cyclone.httpclient import fetch
from restkit.errors import ResourceNotFound
import http_parser.http
log = logging.getLogger()
class MusicTime(object):
"""
fetch times from ascoltami in a background thread; return times
upon request, adjusted to be more precise with the system clock
"""
def __init__(self, period=.2, onChange=lambda position: None, pollCurvecalc=True):
"""period is the seconds between http time requests.
We call onChange with the time in seconds and the total time
The choice of period doesn't need to be tied to framerate,
it's more the size of the error you can tolerate (since we
make up times between the samples, and we'll just run off the
end of a song)
"""
self.period = period
self.hoverPeriod = .05
self.onChange = onChange
self.musicResource = restkit.Resource(networking.musicPlayer.url)
self.position = {}
# driven by our pollCurvecalcTime and also by Gui.incomingTime
self.lastHoverTime = None # None means "no recent value"
self.pollMusicTime()
if pollCurvecalc:
self.pollCurvecalcTime()
def getLatest(self, frameTime=None):
"""
dict with 't' and 'song', etc.
frameTime is the timestamp from the camera, which will be used
instead of now.
Note that this may be called in a gst camera capture thread. Very often.
"""
if not hasattr(self, 'position'):
return {'t' : 0, 'song' : None}
pos = self.position.copy()
now = frameTime or time.time()
if pos.get('playing'):
pos['t'] = pos['t'] + (now - self.positionFetchTime)
else:
if self.lastHoverTime is not None:
pos['hoverTime'] = self.lastHoverTime
return pos
def pollMusicTime(self):
def cb(response):
if response.code != 200:
raise ValueError("%s %s", response.code, response.body)
position = json.loads(response.body)
# this is meant to be the time when the server gave me its
# report, and I don't know if that's closer to the
# beginning of my request or the end of it (or some
# fraction of the way through)
self.positionFetchTime = time.time()
self.position = position
self.onChange(position)
reactor.callLater(self.period, self.pollMusicTime)
def eb(err):
log.warn("talking to ascoltami: %s", err.getErrorMessage())
reactor.callLater(2, self.pollMusicTime)
d = fetch(networking.musicPlayer.path("time"))
d.addCallback(cb)
d.addErrback(eb) # note this includes errors in cb()
def pollCurvecalcTime(self):
"""
poll the curvecalc position when music isn't playing, so replay
can track it.
This would be better done via rdfdb sync, where changes to the
curvecalc position are written to the curvecalc session and we
can pick them up in here
"""
if self.position.get('playing'):
# don't need this position during playback
self.lastHoverTime = None
reactor.callLater(.2, self.pollCurvecalcTime)
return
def cb(response):
if response.code == 404:
# not hovering
self.lastHoverTime = None
reactor.callLater(.2, self.pollCurvecalcTime)
return
if response.code != 200:
raise ValueError("%s %s" % (response.code, response.body))
self.lastHoverTime = json.loads(response.body)['hoverTime']
reactor.callLater(self.hoverPeriod, self.pollCurvecalcTime)
def eb(err):
if self.lastHoverTime:
log.warn("talking to curveCalc: %s", err.getErrorMessage())
self.lastHoverTime = None
reactor.callLater(2, self.pollCurvecalcTime)
d = fetch(networking.curveCalc.path("hoverTime"))
d.addCallback(cb)
d.addErrback(eb) # note this includes errors in cb()
def sendTime(self, t):
"""request that the player go to this time"""
self.musicResource.post("time", payload=json.dumps({"t" : t}),
headers={"content-type" : "application/json"})
|