首页 > 解决方案 > React Native/Firebase/Expo Audio - 在下载开始并且用户离开页面后取消 loadAsync

问题描述

当用户离开我的页面时,我无法取消 loadAsync。我尝试在 useEffect 上使用清理功能,但由于 soundObject 尚未加载,它会给我一个错误,因为 soundObject 等于 null。当其他页面成为焦点时,我也尝试使用 redux 并添加 soundObject.stopAsync 但由于可能尚未设置 soundObject 但它不会取消并且我将播放音频并且无法停止。这是我调用 loadAsync 的暂停/播放按钮组件。任何帮助将不胜感激。谢谢

更新我的播放暂停处理程序 我找到了一种解决方法,即使我觉得有更好的方法。我现在正在调用 Audio.setIsEnabledAsync(false); 作为清理功能。

  //CLEANUP FUNCTION
  useEffect(() => {
    Audio.setIsEnabledAsync(true);
    return function cleanUp() {
      reference.putFile(props.audioFile).cancel();
      Audio.setIsEnabledAsync(false);
    };
  }, []);
import React, { useState, useEffect } from "react";
import { TouchableOpacity } from "react-native";
import { useDispatch, useSelector } from "react-redux";

import storage from "@react-native-firebase/storage";
import { playPause, stopPlay } from "../../../store/actions/playerActions";
import { Audio } from "expo-av";
import SmallIndicator from "../Indicators/SmallIndicator";

import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome";
import { faPlay, faPause } from "@fortawesome/pro-light-svg-icons";
import Colors from "../../../constants/Colors";

const PlayPause = (props) => {
  const dispatch = useDispatch();

  // LOAD FROM FIREBASE VARIABLES
  let audioFile = props.audioFile;
  const reference = storage().ref(audioFile);
  let task = reference.getDownloadURL();

  //HOOKS
  const isPlaying = useSelector((state) => state.player.isPlaying);
  const [iconSwitch, setIconSwitch] = useState(faPlay);
  const [soundObject, setSoundObject] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  // LOAD AUDIO SETTINGS
  useEffect(() => {
    const audioSettings = async () => {
      try {
        await Audio.setAudioModeAsync({
          allowsRecordingIOS: false,
          interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
          playsInSilentModeIOS: true,
          interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
          shouldDuckAndroid: true,
          staysActiveInBackground: false,
          playThroughEarpieceAndroid: true,
        });
      } catch (e) {
        console.log(e);
      }
      audioSettings();
    };
  }, []);

  //CLEANUP FUNCTION
  useEffect(() => {
    Audio.setIsEnabledAsync(true);
    return function cleanUp() {
      reference.putFile(props.audioFile).cancel();
      Audio.setIsEnabledAsync(false);
    };
  }, []);

  // STOP PLAY ON PAGE EXIT
  useEffect(() => {
    ifPlaying();
  }, [isPlaying]);

  const ifPlaying = async () => {
    if (isPlaying === false && soundObject != null) {
      await soundObject.stopAsync();
      await soundObject.unloadAsync();
      setSoundObject(null);
      setIconSwitch(faPlay);
    }
  };

  // PLAY PAUSE TOGGLE
  const handlePlayPause = async () => {
    setIsLoading(true);
    let uri = await task;

    //PLAY
    if (isPlaying === false && soundObject === null) {
      const soundObject = new Audio.Sound();
      await soundObject.loadAsync({ uri }, isPlaying, true);
      setSoundObject(soundObject);
      soundObject.playAsync();
      dispatch(playPause(true));
      setIconSwitch(faPause);

      // PAUSE
    } else if (isPlaying === true && soundObject != null) {
      dispatch(playPause(false));
      setIconSwitch(faPlay);

      // STOP AND PLAY
    } else if (isPlaying === true && soundObject === null) {
      dispatch(stopPlay(true));
      dispatch(playPause(true));
      const soundObject = new Audio.Sound();
      const status = { shouldPlay: true };
      await soundObject.loadAsync({ uri }, status, true);
      setSoundObject(soundObject);
      soundObject.playAsync();
      setIconSwitch(faPause);

      // RESUME PLAY
    } else if (isPlaying === false && soundObject != null) {
      dispatch(playPause(true));
      soundObject.playAsync();
      setIconSwitch(faPause);
    }
    setIsLoading(false);
  };

  console.log(isPlaying);

  if (isLoading) {
    return <SmallIndicator />;
  }

  return (
    <TouchableOpacity onPress={handlePlayPause}>
      <FontAwesomeIcon icon={iconSwitch} size={35} color={Colors.primary} />
    </TouchableOpacity>
  );
};

export default PlayPause;

PlayPause 组件位于我的 SongItem 组件中,我不会添加不适用的代码。

const SongItem = (props) => {
  return (
    <View>
      <PurchaseModal
        visible={modalToggle}
        purchaseSelector={purchaseSelector}
        radio_props={LicenseData}
        onPress={modalToggleHandler}
      />
      <View>
        <Card>
          <BodyText>{props.items.name}</BodyText>
          <View style={styles.innerContainer}>
            <PlayPause audioFile={props.items.audio} />
            <TouchableOpacity onPress={cartPress}>
              <FontAwesomeIcon
                icon={iconSwitch}
                size={35}
                color={Colors.primary}
              />
            </TouchableOpacity>
          </View>
          <TouchableOpacity onPress={modalToggleHandler} style={toggleStyle}>
            <FontAwesomeIcon
              icon={faFileInvoice}
              size={35}
              color={Colors.primary}
            />
          </TouchableOpacity>
        </Card>
      </View>
    </View>
  );
};

SongItem 位于我的 SongScreen 上。当我调用 dispatch(stopPlay) 时,我将 isPlaying 切换为 false;

const SongScreen = (props) => {
  const filteredSongs = useSelector((state) => state.filter.filteredSongs);
  const { goBack } = props.navigation;
  const dispatch = useDispatch();
  const backPress = () => {
    dispatch(stopPlay());
    goBack();
  };

  useEffect(() => {
    props.navigation.addListener("didBlur", () => {
      dispatch(stopPlay());
    });
  });

    return (
      <Gradient>
        <FlatList
          removeClippedSubviews={false}
          windowSize={2}
          maxToRenderPerBatch={6}
          data={filteredSongs}
          keyExtractor={(item) => item.id.toString()}
          renderItem={(itemData) => <SongItem items={itemData.item} />}
        />
        <MainButton name={"Back"} onPress={backPress} />
      </Gradient>
    );
  }
};

标签: javascriptfirebasereact-nativecode-cleanupexpo-av

解决方案


我一直在努力应对同样的挑战,甚至直接询问 expo 并检查了他们的源代码,以发现无法取消并且已经加载音频。

我所做的是在setOnPlaybackStatusUpdate回调的帮助下解决它。

解释:如果我想先取消一首歌曲,我必须等到它加载setOnPlaybackStatusUpdate完毕,并且在它完成加载后可以立即停止和卸载音频。

所以你的stopPlay功能对我来说是这样的:

 try{
        await audio.stopAsync();
        audio.setOnPlaybackStatusUpdate(null);
    }catch(e){
        //Error thrown if audio is still loading
        //Wait until it has finished loading and stop it
        const stopListener = new StopListener();
        audio.setOnPlaybackStatusUpdate(stopListener.getListener())
    }

StopListener 看起来像这样

class StopListener {

    getListener = () => async (status) => {
        const audio = ... //Get the loading audio object here

        audio.setOnPlaybackStatusUpdate(null);
        await audio.stopAsync();
    }
}

如果您找到任何其他解决方案,请分享。


推荐阅读