투케이2K

192. (TWOK/UTIL) [Web/JavaScript] [기능 보완] 자바스크립트 AWS Kvs HLS 뷰어 Viewer 비디오 스트림 영상 재생 수행 유틸 - 재시도 적용 본문

투케이2K 유틸파일

192. (TWOK/UTIL) [Web/JavaScript] [기능 보완] 자바스크립트 AWS Kvs HLS 뷰어 Viewer 비디오 스트림 영상 재생 수행 유틸 - 재시도 적용

투케이2K 2026. 6. 7. 14:45
728x90
반응형

[설 명]

프로그램 : Web / JavaScript

설 명 : [Web/JavaScript] [기능 보완] 자바스크립트 AWS Kvs HLS 뷰어 Viewer 비디오 스트림 영상 재생 수행 유틸 - 재시도 적용

 

[소스 코드]

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

- 개발 환경 : Web


- 개발 기술 : JavaScript / AWS / Kvs / HLS / Viewer


- 사전) 👉 비디오 스트림 간략 설명 정리 : 

  >> 비디오 스트림은 라이브 비디오 및 기타 시간이 인코딩된 데이터를 캡처하고, 선택적으로 저장하고, 실시간, 배치 혹은 애드혹 형식으로 데이터의 소비를 가능하게 할 수 있도록 해 주는 리소스입니다

  >> 일반적인 구성에서는 Kinesis 비디오 스트림은 데이터를 푸시해 주는 생산자가 하나만 있습니다 (실시간 스트리밍 데이터를 밀어 넣어주는 하드웨어 기기)


- 사전) 👉 HLS 개념 설명 : 

  >> HTTP 라이브 스트리밍으로 인터넷을 통해 소비자에게 미디어 콘텐츠를 제공하는 데 사용되는 스트리밍 프로토콜입니다

  >> HLS 는 Apple 장치에서 지원하는 유일한 형식입니다

  >> 미디어는 h.264 또는 h.265 인코딩된 비디오를 포함해야 하며 AAC 인코딩된 오디오는 선택 사항입니다

  >> 미디어 타입 유효한 값의 예로는 "video/h264" 및 "video/h264,audio/aac" 가 있습니다


- 사전) 👉 PlaybackMode 설명 : 

  >> 라이브, 라이브 재생 또는 아카이브된 온디맨드 데이터를 검색할지 여부입니다

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





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

