webapp: introduce Laravel app under webapp/ for scene overlays

Replaces the per-scene HTML directories (landing/, loading/, game/,
desktop/, goodbye/, music-box/, music/) with a single Laravel app
serving every overlay over HTTP. Supervisord runs `php artisan serve`
on 127.0.0.1:1118 and the OBS scene JSON now references HTTP routes
instead of file:// URLs.

Highlights:
 - public/css/hud.css consolidates the duplicated HUD chrome,
   scanlines/vignette/flicker, terminal styling, and pulse keyframes
   that were copy-pasted across all seven scenes.
 - Blade partials own hud-strip, crt-overlays, obs-ws-scripts,
   camera-frame, screen-frame; the seven scenes extend a shared
   overlay layout.
 - Artisan commands (`rig:setup`, `rig:telemetry`, `rig:loading`,
   `rig:playlist`, `rig:cmd`) replace the shell scripts that wrote
   per-rig JSON snapshots. TwitchHelix + HardwareSnapshot services
   handle the work the bash + Python helpers used to.
 - ObsWsClient + MusicCommandController kill the 250 ms cmd.js poll
   in the music daemon: POST /cmd/{skip|prev|pause|resume} opens a
   short-lived Pawl WS, authenticates, and broadcasts mpd:cmd.
 - AudioController streams files from the configured music dirs so
   CEF can load tracks under the HTTP origin (Chromium blocks
   HTTP-origin pages from loading file:// media).
 - DataController serves /data/playlist.js (with ETag mtime cache)
   and /cover.jpg (no-store) so the existing overlays' window.__PLAYLIST
   and cover.jpg cache-bust pattern keeps working.

scripts/obs-webapp.supervisord.conf is the supervisord unit; install
to /etc/supervisor.d/obs-webapp.conf.
scripts/rewrite-scene-urls.py is a one-shot tool that rewrites
basic/scenes/Default_Stream_HUD.json from file:// to HTTP URLs.
basic/scenes/Default_Stream_HUD.json.pre-webapp is the rollback
artifact (made with OBS closed; full pre-migration state).

The seven old scene directories, vendor/, and the bash scripts are
still on disk pending visual verification; the next commit will
prune them.
This commit is contained in:
Jakub Zych
2026-05-21 12:47:10 +02:00
parent c8f9c11c93
commit 059f069ef4
85 changed files with 19180 additions and 49 deletions

View File

@@ -0,0 +1,31 @@
; supervisord program file for the OPHI-118 scene overlay webapp.
;
; Listens on 127.0.0.1:1118 (CH 118.0 in the overlay lore, easy to remember).
;
; Install:
; sudo install -m 644 scripts/obs-webapp.supervisord.conf /etc/supervisor.d/obs-webapp.conf
; sudo supervisorctl reread && sudo supervisorctl update
;
; Restart after config / route / env changes:
; sudo supervisorctl restart obs-webapp
;
; Logs:
; tail -F webapp/storage/logs/supervisord.{out,err}.log
[program:obs-webapp]
command=/usr/bin/php artisan serve --host=127.0.0.1 --port=1118
directory=/home/jin/.config/obs-studio/webapp
user=jin
environment=HOME="/home/jin",USER="jin",PATH="/usr/local/sbin:/usr/local/bin:/usr/bin"
autostart=true
autorestart=true
startsecs=2
startretries=3
stopwaitsecs=10
stopsignal=TERM
stdout_logfile=/home/jin/.config/obs-studio/webapp/storage/logs/supervisord.out.log
stderr_logfile=/home/jin/.config/obs-studio/webapp/storage/logs/supervisord.err.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=3

82
scripts/rewrite-scene-urls.py Executable file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Rewrite OBS browser_source URLs in a scene-collection JSON from `file://` to
`http://127.0.0.1:1118/`.
Usage:
python3 scripts/rewrite-scene-urls.py basic/scenes/Default_Stream_HUD.json
Idempotent: runs against a partially-converted file are no-ops.
Also moves the landing/static-hum.wav ffmpeg_source path to its new home under
webapp/public/audio/.
OBS MUST be closed when this runs — OBS rewrites scene files on exit.
"""
import json
import re
import sys
BROWSER_MAPPING = {
'landing/index.html': 'http://127.0.0.1:1118/landing',
'loading/index.html': 'http://127.0.0.1:1118/loading',
'game/index.html': 'http://127.0.0.1:1118/game',
'desktop/index.html': 'http://127.0.0.1:1118/desktop',
'goodbye/index.html': 'http://127.0.0.1:1118/goodbye',
'music/index.html': 'http://127.0.0.1:1118/music-daemon',
'music-box/index.html': 'http://127.0.0.1:1118/music-box',
'music-box/widget.html': 'http://127.0.0.1:1118/music-box/widget',
'music-box/cover.html': 'http://127.0.0.1:1118/music-box/cover',
'music-box/nc-music-box.html': 'http://127.0.0.1:1118/music-box/nc',
}
AUDIO_OLD = 'landing/static-hum.wav'
AUDIO_NEW = 'webapp/public/audio/static-hum.wav'
def rewrite_browser_source(src):
s = src.get('settings', {})
for old, new in BROWSER_MAPPING.items():
for k in ('url', 'local_file'):
v = s.get(k, '')
if old in v:
# Preserve query strings (e.g. ?bars=0).
m = re.search(r'\?[^"\s]*$', v)
qs = m.group(0) if m else ''
s['url'] = new + qs
s.pop('local_file', None)
s['is_local_file'] = False
return True
return False
def rewrite_audio_source(src):
s = src.get('settings', {})
lf = s.get('local_file', '')
if lf.endswith(AUDIO_OLD):
s['local_file'] = lf.replace(AUDIO_OLD, AUDIO_NEW)
return True
return False
def main():
if len(sys.argv) != 2:
print(__doc__, file=sys.stderr)
return 2
path = sys.argv[1]
doc = json.load(open(path))
rewrites = 0
for src in doc.get('sources', []):
sid = src.get('id', '')
if sid == 'browser_source':
if rewrite_browser_source(src):
rewrites += 1
elif sid == 'ffmpeg_source':
if rewrite_audio_source(src):
rewrites += 1
json.dump(doc, open(path, 'w'), indent=4, ensure_ascii=False)
print(f'wrote {path} ({rewrites} sources rewritten)')
if __name__ == '__main__':
sys.exit(main() or 0)