如何使用 WebRTC 或 WebSocket 制作协作白板?

问题描述 投票:0回答:1

我使用 WebRTC 和 MERN 创建了一个视频聊天应用程序。但是,我想向应用程序添加协作白板,以便当一个人在其画布元素上绘图时,相同的形状将出现在其他用户的画布上。

Session.js

import React, { useState, useEffect, useRef } from "react";
import Videoplayer from "./Videoplayer";
import Options from "../pages/Options";
import { ContextProvider } from "../pages/Context";
import { Checklogin } from "./Checklogin";

function Session() {
  return (
      <div>
        <Checklogin/>
        <ContextProvider>
          <Videoplayer/>
          <Options/>
        </ContextProvider> 
      </div>
  );
}
export default Session;

Context.js

import React, {createContext, useState, useRef, useEffect} from 'react';
import {io} from 'socket.io-client';
import Peer from 'simple-peer';

const SocketContext = createContext();
var server = process.env.REACT_APP_SERVER;
const socket = io(`${server}`);

function ContextProvider({children}, props)  {
    const [stream, setStream] = useState(null);
    const [me, setMe] = useState('');
    const [call, setCall] = useState(null);
    const [callEnded, setCallEnded] = useState(null);
    const [callAccepted, setCallAccepted] = useState(null);
    const [name, setName] = useState('');
    const myVideo = useRef();
    const userVideo = useRef();
    const connectionRef = useRef();
    var url = new URLSearchParams(window.location.search);
    var id = url.get("id");
    const canvas = useRef();

    useEffect(() => {
        navigator.mediaDevices.getUserMedia({video: true, audio: true})
        .then((currentStream) => {
            setStream(currentStream);
            if (myVideo.current) 
                myVideo.current.srcObject = currentStream;
        });

        socket.on('me', (id) => {setMe(id);});
        socket.on('calluser', ({from, name: callerName, signal}) => {
            setCall({isReceivedCall: true, from, name: callerName, signal});
        });
    }, []);

    function callUser(id) {
        var peer = new Peer({initiator: true, trickle: false, stream});
        peer.on('signal', (data) => {
            socket.emit('calluser', {userToCall: id, signalData: data, from: me, name})
        });
        peer.on('stream', (currentStream) => {
            userVideo.current.srcObject = currentStream;
        });
        socket.on('callaccepted', (signal) => {
            setCallAccepted(true);
            peer.signal(signal);
        });
        connectionRef.current = peer;
    }

    function answerCall() {
        if (call == null) 
            return;

        setCallAccepted(true);
        var peer = new Peer({initiator: false, trickle: false, stream});
        peer.on('signal', (data) => {
            socket.emit('answercall', {signal: data, to: call.from});
        });
        peer.on('stream', (currentStream) => {
            userVideo.current.srcObject = currentStream;
        });
        peer.signal(call.signal);
        connectionRef.current = peer;
    }

    function leaveCall() {
        setCallEnded(true);
        connectionRef.current.destroy();
        window.location.reload();
    }

    return(
        <SocketContext.Provider value={{ id, call, callAccepted,  myVideo,  userVideo,  stream,  name, setName,  callEnded,  me,  callUser,  leaveCall,  answerCall}}>
            {children}
        </SocketContext.Provider>
    )
};

export {ContextProvider, SocketContext};

Options.js 获取其他用户的套接字 id 并使用 Context.js 提供的函数初始化 WebRTC 连接。它获取远程用户的视频流并将其引用到 VideoPlayer。

选项.js

