Js+Video, загрузка из нескольких частей, возобновление с точки останова

JavaScript
Js+Video, загрузка из нескольких частей, возобновление с точки останова

предисловие

При разработке проекта возникает проблема загрузки файлов.Для производительности и технической реализации часто используется термин «шардинг», который также может включать в себя операцию возобновления загрузки в соответствии с фактическими требованиями проекта;

Сегодня поговорим о сегментировании и возобновлении с точек останова~

анализ случая

  • задний план

    Для бизнес-требований загрузка видео предполагает ситуацию, когда весь файл слишком большой, и ситуация составляет несколько гигабайт или даже больше десятка гигабайт, которые нужно разделить и загрузить;

    Разделение: как следует из названия, большие фрагменты разрезаются на небольшие фрагменты, оптимизированные для производительности, чтобы облегчить получение услуг;

    После этого, когда проблема с загрузкой фрагментов будет решена, на следующем шаге необходимо рассмотреть полное слияние видео и логику синхронизации внешних и внутренних фрагментов;

  • Процесс шардинга и описание процесса

    1. Проверьте -- размер файлового ресурса, ограничения формата ~ Можно ли контролировать ограниченный размер ресурса
    2. Идентификатор видео — создание уникального идентификатора файла (MD5) ~ Обычные проекты должны быть отделены от пользовательского режима и использовать каждый ресурс как уникальный идентификатор, чтобы избежать злоупотребления связыванием ресурсов.
    3. Slices - нарезка по размеру ломтика, количество ломтиков ~ Ограничить размер каждого слайса и общее количество слайсов
    4. Аутентификация ~ Текущий статус видео, загружено, требует возобновления или может быть загружено в обычном режиме
    5. Возобновление аутентификации (аномальное сегментирование, возобновление после точки останова) ~ Это может включать в себя непрерывную загрузку видео, поэтому необходимо получить количество загруженных сегментов через аутентификацию и сравнить их с исходным видео, а затем загрузить его снова;
    6. загружать части последовательно ~ успешное удаление неудачной постановки
  • загрузка точки останова

    1. После сбоя шарда сразу загрузить его (то есть временно сохраняется количество сбойных шардов) ~ Нормальный процесс, количество неудачных срезов, переменная система может быть временно сохранена, а затем войти в зону ожидания для возобновления работы
  • http

    1. Обработайте загрузку через некоторое время или при обновлении страницы, чтобы потерять состояние этого файла. ~ В ненормальных случаях локальные переменные теряются, и количество слайсов необходимо пересчитывать, а количество слайсов, которые не были загружены, необходимо фильтровать.

Конкретный логический поток

