diff --git a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java index 3e9234b9d..dfd5c8a6b 100644 --- a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java +++ b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java @@ -15,6 +15,7 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; @@ -52,6 +53,7 @@ class GetUserMediaImpl { private final Map tracks = new HashMap<>(); private final WebRTCModule webRTCModule; + private String audioDeviceId; private Promise displayMediaPromise; private Intent mediaProjectionPermissionResultData; @@ -59,6 +61,7 @@ class GetUserMediaImpl { GetUserMediaImpl(WebRTCModule webRTCModule, ReactApplicationContext reactContext) { this.webRTCModule = webRTCModule; this.reactContext = reactContext; + this.audioDeviceId = WebRTCModuleOptions.getInstance().defaultAudioDeviceId; reactContext.addActivityEventListener(new BaseActivityEventListener() { @Override @@ -85,8 +88,80 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, private AudioTrack createAudioTrack(ReadableMap constraints) { ReadableMap audioConstraintsMap = constraints.getMap("audio"); + Log.d(TAG, "=========================================="); + Log.d(TAG, "JONATHAN'S FORK: USING CUSTOM AUDIO DEVICE CODE"); + Log.d(TAG, "=========================================="); + Log.d(TAG, "getUserMedia(audio): " + audioConstraintsMap); + // Check if a specific audio device ID was requested + if (audioConstraintsMap != null && audioConstraintsMap.hasKey("deviceId")) { + try { + // IMPORTANT: Check the type of deviceId to avoid casting errors + ReadableType deviceIdType = audioConstraintsMap.getType("deviceId"); + Log.d(TAG, "Audio deviceId type: " + deviceIdType); + + if (deviceIdType == ReadableType.String) { + // Case 1: deviceId is a simple string + String deviceId = audioConstraintsMap.getString("deviceId"); + if (deviceId != null && !deviceId.isEmpty()) { + Log.d(TAG, "Using specific audio device ID (string): " + deviceId); + this.audioDeviceId = deviceId; + } + } + else if (deviceIdType == ReadableType.Map) { + // Case 2: deviceId is an object with exact/ideal constraints + ReadableMap deviceIdMap = audioConstraintsMap.getMap("deviceId"); + Log.d(TAG, "Audio deviceId is a map: " + deviceIdMap); + + // Try to get the "exact" constraint first + if (deviceIdMap.hasKey("exact")) { + ReadableType exactType = deviceIdMap.getType("exact"); + if (exactType == ReadableType.String) { + String exactDeviceId = deviceIdMap.getString("exact"); + if (exactDeviceId != null && !exactDeviceId.isEmpty()) { + Log.d(TAG, "Using exact audio device ID: " + exactDeviceId); + this.audioDeviceId = exactDeviceId; + } + } else { + Log.w(TAG, "Exact deviceId is not a string but: " + exactType); + } + } + // If no exact value, try the "ideal" constraint + else if (deviceIdMap.hasKey("ideal")) { + ReadableType idealType = deviceIdMap.getType("ideal"); + if (idealType == ReadableType.String) { + String idealDeviceId = deviceIdMap.getString("ideal"); + if (idealDeviceId != null && !idealDeviceId.isEmpty()) { + Log.d(TAG, "Using ideal audio device ID: " + idealDeviceId); + this.audioDeviceId = idealDeviceId; + } + } else { + Log.w(TAG, "Ideal deviceId is not a string but: " + idealType); + } + } + else { + // Fallback: Just use whatever is in the map as a last resort + Log.d(TAG, "No exact/ideal keys in deviceId map, using default"); + } + } + else if (deviceIdType == ReadableType.Array) { + // Case 3: deviceId is an array + Log.w(TAG, "Audio deviceId is an array, not currently supported"); + } + else { + // Case 4: deviceId is some other type + Log.w(TAG, "Unsupported deviceId type: " + deviceIdType); + } + } catch (Exception e) { + // Log the full stack trace to help with debugging + Log.e(TAG, "Error processing audio deviceId constraint", e); + } + } + + // Log the final chosen audio device ID + Log.d(TAG, "Final audio device ID being used: " + this.audioDeviceId); + String id = UUID.randomUUID().toString(); PeerConnectionFactory pcFactory = webRTCModule.mFactory; MediaConstraints peerConstraints = webRTCModule.constraintsForOptions(audioConstraintsMap); @@ -157,7 +232,7 @@ ReadableArray enumerateDevices() { } WritableMap audio = Arguments.createMap(); - audio.putString("deviceId", "audio-1"); + audio.putString("deviceId", audioDeviceId); // Use the configured/current audio device ID audio.putString("groupId", ""); audio.putString("label", "Audio"); audio.putString("kind", "audioinput"); @@ -344,7 +419,7 @@ void createStream(MediaStreamTrack[] tracks, BiConsumer audioProcessingFactoryFactory; + // Default audio device identifier used when no specific device is requested + public String defaultAudioDeviceId = "audio-1"; + public Loggable injectableLogger; public Logging.Severity loggingSeverity; public String fieldTrials; diff --git a/ios/RCTWebRTC/WebRTCModule.m b/ios/RCTWebRTC/WebRTCModule.m index 0672802b6..a4297253d 100644 --- a/ios/RCTWebRTC/WebRTCModule.m +++ b/ios/RCTWebRTC/WebRTCModule.m @@ -10,6 +10,10 @@ #import "WebRTCModule+RTCPeerConnection.h" #import "WebRTCModule.h" #import "WebRTCModuleOptions.h" +#import + +// Define fork version constant - increment this when making changes to the fork +static NSString * const FORK_VERSION = @"fork-version-1"; @interface WebRTCModule () @end @@ -141,4 +145,21 @@ - (dispatch_queue_t)methodQueue { ]; } +/** + * Get the default audio device ID + */ +RCT_EXPORT_METHOD(getDefaultAudioDeviceId:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + NSString *deviceId = [WebRTCModuleOptions sharedInstance].defaultAudioDeviceId ?: @""; + resolve(deviceId); +} + +/** + * Get the version of this custom fork + */ +RCT_EXPORT_METHOD(getForkVersion:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + resolve(FORK_VERSION); +} + @end diff --git a/src/AudioDeviceModule.ts b/src/AudioDeviceModule.ts new file mode 100644 index 000000000..358eef54a --- /dev/null +++ b/src/AudioDeviceModule.ts @@ -0,0 +1,25 @@ +import { NativeModules } from 'react-native' +const { WebRTCModule } = NativeModules + +/** + * Set the default audio device ID to use when no specific device is requested. + * This allows applications to control which audio device is used by default. + * + * @param deviceId - The device ID to use as default (e.g., "audio-1", "expo-av-audio", etc.) + */ +export function setDefaultAudioDeviceId(deviceId: string): void { + if (typeof deviceId !== 'string' || !deviceId.trim()) { + throw new TypeError('deviceId must be a non-empty string') + } + + WebRTCModule.setDefaultAudioDeviceId(deviceId) +} + +/** + * Get the current default audio device ID. + * + * @returns A promise that resolves to the current default audio device ID + */ +export function getDefaultAudioDeviceId(): Promise { + return WebRTCModule.getDefaultAudioDeviceId() +} diff --git a/src/index.ts b/src/index.ts index fc8908959..38aae25c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,96 +1,115 @@ -import { NativeModules, Platform } from 'react-native'; -const { WebRTCModule } = NativeModules; +import { NativeModules, Platform } from 'react-native' +const { WebRTCModule } = NativeModules if (WebRTCModule === null) { - throw new Error(`WebRTC native module not found.\n${Platform.OS === 'ios' ? - 'Try executing the "pod install" command inside your projects ios folder.' : - 'Try executing the "npm install" command inside your projects folder.' - }`); + throw new Error( + `WebRTC native module not found.\n${ + Platform.OS === 'ios' + ? 'Try executing the "pod install" command inside your projects ios folder.' + : 'Try executing the "npm install" command inside your projects folder.' + }`, + ) } -import { setupNativeEvents } from './EventEmitter'; -import Logger from './Logger'; -import mediaDevices from './MediaDevices'; -import MediaStream from './MediaStream'; -import MediaStreamTrack, { type MediaTrackSettings } from './MediaStreamTrack'; -import MediaStreamTrackEvent from './MediaStreamTrackEvent'; -import permissions from './Permissions'; -import RTCAudioSession from './RTCAudioSession'; -import RTCErrorEvent from './RTCErrorEvent'; -import RTCFrameCryptor, { RTCFrameCryptorState } from './RTCFrameCryptor'; -import RTCFrameCryptorFactory, { RTCFrameCryptorAlgorithm, RTCKeyProviderOptions } from './RTCFrameCryptorFactory'; -import RTCIceCandidate from './RTCIceCandidate'; -import RTCKeyProvider from './RTCKeyProvider'; -import RTCPIPView, { startIOSPIP, stopIOSPIP } from './RTCPIPView'; -import RTCPeerConnection from './RTCPeerConnection'; -import RTCRtpReceiver from './RTCRtpReceiver'; -import RTCRtpSender from './RTCRtpSender'; -import RTCRtpTransceiver from './RTCRtpTransceiver'; -import RTCSessionDescription from './RTCSessionDescription'; -import RTCView, { type RTCVideoViewProps, type RTCIOSPIPOptions } from './RTCView'; -import ScreenCapturePickerView from './ScreenCapturePickerView'; +import { + getDefaultAudioDeviceId, + setDefaultAudioDeviceId, +} from './AudioDeviceModule' +import { setupNativeEvents } from './EventEmitter' +import Logger from './Logger' +import mediaDevices from './MediaDevices' +import MediaStream from './MediaStream' +import MediaStreamTrack, { type MediaTrackSettings } from './MediaStreamTrack' +import MediaStreamTrackEvent from './MediaStreamTrackEvent' +import permissions from './Permissions' +import RTCAudioSession from './RTCAudioSession' +import RTCErrorEvent from './RTCErrorEvent' +import RTCFrameCryptor, { RTCFrameCryptorState } from './RTCFrameCryptor' +import RTCFrameCryptorFactory, { + RTCFrameCryptorAlgorithm, + RTCKeyProviderOptions, +} from './RTCFrameCryptorFactory' +import RTCIceCandidate from './RTCIceCandidate' +import RTCKeyProvider from './RTCKeyProvider' +import RTCPIPView, { startIOSPIP, stopIOSPIP } from './RTCPIPView' +import RTCPeerConnection from './RTCPeerConnection' +import RTCRtpReceiver from './RTCRtpReceiver' +import RTCRtpSender from './RTCRtpSender' +import RTCRtpTransceiver from './RTCRtpTransceiver' +import RTCSessionDescription from './RTCSessionDescription' +import RTCView, { + type RTCIOSPIPOptions, + type RTCVideoViewProps, +} from './RTCView' +import ScreenCapturePickerView from './ScreenCapturePickerView' -Logger.enable(`${Logger.ROOT_PREFIX}:*`); +Logger.enable(`${Logger.ROOT_PREFIX}:*`) // Add listeners for the native events early, since they are added asynchronously. -setupNativeEvents(); +setupNativeEvents() export { - RTCIceCandidate, - RTCPeerConnection, - RTCSessionDescription, - RTCView, - RTCPIPView, - ScreenCapturePickerView, - RTCRtpTransceiver, - RTCRtpReceiver, - RTCRtpSender, - RTCErrorEvent, - RTCAudioSession, - RTCFrameCryptor, - RTCFrameCryptorAlgorithm, - RTCFrameCryptorState, - RTCFrameCryptorFactory, - RTCKeyProvider, - RTCKeyProviderOptions, - MediaStream, - MediaStreamTrack, - type MediaTrackSettings, - type RTCVideoViewProps, - type RTCIOSPIPOptions, - mediaDevices, - permissions, - registerGlobals, - startIOSPIP, - stopIOSPIP, -}; + // Audio device management + getDefaultAudioDeviceId, + mediaDevices, + MediaStream, + MediaStreamTrack, + permissions, + registerGlobals, + RTCAudioSession, + RTCErrorEvent, + RTCFrameCryptor, + RTCFrameCryptorAlgorithm, + RTCFrameCryptorFactory, + RTCFrameCryptorState, + RTCIceCandidate, + RTCKeyProvider, + RTCKeyProviderOptions, + RTCPeerConnection, + RTCPIPView, + RTCRtpReceiver, + RTCRtpSender, + RTCRtpTransceiver, + RTCSessionDescription, + RTCView, + ScreenCapturePickerView, + setDefaultAudioDeviceId, + startIOSPIP, + stopIOSPIP, + type MediaTrackSettings, + type RTCIOSPIPOptions, + type RTCVideoViewProps, +} -declare const global: any; +declare const global: any function registerGlobals(): void { - // Should not happen. React Native has a global navigator object. - if (typeof global.navigator !== 'object') { - throw new Error('navigator is not an object'); - } + // Should not happen. React Native has a global navigator object. + if (typeof global.navigator !== 'object') { + throw new Error('navigator is not an object') + } - if (!global.navigator.mediaDevices) { - global.navigator.mediaDevices = {}; - } + if (!global.navigator.mediaDevices) { + global.navigator.mediaDevices = {} + } - global.navigator.mediaDevices.getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); - global.navigator.mediaDevices.getDisplayMedia = mediaDevices.getDisplayMedia.bind(mediaDevices); - global.navigator.mediaDevices.enumerateDevices = mediaDevices.enumerateDevices.bind(mediaDevices); + global.navigator.mediaDevices.getUserMedia = + mediaDevices.getUserMedia.bind(mediaDevices) + global.navigator.mediaDevices.getDisplayMedia = + mediaDevices.getDisplayMedia.bind(mediaDevices) + global.navigator.mediaDevices.enumerateDevices = + mediaDevices.enumerateDevices.bind(mediaDevices) - global.RTCIceCandidate = RTCIceCandidate; - global.RTCPeerConnection = RTCPeerConnection; - global.RTCRtpReceiver = RTCRtpReceiver; - global.RTCRtpSender = RTCRtpReceiver; - global.RTCSessionDescription = RTCSessionDescription; - global.MediaStream = MediaStream; - global.MediaStreamTrack = MediaStreamTrack; - global.MediaStreamTrackEvent = MediaStreamTrackEvent; - global.RTCRtpTransceiver = RTCRtpTransceiver; - global.RTCRtpReceiver = RTCRtpReceiver; - global.RTCRtpSender = RTCRtpSender; - global.RTCErrorEvent = RTCErrorEvent; + global.RTCIceCandidate = RTCIceCandidate + global.RTCPeerConnection = RTCPeerConnection + global.RTCRtpReceiver = RTCRtpReceiver + global.RTCRtpSender = RTCRtpReceiver + global.RTCSessionDescription = RTCSessionDescription + global.MediaStream = MediaStream + global.MediaStreamTrack = MediaStreamTrack + global.MediaStreamTrackEvent = MediaStreamTrackEvent + global.RTCRtpTransceiver = RTCRtpTransceiver + global.RTCRtpReceiver = RTCRtpReceiver + global.RTCRtpSender = RTCRtpSender + global.RTCErrorEvent = RTCErrorEvent }