Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ class GetUserMediaImpl {
private final Map<String, TrackPrivate> tracks = new HashMap<>();

private final WebRTCModule webRTCModule;
private String audioDeviceId;

private Promise displayMediaPromise;
private Intent mediaProjectionPermissionResultData;

GetUserMediaImpl(WebRTCModule webRTCModule, ReactApplicationContext reactContext) {
this.webRTCModule = webRTCModule;
this.reactContext = reactContext;
this.audioDeviceId = WebRTCModuleOptions.getInstance().defaultAudioDeviceId;

reactContext.addActivityEventListener(new BaseActivityEventListener() {
@Override
Expand Down Expand Up @@ -87,6 +89,15 @@ private AudioTrack createAudioTrack(ReadableMap constraints) {

Log.d(TAG, "getUserMedia(audio): " + audioConstraintsMap);

// Check if a specific audio device ID was requested
if (audioConstraintsMap != null && audioConstraintsMap.hasKey("deviceId")) {
Object deviceId = audioConstraintsMap.getString("deviceId");
if (deviceId != null) {
Log.d(TAG, "Using specific audio device ID: " + deviceId);
this.audioDeviceId = deviceId.toString();
}
}

String id = UUID.randomUUID().toString();
PeerConnectionFactory pcFactory = webRTCModule.mFactory;
MediaConstraints peerConstraints = webRTCModule.constraintsForOptions(audioConstraintsMap);
Expand Down Expand Up @@ -157,7 +168,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");
Expand Down Expand Up @@ -344,7 +355,7 @@ void createStream(MediaStreamTrack[] tracks, BiConsumer<String, ArrayList<Writab

if (track instanceof AudioTrack) {
WritableMap settings = Arguments.createMap();
settings.putString("deviceId", "audio-1");
settings.putString("deviceId", audioDeviceId); // Use the configured/current audio device ID
settings.putString("groupId", "");
trackInfo.putMap("settings", settings);
}
Expand Down Expand Up @@ -433,6 +444,18 @@ void setVideoEffect(String trackId, String name) {
}
}

/**
* Set the audio device ID to use for future getUserMedia calls.
*
* @param deviceId The device ID to use
*/
void setAudioDeviceId(String deviceId) {
if (deviceId != null && !deviceId.isEmpty()) {
Log.d(TAG, "Setting audio device ID to: " + deviceId);
this.audioDeviceId = deviceId;
}
}

/**
* Application/library-specific private members of local
* {@code MediaStreamTrack}s created by {@code GetUserMediaImpl}.
Expand Down
30 changes: 30 additions & 0 deletions android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -1544,4 +1544,34 @@ public void addListener(String eventName) {
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}

/**
* 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.)
*/
@ReactMethod
public void setDefaultAudioDeviceId(String deviceId) {
if (deviceId != null && !deviceId.isEmpty()) {
Log.d(TAG, "Setting default audio device ID to: " + deviceId);
WebRTCModuleOptions.getInstance().defaultAudioDeviceId = deviceId;

// Update current instance
if (getUserMediaImpl != null) {
getUserMediaImpl.setAudioDeviceId(deviceId);
}
}
}

/**
* Get the current default audio device ID.
*
* @param promise Promise to resolve with the current default audio device ID
*/
@ReactMethod
public void getDefaultAudioDeviceId(Promise promise) {
String deviceId = WebRTCModuleOptions.getInstance().defaultAudioDeviceId;
promise.resolve(deviceId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public class WebRTCModuleOptions {
public AudioDeviceModule audioDeviceModule;
public Callable<AudioProcessingFactory> 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;
Expand Down
25 changes: 25 additions & 0 deletions src/AudioDeviceModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NativeModules } from 'react-native'

Check failure on line 1 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Missing semicolon
const { WebRTCModule } = NativeModules

Check failure on line 2 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Missing semicolon

/**
* 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()) {

Check failure on line 11 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
throw new TypeError('deviceId must be a non-empty string')

Check failure on line 12 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 8 spaces but found 4

Check failure on line 12 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Missing semicolon
}

Check failure on line 13 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2

WebRTCModule.setDefaultAudioDeviceId(deviceId)

Check failure on line 15 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2

Check failure on line 15 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Missing semicolon
}

/**
* Get the current default audio device ID.
*
* @returns A promise that resolves to the current default audio device ID
*/
export function getDefaultAudioDeviceId(): Promise<string> {
return WebRTCModule.getDefaultAudioDeviceId()

Check failure on line 24 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2

Check failure on line 24 in src/AudioDeviceModule.ts

View workflow job for this annotation

GitHub Actions / Lint

Missing semicolon
}
179 changes: 99 additions & 80 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll undo all of the reformatting changes from my editor. sorry about that 😅

}