React Native/Firebase/Expo Audio - 在下载开始并且用户离开页面后取消 loadAsync

React Native/Firebase/Expo Audio - Cancel loadAsync after download starts and user navigates away from page

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

更新我的播放暂停处理程序 尽管我觉得有更好的方法,但我已经找到了解决方法。我现在打电话给 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>
    );
  }
};

看来您没有正确使用 React 生命周期。

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

这是不正确的(至少它没有按照评论所说的去做)。相反,它说:当 isPlaying 标志改变时,然后执行 ifPlaying.

你需要这样的东西来停止你的异步代码:

  // STOP PLAY ON PAGE EXIT
  useEffect(() => {
    return cleanUp() {
       ifPlaying();
    }
  });

您 return 在 useEffect 中所做的任何事情都将在您的组件被销毁之前执行。

我一直在努力应对同样的挑战,甚至直接询问了 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();
    }
}

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