Skip to content

Commit a5f1d96

Browse files
authored
Fix/return session activation errors (#891)
* fix: return session activation/deactivation errors from session manager * fix: formatter
1 parent 0166bcd commit a5f1d96

File tree

8 files changed

+170
-25
lines changed

8 files changed

+170
-25
lines changed

apps/common-app/src/demos/Record/Record.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const Record: FC = () => {
4747
resumeIconResourceName: 'resume',
4848
color: 0xff6200,
4949
});
50-
}
50+
};
5151

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

70-
const success = await AudioManager.setAudioSessionActivity(true);
70+
let success = false;
71+
72+
try {
73+
success = await AudioManager.setAudioSessionActivity(true);
74+
} catch (error) {
75+
console.error(error);
76+
Alert.alert('Error', 'Failed to activate audio session for recording.');
77+
setState(RecordingState.Idle);
78+
return;
79+
}
7180

7281
if (!success) {
7382
Alert.alert('Error', 'Failed to activate audio session for recording.');
83+
setState(RecordingState.Idle);
7484
return;
7585
}
7686

packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.mm

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,28 @@ - (dispatch_queue_t)methodQueue
123123
resolve reject : (RCTPromiseRejectBlock)reject)
124124
{
125125
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
126-
auto success = [self.audioSessionManager setActive:enabled];
126+
NSError *error = nil;
127+
128+
auto success = [self.audioSessionManager setActive:enabled error:&error];
129+
130+
if (!success) {
131+
NSDictionary *meta = @{
132+
@"nativeCode" : @(error.code),
133+
@"nativeDomain" : error.domain ?: @"",
134+
@"nativeDesc" : error.description ?: @"",
135+
};
136+
137+
NSError *jsError =
138+
[NSError errorWithDomain:@"AudioAPIModule"
139+
code:error.code
140+
userInfo:@{
141+
NSLocalizedDescriptionKey : @"Failed to set audio session active state",
142+
@"meta" : meta,
143+
}];
144+
145+
reject(@"E_AUDIO_SESSION", @"Failed to set audio session active state", jsError);
146+
return;
147+
}
127148

128149
resolve(@(success));
129150
});

packages/react-native-audio-api/ios/audioapi/ios/system/AudioEngine.mm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ - (void)cleanup
3838
self.sourceFormats = nil;
3939
self.inputNode = nil;
4040

41-
[self.sessionManager setActive:false];
41+
[self.sessionManager setActive:false error:nil];
4242
self.sessionManager = nil;
4343
}
4444

@@ -182,7 +182,9 @@ - (bool)startEngine
182182
return true;
183183
}
184184

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

packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
options:(NSArray *)options
2929
allowHaptics:(BOOL)allowHaptics;
3030

31-
- (bool)setActive:(bool)active;
31+
- (bool)setActive:(bool)active error:(NSError **)error;
3232
- (void)markInactive;
3333
- (void)disableSessionManagement;
3434

packages/react-native-audio-api/ios/audioapi/ios/system/AudioSessionManager.mm

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,14 @@ - (void)setAudioSessionOptions:(NSString *)categoryStr
109109
}
110110
}
111111

112-
- (bool)setActive:(bool)active
112+
- (bool)setActive:(bool)active error:(NSError **)error
113113
{
114+
bool success = false;
115+
114116
if (!self.shouldManageSession) {
115117
return true;
116118
}
117119

118-
NSError *error = nil;
119-
bool success = false;
120-
121120
if (self.isActive == active) {
122121
return true;
123122
}
@@ -130,18 +129,12 @@ - (bool)setActive:(bool)active
130129
}
131130
}
132131

133-
success = [self.audioSession setActive:active error:&error];
132+
success = [self.audioSession setActive:active error:error];
134133

135134
if (success) {
136135
self.isActive = active;
137136
}
138137

139-
if (error != nil) {
140-
NSLog(@"[AudioSessionManager] setting session as %@ failed", active ? @"ACTIVE" : @"INACTIVE");
141-
} else {
142-
NSLog(@"[AudioSessionManager] session is %@", active ? @"ACTIVE" : @"INACTIVE");
143-
}
144-
145138
return success;
146139
}
147140

packages/react-native-audio-api/src/system/AudioManager.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
import { AudioEventEmitter, AudioEventSubscription } from '../events';
2+
import { SystemEventCallback, SystemEventName } from '../events/types';
3+
import { NativeAudioAPIModule } from '../specs';
4+
import { parseNativeError } from './errors';
15
import {
2-
SessionOptions,
3-
PermissionStatus,
46
AudioDevicesInfo,
57
IAudioManager,
8+
PermissionStatus,
9+
SessionOptions,
610
} from './types';
7-
import { SystemEventName, SystemEventCallback } from '../events/types';
8-
import { AudioEventEmitter, AudioEventSubscription } from '../events';
9-
import { NativeAudioAPIModule } from '../specs';
1011

