如何实现React Native中音频互斥播放:点击新音频时停止旧音频
解决Expo React Native多音频同时播放问题
问题核心在于每个SoundPlayer组件独立维护自身的音频实例,互相无法感知对方的播放状态,导致多个音频同时播放。以下是两种可行的解决方案:
方案一:状态提升到父组件(简单场景适用)
将音频播放的控制逻辑统一放到父组件中,由父组件维护当前播放的音频实例,确保同一时间仅一个音频播放。
1. 修改父组件,统一管理音频播放
import React, { useState } from 'react'; import { Audio } from 'expo-av'; import SoundPlayer from './SoundPlayer'; import English from './English'; import NoteDesc from './NoteDesc'; export default function YourParentComponent({ route }) { const [currentPlayingSound, setCurrentPlayingSound] = useState(null); const handlePlaySound = async (voice) => { // 停止并卸载当前播放的音频 if (currentPlayingSound) { await currentPlayingSound.stopAsync(); await currentPlayingSound.unloadAsync(); } // 创建新音频实例并播放 const { sound: newSound } = await Audio.Sound.createAsync(voice); setCurrentPlayingSound(newSound); await newSound.playAsync(); // 监听播放结束,自动清理状态 newSound.setOnPlaybackStatusUpdate((status) => { if (status.didJustFinish) { setCurrentPlayingSound(null); newSound.unloadAsync(); } }); }; return ( <> <SoundPlayer onPlay={() => handlePlaySound(route.params.voice)}> <English English={route.params.English} /> </SoundPlayer> <SoundPlayer onPlay={() => handlePlaySound(route.params.voice2)}> <NoteDesc noteDesc={route.params.noteDesc} /> </SoundPlayer> </> ); }
2. 简化SoundPlayer组件
去掉内部的音频处理逻辑,仅负责触发父组件传递的播放回调:
import React from "react"; import { TouchableOpacity, StyleSheet, View } from "react-native"; export default function SoundPlayer({ onPlay, children }) { return ( <View> <TouchableOpacity onPress={onPlay}>{children}</TouchableOpacity> </View> ); } const styles = StyleSheet.create({ container: {}, English: { color: "snow", fontSize: 30, textAlign: "center", }, });
方案二:使用Context全局管理音频(复杂场景适用)
当应用中有多个音频播放组件,需要跨组件统一控制时,使用React Context更灵活。
1. 创建音频Context
// AudioContext.js import React, { createContext, useState, useContext } from 'react'; import { Audio } from 'expo-av'; const AudioContext = createContext(); export function AudioProvider({ children }) { const [currentSound, setCurrentSound] = useState(null); const playSound = async (voice) => { // 停止当前音频 if (currentSound) { await currentSound.stopAsync(); await currentSound.unloadAsync(); } // 创建并播放新音频 const { sound: newSound } = await Audio.Sound.createAsync(voice); setCurrentSound(newSound); await newSound.playAsync(); // 播放结束自动清理 newSound.setOnPlaybackStatusUpdate((status) => { if (status.didJustFinish) { setCurrentSound(null); newSound.unloadAsync(); } }); }; return ( <AudioContext.Provider value={{ playSound }}> {children} </AudioContext.Provider> ); } export function useAudio() { return useContext(AudioContext); }
2. 在应用根组件包裹AudioProvider
// App.js import { AudioProvider } from './AudioContext'; import YourParentComponent from './YourParentComponent'; export default function App() { return ( <AudioProvider> <YourParentComponent /> </AudioProvider> ); }
3. 修改SoundPlayer组件使用Context
import React from "react"; import { TouchableOpacity, StyleSheet, View } from "react-native"; import { useAudio } from './AudioContext'; export default function SoundPlayer({ voice, children }) { const { playSound } = useAudio(); const handlePlay = () => { playSound(voice); }; return ( <View> <TouchableOpacity onPress={handlePlay}>{children}</TouchableOpacity> </View> ); } const styles = StyleSheet.create({ container: {}, English: { color: "snow", fontSize: 30, textAlign: "center", }, });
4. 父组件恢复原写法
无需额外修改,直接使用原组件结构即可:
<SoundPlayer voice={route.params.voice}> <English English={route.params.English} /> </SoundPlayer> <SoundPlayer voice={route.params.voice2}> <NoteDesc noteDesc={route.params.noteDesc} /> </SoundPlayer>
内容的提问来源于stack exchange,提问作者sahasrabhuja neuron
相关产品推荐
相关产品推荐

