Feature: Play remote audio on iPhone via WebAudio + self-hosted Opus WASM

iOS WebKit lacks Media Source Extensions with WebM/Opus, so remote audio
was silent on iPhone Safari/Chrome even though audio frames kept flowing.
Add a Web Audio API fallback path that decodes Opus packets with the
`opus-decoder` WASM library and schedules them onto an AudioContext.

The decoder bundle is now self-hosted — served at /static/opus-decoder.min.js
by both the Go and C++ servers — instead of pulled from a CDN, which was
unreachable from some networks.

Gotcha fixed during bring-up: the unscoped `opus-decoder` build's
`OpusDecoder.decodeFrame` is synchronous and returns {channelData,
samplesDecoded, sampleRate, errors} directly, not a Promise, so it must be
called in try/catch rather than via .then().

Also remove the on-screen diagnostic overlay (audio toast + frame counter)
used while debugging the phone without a console; diagnostics now go to
console.* only.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxianganddeepseek-v4-pro committed 2026-10-03 18:23:10 +02:00
1 parent d118558eda
commit f3a05c14b1
10 files changed
+655 -13

No files matched your search

+4
View File
@@ -255,6 +255,7 @@ void CWebService::ServerThread(int port) {
static std::string cachedXtermJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_JS);
static std::string cachedXtermCss = LoadBinaryResourceAsString(IDR_WEB_XTERM_CSS);
static std::string cachedXtermFitJs = LoadBinaryResourceAsString(IDR_WEB_XTERM_FIT_JS);
static std::string cachedOpusDecoderJs = LoadBinaryResourceAsString(IDR_WEB_OPUS_DECODER_JS);
auto buildStatic = [](const std::string& body, const std::string& mime) {
ws::HttpResponse r = ws::HttpResponse::OK(body, mime);
@@ -274,6 +275,9 @@ void CWebService::ServerThread(int port) {
} else if (path == "/static/xterm-fit.js") {
if (cachedXtermFitJs.empty()) return ws::HttpResponse::NotFound();
return buildStatic(cachedXtermFitJs, "application/javascript; charset=utf-8");
} else if (path == "/static/opus-decoder.min.js") {
if (cachedOpusDecoderJs.empty()) return ws::HttpResponse::NotFound();
return buildStatic(cachedOpusDecoderJs, "application/javascript; charset=utf-8");
} else if (path == "/health") {
return ws::HttpResponse::OK("{\"status\":\"ok\"}", "application/json");
} else if (path == "/manifest.json") {