From dda425f7c6a9523b2f5a89b3a94c6a65141213bc Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sun, 20 Sep 2026 13:28:17 -0700 Subject: [PATCH] Keep native demo playing through Galaxy status timeouts --- roadscore/MAC_SHOWCASE.md | 2 +- roadscore/prototype/paired_showcase.py | 63 +++++++++++++- .../prototype/test_paired_audio_first.py | 83 +++++++++++++++++-- 3 files changed, 136 insertions(+), 12 deletions(-) diff --git a/roadscore/MAC_SHOWCASE.md b/roadscore/MAC_SHOWCASE.md index b8f70fc42b..afdd147374 100644 --- a/roadscore/MAC_SHOWCASE.md +++ b/roadscore/MAC_SHOWCASE.md @@ -48,7 +48,7 @@ Compatible archives contain `dry.wav`, `launch.json`, `audio_blocks.jsonl`, `rep Both saved launchers support `--muted`, `--duration SECONDS` and `--no-browser`. Paired `--check` contacts Galaxy to verify the target is offroad; independent `--check` validates local prerequisites only. The controls server binds to loopback and refuses an occupied port. The paired target must be an explicit private IPv4 address on Galaxy port 8082. -The independent mode can follow Galaxy controls with `--paired-comma URL`, but controls alone do not synchronize playback. The paired `--demo` launcher additionally holds both starts and enables playhead following. The Mac follows the comma; it never adjusts the comma's audio clock to match its screen. If the Mac replay fails after playback begins, the launcher records a degraded display and lets healthy comma audio finish. Startup failure, native failure, ownership changes and Ctrl+C still stop the owned pair. `--demo --screen-mirror` explicitly enables JPEG capture for the earlier browser mirror. Normal paired playback does not capture or encode a video feed. +The independent mode can follow Galaxy controls with `--paired-comma URL`, but controls alone do not synchronize playback. The paired `--demo` launcher additionally holds both starts and enables playhead following. The Mac follows the comma; it never adjusts the comma's audio clock to match its screen. If the Mac replay fails after playback begins, the launcher records a degraded display and lets healthy comma audio finish. Temporary Galaxy status failures during playback retry without restarting either replay. After four consecutive failures, the Mac closes and leaves comma playback running to its local end; the terminal reports that its status is unknown. Reconnect to Galaxy to check or stop it. Retry and recovery evidence is saved in `peer_monitor.json`. Startup failure, verified native failure, ownership changes and Ctrl+C still attempt to stop the owned pair. `--demo --screen-mirror` explicitly enables JPEG capture for the earlier browser mirror. Normal paired playback does not capture or encode a video feed. Prepared native Bluetooth playback requests250ms of output buffering while retaining20ms callbacks. The backend may round this request; both requested and actual latency are recorded in `prepared_summary.json`, along with callback gaps, processing times and underflow flags. DAC timestamps continue to drive presentation timing, so no second manual delay is added for this queue. Mac, system-speaker and calibration defaults are unchanged. diff --git a/roadscore/prototype/paired_showcase.py b/roadscore/prototype/paired_showcase.py index 336d91f789..fdecf73cda 100644 --- a/roadscore/prototype/paired_showcase.py +++ b/roadscore/prototype/paired_showcase.py @@ -11,6 +11,8 @@ import threading import time import uuid from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from http.client import IncompleteRead +from urllib.error import HTTPError, URLError from urllib.parse import urlencode from urllib.request import Request, build_opener, ProxyHandler @@ -195,6 +197,48 @@ def stop_mac(process): process.wait(timeout=5) +def retryable_status_error(error): + if isinstance(error, HTTPError): + return error.code in (408, 429, 500, 502, 503, 504) + return isinstance(error, (URLError, TimeoutError, ConnectionError, IncompleteRead)) + + +class PlaybackMonitor: + """Retry only status reads, without restarting or stopping native playback.""" + def __init__(self, peer, out, request_id): + self.peer, self.out, self.request_id = peer, out, request_id + self.failures = self.total_failures = 0 + self.handoff = False + + def record(self, state, error=None): + report = dict(state=state,request_id=self.request_id,wall=time.monotonic(), + consecutive_failures=self.failures,total_failures=self.total_failures, + error=repr(error) if error else None,native_stop_requested=False) + try:(self.out/'peer_monitor.json').write_text(json.dumps(report,indent=2)) + except OSError as error:print('Could not record connection status: '+str(error),flush=True) + + def read(self): + try:return self.peer.call('demo_ready',{}) + except Exception as error: + if not retryable_status_error(error):raise + self.failures += 1;self.total_failures += 1 + self.handoff = self.failures >= 4 + self.record('UNREACHABLE' if self.handoff else 'RETRYING',error) + if self.failures == 1: + print('Galaxy status unavailable; leaving playback running and retrying.',flush=True) + if self.handoff: + print('Galaxy monitoring lost after four attempts. Comma playback has NOT been stopped; ' + 'it can finish locally. Reconnect to Galaxy to check or stop it.',flush=True) + else:time.sleep((.5,1.,2.)[self.failures-1]) + return None + + def recovered(self): + if self.failures: + self.failures = 0 + self.record('CONNECTED') + print('Galaxy monitoring restored; playback was not restarted.',flush=True) + + def native_pair_main(): parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('--roadscore',action='store_true');parser.add_argument('--demo',action='store_true') @@ -225,7 +269,7 @@ def native_pair_main(): command=mac_command(project,args.route,selected,peer.peer.base_url,mac_out,args.duration,fullscreen=args.fullscreen) (out/'launch.json').write_text(json.dumps(dict(request_id=request_id,route=selected['route'], command=command,archive=selected['archive'],generation_invoked=False,screen_mirror=False,muted=args.muted),indent=2)) - process=None;launched=False;began=time.monotonic() + process=None;launched=False;leave_native_running=False;began=time.monotonic() def interrupt(*_):raise KeyboardInterrupt signal.signal(signal.SIGTERM,interrupt) try: @@ -261,10 +305,17 @@ def native_pair_main(): print('Session evidence: '+str(out),flush=True) started=time.monotonic() mac_finished=False + monitor=PlaybackMonitor(peer,out,request_id) while time.monotonic()-started