blob/URL.createObjectURL()/reader.readAsDataURL/文件上传

编程知识 更新时间:2023-05-03 01:33:00

文章目录

    • Blob和ArrayBuffer
    • 实战一:上传图片预览
    • 实战二:以Blob URL加载网络视频
  • 前端实现文件下载(a标签实现文件下载 避免直接打开问题)
      • 先说结论
      • 方案一 a标签+download属性
      • 方案二 后端设置下载请求的响应头 Content-Disposition 强制下载
      • 方案三 通过接口跨域请求,动态创建a标签,以blob形式下载
  • 关于 video 标签 src 带有blob:http的 一些想法
    • 1. 分析
    • 2. 找真实地址
    • 3. 找关联
  • FileReader.readAsDataURI 与 URL.createObjectURL(blob) 区别

自从 HTML5提供了video标签,在网页中播放视频已经变成一个非常简单的事,只要一个video标签,src属性设置为视频的地址就完事了。由于src指向真实的视频网络地址,在早期一般网站资源文件不怎么通过referer设置防盗链,当我们拿到视频的地址后可以随意的下载或使用(每次放假回家,就会有亲戚找我帮忙从一些视频网站上下东西)。

目前的云存储服务商大部分都支持referer防盗链。其原理就是在访问资源时,请求头会带上发起请求的页面地址,判断其不存在(表示直接访问图片地址)或不在白名单内,即为盗链。

可是从某个时间开始我们打开调试工具去看各大视频网站的视频src会发现,它们统统变成了这样的形式。

Blob和ArrayBuffer

Blob(Binary Large Object)二进制类型的大对象,其名称来源于SQL数据库,表示一个不可变、原始数据的类文件对象;
在JavaScript中,它不一定非得是大量数据,其也可以表示一个小型文本文件的内容;
Blob是不透明的,只能获取它们的大小、MIME类型以及将它们分割成更小的Blob;
构造函数:
Blob(blobParts [, options]):返回一个Blob对象,其内容由参数中给定的数组串联组成;
参数:
blobParts是一个由ArrayBuffer、ArrayBufferView、Blob、String等对象构成的Array,或者其他类似对象的混合体,它将会被放入Blob;其中,Strings会被编码为UTF-8;options是一个可选的BlobPropertyBag([bɡ])字典,它可能会指定如下两个属性:
type,默认值为 “”,它代表了将会被放入到blob中的数组内容的MIME类型;
endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入,它是以下两个值中的一个:“native”,代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 “transparent”,代表会保持blob中保存的结束符不变;

ArrayBuffer()是一个普通的JavaScript构造函数,可用于在内存中分配特定数量的字节空间。

const buf = new ArrayBuffer(16);   // 在内存中分配16 字节
alert(buf.byteLength);               // 16
  var blob = new Blob();
  var blob = new Blob([]); console.log(blob);
  var buffer = new ArrayBuffer(32);
  var blob = new Blob([buffer]); console.log(blob);
  var int8 = new Int8Array(10);
  var blob = new Blob([int8]); console.log(blob);
  var blob = new Blob(['大师哥王唯']); console.log(blob);
  var newBlob = new Blob([blob]);
  var aFileParts = ['<a id="a"><b>Web前端开发</b></a>'];
  var blob = new Blob(aFileParts, { type: 'text/html' }); 
  console.log(blob); // Blob

还可以通过其他对象创建Blob对象,如:

  var person = { username: "王唯", sex: true, age: 18 }; 
  var blob = new Blob([JSON.stringify(person, null, 2)], { type: 'application/json' }); console.log(blob);

Blob表示的不一定是JavaScript原生格式的数据,也可能是File对象,File接口基于Blob,继承了Blob的功能并将其扩展使其支持用户系统上的文件;

<input type="file" id="myfile">
  window.onload = function () {
    var myfile = document.getElementById("myfile");
    myfile.onchange = function (event) {
      var file = event.target.files[0];
      console.log(file);
      console.log(file instanceof Blob); // true
    }
  }

Blob属性:
size:只读,Blob对象中所包含数据的大小(字节);
type:只读,一个字符串,表明该Blob对象所包含数据的MIME类型;如果类型未知,则该值为空字符串;
Blob方法:

  • text():返回一个promise且包含blob所有内容的UTF-8格式的字符串;
var blob = new Blob(['大师哥王唯']);
blob.text().then(value => console.log(value))
  • arrayBuffer():返回一个promise且包含blob所有内容的二进制格式的ArrayBuffer;
var buffer = new ArrayBuffer(32);
var blob = new Blob([buffer]);
blob.arrayBuffer().then(buffer => console.log(buffer)); // ArrayBuffer(32)

Blob主要用于大量API需要进行二进制数据交换场景,为这些应用提供了通用、高效的数据交换机制,如图:

FileReader.readAsDataURL这个函数是没有返回值的,看着似乎跟URL.createObjectURL一样有返回值得,其实只有后者有返回值。

通过这些API,可以获取Blob对象,例如:

message事件从其他窗口或者线程中获取Blob;
可以从客户端数据库中获取Blob;
可以使用XHR2,从Web中下载Blob;
还有File对象,它是Blob的子类;
一旦获取了Blob对象,就可以对其进行很多的操作,如:

可以使用postMessage()方法向其他窗口或Worker发送一个Blob;
可以将Blob存储在客户端数据库中;
可以通过将Blob传递给XHR对象的send()方法,来将该Blob上传到服务端;
可以使用URL.createObjectURL()函数获取一个特殊的blob://URL,该URL代表Blob的内容,然后,将其和DOM或者CSS结合使用;
可以使用FileReader对象来异步地将一个Blob内容抽取成一个字符串或者ArrayBuffer;
可以使用File和FileWriter对象,来实现将一个Blob写入到一个本地文件中;

XMLHttpRequest中的Blob:

在HTML 5中,可以通过XML HttpRequest对象的send方法向服务器端发送Blob对象,因为所有File对象(代表一个文件)都是一个Blob对象,所以同样可以通过发送Blob对象的方式来上传文件,如:

