ASP.NET WebForm中SignalR+WebRTC无法接收其他用户视频流求助
ASP.NET WebForm + SignalR + WebRTC 视频通话接收远程流问题
我正在使用ASP.NET WebForm、SignalR和WebRTC开发视频通话功能,需求为:用户访问Page1.aspx页面时,可互相查看对方摄像头的视频。
目前页面可显示本地视频流,但当其他用户访问Page1.aspx时,我无法接收到新用户的视频流。已编写receiveVideoStream处理接收逻辑,但仍无法正常工作,且控制台无任何报错信息,恳请帮助。
现有代码
Page1.aspx
<ul id="_listvideouser" class="gap15"></ul> <input id="hdtempIDUser" type="hidden" runat="server" /> <script src="scripts/jquery.signalR-2.4.3.min.js"></script> <script src="signalr/hubs"></script> <script> var userId = $('#hdtempIDUser').val();; $(function () { const hub = $.connection.videoHub; $.connection.hub.start().done(function () { startLocalVideoStream(); }); const localVideo = document.createElement('video'); const remoteVideos = new Map(); // // When receiving a video stream from someone else hub.client.receiveVideoStream = function (userId, videoStream) { if (!remoteVideos[userId]) { remoteVideos[userId] = videoStream; const li = $('<li data-user-id=' + userId + '></li>'); const remoteVideo = document.createElement('video'); remoteVideo.srcObject = videoStream; remoteVideo.autoplay = true; li.append(remoteVideo); $('#_listvideouser').append(li); } }; // Process video stream from current user function startLocalVideoStream() { navigator.mediaDevices.getUserMedia({ video: true, audio: true }) .then(function (stream) { localVideo.srcObject = stream; localVideo.autoplay = true; // Gửi video stream đến hub hub.server.sendVideoStream(userId, stream); const li = $('<li data-user-id=' + userId + '></li>'); li.append(localVideo); $('#_listvideouser').append(li); // offer, handle ICE candidates, ... }) .catch(function (error) { console.error('Error accessing webcam:', error); }); } }); </script>
VideoHub.cs
public class VideoHub : Hub { public void SendVideoStream(string userId, string videoStream) { Clients.Others.ReceiveVideoStream(userId, videoStream); } }
问题分析与修复方案
核心问题
- WebRTC流无法直接通过SignalR传输:
MediaStream是浏览器本地对象,无法序列化成字符串通过SignalR发送,你当前直接传stream到Hub,实际会被转成无效值,导致接收方拿不到有效流。 - SignalR回调注册时机错误:
hub.client.receiveVideoStream在Hub连接启动后才注册,可能错过早期的回调通知。 - Map对象使用错误:声明了
remoteVideos为Map,但用了对象的访问方式remoteVideos[userId],不符合Map的API规范。
修复步骤
1. 调整SignalR回调注册顺序
将客户端回调方法移到$.connection.hub.start()之前,确保连接建立前就完成注册:
$(function () { const hub = $.connection.videoHub; const localVideo = document.createElement('video'); const remoteVideos = new Map(); // 先注册所有客户端回调 hub.client.receiveOffer = function(senderId, offer) { /* 处理逻辑 */ }; hub.client.receiveAnswer = function(senderId, answer) { /* 处理逻辑 */ }; hub.client.receiveIceCandidate = function(senderId, candidate) { /* 处理逻辑 */ }; $.connection.hub.start().done(function () { startLocalVideoStream(); }); // ... 其他代码 });
2. 改用WebRTC信令流程(SignalR仅传信令)
WebRTC需要通过信令交换SDP会话描述和ICE候选,SignalR作为信令通道,直接建立P2P流传输。修改Page1.aspx的JavaScript:
var userId = $('#hdtempIDUser').val(); $(function () { const hub = $.connection.videoHub; const localVideo = document.createElement('video'); const remoteVideos = new Map(); const peerConnections = new Map(); // 存储每个用户的PeerConnection // 接收新用户加入通知 hub.client.userJoined = function(newUserId) { // 主动向新用户发起连接 createPeerConnection(newUserId); peerConnections.get(newUserId).createOffer() .then(offer => peerConnections.get(newUserId).setLocalDescription(offer)) .then(() => hub.server.sendOffer(newUserId, userId, peerConnections.get(newUserId).localDescription)); }; // 接收SDP Offer hub.client.receiveOffer = function(senderId, offer) { createPeerConnection(senderId); peerConnections.get(senderId).setRemoteDescription(new RTCSessionDescription(offer)) .then(() => peerConnections.get(senderId).createAnswer()) .then(answer => peerConnections.get(senderId).setLocalDescription(answer)) .then(() => hub.server.sendAnswer(senderId, userId, peerConnections.get(senderId).localDescription)); }; // 接收SDP Answer hub.client.receiveAnswer = function(senderId, answer) { peerConnections.get(senderId).setRemoteDescription(new RTCSessionDescription(answer)); }; // 接收ICE候选 hub.client.receiveIceCandidate = function(senderId, candidate) { if (peerConnections.has(senderId)) { peerConnections.get(senderId).addIceCandidate(new RTCIceCandidate(candidate)); } }; $.connection.hub.start().done(function () { startLocalVideoStream(); // 通知服务器当前用户上线 hub.server.userJoined(userId); }); // 创建PeerConnection实例 function createPeerConnection(targetUserId) { const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] // 公共STUN服务器,用于NAT穿透 }); // 添加本地流到连接 localVideo.srcObject.getTracks().forEach(track => pc.addTrack(track, localVideo.srcObject)); // 接收远程流并渲染 pc.ontrack = function(event) { if (!remoteVideos.has(targetUserId)) { const remoteVideo = document.createElement('video'); remoteVideo.srcObject = event.streams[0]; remoteVideo.autoplay = true; remoteVideo.playsInline = true; const li = $(`<li data-user-id="${targetUserId}"></li>`); li.append(remoteVideo); $('#_listvideouser').append(li); remoteVideos.set(targetUserId, remoteVideo); } }; // 发送ICE候选到对方 pc.onicecandidate = function(event) { if (event.candidate) { hub.server.sendIceCandidate(targetUserId, event.candidate); } }; peerConnections.set(targetUserId, pc); return pc; } function startLocalVideoStream() { navigator.mediaDevices.getUserMedia({ video: true, audio: true }) .then(function(stream) { localVideo.srcObject = stream; localVideo.autoplay = true; localVideo.playsInline = true; const li = $(`<li data-user-id="${userId}"></li>`); li.append(localVideo); $('#_listvideouser').append(li); }) .catch(function(error) { console.error('Error accessing webcam:', error); }); } });
3. 更新VideoHub.cs的信令转发逻辑
修改Hub类,处理信令消息的转发和在线用户管理:
using System.Collections.Generic; using Microsoft.AspNet.SignalR; public class VideoHub : Hub { private static readonly HashSet<string> OnlineUsers = new HashSet<string>(); public void UserJoined(string userId) { OnlineUsers.Add(userId); // 通知所有在线用户新用户加入 Clients.Others.UserJoined(userId); // 可选:通知新用户已在线的其他用户 Clients.Caller.ReceiveOnlineUsers(OnlineUsers.Where(u => u != userId).ToList()); } public void SendOffer(string targetUserId, string senderUserId, dynamic offer) { Clients.User(targetUserId).ReceiveOffer(senderUserId, offer); } public void SendAnswer(string targetUserId, string senderUserId, dynamic answer) { Clients.User(targetUserId).ReceiveAnswer(senderUserId, answer); } public void SendIceCandidate(string targetUserId, dynamic candidate) { Clients.User(targetUserId).ReceiveIceCandidate(Context.ConnectionId, candidate); } public override Task OnDisconnected(bool stopCalled) { var userId = Context.QueryString["userId"]; // 或者用你传递的用户标识 if (!string.IsNullOrEmpty(userId) && OnlineUsers.Contains(userId)) { OnlineUsers.Remove(userId); Clients.Others.UserLeft(userId); } return base.OnDisconnected(stopCalled); } }
4. 关键注意事项
- HTTPS要求:WebRTC在非localhost环境下必须使用HTTPS,否则
getUserMedia会被浏览器阻止。 - STUN/TURN服务器:如果用户处于不同网络环境,需要配置STUN/TURN服务器穿透NAT,示例中用了Google的公共STUN,生产环境建议自行部署或使用付费服务。
- 用户ID唯一性:确保
hdtempIDUser的值全局唯一,避免信令混乱。
内容的提问来源于stack exchange,提问作者Chim Di Tru
相关产品推荐
相关产品推荐

