android(call): Increment 2a — native LiveKit call audio, background service, speaker/BT routing

Replaces WebView call media on Android with a native LiveKit connection (iOS
parity), so call audio survives backgrounding and the output can be switched
to Speaker/Bluetooth/Earpiece — the #3 (missing speaker control) and #4
(background audio) items.

Gated behind a new, default-off server flag ANDROID_NATIVE_MEDIA (independent
of ANDROID_CALLS, which only governs the native ring). Flag off = today's
WebView media, video included; flag on = native audio (video is Increment 2b,
so enable it for VOICE testing first). No behavior change until flipped.

Native (mobile/plugins/native-call/android):
- build.gradle: add kotlin-android + io.livekit:livekit-android 2.29.2 (+ twilio
  audioswitch, coroutines) + JitPack repo; minSdk 24 (LiveKit floor).
- CallMedia.kt: LiveKit Room wrapper — connect/mic/disconnect + AudioSwitchHandler
  routing (auto + selectDevice) and route-change events.
- CallForegroundService.java: microphone-type foreground service for background
  audio (no-ops without RECORD_AUDIO so it never crashes the call).
- NativeCallPlugin.java: real bodies for reportOutgoingCall/reconnectRoom/setMuted
  /endCall + new setAudioRoute/getAudioRoute; fires callConnected/callError/audioRoute.
- AndroidManifest: FG service + FOREGROUND_SERVICE(_MICROPHONE)/RECORD_AUDIO/
  MODIFY_AUDIO_SETTINGS/BLUETOOTH_CONNECT.
- android-patch.js: bump the generated app minSdk to 24.

Server:
- config.js/routes.js: ANDROID_NATIVE_MEDIA → /api/meetings/config.androidNativeMedia.

Web (home.html):
- Android flips to the native-media path when androidNativeMedia is set; the
  existing outgoing/answer/mic/end code then drives the plugin unchanged.
- Speaker button wired for Android native: cycles speaker/earpiece/bluetooth via
  NC.setAudioRoute and reflects the real output from the audioRoute event.

Video stays no-op on the native path (Increment 2b).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
SravanandClaude Opus 4.8 committed 2026-10-30 17:25:18 +05:30
1 parent 08eff043ec
commit aa4aeeaba1
9 files changed
+444 -24

No files matched your search

+21
View File
@@ -16,6 +16,27 @@ if (!p || !fs.existsSync(p)) { console.log(' (AndroidManifest.xml not found —
try {
let s = fs.readFileSync(p, 'utf8');
// (0) LiveKit Android (native call media, Increment 2) requires a higher minSdk than Capacitor's default.
// Bump it in the generated variables.gradle so the library manifest merge holds (a library minSdk above the
// app's fails the build). variables.gradle sits at android/variables.gradle (3 dirs up from app/src/main).
try {
const path = require('path');
const varsPath = path.resolve(path.dirname(p), '..', '..', '..', 'variables.gradle');
if (fs.existsSync(varsPath)) {
let v = fs.readFileSync(varsPath, 'utf8');
const m = v.match(/minSdkVersion\s*=\s*(\d+)/);
if (m && parseInt(m[1], 10) < 24) {
v = v.replace(/minSdkVersion\s*=\s*\d+/, 'minSdkVersion = 24');
fs.writeFileSync(varsPath, v);
console.log(' minSdkVersion bumped to 24 (LiveKit Android floor)');
} else {
console.log(' minSdkVersion already >= 24 or not found — no bump');
}
} else {
console.log(' variables.gradle not found — minSdk bump skipped');
}
} catch (e) { console.log(' minSdk bump skipped:', e.message); }
// (1) Native-call FirebaseMessagingService swap. The native-call plugin declares its own MESSAGING_EVENT
// service (BzCallMessagingService) that handles call-wake data messages AND forwards regular push to Capacitor
// via super. Remove @capacitor/push-notifications' own MessagingService here — the APP manifest is the highest