//向服务器发送blob对象
  function uploadDocument() {
    var bb = new Blob([document.documentElement.outerHTML], { type: "text/html" });
    var xhr = new XMLHttpRequest();
    	xhr.open('POST', 'sendblob.php?fileName=' + getFileName());
    var progressBar = document.getElementById('progress');
    xhr.upload.onprogress = function (e) {
      if (e.lengthComputable) {
        progressBar.value = (e.loaded / e.total) * 100;
        console.log("上传成功");
      }
    }
    console.log(bb); xhr.send(bb);
  }
  // 获取当前页面文件的文件名
  function getFileName() {
    var url = window.location.href;
    var pos = url.lastIndexOf("\\");
    if (pos == -1) {
      // pos==-1表示为本地文件
      pos = url.lastIndexOf("/");
      // 本地文件路径分割符为"/"
      var fileName = url.substring(pos + 1);
      // 从url中获得文件名
      return fileName
    }
    var btnUpload = document.getElementById("btnUpload");
    btnUpload.addEventListener("click", uploadDocument);

XHR2中的responseType可以指定为blob,以便于从服务器接收Blob数据,如:

  // 以Blob的形式获取URL指定的内容,并将其传递给指定的回调函数
  function getBlobData(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.responseType = "blob";
    xhr.onload = function () {
      callback(xhr.response);
    };
    xhr.send(null);
  }
  // 如果要下载的数据量很大,想要显示一条进度条,可以使用onprogress事件
  function doBlobHandler(blob) {
    // console.log(blob); 
    //Blob
    var url = URL.createObjectURL(blob);
    var img = document.createElement("img");
    img.src = url; document.body.appendChild(img);
  }
  var btnGetBlob = document.getElementById("btnGetBlob");
  btnGetBlob.addEventListener("click", function () {
    getBlobData("getBlob.php", doBlobHandler)
  })

File API
通常情况下,File对象是来自用户在一个元素上选择文件后files属性返回的FileList集合(或者说是一个类数组)对象,如:

  <input type="file" id="myfile" multiple /><input type="button" value="获取文件" id="btn">
  var myfile = document.getElementById("myfile");
  console.log(myfile.files);
  // FileList {length: 0}
  var btn = document.getElementById("btn");
  btn.addEventListener("click", function (event) {
    console.log(myfile.files);
    // FileList {length: 3}
  });

FileList
通常一个FileList对象来自于一个HTML的元素的files属性,它是HTML5新增的集合属性,其中包含了一组File对象,每个File对象对应着用户所选择的文件;如果元素具有multiple属性,用户可以选择多个文件,否则,该FileList只能包含一个文件;

var file = myfile.files[0];console.log(file); // File

FileList对象具有length属性,其返回列表中的文件数量;

  for (var i = 0, len = myfile.files.length;
    i < len; i++) {
      var file = myfile.files[i];
    console.log(file);
  }

FileList对象还有个item(index)方法,其根据index索引值,返回FileList对象中对应的File对象;如:

var file = myfile.files[i];
// 或者
var file = myfile.files.item(i);

该FileList对象也有可能来自用户的拖放操作;

File接口:
File继承自Blob,是特殊类型的 Blob,且可以用在任意的Blob类型的context中;如:FileReader、 URL.createObjectURL()、createImageBitmap()及XMLHttpRequest.send()都能处理Blob和File;
File构造函数:File(bits, name[, options]);
参数:

bits:一个包含ArrayBuffer、ArrayBufferView、Blob,或者DOMString对象的Array,或者任何这些对象的组合;即为UTF-8编码的文件内容;
name:USVString,表示文件名称,或者文件路径;
options:可选,一个选项对象,包含文件的可选属性;可用的选项如下:
type:DOMString,表示将要放到文件中的内容的MIME类型,默认值为 “”;
lastModified:数值,表示文件最后修改时间的 Unix 时间戳(毫秒),默认值为 Date.now();

var file = new File(["zeronetwork"], "myfiles/demo.txt",{type:"text/plain",lastModified: 1654300000000});

File对象属性:

lastModified属性:只读,返回当前File对象所引用文件的最后修改时间,自UNIX时间起始以来的毫秒数;
lastModifiedDate属性:只读,返回当前File对象所引用文件最后修改时间的Date对象;
name:只读,返回当前File对象所引用的本地文件名字,但由于安全原因,返回的值并不包含文件路径;
webkitRelativePath非标准属性:只读,返回File相关的path或URL;
size:以字节为单位返回文件的大小;
type属性,只读,返回文件的MIME Type;

console.log(file.name); // example.txt
console.log(file.lastModified); // 1649726357207// Tue Apr 12 2022 09:19:17 GMT+0800
console.log(file.lastModifiedDate);
console.log(file.size); // 15
console.log(file.type); // text/plain
console.log(file.webkitRelativePath); // ""

对于type属性,浏览器不会实际读取文件的字节流,来判断它的媒体类型;它只基于文件扩展名;而且, type属性仅仅对常见文件类型可靠,如图像、文档、音频和视频;不常见的文件扩展名会返回空字符串;

示例:显示选择文件信息

  <div><input type="file" id="myFiles" name="myFiles" multiple><br />共选择 <span id="fileNum">0</span> 个文件,共 <span
      id="fileSize">0</span></div>
  <script>
    window.onload = function () {
      var myFiles = document.getElementById("myFiles");
      myFiles.onchange = function (event) {
        var nBytes = 0,
          oFiles = event.target.files,
          nFiles = oFiles.length;
        for (var i = 0; i < nFiles; i++) {
          nBytes += oFiles[i].size;
        }
        var sOutput = nBytes + " bytes";
        var aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
        for (var nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1;
          nApprox /= 1024, nMultiple++) {
          sOutput = nApprox.toFixed(3) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)";
        }
        document.getElementById("fileNum").innerHTML = nFiles; document.getElementById("fileSize").innerHTML = sOutput;
      }
    }
  </script>

大文件切块处理的思路

使用 FileReader.readAsArrayBuffer() 读取文件,在读取成功后 result 属性中将包含一个 ArrayBuffer 对象以表示所读取文件的数据。
对读取到的 ArrayBuffer 对象进行拆分,放到一个数组中
使用 Promise.all 来顺序的处理拆分后的数组(保证顺序传输)
使用 Uint8Array 将 ArrayBuffer 拆后后的数组生成 二进制字节数组
最后将二进制字节数组转为 base64 编码的字符串
传输的是 base64 编码的字符串,到达目的地后,需要使用 base64 解码,才能得到原始的二进制字节信息。

以下代码实现的功能:

在特定区域识别拖拽的文件
有一个配置项:
小于 20 MB 算小文件,直接整个处理;
大于 20 MB 算大文件,进行切块处理;
大文件切块后,将依次按照切块的顺序上传
注意事项:最后从浏览器传输文件数据的时候,传输的是 base64 编码的字符串,因此传送到目的地后,需要 base64 解码后再进行操作

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        body {
            width: 100vw;
            height: 100vh;
        }

        .drag-area-container {
            width: 800px;
            height: 800px;
            border: 1px solid #0f0;
        }

        .highlight {
            border: 10px solid #0f0;
        }
    </style>
