JavaScript

387. (javaScript) 자바스크립트 AWS Kvs WebRTC 디바이스 역할 (master) sdp answer 응답 및 실시간 비디오 스트림 전송 수행

투케이2K 2025. 5. 12. 20:09
728x90

[개발 환경 설정]

개발 툴 : Edit++

개발 언어 : JavaScript

 

[소스 코드]

-----------------------------------------------------------------------------------------
[사전 설명 및 설정 사항]
-----------------------------------------------------------------------------------------

- 개발 환경 : Web

- 개발 기술 : JavaScript (자바스크립트) / AWS / Kvs / WebRTC

-----------------------------------------------------------------------------------------





-----------------------------------------------------------------------------------------
[소스 코드]
-----------------------------------------------------------------------------------------

<!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;
        }


        .swal-custom-width { width: 280px !important; }
        .swal-footer { text-align: center; }
        .swal-modal { text-align: center; }
        .swal-text { text-align: center; }

    </style>





    <!-- [CDN 주소 설정] -->
    <script src="https://code.jquery.com/jquery-latest.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.js"></script>
    <script src="https://unpkg.com/sweetalert/dist/sweetalert.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>

        // --------------------------------------------------------------------------------------------------------------

        /*
        -----------------------------------------
        [요약 설명]
        -----------------------------------------
        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
        -----------------------------------------
        */

        // --------------------------------------------------------------------------------------------------------------

        // [전역 변수 선언]
        var remotePlayTimer = null;
        var remotePlayFlag = false;
        var remotePlayCount = 0;
        var remoteSrcArray = [];

        var kinesisVideoClient = null;

        var region = ''; // [AWS 리젼]
        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 데이터 삽입 수행]
            // -----------------------------------------
            var webJson = {
                region : "ap-northeast-1",
                accessKeyId : "AK..7Q",
                secretAccessKey : "ZzQ..Kxj",
                channelARN : "arn:aws:kinesisvideo:ap-northeast-1:095225280673:channel/.."
            };

            setSystem(JSON.stringify(webJson)); // [Web 자체 데이터 생성 호출]

        };

        // --------------------------------------------------------------------------------------------------------------

        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 리전]
                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("[accessKeyId] : " + accessKeyId);
                console.log("-----------------------------------------");
                console.log("[secretAccessKey] : " + secretAccessKey);
                console.log("-----------------------------------------");
                console.log("[channelARN] : " + channelARN);
                console.log("=========================================");
                console.log("-");

                // -----------------------------------------
                // [비디오 플레이 컴포넌트 지정 및 이벤트 지정]
                // -----------------------------------------
                const remoteView = document.getElementById("remoteView");

                remoteView.onerror = (event) => {
                    console.error("-");
                    console.error("=========================================");
                    console.error("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onerror");
                    console.error("=========================================");
                    console.error("-");

                    // -----------------------------------------
                    // [플래그 값 변경]
                    // -----------------------------------------
                    remotePlayFlag = false;
                    remotePlayCount = 0;
                };

                remoteView.onwaiting = (event) => {
                    console.log("-");
                    console.log("=========================================");
                    console.log("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onwaiting");
                    console.log("=========================================");
                    console.log("-");

                    // -----------------------------------------
                    // [플래그 값 변경]
                    // -----------------------------------------
                    remotePlayFlag = false;
                    remotePlayCount = 0;
                };

                remoteView.onpause = (event) => {
                    console.error("-");
                    console.error("=========================================");
                    console.error("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onpause");
                    console.error("=========================================");
                    console.error("-");

                    // -----------------------------------------
                    // [플래그 값 변경]
                    // -----------------------------------------
                    remotePlayFlag = false;
                    remotePlayCount = 0;
                };

                remoteView.onplaying = (event) => {
                    console.warn("-");
                    console.warn("=========================================");
                    console.warn("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onplaying");
                    console.warn("=========================================");
                    console.warn("-");

                    // -----------------------------------------
                    // [플래그 값 변경]
                    // -----------------------------------------
                    remotePlayFlag = true;
                };



                // -----------------------------------------
                // [KVS 클라이언트 생성]
                // -----------------------------------------
                kinesisVideoClient = new AWS.KinesisVideo({
                    region,
                    accessKeyId,
                    secretAccessKey,
                    correctClockSkew: true,
                });



                // -----------------------------------------
                // [신호 채널 끝점 가져오기]
                // -----------------------------------------
                // 각 신호 채널에는 데이터 플레인 작업을 위해 연결할 HTTPS 및 WSS 엔드포인트가 할당됩니다
                // -----------------------------------------
                const endpoints = await kinesisVideoClient.getSignalingChannelEndpoint({
                    ChannelARN: channelARN,
                    SingleMasterChannelEndpointConfiguration: {
                        Protocols: ['WSS', 'HTTPS'],
                        Role: KVSWebRTC.Role.MASTER,
                    },
                })
                .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,
                });



                // -----------------------------------------
                // [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
                };

                const peerConnection = new RTCPeerConnection(config);


                // -----------------------------------------
                // [WebRTC 시그널링 클라이언트 만들기]
                // -----------------------------------------
                // 이것은 신호 채널을 통해 메시지를 보내는 데 사용되는 실제 클라이언트입니다
                // -----------------------------------------
                const signalingClient = new KVSWebRTC.SignalingClient({
                    channelARN,
                    channelEndpoint: endpointsByProtocol.WSS,
                    role: KVSWebRTC.Role.MASTER,
                    region,
                    credentials: {
                        accessKeyId : accessKeyId,
                        secretAccessKey : secretAccessKey,
                    },
                    systemClockOffset: kinesisVideoClient.config.systemClockOffset,
                });



                // -----------------------------------------
                // [시그널링 클라이언트 이벤트 리스너 추가]
                // -----------------------------------------
                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: false,
                    });
                    remoteView.srcObject = localStream; // [비디오 뷰에 카메라 및 오디오 스트림 지정]

                    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream)); // [카메라 스트림 트랙에 추가]
                });

                // [Viewer 의 SDP Offer 수신 대기]
                signalingClient.on('sdpOffer', async (offer, remoteClientId) => {

                    // [peerConnection remoteClientId 지정]
                    peerConnection.onicecandidate = event => {
                        if (event.candidate) {
                            signalingClient.sendIceCandidate(event.candidate, remoteClientId);
                        }
                    };
                    console.log(">>>>>>>>>>>>>>>>>>>>>>>> [sdpOffer] : [sendIceCandidate] : [Finish] >>>>>>>>>>>>>>>>>>>>>>>>");


                    // [상대방 오퍼를 내 Remote 에 추가]
                    await peerConnection.setRemoteDescription(offer);
                    console.log(">>>>>>>>>>>>>>>>>>>>>>>> [sdpOffer] : [setRemoteDescription] : [Finish] >>>>>>>>>>>>>>>>>>>>>>>>");


                    // [Answer 옵션 지정 및 생성]
                    const answer = await peerConnection.createAnswer();
                    console.log(">>>>>>>>>>>>>>>>>>>>>>>> [sdpOffer] : [createAnswer] : [Finish] >>>>>>>>>>>>>>>>>>>>>>>>");


                    // [Answer 를 내 local 로 저장]
                    await peerConnection.setLocalDescription(answer);
                    console.log(">>>>>>>>>>>>>>>>>>>>>>>> [sdpOffer] : [setLocalDescription] : [Finish] >>>>>>>>>>>>>>>>>>>>>>>>");


                    // [SDP Answer 응답 전송]
                    console.log(">>>>>>>>>>>>>>>>>>>>>>>> [sdpOffer] : [sendSdpAnswer] : ["+answer.type+"] >>>>>>>>>>>>>>>>>>>>>>>>");

                    //signalingClient.sendSdpAnswer(answer); // [클라이언트가 응답 받지 못함]
                    signalingClient.sendSdpAnswer(peerConnection.localDescription, remoteClientId); // [접속 시도한 기기 아이디를 지정해 줘야함]


                    console.log("-");
                    console.log("=========================================");
                    console.log("[setSystem] : [로컬 <-> 원격 연결 신호 확인] : [signalingClient] : [sdpOffer] : [Start]");
                    console.log("-----------------------------------------");
                    console.log("[VIEWER] Received SDP Offer from Viewer : ", remoteClientId);
                    console.log("-----------------------------------------");
                    console.log("[설 명] : 뷰어 SDP Offer 확인 및 Answer 응답 전송 수행");
                    console.log("=========================================");
                    console.log("-");

                });

                // [Viewer 의 ICE Candidate 수신 대기]
                signalingClient.on('iceCandidate', candidate => {
                    console.log(">>>>>>>>>>>>>>>>>>>>>>>> [iceCandidate] : [원격 접속 연결 시도 리스트 추가] : ["+JSON.stringify(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("-");
                });


                // -----------------------------------------
                // [신호 연결 수행]
                // -----------------------------------------
                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" controls id="remoteView"></video>
</div>

</body>

</html>

-----------------------------------------------------------------------------------------





-----------------------------------------------------------------------------------------
[참고 사이트]
-----------------------------------------------------------------------------------------

[자바스크립트 AWS WebRTC 실시간 동영상 재생 수행 - KVS Stream Video]

https://blog.naver.com/kkh0977/223170500993?trackingCode=blog_bloghome_searchlist


[Aws Kinesis Video Streams] Aws Kvs WebRTC 실시간 영상 재생 관련 구성 요소 및 용어 정리

https://blog.naver.com/kkh0977/223858189791


[Aws Security Token Service] Aws STS 임시 보안 자격 증명 설명 정리

https://blog.naver.com/kkh0977/223846461194


[Aws AssumeRole] Aws AssumeRole 역할 전환 및 임시 자격 증명 공유 사용 설명 정리

https://blog.naver.com/kkh0977/223853566319


[Aws Kinesis Video Streams] Aws Kvs 구성 요소 용어 정리 - 생산자 , 소비자 , 청크 , 조각

https://blog.naver.com/kkh0977/223858193008

-----------------------------------------------------------------------------------------
 
728x90
반응형