如果是socket.io的话,如下:
const socket = io('yourServerAddress');
socket.on('yourEvent', audioFrameData => {
如果用的是websocket的话,如下:
const ws = new WebSocket('yourServerAddrsss');
ws.onmessage = (event) => {
不管是socket.io还是websocket,只是传输协议的不同,播放逻辑都是一样的。
下面的播放代码都有socket.io做样例,不再一一说明。
网上搜到的大多是这种,通过AudioContext解码音频帧数据,然后创建BufferSource去逐帧播放。
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
let audioBufferChunks = [];
let audioStarted = false;
const socket = io('yourServerAddress');
socket.on('yourEvent', audioFrameData => {
const arrayBufferMeta = stringToArrayBuffer(audioFrameData, 'hex');
audioContext.decodeAudioData(arrayBufferMeta.buffer, (buffer) => {
audioBufferChunks.push(buffer)
if (audioStarted === false) {
playSound()
function playSound() {
try {
if (audioBufferChunks.length !== 0) {
audioStarted = true;
const source = audioContext.createBufferSource();
source.buffer = audioBufferChunks[0]
source.connect(audioContext.destination)
source.start(0)
source.addEventListener('ended', () => {
audioBufferChunks.splice(0, 1);
playSound();
} else {
audioStarted = false;
} catch (e) {
console.log(e)
audioStarted = false;
function stringToArrayBuffer(string, type) {
if (type === 'base64') {
if (type === 'hex') {
const hexString = string.replace(/^0x/, '');
if (hexString.length % 2 != 0) {
console.log('WARNING: expecting an even number of characters in the hexString');
const bad = hexString.match(/[G-Z\s]/i);
if (bad) {
console.log('WARNING: found non-hex characters', bad);
const pairs = hexString.match(/[\dA-F]{2}/gi);
const integers = pairs.map(function (s) {
return parseInt(s, 16);
const array = new Uint8Array(integers);
return array;
这个方法可用,但是有一个很严重的问题,帧与帧之间切换的时候,会有爆音,非常明显,根本不能用于生产环境。
当时试着在帧与帧之间加淡入淡出效果,一定程序上缓解了爆音的问题,但随之而来的是播放过程的声音间接性的糊一下糊一下的。
帧与帧之间淡入淡出效果实现如下:
* 前面省略
const source = audioContext.createBufferSource();
const currBuffer = audioBufferChunks[0];
let currChannel = currBuffer.getChannelData(0);
const sd = 48000 / 1000 * 1.5;
const ed = currChannel.lenght - sd;
for (let i=0; i < currChannel.lenght; i++) {
let factor = 1;
if (i < sd) {
factor = i / sd;
} else if (i > ed) {
factor = (currChannel.lenght - 1) / sd
currChannel[i] = currChannel[i] * factor
source.buffer = currBuffer;
* 后面省略
经过上面这两番折腾,老板还是不放过!爆音不能接受!声音忽高忽低也不能接受!直到无意间在MDN上看到这个「实验中的功能」,彻底拯救了我!
神奇的实验中的功能点这里
用法如下:
首先,写一个audio
标签,设为;
不显示
<audio id="audioPlay" style=" !important" controls autoplay></audio>
js 部分
const mineCodec = 'audio/aac';
let sourceBuffer;
if ('MediaSource' in window && MediaSource.isTypeSupported(mineCodec)) {
const mediaSource = new MediaSource();
const audioPlay = document.getElementById('audioPlay');
audioPlay.src = window.URL.createObjectURL(mediaSource)
mediaSource.addEventListener('sourceopen', () => {
sourceBuffer = mediaSource.addSourceBuffer(mineCodec)
} else {
console.error('unsupported MIME type or codec: ', mineCodec)
socket.on('yourEvent', (audioFrameData) => {
const arrayBufferMeta = stringToArrayBuffer(audioFrameData, 'hex');
sourceBuffer.appendBuffer(arrayBufferMeta);
后来,公司某个项目中用了国内某摄像头厂家的解决方案,发现他们的网页播放器用的也是方法二,算是大厂背书了吧。此坑已填好,从此我与音视频是路人了,不想再碰音视频!!!