diff --git a/android/app/src/main/assets/public/voice.js b/android/app/src/main/assets/public/voice.js index 3e68b022..837776ea 100644 --- a/android/app/src/main/assets/public/voice.js +++ b/android/app/src/main/assets/public/voice.js @@ -24,10 +24,6 @@ let voiceLoopRestarts = 0; let voiceDeviceChangeHandler = null; let voiceDeviceChangeTimer = null; let isVoiceSpeakerMode = false; -let voiceTapNode = null; -let pcmBatch = null; -let pcmBatchLen = 0; -let voicePcmFlushTimer = null; const SPEAKING_HANGOVER_MS = 400; @@ -123,37 +119,15 @@ function int16ToBase64(view) { return btoa(bin); } -function flushPcmBatch() { - if (!pcmBatchLen || !pcmBatch) return; - const native = getNativeVoiceRouting(); - if (native && native.nativeAudioFeed && native.nativeAudioStart) { - const slice = new Int16Array(pcmBatch.subarray(0, pcmBatchLen)); - try { - native.nativeAudioFeed({ data: int16ToBase64(slice) }).catch(() => {}); - } catch (e) {} - } - pcmBatchLen = 0; -} - function handlePcmChunk(ev) { + const native = getNativeVoiceRouting(); + if (!native || !native.nativeAudioFeed || !ev.data) return; try { - if (!getNativeVoiceRouting() || !pcmBatch) return; - const chunk = new Int16Array(ev.data); - for (let i = 0; i < chunk.length; i++) { - if (pcmBatchLen >= pcmBatch.length) flushPcmBatch(); - pcmBatch[pcmBatchLen++] = chunk[i]; - } + native.nativeAudioFeed({ data: int16ToBase64(new Int16Array(ev.data)) }).catch(() => {}); } catch (e) {} } function stopNativePlayback() { - if (voicePcmFlushTimer) { clearInterval(voicePcmFlushTimer); voicePcmFlushTimer = null; } - flushPcmBatch(); - if (voiceTapNode) { - try { voiceTapNode.disconnect(); } catch (e) {} - try { voiceTapNode.port.onmessage = null; } catch (e) {} - voiceTapNode = null; - } const native = getNativeVoiceRouting(); if (native && native.nativeAudioStop) native.nativeAudioStop().catch(() => {}); } @@ -226,15 +200,10 @@ function setupVoiceOutput() { // App + native plugin: the mixed call audio is played by a native AudioTrack // (USAGE_VOICE_COMMUNICATION), so it follows the call volume slider and the // earpiece/speaker switch. Nothing is output through the WebAudio graph. + // The receiver worklet forwards mixed PCM straight here (~20 ms packets). if (native && isMobileVoiceDevice()) { stopNativePlayback(); - pcmBatch = new Int16Array(1536); // 32 ms @ 48 kHz - pcmBatchLen = 0; - voiceTapNode = new AudioWorkletNode(audioContext, 'audio-tap'); - voiceTapNode.port.onmessage = handlePcmChunk; - receiverNode.connect(voiceTapNode); - if (voicePcmFlushTimer) clearInterval(voicePcmFlushTimer); - voicePcmFlushTimer = setInterval(flushPcmBatch, 25); + receiverNode.port.onmessage = handlePcmChunk; if (native.nativeAudioStart) native.nativeAudioStart({ sampleRate: audioContext.sampleRate }).catch(() => {}); return; } @@ -435,18 +404,28 @@ class AudioProcessor extends AudioWorkletProcessor { } } class AudioReceiver extends AudioWorkletProcessor { - constructor() { - super(); + // Jitter buffer targets in samples (@48 kHz): + // PREBUF_START - initial prebuffer before first playback (~43 ms) + // PREBUF_RESUME - re-prebuffer after a starvation event (~32 ms) + // HARD_LIMIT - burst ceiling (~128 ms): snap back to target immediately + // Backlog slightly above target is drained by a gentle trim (1 sample per + // quantum) instead of letting latency creep up to the hard limit. + constructor(options) { + super(); this.users = {}; - this.volumes = {}; - + this.volumes = {}; + const opts = (options && options.processorOptions) || {}; + this.forward = !!opts.forward; + this.acc = new Int16Array(960); // ~20 ms @ 48 kHz + this.accLen = 0; + this.port.onmessage = (e) => { if (e.data.action === 'clear') { this.users = {}; this.volumes = {}; return; } if (e.data.action === 'setVolume') { this.volumes[e.data.clientId] = e.data.volume; return; } if (e.data.action === 'remove') { delete this.users[e.data.clientId]; return; } - + const { clientId, audioData } = e.data; - if (!this.users[clientId]) this.users[clientId] = { buffer: new Int16Array(65536), readPtr: 0, writePtr: 0, buffering: true }; + if (!this.users[clientId]) this.users[clientId] = { buffer: new Int16Array(65536), readPtr: 0, writePtr: 0, buffering: true, started: false }; const user = this.users[clientId]; const intData = new Int16Array(audioData); for (let i = 0; i < intData.length; i++) { @@ -462,12 +441,13 @@ class AudioReceiver extends AudioWorkletProcessor { for (const id in this.users) { const user = this.users[id]; let available = (user.writePtr - user.readPtr + 65536) % 65536; - if (user.buffering && available >= 4096) user.buffering = false; - + if (user.buffering && available >= (user.started ? 1536 : 2048)) { user.buffering = false; user.started = true; } + if (!user.buffering) { if (available < output.length) { user.buffering = true; } else { - if (available > 24576) user.readPtr = (user.writePtr - 8192 + 65536) % 65536; + if (available > 6144) user.readPtr = (user.writePtr - 2048 + 65536) % 65536; + else if (available > 2049) user.readPtr = (user.readPtr + 1) % 65536; let vol = this.volumes[id] !== undefined ? this.volumes[id] : 1.0; for (let i = 0; i < output.length; i++) { output[i] += (user.buffer[user.readPtr] / 32768) * vol; @@ -475,28 +455,21 @@ class AudioReceiver extends AudioWorkletProcessor { } } } - } return true; - } -} -class AudioTap extends AudioWorkletProcessor { - constructor() { super(); this.acc = new Int16Array(512); this.accLen = 0; } - process(inputs) { - const ch = inputs[0] && inputs[0][0]; - if (ch && this.port) { - for (let i = 0; i < ch.length; i++) { - const s = Math.max(-1, Math.min(1, ch[i])); + } + if (this.forward && this.port) { + for (let i = 0; i < output.length; i++) { + const s = Math.max(-1, Math.min(1, output[i])); this.acc[this.accLen++] = s < 0 ? s * 0x8000 : s * 0x7FFF; - } - if (this.accLen >= this.acc.length) { - this.port.postMessage(this.acc.buffer, [this.acc.buffer]); - this.acc = new Int16Array(512); - this.accLen = 0; + if (this.accLen >= this.acc.length) { + this.port.postMessage(this.acc.buffer, [this.acc.buffer]); + this.acc = new Int16Array(960); + this.accLen = 0; + } } } return true; } } -registerProcessor('audio-tap', AudioTap); registerProcessor('audio-sender', AudioProcessor); registerProcessor('audio-receiver', AudioReceiver);`; @@ -599,7 +572,8 @@ async function startVoiceSession(serverUrl) { if (!isAudioWorkletLoaded) { await audioContext.audioWorklet.addModule(URL.createObjectURL(new Blob([voiceWorkletCode], {type: 'application/javascript'}))); senderNode = new AudioWorkletNode(audioContext, 'audio-sender'); - receiverNode = new AudioWorkletNode(audioContext, 'audio-receiver'); + const forwardPcm = !!(getNativeVoiceRouting() && isMobileVoiceDevice()); + receiverNode = new AudioWorkletNode(audioContext, 'audio-receiver', {processorOptions: {forward: forwardPcm}}); screenReceiverNode = new AudioWorkletNode(audioContext, 'audio-receiver'); isAudioWorkletLoaded = true; } @@ -1499,8 +1473,6 @@ function endVoiceSession() { isVoiceSpeakerMode = false; voiceLoopRestarts = 0; stopNativePlayback(); - pcmBatch = null; - pcmBatchLen = 0; if (currentVoiceCallId) { let payload = JSON.stringify({ string1: currentVoiceCallType, string2: currentVoiceCallId }); diff --git a/android/app/src/main/java/olcxja/miarven/VoiceRoutingPlugin.java b/android/app/src/main/java/olcxja/miarven/VoiceRoutingPlugin.java index 199ae8a8..23542060 100644 --- a/android/app/src/main/java/olcxja/miarven/VoiceRoutingPlugin.java +++ b/android/app/src/main/java/olcxja/miarven/VoiceRoutingPlugin.java @@ -47,8 +47,16 @@ public class VoiceRoutingPlugin extends Plugin { private AudioTrack nativeTrack = null; private Thread nativeFeedThread = null; - private final LinkedBlockingQueue pcmQueue = new LinkedBlockingQueue<>(); + private final LinkedBlockingQueue pcmQueue = new LinkedBlockingQueue<>(); private volatile boolean nativePlaying = false; + private double pipeDelayEmaMs = -1; + private long lastPipeLogNanos = 0; + + private static final class PcmChunk { + final byte[] data; + final long arrivalNanos; + PcmChunk(byte[] data) { this.data = data; this.arrivalNanos = System.nanoTime(); } + } private final AudioManager.OnAudioFocusChangeListener focusChangeListener = fi -> {}; @@ -266,6 +274,8 @@ public class VoiceRoutingPlugin extends Plugin { nativeTrack = builder.build(); pcmQueue.clear(); + pipeDelayEmaMs = -1; + lastPipeLogNanos = 0; nativePlaying = true; try { nativeTrack.play(); @@ -278,7 +288,7 @@ public class VoiceRoutingPlugin extends Plugin { nativeFeedThread = new Thread(() -> { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); while (nativePlaying) { - byte[] chunk; + PcmChunk chunk; try { chunk = pcmQueue.poll(100, TimeUnit.MILLISECONDS); } catch (InterruptedException ie) { @@ -287,7 +297,22 @@ public class VoiceRoutingPlugin extends Plugin { if (chunk == null) continue; AudioTrack track = nativeTrack; if (track == null || !nativePlaying) break; - int written = track.write(chunk, 0, chunk.length, AudioTrack.WRITE_BLOCKING); + double delayMs = (System.nanoTime() - chunk.arrivalNanos) / 1e6; + pipeDelayEmaMs = pipeDelayEmaMs < 0 ? delayMs : (pipeDelayEmaMs * 0.9 + delayMs * 0.1); + long now = System.nanoTime(); + if (lastPipeLogNanos == 0) lastPipeLogNanos = now; + if (now - lastPipeLogNanos >= 1_000_000_000L) { + lastPipeLogNanos = now; + int underruns = 0; + try { + underruns = track.getUnderrunCount(); + } catch (Exception ignored) {} + log(String.format(java.util.Locale.US, + "pipe: queue=%d emaDelay=%.1f ms buffer=%.0f ms underruns=%d", + pcmQueue.size(), pipeDelayEmaMs, + track.getBufferSizeInFrames() * 1000.0 / track.getSampleRate(), underruns)); + } + int written = track.write(chunk.data, 0, chunk.data.length, AudioTrack.WRITE_BLOCKING); if (written < 0) { Log.w(TAG, "AudioTrack.write error: " + written); break; @@ -320,7 +345,7 @@ public class VoiceRoutingPlugin extends Plugin { while (pcmQueue.size() > 10) { pcmQueue.poll(); // drop oldest to avoid unbounded growth } - pcmQueue.offer(pcm); + pcmQueue.offer(new PcmChunk(pcm)); call.resolve(); } diff --git a/electron/assets/icon.png b/electron/assets/icon.png index a63fdef2..b4b9f367 100644 Binary files a/electron/assets/icon.png and b/electron/assets/icon.png differ diff --git a/icons/icon.png b/icons/icon.png index a63fdef2..b4b9f367 100644 Binary files a/icons/icon.png and b/icons/icon.png differ diff --git a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/Bridge.dex b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/Bridge.dex index 91f146a7..306cc218 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/Bridge.dex and b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/Bridge.dex differ diff --git a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/BridgeWebChromeClient.dex b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/BridgeWebChromeClient.dex index 1da30ac8..2e49c819 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/BridgeWebChromeClient.dex and b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/BridgeWebChromeClient.dex differ diff --git a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/MessageHandler.dex b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/MessageHandler.dex index cc65ba99..2998eaa6 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/MessageHandler.dex and b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/MessageHandler.dex differ diff --git a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/WebViewLocalServer$1.dex b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/WebViewLocalServer$1.dex index 77e2f54b..cecd34b5 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/WebViewLocalServer$1.dex and b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/WebViewLocalServer$1.dex differ diff --git a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/cordova/MockCordovaWebViewImpl.dex b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/cordova/MockCordovaWebViewImpl.dex index 716acfed..1c8a122b 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/cordova/MockCordovaWebViewImpl.dex and b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/cordova/MockCordovaWebViewImpl.dex differ diff --git a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/plugin/CapacitorHttp$1.dex b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/plugin/CapacitorHttp$1.dex index 6b0b8f3f..7c2ae85f 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/plugin/CapacitorHttp$1.dex and b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/plugin/CapacitorHttp$1.dex differ diff --git a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/plugin/SystemBars.dex b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/plugin/SystemBars.dex index a5259da6..8f05de9f 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/plugin/SystemBars.dex and b/node_modules/@capacitor/android/capacitor/build/.transforms/1276a3e332abd14d335321a6d0415747/transformed/bundleLibRuntimeToDirRelease/bundleLibRuntimeToDirRelease_dex/com/getcapacitor/plugin/SystemBars.dex differ diff --git a/node_modules/@capacitor/android/capacitor/build/.transforms/f09febc18048fab5f1299aee3efc9209/transformed/classes/classes_dex/classes.dex b/node_modules/@capacitor/android/capacitor/build/.transforms/f09febc18048fab5f1299aee3efc9209/transformed/classes/classes_dex/classes.dex index 1016c798..dec4de0a 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/.transforms/f09febc18048fab5f1299aee3efc9209/transformed/classes/classes_dex/classes.dex and b/node_modules/@capacitor/android/capacitor/build/.transforms/f09febc18048fab5f1299aee3efc9209/transformed/classes/classes_dex/classes.dex differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar b/node_modules/@capacitor/android/capacitor/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar index a819218d..544cf3c3 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar and b/node_modules/@capacitor/android/capacitor/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar b/node_modules/@capacitor/android/capacitor/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar index 5f127147..a2f9e0ab 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar and b/node_modules/@capacitor/android/capacitor/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/full_jar/release/createFullJarRelease/full.jar b/node_modules/@capacitor/android/capacitor/build/intermediates/full_jar/release/createFullJarRelease/full.jar index fe915a96..fa4c8689 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/full_jar/release/createFullJarRelease/full.jar and b/node_modules/@capacitor/android/capacitor/build/intermediates/full_jar/release/createFullJarRelease/full.jar differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/Bridge.class b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/Bridge.class index c052ced2..f4c10616 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/Bridge.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/Bridge.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/BridgeWebChromeClient.class b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/BridgeWebChromeClient.class index 873124dc..559f1fee 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/BridgeWebChromeClient.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/BridgeWebChromeClient.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/MessageHandler.class b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/MessageHandler.class index 60533c63..8a786ece 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/MessageHandler.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/MessageHandler.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/WebViewLocalServer$1.class b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/WebViewLocalServer$1.class index 22c5a8ea..4f43add4 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/WebViewLocalServer$1.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/WebViewLocalServer$1.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/cordova/MockCordovaWebViewImpl.class b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/cordova/MockCordovaWebViewImpl.class index e4614aa9..bee24b99 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/cordova/MockCordovaWebViewImpl.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/cordova/MockCordovaWebViewImpl.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/CapacitorCookies.class b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/CapacitorCookies.class index 4cdc9649..3c96cf93 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/CapacitorCookies.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/CapacitorCookies.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/CapacitorHttp$1.class b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/CapacitorHttp$1.class index 5e2646de..7e700c51 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/CapacitorHttp$1.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/CapacitorHttp$1.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/SystemBars.class b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/SystemBars.class index 381e26dd..ccd58342 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/SystemBars.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/getcapacitor/plugin/SystemBars.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/local_aar_for_lint/release/out.aar b/node_modules/@capacitor/android/capacitor/build/intermediates/local_aar_for_lint/release/out.aar index 452ac2c6..200270d3 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/local_aar_for_lint/release/out.aar and b/node_modules/@capacitor/android/capacitor/build/intermediates/local_aar_for_lint/release/out.aar differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/Bridge.class b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/Bridge.class index c052ced2..f4c10616 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/Bridge.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/Bridge.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/BridgeWebChromeClient.class b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/BridgeWebChromeClient.class index 873124dc..559f1fee 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/BridgeWebChromeClient.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/BridgeWebChromeClient.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/MessageHandler.class b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/MessageHandler.class index 60533c63..8a786ece 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/MessageHandler.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/MessageHandler.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/WebViewLocalServer$1.class b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/WebViewLocalServer$1.class index 22c5a8ea..4f43add4 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/WebViewLocalServer$1.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/WebViewLocalServer$1.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/cordova/MockCordovaWebViewImpl.class b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/cordova/MockCordovaWebViewImpl.class index e4614aa9..bee24b99 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/cordova/MockCordovaWebViewImpl.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/cordova/MockCordovaWebViewImpl.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/CapacitorCookies.class b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/CapacitorCookies.class index 4cdc9649..3c96cf93 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/CapacitorCookies.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/CapacitorCookies.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/CapacitorHttp$1.class b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/CapacitorHttp$1.class index 5e2646de..7e700c51 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/CapacitorHttp$1.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/CapacitorHttp$1.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/SystemBars.class b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/SystemBars.class index 381e26dd..ccd58342 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/SystemBars.class and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_dir/release/bundleLibRuntimeToDirRelease/com/getcapacitor/plugin/SystemBars.class differ diff --git a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar index 80767010..a98c6520 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar and b/node_modules/@capacitor/android/capacitor/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar differ diff --git a/node_modules/@capacitor/android/capacitor/build/outputs/aar/capacitor-android-release.aar b/node_modules/@capacitor/android/capacitor/build/outputs/aar/capacitor-android-release.aar index c47e7aba..b4c6d4da 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/outputs/aar/capacitor-android-release.aar and b/node_modules/@capacitor/android/capacitor/build/outputs/aar/capacitor-android-release.aar differ diff --git a/node_modules/@capacitor/android/capacitor/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin b/node_modules/@capacitor/android/capacitor/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin index c11fe808..bc2cd00b 100644 Binary files a/node_modules/@capacitor/android/capacitor/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin and b/node_modules/@capacitor/android/capacitor/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin differ diff --git a/node_modules/@capacitor/app/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar b/node_modules/@capacitor/app/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar index ca8dd2ba..349b690c 100644 Binary files a/node_modules/@capacitor/app/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar and b/node_modules/@capacitor/app/android/build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar differ diff --git a/node_modules/@capacitor/app/android/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar b/node_modules/@capacitor/app/android/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar index 3af13e3a..30b3fc75 100644 Binary files a/node_modules/@capacitor/app/android/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar and b/node_modules/@capacitor/app/android/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar differ diff --git a/node_modules/@capacitor/app/android/build/intermediates/full_jar/release/createFullJarRelease/full.jar b/node_modules/@capacitor/app/android/build/intermediates/full_jar/release/createFullJarRelease/full.jar index a369165b..66bcd0c7 100644 Binary files a/node_modules/@capacitor/app/android/build/intermediates/full_jar/release/createFullJarRelease/full.jar and b/node_modules/@capacitor/app/android/build/intermediates/full_jar/release/createFullJarRelease/full.jar differ diff --git a/node_modules/@capacitor/app/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-libraries.xml b/node_modules/@capacitor/app/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-libraries.xml index 2acdc18a..51db8efc 100644 --- a/node_modules/@capacitor/app/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-libraries.xml +++ b/node_modules/@capacitor/app/android/build/intermediates/incremental/lintVitalAnalyzeRelease/release-artifact-libraries.xml @@ -1,9 +1,9 @@ {}); - } catch (e) {} - } - pcmBatchLen = 0; -} - function handlePcmChunk(ev) { + const native = getNativeVoiceRouting(); + if (!native || !native.nativeAudioFeed || !ev.data) return; try { - if (!getNativeVoiceRouting() || !pcmBatch) return; - const chunk = new Int16Array(ev.data); - for (let i = 0; i < chunk.length; i++) { - if (pcmBatchLen >= pcmBatch.length) flushPcmBatch(); - pcmBatch[pcmBatchLen++] = chunk[i]; - } + native.nativeAudioFeed({ data: int16ToBase64(new Int16Array(ev.data)) }).catch(() => {}); } catch (e) {} } function stopNativePlayback() { - if (voicePcmFlushTimer) { clearInterval(voicePcmFlushTimer); voicePcmFlushTimer = null; } - flushPcmBatch(); - if (voiceTapNode) { - try { voiceTapNode.disconnect(); } catch (e) {} - try { voiceTapNode.port.onmessage = null; } catch (e) {} - voiceTapNode = null; - } const native = getNativeVoiceRouting(); if (native && native.nativeAudioStop) native.nativeAudioStop().catch(() => {}); } @@ -226,15 +200,10 @@ function setupVoiceOutput() { // App + native plugin: the mixed call audio is played by a native AudioTrack // (USAGE_VOICE_COMMUNICATION), so it follows the call volume slider and the // earpiece/speaker switch. Nothing is output through the WebAudio graph. + // The receiver worklet forwards mixed PCM straight here (~20 ms packets). if (native && isMobileVoiceDevice()) { stopNativePlayback(); - pcmBatch = new Int16Array(1536); // 32 ms @ 48 kHz - pcmBatchLen = 0; - voiceTapNode = new AudioWorkletNode(audioContext, 'audio-tap'); - voiceTapNode.port.onmessage = handlePcmChunk; - receiverNode.connect(voiceTapNode); - if (voicePcmFlushTimer) clearInterval(voicePcmFlushTimer); - voicePcmFlushTimer = setInterval(flushPcmBatch, 25); + receiverNode.port.onmessage = handlePcmChunk; if (native.nativeAudioStart) native.nativeAudioStart({ sampleRate: audioContext.sampleRate }).catch(() => {}); return; } @@ -435,18 +404,28 @@ class AudioProcessor extends AudioWorkletProcessor { } } class AudioReceiver extends AudioWorkletProcessor { - constructor() { - super(); + // Jitter buffer targets in samples (@48 kHz): + // PREBUF_START - initial prebuffer before first playback (~43 ms) + // PREBUF_RESUME - re-prebuffer after a starvation event (~32 ms) + // HARD_LIMIT - burst ceiling (~128 ms): snap back to target immediately + // Backlog slightly above target is drained by a gentle trim (1 sample per + // quantum) instead of letting latency creep up to the hard limit. + constructor(options) { + super(); this.users = {}; - this.volumes = {}; - + this.volumes = {}; + const opts = (options && options.processorOptions) || {}; + this.forward = !!opts.forward; + this.acc = new Int16Array(960); // ~20 ms @ 48 kHz + this.accLen = 0; + this.port.onmessage = (e) => { if (e.data.action === 'clear') { this.users = {}; this.volumes = {}; return; } if (e.data.action === 'setVolume') { this.volumes[e.data.clientId] = e.data.volume; return; } if (e.data.action === 'remove') { delete this.users[e.data.clientId]; return; } - + const { clientId, audioData } = e.data; - if (!this.users[clientId]) this.users[clientId] = { buffer: new Int16Array(65536), readPtr: 0, writePtr: 0, buffering: true }; + if (!this.users[clientId]) this.users[clientId] = { buffer: new Int16Array(65536), readPtr: 0, writePtr: 0, buffering: true, started: false }; const user = this.users[clientId]; const intData = new Int16Array(audioData); for (let i = 0; i < intData.length; i++) { @@ -462,12 +441,13 @@ class AudioReceiver extends AudioWorkletProcessor { for (const id in this.users) { const user = this.users[id]; let available = (user.writePtr - user.readPtr + 65536) % 65536; - if (user.buffering && available >= 4096) user.buffering = false; - + if (user.buffering && available >= (user.started ? 1536 : 2048)) { user.buffering = false; user.started = true; } + if (!user.buffering) { if (available < output.length) { user.buffering = true; } else { - if (available > 24576) user.readPtr = (user.writePtr - 8192 + 65536) % 65536; + if (available > 6144) user.readPtr = (user.writePtr - 2048 + 65536) % 65536; + else if (available > 2049) user.readPtr = (user.readPtr + 1) % 65536; let vol = this.volumes[id] !== undefined ? this.volumes[id] : 1.0; for (let i = 0; i < output.length; i++) { output[i] += (user.buffer[user.readPtr] / 32768) * vol; @@ -475,28 +455,21 @@ class AudioReceiver extends AudioWorkletProcessor { } } } - } return true; - } -} -class AudioTap extends AudioWorkletProcessor { - constructor() { super(); this.acc = new Int16Array(512); this.accLen = 0; } - process(inputs) { - const ch = inputs[0] && inputs[0][0]; - if (ch && this.port) { - for (let i = 0; i < ch.length; i++) { - const s = Math.max(-1, Math.min(1, ch[i])); + } + if (this.forward && this.port) { + for (let i = 0; i < output.length; i++) { + const s = Math.max(-1, Math.min(1, output[i])); this.acc[this.accLen++] = s < 0 ? s * 0x8000 : s * 0x7FFF; - } - if (this.accLen >= this.acc.length) { - this.port.postMessage(this.acc.buffer, [this.acc.buffer]); - this.acc = new Int16Array(512); - this.accLen = 0; + if (this.accLen >= this.acc.length) { + this.port.postMessage(this.acc.buffer, [this.acc.buffer]); + this.acc = new Int16Array(960); + this.accLen = 0; + } } } return true; } } -registerProcessor('audio-tap', AudioTap); registerProcessor('audio-sender', AudioProcessor); registerProcessor('audio-receiver', AudioReceiver);`; @@ -599,7 +572,8 @@ async function startVoiceSession(serverUrl) { if (!isAudioWorkletLoaded) { await audioContext.audioWorklet.addModule(URL.createObjectURL(new Blob([voiceWorkletCode], {type: 'application/javascript'}))); senderNode = new AudioWorkletNode(audioContext, 'audio-sender'); - receiverNode = new AudioWorkletNode(audioContext, 'audio-receiver'); + const forwardPcm = !!(getNativeVoiceRouting() && isMobileVoiceDevice()); + receiverNode = new AudioWorkletNode(audioContext, 'audio-receiver', {processorOptions: {forward: forwardPcm}}); screenReceiverNode = new AudioWorkletNode(audioContext, 'audio-receiver'); isAudioWorkletLoaded = true; } @@ -1499,8 +1473,6 @@ function endVoiceSession() { isVoiceSpeakerMode = false; voiceLoopRestarts = 0; stopNativePlayback(); - pcmBatch = null; - pcmBatchLen = 0; if (currentVoiceCallId) { let payload = JSON.stringify({ string1: currentVoiceCallType, string2: currentVoiceCallId });