[javascript] 동영상 컨트롤이 계속 작동하도록 Node.js를 사용하여 동영상 파일을 html5 동영상 플레이어로 스트리밍 하시겠습니까?

Tl; Dr-질문 :

비디오 컨트롤이 계속 작동하도록 Node.js를 사용하여 비디오 파일을 html5 비디오 플레이어로 스트리밍하는 올바른 방법은 무엇입니까 ?

나는 생각 이 헤더를 처리하는 방식과 관련이있다. 어쨌든, 여기에 배경 정보가 있습니다. 코드는 약간 길지만 매우 간단합니다.

Node를 사용하여 작은 비디오 파일을 HTML5 비디오로 스트리밍하는 것은 쉽습니다.

작은 비디오 파일을 HTML5 비디오 플레이어로 매우 쉽게 스트리밍하는 방법을 배웠습니다. 이 설정을 사용하면 컨트롤이 내 작업없이 작동하며 비디오는 완벽하게 스트리밍됩니다. Google 문서에서 다운로드 할 수 있는 샘플 비디오와 함께 완전히 작동하는 코드의 작업 사본이 여기에 있습니다 .

고객:

<html>
  <title>Welcome</title>
    <body>
      <video controls>
        <source src="movie.mp4" type="video/mp4"/>
        <source src="movie.webm" type="video/webm"/>
        <source src="movie.ogg" type="video/ogg"/>
        <!-- fallback -->
        Your browser does not support the <code>video</code> element.
    </video>
  </body>
</html>

섬기는 사람:

// Declare Vars & Read Files

var fs = require('fs'),
    http = require('http'),
    url = require('url'),
    path = require('path');
var movie_webm, movie_mp4, movie_ogg;
// ... [snip] ... (Read index page)
fs.readFile(path.resolve(__dirname,"movie.mp4"), function (err, data) {
    if (err) {
        throw err;
    }
    movie_mp4 = data;
});
// ... [snip] ... (Read two other formats for the video)

// Serve & Stream Video

http.createServer(function (req, res) {
    // ... [snip] ... (Serve client files)
    var total;
    if (reqResource == "/movie.mp4") {
        total = movie_mp4.length;
    }
    // ... [snip] ... handle two other formats for the video
    var range = req.headers.range;
    var positions = range.replace(/bytes=/, "").split("-");
    var start = parseInt(positions[0], 10);
    var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
    var chunksize = (end - start) + 1;
    if (reqResource == "/movie.mp4") {
        res.writeHead(206, {
            "Content-Range": "bytes " + start + "-" + end + "/" + total,
                "Accept-Ranges": "bytes",
                "Content-Length": chunksize,
                "Content-Type": "video/mp4"
        });
        res.end(movie_mp4.slice(start, end + 1), "binary");
    }
    // ... [snip] ... handle two other formats for the video
}).listen(8888);

그러나이 방법은 크기가 1GB 미만인 파일로 제한됩니다.

스트리밍 (모든 크기) 비디오 파일 fs.createReadStream

를 사용 fs.createReadStream()하면 서버는 한 번에 모든 파일을 메모리로 읽는 대신 스트림에서 파일을 읽을 수 있습니다. 이것은 작업을 수행하는 올바른 방법처럼 들리며 구문은 매우 간단합니다.

서버 스 니펫 :

movieStream = fs.createReadStream(pathToFile);
movieStream.on('open', function () {
    res.writeHead(206, {
        "Content-Range": "bytes " + start + "-" + end + "/" + total,
            "Accept-Ranges": "bytes",
            "Content-Length": chunksize,
            "Content-Type": "video/mp4"
    });
    // This just pipes the read stream to the response object (which goes 
    //to the client)
    movieStream.pipe(res);
});

movieStream.on('error', function (err) {
    res.end(err);
});

이것은 비디오를 잘 스트리밍합니다! 그러나 비디오 컨트롤은 더 이상 작동하지 않습니다.



답변

Accept Ranges헤더 (의 비트 writeHead()) 작업에 HTML5 비디오 컨트롤이 필요합니다.

맹목적으로 전체 파일을 보내는 대신 먼저 Accept RangesREQUEST 에서 헤더를 확인한 다음 해당 비트를 읽고 보내야한다고 생각합니다. fs.createReadStream지원 startend 옵션.

그래서 나는 예제를 시도했고 작동합니다. 코드는 예쁘지는 않지만 이해하기 쉽습니다. 먼저 시작 / 종료 위치를 얻기 위해 범위 헤더를 처리합니다. 그런 다음 fs.stat전체 파일을 메모리로 읽지 않고 파일 크기를 가져 오는 데 사용 합니다. 마지막으로를 사용 fs.createReadStream하여 요청 된 부분을 클라이언트에 보냅니다.

var fs = require("fs"),
    http = require("http"),
    url = require("url"),
    path = require("path");

