Проблемы со звуком Javascript - PullRequest
0 голосов
/ 18 апреля 2020

Я пишу интерфейс для игрового движка в Javascript. Движок запускается на сервере и отправляет изображения и звуки в веб-браузер через «SignalR». Я использую платформу React.

При запуске игры сервер отправляет небольшие звуковые сэмплы в формате WAVE, передаваемые в этот компонент через AudioPlayerProps.

У меня две основные проблемы со звуком , Во-первых, звук звучит как «несвязанный». И второе: через некоторое время звук просто перестает играть. Я вижу звук в очереди аудио, но метод playNextAudioTrack не вызывается. В консоли нет ошибок, объясняющих это.

Если это не лучший способ обеспечить звук для игрового интерфейса, пожалуйста, дайте мне знать.

Также, если вы хотите увидеть больше кода, пожалуйста, дайте мне знать. Это огромный многоуровневый проект, поэтому я показываю только то, что, я думаю, вам нужно увидеть.

Сейчас я тестирую в Chrome. На этом этапе мне нужно включить инструменты DEV, чтобы обойти «пользователь не взаимодействовал со страницей, поэтому вы не можете воспроизвести любую проблему со звуком». Я разберусь с этим вопросом в свое время.

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;
}

1 Ответ

0 голосов
/ 21 апреля 2020

Я решил свою проблему. Вот класс машинописи, который я написал для обработки фрагментированного аудио в JavaScript.

Я не эксперт 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;
    }
}
...