1112
class AudioManager implements IAudioManager {
1213
private readonly audioEventEmitter: AudioEventEmitter;
@@ -18,8 +19,15 @@ class AudioManager implements IAudioManager {
1819
return NativeAudioAPIModule.getDevicePreferredSampleRate();
1920
}
2021

21-
setAudioSessionActivity(enabled: boolean): Promise<boolean> {
22-
return NativeAudioAPIModule.setAudioSessionActivity(enabled);
22+
async setAudioSessionActivity(enabled: boolean): Promise<boolean> {
23+
try {
24+
const success =
25+
await NativeAudioAPIModule.setAudioSessionActivity(enabled);
26+
27+
return success;
28+
} catch (error) {
29+
throw parseNativeError(error);
30+
}
2331
}
2432

2533
setAudioSessionOptions(options: SessionOptions) {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { AudioApiError } from '../errors';
2+
3+
export interface NativeActivationErrorMetadata {
4+
nativeDesc: string;
5+
nativeCode: number;
6+
nativeDomain: string;
7+
}
8+
9+
interface ErrorWithUserInfo {
10+
userInfo?: {
11+
meta?: Record<string, unknown>;
12+
};
13+
}
14+
15+
interface ErrorWithNativeError {
16+
nativeError?: {
17+
userInfo: {
18+
meta?: Record<string, unknown>;
19+
};
20+
};
21+
}
22+
23+
interface ErrorWithDetails {
24+
details?: {
25+
meta?: Record<string, unknown>;
26+
};
27+
}
28+
29+
function parseNativeCode(code: number): string {
30+
switch (code) {
31+
case 0:
32+
return 'NoError';
33+
case -50:
34+
return 'BadParam';
35+
case 1836282486:
36+
return 'MediaServicesFailed';
37+
case 560030580:
38+
return 'IsBusy';
39+
case 560161140:
40+
return 'IncompatibleCategory';
41+
case 560557684:
42+
return 'CannotInterruptOthers';
43+
case 1701737535:
44+
return 'MissingEntitlement';
45+
case 1936290409:
46+
return 'SiriIsRecording';
47+
case 561015905:
48+
return 'CannotStartPlaying';
49+
case 561145187:
50+
return 'CannotStartRecording';
51+
case 561017449:
52+
return 'InsufficientPriority';
53+
case 561145203:
54+
return 'ResourceNotAvailable';
55+
case 2003329396:
56+
return 'Unspecified';
57+
case 561210739:
58+
return 'ExpiredSession';
59+
case 1768841571:
60+
return 'SessionNotActive';
61+
default:
62+
return 'NoError';
63+
}
64+
}
65+
66+
export class SessionActivationError extends AudioApiError {
67+
nativeErrorInfo?: NativeActivationErrorMetadata;
68+
69+
constructor(nativeErrorInfo?: NativeActivationErrorMetadata) {
70+
if (!nativeErrorInfo) {
71+
super('Failed to activate audio session with unknown error');
72+
this.name = 'SessionActivationError';
73+
return;
74+
}
75+
76+
const codeName = parseNativeCode(nativeErrorInfo.nativeCode);
77+
78+
super(
79+
`[${codeName}] Failed to activate audio session, code: ${nativeErrorInfo.nativeCode}`
80+
);
81+
82+
this.name = 'SessionActivationError';
83+
this.nativeErrorInfo = nativeErrorInfo;
84+
}
85+
}
86+
87+
export function parseNativeError(error: unknown): SessionActivationError {
88+
const errorMeta =
89+
(error as ErrorWithUserInfo)?.userInfo?.meta ??
90+
(error as ErrorWithNativeError)?.nativeError?.userInfo?.meta ??
91+
(error as ErrorWithDetails)?.details?.meta;
92+
93+
console.log('Parsed error meta:', errorMeta);
94+
95+
if (!errorMeta || typeof errorMeta !== 'object') {
96+
return new SessionActivationError();
97+
}
98+
99+
const { nativeCode, nativeDesc, nativeDomain } =
100+
errorMeta as unknown as NativeActivationErrorMetadata;
101+
102+
if (isNaN(nativeCode) || !nativeDesc || !nativeDomain) {
103+
return new SessionActivationError();
104+
}
105+
106+
return new SessionActivationError({
107+
nativeCode,
108+
nativeDesc,
109+
nativeDomain,
110+
});
111+
}

packages/react-native-audio-api/src/system/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { SystemEventName, SystemEventCallback } from '../events/types';
21
import type { AudioEventSubscription } from '../events';
2+
import type { SystemEventCallback, SystemEventName } from '../events/types';
33

44
export type IOSCategory =
55
| 'record'

0 commit comments

Comments
 (0)