Rev. 2.73

이전 포스트에서 ArrayBuffer에 대해 알아보았습니다. 일반적으로 컴퓨팅에서 말하는 블랍(BLOB)은 대체로 커다란 파일을 일컫는 말이며, 사운드, 비디오와 같은 멀티미디어 데이터를 객체로 다루기 위해 주로 사용되는 것으로 알려졌습니다. File API가 생겨나면서 동시에 등장한 BLOB은 자바스크립트에서 조금 다른 의미로 해석되는데 지금은 파일을 다루기 위한 메모리 참조 수단 정도입니다.

원래 BLOB 객체는 스트림을 이용해서 읽기/쓰기를 하는 것이 정설이지만 자바스크립트에서는 바이너리로 수신한 청크(Chunk)들을 한방에 생성하여 File로 인식하는 용도로 자주 사용되며, FileReader API와 함께 사용하여 자바스크립트로 버퍼링을 제어하는 수준의 미디어 스트리밍을 구현할 수 있게 합니다. 또한, 이를 DOM으로 연결하기 위한 URL.createObjectURL이 존재합니다. 다음 예제는 AJAX에서 BLOB 데이터 형식으로 수신한 이미지를 DOM에 참조시키고 이미지가 출력된 후 URL.revokeObjectURL을 이용하여 BLOB을 해제하는 것입니다.

var req = new XMLHttpReqest();
xhr.open("GET", "download?name=" + name, true);
xhr.responseType = "blob";
xhr.onreadystatechange = function () {
  if (xhr.readyState == xhr.DONE) {
    var blob = xhr.reponse;
    var image = document.getElementById("my-image");
    image.addEventListener("load", function (evt) {
      URL.revokeObjectURL(evt.target.src);
    }
    image.src = URL.createObjectURL(blob);
  }
}
xhr.send();

Node.js의 BinaryJS모듈을 이용하여 스트림으로 수신한 Chunk를 배열로 취합하고 스트림이 끝나는 시점에 Blob으로 변환하여 DOM으로 참조시키는 예제:

// Connect to Binary.js server
var client = new BinaryClient('ws://localhost:9000');
// Received new stream from server!
client.on('stream', function(stream, meta){    
  // Buffer for parts
  var parts = [];
  // Got new data
  stream.on('data', function(arrayBuffer){
    parts.push(arrayBuffer);
  });
  stream.on('end', function(){
    // Display new data in browser!
    var img = document.createElement("img");
    img.src = URL.createObjectURL(new Blob(parts));
    document.body.appendChild(img);
  });
});

다음 예제는 MediaSource API와 연계하여 작동하는 것을 가정한 코드로서 현재 구글 크롬 브라우저에서 구현 중인 명세이고 WebM 포맷(vorbis, vp8 인코딩)으로 만들어진 미디어만 정상적으로 작동(웹 소켓 바이너리 통신)하는 것을 직접 확인한 것입니다. 처음으로 수신한 Chunk의 Blob 객체를 생성하고 FileReader API에서 뒤이어 수신하는 Chunk를 추가(Append)하여 미디어 스트리밍을 구현한 예제입니다.

var xhr = new XMLHttpRequest();
xhr.open('GET', 'test.webm', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
  if (xhr.status != 200) {
    alert("Unexpected status code " + xhr.status + " for " + url);
    return false;
  }
  callback(new Uint8Array(xhr.response));
};
xhr.send();

function callback(uInt8Array) {
  var file = new Blob([uInt8Array], {type: 'video/webm'});
  var chunkSize = Math.ceil(file.size / NUM_CHUNKS);
  console.log('num chunks:' + NUM_CHUNKS);
  console.log('chunkSize:' + chunkSize + ', totalSize:' + file.size);
  // Slice the video into NUM_CHUNKS and append each to the media element.
  var i = 0;
  (function readChunk_(i) {
    var reader = new FileReader();
    // Reads aren't guaranteed to finish in the same order they're started in,
    // so we need to read + append the next chunk after the previous reader
    // is done (onload is fired).
    reader.onload = function(e) {
      sourceBuffer.append(new Uint8Array(e.target.result));
      console.log('appending chunk:' + i);
      if (i == NUM_CHUNKS - 1) {
        mediaSource.endOfStream();
      } else {
        if (video.paused) {
          video.play(); // Start playing after 1st chunk is appended.
        }
        readChunk_(++i);
      }
    };
    var startByte = chunkSize * i;
    var chunk = file.slice(startByte, startByte + chunkSize);
    reader.readAsArrayBuffer(chunk);
  })(i); // Start the recursive call by self calling.
}

여기에 서버에서 재생시간 대비 클라이언트의 수신 속도를 측정하여 패킷을 절약하는 작업만 추가로 해주면 레알 미디어 스트리밍이 구현되는 것입니다.

Comments

Got something to add? You can just leave a comment.

Your Reaction Time!

captcha

avatar