Рекомендации по чтению: следующий код не представляет весь процесс. Он просто упрощает и перечисляет основные методы решения. Поэтому рекомендуется прочитать шаги при входе в файл fileOutput и понять цель идеи. Вы можете увидеть конкретную реализацию каждого шага;

  • Html

    <label for="upvideo"></label>
    <input accept="audio/mp4, video/mp4" style="display:none" id="upvideo" onchange="fileOutput" type="file" />
    注:label for 上传样式
    
  • Скрипт ---> Запись файла

    参数配置区域
    this.limitSize // 文件大小
    this.fileArr // 文件分片数组 可用于暂存分片数
    async fileOutput (e) {
    // 文件输出口
    // 第一步 校验文件
    // 针对格式大小的初步过滤
    await this._fileCheck(e) // 文件格式 大小校验
    
    // 第二步 视频标识
    // 每个视频需要生成唯一id,这里以每个视频前5m作为标识
    const mdskey = await this._getMd5(e.target.files[0], 1024 * 1024 * 5) // md5 视频唯一标识
    
    // 第三步 分片
    // 获取分片数 分片文件 可配置每片的大小
    // 这里以每片5M为例,当然片数的大小取决于服务器的一个负载能力以及后期用户网速问题,所以建议每片不要过大,10m左右即可,这里咱们以5M为例
    const fileArr = await this._getVideoFilsGrouping(e.target.files[0], 1024 * 1024 * 5) // 分片
    
    // 第四步 校验视频状态
    // 以md5标识验证当前传输视频 是归属于续传还是正常流程 下面为模拟api请求
    const { data } = await this.$fetch.demo({total: Object.keys(fileArr).length, md5: mdskey}) // 授权 data 已上传的分片
    
    // 第五步 删除分片
    // 上述如果该视频存在续传情况 要相应对比 删除多余分片
    await this._deleteKey(result, data)
    
    // 第六步 上传分片 data(可重新定义变量,也可直接使用,建议重新定义,结构清晰)  
    this._forEachUp(data)
    // 涉及断点上传 重新在走 this.forEachUp(data) 即可
    // 不建议无线递归次上传方法,交互最好有个异常提示框让用户选择是否 断点上传,即主动非被动;
    }
    

    Подробная функция ---> Что делает каждая функция?

  • _fileCheck

    _fileCheck (e) {
    // 文件格式 大小校验
    return new Promise((resolve, reject) => {
     if (!(e.target.files[0] && e.target.files[0].type)) {
     // 视频有效性验证
       document.getElementById('upvideo').value = null
       return
     }
     if (this.limitSize < blob.size) {
       // 最大不能超过多少
       return
     }
     resolve(1)
    })
    }
    
  • _getMd5

    _getMd5 (blob, size) {
     // md5 对应文件唯一标识
     // size 默认视频前5M作为此视频唯一标识,理论上MD5可加密任意字符,但是你要相信对于js来说一定有性能瓶颈,所以不 建议过大,如果需求需要,也尽量建议不要超过30m
     return new Promise((resolve, reject) => {
     if (!blob) return
     blob = blob.slice(0, size, 'video/mp4')
     const reader = new FileReader()
     reader.readAsArrayBuffer(blob)
     reader.onload = (el) => {
      /* --- 将 Unicode 编码转为一个字符 area --- */
      var binary = ''
      var bytes = new Uint8Array(el.target.result)
      var length = bytes.byteLength
      for(var i = 0; i< length; i ++){
        binary += String.fromCharCode(bytes[i])
      }
      /* --- 将 Unicode 编码转为一个字符 area --- */
      const md5str = CryptoJS.MD5(CryptoJS.enc.Latin1.parse(binary)).toString()
      resolve(md5str)
    }
    })
    }
    
     知识点:
     1. 加密方式非唯一,仅前后端统一即可;
     2. 上述为buffer ---> 8位转码字符 ---> 字符集Latin1编码 ---> MD5;
     3. 上述binary最终其实就是一串二进制编码,整个编码区其实可以被 reader.readAsBinaryString(blob)   替换,但由于readAsBinaryString是非标,而且12年已经被移出W3C草案,所以不建议使用,但不代表一定不能用,兼容问题自行斟酌;
    
  • _getVideoFilsGrouping

    // 得到视频流分组
    _getVideoFilsGrouping (blob, size) {
    return new Promise((resolve, reject) => {
      const len = size || 1024 * 1024 // 2 = 1021 * 1024
      const frist = parseInt(blob.size / len) // 分组 正常格式长度数量
      const second = blob.size % len ? 1 : 0 // 分组 含有剩余分至1组
      const allnum = frist + second // 总分组量
      this.allnum = allnum
      let fileObject = {} // 数据流详细分组容器
      if (allnum) {
        if (frist) {
          for (let i = 1; i <= allnum; i++) {
            fileObject[i] = blob.slice((i - 1) * len, len + len * (i - 1), 'video/mp4')
          }
        } else {
          fileObject[1] = blob
        }
      }
      resolve(fileObject)
    })
    }
    注意点: 因本身视频分片不存在特殊标识,亦考虑性能,前后端每片以顺序作为每片标识
    
  • _deleteKey

    _deleteKey (result, data) {
    // 删除已上传分片
    return new Promise((resolve, reject) => {
       if (data.slices && data.slices.length) {
          if (data.slices.length !== this.allnum) {
            data.slices.forEach(ele => {
              delete result[ele]
              if (ele === data.slices[data.slices.length -1]) {
                resolve(1)
              }
            })
          } else {
            resolve(1)
          }
        } else {
          resolve(1)
        }
    })
    

    }

  • _forEachUp

    // 上传
    async _forEachUp (result) {
     // 这里仅是一个简单的上传示例
     // 实际业务需求可能涉及更多业务样式等逻辑
    const keys = Object.keys(result) || []
    const lastKey = keys.length && keys[keys.length - 1]
    for (let i in result) {
      // param 上传所需的参数
      // lastKey === i 是否是最后一个 如果是最后一个并且存在上传失败的分片 将触发断点上传 并且当最后一个分片上传完成之后 要有个变量数组暂存剩余分片的数量
      // result和i的作用 当前分片上传成功 delete result[i]
      await this.$fetch.demo('url', param, lastKey === i, result, i)
    }
    }
    

резюме

Из вышеизложенного мы узнали относительно классический фрагментированный процесс возобновления, за исключением характеристик точки останова, возобновления, MD5, кодирования и т. д., с точки зрения общей архитектуры, уровня видео, мы видим, что весь процесс полностью разделен из концепции пользовательского режима , то есть видео является независимым, поэтому интерфейсные и внутренние службы будут очень гибкими, уменьшат связь с бизнесом и смогут эффективно избежать ситуации злоупотребления одним и тем же видео. Конечно, конкретный анализ конкретного бизнеса нельзя обобщить одним словом, как объяснялось выше.Некоторые методы одинаковы, все дороги ведут в Рим, и количество дорог не ограничено, но каждая дорога имеет разное значение. опыт Вы можете комментировать и обмениваться в области комментариев!