From df38124ccea4e601838892cc847e76ca80e291a1 Mon Sep 17 00:00:00 2001 From: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> Date: Sat, 7 Nov 2026 05:03:22 +0000 Subject: [PATCH] refactor(mobile): drop the unused playback path from the dictation audio module (#39293) --- mobile/packages/expo-two-way-audio/README.md | 23 +-- .../expo/modules/twowayaudio/AudioEngine.kt | 123 +-------------- .../ExpoTwoWayAudioLifeCycleListener.kt | 8 +- .../twowayaudio/ExpoTwoWayAudioModule.kt | 27 +--- .../expo-two-way-audio/ios/AudioEngine.swift | 140 +----------------- .../ios/ExpoTwoWayAudioModule.swift | 35 +---- .../src/ExpoTwoWayAudioModule.web.ts | 10 -- .../packages/expo-two-way-audio/src/core.ts | 20 --- .../packages/expo-two-way-audio/src/events.ts | 1 - mobile/web-entry/web-overrides.json | 2 +- 10 files changed, 22 insertions(+), 367 deletions(-) diff --git a/mobile/packages/expo-two-way-audio/README.md b/mobile/packages/expo-two-way-audio/README.md index 3a66efce17b..80d2e793b27 100644 --- a/mobile/packages/expo-two-way-audio/README.md +++ b/mobile/packages/expo-two-way-audio/README.md @@ -1,13 +1,12 @@ # Orca Two Way Audio -Vendored Expo module for capturing and playing PCM audio data in the Orca mobile app (iOS and Android). +Vendored Expo module for capturing PCM microphone audio in the Orca mobile app (iOS and Android). Orca uses it for voice dictation; the upstream playback path was removed because nothing played audio through it. -The aim of the module is to facilitate creating real-time conversational apps. The following features are provided: +The following features are provided: - Request audio recording permissions - Get clean (applying Acoustic Echo Cancelling) microphone samples in PCM format (1 channel 16 bit at 16kHz) -- Play audio samples in PCM format (1 channel 16 bit at 16kHz). Playback happens through main speaker unless external audio sources are connected. -- Provide volume level both for the input and output samples. Float between 0 and 1. +- Provide the input volume level. Float between 0 and 1. - [iOS only] Get microphone mode and prompt user to select a microphone mode. ## Installation @@ -39,22 +38,6 @@ npm i @orca/expo-two-way-audio ``` -1. Play audio - - > [!NOTE] - > The sample below uses the `buffer` module: - > `npm install buffer` - - ```JSX - import { Buffer } from "buffer"; - - // As an example, play pcm data hardcoded in a variable. - const audioChunk = "SOME PCM DATA BASE64 ENCODED HERE" - const buffer = Buffer.from(audioChunk, "base64"); - const pcmData = new Uint8Array(buffer); - playPCMData(pcmData); - ``` - 1. Get microphone samples ```JSX diff --git a/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/AudioEngine.kt b/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/AudioEngine.kt index 301b3a43cc5..62ea7b4722c 100644 --- a/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/AudioEngine.kt +++ b/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/AudioEngine.kt @@ -8,7 +8,6 @@ import android.media.AudioFocusRequest import android.media.AudioFormat import android.media.AudioManager import android.media.AudioRecord -import android.media.AudioTrack import android.media.MediaRecorder import android.media.audiofx.AcousticEchoCanceler import android.media.audiofx.NoiseSuppressor @@ -16,10 +15,7 @@ import android.os.Build import android.os.PowerManager import android.util.Log import androidx.annotation.RequiresApi -import java.util.Queue -import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.Executors -import java.util.concurrent.atomic.AtomicBoolean import kotlin.math.pow @@ -30,32 +26,22 @@ class AudioEngine (context: Context) { private lateinit var audioRecord: AudioRecord private lateinit var audioManager: AudioManager - private lateinit var audioTrack: AudioTrack private var audioFocusRequest: AudioFocusRequest? = null private var audioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null private var audioDeviceCallback: android.media.AudioDeviceCallback? = null - private val audioSampleQueue: Queue = ConcurrentLinkedQueue() - private val playbackRunning = AtomicBoolean(false) private var echoCanceler: AcousticEchoCanceler? = null private var noiseSuppressor: NoiseSuppressor? = null private val executorServiceMicrophone = Executors.newFixedThreadPool(1) - private val executorServicePlayback = Executors.newFixedThreadPool(1) private var speakerDevice: AudioDeviceInfo? = null private var bridgeWindowStartedAtMs = System.currentTimeMillis() private var micEvents = 0 private var micBytes = 0L - private var playbackEvents = 0 - private var playbackQueuedBytes = 0L - private var playbackWrites = 0 - private var playbackWriteBytes = 0L var isRecording = false private var isRecordingBeforePause = false - var isPlaying = false var onMicDataCallback: ((ByteArray) -> Unit)? = null var onInputVolumeCallback: ((Float) -> Unit)? = null - var onOutputVolumeCallback: ((Float) -> Unit)? = null var onAudioInterruptionCallback: ((String) -> Unit)? = null init { @@ -70,18 +56,11 @@ class AudioEngine (context: Context) { } Log.d( "AudioEngine", - "[bridge] $reason mic=$micEvents" + - "ev/${micBytes}B playbackQueue=$playbackEvents" + - "ev/${playbackQueuedBytes}B playbackWrites=$playbackWrites" + - "ev/${playbackWriteBytes}B windowMs=$elapsedMs" + "[bridge] $reason mic=${micEvents}ev/${micBytes}B windowMs=$elapsedMs" ) bridgeWindowStartedAtMs = now micEvents = 0 micBytes = 0 - playbackEvents = 0 - playbackQueuedBytes = 0 - playbackWrites = 0 - playbackWriteBytes = 0 } @SuppressLint("NewApi") @@ -111,29 +90,6 @@ class AudioEngine (context: Context) { } else { updateLegacyAudioRouting() } - - val bufferSize = AudioTrack.getMinBufferSize( - SAMPLE_RATE, - AudioFormat.CHANNEL_OUT_MONO, - AUDIO_FORMAT - ) - - audioTrack = AudioTrack( - AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) - .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) - .build(), - AudioFormat.Builder() - .setEncoding(AUDIO_FORMAT) - .setSampleRate(SAMPLE_RATE) - .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) - .build(), - bufferSize, - AudioTrack.MODE_STREAM, - audioManager.generateAudioSessionId() - ).apply { - play() - } } @RequiresApi(Build.VERSION_CODES.M) @@ -195,10 +151,9 @@ class AudioEngine (context: Context) { AudioManager.AUDIOFOCUS_LOSS -> { Log.d("AudioEngine", "Audio focus lost") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - pauseRecordingAndPlayer() + pauseRecording() } else { stopRecording() - stopPlayback() // Pre-Q stops for good rather than pausing, so the focus goes back too. abandonAudioFocus() } @@ -339,47 +294,6 @@ class AudioEngine (context: Context) { return isRecording } - fun playPCMData(data: ByteArray) { - audioSampleQueue.add(data) - playbackEvents += 1 - playbackQueuedBytes += data.size.toLong() - flushBridgeStats("queue") - if (playbackRunning.compareAndSet(false, true)) { - playAudioFromSampleQueue() - } - } - - private fun playAudioFromSampleQueue() { - executorServicePlayback.execute{ - isPlaying = true - try { - while (true){ - val data = audioSampleQueue.poll() ?: break - playSample(data) - val audioVolume = calculateRMSLevel(data) - onOutputVolumeCallback?.invoke(audioVolume) - } - }catch (e: Exception){ - Log.e("AudioEngine", "Error playing audio", e) - e.printStackTrace() - }finally { - playbackRunning.set(false) - isPlaying = false - onOutputVolumeCallback?.invoke(0.0F) - if (audioSampleQueue.isNotEmpty() && playbackRunning.compareAndSet(false, true)) { - playAudioFromSampleQueue() - } - } - } - } - - private fun playSample(data: ByteArray) { - val written = audioTrack.write(data, 0, data.size) - playbackWrites += 1 - playbackWriteBytes += written.coerceAtLeast(0).toLong() - flushBridgeStats("write") - } - fun bypassVoiceProcessing(bypass: Boolean) { if (bypass) { echoCanceler?.enabled = false @@ -391,14 +305,13 @@ class AudioEngine (context: Context) { } @RequiresApi(Build.VERSION_CODES.Q) - fun pauseRecordingAndPlayer() { + fun pauseRecording() { isRecordingBeforePause = isRecording isRecording = toggleRecording(false, clearPauseResume = false) - audioTrack.pause() } @RequiresApi(Build.VERSION_CODES.Q) - fun resumeRecordingAndPlayer() { + fun resumeRecording() { requestAudioFocus() // Consume the flag: a second resume without an intervening pause must not reopen the microphone. val shouldResumeRecording = isRecordingBeforePause @@ -407,42 +320,15 @@ class AudioEngine (context: Context) { if (shouldResumeRecording) { isRecording = toggleRecording(true) } - audioTrack.play() - } - - fun stopPlayback() { - audioSampleQueue.clear() - audioTrack.pause() - audioTrack.flush() - playbackRunning.set(false) - isPlaying = false - onOutputVolumeCallback?.invoke(0.0F) - Log.d("AudioEngine", "Playback stopped") - } - - fun pausePlayback() { - audioTrack.pause() - Log.d("AudioEngine", "Playback paused") - } - - fun resumePlayback() { - audioTrack.play() - Log.d("AudioEngine", "Playback resumed") } @SuppressLint("NewApi") fun tearDown() { stopRecording() - if (::audioTrack.isInitialized) { - audioTrack.stop() - audioTrack.release() - } echoCanceler?.release() echoCanceler = null noiseSuppressor?.release() noiseSuppressor = null - audioSampleQueue.clear() - playbackRunning.set(false) audioManager.mode = AudioManager.MODE_NORMAL if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { audioDeviceCallback?.let { audioManager.unregisterAudioDeviceCallback(it) } @@ -453,7 +339,6 @@ class AudioEngine (context: Context) { } abandonAudioFocus() executorServiceMicrophone.shutdownNow() - executorServicePlayback.shutdownNow() } diff --git a/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/ExpoTwoWayAudioLifeCycleListener.kt b/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/ExpoTwoWayAudioLifeCycleListener.kt index cdad2de63f4..ed23216bf1a 100644 --- a/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/ExpoTwoWayAudioLifeCycleListener.kt +++ b/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/ExpoTwoWayAudioLifeCycleListener.kt @@ -6,13 +6,13 @@ import expo.modules.core.interfaces.ReactActivityLifecycleListener class ExpoTwoWayAudioLifeCycleListener : ReactActivityLifecycleListener { override fun onPause(activity: Activity?) { super.onPause(activity) - // At the moment background audio recording and playback is not supported - ExpoTwoWayAudioModule.audioEngine?.pauseRecordingAndPlayer() + // At the moment background audio recording is not supported + ExpoTwoWayAudioModule.audioEngine?.pauseRecording() } override fun onResume(activity: Activity?) { super.onResume(activity) - // At the moment background audio recording and playback is not supported - ExpoTwoWayAudioModule.audioEngine?.resumeRecordingAndPlayer() + // At the moment background audio recording is not supported + ExpoTwoWayAudioModule.audioEngine?.resumeRecording() } } diff --git a/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/ExpoTwoWayAudioModule.kt b/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/ExpoTwoWayAudioModule.kt index b24c26be509..9fd67eb1c3e 100644 --- a/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/ExpoTwoWayAudioModule.kt +++ b/mobile/packages/expo-two-way-audio/android/src/main/java/expo/modules/twowayaudio/ExpoTwoWayAudioModule.kt @@ -10,7 +10,6 @@ class ExpoTwoWayAudioModule : Module() { companion object { private const val ON_MIC_DATA_EVENT = "onMicrophoneData" private const val ON_INPUT_VOLUME_LEVEL_EVENT = "onInputVolumeLevelData" - private const val ON_OUTPUT_VOLUME_LEVEL_EVENT = "onOutputVolumeLevelData" private const val ON_RECORDING_CHANGE_EVENT = "onRecordingChange" private const val ON_AUDIO_INTERRUPTION_EVENT = "onAudioInterruption" var audioEngine: AudioEngine? = null @@ -51,36 +50,16 @@ class ExpoTwoWayAudioModule : Module() { } Function("restart") { - audioEngine?.resumeRecordingAndPlayer() + audioEngine?.resumeRecording() sendEvent(ON_RECORDING_CHANGE_EVENT, mapOf( "data" to (audioEngine?.isRecording ?: false) )) } - Function("playPCMData") { data: kotlin.ByteArray -> - audioEngine?.playPCMData(data) - } - Function("bypassVoiceProcessing") { bypass: Boolean -> audioEngine?.bypassVoiceProcessing(bypass) } - Function("isPlaying") { - audioEngine?.isPlaying ?: false - } - - Function("stopPlayback") { - audioEngine?.stopPlayback() - } - - Function("pausePlayback") { - audioEngine?.pausePlayback() - } - - Function("resumePlayback") { - audioEngine?.resumePlayback() - } - Function("getMicrophoneModeIOS") { throw UnsupportedOperationException("getMicrophoneModeIOS is only supported on iOS") } @@ -118,7 +97,6 @@ class ExpoTwoWayAudioModule : Module() { Events( ON_MIC_DATA_EVENT, ON_INPUT_VOLUME_LEVEL_EVENT, - ON_OUTPUT_VOLUME_LEVEL_EVENT, ON_RECORDING_CHANGE_EVENT, ON_AUDIO_INTERRUPTION_EVENT ) @@ -132,9 +110,6 @@ class ExpoTwoWayAudioModule : Module() { onInputVolumeCallback = { level -> sendEvent(ON_INPUT_VOLUME_LEVEL_EVENT, bundleOf("data" to level)) } - onOutputVolumeCallback = { level -> - sendEvent(ON_OUTPUT_VOLUME_LEVEL_EVENT, bundleOf("data" to level)) - } onAudioInterruptionCallback = { data -> sendEvent(ON_AUDIO_INTERRUPTION_EVENT, bundleOf("data" to data)) sendEvent(ON_RECORDING_CHANGE_EVENT, bundleOf( diff --git a/mobile/packages/expo-two-way-audio/ios/AudioEngine.swift b/mobile/packages/expo-two-way-audio/ios/AudioEngine.swift index ac20b56b1bf..c11194ebd71 100644 --- a/mobile/packages/expo-two-way-audio/ios/AudioEngine.swift +++ b/mobile/packages/expo-two-way-audio/ios/AudioEngine.swift @@ -3,7 +3,6 @@ import Foundation class AudioEngine { private var avAudioEngine = AVAudioEngine() - private var speechPlayer = AVAudioPlayerNode() private var engineConfigChangeObserver: Any? private var sessionInterruptionObserver: Any? private var mediaServicesResetObserver: Any? @@ -15,28 +14,16 @@ class AudioEngine { public var onMicDataCallback: ((Data) -> Void)? public var onInputVolumeCallback: ((Float) -> Void)? - public var onOutputVolumeCallback: ((Float) -> Void)? public var onAudioInterruptionCallback: ((String) -> Void)? - private var inputLevelTimer: Timer? - private var outputLevelTimer: Timer? - private var inputBuffer = [Float](repeating: 0, count: 2048) - private var outputBuffer = [Float](repeating: 0, count: 2048) private var inputBufferIndex = 0 - private var outputBufferIndex = 0 - - private var hasFirstInputBeenDiscarded = false - private var discardRecording = false - private var discardFirstInputMillis = 2000 enum AudioEngineError: Error { case audioFormatError } init() throws { - avAudioEngine.attach(speechPlayer) - guard let format = AVAudioFormat(standardFormatWithSampleRate: 16000, channels: 1) else { throw AudioEngineError.audioFormatError } @@ -121,25 +108,16 @@ class AudioEngine { avAudioEngine.inputNode.isVoiceProcessingInputMuted = !isRecording - let output = avAudioEngine.outputNode - let mainMixer = avAudioEngine.mainMixerNode - - avAudioEngine.connect(speechPlayer, to: mainMixer, format: voiceIOFormat) - avAudioEngine.connect(mainMixer, to: output, format: voiceIOFormat) + // Voice processing runs input and output as one unit, so keep the output side wired even though nothing plays. + avAudioEngine.connect(avAudioEngine.mainMixerNode, to: avAudioEngine.outputNode, format: voiceIOFormat) input.installTap(onBus: 0, bufferSize: 2048, format: voiceIOFormat) { [weak self] buffer, when in - // Skip mic processing while playback’s echo canceller adapts; see playPCMData. - if self?.isRecording == true && self?.discardRecording == false { + if self?.isRecording == true { self?.processMicrophoneBuffer(buffer) self?.updateInputVolume() } } - mainMixer.installTap(onBus: 0, bufferSize: 2048, format: voiceIOFormat) { [weak self] buffer, when in - self?.processOutputBuffer(buffer) - self?.updateOutputVolume() - } - avAudioEngine.prepare() } @@ -165,21 +143,6 @@ class AudioEngine { onMicDataCallback?(data) } - func processOutputBuffer(_ buffer: AVAudioPCMBuffer) { - guard let channelData = buffer.floatChannelData?[0] else { - print("Error: Could not access channel data") - return - } - - let frameCount = Int(buffer.frameLength) - - for i in 0.. AVAudioPCMBuffer? { - let frameCount = UInt32(data.count) / 2 // 16-bit input = 2 bytes per frame - - let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, - sampleRate: 16000, - channels: 1, - interleaved: false)! - - guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCount) else { - return nil - } - - buffer.frameLength = frameCount - - data.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) in - if let sourcePtr = rawBufferPointer.baseAddress?.assumingMemoryBound(to: Int16.self), - let destPtr = buffer.floatChannelData?[0] { - for i in 0.. Bool { - guard toggleRecording(true) else { return false } - speechPlayer.play() - return true } func tearDown() { @@ -299,31 +204,7 @@ class AudioEngine { isTornDown = true // Stop I/O first; deactivating a session with running I/O returns a busy error. avAudioEngine.stop() - stopRecordingAndPlayer() - } - - var isPlaying: Bool { - return speechPlayer.isPlaying - } - - func stopPlayback() { - speechPlayer.stop() - outputBuffer = [Float](repeating: 0, count: 2048) - updateOutputVolume() - print("Playback stopped") - } - - func pausePlayback() { - speechPlayer.pause() - print("Playback paused") - } - - func resumePlayback() { - guard !isTornDown else { return } - if !speechPlayer.isPlaying { - speechPlayer.play() - print("Playback resumed") - } + stopRecording() } private func checkEngineIsRunning() { @@ -342,14 +223,14 @@ class AudioEngine { switch type { case .began: interruptionIntent.began(wasRecording: isRecording) - self.stopRecordingAndPlayer(clearInterruptionResume: false) + self.stopRecording(clearInterruptionResume: false) onAudioInterruptionCallback?("began") case .ended: let rawOptions = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0 let shouldResume = AVAudioSession.InterruptionOptions(rawValue: rawOptions).contains(.shouldResume) switch interruptionIntent.ended(shouldResume: shouldResume, isRecording: isRecording) { case .resume: - let resumed = resumeRecordingAndPlayer() + let resumed = toggleRecording(true) onAudioInterruptionCallback?(resumed ? "ended" : "blocked") case .blocked: onAudioInterruptionCallback?("blocked") @@ -357,7 +238,7 @@ class AudioEngine { break // A newer explicit start already owns the microphone. } @unknown default: - stopRecordingAndPlayer() + stopRecording() onAudioInterruptionCallback?("blocked") } } @@ -381,11 +262,6 @@ class AudioEngine { onInputVolumeCallback?(volume) } - private func updateOutputVolume() { - let volume = calculateRMSLevel(from: outputBuffer) - onOutputVolumeCallback?(volume) - } - private func calculateRMSLevel(from buffer: [Float]) -> Float { let epsilon: Float = 1e-5 // To avoid log(0) let rmsValue = sqrt(buffer.reduce(0) { $0 + $1 * $1 } / Float(buffer.count)) diff --git a/mobile/packages/expo-two-way-audio/ios/ExpoTwoWayAudioModule.swift b/mobile/packages/expo-two-way-audio/ios/ExpoTwoWayAudioModule.swift index 3fae29b1334..5255b81cd41 100644 --- a/mobile/packages/expo-two-way-audio/ios/ExpoTwoWayAudioModule.swift +++ b/mobile/packages/expo-two-way-audio/ios/ExpoTwoWayAudioModule.swift @@ -3,7 +3,6 @@ import AVFoundation let ON_MIC_DATA_EVENT_NAME = "onMicrophoneData" let ON_INPUT_VOLUME_LEVEL_EVENT_NAME = "onInputVolumeLevelData" -let ON_OUTPUT_VOLUME_LEVEL_EVENT_NAME = "onOutputVolumeLevelData" let ON_RECORDING_CHANGE_EVENT_NAME = "onRecordingChange" let ON_AUDIO_INTERRUPTION_EVENT_NAME = "onAudioInterruption" @@ -31,7 +30,6 @@ public class ExpoTwoWayAudioModule: Module { self.audioEngine = try AudioEngine() self.setupMicrophoneCallback() self.setupInputAudioLevelCallback() - self.setupOutputAudioLevelCallback() self.setupAudioInterruptionCallback() return true } catch { @@ -98,7 +96,7 @@ public class ExpoTwoWayAudioModule: Module { } Function("restart") { - self.audioEngine?.resumeRecordingAndPlayer() + _ = self.audioEngine?.toggleRecording(true) self.sendEvent( ON_RECORDING_CHANGE_EVENT_NAME, [ @@ -107,30 +105,10 @@ public class ExpoTwoWayAudioModule: Module { } - Function("playPCMData") { (pcmData: Data) in - self.audioEngine?.playPCMData(pcmData) - } - Function("bypassVoiceProcessing") { (bypass: Bool) in self.audioEngine?.bypassVoiceProcessing(bypass) } - Function("isPlaying") { () -> Bool in - return self.audioEngine?.isPlaying ?? false - } - - Function("stopPlayback") { - self.audioEngine?.stopPlayback() - } - - Function("pausePlayback") { - self.audioEngine?.pausePlayback() - } - - Function("resumePlayback") { - self.audioEngine?.resumePlayback() - } - AsyncFunction("getMicrophonePermissionsAsync") { (promise: Promise) in EXPermissionsMethodsDelegate.getPermissionWithPermissionsManager( self.appContext?.permissions, @@ -152,7 +130,6 @@ public class ExpoTwoWayAudioModule: Module { Events([ ON_MIC_DATA_EVENT_NAME, ON_INPUT_VOLUME_LEVEL_EVENT_NAME, - ON_OUTPUT_VOLUME_LEVEL_EVENT_NAME, ON_RECORDING_CHANGE_EVENT_NAME, ON_AUDIO_INTERRUPTION_EVENT_NAME, ]) @@ -178,16 +155,6 @@ public class ExpoTwoWayAudioModule: Module { } } - private func setupOutputAudioLevelCallback() { - audioEngine?.onOutputVolumeCallback = { [weak self] level in - self?.sendEvent( - ON_OUTPUT_VOLUME_LEVEL_EVENT_NAME, - [ - "data": level - ]) - } - } - private func setupAudioInterruptionCallback() { audioEngine?.onAudioInterruptionCallback = { [weak self] data in self?.sendEvent( diff --git a/mobile/packages/expo-two-way-audio/src/ExpoTwoWayAudioModule.web.ts b/mobile/packages/expo-two-way-audio/src/ExpoTwoWayAudioModule.web.ts index db4f8f5b638..797f5f5f373 100644 --- a/mobile/packages/expo-two-way-audio/src/ExpoTwoWayAudioModule.web.ts +++ b/mobile/packages/expo-two-way-audio/src/ExpoTwoWayAudioModule.web.ts @@ -6,7 +6,6 @@ type EventSubscription = { type ExpoTwoWayAudioWebModule = { initialize: () => Promise - playPCMData: (audioData: Uint8Array) => void bypassVoiceProcessing: (bypass: boolean) => void toggleRecording: (val: boolean) => boolean isRecording: () => boolean @@ -16,10 +15,6 @@ type ExpoTwoWayAudioWebModule = { requestMicrophonePermissionsAsync: () => Promise getMicrophoneModeIOS: () => null setMicrophoneModeIOS: () => void - isPlaying: () => boolean - stopPlayback: () => void - pausePlayback: () => void - resumePlayback: () => void addListener: (eventName: string, handler: (ev: unknown) => void) => EventSubscription } @@ -35,7 +30,6 @@ const noop = () => undefined const ExpoTwoWayAudioModule: ExpoTwoWayAudioWebModule = { // QA browser has no shell microphone; page dictation uses platform/dictation-capture.web.ts native.audio.start|read|stop instead. initialize: async () => false, - playPCMData: noop, bypassVoiceProcessing: noop, toggleRecording: () => false, isRecording: () => false, @@ -45,10 +39,6 @@ const ExpoTwoWayAudioModule: ExpoTwoWayAudioWebModule = { requestMicrophonePermissionsAsync: async () => deniedMicrophonePermission, getMicrophoneModeIOS: () => null, setMicrophoneModeIOS: noop, - isPlaying: () => false, - stopPlayback: noop, - pausePlayback: noop, - resumePlayback: noop, addListener: () => ({ remove: noop }) } diff --git a/mobile/packages/expo-two-way-audio/src/core.ts b/mobile/packages/expo-two-way-audio/src/core.ts index a1c2c156818..e0ebb5e7c6b 100644 --- a/mobile/packages/expo-two-way-audio/src/core.ts +++ b/mobile/packages/expo-two-way-audio/src/core.ts @@ -5,10 +5,6 @@ export async function initialize() { return await ExpoTwoWayAudioModule.initialize() } -export function playPCMData(audioData: Uint8Array) { - return ExpoTwoWayAudioModule.playPCMData(audioData) -} - export function bypassVoiceProcessing(bypass: boolean) { return ExpoTwoWayAudioModule.bypassVoiceProcessing(bypass) } @@ -44,19 +40,3 @@ export function getMicrophoneModeIOS() { export function setMicrophoneModeIOS() { return ExpoTwoWayAudioModule.setMicrophoneModeIOS() } - -export function isPlaying(): boolean { - return ExpoTwoWayAudioModule.isPlaying() -} - -export function stopPlayback() { - return ExpoTwoWayAudioModule.stopPlayback() -} - -export function pausePlayback() { - return ExpoTwoWayAudioModule.pausePlayback() -} - -export function resumePlayback() { - return ExpoTwoWayAudioModule.resumePlayback() -} diff --git a/mobile/packages/expo-two-way-audio/src/events.ts b/mobile/packages/expo-two-way-audio/src/events.ts index 06f6e7b30e3..d98d023aa98 100644 --- a/mobile/packages/expo-two-way-audio/src/events.ts +++ b/mobile/packages/expo-two-way-audio/src/events.ts @@ -19,7 +19,6 @@ export interface AudioInterruptionEvent { export interface ExpoTwoWayAudioEventMap { onMicrophoneData: MicrophoneDataEvent onInputVolumeLevelData: VolumeLevelEvent - onOutputVolumeLevelData: VolumeLevelEvent onRecordingChange: RecordingChangeEvent onAudioInterruption: AudioInterruptionEvent } diff --git a/mobile/web-entry/web-overrides.json b/mobile/web-entry/web-overrides.json index 3a4e2ef6bc0..880961111cd 100644 --- a/mobile/web-entry/web-overrides.json +++ b/mobile/web-entry/web-overrides.json @@ -15,7 +15,7 @@ }, { "file": "packages/expo-two-way-audio/src/ExpoTwoWayAudioModule.web.ts", - "reason": "Vendored with the module, not added for Route A. Nothing on the page reaches it any more: dictation's capture went behind src/platform/dictation-capture.web.ts, which asks the shell for the microphone, so the only importer of @orca/expo-two-way-audio is the native half of that seam. The file stays because it ships with the vendored module, and it answers denied microphone permission and no playback, which is what a browser outside the shell can honestly say." + "reason": "Vendored with the module, not added for Route A. Nothing on the page reaches it any more: dictation's capture went behind src/platform/dictation-capture.web.ts, which asks the shell for the microphone, so the only importer of @orca/expo-two-way-audio is the native half of that seam. The file stays because it ships with the vendored module, and it answers denied microphone permission, which is what a browser outside the shell can honestly say." }, { "file": "src/navigation/use-back-claim.web.ts",