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

如何通过SignalR将Angular应用音频流录制到Asp.Net Core API?

你选SignalR+WebSocket来实现Angular到ASP.NET Core的音频流录制完全是个靠谱的方案!WebSocket本身就适合低延迟的实时数据传输,SignalR又帮我们封装了连接管理、重连等复杂逻辑,非常适配音频流这种持续传输的场景。下面我给你一步步拆解具体实现:

一、前端Angular代码完善

你已经成功获取了MediaStream,接下来只需要把音频数据通过SignalR分块发送到后端就行。这里我们用MediaRecorder来处理音频流的分片传输:

首先确保你已经安装了SignalR的npm包:

npm install @microsoft/signalr

然后在你的组件里完善代码:

import { Component, ViewChild, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';

@Component({
  selector: 'app-audio-recorder',
  templateUrl: './audio-recorder.component.html',
  styleUrls: ['./audio-recorder.component.css']
})
export class AudioRecorderComponent implements OnInit, OnDestroy {
  @ViewChild('video') video!: ElementRef<HTMLVideoElement>;
  private stream!: MediaStream;
  private connection!: HubConnection;
  private mediaRecorder!: MediaRecorder;
  private isRecording = false;

  async ngOnInit() {
    // 初始化SignalR连接
    this.connection = new HubConnectionBuilder()
      .withUrl('https://your-api-domain/hubs/audioRecorder') // 替换成你的API Hub地址
      .withAutomaticReconnect()
      .build();

    try {
      await this.connection.start();
      console.log('SignalR连接已建立');
    } catch (err) {
      console.error('SignalR连接失败:', err);
    }
  }

  async startRecording() {
    try {
      // 获取音频流(你已实现的部分)
      this.stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
      // 绑定到video元素(如果需要预览音频对应的视频流)
      this.video.nativeElement.srcObject = this.stream;

      // 创建MediaRecorder,选择webm格式(大部分现代浏览器支持)
      this.mediaRecorder = new MediaRecorder(this.stream, { mimeType: 'audio/webm' });
      
      // 监听音频分片数据生成事件
      this.mediaRecorder.ondataavailable = (event) => {
        if (event.data.size > 0 && this.connection.state === 'Connected') {
          // 将Blob转换为ArrayBuffer后发送到后端
          event.data.arrayBuffer().then(buffer => {
            this.connection.invoke('SendAudioChunk', buffer).catch(err => {
              console.error('发送音频块失败:', err);
            });
          });
        }
      };

      // 设置每1秒生成一个音频分片(可根据需求调整)
      this.mediaRecorder.start(1000);
      this.isRecording = true;
      console.log('开始录制音频');
    } catch (err) {
      console.error('获取媒体流或初始化录制失败:', err);
    }
  }

  stopRecording() {
    if (this.isRecording && this.mediaRecorder) {
      this.mediaRecorder.stop();
      this.stream.getTracks().forEach(track => track.stop());
      this.isRecording = false;
      console.log('停止录制音频');
      
      // 告知后端录制结束,以便完成文件保存
      this.connection.invoke('FinishRecording').catch(err => {
        console.error('通知录制结束失败:', err);
      });
    }
  }

  ngOnDestroy() {
    // 组件销毁时清理资源
    if (this.connection) {
      this.connection.stop();
    }
    if (this.stream) {
      this.stream.getTracks().forEach(track => track.stop());
    }
  }
}
二、后端ASP.NET Core SignalR Hub实现

首先确保你已经在项目中安装了SignalR包:

dotnet add package Microsoft.AspNetCore.SignalR

然后创建AudioRecorderHub类:

using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;

namespace YourApiNamespace.Hubs
{
    public class AudioRecorderHub : Hub
    {
        // 用线程安全字典存储每个客户端的音频文件流(通过ConnectionId区分)
        private static readonly ConcurrentDictionary<string, FileStream> _clientAudioStreams = new();

        public async Task SendAudioChunk(byte[] audioChunk)
        {
            var connectionId = Context.ConnectionId;
            
            // 首次接收该客户端数据时,创建本地文件流
            if (!_clientAudioStreams.TryGetValue(connectionId, out var stream))
            {
                var filePath = Path.Combine(Directory.GetCurrentDirectory(), "AudioRecordings", $"{connectionId}_{DateTime.Now:yyyyMMddHHmmss}.webm");
                Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
                stream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
                _clientAudioStreams.TryAdd(connectionId, stream);
            }

            // 写入音频分片数据
            await stream.WriteAsync(audioChunk, 0, audioChunk.Length);
        }

        public async Task FinishRecording()
        {
            var connectionId = Context.ConnectionId;
            if (_clientAudioStreams.TryRemove(connectionId, out var stream))
            {
                await stream.FlushAsync();
                stream.Close();
                
                // 这里可以添加后续逻辑:比如转码为MP3、存储到云存储、记录到数据库等
                await Clients.Caller.SendAsync("RecordingFinished", "音频录制已成功保存");
            }
        }

        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            // 客户端断开连接时,清理未关闭的文件流
            if (_clientAudioStreams.TryRemove(Context.ConnectionId, out var stream))
            {
                stream.Close();
            }
            await base.OnDisconnectedAsync(exception);
        }
    }
}

接下来在Program.cs中配置SignalR和跨域(如果前后端不在同一域名):

var builder = WebApplication.CreateBuilder(args);

// 添加SignalR服务
builder.Services.AddSignalR();

// 配置跨域策略(替换成你的Angular应用地址)
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAngularOrigin", policy =>
    {
        policy.WithOrigins("http://your-angular-app-domain")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials();
    });
});

var app = builder.Build();

// 启用跨域
app.UseCors("AllowAngularOrigin");

app.UseHttpsRedirection();
app.UseAuthorization();

// 映射SignalR Hub路由
app.MapHub<AudioRecorderHub>("/hubs/audioRecorder");

app.Run();
三、关键注意事项
  • 音频格式兼容性:MediaRecorder支持的格式依赖浏览器,webm是现代浏览器的通用选择;如果需要MP3等格式,可以在后端用FFmpeg等工具转码。
  • 分片大小调整:示例中设置1秒分片,你可以根据网络情况调整——太小会增加请求量,太大则会提升传输延迟。
  • 连接可靠性:SignalR的自动重连能处理临时断连,但建议在前端监听连接状态变化,给用户直观提示。
  • 生产环境存储:示例中把音频保存到服务器本地,生产环境建议用云存储(如Azure Blob、AWS S3)或数据库存储,避免服务器磁盘占用过高。
  • 权限与环境:getUserMedia需要用户授权麦克风权限,且仅在HTTPS环境(localhost除外)下生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:02:29