HTML5 API - Video - 2


Canvas 객체의 기능 중 앞서 배운 Video 객체와 연동 가능하며, 응용범위 또한 큰 기능에 대해 설명 드리도록 하겠습니다.


객체 맴버인 drawImage() 메서드를 활용한 방법이며, Image 객체 대신 Video 객체를 인자 값으로 전달하여 해당 프레임의 이미지를 캔버스에 노출시킬 수 있는 기능입니다.


미디어클립의 이미지 프레임을 캡쳐하는 코드는 아래와 같습니다.


- this.player(Video 객체)


  1. ctx.drawImage(this.player, 0, 0, this.width, this.height, 0, 0, this.width, this.height);


또한, 아래 코드는 이전 포스트의 Video API를 응용하여, 원본(미디어 클립), 캔버스(재생 중인 이미지를 노출시킬 캔버스), 복사본(저장된 이미지 배열을 노출시킬 캔버스)을 생성하여 재생 중인 원본(미디어클립) 이미지 프레임을 캔버스에 노출시키고, 저장된 이미지 프레임을 배열로 만들어 Canvas 객체의 getImageData(), putImageData() 를 활용하여 복사본 캔버스에 다시 한번 노출시킵니다.(녹화 기능)




- 왼쪽(미디어클립), 중앙(재생 캔버스), 오른쪽(녹화 캔버스)



