Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 12 additions & 2 deletions apps/common-app/src/demos/Record/Record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const Record: FC = () => {
resumeIconResourceName: 'resume',
color: 0xff6200,
});
}
};

const onStartRecording = useCallback(async () => {
if (state !== RecordingState.Idle) {
Expand All @@ -67,10 +67,20 @@ const Record: FC = () => {
setHasPermissions(true);
}

const success = await AudioManager.setAudioSessionActivity(true);
let success = false;

try {
success = await AudioManager.setAudioSessionActivity(true);
} catch (error) {
console.error(error);
Alert.alert('Error', 'Failed to activate audio session for recording.');
setState(RecordingState.Idle);
return;
}

if (!success) {
Alert.alert('Error', 'Failed to activate audio session for recording.');
setState(RecordingState.Idle);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,28 @@ - (dispatch_queue_t)methodQueue
resolve reject : (RCTPromiseRejectBlock)reject)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
auto success = [self.audioSessionManager setActive:enabled];
NSError *error = nil;

auto success = [self.audioSessionManager setActive:enabled error:&error];

if (!success) {
NSDictionary *meta = @{
@"nativeCode" : @(error.code),
@"nativeDomain" : error.domain ?: @"",
@"nativeDesc" : error.description ?: @"",
};

NSError *jsError =
[NSError errorWithDomain:@"AudioAPIModule"
code:error.code
userInfo:@{
NSLocalizedDescriptionKey : @"Failed to set audio session active state",
@"meta" : meta,
}];

reject(@"E_AUDIO_SESSION", @"Failed to set audio session active state", jsError);
return;
}

resolve(@(success));
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ - (void)cleanup
self.sourceFormats = nil;
self.inputNode = nil;

[self.sessionManager setActive:false];
[self.sessionManager setActive:false error:nil];
self.sessionManager = nil;
}

Expand Down Expand Up @@ -182,7 +182,9 @@ - (bool)startEngine
return true;
}

if (![self.sessionManager setActive:true]) {
if (![self.sessionManager setActive:true error:&error]) {
// TODO: return user facing error
NSLog(@"Error while activating audio session: %@", [error debugDescription]);
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
options:(NSArray *)options
allowHaptics:(BOOL)allowHaptics;

- (bool)setActive:(bool)active;
- (bool)setActive:(bool)active error:(NSError **)error;
- (void)markInactive;
- (void)disableSessionManagement;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,14 @@ - (void)setAudioSessionOptions:(NSString *)categoryStr
}
}

- (bool)setActive:(bool)active
- (bool)setActive:(bool)active error:(NSError **)error
{
bool success = false;

if (!self.shouldManageSession) {
return true;
}

NSError *error = nil;
bool success = false;

if (self.isActive == active) {
return true;
}
Expand All @@ -130,18 +129,12 @@ - (bool)setActive:(bool)active
}
}

success = [self.audioSession setActive:active error:&error];
success = [self.audioSession setActive:active error:error];

if (success) {
self.isActive = active;
}

if (error != nil) {
NSLog(@"[AudioSessionManager] setting session as %@ failed", active ? @"ACTIVE" : @"INACTIVE");
} else {
NSLog(@"[AudioSessionManager] session is %@", active ? @"ACTIVE" : @"INACTIVE");
}

return success;
}

Expand Down
22 changes: 15 additions & 7 deletions packages/react-native-audio-api/src/system/AudioManager.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AudioEventEmitter, AudioEventSubscription } from '../events';
import { SystemEventCallback, SystemEventName } from '../events/types';
import { NativeAudioAPIModule } from '../specs';
import { parseNativeError } from './errors';
import {
SessionOptions,
PermissionStatus,
AudioDevicesInfo,
IAudioManager,
PermissionStatus,
SessionOptions,
} from './types';
import { SystemEventName, SystemEventCallback } from '../events/types';
import { AudioEventEmitter, AudioEventSubscription } from '../events';
import { NativeAudioAPIModule } from '../specs';