http.createServer(function (req, res) {
  if (req.url != "/movie.mp4") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end('<video src="http://localhost:8888/movie.mp4" controls></video>');
  } else {
    var file = path.resolve(__dirname,"movie.mp4");
    fs.stat(file, function(err, stats) {
      if (err) {
        if (err.code === 'ENOENT') {
          // 404 Error if file not found
          return res.sendStatus(404);
        }
      res.end(err);
      }
      var range = req.headers.range;
      if (!range) {
       // 416 Wrong range
       return res.sendStatus(416);
      }
      var positions = range.replace(/bytes=/, "").split("-");
      var start = parseInt(positions[0], 10);
      var total = stats.size;
      var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
      var chunksize = (end - start) + 1;

      res.writeHead(206, {
        "Content-Range": "bytes " + start + "-" + end + "/" + total,
        "Accept-Ranges": "bytes",
        "Content-Length": chunksize,
        "Content-Type": "video/mp4"
      });

      var stream = fs.createReadStream(file, { start: start, end: end })
        .on("open", function() {
          stream.pipe(res);
        }).on("error", function(err) {
          res.end(err);
        });
    });
  }
}).listen(8888);


답변

이 질문에 대한 대답 은 훌륭하며 받아 들여진 대답으로 남아 있어야합니다. 그러나 읽기 스트림이 항상 종료 / 닫히지 않는 코드에 문제가 발생했습니다. 해결책의 일부는 두 번째 인수 autoClose: true와 함께 보내는 것이 었습니다 .start:start, end:endcreateReadStream

해결책의 다른 부분은 chunksize응답에서 전송되는 최대 값을 제한하는 것이 었 습니다. 다른 대답은 다음 end과 같습니다.

var end = positions[1] ? parseInt(positions[1], 10) : total - 1;

… 이는 요청 된 시작 위치에서 마지막 바이트까지 파일의 나머지 부분을 전송하는 효과가 있습니다. 그러나 클라이언트 브라우저에는 해당 스트림의 일부만 읽을 수있는 옵션이 있으며 아직 모든 바이트가 필요하지 않은 경우이를 수행합니다. 이로 인해 브라우저가 더 많은 데이터를 가져올 시간 (예 : 탐색 / 스크럽과 같은 사용자 작업 또는 스트림 재생)을 결정할 때까지 스트림 읽기가 차단됩니다.

<video>사용자가 비디오 파일을 삭제할 수있는 페이지에 요소를 표시했기 때문에이 스트림을 닫아야했습니다 . 그러나 파일은 클라이언트 (또는 서버)가 연결을 닫을 때까지 파일 시스템에서 제거되지 않았습니다. 이것이 스트림이 종료 / 닫히는 유일한 방법이기 때문입니다.

내 솔루션은 maxChunk구성 변수를 설정하고 1MB로 설정하고 한 번에 1MB 이상의 읽기 스트림을 응답에 파이프하지 않는 것입니다.

// same code as accepted answer
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
var chunksize = (end - start) + 1;

// poor hack to send smaller chunks to the browser
var maxChunk = 1024 * 1024; // 1MB at a time
if (chunksize > maxChunk) {
  end = start + maxChunk - 1;
  chunksize = (end - start) + 1;
}

이는 각 요청 후 읽기 스트림이 종료 / 닫히고 브라우저에 의해 유지되지 않도록하는 효과가 있습니다.

또한 이 문제를 다루는 별도의 StackOverflow 질문답변 을 작성했습니다.


답변

먼저 app.js게시하려는 디렉토리에 파일을 만듭니다 .

var http = require('http');
var fs = require('fs');
var mime = require('mime');
http.createServer(function(req,res){
    if (req.url != '/app.js') {
    var url = __dirname + req.url;
        fs.stat(url,function(err,stat){
            if (err) {
            res.writeHead(404,{'Content-Type':'text/html'});
            res.end('Your requested URI('+req.url+') wasn\'t found on our server');
            } else {
            var type = mime.getType(url);
            var fileSize = stat.size;
            var range = req.headers.range;
                if (range) {
                    var parts = range.replace(/bytes=/, "").split("-");
                var start = parseInt(parts[0], 10);
                    var end = parts[1] ? parseInt(parts[1], 10) : fileSize-1;
                    var chunksize = (end-start)+1;
                    var file = fs.createReadStream(url, {start, end});
                    var head = {
                'Content-Range': `bytes ${start}-${end}/${fileSize}`,
                'Accept-Ranges': 'bytes',
                'Content-Length': chunksize,
                'Content-Type': type
                }
                    res.writeHead(206, head);
                    file.pipe(res);
                    } else {
                    var head = {
                'Content-Length': fileSize,
                'Content-Type': type
                    }
                res.writeHead(200, head);
                fs.createReadStream(url).pipe(res);
                    }
            }
        });
    } else {
    res.writeHead(403,{'Content-Type':'text/html'});
    res.end('Sorry, access to that file is Forbidden');
    }
}).listen(8080);

간단히 실행하면 node app.js서버가 포트 8080에서 실행됩니다. 비디오 외에도 모든 종류의 파일을 스트리밍 할 수 있습니다.


답변