1.项目框架:vue

2.需求:项目需要下载大的视频文件

3.视频下载踩坑日记

方法1. 遇到这个需求我首先用a标签去模拟一个点击事件想通过这种方式来实现点击下载

let link = document.createElement('a'); //创建a标签
link.href = url  //添加链接
link.download = url.split('/')[url.split('/').length -1] //  // 给文件加下载文件的名字
document.body.appendChild(link) //a标签加到body
link.click(); //模拟a标签点击
document.body.removeChild(link) // 下载完成移除元素

在这么做之后 点击下载我发现视频不是像预想中的那样被下载,而是在当前页面跳转打开播放视频
所以我开始找别的办法

方法2. blob文件流方式下载视频
后面我只能让后台给我返回文件流的形式:

 transBlob(url) {
      this.$http({
        method: "GET",
        url: "这是请求blob文件的url",
        responseType:'blob',
        params: {
          file_name: '参数'
        },
        onDownloadProgress: (event) => {
        // 下载进度查看
        console.log(event)
          }
        onUploadProgress: (event) => {
        // 上传进度查看(与视频下载无关,上传可以用到)
        console.log(event)
          }

      }).then((res) => {
          let link = document.createElement('a');
          let blob = new Blob([res.data], { type: 'application/octet-stream' }) // 这里可以定义下载类型
          link.download = url.split('/')[url.split('/').length -1] //  // 下载文件的名字
          if (window.navigator && window.navigator.msSaveOrOpenBlob) {
            window.navigator.msSaveOrOpenBlob(blob , url.split('/')[url.split('/').length -1]);
          } //兼容
          console.log(res)
          link.href = window.URL.createObjectURL(blob)  //链接
          document.body.appendChild(link) //a标签加到body
          link.click(); //模拟a标签点击
          // window.open(link.href)

          document.body.removeChild(link) // 下载完成移除元素
          window.URL.revokeObjectURL(blob) // 释放掉blob对象
          // console.log(link.href)
      }).catch(error => console.log(error))

    },

在这种方式使用之后,视频的下载实现了。
但是问题又来了,blob形式的下载,直到接口请求完成之后,前端才弹框提示另存为,点击确定立马下载完成。

也就是说在我点击下载,到后台文件流传输完成之前,前端是没有任何反应的。小文件好说,几秒的等待能接受,但是对于大文件,几百兆,几个G,网速又不行的时候,这种会让用户的体验极差。
我希望在我点击下载能立马弹窗另存为,像浏览器下载其他文件一样,在左下角出现一个下载进度与提示
在这里插入图片描述

在这里插入图片描述
这样的交给浏览器下载的效果。

方法 3. 这里我查询网上的解决办法,使用blob只能是在前端手动加一个进度条来实现,这样能在视觉上给用户正在下载文件的感觉,通过onDownloadProgress监听可以实现。但是我们产品并不想要这样的效果。

在产品表达不希望加进度条之后我又跌跌撞撞无头脑的尝试了很多种办法,网上的 form表单形式,iframe形式,使用download.js这个插件等。都以失败告终。
(download.js使用起来是很方便,但是它的传参参数第一位视频的话也是要求blob格式。。。)

终极方案:a标签 + nginx配置 产品后来说你要不看一下竞品是怎么做的,于是我到竞品产品体验了一下,发现在前端也只是用简单的a标签来实现的,并没有额外的操作。我复制了下竞品返回的下载链接,发现在浏览器访问这个链接立马弹出另存为,我又问了相关的后台朋友这种链接怎么做的,最后得出结论,在部署时服务器进行一些相关的配置就可以啊。。。,
在这里插入图片描述
博文
https://blog.csdn.net/weixin_30407613/article/details/96251050

白折腾一天的我。。。
最后运维配置了nginx,使用的方法1解决了问题。
访问链接时,浏览器会默认打开它能打开的文件,像img,pdf,视频等,不能打开的会提示下载。大概是这么个原理。

后面测试提出一个bug,素材在修改了文件名之后,下载的时候文件名也得变化,所以需要修改文件名。我用的a标签,加了download属性之后发现不生效,并且有很大的兼容性问题。又折腾半天没解决。后来又去看了竞品,猜测也是nginx做的配置。后来也是后台配置之后直接就改了名。
所以文件名的修改也是后台做比较好。

终极方案升级版(fetch + streamSaver)

最近后端那边又需要改为blob流文件形式的下载,不再返回url。。。。一番度娘,发现streamSaver实现能达到比较好的效果,还支持下载大文件,多文件,(此处为单文件下载例子)

streamSaver使用方法
1.安装
npm install streamsaver

2.使用

const streamSaver = require('streamsaver');
    // 下载素材按钮
    downloadMaterial(item) {
      const url1 = this.$http.defaults.baseURL + '/upload/getxxxx'
      let data = {ip_id: item.ip_id}
      fetch(url1, {
        method: 'POST',
        cache: 'no-cache',
        headers: {
          "content-type": "application/x-www-form-urlencoded",
        },
        body: this.qs.stringify(data)
      }).then(res => {
		
		// 此处name为文件名,format为文件格式,例如 001.mp4
        const fileStream = streamSaver.createWriteStream('name.format', {
          size : res.headers.get("content-length")
        })

        const readableStream = res.body

        // more optimized
        if (window.WritableStream && readableStream.pipeTo) {
          return readableStream.pipeTo(fileStream)
              .then(() => console.log('done writing'))
        }
        window.writer = fileStream.getWriter()

        const reader = res.body.getReader()
        const pump = () => reader.read()
            .then(res => res.done
                ? window.writer.close()
                : window.writer.write(res.value).then(pump))
        pump()
      })
    },

参考博文:
https://blog.csdn.net/fxtxz2/article/details/116452505

Logo

前往低代码交流专区

更多推荐