class AudioManager implements IAudioManager {
private readonly audioEventEmitter: AudioEventEmitter;
Expand All @@ -18,8 +19,15 @@ class AudioManager implements IAudioManager {
return NativeAudioAPIModule.getDevicePreferredSampleRate();
}

setAudioSessionActivity(enabled: boolean): Promise<boolean> {
return NativeAudioAPIModule.setAudioSessionActivity(enabled);
async setAudioSessionActivity(enabled: boolean): Promise<boolean> {
try {
const success =
await NativeAudioAPIModule.setAudioSessionActivity(enabled);

return success;
} catch (error) {
throw parseNativeError(error);
}
}

setAudioSessionOptions(options: SessionOptions) {
Expand Down
111 changes: 111 additions & 0 deletions packages/react-native-audio-api/src/system/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { AudioApiError } from '../errors';

export interface NativeActivationErrorMetadata {
nativeDesc: string;
nativeCode: number;
nativeDomain: string;
}

interface ErrorWithUserInfo {
userInfo?: {
meta?: Record<string, unknown>;
};
}

interface ErrorWithNativeError {
nativeError?: {
userInfo: {
meta?: Record<string, unknown>;
};
};
}

interface ErrorWithDetails {
details?: {
meta?: Record<string, unknown>;
};
}

function parseNativeCode(code: number): string {
switch (code) {
case 0:
return 'NoError';
case -50:
return 'BadParam';
case 1836282486:
return 'MediaServicesFailed';
case 560030580:
return 'IsBusy';
case 560161140:
return 'IncompatibleCategory';
case 560557684:
return 'CannotInterruptOthers';
case 1701737535:
return 'MissingEntitlement';
case 1936290409:
return 'SiriIsRecording';
case 561015905:
return 'CannotStartPlaying';
case 561145187:
return 'CannotStartRecording';
case 561017449:
return 'InsufficientPriority';
case 561145203:
return 'ResourceNotAvailable';
case 2003329396:
return 'Unspecified';
case 561210739:
return 'ExpiredSession';
case 1768841571:
return 'SessionNotActive';
default:
return 'NoError';
}
}

export class SessionActivationError extends AudioApiError {
nativeErrorInfo?: NativeActivationErrorMetadata;

constructor(nativeErrorInfo?: NativeActivationErrorMetadata) {
if (!nativeErrorInfo) {
super('Failed to activate audio session with unknown error');
this.name = 'SessionActivationError';
return;
}

const codeName = parseNativeCode(nativeErrorInfo.nativeCode);

super(
`[${codeName}] Failed to activate audio session, code: ${nativeErrorInfo.nativeCode}`
);

this.name = 'SessionActivationError';
this.nativeErrorInfo = nativeErrorInfo;
}
}

export function parseNativeError(error: unknown): SessionActivationError {
const errorMeta =
(error as ErrorWithUserInfo)?.userInfo?.meta ??
(error as ErrorWithNativeError)?.nativeError?.userInfo?.meta ??
(error as ErrorWithDetails)?.details?.meta;

console.log('Parsed error meta:', errorMeta);

if (!errorMeta || typeof errorMeta !== 'object') {
return new SessionActivationError();
}

const { nativeCode, nativeDesc, nativeDomain } =
errorMeta as unknown as NativeActivationErrorMetadata;

if (isNaN(nativeCode) || !nativeDesc || !nativeDomain) {
return new SessionActivationError();
}

return new SessionActivationError({
nativeCode,
nativeDesc,
nativeDomain,
});
}
2 changes: 1 addition & 1 deletion packages/react-native-audio-api/src/system/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SystemEventName, SystemEventCallback } from '../events/types';
import type { AudioEventSubscription } from '../events';
import type { SystemEventCallback, SystemEventName } from '../events/types';

export type IOSCategory =
| 'record'
Expand Down