일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 자바
- Baekjoon
- 알고리즘
- 코딩테스트
- CSS
- Next.js
- js
- 프로그래머스
- 자바스크립트
- Lv2
- CLASS
- Typescript
- 네트워크
- greedy
- 연습문제
- SWEA
- 정렬
- bfs/dfs
- node.js
- 백준
- 그리디
- React
- 프로그래머스 JS
- Java
- 이것이 코딩테스트다 with 파이썬
- Python
- Lv1
- javascript
- programmers
- 코딩테스트 입문
- Today
- Total
개발야옹
[Web] WebRTC와 SIP를 활용하여 통화기능 구현하기 - React + Typescript 본문
WebRTC?
WebRTC(Web Real-Time Communication)는 웹 브라우저와 모바일에서 플러그인이나 추가 소프트웨어 없이 실시간으로 통신하게 해주는 기술로, WebRTC를 통해 음성 통화, 비디오 통화, 파일 공유 등을 웹 브라우저만으로 할 수 있게 해준다.
주요기능
1. 음성 및 비디오 통화
2. 데이터 공유
WebRTC 구성요소
1. getUserMedia: 사용자의 장치(카메라, 마이크 등)에서 미디어 스트림을 가져온다.
- 사용자에게 장치에 대한 권한을 요청하고, 비디오와 오디오 데이터를 캡쳐할 수 있다.
2. RTCPeerConnection: 두 브라우저 간의 직접 연결을 설정하고 관리한다.
- 피어 간의 미디어 스트림을 주고받을 수 있다.
3. ICE (Interactice Connectivity Establishment): 피어간의 연결을 설정하고 유지하는 데 도움을 준다.
- NAT 및 방화벽을 통과하여 두 브라우저가 서로 연결될 수 있도록 한다.
WebRTC의 작동 방식
1. 미디어 캡처 : 'getUserMedia'를 사용하여 사용자의 카메라와 마이크에서 미디어 스트림을 캡처한다
2. 피어 연결 설정: 'RTCPeerConnection'을 사용하여 두 브라우저 간의 연결을 설정한다. 이 과정에서 ICE 프로토콜을 사용하여 서로의 네트워크 환경에 맞는 최적의 경로를 찾는다.
3. 신호 교환: 두 브라우저가 서로 연결되기 위해 서버를 통해서 신호 교환을 한다.
- Offer/Answer: 한 브라우저가 연결 요청(Offer)을 보내면, 다른 브라우저는 이를 수락(Answer)한다.
- ICE Candidates: 연결을 설정하기 위해 서로의 네트워크 정보(ICE Candidates)를 교환한다.
4. 미디어 스트림 전송: 연결이 설정되면, 두 브라우저는 직접 미디어 스트림을 주고 받을 수 있다.
SIP?
SIP(Session Initiation Protocol)은 통신 세션을 제어하기 위한 프로토콜이다.
OSI 7계층의 응용계층에 속하며, TCP, UDP 모두 사용가능하며 Request/Response 구조이다.
인터넷전화, 멀티미디어 배포 회의가 포함이 되며 SIP는 이를 설정, 수정, 종료할 수 있는 시그널링 프로토콜이다.
React + Typescript 환경에서 WebRTC + SIP로 통화기능 구현하기
1. 프로젝트 생성
npx create-react-app webrtc-app --template typescript
2. 패키지 설치
npm install socket.io-client
npm install jssip // 통화 기능을 위한 라이브러리
3. Socket.io 설정
'src/socket.ts'
import { io } from 'socket.io-client';
const socket = io('http://localhost:5000'); // 서버 주소에 맞게 변경
export default socket;
4. 미디어 스트림 설정
사용자의 비디오와 오디오 스트림을 가져오는 방법 설정
// 'src/hooks/useMediaStream.ts'
import { useEffect, useState } from 'react';
const useMediaStream = () => {
const [stream, setStream] = useState<MediaStream | null>(null);
useEffect(() => {
const getMediaStream = async () => {
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
setStream(mediaStream);
} catch (error) {
console.error('Error accessing media devices.', error);
}
};
getMediaStream();
}, []);
return stream;
};
export default useMediaStream;
5. RTCPeerConnection 설정
"RTCPeerConnection"을 설정하고 연결 과정을 관리하는 훅 생성
// 'src/hooks/usePeerConnection.ts'
import { useEffect, useRef } from 'react';
import socket from '../socket';
const usePeerConnection = (localStream: MediaStream | null) => {
const peerConnection = useRef<RTCPeerConnection | null>(null);
useEffect(() => {
if (!localStream) return;
peerConnection.current = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
});
peerConnection.current.onicecandidate = event => {
if (event.candidate) {
socket.emit('ice-candidate', event.candidate);
}
};
peerConnection.current.ontrack = event => {
// 리모트 스트림을 설정하는 곳
};
localStream.getTracks().forEach(track => {
peerConnection.current?.addTrack(track, localStream);
});
socket.on('ice-candidate', (candidate: RTCIceCandidate) => {
peerConnection.current?.addIceCandidate(new RTCIceCandidate(candidate));
});
socket.on('offer', async (offer: RTCSessionDescriptionInit) => {
if (peerConnection.current) {
await peerConnection.current.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await peerConnection.current.createAnswer();
await peerConnection.current.setLocalDescription(answer);
socket.emit('answer', answer);
}
});
socket.on('answer', async (answer: RTCSessionDescriptionInit) => {
if (peerConnection.current) {
await peerConnection.current.setRemoteDescription(new RTCSessionDescription(answer));
}
});
return () => {
peerConnection.current?.close();
peerConnection.current = null;
};
}, [localStream]);
const createOffer = async () => {
if (peerConnection.current) {
const offer = await peerConnection.current.createOffer();
await peerConnection.current.setLocalDescription(offer);
socket.emit('offer', offer);
}
};
return { createOffer };
};
export default usePeerConnection;
6. SIP 설정
SIP서버와 연결하기 위해 필요한 설정 정의.
// src/sipConfig.ts
export const sipConfig = {
uri: 'sip:your_sip_username@your_sip_server',
password: 'your_sip_password',
wsServers: 'wss://your_sip_server:8089/ws',
display_name: 'Your Display Name'
};
7. SIP 초기화
SIP UA(User Agent)를 초기화하고 전화 기능 설정
// src/hooks/useSip.ts
import { useEffect, useRef } from 'react';
import JsSIP from 'jssip';
import { sipConfig } from '../sipConfig';
const useSip = () => {
const uaRef = useRef<JsSIP.UA | null>(null);
useEffect(() => {
const socket = new JsSIP.WebSocketInterface(sipConfig.wsServers);
const configuration = {
sockets: [socket],
uri: sipConfig.uri,
password: sipConfig.password,
display_name: sipConfig.display_name,
};
uaRef.current = new JsSIP.UA(configuration);
uaRef.current.start();
return () => {
uaRef.current?.stop();
};
}, []);
const makeCall = (target: string) => {
if (uaRef.current) {
const eventHandlers = {
progress: () => {
console.log('call is in progress');
},
failed: () => {
console.log('call failed');
},
ended: () => {
console.log('call ended');
},
confirmed: () => {
console.log('call confirmed');
},
};
const options = {
eventHandlers,
mediaConstraints: { audio: true, video: false },
};
uaRef.current.call(target, options);
}
};
return { makeCall };
};
export default useSip;
6. 컴포넌트 설정
// src/App.tsx
import React, { useRef } from 'react';
import useMediaStream from './hooks/useMediaStream';
import usePeerConnection from './hooks/usePeerConnection';
const App: React.FC = () => {
const localVideoRef = useRef<HTMLVideoElement | null>(null);
const remoteVideoRef = useRef<HTMLVideoElement | null>(null);
const localStream = useMediaStream();
const { createOffer } = usePeerConnection(localStream);
if (localStream && localVideoRef.current && !localVideoRef.current.srcObject) {
localVideoRef.current.srcObject = localStream;
}
return (
<div>
<video ref={localVideoRef} autoPlay playsInline muted />
<video ref={remoteVideoRef} autoPlay playsInline />
<button onClick={createOffer}>Start Call</button>
</div>
);
};
export default App;
'프로그래밍' 카테고리의 다른 글
[협업 도구] Jira 기본 사용법 (0) | 2024.07.03 |
---|---|
[우아한테크코스 - 프리코스(BE)] 3주차 미션 과정 (0) | 2023.11.09 |
[우아한테크코스 - 프리코스(BE)] 2주차 미션 과정 (0) | 2023.11.01 |
MVC (Model, View, Controller) (0) | 2023.10.29 |
Java Naming Convention (0) | 2023.10.24 |