이번에는 스트리밍을 만들어 볼 예정이다
우선 스트리밍 또한 화상통화와 마찬가지로
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));