Notice
Recent Posts
Recent Comments
Link
투케이2K
317. (javaScript) 자바스크립트 AWS WebRTC 실시간 동영상 재생 수행 - KVS Stream Video 본문
JavaScript
317. (javaScript) 자바스크립트 AWS WebRTC 실시간 동영상 재생 수행 - KVS Stream Video
투케이2K 2023. 7. 31. 08:05[개발 환경 설정]
개발 툴 : Edit++
개발 언어 : JavaScript
[소스 코드]
<!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;
}
#container_localView {
width: 95%;
height: 20%;
margin: 0 auto;
position: relative;
top: 10%;
border: 10px #333 solid;
}
#localView {
width: 100%;
height: 100%;
margin: 0 auto;
background-color: #666;
}
#container_remoteView {
width: 95%;
height: 20%;
margin: 0 auto;
position: relative;
top: 20%;
border: 10px #0000ff solid;
}
#remoteView {
width: 100%;
height: 100%;
margin: 0 auto;
background-color: #666;
}
</style>
<!-- ===================================================================================================== -->
<!-- [CDN 주소 설정] -->
<!-- ===================================================================================================== -->
<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>
// --------------------------------------------------------------------------------------------------------------
/*
-----------------------------------------
[요약 설명]
-----------------------------------------
1. CDN 설치 방법 : CDN 의존성 코드 참고
-----------------------------------------
2. 참고 사이트 :
https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-sdk-js.html
https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-js
https://github.com/aws/aws-sdk-js
https://www.npmjs.com/package/amazon-kinesis-video-streams-webrtc
https://www.jsdelivr.com/package/npm/amazon-kinesis-video-streams-webrtc-nodejs
https://developer.mozilla.org/ko/docs/Web/API/RTCPeerConnection
https://www.jsdelivr.com/package/npm/vlc-media-player
https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-how-it-works.html
https://lovejaco.github.io/posts/webrtc-connectivity-and-nat-traversal/
-----------------------------------------
*/
// --------------------------------------------------------------------------------------------------------------
// [전역 변수 선언]
var remotePlayTimer = null;
var remotePlayFlag = false;
var remotePlayCount = 0;
var remoteSrcArray = [];
var kinesisVideoClient = null;
const region = 'ap-northeast-2'; // [AWS 리젼]
const clientId = 'TEST_0000000123'; // [클라이언트 아이디]
const accessKeyId = 'ASIARMK66PCQRBLXQNUL'; // [실시간 영상 재생에 필요한 액세스 키]
const secretAccessKey = 'vXJilrfNfBxZIMx0BdkFkMd8nbsOhRGyfGZKqrfD'; // [실시간 영상 재생에 필요한 시크릿 키]
const sessionToken = 'IQoJb3JpZ2lW5vcnRoZWFzdC0yIkcwRQIhAOJpIqcH3wA/S7MqVqf2vXxKP6dONpoKQho7jmV5VhBUAiAFUVhK+8caN54rYernhV6Sop0Vo5oUVsqvx2TBTzo8FyrRx+wc='; // [실시간 영상 재생에 필요한 세션 토큰]
const channelARN = 'arn:aws:kinesisvideo:ap-northeast-2:095225280673:channel/TEST_0000000123/1689570882181'; // [실시간 스트리밍 채널 ARN 주소]
// --------------------------------------------------------------------------------------------------------------
// [html 최초 로드 및 이벤트 상시 대기 실시]
window.onload = async function() {
console.log("");
console.log("=========================================");
console.log("[window onload] : [start]");
console.log("=========================================");
console.log("");
// -----------------------------------------
// [비디오 플레이 컴포넌트 지정]
// -----------------------------------------
const localView = document.getElementById("localView");
const remoteView = document.getElementById("remoteView");
//remoteView.onload = (event) => {};
//remoteView.onchange = (event) => {};
remoteView.onerror = (event) => {
console.error("");
console.error("=========================================");
console.error("[window onload] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onerror");
console.error("=========================================");
console.error("");
// [플래그 값 변경]
remotePlayFlag = false;
remotePlayCount = 0;
};
remoteView.onwaiting = (event) => {
console.log("");
console.log("=========================================");
console.log("[window onload] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onwaiting");
console.log("=========================================");
console.log("");
// [플래그 값 변경]
remotePlayFlag = false;
remotePlayCount = 0;
};
remoteView.onpause = (event) => {
console.error("");
console.error("=========================================");
console.error("[window onload] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onpause");
console.error("=========================================");
console.error("");
// [플래그 값 변경]
remotePlayFlag = false;
remotePlayCount = 0;
};
remoteView.onplaying = (event) => {
console.warn("");
console.warn("=========================================");
console.warn("[window onload] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onplaying");
console.warn("=========================================");
console.warn("");
// [플래그 값 변경]
remotePlayFlag = true;
};
// video.onplay = (event) => {};
// -----------------------------------------
// [KVS 클라이언트 생성]
// -----------------------------------------
kinesisVideoClient = new AWS.KinesisVideo({
region,
credentials: {
accessKeyId,
secretAccessKey,
sessionToken,
},
correctClockSkew: true,
});
// -----------------------------------------
// [신호 채널 끝점 가져오기]
// -----------------------------------------
// 각 신호 채널에는 데이터 플레인 작업을 위해 연결할 HTTPS 및 WSS 엔드포인트가 할당됩니다
// -----------------------------------------
const getSignalingChannelEndpointResponse = await kinesisVideoClient
.getSignalingChannelEndpoint({
ChannelARN: channelARN,
SingleMasterChannelEndpointConfiguration: {
Protocols: ['WSS', 'HTTPS'],
Role: KVSWebRTC.Role.VIEWER,
},
})
.promise();
const endpointsByProtocol = getSignalingChannelEndpointResponse.ResourceEndpointList.reduce((endpoints, endpoint) => {
endpoints[endpoint.Protocol] = endpoint.ResourceEndpoint;
return endpoints;
}, {});
// -----------------------------------------
// [KVS 시그널링 클라이언트 만들기]
// -----------------------------------------
// 응답 의 HTTPS 끝점은 GetSignalingChannelEndpoint이 클라이언트와 함께 사용됩니다
// -----------------------------------------
// 이 클라이언트는 실제 신호가 아닌 ICE 서버를 가져오는 데만 사용됩니다
// -----------------------------------------
const kinesisVideoSignalingChannelsClient = new AWS.KinesisVideoSignalingChannels({
region: region,
credentials: {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: sessionToken,
},
endpoint: endpointsByProtocol.HTTPS,
//endpoint: endpointsByProtocol.WSS,
correctClockSkew: true,
});
// -----------------------------------------
// [ICE 서버 구성 가져오기]
// -----------------------------------------
// 최상의 성능을 위해 STUN 및 TURN ICE 서버 구성을 수집합니다
// -----------------------------------------
// [사용 서버 정의]
const iceServers = [];
// [TUN]
const getIceServerConfigResponse = await kinesisVideoSignalingChannelsClient
.getIceServerConfig({
ChannelARN: channelARN,
})
.promise();
const tunServerList = getIceServerConfigResponse.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("[window onload] : [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,
sessionToken: sessionToken,
},
systemClockOffset: kinesisVideoClient.config.systemClockOffset,
});
// -----------------------------------------
// [시그널링 클라이언트 이벤트 리스너 추가]
// -----------------------------------------
// [신호 채널 연결이 열리면 웹캠에 연결하고 제안을 만들어 마스터에게 보냅니다]
signalingClient.on('open', async () => {
console.log("");
console.log("=========================================");
console.log("[window onload] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [open] : [Start]");
console.log("-----------------------------------------");
console.log("[VIEWER] Connected to signaling service");
console.log("-----------------------------------------");
console.log("[로 직] : ", "신호 채널 연결 확인 >> 웹캠 권한 체크 및 LOCAL SRC 지정 수행");
console.log("=========================================");
console.log("");
// [웹캠에서 스트림을 가져와 피어 연결에 추가하고 로컬 보기에 표시]
try {
const localStream = await navigator.mediaDevices.getUserMedia({
video: { width: { ideal: 1280 }, height: { ideal: 720 } },
audio: true,
}); // [웹 카메라 권한 요청]
// [로컬 영상 출력 스트림 지정]
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
localView.srcObject = localStream;
console.log("");
console.log("=========================================");
console.log("[window onload] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [open] : [Local Src]");
console.log("-----------------------------------------");
console.log("[LOCAL SRC] ::", JSON.stringify(localView.srcObject));
console.log("-----------------------------------------");
console.log("[로 직] : ", "로컬 localView.srcObject 주소 지정");
console.log("=========================================");
console.log("");
} catch (e) {
console.error("");
console.error("=========================================");
console.error("[window onload] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [open] : [Exception]");
console.error("-----------------------------------------");
console.error("[ERROR] : " + JSON.stringify(e));
console.error("=========================================");
console.error("");
return;
}
// [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("[window onload] : [로컬 <-> 원격 연결 신호 확인] : [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("");
await peerConnection.setRemoteDescription(answer);
});
// [마스터로부터 ICE 후보를 수신하면 피어 연결에 추가]
signalingClient.on('iceCandidate', candidate => {
console.log("");
console.log("=========================================");
console.log("[window onload] : [로컬 <-> 원격 연결 신호 확인] : [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("[window onload] : [로컬 <-> 원격 연결 신호 확인] : [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("[window onload] : [로컬 <-> 원격 연결 신호 확인] : [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("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [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("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [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("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [track] : [Start]");
console.log("-----------------------------------------");
console.log("[VIEWER] Received remote track ::", JSON.stringify(event));
console.log("=========================================");
console.log("");
if (remoteView.srcObject) {
console.error("");
console.error("=========================================");
console.error("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [track] : [return]");
console.error("-----------------------------------------");
console.error("[설 명] 원격 비디오 REMOTE SRC 이미 설정 되어 있는 상태");
console.error("=========================================");
console.error("");
// [배열에 담아 놓는다 : 첫번째 연결이 실패할 경우 >> 다른걸로 재생 위함]
remoteSrcArray.push(event.streams[0]);
return;
}
else {
remoteView.srcObject = event.streams[0];
console.warn("");
console.warn("=========================================");
console.warn("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [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("");
// [타이머 돌리면서 실제로 재생되고 있는 상태인지 확인]
/*
remotePlayTimer = setInterval(function() {
/*
remotePlayTimer = setInterval(function() {
// [원격 비디오 재생 여부 확인]
if (remotePlayFlag == true){
console.warn("");
console.warn("=========================================");
console.warn("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [track] : [Remote Src]");
console.warn("-----------------------------------------");
console.warn("[로 직] : ", "원격 비디오 재생 정상 플레이 상태 확인");
console.warn("=========================================");
console.warn("");
// [타이머 종료 함수 호출]
clearInterval(remotePlayTimer); // [반복 실행 정지]
remotePlayTimer = null; // [객체 초기화]
remotePlayCount = 0;
return;
}
if (remotePlayFlag == false && remotePlayCount <= 10){ // [10 초 간 재생 체크]
console.log("");
console.log("=========================================");
console.log("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [track] : [Remote Src]");
console.log("-----------------------------------------");
console.log("[로 직] : ", "원격 비디오 재생 플레이 상태 체크 수행");
console.log("-----------------------------------------");
console.log("[체크 카운트] : ", remotePlayCount);
console.log("=========================================");
console.log("");
remotePlayCount ++;
}
else { // [10 초 이상 : 안붙음]
// [추가로 연결 시도할 데이터가 있는지 확인]
if (remoteSrcArray != null && remoteSrcArray.length>0){
console.log("");
console.log("=========================================");
console.log("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [track] : [Remote Src]");
console.log("-----------------------------------------");
console.log("[로 직] : ", "원격 비디오 재생 플레이 연기 및 화면 안나오는 상태 remoteView.srcObject 초기화");
console.log("=========================================");
console.log("");
remoteView.srcObject = remoteSrcArray[remoteSrcArray.length-1];
remoteSrcArray.pop(); // [배열 마지막 삭제]
remotePlayCount = 0;
remoteView.load();
}
else {
console.error("");
console.error("=========================================");
console.error("[window onload] : [로컬 <-> 원격 접속 상태 확인] : [peerConnection] : [track] : [Remote Src]");
console.error("-----------------------------------------");
console.error("[로 직] : ", "원격 비디오 재생 플레이 연기 및 화면 안나오는 상태 remoteView.srcObject 초기화");
console.error("=========================================");
console.error("");
// [타이머 종료 함수 호출]
clearInterval(remotePlayTimer); // [반복 실행 정지]
remotePlayTimer = null; // [객체 초기화]
remotePlayCount = 0;
remoteView.srcObject = null;
}
}
}, 1500);
// */
}
});
// -----------------------------------------
// [신호 연결 수행]
// -----------------------------------------
signalingClient.open(); // [카메라 권한 활성]
};
// --------------------------------------------------------------------------------------------------------------
</script>
</head>
<body>
<!-- [컨테이너 생성] -->
<div id="container_localView">
<video autoplay="true" id="localView"></video>
</div>
<!-- [컨테이너 생성] -->
<div id="container_remoteView">
<video autoplay="true" id="remoteView"></video>
</div>
</body>
</html>
반응형
'JavaScript' 카테고리의 다른 글
Comments