乐趣区

关于javascript:实时音频播放

需要

还是上个学校的我的项目,须要实时播放音频。

 拾音器有一个 rtsp 的地址,而后去找了很多插件,前端能够间接解析用的,尝试了很多办法,ff 什么的都没有实现,可能是没有找对办法,我感觉再这么上来必定不能按期交付了,所以和后盾沟通了一下,还是通过 websocket 传输二进制,前端拿到后再解析,而后去播放。

ok,有思路了就开始实现。

实现步骤

  1. websocket 连贯,这里 websocket 不做过多解释,具体可看上篇文章。
// 因为需要是须要点击之后开始播放,所以须要在 useEffect 中写,而是在点击事件里。const playAudio = async (data: any) => {const ws2 = new WebSocket(url2);
        setAudioSocket(() => ws2)
    }
  1. 定义相干回调函数
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!
心愿能帮忙到你们。如果大家有更好的方法,能够交换,或者斧正。

退出移动版