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

Blazor+JSInterop录音时长超3-4秒时停止异常求助

Blazor JSInterop录音长音频时WebSocket中断问题解决建议

问题描述

开发支持20秒内短音频录制的Blazor应用,通过JSInterop实现录音功能。当前仅录音时长<3-4秒时能正常停止;录音更长时,执行停止操作后流程丢失,无可见错误,页面短暂显示“尝试重新连接”,疑似Blazor WebSocket连接中断。已添加try-catch未捕获到错误,相关代码如下:

C#代码

public async Task<string?> StopRecording()
{
    var module = await moduleTask.Value;
    var jsonDocument = await module.InvokeAsync<JsonDocument>("stopRecording");
    if (jsonDocument == null)
    {
        // Happens when recording is too quickly stopped
        return null;
    }

    string recordingId = jsonDocument.RootElement.GetProperty("id").GetString();
    string base64String = jsonDocument.RootElement.GetProperty("data").GetString();
    if (string.IsNullOrEmpty(base64String))
    {
        throw new Exception("Pronunciation data is empty");
    }

    await module.InvokeVoidAsync("showStartButton");
    await module.InvokeAsync<JsonDocument>("addRecordingListItem", recordingId, base64String, false, true);
    
    return base64String;
}

JS代码

function base64ToBlob(base64String) {
    const byteCharacters = atob(base64String);
    const byteNumbers = new Array(byteCharacters.length);

    for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);

    return new Blob([byteArray], { type: "audio/ogg; codecs=opus" });
}
export function addExistingEntryRecording(soundDto) {
    console.log(soundDto);

    addRecordingListItem(soundDto.soundId, base64ToBlob(soundDto.recordingB64));
}

export function startRecording(recordingId, lblRecord, lblStopRecording) {
    _lblRecord = lblRecord;
    _lblStopRecording = lblStopRecording;

    showStopButton();

    navigator.mediaDevices.getUserMedia({ audio: true })
        .then(function (stream) {

            _latestRecordingId = recordingId;

            let chunks = [];

            mediaRecorder = new MediaRecorder(stream);
            mediaRecorder.audioBitsPerSecond
            mediaRecorder.addEventListener("dataavailable", function (event) {
                chunks.push(event.data);
            });

            mediaRecorder.addEventListener("stop", function () {
                const audioBlob = new Blob(chunks, { type: "audio/ogg; codecs=opus" });
                recordedBlob = audioBlob;
            });

            mediaRecorder.start();
        })
        .catch(function (err) {
            console.log("The following error occurred: " + err);
        });
}

export function stopRecording() {
    if (!mediaRecorder) {
        return;
    }

    return new Promise((resolve, reject) => {
        mediaRecorder.addEventListener("stop", function () {
            const reader = new FileReader();
            reader.onloadend = function () {
                const base64String = reader.result.split(',')[1];
                resolve({ id: _latestRecordingId, data: base64String });
            };
            reader.readAsDataURL(recordedBlob);
        });
        mediaRecorder.stop();
    });
}

解决建议

1. 修复重复绑定stop事件的问题

每次调用stopRecording都会给mediaRecorder绑定新的stop事件,多次累积后会导致逻辑混乱,长音频数据处理时更易触发异常。修改stopRecording函数,绑定事件后立即移除自身,避免重复绑定:

export function stopRecording() {
    if (!mediaRecorder) {
        return Promise.resolve(null);
    }

    return new Promise((resolve, reject) => {
        const onStopHandler = function () {
            // 移除事件监听,防止重复触发
            mediaRecorder.removeEventListener("stop", onStopHandler);
            
            const reader = new FileReader();
            reader.onloadend = function () {
                try {
                    const base64String = reader.result.split(',')[1];
                    resolve({ id: _latestRecordingId, data: base64String });
                } catch (err) {
                    console.error("Base64处理失败:", err);
                    reject(err);
                }
            };
            reader.onerror = function (err) {
                console.error("FileReader读取失败:", err);
                reject(err);
            };
            reader.readAsDataURL(recordedBlob);
            
            // 及时清理资源
            recordedBlob = null;
            mediaRecorder = null;
        };
        
        mediaRecorder.addEventListener("stop", onStopHandler);
        mediaRecorder.stop();
    });
}

2. 降低音频数据大小,避免WebSocket过载

长音频生成的base64字符串过大,会导致Blazor WebSocket连接因传输负载过高中断。可以通过降低音频比特率减少文件体积:
修改startRecording中MediaRecorder的初始化代码,指定较低的比特率:

// 例如设置为64kbps,可根据需求调整
mediaRecorder = new MediaRecorder(stream, { audioBitsPerSecond: 64000 });

3. 及时清理媒体资源,防止内存泄漏

录音停止后未关闭媒体流、清理临时数据,长期运行会导致内存累积,引发异常。修改startRecording中的stop事件处理逻辑:

mediaRecorder.addEventListener("stop", function () {
    const audioBlob = new Blob(chunks, { type: "audio/ogg; codecs=opus" });
    recordedBlob = audioBlob;
    
    // 关闭媒体流的所有轨道
    stream.getTracks().forEach(track => track.stop());
    // 清空临时数据
    chunks = [];
});

4. 增强错误捕获与日志输出

在C#和JS代码中添加更完整的错误处理,便于排查隐藏问题:
修改C#的StopRecording方法:

public async Task<string?> StopRecording()
{
    try
    {
        var module = await moduleTask.Value;
        var jsonDocument = await module.InvokeAsync<JsonDocument>("stopRecording");
        if (jsonDocument == null)
        {
            return null;
        }

        string recordingId = jsonDocument.RootElement.GetProperty("id").GetString();
        string base64String = jsonDocument.RootElement.GetProperty("data").GetString();
        if (string.IsNullOrEmpty(base64String))
        {
            throw new Exception("Pronunciation data is empty");
        }

        await module.InvokeVoidAsync("showStartButton");
        await module.InvokeAsync<JsonDocument>("addRecordingListItem", recordingId, base64String, false, true);
        
        return base64String;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"停止录音异常: {ex.Message}\n{ex.StackTrace}");
        return null;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 13:16:05