</head>
<body>
    <div class="drag-area-container">
    </div>

    <script>
        (function() {
            // 禁止浏览器的拖拽默认事件
            var globalDragFile = function() {
                let globalDragArea = document.querySelector('body')

                globalDragArea.addEventListener('dragover',function(e){
                    e.preventDefault()
                })

                globalDragArea.addEventListener('drop', function(e) {
                    e.preventDefault()
                })
            }

            // 识别指定区域的拖动效果
            var localDragFile = function() {
                let localDragArea = document.querySelector('.drag-area-container')

                localDragArea.addEventListener('dragenter',function(e){
                    // 拖动文件到指定区域时,添加高亮
                    localDragArea.classList.add('highlight')
                    e.preventDefault()
                })

                localDragArea.addEventListener('dragover',function(e){
                    e.preventDefault()
                })

                localDragArea.addEventListener('drop', function(e) {
                    e.preventDefault()

                    // 去除高亮
                    localDragArea.classList.remove('highlight')

                    var file = e.dataTransfer.files[0]
                    console.log("file = ", file);
                    uploadFile(file)
                })
            }

            // 处理文件 && 上传文件
            var uploadFile = function(file) {
                // 使用 FileReader 读取文件的数据
                var reader = new FileReader()

                reader.onloadend = function() {
                    var file_result = this.result               // ArrayBuffer 数据对象
                    var file_length = file_result.byteLength

                    // 小于 20 MB 为小文件,则整个读取并上传
                    // 大于 20 MB 为大文件,则需要将它切成小块,分别上传
                    var step = 1024 * 1024 * 20

                    if (file_length < step) {
                        console.log("小文件,直接整个上传 ");
                        handleSmallFile(file_result)
                    } else {
                        console.log("大文件,切块分别上传 ");
                        var block_arr = splitBigFile(file_result, file_length, step)

                        handleBigFile(block_arr).then(function(results) {
                            console.log("大文件,切块上传成功 result = ", results)
                        })
                    }
                }

                reader.readAsArrayBuffer(file)
                /*
                    readAsArrayBuffer()        // 读取完成,result  属性中保存的将是被读取文件的 ArrayBuffer 数据对象。
                    readAsBinaryString()       // 读取完成,result  属性中将包含所读取文件的原始二进制数据。
                    readAsDataURL()            // 读取完成,result 属性中将包含一个 data: URL 格式的 Base64 字符串以表示所读取文件的内容。
                    readAsText()               // 读取完成,result 属性中将包含一个字符串以表示所读取的文件内容。
                */
            }

            var handleSmallFile = function(file_result) {
                // 先读取到 ArrayBuffer,再获取 ArrayBuffer 的 Uint8Array 字节数组形式,最后用 base64 编码字节数组用于传输。
                var unit8_data = new Uint8Array(file_result)            // 提取二进制字节数组,使用 Uint8Array 表示
                var base64_data = binary2base64(unit8_data)             // base64 编码

                console.log("==== handle upload start ====");
                console.log("data = ", base64_data);
                console.log("==== handle upload end ====");
            }

            // 根据指定的 step 大小,切出来指定的 step 大小的块
            var splitBigFile = function(file, file_length, step) {
                var step_times = Math.ceil(file_length / step)
                var start = 0
                var block_arr = []

                for (i = 0; i < step_times; i++) {
                    var block = file.slice(start, start + step)
                    start = start + step
                    block_arr.push(block)
                }

                return block_arr
            }

            var handleBigFile = async function(big_files) {
                return Promise.all([].map.call(big_files, function(file, index) {
                    return new Promise(function(resolve, reject) {
                        // 先读取到 ArrayBuffer,再获取 ArrayBuffer 的 Uint8Array 字节数组形式,最后用 base64 编码字节数组用于传输。
                        var view = new Uint8Array(file)             // 提取二进制字节数组,使用 Uint8Array 表示
                        var base64_data = binary2base64(view)       // base64 编码

                        console.log("==== handle upload start ====");
                        console.log("block index = ", index);
                        console.log("data = ", base64_data);
                        console.log("==== handle upload end ====");
                        resolve("Promise file")
                    })
                })).then(function(results) {
                    return results;
                })
            }

            // 二进制字节数组转 base64 编码的字符串
            var binary2base64 = function(bi) {
                let str = '';
                for (let i = 0, len = bi.length; i < len; i++) {
                    str += String.fromCharCode(bi[i]);
                }
                return btoa(str);
            }

            var __main = function() {
                // 禁止浏览器的拖拽默认事件
                globalDragFile()

                // 识别指定区域的拖动效果
                localDragFile()
            }

            __main()
        })()
    </script>
</body>
</html>

File类本身没有定义任何方法,但是它从Blob类继承了slice方法,针对大文件传输的场景,我们可以使用 slice 方法对大文件进行切割,然后分片进行上传;如下:

<input type="file" name="file" id="file"><button id="upload" onClick="upload()">上传</button>

  var chunkSize = 1 * 1024 * 1024;
  // 每个文件切片大小定为1MBvar totalChunk;
  //发送请求
  function upload() {
    var file = document.getElementById("file").files[0];
    var start = 0; var end; var index = 0;
    var filesize = file.size; var filename = file.name;
    //计算文件切片总数
    totalChunk = Math.ceil(filesize / chunkSize);
    console.log(totalChunk);
    while (start < filesize) {
      end = start + chunkSize;
      // 匹配最后一个分片的情况
      if (end > filesize) {
        end = filesize;
      }
      var chunk = file.slice(start, end);
      //切割文件// console.log(chunk);
      // 值形式如:mytxt.txt0、mytxt.txt1...
      var sliceIndex = file.name + "__" + index;
      var formData = new FormData();
      formData.append("file", chunk, sliceIndex);
      // 如果是最后一个分片,服务端可以合并文件
      if (end == filesize) {
        formData.append("filename", file.name);
        formData.append("totalchunk", totalChunk);
        formData.append("done", true);
      }
      var xhr = new XMLHttpRequest();
      xhr.open("POST", "postfile.php");
      xhr.onload = function () {
        console.log(xhr.response);
        if (xhr.response && xhr.response.status == 4) {
          console.log(xhr.response.filename + "上传成功")
        }
      }
      xhr.responseType = "json";
      xhr.send(formData); start = end; index++
    }
  }