전체 소스는 아래와 같습니다.


  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4.     <title>Video</title>
  5.     <style type="text/css">
  6.        
  7.     * html
  8.     {
  9.         margin:0;
  10.         padding:0;
  11.     }
  12.    
  13.    
  14.     body
  15.     {
  16.         width:100%;
  17.         height:100%;
  18.     }
  19.     </style>
  20. </head>
  21. <body>
  22. <script type="text/javascript">
  23. //<![CDATA[
  24.  
  25.     var Video = (function (win, doc) {
  26.  
  27.         return function (opt) {
  28.  
  29.             function init() {
  30.  
  31.                 this.options = {
  32.                     player: null,
  33.  
  34.                     wrap: null,
  35.  
  36.                     id: '',
  37.                     src: '',
  38.                     poster: '',
  39.                     width: 500,
  40.                     height: 344,
  41.                     autoplay: false,
  42.                     controls: false,
  43.                     loop: false,
  44.                     isloaded: false,
  45.                     onload: function () { ; },
  46.                     onplay: function () { ; },
  47.                     onprogress: function () { ; },
  48.                     ontimeupdate: function () { ; },
  49.                     onended: function () { ; },
  50.                     onerror: function () { ; }
  51.  
  52.                 };
  53.  
  54.                 extend.call(this.options, opt);
  55.  
  56.                 // audio load
  57.                 load.call(this.options);
  58.  
  59.                 return this;
  60.             }
  61.  
  62.             Video.fn = init.prototype = {
  63.                 play: play,
  64.                 stop: stop,
  65.                 isEnded: isEnded,
  66.                 getTimeout: getTimeout,
  67.                 getStartTime: getStartTime,
  68.                 getSrc: getSrc,
  69.                 isBuffering: isBuffering,
  70.                 getCurrentTimeout: getCurrentTimeout,
  71.                 setVolume: setVolume,
  72.                 setMuted: setMuted,
  73.                 getVideoSize: getVideoSize
  74.  
  75.             }
  76.  
  77.             return new init();
  78.         };
  79.  
  80.         // audio 객체를 생성한다.
  81.         function createElement() {
  82.  
  83.             this.player = document.createElement('video');
  84.             this.player.id = this.id;
  85.             this.player.src = this.src;
  86.  
  87.             // 미디어 클립이 로드되는 동안 컨텐츠 대신 보여질 이미지 파일의 URL을 지정한다.
  88.             this.player.poster = this.poster;
  89.             // 미디어 클립의 가로폭
  90.             this.player.width = this.width;
  91.             // 미디어 클립의 세로폭
  92.             this.player.height = this.height;
  93.  
  94.             this.player.autoplay = this.autoplay;
  95.             this.player.controls = this.controls;
  96.             this.player.loop = this.loop;
  97.  
  98.             var that = this;
  99.  
  100.             // 미디어클립을 재생할 준비가 완료 시 이벤트 핸들러
  101.             bind(this.player, 'canplay', function () {
  102.  
  103.                 that.onload.call(null);
  104.                 this.isloaded = true;
  105.             });
  106.  
  107.             // 미디어클립 재생 클릭 시 이벤트 핸들러
  108.             bind(this.player, 'play', function () { that.onplay(); });
  109.             // 브라우저가 미디어클립 데이터를 가져오는 프로세스에 있을 시 이벤트 핸들러
  110.             bind(this.player, 'progress', function () { that.onprogress(); });
  111.             // 미디어 클립 재생 시 지속적으로 발생(재생 위치가 변경되었을때)
  112.             bind(this.player, 'timeupdate', function () { that.ontimeupdate(); });
  113.             // 미디어 클립 종료 시 이벤트 핸들러
  114.             bind(this.player, 'ended', function () { that.onended(); });
  115.             // 미디어 클립 로드 오류 시 이벤트 핸들러
  116.             bind(this.player, 'error', function () { that.onerror(); });
  117.  
  118.             return this.player;
  119.         };
  120.  
  121.         // audio 객체를 로드합니다.
  122.         function load() {
  123.  
  124.             var elem = createElement.call(this);
  125.  
  126.             if (elem.load) (this.wrap || document.body).appendChild(elem);
  127.             else alert('Video 객체를 지원하지 않는 브라우저 입니다.');
  128.         };
  129.  
  130.         // 미디어클립을 재생합니다.
  131.         function play() {
  132.  
  133.             this.options.player.play();
  134.  
  135.             return this;
  136.         };
  137.  
  138.         // 미디어클립 재생을 중지합니다.
  139.         function stop() {
  140.  
  141.             !this.options.player.paused && this.options.player.pause();
  142.             return this;
  143.         }
  144.  
  145.         // 미디어클립 재생 여부를 반환
  146.         function isEnded() {
  147.             return this.options.player.ended;
  148.         };
  149.  
  150.  
  151.         // 미디어클립의 총 재생시간을 반환
  152.         function getTimeout(type) {
  153.  
  154.             type = type || 'hours';
  155.  
  156.             return {
  157.                 'second': this.options.player.startTime,
  158.                 'hours': getDateSecondToHours(this.options.player.duration)
  159.             }[type];
  160.         };
  161.  
  162.         // 미디어클립의 현재 재생시간을 반환
  163.         function getCurrentTimeout(type) {
  164.  
  165.             type = type || 'hours';
  166.  
  167.             return {
  168.                 'second': this.options.player.currentTime,
  169.                 'hours': getDateSecondToHours(this.options.player.currentTime)
  170.             }[type];
  171.         };
  172.  
  173.         // 미디어 클립의 현재 재생 시간을 반환
  174.         // 0.0(min) ~ 1(max) 할당한다.
  175.         function setVolume(num) {
  176.  
  177.             num = num || 0;
  178.  
  179.             this.options.player.volume = num / 10;
  180.  
  181.             return this;
  182.         };
  183.  
  184.         // 음소거 여부를 설정한다.
  185.         function setMuted(b) {
  186.  
  187.             b = b || false;
  188.  
  189.             this.options.player.muted = b;
  190.  
  191.             return this;
  192.         };
  193.  
  194.         // 미디어 클립이 재생을 시작할 수 있는 가능한 가장 빠른 시간을 반환합니다.
  195.         // 미디어 클립이 스트리밍되거나, 버퍼링되지 않으면 0을 반환합니다.
  196.         function getStartTime(type) {
  197.  
  198.             return this.options.player.startTime;
  199.         };
  200.  
  201.         // 현재 버퍼링 상태 여부를 반환
  202.         function isBuffering() {
  203.             return this.options.player.startTime !== 0;
  204.         };
  205.  
  206.         // 현재 재생중인 파일명을 반환합니다.
  207.         function getSrc() {
  208.             return this.options.player.currentSrc;
  209.         };
  210.  
  211.         // 미디어클립의 사이즈를 반환한다.
  212.         function getVideoSize() {
  213.             return {
  214.                 w: this.options.player.videoWidth,
  215.                 h: this.options.player.videoHeight
  216.             }
  217.         }
  218.  
  219.  
  220.         function getDateSecondToHours(i) {
  221.  
  222.             i = i || 0;
  223.  
  224.             var h = parseInt(((i / 3600) % 24), 10)
  225.               , m = parseInt(((i / 60) % 60), 10)
  226.               , s = parseInt(((i / 1) % 60), 10);
  227.  
  228.             h = h < 10 ? '0' + h : h;
  229.             m = m < 10 ? '0' + m : m;
  230.             s = s < 10 ? '0' + s : s;
  231.  
  232.             return h + ':' + m + ':' + s;
  233.         };
  234.  
  235.         // 객체 상속 함수
  236.         function extend() {
  237.  
  238.             var target = this
  239.           , opts = []
  240.           , src = null
  241.           , copy = null;
  242.  
  243.             for (var i = 0, length = arguments.length; i < length; i++) {
  244.  
  245.                 opts = arguments[i];
  246.  
  247.                 for (var n in opts) {
  248.                     src = target[n];
  249.                     copy = opts[n];
  250.  
  251.                     if (src === copy) continue;
  252.                     if (copy) target[n] = copy;
  253.                 }
  254.             }
  255.         };
  256.  
  257.  
  258.     })(window, document);
  259.  
  260.  
  261.     bind(window, 'load', function () {
  262.  
  263.         p1 = Video({
  264.             id: 'video_test1',
  265.             src: 'http://m.fpscamp.com/test.mp4',
  266.             wrap: document.getElementById('container'),
  267.             poster: 'http://static.naver.net/www/u/2010/0611/nmms_215646753.gif',
  268.             autoplay: true,
  269.             controls: true,
  270.             loop: false,
  271.             onload: function () {
  272.             },
  273.             onplay: function () {
  274.                
  275.                 // 캔버스
  276.                 var canvas = createCanvasElement.call(this, 'canvas_test');
  277.                 // 복사본 캔버스
  278.                 var canvas_copy = createCanvasElement.call(this, 'canvas_copy');
  279.  
  280.                 var that = this;
  281.                 t = window.setInterval(function () {
  282.                     if (!that.player.paused) {
  283.                         updateFrame.call(that, canvas);
  284.                     }
  285.                     else {
  286.                         window.clearInterval(t);
  287.                     }
  288.                 }, 60);
  289.             },
  290.             onprogress: function () {
  291.             },
  292.             ontimeupdate: function () {
  293.             },
  294.             onended: function () {
  295.  
  296.                 window.clearInterval(t);
  297.                 var frameLength = 0;
  298.  
  299.                 (function () {
  300.                     // 원본 재생이 끝내면, 배열에 저장된 이미지를 복사본 캔버스에 재생시킵니다.
  301.                     frameTimerId = window.setInterval(function () {
  302.                         if (frameLength < frames.length) {
  303.                             document.getElementById('canvas_copy').getContext('2d').putImageData(frames[frameLength], 0, 0);
  304.                             frameLength++;
  305.                         }
  306.                         else {
  307.                             window.clearInterval(frameTimerId);
  308.                         }
  309.                     }, 60);
  310.                 })();
  311.             },
  312.             onerror: function () {
  313.                 alert(this === window);
  314.             }
  315.         });
  316.     });
  317.  
  318.     var canvas = null
  319.       , ctx = null;      
  320.     function createCanvasElement(id) {
  321.  
  322.         id = id || '';
  323.  
  324.         var elem = document.getElementById(id);
  325.  
  326.         if (!elem) {
  327.             elem = document.createElement('canvas');
  328.  
  329.             elem.id = id;
  330.             elem.width = this.width;
  331.             elem.height = this.height;
  332.  
  333.             document.getElementById('container').appendChild(elem);
  334.  
  335.             return elem;
  336.         }
  337.         else {
  338.             return elem;
  339.         }
  340.     }
  341.  
  342.     var frames = []
  343.       , frameTimerId = null;
  344.     function updateFrame(canvas) {
  345.  
  346.         ctx = canvas.getContext('2d');
  347.         ctx.drawImage(this.player, 0, 0, this.width, this.height, 0, 0, this.width, this.height);
  348.  
  349.         var pxs = ctx.getImageData(0, 0, this.width, this.height);
  350.  
  351.         frames.push(pxs);
  352.     }
  353.  
  354.     // 이벤트 할당
  355.     function bind(elem, type, handler, capture) {
  356.         type = typeof type === 'string' && type || '';
  357.         handler = handler || function () { ; };
  358.  
  359.         if (elem.addEventListener) {
  360.             elem.addEventListener(type, handler, capture);
  361.         }
  362.         else if (elem.attachEvent) {
  363.             elem.attachEvent('on' + type, handler);
  364.         }
  365.  
  366.         return elem;
  367.     };
  368.  
  369. //]]>
  370. </script>
  371. <input id="" type="button" value="play" onclick="p1.play()" />
  372. <input id="" type="button" value="stop" onclick="p1.stop()" />
  373. <input id="" type="button" value="isEnded" onclick="alert(p1.isEnded())" />
  374. <input id="" type="button" value="getTimeout" onclick="alert(p1.getTimeout())" />
  375. <input id="" type="button" value="getStartTime" onclick="alert(p1.getStartTime())" />
  376. <input id="" type="button" value="getSrc" onclick="alert(p1.getSrc())" />
  377. <input id="" type="button" value="isBuffering" onclick="alert(p1.isBuffering())" />
  378. <input id="" type="button" value="getCurrentTimeout" onclick="alert(p1.getCurrentTimeout())" />
  379. <input id="" type="button" value="setVolume" onclick="p1.setVolume(5)" />
  380. <input id="" type="button" value="setMuted" onclick="p1.setMuted(true)" />
  381. <input id="Button1" type="button" value="getVideoSize(width)" onclick="alert(p1.getVideoSize().w)" />
  382. <input id="Button2" type="button" value="getVideoSize(height)" onclick="alert(p1.getVideoSize().h)" />
  383. <div id="container">
  384. </div>
  385. </body>
  386. </html>