This native package adds the ability to change the system volume on iOS and Android, listen to volume changes and suppress the native volume UI to build your own volume slider and UX. It also provides a simple API to get the current volume level. On iOS, you can check if the silent switch is enabled and listen to changes. On Android, you can set and listen for ringer mode changes.
This library does not work in a Simulator or Emulator. It is only working on a real device.
# Using npm
npm install react-native-volume-manager
# using yarn
yarn add react-native-volume-manager
Linking the package manually is not required anymore with Autolinking.
This library adds native code. It does not work with Expo Go but you can easily install it using a custom dev client. Thats how it should be done in 2022 :).
No config plugin required.
All methods are available under the VolumeManager
namespace or can be imported directly.
(eg. addVolumeListener
)
import { VolumeManager } from 'react-native-volume-manager';
// ...
// set the volume, value between 0 and 1 (float)
await VolumeManager.setVolume(0.5); // float value between 0 and 1
// set volume with extra options
await VolumeManager.setVolume(0.5, {
// defaults to "music" (Android only)
type: 'system',
// defaults to false, can surpress the native UI Volume Toast (iOS & Android)
showUI: true,
// defaults to false (Android only)
playSound: false,
});
// Get the current volume async, type defaults to "music"
// (Android only, iOS only has one type of Volume)
// see down below for more types
// if you don't add a type, you'll get an object as a return on
// Android with all Volume types
const { volume } = await VolumeManager.getVolume(type: 'music');
// The oldschool way
VolumeManager.getVolume('music').then((result) => {
// can be a number or object (depends on iOS or Android and if supplied for type)
console.log(result);
// NOTE: if you don't supply a type to getVolume on Android,
//you will receive the VolumeResult object:
/*
{
volume: number, // these are the same
music: number, // these are the same
system: number, // these are the same
ring: number,
alarm: number,
notification: number,
}
*/
// iOS will always return only the volume as float
});
// listen to volume changes (example)
useEffect(() => {
const volumeListener = VolumeManager.addVolumeListener((result) => {
// returns the current volume as a float (0-1)
console.log(result.volume);
// on android, the result object will also have the keys
// music, system, ring, alarm, notification
});
// clean up function
return function () {
// remove listener, just call .remove on the volumeListener
// EventSubscription. Never forget to clean up your listeners.
volumeListener.remove();
}
}, []);
// or with useFocusEffect from react-navigation
useFocusEffect(
React.useCallback(() => {
const volumeListener = VolumeManager.addVolumeListener(async (result) => {
if (Platform.OS === 'ios') {
try {
// once we detected a user on iOS triggered volume change,
// we can change the audio mode to allow playback even when the
// silent switch is activated. This can help to achieve effects like in
// instragram reels, where videos are muted on silent switch
// This example requires expo-av
await Audio.setAudioModeAsync({
playsInSilentModeIOS: true,
});
} catch {}
}
});
return function blur() {
// remove listener, just call .remove on the emitter
volumeListener.remove();
};
}, [])
);
There is no native iOS API to detect if the mute switch is enabled/disabled on a device.
The general principle to check if the device is muted is to play a short sound without audio and detect the length it took to play. Has a trigger rate of 1 second.
Note: The check is performed on the native main thread, not the JS thread.
You can increase or decrease how often the check is performed by changing the VolumeManager.setNativeSilenceCheckInterval(1)
property.
Minimum value is 0.5
, default is 2
. The default value is usually enough.
import { VolumeManager } from 'react-native-volume-manager';
const [isSilent, setIsSilent] = useState<boolean>();
// optional, default is 2 seconds. You can choose to set a higher value
// when fast recognition is not critical (will save Battery)
VolumeManager.setNativeSilenceCheckInterval(1); // min 0.5, default 2
// ....
// ....
useEffect(() => {
const silentListener = VolumeManager.addSilentListener((status) => {
setIsSilent(status); // status is a boolean
});
return () => {
// remove listener, just call .remove on the emitter return
// never forget to clean up
silentListener.remove();
};
}, []);
You can also use the hook.
import { useSilentSwitch } from 'react-native-volume-manager';
//....
const isSilent = useSilentSwitch();
// or with parameter which controls which
// interval the native thread should look for changes. 0.5 is min
// defaults to 2 (higher values are even recommened since it will drain less battery)
const isSilent = useSilentSwitch(1);
// returns boolean on iOS and undefined on any other platform.
Method | Description |
---|---|
Volume | |
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() |
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. |
Hookconst { 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): boolean => void |
Listen to silent switch changes on iOS. Returns true if muted and false if not. Remove the listener when you don't need it anymore.Store the return of const listener = VolumeManager.addSilentListener() in a variable and call it withlistener.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) |
Hookconst isSilent = useSilentSwitch(interval?: number); |
Returns a boolean if the iOS silent switch is active. Returns undefined on other platforms. The inverval is optional and controls how often the native thread should check for changes. Defaults to 2 (seconds). Mininum is 0.5. |
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>
);
}
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>
);
}
setRingerMode is an async function that sets the given ringer mode to the device and resolves the mode if it is set. (Resolves 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>
);
}
From N onward, ringer mode adjustments that would toggle Do Not Disturb are 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 run into the Not allowed to change Do Not Disturb state error. The example below checks the DND access and if user hasn't given the access opens the settings for it.
First you need to add the line below to your AndroidManifest.xml to be able to see your app 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) => {
// From N onward, ringer mode adjustments that would toggle Do Not Disturb
// are not allowed unless the app has been granted Do Not Disturb Access.
// @see https://developer.android.com/reference/android/media/AudioManager#setRingerMode(int)
if (newMode === RINGER_MODE.silent || mode === RINGER_MODE.silent) {
const hasDndAccess = await checkDndAccess();
if (hasDndAccess === false) {
// This function opens the DND settings.
// You can ask user to give the permission with a modal before calling this function.
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>
);
}
See the contributing guide to learn how to contribute to the repository and the development workflow.
- Uses code from https://github.com/c19354837/react-native-system-setting
- Uses code from https://github.com/vitorverasm/react-native-silent
- Uses code from https://github.com/GeorgyMishin/react-native-silent-listener
- Fully implements https://github.com/reyhankaplan/react-native-ringer-mode
I used parts or even the full source code of these libraries (with plenty of adjustments and rewirtes to TS) to make this library work on Android and iOS and to have a mostly unified API which does everything related to Volume. Since most of the packages I've found have been unmaintained or abandoned and also only solved parts of the issues, I decided to make my own. I hope you like it!
MIT