Javascript的声音问题

Javascript的声音问题,javascript,reactjs,html5-audio,Javascript,Reactjs,Html5 Audio,我正在用Javascript编写一个游戏引擎的前端。引擎在服务器上运行,并通过“信号器”向web浏览器发送图片和声音。我正在使用React框架 当游戏运行时,服务器发送波形格式的小声音样本,通过AudioPlayerProps传递到此组件 我的声音有两个主要问题。首先,声音听起来“脱节”。 第二个是,过了一段时间,声音就停止了。我可以看到声音在音频队列中排队,但没有调用“playNextAudioTrack”方法。控制台中没有解释这一点的错误 如果这不是为游戏前端提供声音的最佳方式,请让我知道

我正在用Javascript编写一个游戏引擎的前端。引擎在服务器上运行,并通过“信号器”向web浏览器发送图片和声音。我正在使用React框架

当游戏运行时,服务器发送波形格式的小声音样本,通过AudioPlayerProps传递到此组件

我的声音有两个主要问题。首先,声音听起来“脱节”。 第二个是,过了一段时间,声音就停止了。我可以看到声音在音频队列中排队,但没有调用“playNextAudioTrack”方法。控制台中没有解释这一点的错误

如果这不是为游戏前端提供声音的最佳方式,请让我知道

此外,如果你想看到更多的代码,请让我知道。这是一个巨大的多层项目,所以我只展示我认为您需要看到的内容

现在我正在测试Chrome。在这个阶段,我需要打开开发工具来克服“用户没有与页面交互,所以你不能播放任何声音问题”。我会在适当的时候解决这个问题

import * as React from "react";
import { useEffect, useState } from "react";