带进度条的上传大文件:

<div id="progress">
  <div id="finish" style="width: 0%;"></div>
</div>
<div><input type="file" name="file" id="upfile"><input type="button" value="停止" id="stop"></div>
  #progress {
    width: 300px;
    height: 20px;
    background-color: #f7f7f7;
    margin-bottom: 20px;
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
    border-radius: 4px;
    background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
  }

  #finish {
    background-color: #149bdf;
    background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
    background-size: 40px 40px;
    he
  var upfile = document.getElementById("upfile");
  var stopBtn = document.getElementById('stop');
  var upload = new Upload(); upfile.onchange = function () {
    upload.addFileAndSend(this);
  }
  stopBtn.onclick = function () {
    this.value = "停止中"; upload.stop();
    this.value = "已停止";
  }
  function Upload() {
    var xhr = new XMLHttpRequest(); var LENGTH = 1 * 1024 * 1024;
    var start = 0; var end = start + LENGTH; var file; var blob; var blobNum = 1;
    var totalBlob = 0; var isStop = 0; var md5filename = '';
    //对外方法,传入文件对象
    this.addFileAndSend = function (that) {
      file = that.files[0];
      totalBlob = Math.ceil(file.size / LENGTH);
      blob = cutFile(file); sendFile(blob, file);
      blobNum += 1;
    }
    //停止文件上传
    this.stop = function () {
      xhr.abort(); isStop = 1;
    }
    // 重新开始
    this.restart = function () {
      sendFile(blob, file);
      is_stop = 0;
    }
    //文件分片
    function cutFile(file) {
      var fileBlob = file.slice(start, end);
      start = end; end = start + LENGTH; return fileBlob;
    };
    //发送文件
    function sendFile(blob, file) {
      var formData = new FormData();
      formData.append('file', blob);
      formData.append('blobNum', blobNum);
      formData.append('totalBlob', totalBlob);
      formData.append('fileName', file.name);
      // formData.append('md5_file_name',md5filename);
      xhr.open('POST', './uploadfile.php', false);
      // 此处使用同步的
      xhr.onreadystatechange = function () {
        var per; var progressObj = document.getElementById('finish');
        if (totalBlob == 1) { per = '100%'; }
        else {
          per = Math.min(100, (blobNum / totalBlob) * 100) + '%';
        }
        progressObj.style.width = per;
        var t = setTimeout(function () {
          if (start < file.size && isStop === 0) {
            blob = cutFile(file);
            sendFile(blob, file); blobNum++; console.log(start + ":" + blobNum + "/" + totalBlob);
          } else {
            clearTimeout(t); console.log("上传成功");
          }
        }, 1000);
      }
      xhr.send(formData);
    }
  }

File对象是特殊类型的Blob,且可以用在任意的Blob类型的context中,比如:FileReader、URL.createObjectURL()、createImageBitmmmap()及XMLHttpRequest.send()都能处理Blob和File;

FileReader:

FileReader对象允许JavaScript异步读取存储在用户计算机上的文件(或原始数据缓存区)的内容,使用File或Blob对象指定要读取的文件或数据;
FileReader经常被用于Web Worker中;

构造函数:FileReader():返回一个FileReader对象,没有参数;

var reader = new FileReader();console.log(reader); // FileReader
如:

  var myfile = document.getElementById("myfile");
  myfile.onchange = function () {
    var reader = new FileReader();
    reader.onload = function (event) {
      console.log(event.target.result);
    };
    reader.readAsText(event.target.files[0]);
  }

FileReader对象属性:

error属性:只读,表示在读取文件时发生的错误;
readyState属性:只读,表示FileReader状态,可能的值有:
EMPTY(0):还没有加载任何数据;
LOADING(1):数据正在被加载;
DONE(2):已完成全部的读取请求
result属性:只读,返回文件的内容,其仅在读取操作完成后才有效,并且,数据的格式取决于使用哪个方法来启动读取操作;

  myfile.onchange = function () {
    var reader = new FileReader(); reader.onload = function (event) {
      console.log(reader.error); //
      nullconsole.log(reader.readyState);
      // 2
      console.log(event.target.result);
      // content
    }; console.log(reader.readyState);
      //0
    reader.readAsText(event.target.files[0]);
  }

FileReader事件:

onload:处理load事件,即在读取操作完成时触发;
onloadstart:在读取操作开始时触发;
onloadend:在读取操作结束时触发;
onprogress:在读取Blob时反复触发,大概间隔50ms左右;
onerror:处理error事件,即在读取操作发生错误时触发;
onabort:处理abort事件,即在读取操作被中断时触发;

  function handler(event){
    console.log(event);}
    reader.onabort = handler;
    reader.onerror = handler;
    reader.onload = handler;
    reader.onloadstart = handler;
    reader.onloadend = handler;
    reader.onprogress = handler;

如:

<input type="file" id="myfile" multiple />
<div id="output"></div><progress id="progress" max="100"></progress>
<script>
  var myfile = document.getElementById("myfile");
  myfile.onchange = function (evt) {
    var info = "",
      output = document.getElementById("output"),
      progress = document.getElementById("progress"),
      files = evt.target.files, type = "default",
      reader = new FileReader();
    if (/image/.test(files[0].type)) {
      reader.readAsDataURL(files[0]);
      type = "image";
    }
    else {
      reader.readAsText(files[0]); type = "text";
    } reader.onerror = function () {
      output.innerHTML = "不能读取文件,错误码是:" + reader.error.code;
    };
    reader.onprogress = function (event) {
      if (event.lengthComputable) progress.value = Math.round(event.loaded / event.total * 100);
    }
    reader.onload = function (event) {
      switch (type) {
        case "image": var img = document.createElement("img");
          img.src = event.target.result; output.appendChild(img);
          break;
        case "text": output.appendChild(document.createTextNode(event.target.result));
          break;
        default: output.innerHTML = "其它内容...";
          break;
      }
    };
  }

FileReader方法:

abort():中止读取操作;在返回时,readyState属性为DONE;

readAsText(blob[, encoding]):以纯文本形式读取指定的blob中的内容,一旦完成,result属性中将包含一个字符串以表示所读取的文件内容;
可选的参数encoding表示编码类型,如缺省,则默认为“utf-8”类型;

