Mercurial > code > home > repos > light9
view light9/ascoltami/musictime_client.py @ 2183:081f36506ad3
address a bunch of type errors and loose types
author | drewp@bigasterisk.com |
---|---|
date | Fri, 19 May 2023 17:28:03 -0700 |
parents | 5db8e7698d6a |
children | ccdfdc8183ad |
line wrap: on
line source
import time, json, logging from typing import Dict, cast from twisted.internet.interfaces import IReactorTime 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.positionFetchTime = 0 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) cast(IReactorTime, reactor).callLater(self.period, self.pollMusicTime) # type: ignore def eb(err): log.warn("talking to ascoltami: %s", err.getErrorMessage()) cast(IReactorTime, reactor).callLater(2, self.pollMusicTime) # type: ignore 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"]}, )