feat(rn) implement startSilent

Technically, on Android, the audio mode is configured but no audio is
played. Since the configured audio mode matches what we expect from a
calling app (what we support to coexist with) this is enough to not
create audio disruptions.
This commit is contained in:
Saúl Ibarra Corretgé
2025-01-17 11:40:28 +01:00
committed by Saúl Ibarra Corretgé
parent 95a6001a6f
commit b3ee8fe127
5 changed files with 110 additions and 8 deletions

View File

@@ -136,6 +136,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
private String userSelectedDevice;
/**
* Whether or not audio is disabled.
*/
private boolean audioDisabled;
/**
* Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application.
@@ -236,6 +241,12 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioDeviceHandler.stop();
}
audioDeviceHandler = null;
if (audioDisabled) {
return;
}
if (useConnectionService()) {
audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager);
} else {
@@ -278,6 +289,27 @@ class AudioModeModule extends ReactContextBaseJavaModule {
});
}
@ReactMethod
public void setDisabled(final boolean disabled, final Promise promise) {
if (audioDisabled == disabled) {
promise.resolve(null);
return;
}
JitsiMeetLogger.i(TAG + " audio disabled: " + disabled);
audioDisabled = disabled;
setAudioDeviceHandler();
if (disabled) {
mode = -1;
availableDevices.clear();
resetSelectedDevice();
}
promise.resolve(null);
}
/**
* Public method to set the current audio mode.
*
@@ -287,7 +319,12 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void setMode(final int mode, final Promise promise) {
if (mode != DEFAULT && mode != AUDIO_CALL && mode != VIDEO_CALL) {
if (audioDisabled) {
promise.resolve(null);
return;
}
if (mode < DEFAULT || mode > VIDEO_CALL) {
promise.reject("setMode", "Invalid audio mode " + mode);
return;
}

View File

@@ -21,6 +21,7 @@
#import <WebRTC/WebRTC.h>
#import "JitsiAudioSession+Private.h"
#import "callkit/JMCallKitProxy.h"
// Audio mode
@@ -54,6 +55,7 @@ static NSString * const kDeviceTypeUnknown = @"UNKNOWN";
RTCAudioSessionConfiguration *audioCallConfig;
RTCAudioSessionConfiguration *videoCallConfig;
RTCAudioSessionConfiguration *earpieceConfig;
BOOL audioDisabled;
BOOL forceSpeaker;
BOOL forceEarpiece;
BOOL isSpeakerOn;
@@ -146,9 +148,36 @@ RCT_EXPORT_MODULE();
#pragma mark - Exported methods
RCT_EXPORT_METHOD(setDisabled:(BOOL)disabled
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
if (audioDisabled == disabled) {
resolve(nil);
return;
}
RCTLogInfo(@"[AudioMode] audio disabled: %d", disabled);
audioDisabled = disabled;
JMCallKitProxy.enabled = !disabled;
RTCAudioSession *session = JitsiAudioSession.rtcAudioSession;
if (disabled) {
[session removeDelegate:self];
} else {
[session addDelegate:self];
}
session.useManualAudio = disabled;
}
RCT_EXPORT_METHOD(setMode:(int)mode
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
if (audioDisabled) {
resolve(nil);
return;
}
RTCAudioSessionConfiguration *config = [self configForMode:mode];
NSError *error;
@@ -177,6 +206,11 @@ RCT_EXPORT_METHOD(setMode:(int)mode
RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
if (audioDisabled) {
resolve(nil);
return;
}
RCTLogInfo(@"[AudioMode] Selected device: %@", device);
RTCAudioSession *session = JitsiAudioSession.rtcAudioSession;
@@ -239,6 +273,10 @@ RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
}
RCT_EXPORT_METHOD(updateDeviceList) {
if (audioDisabled) {
return;
}
[self notifyDevicesChanged];
}

View File

@@ -24,11 +24,15 @@
}
+ (void)activateWithAudioSession:(AVAudioSession *)session {
[self.rtcAudioSession audioSessionDidActivate:session];
if (!self.rtcAudioSession.useManualAudio) {
[self.rtcAudioSession audioSessionDidActivate:session];
}
}
+ (void)deactivateWithAudioSession:(AVAudioSession *)session {
[self.rtcAudioSession audioSessionDidDeactivate:session];
if (!self.rtcAudioSession.useManualAudio) {
[self.rtcAudioSession audioSessionDidDeactivate:session];
}
}
@end

View File

@@ -86,12 +86,16 @@ export function createDesiredLocalTracks(...desiredTypes: any) {
dispatch(destroyLocalDesktopTrackIfExists());
if (desiredTypes.length === 0) {
const { startSilent } = state['features/base/config'];
const { video } = state['features/base/media'];
// XXX: Always create the audio track early, even if it will be muted.
// This fixes a timing issue when adding the track to the conference which
// manifests primarily on iOS 15.
desiredTypes.push(MEDIA_TYPE.AUDIO);
if (!startSilent) {
// Always create the audio track early, even if it will be muted.
// This fixes a timing issue when adding the track to the conference which
// manifests primarily on iOS 15.
// Unless we are silent, of course.
desiredTypes.push(MEDIA_TYPE.AUDIO);
}
// XXX When the app is coming into the foreground from the
// background in order to handle a URL, it may realize the new

View File

@@ -10,9 +10,11 @@ import {
CONFERENCE_LEFT
} from '../../base/conference/actionTypes';
import { getCurrentConference } from '../../base/conference/functions';
import { SET_CONFIG } from '../../base/config/actionTypes';
import { AUDIO_FOCUS_DISABLED } from '../../base/flags/constants';
import { getFeatureFlag } from '../../base/flags/functions';
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
import { parseURIString } from '../../base/util/uri';
import { _SET_AUDIOMODE_DEVICES, _SET_AUDIOMODE_SUBSCRIPTIONS } from './actionTypes';
import logger from './logger';
@@ -44,7 +46,7 @@ MiddlewareRegistry.register(store => next => action => {
}
case APP_WILL_MOUNT:
_appWillMount(store);
case CONFERENCE_FAILED: // eslint-disable-line no-fallthrough
case CONFERENCE_FAILED:
case CONFERENCE_LEFT:
/*
@@ -60,6 +62,23 @@ MiddlewareRegistry.register(store => next => action => {
case SET_AUDIO_ONLY:
return _updateAudioMode(store, next, action);
case SET_CONFIG: {
const { locationURL } = store.getState()['features/base/connection'];
const location = parseURIString(locationURL?.href ?? '');
/**
* Don't touch the current value if there is no room in the URL. This
* avoids audio cutting off for a moment right after the user leaves
* a meeting. The next meeting join will set it to the right value.
*/
if (location.room) {
const { startSilent } = action.config;
AudioMode.setDisabled?.(Boolean(startSilent));
}
break;
}
}
/* eslint-enable no-fallthrough */