//创建一个以二进制数据存储的html文件
const text = "<div>hello world</div>";
const blob = new Blob([text], { type: "text/html" }); // Blob {size: 22, type: "text/html"}
//以文本读取
const textReader = new FileReader();
textReader.readAsText(blob);
textReader.onload = function() {
  console.log(textReader.result); // <div>hello world</div>
};
//以ArrayBuffer形式读取
const bufReader = new FileReader();
bufReader.readAsArrayBuffer(blob);
bufReader.onload = function() {
  console.log(new Uint8Array(bufReader.result)); // Uint8Array(22) [60, 100, 105, 118, 62, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 60, 47, 100, 105, 118, 62]
};

读取部分内容:

  myfile.onchange = function (evt) {
    var file = evt.target.files[0];
    if (file) {
      var blob = file.slice(0, 20);
      var reader = new FileReader();
      reader.onload = function (event) {
        console.log(event.target.result);
      }
      reader.readAsText(blob);
    }
  }

readAsArrayBuffer(blob):开始读取参数blob指定的Blob或File对象中的内容,一旦完成,result属性中保存的将是被读取文件的ArrayBuffer数据对象;

  <input type="file" id="myfile">
  window.onload = function () {
    var myfile = document.getElementById("myfile");
    myfile.onchange = function (event) {
      var file = event.target.files[0];
      var reader = new FileReader();
      reader.addEventListener("loadend", function (e) {
        console.log(e.target.result);
        //ArrayBuffer
      }, false);
      if (file) {
        reader.readAsArrayBuffer(file);
      }
    }
  }

获取文件类型:以高位优先读取文件的前4个字节:

  /*获取文件类型:以高位优先读取文件的前4个字节:*/
  // 检测指定的blob的前4个字节
  // 读出来的内容就是文件的类型,可以将其设置成blob的属性
  function typefile(file) {
    var slice = file.slice(0, 4);
    // 只读取文件起始部分
    var reader = new FileReader();
    reader.readAsArrayBuffer(slice);
    reader.onload = function (e) {
      var buffer = reader.result;
      var view = new DataView(buffer);
      var magic = view.getUint32(0, false);
      //高位优先,读取4个字节
      console.log(magic);
      //2303741511
      switch (magic) {
        //检测文件类型
        case 0x89504E47:
          file.verified_type = "image/png";
          break;
        case 0x47494638:
          file.verified_type = "image/gif";
          break;
        case 0x25504446:
          file.verified_type = "application/pdf"; break;
        case 0x504b0304:
          file.verified_type = "application/zip";
          break;
      }
      console.log(file.name, file.verified_type);
    };
  }

readAsBinaryString(blob):开始读取指定的Blob中的内容,一旦完成,result属性中将包含所读取文件的原始二进制数据,字符串中每个字符表示一个字节,已被废弃,使用readAsArrayBuffer()代替;

  var canvas = document.createElement('canvas');
  var width = 200, height = 200; canvas.width = width; canvas.height = height;
  document.body.appendChild(canvas);
  var ctx = canvas.getContext('2d');
  ctx.strokeStyle = '#090';
  ctx.beginPath();
  ctx.arc(width / 2, height / 2, width / 2 - width / 10, 0, Math.PI * 2);
  ctx.stroke();
  canvas.toBlob(function (blob) {
    var reader = new FileReader();
    reader.onloadend = function (ev) {
      console.log(ev.target.result);
    }
    reader.readAsBinaryString(blob);
  });

readAsDataURL(blob):开始读取指定的Blob中的内容,一旦完成,result属性中将包含一个data: URL格式的base64字符串以表示所读取文件的内容;

<input type="file" id="myfile">
<br>
<img src="" height="200" alt="图片预览">
<script>
  window.onload = function () {
    var myfile = document.getElementById("myfile");
    myfile.onchange = function (event) {
      var preview = document.querySelector('img');
      var file = event.target.files[0];
      var reader = new FileReader();
      reader.addEventListener("load", function (e) {
        preview.src = e.target.result;
      }, false);
      if (file) {
        reader.readAsDataURL(file);
      }
    }
  }
</script>

data URL由四部分组成:data:[][;base64],

data::前缀;
[]:MIME type,代表数据的类型,比如’image/jpeg’;如果忽略的话,默认是"text/plain;charset=US-ASCII";
[;base64]:可选的base64标识
:数据本身,如果是简单的纯文本,可以直接嵌入文本;如果不是纯文本,上一个参数可以标识为base64,并且嵌入base64编码的二进制数据;
读取多个文件,如:

<input type="file" id="myfile" multiple>
<div id="preview">
  function readAndPreview(file) {
    var preview = document.querySelector('#preview');
    // 确保 `file.name` 符合我们要求的扩展名
    if (/\.(jpe?g|png|gif)$/i.test(file.name)) {
      var reader = new FileReader();
      reader.addEventListener("load", function () {
        var image = new Image();
        image.height = 100;
        image.title = file.name;
        image.src = this.result;
        preview.appendChild(image);
      }, false);
      reader.readAsDataURL(file);
    }
  }
  window.onload = function () {
    var myfile = document.getElementById("myfile");
    myfile.onchange = function (event) {
      var files = event.target.files;
      if (files) {
        [].forEach.call(files, readAndPreview);
      }
    }
  }

例子:显示缩略图

  function handleFiles(files) {
    var dropbox = document.getElementById("dropbox");
    for (var i = 0, len = files.length; i < len; i++) {
      var file = files[i];
      var imageType = /^image\//;
      if (!imageType.test(file.type)) continue;
      var img = document.createElement("img");
      img.classList.add("img");
      img.file = file; dropbox.appendChild(img);
      // 此时,img没有src,即还不是真正的图片//
      console.log(img);
      var reader = new FileReader();
      reader.onload = (function (aImg) {
        return function (e) {
          aImg.src = e.target.result;
        };
      })(img); reader.readAsDataURL(file);
    }
  }

对象URL(Blob URL):

对象URL也被称为blob URL,指的是引用保存在File或Blob中数据的URL;使用它就可以不必把文件内容读取到JavaScript中而直接使用文件内容;为此,只要在需要文件内容的地方提供对象URL即可;

对象URL主要使用URL类的createobjectURL()和revokeObjectURL()两个静态方法实现;
createObjectURL(object):返回一个DOMString,包含了一个对象URL,该URL可用于指定源object的内容,如包含一个唯一的blob链接;
参数object用于创建URL的File、Blob对象或者MediaSource对象;
使用此方法,可以创建用于引用任何数据的简单URL字符串,也可以引用一个包括用户本地文件的File对象,如:

  var myfile = document.getElementById("myfile");
  myfile.onchange = function (event) {
    var file = event.target.files[0];
    var objURL = URL.createObjectURL(file);
    // blob:http://127.0.0.1:5500/acc683a4-c890-4399-850b-375ebb714f2
    cconsole.log(objURL);
  };

