diff --git a/android/java/main/java/ai/moonshine/voice/Transcriber.java b/android/java/main/java/ai/moonshine/voice/Transcriber.java
--- a/android/java/main/java/ai/moonshine/voice/Transcriber.java
+++ b/android/java/main/java/ai/moonshine/voice/Transcriber.java
@@ -25,7 +25,7 @@ import java.util.HashMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.Map;

-public class Transcriber {
+public class Transcriber implements AutoCloseable {
   private int transcriberHandle = -1;
   private int defaultStreamHandle = -1;
   private final List<Consumer<TranscriptEvent>> listeners =
@@ -85,18 +85,22 @@ public class Transcriber {
     this.getDefaultStreamHandle();
   }

-  protected void finalize() throws Throwable {
+  @Override
+  public synchronized void close() {
     if (this.transcriberHandle >= 0) {
       if (this.defaultStreamHandle >= 0) {
         JNI.moonshineFreeStream(this.transcriberHandle,
                                 this.defaultStreamHandle);
         this.defaultStreamHandle = -1;
       }
+      this.removeAllListeners();
       JNI.moonshineFreeTranscriber(this.transcriberHandle);
       this.transcriberHandle = -1;
     }
   }

+  protected void finalize() throws Throwable { this.close(); }
+
   public Transcript transcribeWithoutStreaming(float[] audioData,
                                                int sampleRate) {
     return JNI.moonshineTranscribeWithoutStreaming(this.transcriberHandle,
diff --git a/build.gradle.kts b/build.gradle.kts
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,6 +5,28 @@ plugins {
     id("com.vanniktech.maven.publish") version "0.28.0"
 }

+fun envOrProp(envName: String, propName: String, fallback: String? = null): String? {
+    val envValue = System.getenv(envName)?.trim()
+    if (!envValue.isNullOrEmpty()) {
+        return envValue
+    }
+
+    val propValue = providers.gradleProperty(propName).orNull?.trim()
+    if (!propValue.isNullOrEmpty()) {
+        return propValue
+    }
+
+    return fallback
+}
+
+val moonshineAbiFilters = envOrProp(
+    "SITEED_MOONSHINE_ANDROID_ABIS",
+    "siteedMoonshineAndroidAbis",
+    "arm64-v8a"
+)?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() } ?: emptyList()
+val moonshineOrtLibPath = envOrProp("SITEED_MOONSHINE_ORT_LIB_PATH", "siteedMoonshineOrtLibPath")
+val moonshineOrtIncludeDir = envOrProp("SITEED_MOONSHINE_ORT_INCLUDE_DIR", "siteedMoonshineOrtIncludeDir")
+
 android {
     namespace = "ai.moonshine.voice"
     compileSdk = 35
@@ -16,12 +38,17 @@ android {
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles("consumer-rules.pro")
         ndk {
-            // Only build for ARM64 to match the app module
-            abiFilters += listOf("arm64-v8a")
+            abiFilters += moonshineAbiFilters
         }
         externalNativeBuild {
             cmake {
                 arguments("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
+                if (!moonshineOrtLibPath.isNullOrEmpty()) {
+                    arguments("-DMOONSHINE_ONNXRUNTIME_LIB_PATH=$moonshineOrtLibPath")
+                }
+                if (!moonshineOrtIncludeDir.isNullOrEmpty()) {
+                    arguments("-DMOONSHINE_ONNXRUNTIME_INCLUDE_DIR=$moonshineOrtIncludeDir")
+                }
             }
         }
     }
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -97,7 +97,7 @@ target_include_directories(moonshine PRIVATE
     ${CMAKE_CURRENT_LIST_DIR}/moonshine-utils
     ${CMAKE_CURRENT_LIST_DIR}/ort-utils
     ${CMAKE_CURRENT_LIST_DIR}/bin-tokenizer
-    ${CMAKE_CURRENT_LIST_DIR}/third-party/onnxruntime/include
+    ${ONNXRUNTIME_INCLUDE_DIR}
     ${CMAKE_CURRENT_LIST_DIR}/third-party/utf-8/
 )

diff --git a/core/ort-utils/CMakeLists.txt b/core/ort-utils/CMakeLists.txt
--- a/core/ort-utils/CMakeLists.txt
+++ b/core/ort-utils/CMakeLists.txt
@@ -31,7 +31,7 @@ target_compile_options(ort-utils PRIVATE

 target_include_directories(ort-utils PRIVATE
     ${MOONSHINE_UTILS_INCLUDE_DIR}
-    ${CMAKE_CURRENT_LIST_DIR}/../third-party/onnxruntime/include
+    ${ONNXRUNTIME_INCLUDE_DIR}
 )

 if (ANDROID)
@@ -69,7 +69,7 @@ target_include_directories(ort-utils-test PRIVATE
     ${CMAKE_CURRENT_LIST_DIR}
     ${CMAKE_CURRENT_LIST_DIR}/../moonshine-utils
     ${CMAKE_CURRENT_LIST_DIR}/../third-party/doctest
-    ${CMAKE_CURRENT_LIST_DIR}/../third-party/onnxruntime/include
+    ${ONNXRUNTIME_INCLUDE_DIR}
 )

 target_link_libraries(ort-utils-test PRIVATE
diff --git a/core/third-party/onnxruntime/find-ort-library-path.cmake b/core/third-party/onnxruntime/find-ort-library-path.cmake
--- a/core/third-party/onnxruntime/find-ort-library-path.cmake
+++ b/core/third-party/onnxruntime/find-ort-library-path.cmake
@@ -1,4 +1,16 @@
-if (ANDROID)
+if (DEFINED MOONSHINE_ONNXRUNTIME_INCLUDE_DIR AND NOT "${MOONSHINE_ONNXRUNTIME_INCLUDE_DIR}" STREQUAL "")
+    set(ONNXRUNTIME_INCLUDE_DIR "${MOONSHINE_ONNXRUNTIME_INCLUDE_DIR}" CACHE INTERNAL "")
+elseif(DEFINED ENV{MOONSHINE_ONNXRUNTIME_INCLUDE_DIR} AND NOT "$ENV{MOONSHINE_ONNXRUNTIME_INCLUDE_DIR}" STREQUAL "")
+    set(ONNXRUNTIME_INCLUDE_DIR "$ENV{MOONSHINE_ONNXRUNTIME_INCLUDE_DIR}" CACHE INTERNAL "")
+else()
+    set(ONNXRUNTIME_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include" CACHE INTERNAL "")
+endif()
+
+if (DEFINED MOONSHINE_ONNXRUNTIME_LIB_PATH AND NOT "${MOONSHINE_ONNXRUNTIME_LIB_PATH}" STREQUAL "")
+    set(ONNXRUNTIME_LIB_PATH "${MOONSHINE_ONNXRUNTIME_LIB_PATH}" CACHE INTERNAL "")
+elseif(DEFINED ENV{MOONSHINE_ONNXRUNTIME_LIB_PATH} AND NOT "$ENV{MOONSHINE_ONNXRUNTIME_LIB_PATH}" STREQUAL "")
+    set(ONNXRUNTIME_LIB_PATH "$ENV{MOONSHINE_ONNXRUNTIME_LIB_PATH}" CACHE INTERNAL "")
+elseif (ANDROID)
     # Detect Android ABI and map to library directory name
     if(ANDROID_ABI STREQUAL "armeabi-v7a")
         set(ONNXRUNTIME_ABI_DIR "armeabi-v7a")
