Notice
Recent Posts
Recent Comments
Link
투케이2K
167. (TWOK/UTIL) [Web/JavaScript] AWS Kvs WebRtc 뷰어 Viewer 실시간 비디오 및 오디오 영상 통화 수행 본문
투케이2K 유틸파일
167. (TWOK/UTIL) [Web/JavaScript] AWS Kvs WebRtc 뷰어 Viewer 실시간 비디오 및 오디오 영상 통화 수행
투케이2K 2025. 9. 14. 14:44728x90
[설 명]
프로그램 : Web / JavaScript
설 명 : [Web/JavaScript] AWS Kvs WebRtc 뷰어 Viewer 실시간 비디오 및 오디오 영상 통화 수행
[소스 코드]
-----------------------------------------------------------------------------------------
[사전 설명 및 설정 사항]
-----------------------------------------------------------------------------------------
- 개발 환경 : Web
- 개발 기술 : JavaScript (자바스크립트) / AWS / Kvs / WebRTC / Viewer
- 사전) AWS KVS WebRTC 설명 :
>> WebRTC 란 웹, 애플리케이션, 디바이스 간 중간자 없이 오디오나 영상 미디어를 포착하고 실시간 스트림할 뿐 아니라, 임의의 데이터도 교환할 수 있도록 하는 기술입니다
>> WebRTC 는 간단한 API 를 통해 웹 브라우저, 모바일 애플리케이션 및 커넥티드 디바이스 간에 실시간 통신을 활성화할 수 있습니다
>> WebRTC 주요 용어 :
- SDP (Session Description Protocol) : 오디오/비디오 코덱, 해상도, 포트 등 스트리밍 정보를 담은 텍스트 포맷
- Offer / Answer : 통신 연결을 협상하기 위한 SDP 메시지 (초기 연결 설정)
- ICE (Interactive Connectivity Establishment) : NAT/P2P 환경에서도 연결 가능한 경로(IP, 포트 등)를 찾기 위한 기술
- Candidate : 가능한 연결 경로 (IP + Port 조합)
>> WebRTC [ICE] 연결 형태 :
- Relayed Address : TURN 서버가 패킷 릴레이를 위해 할당하는 주소
- Server Reflexive Address : NAT 가 매핑한 클라이언트의 공인망 (Public IP, Port)
- Local Address : 클라이언트의 사설주소 (Private IP, Port)
>> WebRTC STUN 및 TURN 서버 설명 :
- (같은 와이파이 망) STUN 서버는 HOST 를 거쳐 >> Server Reflexive Address 만을 응답하지만,
(릴레이서버 사용) TURN 서버는 Relayed Address와 Server Reflexive Address 를 모두 응답한다
- STUN, TURN 서버를 이용해 SDP Answer IP주소 를 취득 >> RTCPeerConnection Remote 연결 수행
>> WebRTC SDP 오퍼 생성 (뷰어) 및 응답 (마스터) 스트리밍 플로우 :
[Viewer → Signaling Server] -- SDP Offer --> [Master] : 뷰어는 마스터로 스트리밍 오퍼 신호 보낸다
[Master] -- SDP Answer --> [Viewer] : 마스터는 특정 뷰어의 오퍼 신호 확인 후 응답을 보낸다
[Viewer] -- ICE Candidate --> [Master] : 스트리밍을 할 수 있는 경로 확인
[Master] -- ICE Candidate --> [Viewer] : 스트리밍을 할 수 있는 경로 확인
P2P 연결 성립 → 스트리밍 시작
-----------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------
[소스 코드]
-----------------------------------------------------------------------------------------
<!DOCTYPE HTML>
<html lang="ko">
<head>
<title>javaScriptTest</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 반응형 구조 만들기 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<!-- 내부 CSS 스타일 지정 -->
<style>
html, body {
width: 100%;
height: 100%;
margin : 0 auto;
padding : 0;
border : none;
background-color: #666;
}
#container_remoteView {
width: 100%;
height: 98%;
margin: 0 auto;
position: relative;
top: 1%;
}
#remoteView {
width: 100%;
height: 100%;
margin: 0 auto;
background-color: #666;
}
</style>
<!-- [CDN 주소 설정] -->
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script src="https://unpkg.com/amazon-kinesis-video-streams-webrtc/dist/kvs-webrtc.min.js"></script>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1416.0.min.js"></script>
<!-- [자바스크립트 코드 지정] -->
<script>
// --------------------------------------------------------------------------------------------------------------
// [전역 변수 선언]
var kinesisVideoClient = null;
var region = ''; // [AWS 리젼]
var clientId = ''; // [클라이언트 아이디]
var accessKeyId = ''; // [실시간 영상 재생에 필요한 액세스 키]
var secretAccessKey = ''; // [실시간 영상 재생에 필요한 시크릿 키]
var channelARN = ''; // [실시간 스트리밍 채널 ARN 주소]
// --------------------------------------------------------------------------------------------------------------
// [html 최초 로드 및 이벤트 상시 대기 실시]
window.onload = async function() {
console.log("-");
console.log("=========================================");
console.log("[window onload] : [start]");
console.log("=========================================");
console.log("-");
// -----------------------------------------
// [JSON 데이터 삽입 수행] : 하드 코딩 : SKS AWS 됴쿄 리전
// -----------------------------------------
//*
var webJson = {
region : "ap-northeast-1",
clientId : "DEVICE_1", // 중복 되지 않은 고유 클라이언트 값
accessKeyId : "AK..7Q",
secretAccessKey : "Zz..xj",
channelARN : "arn:aws:kinesisvideo:ap-northeast-1:123456789012:channel/DEVICE_1/123456789012"
};
setSystem(JSON.stringify(webJson)); // [Web 자체 데이터 생성 호출]
// */
// -----------------------------------------
// [안드로이드 동작 수행 호출]
// -----------------------------------------
// window.android.system(); // [JSON 전송]
};
// --------------------------------------------------------------------------------------------------------------
async function setSystem(jsonData){
console.log("-");
console.log("=========================================");
console.log("[setSystem] : 웹 데이터 전달 받음");
console.log("-----------------------------------------");
console.log("[데이터] : " + JSON.stringify(jsonData));
console.log("=========================================");
console.log("-");
try {
// -----------------------------------------
// [json 데이터 파싱 수행 실시]
// -----------------------------------------
var jsonObject = JSON.parse(jsonData);
region = String(jsonObject.region); // [AWS 리전]
clientId = String(jsonObject.clientId); // [클라이언트 아이디]
accessKeyId = String(jsonObject.accessKeyId); // [실시간 영상 재생에 필요한 액세스 키]
secretAccessKey = String(jsonObject.secretAccessKey); // [실시간 영상 재생에 필요한 시크릿 키]
channelARN = String(jsonObject.channelARN); // [실시간 스트리밍 채널 ARN 주소]
console.log("-");
console.log("=========================================");
console.log("[setSystem] : 데이터 파싱 정보");
console.log("-----------------------------------------");
console.log("[region] : " + region);
console.log("-----------------------------------------");
console.log("[clientId] : " + clientId);
console.log("-----------------------------------------");
console.log("[accessKeyId] : " + accessKeyId);
console.log("-----------------------------------------");
console.log("[secretAccessKey] : " + secretAccessKey);
console.log("-----------------------------------------");
console.log("[channelARN] : " + channelARN);
console.log("=========================================");
console.log("-");
// -----------------------------------------
// [비디오 플레이 컴포넌트 지정 및 이벤트 지정]
// -----------------------------------------
const remoteView = document.getElementById("remoteView");
//remoteView.onload = (event) => {};
//remoteView.onchange = (event) => {};
remoteView.onerror = (event) => {
console.error("");
console.error("=========================================");
console.error("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onerror");
console.error("=========================================");
console.error("");
};
remoteView.onwaiting = (event) => {
console.log("");
console.log("=========================================");
console.log("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onwaiting");
console.log("=========================================");
console.log("");
};
remoteView.onpause = (event) => {
console.error("");
console.error("=========================================");
console.error("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onpause");
console.error("=========================================");
console.error("");
};
remoteView.onplaying = (event) => {
console.warn("");
console.warn("=========================================");
console.warn("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onplaying");
console.warn("=========================================");
console.warn("");
};
// video.onplay = (event) => {};
// -----------------------------------------
// [KVS 클라이언트 생성]
// -----------------------------------------
kinesisVideoClient = new AWS.KinesisVideo({
region,
accessKeyId,
secretAccessKey,
correctClockSkew: true,
});
console.log("-");
console.log("=========================================");
console.log("[setSystem] : [KinesisVideo] : 비디오 재생 및 정보 관련 객체 초기화 수행]");
console.log("=========================================");
console.log("-");
// -----------------------------------------
// [신호 채널 끝점 가져오기]
// -----------------------------------------
// 각 신호 채널에는 데이터 플레인 작업을 위해 연결할 HTTPS 및 WSS 엔드포인트가 할당됩니다
// -----------------------------------------
const endpoints = await kinesisVideoClient.getSignalingChannelEndpoint({
ChannelARN: channelARN,
SingleMasterChannelEndpointConfiguration: {
Protocols: ['WSS', 'HTTPS'],
Role: KVSWebRTC.Role.VIEWER,
},
})
.promise();
const endpointsByProtocol = endpoints.ResourceEndpointList.reduce((acc, endpoint) => {
acc[endpoint.Protocol] = endpoint.ResourceEndpoint;
return acc;
}, {});
// -----------------------------------------
// [KVS 시그널링 클라이언트 만들기]
// -----------------------------------------
// 응답 의 HTTPS 끝점은 GetSignalingChannelEndpoint이 클라이언트와 함께 사용됩니다
// -----------------------------------------
// 이 클라이언트는 실제 신호가 아닌 ICE 서버를 가져오는 데만 사용됩니다
// -----------------------------------------
const kinesisVideoSignalingChannelsClient = new AWS.KinesisVideoSignalingChannels({
region: region,
accessKeyId,
secretAccessKey,
endpoint: endpointsByProtocol.HTTPS,
correctClockSkew: true,
});
console.log("-");
console.log("=========================================");
console.log("[setSystem] : [KinesisVideoSignalingChannels] : iceServers 신호 채널 정보 확인 객체 초기화 수행]");
console.log("=========================================");
console.log("-");
// -----------------------------------------
// [ICE 서버 구성 가져오기]
// -----------------------------------------
// 최상의 성능을 위해 STUN 및 TURN ICE 서버 구성을 수집합니다
// -----------------------------------------
// [사용 서버 정의]
// -----------------------------------------
const iceServers = [];
// [TUN]
const iceRes = await kinesisVideoSignalingChannelsClient
.getIceServerConfig({
ChannelARN: channelARN,
})
.promise();
const tunServerList = iceRes.IceServerList;
tunServerList?.forEach((iceServer) => {
iceServers.push({
urls: iceServer.Uris,
username: iceServer.Username,
credential: iceServer.Password,
});
});
// [STUN]
const stunIceServers = { urls: `stun:stun.kinesisvideo.${region}.amazonaws.com:443` };
iceServers.push(stunIceServers); // [추가]
console.log("-");
console.log("=========================================");
console.log("[setSystem] : [iceServers] : [서버 연결 정보 확인]");
console.log("-----------------------------------------");
console.log("[스턴 : stunIceServers] : " + JSON.stringify(stunIceServers));
console.log("-----------------------------------------");
console.log("[턴 : tunIceServers] : " + JSON.stringify(tunServerList));
console.log("-----------------------------------------");
console.log("[전체 : ALL] : " + JSON.stringify(iceServers));
console.log("=========================================");
console.log("-");
// -----------------------------------------
// [RTCPeerConnection 만들기]
// -----------------------------------------
// RTCPeerConnection은 웹 에서 WebRTC 통신을 위한 기본 인터페이스입니다
// -----------------------------------------
const config = {
iceServers: iceServers,
iceTransportPolicy: "all", // all | relay
};
const peerConnection = new RTCPeerConnection(config);
// -----------------------------------------
// [WebRTC 시그널링 클라이언트 만들기]
// -----------------------------------------
// 이것은 신호 채널을 통해 메시지를 보내는 데 사용되는 실제 클라이언트입니다
// -----------------------------------------
const signalingClient = new KVSWebRTC.SignalingClient({
channelARN,
channelEndpoint: endpointsByProtocol.WSS,
clientId,
role: KVSWebRTC.Role.VIEWER,
region,
credentials: {
accessKeyId : accessKeyId,
secretAccessKey : secretAccessKey,
},
systemClockOffset: kinesisVideoClient.config.systemClockOffset,
});
console.log("-");
console.log("=========================================");
console.log("[setSystem] : [RTCPeerConnection 및 SignalingClient 생성 이벤트 수신 대기 수행]");
console.log("=========================================");
console.log("-");
// -----------------------------------------
// [시그널링 클라이언트 이벤트 리스너 추가]
// -----------------------------------------
signalingClient.on('open', async () => {
console.log("-");
console.log("=========================================");
console.log("[setSystem] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [open] : [Start]");
console.log("-----------------------------------------");
console.log("[VIEWER] Connected to signaling service");
console.log("-----------------------------------------");
console.log("[로 직] : ", "신호 채널 연결 확인 >> Viewer 의 SDP Offer 수신 대기 수행");
console.log("=========================================");
console.log("-");
// -----------------------------------------
// [카메라 및 오디오 사용 권한 요청 >> 트랙 스트림 추가]
// -----------------------------------------
const localStream = await navigator.mediaDevices.getUserMedia({ // [휴대폰 권한 요청]
video: true,
audio: true,
});
// [해당 트랙 추가로 > 마스터에게 비디오, 음성 데이터 전달]
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream)); // [스트림 트랙에 추가]
// -----------------------------------------
// [SDP 오퍼 생성 및 마스터에게 전송]
// -----------------------------------------
const offer = await peerConnection.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
});
await peerConnection.setLocalDescription(offer);
signalingClient.sendSdpOffer(peerConnection.localDescription);
});
// [SDP 응답이 마스터로부터 다시 수신되면 피어 연결에 추가하십시오]
signalingClient.on('sdpAnswer', async answer => {
console.warn("");
console.warn("=========================================");
console.warn("[setSystem] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [sdpAnswer] : [Start]");
console.warn("-----------------------------------------");
console.warn("[VIEWER] Received SDP answer : ", JSON.stringify(answer));
console.warn("-----------------------------------------");
console.warn("[설 명] : 원격 접속 연결 시도 리스트 확인");
console.warn("-----------------------------------------");
console.warn("[로 직] : ", "sdp 자격 인증 확인 완료 >> peerConnection.setRemoteDescription 피어 연결에 추가 실시");
console.warn("=========================================");
console.warn("");
// -----------------------------------------
// [peerConnection.setRemoteDescription 에 응답 받은 answer 추가]
// -----------------------------------------
await peerConnection.setRemoteDescription(answer);
});
// [마스터로부터 ICE 후보를 수신하면 피어 연결에 추가]
signalingClient.on('iceCandidate', candidate => {
console.log("");
console.log("=========================================");
console.log("[setSystem] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [iceCandidate] : [Start]");
console.log("-----------------------------------------");
console.log("[VIEWER] Received ICE candidate : ", JSON.stringify(candidate));
console.log("-----------------------------------------");
console.log("[설 명] : 원격 접속 연결 시도 리스트 추가");
console.log("-----------------------------------------");
console.log("[로 직] : ", "peerConnection.addIceCandidate 피어 연결에 추가");
console.log("=========================================");
console.log("");
peerConnection.addIceCandidate(candidate);
});
// [ICE 종료 핸들러]
signalingClient.on('close', () => {
console.error("");
console.error("=========================================");
console.error("[setSystem] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [close] : [Start]");
console.error("-----------------------------------------");
console.error("[VIEWER] Disconnected from signaling channel");
console.error("-----------------------------------------");
console.error("[설 명] 원격 연결 신호 끊김 >> 원격 비디오 재생 종료 됨");
console.error("=========================================");
console.error("");
});
// [ICE 에러 핸들러]
signalingClient.on('error', error => {
console.error("");
console.error("=========================================");
console.error("[setSystem] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [error] : [Start]");
console.error("-----------------------------------------");
console.error("[VIEWER] Signaling client error : ", JSON.stringify(error));
console.error("=========================================");
console.error("");
});
// -----------------------------------------
// [피어 연결 이벤트 리스너 추가]
// -----------------------------------------
// [피어 연결에 의해 생성된 모든 ICE 후보를 다른 피어로 보냅니다]
// -----------------------------------------
peerConnection.addEventListener('icecandidate', ({ candidate }) => {
if (candidate) {
console.log("");
console.log("=========================================");
console.log("[setSystem] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [icecandidate] : [Start]");
console.log("-----------------------------------------");
console.log("[Event] icecandidate : ", JSON.stringify(candidate));
console.log("-----------------------------------------");
console.log("[설 명] : 원격 접속 연결 시도 수행");
console.log("-----------------------------------------");
console.log("[로 직] : ", "signalingClient.sendIceCandidate 보내기");
console.log("=========================================");
console.log("");
signalingClient.sendIceCandidate(candidate);
}
else {
// [더 이상 ICE 후보가 생성되지 않습니다]
console.log("");
console.log("=========================================");
console.log("[setSystem] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [close] : [Start]");
console.log("-----------------------------------------");
console.log("[Event] icecandidate : ", JSON.stringify(candidate));
console.log("-----------------------------------------");
console.log("[설 명] : 더이상 원격 접속 연결 시도할 것이 없음");
console.log("=========================================");
console.log("");
}
});
// [원격 트랙이 수신되면 원격 보기에 추가합니다]
peerConnection.addEventListener('track', event => {
console.log("");
console.log("=========================================");
console.log("[setSystem] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [track] : [Start]");
console.log("-----------------------------------------");
console.log("[VIEWER] Received remote track ::", JSON.stringify(event));
console.log("=========================================");
console.log("");
// [스트림 재생 src 지정] : 마스터가 전달한 스트림 데이터 출력
remoteView.srcObject = event.streams[0];
console.warn("");
console.warn("=========================================");
console.warn("[setSystem] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [track] : [Remote Src]");
console.warn("-----------------------------------------");
console.warn("[REMOTE SRC] ::", JSON.stringify(event.streams[0]));
console.warn("-----------------------------------------");
console.warn("[로 직] : ", "원격 remoteView.srcObject 주소 지정 >> 원격 비디오 재생 REMOTE SRC 지정 및 비디오 재생 실시");
console.warn("=========================================");
console.warn("");
});
// -----------------------------------------
// [신호 연결 수행]
// -----------------------------------------
signalingClient.open(); // [signalingClient 열기]
}
catch (exception) {
console.error("-");
console.error("=========================================");
console.error("[setSystem] : [Exception] : 예외 상황 발생");
console.error("-----------------------------------------");
console.error("[ERROR] : ", JSON.stringify(exception.message));
console.error("=========================================");
console.error("-");
}
};
// --------------------------------------------------------------------------------------------------------------
</script>
</head>
<body>
<!-- [컨테이너 생성] -->
<div id="container_remoteView">
<video autoplay="true" id="remoteView"></video>
</div>
</body>
</html>
-----------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------
[참고 사이트]
-----------------------------------------------------------------------------------------
[자바스크립트 AWS WebRTC 실시간 동영상 재생 수행]
https://blog.naver.com/kkh0977/223170500993?trackingCode=blog_bloghome_searchlist
[Aws Kvs WebRTC 실시간 영상 재생 관련 구성 요소 및 용어 정리]
https://blog.naver.com/kkh0977/223858189791
[Aws KVS WebRTC 채널 생성 및 삭제에 관한 요금 정책 정리]
https://blog.naver.com/kkh0977/223995447546
-----------------------------------------------------------------------------------------
728x90
반응형
'투케이2K 유틸파일' 카테고리의 다른 글
Comments
