From b3ee8fe127e017aff28b6e2c264c24f70f3ed59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Fri, 17 Jan 2025 11:40:28 +0100 Subject: [PATCH] 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. --- .../org/jitsi/meet/sdk/AudioModeModule.java | 39 ++++++++++++++++++- ios/sdk/src/AudioMode.m | 38 ++++++++++++++++++ ios/sdk/src/JitsiAudioSession.m | 8 +++- react/features/base/tracks/actions.any.ts | 12 ++++-- .../features/mobile/audio-mode/middleware.ts | 21 +++++++++- 5 files changed, 110 insertions(+), 8 deletions(-) diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java index 3888c5d9bc..95c1241af9 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java @@ -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; } diff --git a/ios/sdk/src/AudioMode.m b/ios/sdk/src/AudioMode.m index b35113b8e9..f5fc71d9e3 100644 --- a/ios/sdk/src/AudioMode.m +++ b/ios/sdk/src/AudioMode.m @@ -21,6 +21,7 @@ #import #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]; } diff --git a/ios/sdk/src/JitsiAudioSession.m b/ios/sdk/src/JitsiAudioSession.m index 049f13dbaf..d71c2554bd 100644 --- a/ios/sdk/src/JitsiAudioSession.m +++ b/ios/sdk/src/JitsiAudioSession.m @@ -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 diff --git a/react/features/base/tracks/actions.any.ts b/react/features/base/tracks/actions.any.ts index 65e7b2a0e6..e6a335bcb7 100644 --- a/react/features/base/tracks/actions.any.ts +++ b/react/features/base/tracks/actions.any.ts @@ -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 diff --git a/react/features/mobile/audio-mode/middleware.ts b/react/features/mobile/audio-mode/middleware.ts index 0be196e09f..a1b1675837 100644 --- a/react/features/mobile/audio-mode/middleware.ts +++ b/react/features/mobile/audio-mode/middleware.ts @@ -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 */