Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
1ebd892
Feat: Typesafe configureAVAudioSession function that ensures compatib…
ChristopherGabba Mar 11, 2025
8a73b49
feat: Added typesafe function for configuring ios audio sessions with…
ChristopherGabba Mar 11, 2025
223b205
feat: new `configureAVSession` function
ChristopherGabba Mar 11, 2025
2ef8f33
remove accidental code left in demo
ChristopherGabba Mar 11, 2025
de7b978
fix: compile bug on android (previously missed)
ChristopherGabba Mar 12, 2025
ae203ba
chore: unneeded imports from example
ChristopherGabba Mar 12, 2025
0fa69f6
feat: #51 new getAVStatus method and README format
ChristopherGabba Mar 12, 2025
fe8bb7a
feat: added type safety to categoryOptions
ChristopherGabba Mar 13, 2025
3eb54ce
fix: small ts bug
ChristopherGabba Mar 13, 2025
b69053f
revert: imports to react-native-volume-manager
ChristopherGabba Mar 13, 2025
40cee60
chore: add new audio session functions to example
ChristopherGabba Mar 13, 2025
c5db7e7
chore: small change to types and categories
ChristopherGabba Mar 13, 2025
2abbe78
chore: deprecate `enable` & `enableInSilenceMode`
ChristopherGabba Mar 13, 2025
fbff221
fix: improve examples
ChristopherGabba Mar 13, 2025
7984ce4
feat: new `activate` and `deactivate` functions
ChristopherGabba Mar 13, 2025
6222ff4
remove random eslint plugin
ChristopherGabba Mar 14, 2025
d546291
chore: remove "AV" from all functions
ChristopherGabba Mar 14, 2025
25c105e
chore: remove version checks prior to ios 15
ChristopherGabba Mar 14, 2025
09f5144
chore: remove deprecated functions
ChristopherGabba Mar 14, 2025
d909baf
chore: ran `yarn install` in the example & rebuilt
ChristopherGabba Mar 14, 2025
3056d48
chore: remove non-new arch example
ChristopherGabba Mar 14, 2025
3ae2722
feat: new AVAudio Testing modal
ChristopherGabba Mar 14, 2025
2bffd28
fix: ts error
ChristopherGabba Mar 14, 2025
890a258
fix: bug in deactivation found after testing
ChristopherGabba Mar 14, 2025
b17de00
feat: allow just setting mode
ChristopherGabba Mar 14, 2025
cdeb2e3
fix: bug in deactivation found after testing
ChristopherGabba Mar 14, 2025
f05db07
feat: allow just setting mode
ChristopherGabba Mar 14, 2025
4ceeb1b
fix: bug in deactivation found after testing
ChristopherGabba Mar 14, 2025
78a05fd
fix: activation bug
ChristopherGabba Mar 14, 2025
edc3b78
fix: ts error
ChristopherGabba Mar 14, 2025
042bd00
fix: bug in deactivation found after testing
ChristopherGabba Mar 14, 2025
bca32f2
fix: bug in deactivation found after testing
ChristopherGabba Mar 14, 2025
732d74f
fix: activation bug
ChristopherGabba Mar 14, 2025
6f88f2b
fix: small bug with activate
ChristopherGabba Mar 14, 2025
a0ccca2
fix: accidentally removed @end
ChristopherGabba Mar 14, 2025
ceb4ac8
Merge branch 'feat/typesafe-ios-audio-configuration'
ChristopherGabba Mar 14, 2025
d7ce4e2
fix: default input on function
ChristopherGabba Mar 14, 2025
d769d55
fix: default options for activation & deactivation
ChristopherGabba Mar 14, 2025
015c409
fix: activation options should be optional
ChristopherGabba Mar 14, 2025
cc05aab
publish
ChristopherGabba Mar 18, 2025
ec5ca23
revert: previous test of git ignore
ChristopherGabba Mar 18, 2025
1a86a68
revert lib file addition
ChristopherGabba Mar 18, 2025
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
409 changes: 380 additions & 29 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
Expand Down Expand Up @@ -187,14 +188,29 @@ public String getName() {
return NAME;
}