这个对象URL是一个标识File对象的字符串,其以“blob://”开始,紧跟着是一小串文本字符串,该字符串用不透明的唯一标识符来标识Blob,指向一块内存地址;

即使对一个已创建了对象URL的文件再次创建一个对象URL,该URL都不相同,如:

  // ...var objURL1 = URL.createObjectURL(file);
  // blob:
  http://127.0.0.1:5500/51a3ee15-d8de-4c57-908f-751baf363bc6
  console.log(objURL1);

blob://URL模式被显式的设计成像一个简化的http://URL那样工作,当请求一个blob://URL的时候,浏览器也会像访问HTTP服务器那样做出响应;如果请求的Blob URL已经失效,浏览器必须返回一个404无法找到的状态码;如果请求的Blob URL来自另外的源,那么浏览器必须返回403禁止访问的状态码;

Blob URL只允许通过GET请求获取,并且一旦获取成功,浏览器必须返回一个HTTP 200 OK的状态码,同时响应的Content-Type头信息为该Blob的type属性值,如:“image/png“;

因为这个字符串是一个URL,所以在DOM中也可以使用,如:

  myfile.onchange = function (event) {
    var file = event.target.files[0];
    var objURL = URL.createObjectURL(file);
    var img = document.createElement("img");
    img.src = objURL; document.body.appendChild(img);
  };

revokeObjectURL(objectURL)方法:
在每次调用createObjectURL()方法时,都会创建一个新的URL对象,当不再需要这些 URL 对象时,每个对象必须通过调用revokeObjectURL()方法来释放;虽然关闭浏览器,会自动释放它们,但是为了获得最佳性能和内存使用状况,应该在安全的时机主动释放掉它们;
参数objectURL是一个DOMString,表示通过调用createObjectURL()方法产生的URL对象,如:

// ...URL.revokeObjectURL(objURL);

示例:使用对象URL来显示多张图片

<input type="file" id="myfile" multiple accept="image/*" style="display: none;" />
<a href="javascript:void(0)" id="fileSelect">选择文件</a>
<div id="fileList">
  <p>没有选择的文件</p>
</div>
<script>
  window.onload = function () {
    window.URL = window.URL || window.webkitURL;
    var fileSelect = document.getElementById("fileSelect"),
      myfile = document.getElementById("myfile"),
      fileList = document.getElementById("fileList");
    fileSelect.addEventListener("click", function (e) {
      if (myfile) myfile.click();
      e.preventDefault();
    });
    myfile.onchange = function (event) {
      var files = event.target.files;
      if (!files.length) fileList.innerHTML = "<p>没有选择任何文件</p>";
      else {
        fileList.innerHTML = "";
        var list = document.createElement("ul");
        fileList.appendChild(list);
        for (var i = 0, len = files.length; i < len; i++) {
          var li = document.createElement("li");
          list.appendChild(li);
          var img = document.createElement("img");
          img.src = URL.createObjectURL(files[i]);
          img.height = 80; img.onload = function () {
            // 当图片加载完成之后对象URL就不再需要了
            URL.revokeObjectURL(this.src);
          };
          li.appendChild(img);
          var info = document.createElement("span");
          info.innerHTML = files[i].name + ":" + files[i].size + "bytes";
          li.appendChild(info);
        }
      }
    }
  };
</script>

例子:用对象URL显示PDF,对象URL可以用于image之外的其它东西!它可以用于显示嵌入的PDF文件或任何其它浏览器能显示的资源;如:

<input type="file" id="myfile" accept="application/PDF">
<iframe id="viewer"></iframe>
<script>
  window.onload = function () {
    var myfile = document.getElementById("myfile");
    myfile.onchange = function (event) {
      var file = event.target.files[0];
      if (file) {
        var objURL = URL.createObjectURL(file);
        var iframe = document.getElementById("viewer");
        iframe.setAttribute('src', objURL);
        URL.revokeObjectURL(objURL);
      }
    };
  }
</script>

示例:使用Blob对象存储下载数据:从互联网上下载的数据可以存储到Blob对象中,特别是在一些需要鉴权的接口中,可以使用Ajax请求,将鉴权信息附在请求里,下载得到blob对象,然后将blob作为url使用;或者在前端直接通过构建Blob对象进行前端文件下载,如:

<ul id="filelist"></ul>
<script>
  window.onload = function () {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "blob.php?action=getall&id=1&key=1234");
    xhr.onload = function () {
      var filelist = document.getElementById("filelist");
      var files = xhr.response;
      for (var i = 0, len = files.length; i < len; i++) {
        var file = files[i];
        var li = document.createElement("li");
        li.innerHTML = file.filename + "(" + file.filesize + ")";
        var btn = document.createElement("input");
        btn.type = "button";
        btn.value = "下载";
        btn.dataset['fileid'] = file.id;
        btn.dataset['filename'] = file.filename;
        btn.onclick = downloadHandle; li.appendChild(btn); filelist.appendChild(li);
      }
    };
    xhr.responseType = "json";
    xhr.withCredentials = true;
    xhr.send(null);
  }
  function downloadHandle(event) {
    console.log(event.target.dataset.fileid);
    var fileid = event.target.dataset.fileid;
    var xhr = new XMLHttpRequest();
    // xhr.open("GET", "images/1.jpg");
    xhr.open("GET", "blob.php?action=download&id=" + fileid);
    xhr.onload = function () {
      // console.log(xhr.response);
      var url = URL.createObjectURL(xhr.response);
      var a = document.createElement("a");
      a.setAttribute('download', event.target.dataset.filename);
      a.href = url; a.click();
    };
    xhr.responseType = "blob";
    xhr.withCredentials = true;
    xhr.send(null);
  }
  </script>

实战一:上传图片预览

有时我们通过input上传图片文件之前,会希望可以预览一下图片,这个时候就可以通过前面所学到的东西实现,而且非常简单。

html

<input id="upload" type="file" />
<img id="preview" src="" alt="预览"/>

这样一个图片上传预览就实现了,同样这个方法也适用于上传视频的预览。

实战二:以Blob URL加载网络视频

现在我们有一个网络视频的地址,怎么能将这个视频地址变成Blob URL是形式呢,思路肯定是先要拿到存储这个视频原始数据的Blob对象,但是不同于input上传可以直接拿到File对象,我们只有一个网络地址。

