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
相关产品推荐
相关产品推荐

