Node.js發送視頻流

Node.js中的流

Node.js的流(Stream) API 非常強大,它是處理流數據的抽象接口。流可以看成是一種數據的集合,但它並不是一下子全部讀到內存裏面,而是一塊一塊地去產生、消耗,這種方式最顯而易見的好處是可以方便地處理大文件。數據流可以是可讀流、可寫流,實際上Node.js中的流分爲4種類型 : Readable、Writable、Duplex、Transform。

  • Readable Stream:可讀流是對可消費的數據源進行的抽象,比如fs.createReadStream
  • Writable Stream:可寫流是對流的目的地(destination)的抽象,destination運允許數據寫入,比如fs.createWriteStream
  • Duplex Stream:雙工流是同時實現了 Readable 和 Writable 接口的流,既能寫又能讀。比如TCP socket
  • Transform Stream:交換流本質上是一種Duplex流,可以將其看成輸入Writable流,輸出的是Readable流,也可以稱之爲“通過流”(through streams)。比如zlib streams。

Node中有許多內置對象實現了Stream接口:
Node實現Stream的內置對象

對於TCP sockets、zlib 和 crypto 流而言,他們是Duplex Stream。

.pipe()方法

對於Readable流而言,有兩種消費數據流的方式:Paused Mode 和 Flowing Mode。簡單來說,Paused Mode就像是把水缸裏面的水一瓢瓢舀出來,可以根據需要使用read()方法去消費數據流;Flowing Mode就像是給水缸接了根管子,水可以從高處流到低處,我們可以監聽data事件得到一塊數據流。

所有的Readable流默認是Paused Mode,使用resume()、pause()方法可以與 Flowing Mode 相互切換,resume()方法就像是給水缸接好管子,水自動流動;pause()方法就像移除管子,我們得手動去舀水。這種切換方式很簡單,是有時候是自動發生的。
Paused Mode 與 Flowing Mode

當Readable流使用pipe()方法時,就相當於給數據流接上了管子,數據流會自動從上游流到下游。在使用pipe()時,需要注意的是上游是Readable,下游是Writable,即:

readableSrc.pipe(writableDest)

由於Duplex流 實現了Readable、和Writable,可以將Duplex或Transform 放在“中游”:

readableSrc
  .pipe(transformStream1)
  .pipe(transformStream2)
  .pipe(finalWrtitableDest)

我們知道,Node中的Server端HTTP response是Writable流,而通過fs.createReadStream讀取視頻數據得到的是Readable流。因此,Node的Server端可以直接使用pipe()方法將視頻流發到前端:

//前端
<video src="/video"></video>

//服務端
router.get('/video', function(req, res, next) {

    let head = { 'Content-Type': 'video/mp4' };
    //需要設置HTTP HEAD
    res.writeHead(200, head);
    //使用pipe
    fs.createReadStream('./assets/sintel.mp4')
        .pipe(res);

});

前端結果:
這裏寫圖片描述

可以注意到前端下載了一個html和video。

HTTP 206

HTTP/1.1 206狀態碼錶示的是:客戶端通過發送範圍請求頭Range獲取資源的部分數據。這種請求可以將服務端文件分割成多個部分傳給客戶端,可用於解決網絡問題以及大文件下載問題。對於一個很大的視頻,就可以採用這種請求將視頻流分成多個部分下載。
需要關注的HTTP Headers有:

  • Range:用於請求頭中,指定第一個字節的位置和最後一個字節的位置,一般格式:Range:(unit=first byte pos)-[last byte pos] 。如 Range:bytes=0- 表示請求服務端第0及以後bytes的數據; Range:bytes=0-999 表示0到999 bytes的數據,注意這個區間的長度是1000bytes。
  • Accept-Ranges:用於響應頭,表明服務器支持Range請求,以及服務器所支持的單位是字節(這也是唯一可用的單位);Accept-Ranges: none 響應頭表示服務器不支持範圍請求。
  • Content-Range:用於響應頭,指定整個實體中的一部分的插入位置,一般格式: Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]。如Content-Range:bytes 1000000-1999999/3332038 表示的是服務端資源總大小3332038 bytes,此次返回的是其中第1000000到1999999 bytes 的數據。
  • Content-Length:用於響應頭,表明了響應實體的大小,它應該等於Content-Range中的 last byte pos - first byte pos + 1。

將視頻流分成多個部分發給前端,只要注意控制好流的數據區間即可,服務端代碼如下:

router.get('/video', function(req, res, next) {
    let path = './assets/sintel.mp4';
    let stat = fs.statSync(path);
    let fileSize = stat.size;
    let range = req.headers.range;

    // fileSize 3332038

    if (range) {
        //有range頭才使用206狀態碼

        let parts = range.replace(/bytes=/, "").split("-");
        let start = parseInt(parts[0], 10);
        let end = parts[1] ? parseInt(parts[1], 10) : start + 999999;

        // end 在最後取值爲 fileSize - 1 
        end = end > fileSize - 1 ? fileSize - 1 : end;

        let chunksize = (end - start) + 1;
        let file = fs.createReadStream(path, { start, end });
        let head = {
            'Content-Range': `bytes ${start}-${end}/${fileSize}`,
            'Accept-Ranges': 'bytes',
            'Content-Length': chunksize,
            'Content-Type': 'video/mp4',
        };
        res.writeHead(206, head);
        file.pipe(res);
    } else {
        let head = {
            'Content-Length': fileSize,
            'Content-Type': 'video/mp4',
        };
        res.writeHead(200, head);
        fs.createReadStream(path).pipe(res);
    }

});

前端結果:
這裏寫圖片描述

這次的視頻就被分成了4個部分,每個video請求及結果如下:

順序 Request Response
1 Range:bytes=0- Content-Range:bytes 0-999999/3332038 Content-Length:1000000
2 Range:bytes=1000000- Content-Range:bytes 1000000-1999999/3332038 Content-Length:1000000
3 Range:bytes=2000000- Content-Range:bytes 2000000-2999999/3332038 Content-Length:1000000
4 Range:bytes=3000000- Content-Range:bytes 3000000-3332037/3332038 Content-Length:332038

代碼地址:https://git.oschina.net/liuyaqi/node-video-stream_demo.git

參考鏈接:
菜鳥教程: Node.js Stream(流)
Node中文文檔 Stream
Node.js Streams: Everything you need to know
RFC 2616: HTTP狀態碼定義
http斷點續傳原理:http頭 Range、Content-Range

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章