Skip to content

Commit 44369fc

Browse files
michalsekMaciej Makowski
andauthored
Add devices info to session manager (#551)
* feat: add devices info to session manager * feat: add devices info to session manager * feat: android devices wanna be * fix: android impl * ci: yarn format * docs: added getDevicesInfo to docs --------- Co-authored-by: Maciej Makowski <maciej.makowski2608@gmail.com>
1 parent 5656a2d commit 44369fc

File tree

11 files changed

+150
-4
lines changed

11 files changed

+150
-4
lines changed

apps/common-app/src/examples/AudioFile/AudioFile.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const AudioFile: FC = () => {
1818
await AudioPlayer.play();
1919

2020
AudioManager.observeAudioInterruptions(true);
21+
22+
AudioManager.getDevicesInfo().then(console.log);
2123
}
2224

2325
setIsPlaying((prev) => !prev);

packages/audiodocs/docs/system/audio-manager.mdx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Optional, ReadOnly, OnlyiOS } from '@site/src/components/Badges';
66

77
# AudioManager
88

9-
The `AudioManager` is a layer of an abstraction between user and a system.
9+
The `AudioManager` is a layer of an abstraction between user and a system.
1010
It provides a set of system-specific functions that are invoked directly in native code, by related system.
1111

1212
## Example
@@ -62,6 +62,7 @@ function App() {
6262
}
6363
);
6464

65+
AudioManager.getDevicesInfo().then(console.log);
6566