// Keep: Required no-op methods for iOS compatibility
@ReactMethod
public void enable(final Boolean enabled, final Boolean async) {
public void activateAudioSession(final Boolean runAsync) {
// no op
}

@ReactMethod
public void setCategory(final String category, final Boolean mixWithOthers) {
public void deactivateAudioSession(final Boolean restorePreviousSessionOnDeactivation, final Boolean runAsync) {
// no op
}

@ReactMethod
public void configureAudioSession(final String category,
final String mode,
final String policy,
final ReadableArray categoryOptions,
final boolean prefersNoInterruptionFromSystemAlerts,
final boolean prefersInterruptionOnRouteDisconnect,
final boolean allowHapticsAndSystemSoundsDuringRecording) {
// no op
}

@ReactMethod
public void getAudioSessionStatus() {
// no op
}

Expand Down
243 changes: 243 additions & 0 deletions example/AVAudioSessionTestingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useVideoPlayer, VideoSource, VideoView } from 'expo-video';
import {
AVAudioSessionCategory,
AVAudioSessionCategoryOptions,
AVAudioSessionCompatibleCategoryOptions,
AVAudioSessionCompatibleModes,
AVAudioSessionMode,
configureAudioSession,
getAudioSessionStatus,
VolumeManager,
} from 'react-native-volume-manager';
import { Dropdown } from 'react-native-element-dropdown';
import { useState } from 'react';

type AudioModalProps = {
visible: boolean;
onCloseButtonPressed: () => void;
};

type TestConfiguration = {
key: number;
title: string;
configuration: {
category: AVAudioSessionCategory;
mode: AVAudioSessionCompatibleModes[AVAudioSessionCategory];
categoryOptions: AVAudioSessionCompatibleCategoryOptions[AVAudioSessionCategory][];
prefersNoInterruptionFromSystemAlerts: boolean;
};
};

const testConfigurations: TestConfiguration[] = [
{
key: 0,
title: 'Ambient - Default',
configuration: {
category: AVAudioSessionCategory.Ambient,
mode: AVAudioSessionMode.Default,
categoryOptions: [AVAudioSessionCategoryOptions.AllowBluetooth],
prefersNoInterruptionFromSystemAlerts: true,
},
},
{
key: 1,
title: 'Recording Audio',
configuration: {
category: AVAudioSessionCategory.Record,
mode: AVAudioSessionMode.Default,
categoryOptions: [AVAudioSessionCategoryOptions.AllowBluetooth],
prefersNoInterruptionFromSystemAlerts: true,
},
},
{
key: 2,
title: 'Movie Playback and Ducking Audio',
configuration: {
category: AVAudioSessionCategory.Playback,
mode: AVAudioSessionMode.MoviePlayback,
categoryOptions: [
AVAudioSessionCategoryOptions.DuckOthers,
AVAudioSessionCategoryOptions.AllowBluetooth,
],
prefersNoInterruptionFromSystemAlerts: true,
},
},
{
key: 3,
title: 'Playing And Recording Simultaneously',
configuration: {
category: AVAudioSessionCategory.PlayAndRecord,
mode: AVAudioSessionMode.VideoRecording,
categoryOptions: [AVAudioSessionCategoryOptions.AllowBluetooth],
prefersNoInterruptionFromSystemAlerts: true,
},
},
{
key: 4,
title: 'Movie Playback And Mixing',
configuration: {
category: AVAudioSessionCategory.Playback,
mode: AVAudioSessionMode.MoviePlayback,
categoryOptions: [AVAudioSessionCategoryOptions.AllowBluetooth],
prefersNoInterruptionFromSystemAlerts: true,
},
},
];

/**
* This component is used for testing the AVAudioSession.
* In order to test this properly, we use the `expo-video` package with a patch that comments out
* its internal AudioSession controls. This will allow us to test how our functions handle the audio.
*/
export const AVAudioSessionTestingModal = (props: AudioModalProps) => {
const { visible, onCloseButtonPressed } = props;

const bigBuckBunnySource: VideoSource =
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';

const elephantsDreamSource: VideoSource =
'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4';

const [selectedConfig, setSelectedConfig] = useState<TestConfiguration>(
testConfigurations[0]
);

async function runConfigurationWithLogging(fn: () => Promise<void>) {
const initialStatus = await getAudioSessionStatus();
console.log('INITIAL_STATUS:', JSON.stringify(initialStatus, null, 4));

await fn();

const statusAfterConfiguration = await getAudioSessionStatus();
console.log(
'NEW_STATUS:',
JSON.stringify(statusAfterConfiguration, null, 4)
);
}

const player1 = useVideoPlayer(bigBuckBunnySource, (player) => {
player.loop = true;
});

const player2 = useVideoPlayer(elephantsDreamSource, (player) => {
player.loop = true;
});

function activateAudioSession() {
VolumeManager.activateAudioSession({
runAsync: true
});
}

function terminateAudioSession() {
VolumeManager.deactivateAudioSession({
restorePreviousSessionOnDeactivation: true,
runAsync: true
});
}

return (
<Modal
visible={visible}
style={styles.modal}
presentationStyle="overFullScreen"
>
{visible && (
<View style={styles.container}>
<View style={styles.card}>
<Text style={styles.cardTitle}>Select Audio Configuration</Text>
<Dropdown
style={[styles.dropdown]}
data={testConfigurations}
maxHeight={300}
labelField="title"
valueField="configuration"
value={selectedConfig}
onChange={(item: TestConfiguration) => {
setSelectedConfig(item);
runConfigurationWithLogging(() =>
configureAudioSession(item.configuration)
);
}}
/>
<TouchableOpacity
style={styles.button}
onPress={activateAudioSession}
>
<Text style={styles.buttonText}>Start Audio Session</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={terminateAudioSession}
>
<Text style={styles.buttonText}>Stop Audio Session</Text>
</TouchableOpacity>
<View style={styles.container}>
<VideoView player={player1} style={styles.video} />
<VideoView player={player2} style={styles.video} />
</View>
<TouchableOpacity
style={styles.button}
onPress={() => {
player1.pause();
player2.pause();
onCloseButtonPressed();
}}
>
<Text style={styles.buttonText}>Close Modal</Text>
</TouchableOpacity>
</View>
</View>
)}
</Modal>
);
};

const styles = StyleSheet.create({
modal: {
flex: 1,
},
dropdown: {
width: '100%',
alignSelf: 'center',
borderRadius: 2,
borderWidth: 1,
padding: 10,
},
container: {
padding: 20,
paddingTop: 75,
backgroundColor: '#F5F5F5',
},
video: {
width: '100%',
height: 200,
},
card: {
padding: 10,
marginBottom: 10,
borderWidth: 1,
borderRadius: 10,
borderColor: '#DDD',
backgroundColor: '#FFF',
},
cardTitle: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
text: {
fontSize: 16,
},
button: {
padding: 10,
borderRadius: 5,
backgroundColor: '#000',
marginTop: 10,
},
buttonText: {
color: '#FFF',
textAlign: 'center',
},
});
25 changes: 25 additions & 0 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RingerSilentStatus,
} from 'react-native-volume-manager';
import Slider from '@react-native-community/slider';
import { AVAudioSessionTestingModal } from './AVAudioSessionTestingModal';

