WebRTC (2) - 구글 예제 챕터 2 풀어보기
2023. 1. 26. 17:09ㆍ코딩
2단계 : RTCPeerConnection 을 사용하여 비디오 제공하기
- 01/26 초기 작성
심심해서 아주 예전에 공부하다 말았던 webRtc 예제에서 사용되는 메서드 등을 다른 사람들이 그냥 재미로 볼 수 있도록 작성하였습니다.공통
에러 같은 메시지를 시간과 함께 표시하기 위해서 trace 를 사용하는 걸 공통 함수로 사용합니다.
function trace(text) {
text = text.trim();
const now = (window.performance.now() / 1000).toFixed(3);
console.log(now, text);
}
icecandidate
rtc를 사용하면서 요청(offer)과 답(answer)를 하고 받는 것을 끝냈을 때, p2p 연결의 양쪽에서 icecnadidate라는 이벤트를 시작합니다. 그 때 이 아래의 이벤트를 연결해준다고 생각하시면 됩니다.
ice candidate 는 ice (Internet Connectivity Establishment, 즉 인터넷 연결 생성) 입니다.
브라우저가 서로 소통할 수 있게 해주는 방법 입니다. 다수의 후보들이 각각의 연결에서 제안, 동의 과정을 거쳐 하나를 선택하는 겁니다.
function handleConnection(event) {
const peerConnection = event.target;
const iceCandidate = event.candidate;
if (iceCandidate) {
const newIceCandidate = new RTCIceCandidate(iceCandidate);
const otherPeer = getOtherPeer(peerConnection);
otherPeer
.addIceCandidate(newIceCandidate)
.then(() => {
handleConnectionSuccess(peerConnection);
})
.catch((error) => {
handleConnectionFailure(peerConnection, error);
});
trace(
`${getPeerName(peerConnection)} ICE candidate:\n` +
`${event.candidate.candidate}.`
);
}
}
전체 코드
'use strict';
// Set up media stream constant and parameters.
// 비디오 만을 허용
// 오디오는 여전히 사용이 불가능 합니다. 오디오 설정 값의 경우 false 가 default 값으로 저장 되어 있기 때문입니다.
const mediaStreamConstraints = {
video: true,
};
// 비디오만을 교환
const offerOptions = {
offerToReceiveVideo: 1,
};
// 연결 시작 시간
let startTime = null;
// 로컬 비디오
const localVideo = document.getElementById('localVideo');
// 컨트롤 하고 있는 비디오
const remoteVideo = document.getElementById('remoteVideo');
let localStream;
let remoteStream;
// 로컬 연결
let localPeerConnection;
// 컨트롤 연결
let remotePeerConnection;
// Define MediaStreams callbacks.
// 미디어스트림을 로컬 영상 객체의 src에 설정
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
localStream = mediaStream;
trace('Received local stream.');
callButton.disabled = false; // 연결 버튼 허용.
}
// 만약에 문제 발생 시 에러 출력
function handleLocalMediaStreamError(error) {
trace(`navigator.getUserMedia error: ${error.toString()}.`);
}
// 연결 완료된 미디어스트림을 리모트 영상 객체의 src에 설정
function gotRemoteMediaStream(event) {
const mediaStream = event.stream;
remoteVideo.srcObject = mediaStream;
remoteStream = mediaStream;
trace('Remote peer connection received remote stream.');
}
// 영상 로딩 관련해서 로그 찍어주기
function logVideoLoaded(event) {
const video = event.target;
trace(`${video.id} videoWidth: ${video.videoWidth}px, ` +
`videoHeight: ${video.videoHeight}px.`);
}
// 현재 보여주고 있는 화면의 크기가 변경 된다면 시간 찍어주기
function logResizedVideo(event) {
logVideoLoaded(event);
if (startTime) {
const elapsedTime = window.performance.now() - startTime;
startTime = null;
trace(`Setup time: ${elapsedTime.toFixed(3)}ms.`);
}
}
// 로컬 비디오에 메타데이터 로딩 완료 시 이벤트 설정
localVideo.addEventListener('loadedmetadata', logVideoLoaded);
// 리모트 비디오에 "
remoteVideo.addEventListener('loadedmetadata', logVideoLoaded);
// 리모트 비디오에 리사이즈 시 이벤트 설정
remoteVideo.addEventListener('onresize', logResizedVideo);
// 새로운 컴퓨터 후보에 연결
// rtc를 사용하면서 요청과 답을 가지고 받는 것을 끝냈을 때
// p2p 연결의 양쪽에서 icecandidate 라는 이벤트를 시작하는데 그 때 이 이벤트를 연결해준다
function handleConnection(event) {
const peerConnection = event.target;
const iceCandidate = event.candidate;
if (iceCandidate) {
const newIceCandidate = new RTCIceCandidate(iceCandidate);
const otherPeer = getOtherPeer(peerConnection);
otherPeer.addIceCandidate(newIceCandidate)
.then(() => {
handleConnectionSuccess(peerConnection);
}).catch((error) => {
handleConnectionFailure(peerConnection, error);
});
trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
`${event.candidate.candidate}.`);
}
}
// p2p 연결 완료 시 success 로그 찍기
function handleConnectionSuccess(peerConnection) {
trace(`${getPeerName(peerConnection)} addIceCandidate success.`);
};
// p2p 연결 실패 시 error 로그 찍기
function handleConnectionFailure(peerConnection, error) {
trace(`${getPeerName(peerConnection)} failed to add ICE Candidate:\n`+
`${error.toString()}.`);
}
// 연결 상태 변경 시 로그 찍기
function handleConnectionChange(event) {
const peerConnection = event.target;
console.log('ICE state change event: ', event);
trace(`${getPeerName(peerConnection)} ICE state: ` +
`${peerConnection.iceConnectionState}.`);
}
// SDP 설정 실패 시 error 로그 찍기
function setSessionDescriptionError(error) {
trace(`Failed to create session description: ${error.toString()}.`);
}
// SDP 설정 설공 시 success 로그 찍기
function setDescriptionSuccess(peerConnection, functionName) {
const peerName = getPeerName(peerConnection);
trace(`${peerName} ${functionName} complete.`);
}
// 로컬 SDP 설정 완료 시 success 로그 찍기
function setLocalDescriptionSuccess(peerConnection) {
setDescriptionSuccess(peerConnection, 'setLocalDescription');
}
// Callee 가 전달 받는 SDP 가 설정 완료 시 success 로그 찍기
function setRemoteDescriptionSuccess(peerConnection) {
setDescriptionSuccess(peerConnection, 'setRemoteDescription');
}
// Offer SDP 생성
function createdOffer(description) {
trace(`Offer from localPeerConnection:\n${description.sdp}`);
trace('localPeerConnection setLocalDescription start.');
localPeerConnection.setLocalDescription(description)
.then(() => {
setLocalDescriptionSuccess(localPeerConnection);
}).catch(setSessionDescriptionError);
trace('remotePeerConnection setRemoteDescription start.');
remotePeerConnection.setRemoteDescription(description)
.then(() => {
setRemoteDescriptionSuccess(remotePeerConnection);
}).catch(setSessionDescriptionError);
trace('remotePeerConnection createAnswer start.');
remotePeerConnection.createAnswer()
.then(createdAnswer)
.catch(setSessionDescriptionError);
}
// Answer SDP 생성
function createdAnswer(description) {
trace(`Answer from remotePeerConnection:\n${description.sdp}.`);
trace('remotePeerConnection setLocalDescription start.');
remotePeerConnection.setLocalDescription(description)
.then(() => {
setLocalDescriptionSuccess(remotePeerConnection);
}).catch(setSessionDescriptionError);
trace('localPeerConnection setRemoteDescription start.');
localPeerConnection.setRemoteDescription(description)
.then(() => {
setRemoteDescriptionSuccess(localPeerConnection);
}).catch(setSessionDescriptionError);
}
// 시작 버튼
const startButton = document.getElementById('startButton');
// 연결 시작 버튼
const callButton = document.getElementById('callButton');
// 연결 끊기 버튼
const hangupButton = document.getElementById('hangupButton');
// 위의 버튼 disable 설정
callButton.disabled = true;
hangupButton.disabled = true;
// 시작 버튼 눌렀을 때, 로컬 미디어스트림 만들기
function startAction() {
startButton.disabled = true; // 시작버튼 비활성화
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
trace('Requesting local stream.');
}
// 연결 시작 버튼 눌렀을 때, 자원 공유 연결 생성
function callAction() {
callButton.disabled = true; //연결 시작 버튼 비활성화
hangupButton.disabled = false; //연결 끊기 버튼 활성화
trace('Starting call.');
startTime = window.performance.now();
const videoTracks = localStream.getVideoTracks();
const audioTracks = localStream.getAudioTracks();
if (videoTracks.length > 0) {
trace(`Using video device: ${videoTracks[0].label}.`);
}
if (audioTracks.length > 0) {
trace(`Using audio device: ${audioTracks[0].label}.`);
}
const servers = null; // RTC 서버 구성 허용
// 자원 공유 연결 생성, 위에서 작성한 이벤트들 적용
localPeerConnection = new RTCPeerConnection(servers);
trace('Created local peer connection object localPeerConnection.');
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
'iceconnectionstatechange', handleConnectionChange);
remotePeerConnection = new RTCPeerConnection(servers);
trace('Created remote peer connection object remotePeerConnection.');
remotePeerConnection.addEventListener('icecandidate', handleConnection);
remotePeerConnection.addEventListener(
'iceconnectionstatechange', handleConnectionChange);
remotePeerConnection.addEventListener('addstream', gotRemoteMediaStream);
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
}
// 연결 끊기
function hangupAction() {
localPeerConnection.close();
remotePeerConnection.close();
localPeerConnection = null;
remotePeerConnection = null;
hangupButton.disabled = true;
callButton.disabled = false;
trace('Ending call.');
}
// 버튼들에 이벤트 연결
startButton.addEventListener('click', startAction);
callButton.addEventListener('click', callAction);
hangupButton.addEventListener('click', hangupAction);
// 다른 컴퓨터 연결 가져오기
function getOtherPeer(peerConnection) {
return (peerConnection === localPeerConnection) ?
remotePeerConnection : localPeerConnection;
}
// 다른 컴퓨터 이름 가져오기
function getPeerName(peerConnection) {
return (peerConnection === localPeerConnection) ?
'localPeerConnection' : 'remotePeerConnection';
}
// 로그 찍기
function trace(text) {
text = text.trim();
const now = (window.performance.now() / 1000).toFixed(3);
console.log(now, text);
}
실행 화면
먼저 첫 번째 해당 챕터의 파일을 실행하게 되면 아래와 같이 세 가지의 버튼이 나오게 되고, 해당 버튼들 중에 start 버튼을 클릭하게 되면, 현재 본인의 캠을 활성화 시켜줍니다.
두 번째로, Call 버튼을 클릭하게 되면 본인의 캠 화면을 전송하여 받는 화면을 띄워주게 됩니다.
'코딩' 카테고리의 다른 글
Typescript Decorator - 타입스크립트 데코레이터 (0) | 2023.03.22 |
---|---|
CSS Viewport-fit과 Safe Area: 모바일 웹 디자인에서 중요한 역할을 하는 두 속성 (0) | 2023.03.21 |
WebRTC (0) | 2022.10.06 |
Typescript Decorator (타입스크립트 데코레이터) (0) | 2022.07.11 |
클래스와 인터페이스 (0) | 2022.06.28 |