需要
还是上个学校的我的项目,须要实时播放音频。
拾音器有一个 rtsp 的地址,而后去找了很多插件,前端能够间接解析用的,尝试了很多办法,ff 什么的都没有实现,可能是没有找对办法,我感觉再这么上来必定不能按期交付了,所以和后盾沟通了一下,还是通过 websocket 传输二进制,前端拿到后再解析,而后去播放。
ok,有思路了就开始实现。
实现步骤
- websocket 连贯,这里 websocket 不做过多解释,具体可看上篇文章。
// 因为需要是须要点击之后开始播放,所以须要在 useEffect 中写,而是在点击事件里。const playAudio = async (data: any) => {const ws2 = new WebSocket(url2);
setAudioSocket(() => ws2)
}
- 定义相干回调函数
useEffect(() => {if (audioSocket) {
audioSocket.binaryType = 'blob';
audioSocket.onopen = () => {console.log('音频链接上拉!!')
};
audioSocket.onmessage = (data: any) => audioAcceptMessage(data);
audioSocket.onclose = () => audioWebSocketOnClose();
audioSocket.onerror = () => audioWebSocketOnError();
return () => {audioSocket.close();
receivedBlobs = []}
}
}, [audioSocket])
3. 解决音频数据,过后有尝试过各种缓存的 api 办法,然而都不现实。所以尝试了其余形式
// 该链接后盾返回的是 blob 对象,而且是不间断的始终传送,每次解析进去的音频只有 0.05 秒左右,所以须要思考做累加。const audioAcceptMessage = async ({data}: any) => {if (data) {console.log(data, '数据失常传输中')
// receivedBlobs 为全局定义的数组,用来存储后盾的数据
receivedBlobs.push(data)
} else {// 无接收数据}
}
4. 实现形式 1,手动缓存,并解决播放。
const renderAudio = async () => {if (receivedBlobs.length === 0) return false;
// 如果数组的缓存过小,播放工夫会太短,所以这里做了 2 秒提早。// 注:后盾是不间断的始终传输二进制数据的。if (receivedBlobs.length < 10) {setAudioSrcLoading(true);
setTimeout(() => {renderAudio();
}, 2000)
return false;
}
const blob = new Blob([...receivedBlobs], {type: 'audio/mp3'});
receivedBlobs = [];
// 这个 url 曾经能够被 audio 标签间接辨认播放了
const url = URL.createObjectURL(blob);
setAudioSrc(url)
// 以下步骤次要为了获取到这段音频的播放时长,以供触发下一个定时器
const temporaryAudio = new Audio();
temporaryAudio.src = url;
temporaryAudio.addEventListener('loadedmetadata', () => {
let time = temporaryAudio.duration * 1000;
audioTimeout = setTimeout(() => {renderAudio();
}, time)
});
}
<audio src={audioSrc} autoPlay></audio>
毛病:每播放几秒钟,就会缓冲一下 (数据处理工夫和赋值 url 工夫)。好吧,体验不过关。然而的确实现了。
5. 实现形式 2:想到了一个偏方,如果 1 个播放器会卡顿,那两个呢,两个做连接播放呢,有想法了,开干。
// 实时音频播放地址
const [audioSrc, setAudioSrc] = useState<any>();
// 实时音频播放地址 2
const [audioSrc2, setAudioSrc2] = useState<any>();
// 实时音频是否缓冲
const [audioSrcLoading, setAudioSrcLoading] = useState<boolean>(false);
const renderAudio = async () => {if (receivedBlobs.length === 0) return false;
if (receivedBlobs.length < 10) {setAudioSrcLoading(true);
setTimeout(() => {renderAudio();
}, 2000)
return false;
}
const blob = new Blob([...receivedBlobs], {type: 'audio/mp3'});
receivedBlobs = [];
const url = URL.createObjectURL(blob);
// 这里用开关辨别,下一次赋值给哪一个 url
isAudioSrcFlag ? setAudioSrc(url) : setAudioSrc2(url);
isAudioSrcFlag = !isAudioSrcFlag;
const temporaryAudio = new Audio();
temporaryAudio.src = url;
temporaryAudio.addEventListener('loadedmetadata', () => {
let time = temporaryAudio.duration * 1000;
// 这里在下一段完结之前 提前播放,如果不提前播放,那还是会和第一种形式一样,没有扭转。const connectTime = 200; // 连接播放提前时长
time = time > connectTime ? time - connectTime : time;
audioTimeout = setTimeout(() => {renderAudio();
}, time)
console.log(`--------- 此段音频时长为:${temporaryAudio.duration} 秒,下段 ${time / 1000} 秒后播放 }`);
});
}
<audio src={audioSrc} autoPlay></audio>
<audio src={audioSrc2} autoPlay></audio>
稍微革新,性能实现了,嗯 …. 还是有个问题,因为这 200 毫秒是通过测试当前,最不卡顿的临界值,所以每个下一次的播放都会提前,也就会导致缓存数据越来越少,在通过一段时间之后,还是须要缓冲一下,能力持续播放,还是没达到现实状态。好吧,看来这种形式不太能达到本人想要的。
那就持续优化!
6. 实现形式 3:这次采纳了 api 播放音频,不必 audio 标签了。
const renderAudio = async () => {if (receivedBlobs.length === 0) return false;
if (receivedBlobs.length < 10) {setAudioSrcLoading(true);
setTimeout(() => {renderAudio();
}, 2000)
return false;
}
const fileReader = new FileReader()
fileReader.onload = () => {
const arrayBuffer = fileReader.result as ArrayBuffer;
audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => {const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();});
}
setAudioSrcLoading(false);
const blob = new Blob([...receivedBlobs], {type: 'audio/mp3'});
fileReader.readAsArrayBuffer(blob)
receivedBlobs = [];
const url = URL.createObjectURL(blob);
const temporaryAudio = new Audio();
temporaryAudio.src = url;
temporaryAudio.addEventListener('loadedmetadata', () => {
let time = temporaryAudio.duration * 1000;
// 采纳这种形式做连接播放,50 毫秒就能够做到不卡顿,应该是省去了 audio 标签解决的工夫
const connectTime = 50; // 连接播放提前时长
time = time > connectTime ? time - connectTime : time;
audioTimeout = setTimeout(() => {renderAudio();
}, time)
console.log(`--------- 此段音频时长为:${temporaryAudio.duration} 秒,下段 ${time / 1000} 秒后播放 }`);
});
}
论断:性能实现,尽管也须要缓冲,然而做到了大家都能承受的范畴,很长很长一段时间后才会缓冲一次,缓冲的距离很长,所以也还算胜利吧。
over!
心愿能帮忙到你们。如果大家有更好的方法,能够交换,或者斧正。