From 84c31888054369fae13d504bc6499fc1500c6e6a Mon Sep 17 00:00:00 2001 From: shijing xian Date: Tue, 19 May 2026 20:22:19 -0700 Subject: [PATCH 1/2] fix unity Android build --- Runtime/Scripts/Internal/FFIClient.cs | 70 +++++++++++++++++++++++ Runtime/Scripts/Internal/NativeMethods.cs | 14 +++++ Runtime/Scripts/PlatformAudio.cs | 23 ++++---- 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/Runtime/Scripts/Internal/FFIClient.cs b/Runtime/Scripts/Internal/FFIClient.cs index 55c140c0..487fecaa 100644 --- a/Runtime/Scripts/Internal/FFIClient.cs +++ b/Runtime/Scripts/Internal/FFIClient.cs @@ -133,6 +133,48 @@ static void GetMainContext() Utils.Debug("Main Context created"); } +#if UNITY_ANDROID && !UNITY_EDITOR + /// + /// Get the Android application context as a raw jobject pointer. + /// This is passed to the native library for WebRTC audio initialization. + /// + /// IntPtr to the application context jobject, or IntPtr.Zero on failure + private static IntPtr GetAndroidApplicationContext() + { + try + { + // Get the Unity activity + using var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); + using var currentActivity = unityPlayer.GetStatic("currentActivity"); + + if (currentActivity == null) + { + Utils.Error("FFIServer - Failed to get Unity currentActivity"); + return IntPtr.Zero; + } + + // Get the application context from the activity + var applicationContext = currentActivity.Call("getApplicationContext"); + + if (applicationContext == null) + { + Utils.Error("FFIServer - Failed to get Android applicationContext"); + return IntPtr.Zero; + } + + // Get the raw jobject pointer + // Note: We don't dispose the applicationContext here because we're passing + // the raw pointer to native code. The native code will create its own global ref. + return applicationContext.GetRawObject(); + } + catch (System.Exception e) + { + Utils.Error($"FFIServer - Failed to get Android application context: {e.Message}"); + return IntPtr.Zero; + } + } +#endif + private static void InitializeSdk() { #if NO_LIVEKIT_MODE @@ -145,6 +187,34 @@ private static void InitializeSdk() const bool captureLogs = false; #endif +#if UNITY_ANDROID && !UNITY_EDITOR + // Initialize Android WebRTC before the main FFI initialization. + // This initializes the JVM and ContextUtils (required for PlatformAudio). + try + { + IntPtr javaVmPtr = AndroidJNI.GetJavaVM(); + IntPtr contextPtr = GetAndroidApplicationContext(); + + if (javaVmPtr != IntPtr.Zero && contextPtr != IntPtr.Zero) + { + bool contextInitialized = NativeMethods.LiveKitInitializeAndroidContext(javaVmPtr, contextPtr); + if (!contextInitialized) + { + // JVM init still succeeded; only PlatformAudio won't work + Utils.Error("FFIServer - Android context init failed; PlatformAudio will not work"); + } + } + else + { + Utils.Error("FFIServer - Failed to get JavaVM or context for Android init"); + } + } + catch (System.Exception e) + { + Utils.Error($"FFIServer - Android initialization failed: {e.Message}"); + } +#endif + var sdkVersion = PackageVersion.Get(); NativeMethods.LiveKitInitialize(FFICallback, captureLogs, "unity", sdkVersion); diff --git a/Runtime/Scripts/Internal/NativeMethods.cs b/Runtime/Scripts/Internal/NativeMethods.cs index 96a70fbd..4587b345 100644 --- a/Runtime/Scripts/Internal/NativeMethods.cs +++ b/Runtime/Scripts/Internal/NativeMethods.cs @@ -25,5 +25,19 @@ internal static class NativeMethods [DllImport(Lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "livekit_ffi_initialize")] internal extern static FfiHandleId LiveKitInitialize(FFICallbackDelegate cb, bool captureLogs, string sdk, string sdkVersion); + +#if UNITY_ANDROID && !UNITY_EDITOR + /// + /// Initialize Android WebRTC with the application context. + /// This initializes both the JVM and ContextUtils, which is required for + /// Android audio (microphone/speaker) to work via PlatformAudio. + /// + /// Pointer to the JavaVM + /// The Android application context (jobject) + /// true if context initialization succeeded, false otherwise. + /// Note: JVM initialization happens regardless of return value. + [DllImport(Lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "livekit_ffi_initialize_android_context")] + internal extern static bool LiveKitInitializeAndroidContext(IntPtr javaVmPtr, IntPtr contextPtr); +#endif } } \ No newline at end of file diff --git a/Runtime/Scripts/PlatformAudio.cs b/Runtime/Scripts/PlatformAudio.cs index 326a5405..bac40a10 100644 --- a/Runtime/Scripts/PlatformAudio.cs +++ b/Runtime/Scripts/PlatformAudio.cs @@ -202,11 +202,12 @@ public void SetRecordingDevice(uint index) throw new InvalidOperationException($"Recording device index {index} out of range (max: {recording.Count - 1})"); var deviceId = recording[(int)index].Guid; - if (string.IsNullOrEmpty(deviceId)) - throw new InvalidOperationException($"Recording device at index {index} has no GUID"); - - SetRecordingDevice(deviceId); - Utils.Debug($"PlatformAudio: set recording device to index {index} (GUID: {deviceId})"); + // Note: On Android, devices don't have GUIDs - they're identified by index only. + // Android also only reports a single "default" microphone because the system + // automatically selects the best input source based on the audio mode. + // If GUID is empty, we pass an empty string which triggers index-0 fallback in native code. + SetRecordingDevice(deviceId ?? ""); + Utils.Debug($"PlatformAudio: set recording device to index {index} (GUID: {(string.IsNullOrEmpty(deviceId) ? "" : deviceId)})"); } /// @@ -264,11 +265,13 @@ public void SetPlayoutDevice(uint index) throw new InvalidOperationException($"Playout device index {index} out of range (max: {playout.Count - 1})"); var deviceId = playout[(int)index].Guid; - if (string.IsNullOrEmpty(deviceId)) - throw new InvalidOperationException($"Playout device at index {index} has no GUID"); - - SetPlayoutDevice(deviceId); - Utils.Debug($"PlatformAudio: set playout device to index {index} (GUID: {deviceId})"); + // Note: On Android, devices don't have GUIDs - they're identified by index only. + // Android also only reports a single "default" device because audio routing + // (speaker vs earpiece vs Bluetooth) is handled by the system via AudioManager, + // not through WebRTC device selection. Use Android's AudioManager API to switch outputs. + // If GUID is empty, we pass an empty string which triggers index-0 fallback in native code. + SetPlayoutDevice(deviceId ?? ""); + Utils.Debug($"PlatformAudio: set playout device to index {index} (GUID: {(string.IsNullOrEmpty(deviceId) ? "" : deviceId)})"); } /// From 3adf69e296c91f5bd013fb83bf9722cdae6e7abe Mon Sep 17 00:00:00 2001 From: shijing xian Date: Tue, 19 May 2026 21:33:52 -0700 Subject: [PATCH 2/2] upload the android liblivekit_ffi.so for testing --- Runtime/Plugins/ffi-android-arm64/liblivekit_ffi.so | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Plugins/ffi-android-arm64/liblivekit_ffi.so b/Runtime/Plugins/ffi-android-arm64/liblivekit_ffi.so index 2bddb7f9..37b473a8 100644 --- a/Runtime/Plugins/ffi-android-arm64/liblivekit_ffi.so +++ b/Runtime/Plugins/ffi-android-arm64/liblivekit_ffi.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33d86561611775e822eab0f761f2fe6842468befe92b56f1b7186ed77d8a393a -size 18759032 +oid sha256:4d83b90dd881941b16843b5556695bce29fe86317bfc2c67e863d18990798d7a +size 19794416