如何在 Next.js 中快速集成 @microsoft/signalr

如何在 Next.js 中快速集成 @microsoft/signalr

虽然只有少数项目需要集成 WebSockets 来在界面发生变化时实时响应而不重新获取数据,但这些项目仍然数量庞大。

这是一项必不可少的工作,我们不会讨论它们或者比较提供更好开发体验的第三方库。

我的目标是展示如何快速集成 @microsoft/signalr 到 Next.js 中,以及在开发过程中遇到的问题如何解决。

首先,我希望每个人都已经在本地安装和部署了 Next.js 项目。在我的案例中,版本是 13.2.4。让我们添加一些重要的库:swr(版本 2.1.5)用于数据获取以及与本地缓存进一步工作,以及 @microsoft/signalr(版本 7.0.5) - 用于 WebSockets 的 API。

npm install --save @microsoft/signalr swr

让我们从创建一个简单的 fetcher 函数和一个名为 useChatData 的新 hook 开始,以从我们的 REST API 获取初始数据。它返回了聊天消息列表、检测错误和加载状态的字段,以及允许更改缓存数据的 mutate 方法。

// hooks/useChatData.ts
import useSWR from 'swr';

type Message = {
   
   
    content: string;
    createdAt: Date;
    id: string;
};

async function fetcher<TResponse>(url: string, config: RequestInit): Promise<TResponse> {
   
   
    const response = await fetch(url, config);
    if (!response.ok) {
   
   
        throw response;
    }
    return await response.json();
}

export const useChatData = () => {
   
   
    const {
   
    data, error, isLoading, mutate } = useSWR<Message[]>('OUR_API_URL', fetcher);
    return {
   
   
        data: data || [],
        isLoading,
        isError: error,
        mutate,
    };
};

为了测试它是否按预期工作,让我们更新我们的页面组件。在顶部导入我们的 hook,并像下面的代码片段中那样从中提取数据。如果它工作正常,你将看到渲染的数据。如你所见,这很简单。

// pages/chat.ts
import {
   
    useChatData } from 'hooks/useChatData';

const Chat: NextPage = () => {
   
   
    const {
   
    data } = useChatData();

    return (
        <div>
            {
   
   data.map(item => (
                <div key={
   
   item.id}>{
   
   item.content}<
信令服务器代码如下: using Microsoft.AspNetCore.SignalR; using Newtonsoft.Json; using System.Collections.Concurrent; using webrtc_net_api.Models; namespace webrtc_net_api.Service { public class WebRtcHub : Hub { private static readonly ConcurrentDictionary<string, string> _peerConnections = new(); public override async Task OnConnectedAsync() { Console.WriteLine($"新客户端连接: {Context.ConnectionId}"); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception exception) { Console.WriteLine($"客户端断开: {Context.ConnectionId}"); _peerConnections.TryRemove(Context.ConnectionId, out _); await base.OnDisconnectedAsync(exception); } public async Task JoinRoom(string peerId) { Console.WriteLine($"加入房间: {peerId}"); _peerConnections.TryAdd(Context.ConnectionId, peerId); await Groups.AddToGroupAsync(Context.ConnectionId, peerId); } public async Task SendSignal(SignalingMessage message) { Console.WriteLine($"收到信令: {JsonConvert.SerializeObject(message)}"); // 需引用Newtonsoft.Json Console.WriteLine($"目标PeerId: {message.PeerId}, 当前连接数: {_peerConnections.Count}"); // 查找目标连接(根据目标peerId) var targetConnection = _peerConnections .FirstOrDefault(p => p.Value == message.PeerId).Key; if (!string.IsNullOrEmpty(targetConnection)) { Console.WriteLine($"返回数据:{message.Type}"); await Clients.Client(targetConnection).SendAsync("receive_signal", message); } } } } namespace webrtc_net_api.Models { public class SignalingMessage { public string PeerId { get; set; } public string Type { get; set; } public string Sdp { get; set; } public RTCIceCandidate Candidate { get; set; } } public class RTCIceCandidate { public string Candidate { get; set; } public string SdpMid { get; set; } public int SdpMLineIndex { get; set; } } } WebRtcClient.vue代码如下: <template> <div> <video ref="localVideo" autoplay muted></video> <video ref="remoteVideo" autoplay></video> </div> </template> <script> import SimplePeer from 'simple-peer'; import SignalRService from '@/socket'; export default { data() { return { peer: null, signalRService: null, isInitiator: false }; }, async mounted() { this.signalRService = new SignalRService(); await this.signalRService.startConnection('target_peer_id'); // 获取媒体设备 const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); this.$refs.localVideo.srcObject = stream; // 创建 Peer 连接 this.peer = new SimplePeer({ initiator: this.isInitiator, trickle: true, stream: stream, config: { iceServers: [ { urls: 'stun:stun.l.google.com:19302' } ] } }); // 处理信令 this.peer.on('signal', (data) => { this.signalRService.sendSignal({ type: data.type, sdp: data.sdp, peerId: 'target_peer_id' }); }); // 处理远程流 this.peer.on('stream', (remoteStream) => { this.$refs.remoteVideo.srcObject = remoteStream; }); // 处理 ICE 候选 this.peer.on('iceCandidate', (candidate) => { if (candidate) { this.signalRService.sendSignal({ type: 'ice-candidate', candidate: { candidate: candidate.candidate, sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex }, peerId: 'target_peer_id' }); } }); } }; </script> socket.js代码如下: import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; export default class SignalRService { constructor() { this.connection = null; } async startConnection(peerId) { this.connection = new HubConnectionBuilder() .withUrl('http://192.168.10.114:5012/signalr/webrtc', { accessTokenFactory: () => localStorage.getItem('jwt_token') // 可选认证 }) .configureLogging(LogLevel.Information) .withAutomaticReconnect() .build(); this.connection.on('receive_signal', (message) => { this.handleSignal(message); }); try { await this.connection.start(); await this.connection.invoke('JoinRoom', peerId); console.log('SignalR 连接已建立'); } catch (err) { console.error('SignalR 连接失败:', err); setTimeout(() => this.startConnection(peerId), 5000); } } handleSignal(message) { this.peer.signal(message); } sendSignal(message) { this.connection.invoke('SendSignal', message); } } 请提供一个完整的,基于ROS2的WEBRTC视频流推送的功能,代码需完整,并且都在一个文件内,推送地址为:http://192.168.10.114:5012/signalr/webrtc
06-28
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值