React Native Expo 切换音频开关

3
我想在我的React Native Expo应用程序中切换声音,但是当我多次点击开关时会导致声音重叠,我不确定是否需要在音频上设置状态。我似乎找不到任何好的示例- https://docs.expo.io/versions/latest/sdk/av/ 不确定我做错了什么,对RN很新手。
正如您所看到的,这是一个头部按钮,必须在整个应用程序中工作,而不仅仅是在一个屏幕上。
这是我的小吃店 - https://snack.expo.io/@roshambo/sound-toggle-on-and-off
import React, { useState } from "react";
import { HeaderButton } from "react-navigation-header-buttons";
import { Ionicons } from "@expo/vector-icons";
import { Audio } from "expo-av";

const CustomHeaderButton = props => {
const [soundIcon, setSoundIcon] = useState(false);

const soundObject = new Audio.Sound();
(async () => {
  await soundObject.loadAsync(require("../assets/bglaughs.mp3"), {
    volume: 0.25,
    isLooping: true,
    shouldPlay: true,
    isMuted: true
  });
})();

const toggleSound = () => {
  setSoundIcon(prevState => !prevState);
  soundObject.setOnPlaybackStatusUpdate(this._onPlaybackStatusUpdate);
};

_onPlaybackStatusUpdate = playbackStatus => {
  if (playbackStatus.isMuted) {
    playSound();
  } else {
    stopSound();
  }
};

const stopSound = async () => {
  try {
    await soundObject.stopAsync();
  } catch (error) {
    console.log("sound couldn't pause");
  }
};

const playSound = async () => {
  try {
    await soundObject.setIsMutedAsync(false);
    await soundObject.playAsync();
  } catch (error) {
    console.log("sound couldn't play");
  }  
};

return (
<HeaderButton
  {...props}
  IconComponent={Ionicons}
  iconSize={23}
  iconName={soundIcon ? "ios-volume-high" : "ios-volume-off"}
  onPress={toggleSound}
  />
  );
};

export default CustomHeaderButton;

更新:

这是基于Oleg的答案以及我在redux上实现的工作代码。

组件

import React from "react";
import { HeaderButton } from "react-navigation-header-buttons";
import { useDispatch, useSelector } from "react-redux";

import * as soundActions from "../store/actions/sound-action";

import { Ionicons } from "@expo/vector-icons";

const CustomHeaderButton = props => {

  const sound = useSelector(state => state.sound.soundState);

  const dispatch = useDispatch();

  return (
    <HeaderButton
      {...props}
      IconComponent={Ionicons}
      iconSize={23}
      iconName={sound === "playing" ? "ios-volume-high" : "ios-volume-off"}
      onPress={() => {
        dispatch(soundActions.toggleSound(sound));
      }}
    />
  );
};

export default CustomHeaderButton;

减速器

import { TOGGLE_SOUND } from "../actions/sound-action";

import { Audio } from "expo-av";

const initialState = {
  soundState: "nosound"
};

export default (state = initialState, action) => {
  switch (action.type) {
    case TOGGLE_SOUND:
      if (state.soundState === "nosound") {
        (async () => {
          const { sound } = await Audio.Sound.createAsync(
            require("../../assets/bglaughs.mp3"),
            {
              shouldPlay: true,
              volume: 0.25,
              isLooping: true
            }
          );
          this.sound = sound;
        })();
        return {
          ...state,
          soundState: "playing"
        };
      } else if (state.soundState === "playing") {
        (async () => {
          if (this.sound !== null) {
            await this.sound.pauseAsync();
          }
        })();
        return { ...state, soundState: "donepause" };
      } else if (state.soundState === "donepause") {
        (async () => {
          await this.sound.playAsync();
        })();
        return { soundState: "playing" };
      }
  }
  return state;
};

你能提供与问题相关的 Expo Snack 吗? - Oleg
这是我的小吃 - https://snack.expo.io/@roshambo/sound-toggle-on-and-off - roshambo
嗨,我现在想知道我的组件虽然似乎适用于我的应用程序,但我是否需要redux-thunk来处理异步等待请求? - roshambo
是的,如果您想要像我之前描述的那样在Redux中实现。 - Oleg
我认为redux reducers只应该包含“干净的js”,而不调用外部文件/ api /加载库等... - Ben
啊,是的,我应该提取它并将其放在操作中,并将其作为有效载荷传递吗? - roshambo
2个回答

3

谢谢 Oleg,你的答案有效。我重构了它,使用了 React Hook 的 useState。我很快会重新发布我的答案! - roshambo
正如我的代码所示,我在应用程序的标题中使用了开关声音,因此我想知道是否最好使用redux来管理这个开关声音的全局状态?因为它似乎在一个堆栈中正常工作? - roshambo
如果你想的话,你也可以使用redux并查看像fetch api模式这样的声音api。另一种方法是仅使用组件的本地状态而不需要所有这些东西。 - Oleg
那么,Redux 是否是管理整个应用程序中声音状态的最佳选择?据我所知,我已经实现了 localState,但它只适用于一个屏幕。 - roshambo
如果您已经开始在应用程序中使用redux,并且想要将声音API功能分离以分享到应用程序级别,则可以创建声音API服务并像使用fetch API一样使用它的方式/风格... - Oleg

2
阅读了Expo文档后,我想到了这个解决方案。
import React, { useState } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import { Audio } from 'expo-av';
import { MaterialIcons } from '@expo/vector-icons';

const soundObject = new Audio.Sound();

export default AudioFile = (props) => {
  const [isPlaying, setIsPlaying] = useState(false);
  const [isLoaded, setIsLoaded] = useState(false);
  const [icon, setIcon] = useState('play-arrow');

  const playAudioHandler = async (url) => {
    if (isLoaded == true) {
      if (isPlaying == true) {
        await soundObject.pauseAsync();
        setIsPlaying(false);
        setIcon('play-arrow');
      } else {
        await soundObject.playAsync();
        setIsPlaying(true);
        setIcon('pause');
      }
    } else {
      try {
        await soundObject.loadAsync({ uri: url });
        await soundObject
          .playAsync()
          .then(async (playbackStatus) => {
            console.log(playbackStatus);
            setIcon('pause');
            setIsPlaying(playbackStatus.isPlaying);
            setIsLoaded(playbackStatus.isLoaded);
            setTimeout(() => {
              soundObject.unloadAsync();
              setIsLoaded(false);
              setIcon('play-arrow');
            }, playbackStatus.durationMillis);
          })
          .catch((error) => {
            console.log(error);
          });
      } catch (error) {
        console.log(error);
      }
    }
  };

1
我认为您犯了一个错误,应该是playbackStatus.durationMillis而不是palybackStatus.playableDurationMillis。 - Zeyad Shaban

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接