import { SocketContext } from "../pages/Context";
import React, { useContext, useEffect, useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import { ToastContainer, toast } from "react-toastify";
import Whiteboard from './Whiteboard';

function Options() {
    const navigate = useNavigate();
    const { me, call, leaveCall, callUser, answerCall, id } = useContext(SocketContext);
    var flag = true;
    var server = process.env.REACT_APP_SERVER;
    const [peerSocket, setPeerSocket] = useState('');

    useEffect(() => {    
        try {
            axios.post(`${server}/session`,
              {"me": me, "id": id},
              { withCredentials: true })
            .then(res => {
                if (!res.data.status) {
                    navigate('/');
                }
            });
        } 
        catch (error) {
            console.log(error);
        }
    }, [me, navigate]);

    function getPeer() {    
        try {
            axios.get(`${server}/session?id=${id}&me=${me}`,
              { withCredentials: true })
            .then(res => {
                if (res.data.status) {
                    setPeerSocket(res.data.msg);
                }
            });
          } 
          catch (error) {
            console.log(error);
          }
    };

    var callPeer = function() {
        getPeer();
        if (peerSocket != '') {
            callUser(peerSocket);
            toast.success("Your partner will join you in a few moments", {position: "bottom-left"});
        }
        else {
            getPeer();
            callUser(peerSocket);
        }
    }
    
    var endCall = function() {
        flag = false;
        leaveCall();
    }

    return (
        <>
            {call == null && flag? (<button id="callbtn" onClick={callPeer}>Call</button>): 
                (<button id="answerbtn" onClick={answerCall}>Answer</button>)}
            <button id="hangupbtn" onClick={endCall}>Hang Up</button>
            <Whiteboard id={id} me={me}/>
            <ToastContainer/>
        </>
    )
}

export default Options;

Whiteboard.js 可以在屏幕上显示您正在绘制的内容。但是,它无法将绘图发送给远程用户。我尝试使用 WebSocket 而不是 WebRTC,因为我不知道如何使用 WebRTC。我想我必须在 Whiteboard 中添加对 canvas 元素的引用,并且 Context.js 必须从 canvas 元素捕获流。

Whiteboard.js

import React, {useContext, useState, useEffect, useRef } from "react";
import {io} from 'socket.io-client';

var server = process.env.REACT_APP_SERVER;
var socket = io(`${server}`);

function Whiteboard(props) {
  const canvasRef = useRef(null);
  const contextRef = useRef(null);
  const [drawing, setDrawing] = useState(false);
  var context, canvas;

  useEffect(() => {
    canvas = canvasRef.current;
    canvas.width = window.innerWidth * 0.95;
    canvas.height = window.innerHeight * 1.5;
    canvas.style.width = `${canvas.width}px`;
    canvas.style.height = `${canvas.height}px`;

    context = canvas.getContext('2d');
    context.scale(1, 1);
    context.lineCap = 'butt';
    context.strokeSytle = 'green';
    context.lineWidth = 5;
    contextRef.current = context;
  }, []);

  function startDrawing({nativeEvent}) {
    var {offsetX, offsetY} = nativeEvent;
    contextRef.current.beginPath();
    contextRef.current.moveTo(offsetX, offsetY);
    setDrawing(true);
  }

  function finishDrawing() {
    contextRef.current.closePath();
    setDrawing(false);
  }

  function draw({nativeEvent}) {
    var {offsetX, offsetY} = nativeEvent;
    if(!drawing) {
      return 
    }

    socket.on("draw", ({x, y}) => {
      alert(1)
      contextRef.current.lineTo(x, y);
      contextRef.current.stroke();
    });

    if(props.peer != null && props.peer != '') {
      var data = {
        x: offsetX,
        y: offsetY,
        to: props.peer
      };
      socket.emit('draw', data); //emit coordinates of picture
    }
    contextRef.current.lineTo(offsetX, offsetY);
    contextRef.current.stroke();
  }
  
  return (
    <canvas 
        onMouseDown={startDrawing}
        onMouseUp={finishDrawing}
        onMouseMove={draw}
        ref={canvasRef}>
    </canvas>
  );
}
export default Whiteboard;

Server.js 充当信号服务器来代理 WebRTC 连接并中继绘图事件。

服务器.js

io.on("connection", (socket) => {
    socket.emit("me", socket.id);
    socket.on("disconnect", () => {
        socket.broadcast.emit("callended");
    });
    socket.on("calluser", ({ userToCall, signalData, from, name }) => {
        io.to(userToCall).emit("calluser", { signal: signalData, from, name });
    });
    socket.on("answercall", (data) => {
        io.to(data.to).emit("callaccepted", data.signal);
    });
    socket.on("draw", (data) => {
        console.log(data)
        io.to(data.to).emit("ondraw", {x: data.x, y: data.y});
    });
});

如何捕获远程用户在我的白板上绘制的绘图?

node.js reactjs canvas webrtc simple-peer
1个回答
0
投票

您可以使用socket.io中的rooms来实现这一点。

  1. 为白板分配一个 ID,并在通话中的所有成员都可以访问创建的白板后立即向服务器发出“加入房间”事件。
  2. 现在您可以侦听房间的事件,并使用单个套接字发射将发射发送到客户端到呼叫中的每个成员。

这只是实现这一目标的一种方法。这可能适用于您的情况,也可能不适用,具体取决于有多少用户将与单个白板进行交互。您可能还需要限制某些用户编辑白板,您可以在服务器端使用优先级队列来确保一次更新 1 个像素。但只要后端逻辑是轻量级的,它就应该可以正常工作。

© www.soinside.com 2019 - 2024. All rights reserved.