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

[bug][ios] event listener doesn't trigger #21

Closed youniaogu closed 7 months ago

youniaogu commented 7 months ago

my case is use volume up/down event to control flatlist scroll prev/next, so i make a useVolumeUpDown hook based on addVolumeListener

and my problem is if the app get in background and switch to other app(i reproduce by use youtube), play video and back to app, the listener will never trigger

so is the volume listener have conflict with other app listener?

below is my code and use case:

import { useCallback, useRef, useState } from 'react';
import { useFocusEffect } from '@react-navigation/native';
import { VolumeManager } from 'react-native-volume-manager';
import { Volume } from '~/utils';

export const useVolumeUpDown = (callback: (type: Volume) => void) => {
  const volumeRef = useRef<number>();
  const [initVolume, setInitVolume] = useState<number>();

  const init = useCallback(() => {
    VolumeManager.getVolume().then((volume) => {
      let prev = typeof volume === 'number' ? volume : volume.volume;

      volumeRef.current = prev;
      if (prev <= 0) {
        prev = 0.025;
      } else if (prev >= 1) {
        prev = 0.975;
      }
      VolumeManager.setVolume(prev);
      setInitVolume(prev);
    });
  }, []);
  const listener = useCallback(() => {
    if (typeof initVolume !== 'number') {
      return;
    }

    VolumeManager.showNativeVolumeUI({ enabled: false });
    const volumeListener = VolumeManager.addVolumeListener((result) => {
      if (result.volume - initVolume > 0.0001) {
        setTimeout(
          () => VolumeManager.setVolume(initVolume).finally(() => callback(Volume.Up)),
          200
        );
      } else if (initVolume - result.volume > 0.0001) {
        setTimeout(
          () => VolumeManager.setVolume(initVolume).finally(() => callback(Volume.Down)),
          200
        );
      }
    });

    return () => {
      volumeListener && volumeListener.remove();
      if (typeof volumeRef.current === 'number') {
        VolumeManager.setVolume(volumeRef.current).finally(() => {
          VolumeManager.showNativeVolumeUI({ enabled: true });
        });
      }
    };
  }, [initVolume, callback]);

  useFocusEffect(init);
  useFocusEffect(listener);
};
const Component = () => {
  ...
  const callbackRef = useRef<(type: Volume) => void>();

  callbackRef.current = (type) => {
    if (type === Volume.Down) {
      handleNextPage();
    }
    if (type === Volume.Up) {
      handlePrevPage();
    }
  };

  useVolumeUpDown(
    useCallback((type) => {
      callbackRef.current && callbackRef.current(type);
    }, [])
  );
  ...
}
hirbod commented 7 months ago

The reason is most likely that you're losing the audio session. You could try using the AppState listener to disable the AudioSession when you background the app and then enable it once you reopen the app. To do this, call:

VolumeManager.enable(true)

Alternatively, you can use VolumeManager.setActive(true) or false based on the app's state.

Im one of my Apps, I am doing following:

  const onAppStateChange = useCallback(async (status: AppStateStatus) => {
    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,
  });

Based on what libraries you use, you might need to trigger .enable(true) as well. Check the methods in the README, that should help to solve your issue.

youniaogu commented 7 months ago

Thank you for the reply

I call the VolumeManager.enable(true) and it worked!