본문 바로가기

카테고리 없음

리액트) 디스코드 클론 - 6

이번에는 스트리밍을 만들어 볼 예정이다

우선 스트리밍 또한 화상통화와 마찬가지로 

navigator.mediaDevices

로 가져온다.

다만 useMedia를 가져오는 화상통화와 달리 스트리밍은 유저의 화면을 보여주는 것이므로 getDisplayMedia를 사용하면 된다.

같은 Room.js의 JSX에 해당 코드를 추가하자

<button onClick={onSharingStart} id="start">
        SharingStart
      </button>
      <button onClick={StopSharing}>Stop sharing</button>
      <video autoPlay id="streaming" ref={streamingVideoRef} />

그리고 아래 코드도 추가하자

  const onSharingStart = async () => {
    try {
      await navigator.mediaDevices
      //유저의 화면들을 가져옴
        .getDisplayMedia(displayMediaOptions)
        .then((stream) => {
        //비디오 태그에 송출
          streamingVideoRef.current.srcObject = stream;
        });
    } catch (err) {
      // 에러 컨트롤
      console.error("Error: " + err);
    }
  };

  // stop sharing

  const StopSharing = () => {
    streamingVideoRef.current.srcObject = null;
  	// 방송 종료를 누르면 비디오 태그의 srcObject를 null로
  };

그러면 이제 화면이 송출이 완료되었다.

자 이제 이것을 어떻게 유저들에게 보여줄지 생각해보자.

우선 음성통화(화상통화)의 경우 소켓과 피어를 생성해 피어투 피어로 연결했다.

이미 다른 유저들과 음성 화상 데이터를 주고받는 피어라 다시 재연결 또는 전화를 걸 수가 없다. (왜냐면 이미 한 번 해봤기 때문이다..ㅠ)

그 말인즉슨 다시 피어를 연결해야한다는 뜻이다.

다시 Room.js를 수정하자

// Room.js

//... do something 

 const onSharingStart = async () => {
    // event.stopPropagation();
    try {
      await navigator.mediaDevices
        .getDisplayMedia(displayMediaOptions)
        .then((stream) => {
          streamingVideoRef.current.srcObject = stream;
          
          //add !!
          streamingPeerRef.current = new Peer();

          streamingPeerRef.current.on("open", (id) => {
            socketRef.current.emit("streaming-start", roomid, id);
          });

          streamingPeerRef.current.on("call", (call) => {
            call.answer(stream);
          });
        });
    } catch (err) {
      // Handle error
      console.error("Error: " + err);
    }
  };
  
  // ...do something

 

스트리밍이 시작되면 새 피어를 생성한다.

새 피어를 생성하고 ....on('call')을 통해 다른 유저들에게 통화를 건다.

이때 call.on('stream')은 제외했는데, 이미 다른 유저와의 통화는 다른 피어를 통해 연결되어 있으므로 다른 유저의 스트림 데이터를 받아 올 필요가 없기 때문이다.

// server.js

 // ...io.on('connect',(socket)=>{
 
 	socket.on('join-room', //.... do something
 
	 //... do something
 
	 // add!
    socket.on("streaming-start", (roomid, id) => {
      socket.broadcast.to(roomid).emit("other-user-streaming-start", id);
    });
 
 })
 
 // ...do something

 

// Room.js

 useEffect(() => {
    //소켓 연결
    socketRef.current = io.connect("http://localhost:9000");

    navigator.mediaDevices
      .getUserMedia({ audio: false, video: true })
      .then((stream) => {
       // ...do something

		// add !!!
        socketRef.current.on("other-user-streaming-start", (id) => {
          console.log(id);
          connectToNewUser(id, stream, "username");
        });
      });
	// ...do something ex) 피어 연결
  }, []);

이제 무난하게 스트리밍 또한 연결된 것을 확인할 수가 있다.

추가로 메세지 기능 또한 넣었다.

// Room.js

import React, { useEffect, useRef, useState } from "react";
import { io } from "socket.io-client";
import Peer from "peerjs";
import { useParams } from "react-router-dom";
import Video from "./Video";

var getUserMedia =
  navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozG;