const modeText = {
[RINGER_MODE.silent]: 'Silent',
Expand All @@ -35,6 +36,9 @@ export default function App() {
const [initialQuery, setInitialQuery] = useState<boolean | undefined>();
const [ringerStatus, setRingerStatus] = useState<RingerSilentStatus>();
const [hideUI, setHideUI] = useState<boolean>(false);
const [showAVAudioSessionModal, setShowAVAudioSessionModal] =
useState<boolean>(false);

const volumeChangedByListener = useRef(true);

useEffect(() => {
Expand Down Expand Up @@ -144,6 +148,12 @@ export default function App() {
: 'Unsupported on Android'}
</Text>
</View>
<TouchableOpacity
style={styles.button}
onPress={() => setShowAVAudioSessionModal(true)}
>
<Text style={styles.buttonText}>Test iOS AudioSession</Text>
</TouchableOpacity>
</View>

<View style={styles.card}>
Expand Down Expand Up @@ -214,6 +224,10 @@ export default function App() {
</View>
</View>
</View>
<AVAudioSessionTestingModal
visible={showAVAudioSessionModal}
onCloseButtonPressed={() => setShowAVAudioSessionModal(false)}
/>
</ScrollView>
</SafeAreaView>
);
Expand All @@ -224,6 +238,17 @@ const styles = StyleSheet.create({
padding: 20,
backgroundColor: '#F5F5F5',
},
headerTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
},
subHeaderTitle: {
fontSize: 20,
fontWeight: 'bold',
textDecorationStyle: 'dotted',
marginBottom: 10,
},
card: {
padding: 10,
marginBottom: 10,
Expand Down
2 changes: 1 addition & 1 deletion example/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# example
# example new arch

```
yarn
Expand Down
10 changes: 7 additions & 3 deletions example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
Expand All @@ -19,17 +20,20 @@
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.hirbod.rnvme"
"bundleIdentifier": "com.hirbod.rnvme.newarch"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
},
"package": "com.hirbod.rnvme"
"package": "com.hirbod.rnvme.newarch"
},
"web": {
"favicon": "./assets/favicon.png"
}
},
"plugins": [
"expo-video"
]
}
}
Loading