我们知道平时请求接口我们可以使用xhr(jquery里的ajax和axios就是封装的这个)或fetch,请求一个服务端地址可以返回我们相应的数据,那如果我们用xhr或者fetch去请求一个图片或视频地址会返回什么呢?当然是返回图片和视频的数据,只不过要设置正确responseType才能拿到我们想要的格式数据。

function ajax(url, cb) {
  const xhr = new XMLHttpRequest();
  xhr.open("get", url);
  xhr.responseType = "blob"; // ""|"text"-字符串 "blob"-Blob对象 "arraybuffer"-ArrayBuffer对象
  xhr.onload = function() {
    cb(xhr.response);
  };
  xhr.send();
}

注意XMLHttpRequest和Fetch API请求会有跨域问题,可以通过跨域资源共享(CORS)解决。

看到responseType可以设置blob和arraybuffer我们应该就有谱了,请求返回一个Blob对象,或者返回ArrayBuffer对象转换成Blob对象,然后通过createObjectURL生成地址赋值给视频的src属性就可以了,这里我们直接请求一个Blob对象。

用调试工具查看视频标签的src属性已经变成一个Blob URL,表面上看起来是不是和各大视频网站形式一致了,但是考虑一个问题,这种形式要等到请求完全部视频数据才能播放,小视频还好说,要是视频资源大一点岂不爆炸,显然各大视频网站不可能这么干。

解决这个问题的方法就是流媒体,其带给我们最直观体验就是使媒体文件可以边下边播(像我这样的90后男性最早体会到流媒体好处的应该是源于那款快子头的播放器),web端如果要使用流媒体,有多个流媒体协议可以供我们选择。

前端实现文件下载(a标签实现文件下载 避免直接打开问题)

先说结论

  • 所有情况通用的方式: 后端设置下载请求的响应头 Content-Disposition: attachment; filename=“filename.jpg”
    • attachment 表示让浏览器强制下载
    • filename 用于设置下载弹出框里预填的文件名
  • 非跨域情况下 给a标签加上 download 属性,如
    download 里写文件名 注意后缀 (值非必填)
  • 通过请求解决跨域问题 动态创建a标签通过blob形式下载 具体看下面解析

