import time, json, logging
from typing import Dict
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks
import treq
from light9 import networking
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='ignored'):
"""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.position: Dict[str, float] = {}
# driven by our pollCurvecalcTime and also by Gui.incomingTime
self.lastHoverTime = None # None means "no recent value"
self.pollMusicTime()
def getLatest(self, frameTime=None) -> Dict:
"""
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):
@inlineCallbacks
def cb(response):
if response.code != 200:
raise ValueError("%s %s", response.code,
(yield response.content()))
position = yield response.json()
# 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 = treq.get(networking.musicPlayer.path("time").toPython())
d.addCallback(cb)
d.addErrback(eb) # note this includes errors in cb()
def sendTime(self, t):
"""request that the player go to this time"""
treq.post(
networking.musicPlayer.path('time'),
data=json.dumps({
"t": time
}).encode('utf8'),
headers={b"content-type": [b"application/json"]},
)