투케이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>

 

반응형
Comments