1. 程式人生 > >深入理解xhr的responseType中blob和arrayBuffer

深入理解xhr的responseType中blob和arrayBuffer

最近有個需求,伺服器端下載視訊,儲存到本地,然後再播放,下載儲存後播放不了。debug後發現是responseType未正確設定。

一般的xhr請求

    let url = window.URL || window.webkitURL;
    let xhr = new XMLHttpRequest();
    xhr.open(method, url, [,async=true,]);
    xhr.ontimeout = ()=>{};
    xhr.onreadystatechange=()=>{
      if(xhr.readystate === 4
) { if(xhr.status =200) { let res = xhr.responseText; let blob = new Blob([res], {type: 'video/mpeg4'}); .... .... videoEle.src = url.createObjectURL(blob); }; } };

上面程式碼處理一般的xhr請求足夠滿足,即返回型別為DOMString的,但是處理視訊下載並且儲存後播放就會有問題,上面程式碼處理非同步視訊下載有兩個bug,如果你都知道,就不需要往下看了^_^

再次認識responseType

設定該值能夠改變響應型別(關鍵這句話)。就是告訴伺服器你期望的響應格式。

responseType值的型別可為如下

資料型別
’ ‘ DOMString (這個是預設型別)
arraybuffer ArrayBuffer物件
blob Blob物件
document Document物件
json JavaScript object, parsed from a JSON string returned by the server
text DOMString

video後臺為設定的content-type為application/octet-stream,表示二進位制流。。當時就被這貨坑了一下,以為返回的資料能夠夠Blob建構函式接收,並正確顯示。

Blob物件

Blob也是比較有意思,mdn上的解釋是Blob物件表示不可變的類似檔案物件的原始資料。Blob表示不一定是JavaScript原生形式的資料。

^_^其實就是英文Binary large Object,mysql有此型別資料結構

let blog = new Blob(arrya, options);

Blob() 建構函式返回一個新的 Blob 物件。

  • array 是一個由ArrayBuffer, ArrayBufferView, Blob, DOMString 等物件構成的 Array ,或者其他類似物件的混合體,它將會被放進 Blob。DOMStrings會被編碼為UTF-8。

  • options 是一個可選的BlobPropertyBag字典,它可能會指定如下兩個屬性:

    type,預設值為 “”,它代表了將會被放入到blob中的陣列內容的MIME型別。

ArrayBuffer涉及面比較廣,我的理解是ArrayBuffer代表記憶體之中的一段二進位制資料,一旦生成不能再改。可以通過檢視(TypedArray和DataView)進行操作。

TypedArray陣列只是一層檢視,本身不儲存資料,它的資料都儲存在底層的ArrayBuffer物件之中, 所以通過同一個arraybuffer生成的TypedArray共享記憶體資料。

nodejs中的buffer是對Uint8Array的實現。詳細可參考另外一篇我寫的文章

正確的video流開啟方式

還有一點xhr.responseText的型別為DOMString,只有當responseType為DOMString時才有正確資料,其他型別獲取響應實體用xhr.response。因為一般我們都是獲取json字串,此處也需要注意下。
so正確的程式碼如下:

let url = window.URL || window.webkitURL;
    let xhr = new XMLHttpRequest();
    xhr.open(method, url, [,async=true,]);
    xhr.responseType = 'blob' ; //arraybuffer也可以
    xhr.ontimeout = ()=>{};
    xhr.onreadystatechange=()=>{
      if(xhr.readystate === 4) {
        if(xhr.status =200) {  
          let res = xhr.response;  //不是responseText。
          /*
          *最近看別人的程式碼,發現可以這麼寫
          * let res = 'response' in xhr ? xhr.response : xhr.responseText
          * 厲害!!!
          */
          let blob = new Blob([res], {type: 'video/mpeg4'});
          ....
          ....
          videoEle.src = url.createObjectURL(blob);
          //Videos on Android do not play when the src is set as a blob via create URL, 在移動端有相容問題
        };
      }
    };

後面的內容與本文無關,純作記錄。

後續

專案中的video都儲存在移動裝置中,如果都放在blob中,會造成記憶體的大量佔用,因是cordova的webapp形式,故採用外掛cordova-plugin-file,
相關寫檔案程式碼如下

function writeSystemFile(videoBlob, isAppend) {
    let self = this;
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fs) {
      //console.log('openFsObj', fs);
      //console.log('open file name: ', fs.name);
      fs.root.getFile(self._storeVideoName, { create: true, exclusive: false },function (fileEntry) {
        // var dataObj = new Blob([videoData], { type: 'text/plain' });
        self.writeFile(fileEntry, videoBlob, isAppend);
      }, function(e) {
        console.log('onErrorCreateFile, error:', e);
      });
    }, function(e) {
      console.log('onErrorLoadFs, error:', e);
    });
  }

  function writeFile(fileEntry, dataObj, isAppend) {
    // let self = this;
    fileEntry.createWriter(function (fileWriter) {
      fileWriter.onwriteend = function() {
        console.log('Successful file write...');
        //console.log('fileWriterEnd.length:', fileWriter.length);
      };
      fileWriter.onerror = function (e) {
        console.log('Failed file write: ' + e.toString());
      };
      if(isAppend) { //表示是否追加檔案
        try {
          console.log('fileWriter.length:', fileWriter.length);
          fileWriter.seek(fileWriter.length);
        } catch(e) {
          console.log('file doesn`t exist:', e.toString());
        }
      }
      //console.log('fileWriterStart.length:', fileWriter.length);
      fileWriter.write(dataObj);

    });
  }

讀檔案程式碼

function readSystemFile() {
    let self = this;
    console.log('readSystemFile self._storeVideoName:', self._storeVideoName);
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fs) {
      fs.root.getFile(self._storeVideoName, { create: true, exclusive: false },function (fileEntry) {
        self.readFile(fileEntry);
      }, self.onErrorCreateFile);
    }, function (error) {
      console.log('onErrorLoadFs, error:', error);
    });
  }

 function readFile(fileEntry) {
    let self = this;
    fileEntry.file(function (file) {
      var reader = new FileReader();
      reader.onloadend = function() {
        if(this.result === null) {
          console.log('readFile unexpected this.result == null');
          return;
        }
        console.log(typeof this.result);
        console.log('Successful file read length: ', this.result.length);
        // var blob = new Blob([new Uint8Array(this.result)], { type: "video/mpeg4" });
        console.log('Successful file read: ', this.result);
      };
      // reader.readAsText(file);
      reader.readAsArrayBuffer(file);
      // reader.readAsBinaryString(file);
    }, self.onErrorReadFile);
  }

onErrorReadFile() {
    console.log('Failed file read: ');
  }

因安卓和ios下檔案的儲存路徑不一樣,故需要做一個判斷,檔案的儲存目錄

function getDirectory() {
    let isIOS =/(iPad|iPhone|iPod)/g.test(navigator.userAgent);
    if(isIOS) {
      return cordova.file.documentsDirectory;
    }else {
      return cordova.file.dataDirectory + 'files/';
    }
}