export interface AudioPlayerProps {
    data: string;
}
export const AudioPlayer = function (props: AudioPlayerProps): JSX.Element {
    const [audioQueue, setAudioQueue] = useState<string[]>([])

    useEffect(
        () => {
            if (props.data != undefined) {
                audioQueue.push(props.data);
            }
        }, [props.data]);

    const playNextAudioTrack = () => {
        if (audioQueue.length > 0) {
            const audioBase64 = audioQueue.pop();

            const newAudio = new Audio(`data:audio/wav;base64,${audioBase64}`)
            newAudio.play().then(playNextAudioTrack).catch(
                (error) => {
                    setTimeout(playNextAudioTrack, 10);
                }
            )

        }
        else {
            setTimeout(playNextAudioTrack, 10);
        }
    }

    useEffect(playNextAudioTrack, []);

    return null;
}
import*as React from“React”;
从“react”导入{useffect,useState};
导出接口AudioPlayerProps{
数据:字符串;
}
export const AudioPlayer=函数(道具:AudioPlayerProps):JSX.Element{
常量[audioQueue,setAudioQueue]=useState([])
使用效果(
() => {
if(props.data!=未定义){
audioQueue.push(props.data);
}
},[道具数据];
const playnex taudiotrack=()=>{
如果(audioQueue.length>0){
const audioBase64=audioQueue.pop();
const newAudio=newAudio(`data:Audio/wav;base64,${audioBase64}`)
newAudio.play().then(playNextAudioTrack).catch(
(错误)=>{
设置超时(playNextAudioTrack,10);
}
)
}
否则{
设置超时(playNextAudioTrack,10);
}
}
useEffect(playNextAudioTrack,[]);
返回null;
}

我解决了自己的问题。下面是我编写的用JavaScript处理分块音频的typescript类

我不是JavaScript专家,因此可能存在错误

编辑:在15分钟内多次运行它之后,它在大约10分钟的时候失败了几次。还需要一些工作

// mostly from https://gist.github.com/revolunet/e620e2c532b7144c62768a36b8b96da2
// Modified to play chunked audio for games
import { setInterval } from "timers";

// 
const MaxScheduled = 10;
const MaxQueueLength = 2000;
const MinScheduledToStopDraining = 5;
export class WebAudioStreamer {

    constructor() {
        this.isDraining = false;
        this.isWorking = false;
        this.audioStack = [];
        this.nextTime = 0;
        this.numberScheduled = 0;

        setInterval(() => {
            if (this.audioStack.length && !this.isWorking) {
                this.scheduleBuffers(this);
            }
        }, 0);
    }

    context: AudioContext;
    audioStack: AudioBuffer[];
    nextTime: number;
    numberScheduled: number;
    isDraining: boolean;
    isWorking: boolean;

    pushOntoAudioStack(encodedBytes: number[]) {
        if (this.context == undefined) {
            this.context = new (window.AudioContext)();
        }

        const encodedBuffer = new Uint8ClampedArray(encodedBytes).buffer;
        const streamer: WebAudioStreamer = this;

        if (this.audioStack.length > MaxQueueLength) {
            this.audioStack = [];
        }

        streamer.context.decodeAudioData(encodedBuffer, function (decodedBuffer) {
            streamer.audioStack.push(decodedBuffer);
        }
        );
    }

    scheduleBuffers(streamer: WebAudioStreamer) {
        streamer.isWorking = true;

        if (streamer.context == undefined) {
            streamer.context = new (window.AudioContext)();
        }

        if (streamer.isDraining && streamer.numberScheduled <= MinScheduledToStopDraining) {
            streamer.isDraining = false;
        }

        while (streamer.audioStack.length && !streamer.isDraining) {
            var buffer = streamer.audioStack.shift();
            var source = streamer.context.createBufferSource();
            source.buffer = buffer;
            source.connect(streamer.context.destination);
            if (streamer.nextTime == 0)
                streamer.nextTime = streamer.context.currentTime + 0.01;  /// add 50ms latency to work well across systems - tune this if you like
            source.start(streamer.nextTime);

            streamer.nextTime += source.buffer.duration; // Make the next buffer wait the length of the last buffer before being played
            streamer.numberScheduled++;

            source.onended = function () {
                streamer.numberScheduled--;
            }

            if (streamer.numberScheduled == MaxScheduled) {
                streamer.isDraining = true;
            }

        };

        streamer.isWorking = false;
    }
}
//主要来自https://gist.github.com/revolunet/e620e2c532b7144c62768a36b8b96da2
//修改为播放游戏的分块音频
从“计时器”导入{setInterval};
// 
常量MaxScheduled=10;
常量MaxQueueLength=2000;
const minscheduledtospodraining=5;
导出类WebAudioStreamer{
构造函数(){
this.isDraining=false;
this.isWorking=false;
this.audioStack=[];
this.nextTime=0;
this.numberScheduled=0;
设置间隔(()=>{
if(this.audioStack.length&&!this.isWorking){
this.scheduleBuffers(this);
}
}, 0);
}
语境:听觉语境;
audioStack:AudioBuffer[];
下一时间:数字;
编号计划:编号;
istraining:布尔型;
isWorking:布尔;
pushOntoAudioStack(编码字节:数字[]){
if(this.context==未定义){
this.context=new(window.AudioContext)();
}
const encodedBuffer=新的Uint8ClampedArray(encodedBytes).buffer;
const streamer:WebAudioStreamer=this;
if(this.audioStack.length>MaxQueueLength){
this.audioStack=[];
}
拖缆.上下文.解码音频数据(encodedBuffer,函数(decodedBuffer){
拖缆.音频堆栈.推送(解码缓冲);
}
);
}
scheduleBuffers(拖缆:WebAudio拖缆){
streamer.isWorking=true;
if(streamer.context==未定义){
streamer.context=new(window.AudioContext)();
}

如果(streamer.isTraining&&streamer.numbers按计划尝试使用canplay和ended挂钩来播放和清理音频标签。我没有任何实际经验,因此我不知道它对这种实时敏感的声音播放有什么影响,但我无法想象它会与React状态批处理和信号器套接字数据交互一起工作。我我有在多个平台浏览器上移植Web Audio API和声音播放应用程序的经验:在一些有缺陷的平台上,通过将播放放在setTimeout调用中来解决播放问题。不过,我认为,将它们放在错误处理程序中会导致问题。我使用了Web Audio回退