socket事件触发音频播放不稳定,伴AudioContext未授权启动报错
问题:Socket事件触发时音频播放不稳定,报错AudioContext未授权启动
问题描述
Socket事件触发时尝试播放音频,存在播放不稳定的情况,有时正常有时失败,控制台报错:
"The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page"
原代码:
import React, { useEffect, useState } from "react"; import { useSound } from "use-sound"; import notificationSound from "my_sound.mp3"; import { io } from "socket.io-client"; const Home = ({ children }) => { const [play] = useSound(notificationSound, { volume: 0.5 }); const [playAudio, setPlayAudio] = useState(false); useEffect(() => { if (playAudio === true) { play(); setPlayAudio(false); } }, [playAudio]); useEffect(() => { const socket = io("http://localhost:8081", { auth: { token: localStorage.getItem("user_token"), }, }); socket.on('play-sound', (data) => { setPlayAudio(true); toast.success(`New Order ${data.order_id} Received!!`); }); }, []); return (<div> Hello World </div>) }; module.export = Home;
原因分析
浏览器的自动播放安全策略规定:AudioContext必须在用户主动交互(如点击、触摸页面)之后才能创建或恢复,否则会被浏览器阻塞。你的代码中,use-sound初始化的AudioContext可能还未经过用户交互授权,当Socket事件触发调用play()时,就会触发这个错误。
解决方案
通过用户主动交互激活AudioContext,同时在播放前检查上下文状态,确保可以正常播放。以下是修改后的代码:
import React, { useEffect, useState, useRef } from "react"; import { useSound } from "use-sound"; import notificationSound from "my_sound.mp3"; import { io } from "socket.io-client"; const Home = ({ children }) => { // 解构出sound对象,用于访问底层AudioContext const [play, { sound }] = useSound(notificationSound, { volume: 0.5, interrupt: true }); const [audioActivated, setAudioActivated] = useState(false); // 用useRef保存socket实例,避免重复创建 const socketRef = useRef(null); // 处理用户交互,激活音频上下文 const activateAudio = () => { if (sound && sound.context.state === "suspended") { sound.context.resume(); } setAudioActivated(true); }; useEffect(() => { socketRef.current = io("http://localhost:8081", { auth: { token: localStorage.getItem("user_token"), }, }); socketRef.current.on('play-sound', (data) => { // 播放前检查AudioContext状态,若暂停则先恢复 if (sound && sound.context.state === "suspended") { sound.context.resume().then(() => play()); } else { play(); } toast.success(`New Order ${data.order_id} Received!!`); }); // 组件卸载时断开socket连接 return () => socketRef.current?.disconnect(); }, [play, sound]); return ( <div> {/* 首次加载显示激活按钮,用户点击后授权音频 */} {!audioActivated && ( <button onClick={activateAudio}> 点击激活通知音效 </button> )} Hello World {children} </div> ); }; export default Home;
关键修改说明
- 获取AudioContext实例:从
use-sound的返回值中解构sound对象,直接访问底层的AudioContext。 - 用户交互激活:添加激活按钮,用户首次点击时恢复处于
suspended状态的AudioContext,标记为已激活。 - Socket回调中检查状态:在触发播放前先判断AudioContext状态,如果还处于暂停状态,先调用
resume()再执行播放。 - 优化Socket实例管理:用
useRef保存Socket实例,避免组件重复渲染时重新创建连接,同时在组件卸载时断开连接。
内容的提问来源于stack exchange,提问作者pushkraj_8
相关产品推荐
相关产品推荐

