hirbod / react-native-volume-manager

React Native module which adds the ability to change the system volume on iOS and Android, listen to volume changes and supress the native volume UI to build your own volume slider or UX. It can listen to iOS mute switch and ringer mode changes on Android (and let you set the ringer mode)
MIT License
216 stars 13 forks source link

Volume listener doesn't fire if volume level doesn't change #17

Closed stevengoldberg closed 10 months ago

stevengoldberg commented 10 months ago

My use case is using the hardware volume buttons to take a photo, so all I care about is knowing when the buttons have been pressed.

VolumeManager.addVolumeListener works for every case but two: volume is already 0 and the user presses volume down; volume is already 1 and the user presses volume up. In this case, the volume hasn't changed, so the listener doesn't fire. I still want to know that the button was pressed, though. Is there a way to accomplish this?

stevengoldberg commented 10 months ago

Here's my current hacky workaround. This prevents the volume from ever being 1, so that the volume up button still fires a change and takes the photo. It uses a ref to make sure that handleTakePhoto doesn't get called multiple times.

useEffect(() => {
        resetViewState()
        const handleVolume = async (newVolume) => {
            const { volume } = newVolume ?? (await VolumeManager.getVolume())
            if (volume === 1) {
                await VolumeManager.setVolume(0.94 - Math.random() / 100)
            }
        }
        handleVolume()
        const volumeListener = VolumeManager.addVolumeListener((result) => {
            handleVolume(result)
            if (!takingPhoto.current) {
                handleTakePhoto()
            }
        })
        return () => volumeListener.remove()
    }, [handleTakePhoto])

//...

handleTakePhoto = useCallback(async () => {
    takingPhoto.current = true
   // take the photo
}, []}

const resetViewState = () => {
    takingPhoto.current = false
}
hirbod commented 10 months ago

@StevenGoldberg, this is an interesting use case and it's also a known pattern, so it makes sense to support it. Your workaround is indeed a bit hacky. :D

On iOS, I don't listen to any button presses, but I redirect volume change subscriptions to JavaScript. This is something new I would need to investigate further.

Android should be easy — it might even work already, as I have keypress listeners active.

hirbod commented 10 months ago

@stevengoldberg, I've conducted some research and found that there is no official API. The only way to listen for volume changes without accessing private APIs (for which Apple will reject your app) is to add a KVO for "outputVolume" and send the value as event, which is what I'm currently doing.

Unfortunately, it does not provide any further notifications when the outputVolume is already at 1 or 0 (unless you adjust it in the opposite direction).

I've found similar workarounds like yours, where people set the volume to roughly 0.965 or 0.025. So, your workaround is somewhat acceptable as long as the user is not actively listening to music. However, it's clearly not the perfect solution, especially considering the possibility of the user listening to music on Spotify or any other background music.

I am afraid that this library can't provide any further assistance.

stevengoldberg commented 10 months ago

Thank you for the followup!