问题:
在ios的浏览器,audio无法正常的播放音频(但是有些ios设备可以正常播放出来),其中有一次报错为Uncaught (in promise) {"name": "NotAllowedError", "message": "The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.", "stack": "play@[native code]\nkt@https://pingtanchat.10wei.top/static/js/index-C3rnxwNY.js:148...9"}
描述:
大概的逻辑为,初始化 MediaSource 并设置播放器的源,接收音频数据并将其添加到队列中,然后逐个处理队列中的数据,监听audio-play,设置player播放和暂停,audio-play的触发时机是在获取到音频数据的时候,触发一次,以下是代码
const MyAudio = () => {
const player = useRef(null);
let queue = useRef([]); // 队列,用于管理待处理的音频数据
let sourceBuffer = useRef(null);
let mediaSource = useRef(null);
// 处理队列中的数据
const processQueue = () => {
if (queue.current.length > 0 && !sourceBuffer.current.updating) {
sourceBuffer.current.appendBuffer(queue.current.shift());
}
};
// 接收二进制的音频数据,并读取
useEventBus('Global/tts_addSourceBuffer', (data) => {
const reader = new FileReader(); // 创建 FileReader 对象读取二进制数据
reader.onload = () => {
const arrayBuffer = reader.result; // 获取读取结果(ArrayBuffer)
queue.current.push(arrayBuffer); // 将数据添加到队列
processQueue(); // 处理队列中的数据
};
reader.readAsArrayBuffer(data); // 读取 Blob 数据为 ArrayBuffer
});
// 初始化
useEventBus('Global/initSourceBuffer', () => {
safeExecute(() => {
mediaSource.current = new MediaSource(); // 创建 MediaSource 实例
player.current.src = URL.createObjectURL(mediaSource.current); // 设置播放器的源为 MediaSource 对象
});
// 处理 sourceopen 事件
const handleSourceopen = () => {
sourceBuffer.current = mediaSource.current.addSourceBuffer('audio/mpeg');
sourceBuffer.current.addEventListener('updateend', processQueue);
};
player.current.addEventListener('error', (e) => {
console.error('数字人播放音频错误:', e);
});
// 监听 MediaSource 的 sourceopen 事件
mediaSource.current.addEventListener('sourceopen', handleSourceopen);
});
// 监听 播放和暂停
useEventBus('Global/audio-play', ({ type }) => {
if (type === 'start') {
player.current &&
player.current.play().catch((error) => {
console.error('播放失败:', error);
alert('由于浏览器安全策略,音频无法自动播放,请点击播放按钮手动播放。');
});
} else if (type === 'end') {
player.current && player.current.pause();
}
});
const isMute = useSelector((state) => state.chat.isMute);
useEffect(() => {
player.current.muted = !!isMute;
}, [isMute]);
return <audio ref={player} src="" />;
};
希望可以有一个兼容ios的方案,因为业务逻辑是需要在有音频数据的时候,自动播放音频,不太可能去手动点击,播放音频
修改部分
下方代码是修改版本,可以自动播放,并且有声音了,但是出现一个问题,如果blobQueue.current.shift()取到的blob数据太多,audio播放的音频时间长,会出现播放的过程中突然没有声音的问题,并且进度播完的时候,不会触发onEnded,有没有大佬懂这是什么原因
import { useEventBus } from 'event-bus-hooks';
import { useSelector } from 'react-redux';
const IosAudio = () => {
const player = useRef(null);
const blobQueue = useRef([]); // 用于存储 Blob 的队列
const currentIndex = useRef(0);
const handlePlay = () => {
if (currentIndex.current > blobQueue.current.length - 1) {
currentIndex.current = 0;
return;
}
console.log('------------ 下一个 -------------');
let obj = blobQueue.current.shift();
console.log(obj, obj.length);
const blob = new Blob(obj, { type: 'audio/mp3' }); // 合并队列中的 Blob
player.current.src = URL.createObjectURL(blob);
player.current.load();
};
// 接收二进制的音频数据(Blob 格式),并处理
useEventBus('Global/tts_BlobList', (blobDataArr) => {
blobQueue.current.push(blobDataArr);
});
const handleStop = () => {
if (!player.current) return;
blobQueue.current = [];
player.current.pause();
// 释放资源
URL.revokeObjectURL(player.current.src);
};
const loadedmetadata = () => {
console.log('音频元数据加载完成');
player.current
.play()
.then(() => {
console.log('播放成功');
})
.catch((error) => {
console.error('播放失败:', error);
});
};
// 播放结束
const onended = () => {
currentIndex.current++;
handlePlay();
};
// 监听播放和暂停
useEventBus('Global/audio-play', ({ type }) => {
if (type === 'start') {
handlePlay();
} else if (type === 'end') {
handleStop();
}
});
const isMute = useSelector((state) => state.chat.isMute);
useEffect(() => {
player.current.muted = !!isMute;
}, [isMute]);
return (
<audio
ref={player}
src=""
onLoadedMetadata={loadedmetadata}
onEnded={onended}
onError={(e) => console.error('数字人播放音频错误:', e.message)}
controls
/>
);
};
export default IosAudio;