const Room = () => {
  const socketRef = useRef(null);
  const peerRef = useRef(null);
  const myVideoRef = useRef(null);

  const { roomid } = useParams();
  const [streams, setStreams] = useState([]);
  const [msg, setMsg] = useState([]);

  useEffect(() => {
    //소켓 연결
    socketRef.current = io.connect("http://localhost:9000");

    navigator.mediaDevices
      .getUserMedia({ audio: false, video: true })
      .then((stream) => {
        //내 스트림데이터(비디오, 오디오)를 가져와서 비디오 태그에 연결
        myVideoRef.current.srcObject = stream;

        socketRef.current.on("user-connected", (id, username) => {
          //새 유저 접속 시 기존 유저는 user-connected메세지를 받음
          connectToNewUser(id, stream, username);
          //나 자신(기존유저)와 새 유저의 피어 연결
        });

        socketRef.current.on("user-disconnected", (id) => {
          const new_streams = streams.filter((st) => st.id !== id);
          setStreams(new_streams);
        });

        socketRef.current.on("other-user-streaming-start", (id) => {
          console.log(id);
          connectToNewUser(id, stream, "username");
        });
      });

    socketRef.current.on("receive-message", (ms, id) => {
      //sender를 포함한 룸 유저 전체에게 메세지
      setMsg((p) => [...p, { message: ms, id }]);
    });

    //피어생성
    peerRef.current = new Peer(socketRef.current.id);

    peerRef.current.on("call", (call) => {
      //기존 유저에게 전화걸기
      getUserMedia(
        //전화를 걸어버림
        { video: true, audio: false },
        function (stream) {
          call.answer(stream);
          call.on("stream", function (remoteStream) {
            //기존에 잇던 사람듸 스트림을 받아옴
            setStreams((p) => [...p, { stream: remoteStream, id: call.peer }]);
          });
        },
        function (err) {
          alert(err);
        }
      );
    });

    peerRef.current.on("open", (id) => {
      //피어 생성하면 기본적으로 실행됨
      console.log("음성피어 id = ", id);
      socketRef.current.emit("join-room", roomid, id);
    });
  }, []);

  function connectToNewUser(userId, streams, username) {
    //전화 받기

    const call = peerRef.current.call(userId, streams);
    call.on("stream", (userVideoStream) => {
      //새로 접속한 유저의 스트림을 얻어옴
      setStreams((p) => [...p, { stream: userVideoStream, id: userId }]);
    });
    call.on("close", () => {
      //접속 끊김
      console.log("closed!!");
      setStreams((p) => p.filter((str) => str.id !== call.peer));
      //   비디오 삭제
    });
  }
  const displayMediaOptions = {
    video: {
      cursor: "always",
      height: 500,
      width: 500,
    },
    audio: false,
  };

  const streamingPeerRef = useRef();
  const streamingVideoRef = useRef(null);
  const inputRef = useRef(null);

  const onSharingStart = async () => {
    // event.stopPropagation();
    try {
      await navigator.mediaDevices
        .getDisplayMedia(displayMediaOptions)
        .then((stream) => {
          streamingVideoRef.current.srcObject = stream;
          streamingPeerRef.current = new Peer();

          streamingPeerRef.current.on("open", (id) => {
            socketRef.current.emit("streaming-start", roomid, id);
          });

          streamingPeerRef.current.on("call", (call) => {
            call.answer(stream);
          });
        });
    } catch (err) {
      // Handle error
      console.error("Error: " + err);
    }
  };

  // stop sharing

  const StopSharing = () => {
    streamingVideoRef.current.srcObject = null;
    // if (!streamingSocketRef.current) return;
    // if (!sharingVedioRef.current.srcObject) return;
    // let tracks = sharingVedioRef.current.srcObject.getTracks();
    // tracks.forEach((track) => track.stop());
    // sharingVedioRef.current.srcObject = null;
    // streamingPeerRef.current = null;
  };

  const onSubmit = (e) => {
    e.preventDefault();

    const { value } = inputRef.current;
    socketRef.current.emit("message-send", value, socketRef.current.id, roomid);
    inputRef.current.value = "";
  };

  return (
    <div>
      <div id="chat">
        <form onSubmit={onSubmit}>
          <label htmlFor="text">텍스트 입력 </label>
          <input type="text" id="text" ref={inputRef} />
        </form>

        <div>
          {msg.map((ms, idx) => (
            <div key={idx}>{ms.message}</div>
          ))}
        </div>
      </div>
      <button onClick={onSharingStart} id="start">
        SharingStart
      </button>
      <button onClick={StopSharing}>Stop sharing</button>
      <video autoPlay id="streaming" ref={streamingVideoRef} />
      <video ref={myVideoRef} autoPlay />
      {streams.map((st, idx) => (
        <Video st={st} key={idx} />
      ))}
    </div>
  );
};

export default Room;
// server.js

const express = require("express");
const app = express();

const cors = require("cors");
app.use(cors());

const server = require("http").createServer();
const io = require("socket.io")(server, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"],
    credentials: true,
  },
});

const PORT = 9000;

io.on("connection", (socket) => {
  console.log("connection!!");

  socket.on("join-room", (roomid, id, nickname) => {
    socket.join(roomid);
    console.log(id);
    //new user join to the room

    //sender를 제외한 방의 모든 사람에게 메세지를 날림
    socket.broadcast.to(roomid).emit("user-connected", id, nickname);
    socket.on("disconnect", () => {
      //유저의 소켓연결이 끊어졌을때

      socket.leave(roomid);
      //방에서 내보내고

      //모두에게 알린다
      socket.to(roomid).emit("user-disconnected", id);
    });

    socket.on("streaming-start", (roomid, id) => {
      //sender 빼고 모든 룸안의 유저들에게 메세지를 날림
      socket.broadcast.to(roomid).emit("other-user-streaming-start", id);
    });

    socket.on("message-send", (ms, id, roomid) => {
      //sender를 포함한 룸 유저 전체에게 메세지
      io.in(roomid).emit("receive-message", ms, id);
    });
  });
});

server.listen(PORT, () => console.log("server is running on ", PORT));