<!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://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://sdk.amazonaws.com/js/aws-sdk-2.1560.0.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>

    <!-- <script src="../Desktop/aws-sdk-2.1560.0.min.js"></script> -->
    <!-- <script src="../Desktop/hls.js"></script> -->






    <!-- [자바스크립트 코드 지정] -->
    <script>

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

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

        var kinesisVideoClient = null;

        var localStream = null; // 마스터 카메라 영상 스트림

        var region = 'ap-northeast-1'; // [AWS 리전]
        var accessKeyId = 'AK..7Q'; // [IAM 액세스 키]
        var secretAccessKey = 'Zz..xj'; // [IAM 시크릿 키]
        
        var streamName = 'DEVICE_1'; // [스트림 명칭]

        var hlsStreamType = 'LIVE_REPLAY'; // ✅ [스트리밍 타입] : LIVE or LIVE_REPLAY

        var remoteView = null;
        var retryCnt = 0;

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

        // 🟦 [html 최초 로드 및 이벤트 상시 대기 실시]
        window.onload = async function() {
          console.log("[window onload] : [start]");

          // -----------------------------------------
          // [JSON 데이터 삽입 수행] : 하드 코딩
          // -----------------------------------------
          var webJson = {
            region : region,
            accessKeyId : accessKeyId,
            secretAccessKey : secretAccessKey,

            streamName : streamName,
            
            hlsStreamType : hlsStreamType
          };

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

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

        // 🟦 [AWS KVS HLS 비디오 재생 관련 : getDataEndpoint]
        async function setSystem(jsonData){
          console.log("[setSystem] : 웹 데이터 전달 받음 : ", JSON.stringify(jsonData));

          try {

            // -----------------------------------------
            // [json 데이터 파싱 수행 실시]
            // -----------------------------------------
            var jsonObject = JSON.parse(jsonData);

            var parse_region = String(jsonObject.region); // [AWS 리전]
            var parse_accessKeyId = String(jsonObject.accessKeyId); // [IAM 액세스 키]
            var parse_secretAccessKey = String(jsonObject.secretAccessKey); // [IAM 시크릿 키]
            
            var parse_streamName = String(jsonObject.streamName); // [비디오 스트림 명칭]

            var parse_hlsStreamType = String(jsonObject.hlsStreamType); // [비디오 스트림 타입]

            console.log("");
            console.log("=========================================");
            console.log("[setSystem] : 데이터 파싱 정보");
            console.log("-----------------------------------------");
            console.log("[region] : " + parse_region);
            console.log("-----------------------------------------");
            console.log("[accessKeyId] : " + parse_accessKeyId);
            console.log("-----------------------------------------");
            console.log("[secretAccessKey] : " + parse_secretAccessKey);
            console.log("-----------------------------------------");
            console.log("[streamName] : " + parse_streamName);
            console.log("-----------------------------------------");
            console.log("[hlsStreamType] : " + parse_hlsStreamType);
            console.log("=========================================");
            console.log("");


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

            remoteView.muted = true; // ✅ 음소거 설정 : 웹 자바스크립트 : NotAllowedError 조치
            remoteView.autoplay = true;
            remoteView.controls = true; // 컨트롤 박스 표시

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

            /*
            remoteView.onwaiting = (event) => { // [디바이스 스트리밍 송출 종료 시 로딩 프로그레스를 돌리기 위해 주석]
              console.log("[setSystem] : [remoteView] : 원격 비디오 뷰 컴포넌트 : onwaiting");
            };
            // */

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

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



            // -----------------------------------------
            // [타이머를 반복 돌리며 실시간 해상도 변경 감지]
            // -----------------------------------------
            var lastWidth = 0;
            var lastWidth = 0;

            setInterval(() => {
              console.log('>>>>>>>>>>>>>>>>>> [check setInterval running] >>>>>>>>>>>>>>>>>>');

              const currentWidth = remoteView.videoWidth;
              const currentHeight = remoteView.videoHeight;

              // 해상도가 변경되었을 때만 로그 출력
              if (currentWidth !== lastWidth || currentHeight !== lastHeight) {
                console.log('[setInterval : resolution] : ', currentWidth + 'x' + currentHeight);
                lastWidth = currentWidth;
                lastHeight = currentHeight;

                var msg = "\n" + "[디바이스 기기] : [비디오 해상도 정보 확인]" + "\n";
                msg += "\n" + "[Resolution] : " + currentWidth + 'x' + currentHeight + "\n";

                console.warn("[setSystem] : [Resolution] : ", msg);
              }

            }, 2000); // 2초마다 확인



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

            AWS.config.update({
                region: parse_region,
                accessKeyId: parse_accessKeyId,
                secretAccessKey: parse_secretAccessKey
            });

            kinesisVideoClient = new AWS.KinesisVideo({
                correctClockSkew: true,
            });


            console.log("");
            console.log("=========================================");
            console.log("[setSystem] : [KinesisVideo] : 비디오 재생 및 정보 관련 객체 초기화 수행");
            console.log("=========================================");
            console.log("");


            // -----------------------------------------
            // [KVS 스트림 엔드포인트 가져오기]
            // -----------------------------------------
            kinesisVideoClient.getDataEndpoint({
              StreamName: parse_streamName,
              APIName: 'GET_HLS_STREAMING_SESSION_URL' // ✅ HLS 세션 URL 가져오기 타입 지정
            }, function(err, response){
              if (err){
                console.error("");
                console.error("=========================================");
                console.error("[setSystem] : [getDataEndpoint] : ❌ 예외 상황 발생");
                console.error("-----------------------------------------");
                console.error("[ERROR] : ", err.message);
                console.error("=========================================");
                console.error("");

                return;

              }
              
              console.log("");
              console.log("=========================================");
              console.log("[setSystem] : [getDataEndpoint] : response 확인");
              console.log("-----------------------------------------");
              console.log(JSON.stringify(response));
              console.log("=========================================");
              console.log("");

              const endpoint = response.DataEndpoint;

              if (endpoint != null && endpoint != '' && endpoint != 'undefined'){

                getLiveHLSUrl(parse_region, endpoint, parse_accessKeyId, parse_secretAccessKey, parse_streamName, parse_hlsStreamType);

              }
              else {
                console.error("[setSystem] : [Error] : ❌ DataEndpoint Is Null");
              }

            });

          }
          catch (exception) {
            console.error("[setSystem] : [Exception] : ❌ 예외 상황 발생 : ", exception.message);
          }

        };

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

        // 🟦 [AWS KVS HLS 비디오 재생 관련 : 엔드포인트를 기반으로 HLS 세션 URL 가져오기 및 HLS 뷰어 재생]
        async function getLiveHLSUrl(region, endpoint, accessKeyId, secretAccessKey, streamName, hlsStreamType) {
          console.log("");
          console.log("=========================================");
          console.log("[getLiveHLSUrl] : HLS 뷰어 재생 Url 확인 및 비디오 플레이 수행");
          console.log("-----------------------------------------");
          console.log("[endpoint] : " + endpoint);
          console.log("-----------------------------------------");
          console.log("[region] : " + region);
          console.log("-----------------------------------------");
          console.log("[accessKeyId] : " + accessKeyId);
          console.log("-----------------------------------------");
          console.log("[secretAccessKey] : " + secretAccessKey);
          console.log("-----------------------------------------");
          console.log("[streamName] : " + streamName);
          console.log("-----------------------------------------");
          console.log("[hlsStreamType] : " + hlsStreamType);
          console.log("=========================================");
          console.log("");

          try {

            // -----------------------------------------
            // [엔드포인트를 기반으로 HLS 세션 URL 가져오기]
            // -----------------------------------------
            const kinesisVideoArchivedMedia = new AWS.KinesisVideoArchivedMedia({
              region: region,
              endpoint: endpoint,
              accessKeyId: accessKeyId,
              secretAccessKey: secretAccessKey
            });

            //const startTimestamp = new Date(Date.now() - 30 * 1000); // 과거 : 30초전

            //const startTimestamp = new Date(Date.now() - 60 * 1000); // 과거 : 1분전
            //const startTimestamp = new Date(Date.now() - 3 * 60 * 60 * 1000); // 과거: 3시간 전

            //const startTimestamp = new Date(Date.now() - 24 * 60 * 60 * 1000); // 과거: 24시간 전

            //const endTimestamp = new Date(); // 현재
            //const endTimestamp = new Date(Date.now() + 60 * 1000); // 미래 : 1분후

            var params = null;

            if (hlsStreamType.indexOf("LIVE_REPLAY") >= 0){ // ✅ LIVE_REPLAY
              console.log("[getLiveHLSUrl] : LIVE_REPLAY");

              var startTimestamp = null;

              if (hlsStreamType.indexOf("-24") >= 0){ // LIVE_REPLAY (-24)
                startTimestamp = new Date(Date.now() - 24 * 60 * 60 * 1000); // 과거: 24시간 전
              }
              else { // LIVE_REPLAY (Default)
                startTimestamp = new Date(Date.now() - 30 * 1000); // 과거 : 30초전
              }

              params = {
                StreamName: streamName,
                PlaybackMode: 'LIVE_REPLAY', // 이전 데이터 부터 재생을 하므로 끊김 최소화 : AWS 콘솔 대시보드도 동일 설정
                HLSFragmentSelector: {
                  FragmentSelectorType: 'SERVER_TIMESTAMP', // SERVER_TIMESTAMP 또는 PRODUCER_TIMESTAMP
                  TimestampRange: {
                    StartTimestamp: startTimestamp,
                    //EndTimestamp: endTimestamp // 디바이스가 올린 영상 종료까지 출력 위해 주석 : 주석하지 않으면 시작 ~ 종료 범위까지의 영상만 재생 됨
                  }
                }
              }

            }
            else { // ✅ LIVE
              console.log("[getLiveHLSUrl] : LIVE");

              params = {
                StreamName: streamName,
                PlaybackMode: 'LIVE', // 가장 최신만 재생 (지연 최소, 끊김 위험 높음)
                HLSFragmentSelector: {
                  FragmentSelectorType: 'SERVER_TIMESTAMP', // SERVER_TIMESTAMP 또는 PRODUCER_TIMESTAMP
                }
              }

            }

            kinesisVideoArchivedMedia.getHLSStreamingSessionURL( params , function(err, response){
              if (err){
                console.error("");
                console.error("=========================================");
                console.error("[getLiveHLSUrl] : [getHLSStreamingSessionURL] : ❌ 예외 상황 발생");
                console.error("-----------------------------------------");
                console.error("[ERROR] : ", err.message);
                console.error("=========================================");
                console.error("");

                // [1번 더 재시도 수행]
                if (retryCnt < 6){

                  retryCnt ++; // 카운트 증가
                
                  console.error("[getLiveHLSUrl] : [Error] : 타이머 대기 및 재시도 수행 : ", retryCnt);                          

                  // ✅ 5초 (5000ms) 대기 후 함수 실행
                  setTimeout(() => {
                    getLiveHLSUrl(region, endpoint, accessKeyId, secretAccessKey, streamName, hlsStreamType);
                  }, 5000);

                }
                else {
                  
                  // -----------------------------------------
                  // [에러 알림]
                  // -----------------------------------------
                  var errMsg = err.message;

                  if (errMsg.indexOf("No fragments found in the stream for the streaming request") >= 0){
                    errMsg += "\n\n" + "현재 재생 되고 있는 스트리밍이 없습니다. 스트리밍 재생 여부를 다시 확인해주세요." + "\n";
                  }

                  console.error("[getLiveHLSUrl] : [Error] : ", errMsg);

                  return;

                }

              }

              console.log("");
              console.log("=========================================");
              console.log("[getLiveHLSUrl] : [getHLSStreamingSessionURL] : response 확인");
              console.log("-----------------------------------------");
              console.log(JSON.stringify(response));
              console.log("=========================================");
              console.log("");

              const hlsUrl = response.HLSStreamingSessionURL;

              if (hlsUrl != null && hlsUrl != '' && hlsUrl != 'undefined'){
                Hls.DefaultConfig.debug = true; // 디버깅 로그 출력

                if (Hls.isSupported()){ // Hls 지원 여부 확인

                  const hls = new Hls(); // Default

                  /*
                  const hls = new Hls({ // Custom
                    liveSyncDuration: 1, // 재생지점 - LIVE edge 거리 (초 단위)
                    liveMaxLatencyDuration: 2,
                    //maxLiveSyncPlaybackRate: 1.5,
                    //enableWorker: true,
                    lowLatencyMode: true, // 중요 : Live HLS 지연 없이 재생  (PlaybackMode: 'LIVE')
                    //backBufferLength: 15,
                    //maxBufferLength: 5, // 초 단위 : 최대 버퍼 길이
                    //maxBufferSize: 10* 1000 * 1000, // 10MB // 바이트 단위 : 메모리 제한
                    maxBufferHole: 0.1,
                  });
                  // */


                  hls.loadSource(hlsUrl);
                  hls.attachMedia(remoteView);


                  // [비디오 AutoPlay 설정으로 주석 처리]
                  /*
                  hls.on(Hls.Events.MANIFEST_PARSED, function(){

                    remoteView.play();

                    console.log("[getLiveHLSUrl] : [getHLSStreamingSessionURL] : Hls 스트리밍 재생 수행");
                      
                  });
                  // */


                  hls.on(Hls.Events.ERROR, function(event, data){
                    console.error("");
                    console.error("=========================================");
                    console.error("[getLiveHLSUrl] : [getHLSStreamingSessionURL] : ❌ Hls.Events.ERROR");
                    console.error("-----------------------------------------");
                    console.error("[ERROR] : ", JSON.stringify(data));
                    console.error("-----------------------------------------");
                    console.error("[data.type] : ", data.type);
                    console.error("-----------------------------------------");
                    console.error("[data.details] : ", data.details);
                    console.error("-----------------------------------------");
                    console.error("[data.fatal] : ", data.fatal);
                    console.error("=========================================");
                    console.error("");

                    // [에러 분기 처리 시 사용]
                    // if (data.details === 'bufferStalledError'){ }
                    // if (data.details === 'bufferStalledError'){ }

                  });

                }
                else {
                  console.error("[getLiveHLSUrl] : [Error] : ❌ Hls.isSupported False");
                }

              }
              else {
                console.error("[getLiveHLSUrl] : [Error] : ❌ HLSStreamingSessionURL Is Null");
              }

            })

          }
          catch (exception) {
            console.error("[getLiveHLSUrl] : [Exception] : ❌ 예외 상황 발생 : ", exception.message);
          }

        };

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

    </script>


</head>


<body>


<!-- 🟦 [컨테이너 생성] -->
<div id="container_remoteView">
  <video autoplay="true" id="remoteView"></video>
</div>


</body>

</html>

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





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

▶️ [Web/JavaScript] AWS Kvs HLS 뷰어 Viewer 비디오 스트림 영상 재생 수행

https://kkh0977.tistory.com/8269

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


▶️ [Aws Kinesis Video Streams] Aws KVS 비디오 스트림 , 신호 전송 채널 차이점 설명 정리

https://kkh0977.tistory.com/7956

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


▶️ [Aws Kvs HSL 비디오 스트림 녹화 영상 관련 학습 정리]

https://kkh0977.tistory.com/7967

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


▶️ [자바스크립트 AWS Kvs HLS 비디오 스트림 채널 생성 수행 - createStream]

https://kkh0977.tistory.com/8276

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


▶️ [자바스크립트 AWS Kvs HLS 비디오 스트림 채널 삭제 수행 - deleteStream]

https://kkh0977.tistory.com/8098

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


▶️ [JavaScript] The FragmentSelector is required for LIVE_REPLAY PlaybackMode

https://kkh0977.tistory.com/8256

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


▶️ [JavaScript] No fragments found in the stream for the streaming request

https://kkh0977.tistory.com/8257

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

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