文件下载通常有以下方式:

  1. 下载 a标签访问文件地址
  2. window.open(‘http://localhost:8087/upload/user.png’) 打开文件地址
  3. 后端提供一个接口 /api/download 通过接口返回文件流

浏览器通过请求头Content-Type中的MIME类型(媒体类型,通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型,如 :image/jpeg application/pdf)识别数据类型,对相应的数据做出相应处理,对于图像文本等浏览器可以直接打开的文件,默认处理方式就是打开,为了避免浏览器直接打开文件我们需要做一些处理;

方案一 a标签+download属性

当url是同源(同域名、同协议、同端口号)时,这种情况用 a标签加download属性的方式即可,download属性指示浏览器该下载而不是打开该文件,同时该属性值即下载时的文件名;

a标签中download属性可以更改下载文件的文件名。但是如果是跨域的话,download属性就会失效。

解决方案:

//onclick 事件
<a @click="downloadFile(fileUrl,fileName)">下载文件</a>
 
downloadFile(url, fileName) {
    var x = new XMLHttpRequest();
    x.open("GET", url, true);
    x.responseType = 'blob';
    x.onload=function(e) {
        //会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。
        var url = window.URL.createObjectURL(x.response)
        var a = document.createElement('a');
        a.href = url
        a.download = fileName;
        a.click()
    }
    x.send();
},

方案二 后端设置下载请求的响应头 Content-Disposition 强制下载

这是最通用的一种方式 不受跨域和请求方式的影响

Content-Disposition: attachment; filename=“filename.jpg”

想使用window.open实现强制下载的可以用这种方式

在常规的 HTTP 应答中,该响应头的值表示对响应内容的展现形式

inline 表示将响应内容作为页面的一部分进行展示
attachment 表示将响应内容作为附件下载,大多数浏览器会呈现一个“保存为”的对话框
filename(可选) 指定为保存框中预填的文件名

方案三 通过接口跨域请求,动态创建a标签,以blob形式下载

当接口请求的跨域问题已经解决时(如Nginx方式),可以直接通过请求的方式拿到文件流,将文件流转为blob格式,再通过a标签的download属性下载

// 用fetch发送请求
fetch('/upload/user.png').then((res) => {
  res.blob().then((blob) => {
    const blobUrl = window.URL.createObjectURL(blob);
    // 这里的文件名根据实际情况从响应头或者url里获取
    const filename = 'user.jpg';
    const a = document.createElement('a');
    a.href = blobUrl;
    a.download = filename;;
    a.click();
    window.URL.revokeObjectURL(blobUrl);
  });
});

上面通过原生fetch请求,动态生成一个a标签实现文件下载

res.blob() 该方法是Fetch API的response对象方法,该方法将后端返回的文件流转换为返回blob的Promise;blob(Binary Large Object)是一个二进制类型的对象,记录了原始数据信息

URL.createObjectURL(blob) 该方法的返回值可以理解为一个 指向传入参数对象的url 可以通过该url访问 参数传入的对象

该方法需要注意的是,即便传入同一个对象作为参数,每次返回的url对象都是不同的
该url对象保存在内存中,只有在当前文档(document)被卸载时才会被清除,因此为了更好的性能,需要通过URL.revokeObjectURL(blobUrl) 主动释放

当我们使用 Fetch API 获取到后端返回的字节流一般都会通过 “res.blob()” 转换成 blob 对象再进一步处理(Fetch API),那么问题来了 --- ”res.blob()” 又做了什么?
怎么下载后端返回的 zip 文件?
这个问题还是比较好解决的,从后端返回的字节数据,都可以通过调用 response 对象的 blob() 方法来将它转换成返回 blob 的 Promise。

那 blob 对象又是什么?我们可以把它当作原始数据对象,它保存着从后端返回的原始数据以及相关信息(比如字节数以及类型)。我们可以通过它获得 Base64 URL 或者 blob URL。然后通过 a 标签的 download 属性来设置文件名,将获得的 URL 赋给 a 标签的 href 属性就大功告成了,当然别忘了调用 click() 开始下载。

示例代码如下:
fetch('example.zip')
.then(res => res.blob())
.then(blob => {
    // 通过 blob 对象获取对应的 url
    const url = URL.createObjectURL(blob)

    let a = document.createElement('a')
    a.download = 'example.zip'
    a.href = url
    document.body.appendChild(a)
    a.click()
    a.remove() // document.body.removeChild(a)
})
.catch(err => {
    console.error(err)
})

每次调用 res.blob() 方法都会执行 “consume body” 动作,“consume body” 的流程大概是这样的:

获取字节流的读取器
通过读取器读取所有的数据
把数据包装成 blob 对象并返回
既然 res.blob() 可以处理字节流,那么我们能不能自己去处理后端返回的字节流,毕竟我们有时候也会遇到这种情况吧(没有的事,吃饱了撑着)。

fetch('example.zip')
.then((res) => {
    // 获取读取器
    const reader = res.body.getReader()
    const type = res.headers.get('Content-Type')
    const data = []

    return new Promise((resolve) => {
        // 读取所有数据
        function push() {
            reader.read().then(({done, value}) => {
                data.push(value)
                if (done) {
                    // 包装成 blob 对象并返回
                    resolve(new Blob(data, { type }))
                } else {
                    push()
                }
            })
        }
        push()
    })
})
.then(blob => {
    const url = URL.createObjectURL(blob)

    let a = document.createElement('a')
    a.download = 'example.zip'
    a.href = url
    document.body.appendChild(a)
    a.click()
    a.remove()
})

xhr 也可以实现,axios也差不多,网上都有

const xhr = new XMLHttpRequest();
xhr.open('GET', '/upload/user.png', true);
xhr.responseType = 'blob';
xhr.onload = function() {
  if (this.status === 200) {
  const fileName = 'test.jpg';
    const blob = new Blob([this.response]);
    const blobUrl = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = blobUrl;
    a.download = fileName;
    a.click();
    window.URL.revokeObjectURL(blobUrl);
  }
};
xhr.send();

注意

  • 当用方案一二实现时,下载文件名优先取的是Content-Disposition的filename而不是download,而通过blob的形式,文件名优先取的download属性,如果都没有设置则取的url最后一节;

  • 当通过接口的形式fetch(‘/upload/downloadfile’)访问文件,又想保留浏览器的预览效果时,可以仅设置Content-Disposition的filename以指定预览时下载的文件名,否则浏览器会默认取url最后一节,即downloadfile为文件名,导致下载的文件无后缀无法打开

  • window.open() 和 a标签 执行的是打开链接的操作,类似于将地址直接输入到浏览器中,相当于从一个域跳到另一个域,因此window.open(‘http://xxx’)可以访问而不会报跨域错误;而fetch/xhr 仅是从当前域发送请求,因此fetch(‘http://xxx’)会报跨域错误

  • 浏览器取下载时文件名的优先级是Content-Disposition: filename=“文件名.png” 优先于 优先于 url最后一节 http://localhost:8087/upload/文件名.png

    关于 video 标签 src 带有blob:http的 一些想法

    之前玩爬虫的时候,看到过video标签中src属性引入的blob:http:xxxx,当时没找到解决思路,今天又遇到类似问题,就试着找了一下。

这是有人问过 https://vimeo/ 这个网站的视频怎么下载。

1. 分析

以这个网址为例:
美天合集团CFO汪润怡谈制胜新兴市场的战略-高顿公开课

看video标签中的src属性,发现
src=“blob:https://open.gaodun/b9d3366f-87ef-4328-9d97-31110de519a1”

复制这个地址去浏览器什么也找不到。

2. 找真实地址

不管上面的问题。先去看一下视频到底从哪来的。以谷歌浏览器为例,选择XHR,发现加载了m3u8文件。

m3u8是一种视频格式,看response中返回的.ts文件,直接复制ts文件的路径打开,就是视频片段。

到这,文件其实已经找到了。但是video中的blob:https://xxxx是什么呢,是怎么找到的文件。

简单来说就是视频对象做了个标记,src指向的是标记。

3. 找关联

当我对着源码和请求的response对照的时候,发现播放器周围的html标签都是后生成的,找到了一个比较“可疑”的js文件。

发现播放器代码附近的:

<div class="playDiv" id="divid"> <script type="text/javascript" src="https://s.gaodun/web/static-player/loader.js?13p9Wv580v1a!!fs-3"></script> </div>

看了js的源码,再跟了下debug。

找到了这个网页请求的m3u8地址是这个:https://vod.gaodun/13p9Wv580v1a!!fs/SD/1.m3u8。

直接浏览器访问就可以获取,就可以获取ts文件。ts文件就是一段段的视频,可以下载下来之后拼接成一个完整的文件。

   至此,关于video 标签 src 带有blob:http的 抓取的就写完了。但是每个网站的情况都不一样。

这里只是提供一种思路,比如刚开始写的 https://vimeo/ 这个网站就不是js,而是json里边包含的视频地址。

结论就是:

blob:https并不是一种协议,而是html5中blob对象在赋给video标签后生成的一串标记,blob对象对象包含的数据,浏览器内部会解析;

每次调用 URL.createObjectURL() 方法都会生成一个地址,这个地址代表着根据 blob 对象生成的资源入口,而这个资源入口存放于浏览器维护的一个 blob URL store 中

在web容器中的页面代码

浏览器访问后的页面代码

这是因为在浏览器中执行了如下js

FileReader.readAsDataURI 与 URL.createObjectURL(blob) 区别

  • 通过FileReader.readAsDataURL(file)可以获取一段data:base64的字符串
  • 通过URL.createObjectURL(blob)可以获取当前文件的一个内存URL
  • 执行时机
    • createObjectURL是同步执行(立即的)
    • FileReader.readAsDataURL是异步执行(过一段时间)
  • 内存使用
    • createObjectURL返回一段带hash的url,并且一直存储在内存中,直到document触发了unload事件(例如:document close)或者执行revokeObjectURL来释放。
    • FileReader.readAsDataURL则返回包含很多字符的base64,并会比blob url消耗更多内存,但是在不用的时候会自动从内存中清除(通过垃圾回收机制)
  • 优劣对比
    • 使用createObjectURL可以节省性能并更快速,只不过需要在不使用的情况下手动释放内存
    • 如果不太在意设备性能问题,并想获取图片的base64,则推荐使用FileReader.readAsDataURL

更多推荐

blob/URL.createObjectURL()/reader.readAsDataURL/文件上传

本文发布于:2023-04-29 21:21:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/154a1fb52ca7b21baf167090a70faeea.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:文件上传   URL   blob   createObjectURL   readAsDataURL

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!

  • 112303文章数
  • 28570阅读数
  • 0评论数