react-native-volume-manager
This native package enhances system volume control on both iOS and Android, allowing you to adjust the volume, monitor volume changes, and suppress the default volume UI to create your own custom volume slider and user experience. The package offers a straightforward API for obtaining the current volume level. On iOS, you can determine if the silent switch is enabled and track any changes, while on Android, you can modify and monitor ringer mode changes.
Notice
This library is incompatible with simulators and emulators and functions exclusively on real devices. Android Emulator 33 and above is supported.
Installation
npm install react-native-volume-manager
yarn add react-native-volume-manager
Using React Native >= 0.60
Linking the package manually is not required anymore with Autolinking.
Expo
This library incorporates native code and is incompatible with Expo Go. However, you can effortlessly install it using a custom dev client as recommended in 2022.
No config plugin required.
Usage 🚀
All methods are available under the VolumeManager
namespace or can be imported directly.
(eg. addVolumeListener
)
import { VolumeManager } from 'react-native-volume-manager';
VolumeManager.showNativeVolumeUI({ enabled: true });
await VolumeManager.setVolume(0.5);
await VolumeManager.setVolume(0.5, {
type: 'system',
showUI: true,
playSound: false,
});
const { volume } = await VolumeManager.getVolume({ type: 'music' });
useEffect(() => {
const volumeListener = VolumeManager.addVolumeListener((result) => {
console.log(result.volume);
});
return () => {
volumeListener.remove();
};
}, []);
useFocusEffect(
React.useCallback(() => {
const volumeListener = VolumeManager.addVolumeListener(async (result) => {
if (Platform.OS === 'ios') {
try {
VolumeManager.enableInSilenceMode(true);
} catch {}
}
});
return () => {
volumeListener.remove();
};
}, [])
);
iOS Audio Session Management
AVAudioSession related functions.
import { VolumeManager } from 'react-native-volume-manager';
VolumeManager.enable(true, true);
VolumeManager.enable(false, true);
VolumeManager.setActive(true, true);
VolumeManager.setActive(false, true);
const onAppStateChange = useCallback(async (status) => {
if (status === 'active') {
if (audioSessionIsInactive.current) {
VolumeManager.setActive(true);
audioSessionIsInactive.current = false;
}
} else if (status === 'background') {
VolumeManager.setActive(false);
audioSessionIsInactive.current = true;
}
}, []);
useAppState({ onChange: onAppStateChange });
If you want to change the Audio Category or Audio Mode on iOS, or play music when the silent switch is active, you can call the functions enableInSilenceMode
, setCategory
, or setMode
. Please refer to Apple's documentation to find out what they do.
Import the VolumeManager from the 'react-native-volume-manager' package:
import { VolumeManager } from 'react-native-volume-manager';
Change the mode using setMode
with available options:
- Default
- VoiceChat
- VideoChat
- GameChat
- VideoRecording
- Measurement
- MoviePlayback
- SpokenAudio
VolumeManager.setMode(mode);
Change the category using setCategory
:
- Ambient
- SoloAmbient
- Playback
- Record
- PlayAndRecord
- MultiRoute
- Alarm
VolumeManager.setCategory(value, mixWithOthers);
Enable or disable playing audio when the silent switch is active using enableInSilenceMode
:
VolumeManager.enableInSilenceMode(true);
VolumeManager.enableInSilenceMode(false);
iOS mute switch listener
There is no native iOS API to determine the mute switch status on a device.
The common approach to check if the device is muted involves playing a brief silent sound and measuring the playback duration. The check is performed every second by default.
Note: The check runs on the native main thread, not the JS thread.
You can customize the check interval by modifying the VolumeManager.setNativeSilenceCheckInterval(1)
property. The minimum value is 0.5
, and the default is 2
. Generally, the default value is sufficient.
import { VolumeManager } from 'react-native-volume-manager';
const [isSilent, setIsSilent] = useState<boolean>();
const [initialQuery, setInitialQuery] = useState<boolean>();
VolumeManager.setNativeSilenceCheckInterval(1);
useEffect(() => {
const silentListener = VolumeManager.addSilentListener((status) => {
setIsSilent(status.isMuted);
setInitialQuery(status.initialQuery);
});
return () => {
silentListener.remove();
};
}, []);
You can also use the useSilentSwitch
hook from react-native-volume-manager
to get the mute switch status.
import { useSilentSwitch } from 'react-native-volume-manager';
const { isMuted, initialQuery } = useSilentSwitch(1);
Note that you can pass an optional interval parameter to control the frequency at which the native thread looks for changes. The default interval is 2 seconds, but higher values are recommended to save battery. The minimum interval is 0.5 seconds. It is recommended to use a higher interval value when fast recognition is not critical.
API
Method | Description |
---|
Volume | |
VolumeManager.showNativeVolumeUI(config: { enabled:boolean }) => void | Disable the native volume toast when using hardware buttons (iOS, Android). |
VolumeManager.getVolume(type?:string) => Promise (async) | Get the volume.
type must be one of music , call , system , ring , alarm , notification , default is music . (Android only, iOS will always report the system volume) |
VolumeManager.setVolume(value:float, config?:object) (async) | Set the system volume by specified value, from 0 to 1. 0 for mute, and 1 for the max volume.
config? can be like {type: 'music', playSound:true, showUI:true}
type : must be one of music , call , system , ring , alarm , notification , default is music . (Android only)
playSound : Whether to play a sound when changing the volume, default is false (Android only)
showUI : Show the native system volume UI, default is false (Android & iOS) |
VolumeManager.addVolumeListener(callback) | Listen to volume changes (soft- and hardware). addVolumeListener will return the listener which is needed for cleanup. Will return a number or object.
Remove the listener when you don't need it anymore. Store the return of
const listener = VolumeManager.addVolumeListener() in a variable and call it with listener.remove() |
iOS AVAudioSession related | |
VolumeManager.enableInSilenceMode(value: boolean) | Enable/Disable audio playback when iOS silent switch is active. Has to be called only once or when you change the Audio category. |
VolumeManager.setCategory(value: AVAudioSessionCategory, mixWithOthers?: boolean) | Change category. Allowed values are Ambient, SoloAmbient, Playback, Record, PlayAndRecord, MultiRoute, Alarm. The second param is false by default. Please refer to Apples documentation |
VolumeManager.setMode(mode: AVAudioSessionMode) | Change Audio mode, available options are Default, VoiceChat, VideoChat, GameChat, VideoRecording, Measurement, MoviePlayback, SpokenAudio |
Android ringer listener | |
VolumeManager.addRingerListener(callback): RingerSilentStatus => void | Android only: Listen to ringer mode changes. Returns an object with type. RingerSilentStatus . No-op on iOS |
VolumeManager.removeRingerListener(listener) => void | Android only: Unlike addVolumeListener , you need to call a separate method and pass the return of addRingerListener . No-op on iOS |
VolumeManager.isRingerListenerEnabled() | Returns bool if listening to ringer mode changes is possible. |
VolumeManager.getRingerMode() => Promise (async) | Get the current ringer mode. Returns RingerModeType |
VolumeManager.setRingerMode(mode: RingerModeType) => Promise (async) | Set the ringer mode. Please have a look at the Hooks / Ringer mode section, because there are special cases with DND. |
requestDndAccess() | Request permission to change ringer mode while in DND or to DND |
VolumeManager.checkDndAccess() | Checks if you have permission to change ringer mode while device is in DND or if you can put the device into DND mode. |
Hook
const { mode, error, setMode } = useRingerMode(); | Returns state and functions to get or set the current ringer mode. This is a one-time getter, if you need monitoring, use the addRinterListener instead. |
iOS silent listener | |
VolumeManager.addSilentListener(callback): RingMuteSwitchStatus => void | Listen to silent switch changes on iOS. Returns on object with type RingMuteSwitchStatus consisting of properties isMuted (indicates physical switch state) and initialQuery (indicates whether reported status is the very first one reported what could be treated as the initial state of the ring/mute switch upon application launch). Remove the listener when you don't need it anymore.
Store the return of const listener = VolumeManager.addSilentListener() in a variable and call it with listener.remove() |
VolumeManager.setNativeSilenceCheckInterval(number) | How often the native thread should check of the silent switch state on iOS has changed. Defaults to 2, minimum value is 0.5. Increase the number if you don't need frequent checks (will help against battery drainage) |
Hook
const { isMuted, initialQuery } = useSilentSwitch(interval?: number); | Returns a boolean if the iOS silent switch is active. Returns undefined on other platforms. The interval is optional and controls how often the native thread should check for changes. Defaults to 2 (seconds). Minimum is 0.5. |
Hooks / Ringer mode (Android only, no-op on iOS)
How to get and set ringer mode with useRingerMode hook
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useRingerMode, RINGER_MODE } from 'react-native-volume-manager';
const modeText = {
[RINGER_MODE.silent]: 'Silent',
[RINGER_MODE.normal]: 'Normal',
[RINGER_MODE.vibrate]: 'Vibrate',
};
export default function App() {
const { mode, error, setMode } = useRingerMode();
return (
<View>
<Text>Ringer Mode: {mode !== undefined ? modeText[mode] : null}</Text>
<View>
<Button title="Silent" onPress={() => setMode(RINGER_MODE.silent)} />
<Button title="Normal" onPress={() => setMode(RINGER_MODE.normal)} />
<Button title="Vibrate" onPress={() => setMode(RINGER_MODE.vibrate)} />
</View>
<View>
<Text>{error?.message}</Text>
</View>
</View>
);
}
How to get ringer mode with getRingerMode
getRingerMode
is an async function and resolves the current ringer mode of the device. (Resolves undefined on non-Android devices.)
import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import {
RINGER_MODE,
getRingerMode,
RingerModeType,
} from 'react-native-volume-manager';
const modeText = {
[RINGER_MODE.silent]: 'Silent',
[RINGER_MODE.normal]: 'Normal',
[RINGER_MODE.vibrate]: 'Vibrate',
};
export default function App() {
const [mode, setMode] = useState<RingerModeType | undefined>();
useEffect(() => {
(async () => {
try {
const currentMode = await getRingerMode();
setMode(currentMode);
} catch (error) {
console.error(error);
}
})();
}, []);
return (
<View>
<Text>Ringer Mode: {mode !== undefined ? modeText[mode] : null}</Text>
</View>
);
}
How to set ringer mode with setRingerMode
setRingerMode
is an asynchronous function that sets the specified ringer mode on the device and resolves the mode once it is set. It returns undefined on non-Android devices.
import React from 'react';
import { View, Button } from 'react-native';
import {
setRingerMode,
RINGER_MODE,
RingerModeType,
} from 'react-native-volume-manager';
export default function App() {
const setMode = (mode: RingerModeType) => {
try {
setRingerMode(mode);
} catch (error) {
console.error(error);
}
};
return (
<View>
<Button title="Silent" onPress={() => setMode(RINGER_MODE.silent)} />
<Button title="Normal" onPress={() => setMode(RINGER_MODE.normal)} />
<Button title="Vibrate" onPress={() => setMode(RINGER_MODE.vibrate)} />
</View>
);
}
Not allowed to change Do Not Disturb state: checkDndAccess & requestDndAccess
Starting from Android N, changing the ringer mode to a state that would enable Do Not Disturb is not allowed unless the app has been granted Do Not Disturb Access. See AudioManager#setRingerMode.
If you want to change the ringer mode from Silent mode or to Silent mode, you may encounter the "Not allowed to change Do Not Disturb state" error. The example below shows how to check for DND access, and if the user hasn't granted access, how to prompt them to open the settings:
First, add the following line to your AndroidManifest.xml
file to enable your app to appear in the settings.
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
And you can check and request permission before setting the ringer mode. Example code below:
import React from 'react';
import { View, Button } from 'react-native';
import {
useRingerMode,
RINGER_MODE,
checkDndAccess,
requestDndAccess,
RingerModeType,
} from 'react-native-volume-manager';
export default function App() {
const { mode, setMode } = useRingerMode();
const changeMode = async (newMode: RingerModeType) => {
if (newMode === RINGER_MODE.silent || mode === RINGER_MODE.silent) {
const hasDndAccess = await checkDndAccess();
if (hasDndAccess === false) {
requestDndAccess();
return;
}
}
setMode(newMode);
};
return (
<View>
<Button title="Silent" onPress={() => changeMode(RINGER_MODE.silent)} />
<Button title="Normal" onPress={() => changeMode(RINGER_MODE.normal)} />
<Button title="Vibrate" onPress={() => changeMode(RINGER_MODE.vibrate)} />
</View>
);
}
Contributing
See the contributing guide to learn how to contribute to the repository and the development workflow.
Special thanks
I used parts, or even the full source code, of these libraries (with plenty of adjustments and rewrites to TypeScript) to make this library work on Android and iOS and to have a mostly unified API that handles everything related to volume. Since many of the packages I found were unmaintained or abandoned and only solved some of the issues, I decided to create my own. I hope you find it useful!
License
MIT