6667
return () => {
6768
remotePlaySubscription?.remove();
@@ -167,6 +168,12 @@ Allows to check if permissions were previously granted.
167168

168169
#### Returns promise of [`PermissionStatus`](/system/audio-manager#permissionstatus) type, which is resolved after receiving answer from the system.
169170

171+
### `getDevicesInfo`
172+
173+
Allows to check currently used and available devices.
174+
175+
#### Returns promise of [`AudioDevicesInfo`](/system/audio-manager#audiodevicesinfo) type, which is resolved after receiving answer from the system.
176+
170177
## Remarks
171178

172179
### `LockScreenInfo`
@@ -195,7 +202,7 @@ interface LockScreenInfo extends BaseLockScreenInfo {
195202
```
196203
</details>
197204

198-
### `SessionOptions`
205+
### `SessionOptions`
199206

200207
<details>
201208
<summary>Type definitions</summary>
@@ -337,3 +344,24 @@ class AudioEventSubscription {
337344
type PermissionStatus = 'Undetermined' | 'Denied' | 'Granted';
338345
```
339346
</details>
347+
348+
### `AudioDevicesInfo`
349+
350+
<details>
351+
<summary>Type definitions</summary>
352+
```typescript
353+
export interface AudioDeviceInfo {
354+
name: string;
355+
category: string;
356+
}
357+
358+
export type AudioDeviceList = AudioDeviceInfo[];
359+
360+
export interface AudioDevicesInfo {
361+
availableInputs: AudioDeviceList;
362+
availableOutputs: AudioDeviceList;
363+
currentInputs: AudioDeviceList; // iOS only
364+
currentOutputs: AudioDeviceList; // iOS only
365+
}
366+
```
367+
</details>

packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,8 @@ class AudioAPIModule(
103103
override fun checkRecordingPermissions(promise: Promise) {
104104
promise.resolve(MediaSessionManager.checkRecordingPermissions())
105105
}
106+
107+
override fun getDevicesInfo(promise: Promise) {
108+
promise.resolve(MediaSessionManager.getDevicesInfo())
109+
}
106110
}

packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.content.Intent
99
import android.content.IntentFilter
1010
import android.content.ServiceConnection
1111
import android.content.pm.PackageManager
12+
import android.media.AudioDeviceInfo
1213
import android.media.AudioManager
1314
import android.os.Build
1415
import android.os.IBinder
@@ -17,6 +18,7 @@ import android.util.Log
1718
import androidx.annotation.RequiresApi
1819
import androidx.core.app.NotificationCompat
1920
import androidx.core.content.ContextCompat
21+
import com.facebook.react.bridge.Arguments
2022
import com.facebook.react.bridge.ReactApplicationContext
2123
import com.facebook.react.bridge.ReadableMap
2224
import com.facebook.react.modules.core.PermissionAwareActivity
@@ -186,4 +188,48 @@ object MediaSessionManager {
186188
mChannel.lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
187189
notificationManager.createNotificationChannel(mChannel)
188190
}
191+
192+
@RequiresApi(Build.VERSION_CODES.O)
193+
fun getDevicesInfo(): ReadableMap {
194+
val availableInputs = Arguments.createArray()
195+
val availableOutputs = Arguments.createArray()
196+
197+
for (inputDevice in this.audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {
198+
val deviceInfo = Arguments.createMap()
199+
deviceInfo.putString("name", inputDevice.productName.toString())
200+
deviceInfo.putString("type", parseDeviceType(inputDevice))
201+
202+
availableInputs.pushMap(deviceInfo)
203+
}
204+
205+
for (outputDevice in this.audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {
206+
val deviceInfo = Arguments.createMap()
207+
deviceInfo.putString("name", outputDevice.productName.toString())
208+
deviceInfo.putString("type", parseDeviceType(outputDevice))
209+
210+
availableOutputs.pushMap(deviceInfo)
211+
}
212+
213+
val devicesInfo = Arguments.createMap()
214+
215+
devicesInfo.putArray("currentInputs", Arguments.createArray())
216+
devicesInfo.putArray("currentOutputs", Arguments.createArray())
217+
devicesInfo.putArray("availableInputs", availableInputs)
218+
devicesInfo.putArray("availableOutputs", availableOutputs)
219+
220+
return devicesInfo
221+
}
222+
223+
@RequiresApi(Build.VERSION_CODES.O)
224+
fun parseDeviceType(device: AudioDeviceInfo): String =
225+
when (device.type) {
226+
AudioDeviceInfo.TYPE_BUILTIN_MIC -> "Built-in Mic"
227+
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "Built-in Earpiece"
228+
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "Built-in Speaker"
229+
AudioDeviceInfo.TYPE_WIRED_HEADSET -> "Wired Headset"
230+
AudioDeviceInfo.TYPE_WIRED_HEADPHONES -> "Wired Headphones"
231+
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP -> "Bluetooth A2DP"
232+
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "Bluetooth SCO"
233+
else -> "Other (${device.type})"
234+
}
189235
}

packages/react-native-audio-api/android/src/oldarch/NativeAudioAPIModuleSpec.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,8 @@ public NativeAudioAPIModuleSpec(ReactApplicationContext reactContext) {
7777
@ReactMethod
7878
@DoNotStrip
7979
public abstract void checkRecordingPermissions(Promise promise);
80+
81+
@ReactMethod
82+
@DoNotStrip
83+
public abstract void getDevicesInfo(Promise promise);
8084
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ - (void)invalidate
142142
[self.audioSessionManager checkRecordingPermissions:resolve reject:reject];
143143
}
144144

145+
RCT_EXPORT_METHOD(
146+
getDevicesInfo : (nonnull RCTPromiseResolveBlock)resolve reject : (nonnull RCTPromiseRejectBlock)reject)
147+
{
148+
[self.audioSessionManager getDevicesInfo:resolve reject:reject];
149+
}
150+
145151
#ifdef RCT_NEW_ARCH_ENABLED
146152
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
147153
(const facebook::react::ObjCTurboModule::InitParams &)params

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626
options:(NSArray *)options
2727
allowHaptics:(BOOL)allowHaptics;
2828
- (bool)setActive:(bool)active;
29+
2930
- (void)requestRecordingPermissions:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
3031
- (void)checkRecordingPermissions:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
3132

33+
- (void)getDevicesInfo:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
34+
- (NSArray<NSDictionary *> *)parseDeviceList:(NSArray<AVAudioSessionPortDescription *> *)devices;
35+
3236
@end

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,30 @@ - (NSString *)checkRecordingPermissions
292292
}
293293
}
294294

295+
- (void)getDevicesInfo:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
296+
{
297+
NSMutableDictionary *devicesInfo = [[NSMutableDictionary alloc] init];
298+
299+
[devicesInfo setValue:[self parseDeviceList:[self.audioSession availableInputs]] forKey:@"availableInputs"];
300+
[devicesInfo setValue:[self parseDeviceList:[[self.audioSession currentRoute] inputs]] forKey:@"currentInputs"];
301+
[devicesInfo setValue:[self parseDeviceList:[[self.audioSession currentRoute] outputs]] forKey:@"availableOutputs"];
302+
[devicesInfo setValue:[self parseDeviceList:[[self.audioSession currentRoute] outputs]] forKey:@"currentOutputs"];
303+
304+
resolve(devicesInfo);
305+
}
306+
307+
- (NSArray<NSDictionary *> *)parseDeviceList:(NSArray<AVAudioSessionPortDescription *> *)devices
308+
{
309+
NSMutableArray<NSDictionary *> *deviceList = [[NSMutableArray alloc] init];
310+
311+
for (AVAudioSessionPortDescription *device in devices) {
312+
[deviceList addObject:@{
313+
@"name" : device.portName,
314+
@"category" : device.portType,
315+
}];
316+
}
317+
318+
return deviceList;
319+
}
320+
295321
@end

packages/react-native-audio-api/src/specs/NativeAudioAPIModule.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22
import { TurboModuleRegistry } from 'react-native';
33
import type { TurboModule } from 'react-native';
4-
import { PermissionStatus } from '../system/types';
4+
import { PermissionStatus, AudioDevicesInfo } from '../system/types';
55

66
interface Spec extends TurboModule {
77
install(): boolean;
@@ -30,6 +30,9 @@ interface Spec extends TurboModule {
3030
// Permissions
3131
requestRecordingPermissions(): Promise<PermissionStatus>;
3232
checkRecordingPermissions(): Promise<PermissionStatus>;
33+
34+
// Audio devices
35+
getDevicesInfo(): Promise<AudioDevicesInfo>;
3336
}
3437

3538
const NativeAudioAPIModule = TurboModuleRegistry.get<Spec>('AudioAPIModule');

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { SessionOptions, LockScreenInfo, PermissionStatus } from './types';
1+
import {
2+
SessionOptions,
3+
LockScreenInfo,
4+
PermissionStatus,
5+
AudioDevicesInfo,
6+
} from './types';
27
import {
38
SystemEventName,
49
SystemEventCallback,
@@ -74,6 +79,10 @@ class AudioManager {
7479
async checkRecordingPermissions(): Promise<PermissionStatus> {
7580
return NativeAudioAPIModule!.checkRecordingPermissions();
7681
}
82+
83+
async getDevicesInfo(): Promise<AudioDevicesInfo> {
84+
return NativeAudioAPIModule!.getDevicesInfo();
85+
}
7786
}
7887

7988
export default new AudioManager();

0 commit comments

Comments
 (0)