You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于WebSocket与Audio APIs的iOS实时音频流播放异常问题

iOS上实时音频流播放异常问题

我正在开发一款基于WebSocket与音频API的实时音频流应用,该应用通过WebSocket接收ElevenLabs API返回的音频分片并顺序播放。该应用在多数平台运行正常,但在iOS设备上,音频要么无法播放,要么存在明显延迟。

已尝试方案:

  • 使用Web Audio API:尝试直接通过AudioContext播放音频分片,但iOS对实时流存在兼容性问题。
  • 转换为Blob:将接收的音频分片转换为Blob,通过<audio>元素播放,虽能运行但分片间延迟过高,不符合实时要求。
let connectButton = null;
let disconnectButton = null;
let sendButton = null;
let receiveBox = null;
let inputText = null;
let audioPlayer = document.getElementById("audioPlayer");

let ws = null;
let audioQueue = []; 
let isPlaying = false; 

const ELEVENLABS_WS_URL = "wss://api.elevenlabs.io/v1/text-to-speech/XrExE9yKIg1WjnnlVkGX/stream-input?model_id=eleven_multilingual_v1&inactivity_timeout=30&language=English_American";

function startup() {
    connectButton = document.getElementById("connectButton");
    disconnectButton = document.getElementById("disconnectButton");
    sendButton = document.getElementById("sendButton");
    receiveBox = document.getElementById("receive-box");
    inputText = document.getElementById("inputText");
    audioPlayer = document.getElementById("audioPlayer");

    connectButton.addEventListener("click", initializeWebSocket, false);
    disconnectButton.addEventListener("click", closeWebSocket, false);
    sendButton.addEventListener("click", sendMessage, false);
}

function initializeWebSocket() {
    if (!ws || ws.readyState === WebSocket.CLOSED) {        
        ws = new WebSocket(ELEVENLABS_WS_URL);

        ws.onopen = () => {
            console.log("Connected to ElevenLabs WebSocket.");
            connectButton.disabled = true;
            disconnectButton.disabled = false;
        };

        ws.onmessage = handleWebSocketMessage;
        ws.onerror = (error) => console.error("WebSocket error:", error);
        ws.onclose = () => {
            console.log("Disconnected from ElevenLabs WebSocket.");
            connectButton.disabled = false;
            disconnectButton.disabled = true;
        };
    }
}

function closeWebSocket() {
    if (ws) {
        ws.close();
        console.log("WebSocket connection closed.");
    }
}

function sendMessage() {
    const text = inputText.value.trim();
    if (text) {
        const message = {
            text: text,
            voice_settings: {
                stability: 1,
                similarity_boost: true,
                optimize_streaming_latency: 0
            },
            flush: true,
            xi_api_key: "",
            streaming: true
        };

        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify(message));
        } else {
            console.error("WebSocket is not connected.");
        }
    }
}

function handleWebSocketMessage(event) {
    const audioData = JSON.parse(event.data);
    console.log('audioData: ', typeof audioData);
    
    if (audioData && audioData.audio) {
        const base64Audio = audioData.audio;
        addAudioChunkToQueue(base64Audio);
    } else {
        console.error("No audio data received.");
    }
}

function addAudioChunkToQueue(base64Audio) {
    const audioBuffer = base64ToArrayBuffer(base64Audio);
    audioQueue.push(audioBuffer);

    if (!isPlaying) {
        playNextChunk();
    }
}

function playNextChunk() {
    if (audioQueue.length > 0) {
        isPlaying = true;
        const audioBuffer = audioQueue.shift();

        const audioBlob = new Blob([audioBuffer], { type: 'audio/mpeg' });
        const audioUrl = URL.createObjectURL(audioBlob);
        audioPlayer.src = audioUrl;
        audioPlayer.play()
            .then(() => {
                console.log("Playing audio chunk...");
                audioPlayer.onended = () => {
                    isPlaying = false;
                    playNextChunk(); 
                };
            })
            .catch((error) => {
                console.error("Error playing audio:", error);
                isPlaying = false;
            });
    } else {
        isPlaying = false;
    }
}

function base64ToArrayBuffer(base64) {
    console.log('Converting to arraybuffer');
    const binaryString = window.atob(base64);
    const len = binaryString.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes.buffer;
}

window.addEventListener("load", startup);

内容的提问来源于stack exchange,提问作者b_encora

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 14:47:35