首页 > 其他分享 >video 分片加载

video 分片加载

时间:2024-04-14 23:45:16浏览次数:30  
标签:fileSize const start video sourceBuffer 分片 end 加载

  • API使用: MediaSource + SourceBuffer
  • http Range



  • client.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <video id="video" controls width="500" height="300"></video>

    <script>
      /** @type {HTMLVideoElement} */
      const video = document.getElementById('video');
      const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';

      init();
      async function init() {
        const filesize = await getFileSize();
        const chunckSize = 1024 * 1024 * 1; // 1MB
        // 分片片数
        const chuncks = Math.ceil(filesize / chunckSize);
        let start = 0,
          end = chunckSize + start;
        const url = 'http://localhost:3000';

        /** @type {SourceBuffer} */
        let sourceBuffer = null;
        /** @type {MediaSource} */
        let mediaSource = null;
        let preloadTime = 0;

        function handleProgress() {
          preloadTime = sourceBuffer.buffered.end(0);
          console.log('preloadTime => ', preloadTime);
        };
        let timer = null;
        function handlePlaying() {
          if (timer) {
            clearInterval(timer);
            timer = null;
          }
          timer = setInterval(() => {
            // 当播放位置与当前 SourceBuffer 中已缓冲区的最后一个片段的结尾处相差不超过 2 秒时,则开始下载下一个分片
            if (video.currentTime >= preloadTime - 2) {
              clearInterval(timer);
              timer = null;
              start = end + 1;
              end = start + chunckSize;

              if (start > filesize - 1) {
                // 流媒体结束
                sourceBuffer.abort();
                mediaSource.endOfStream();
                mediaSource = null;
                sourceBuffer = null;
                return;
              }

              handlePlaying();
              handle();
            }
          }, 300);
        }

        video.addEventListener('seeking', async () => {
          console.log('video.currentTime => ', video.currentTime);
          if (video.currentTime <= preloadTime) return;
        });
        video.addEventListener('play', handlePlaying);
        video.addEventListener('progress', handleProgress);

        handle();
        async function handle() {
          if (end > filesize - 1) {
            end = filesize - 1;
          }

          console.log('start, end => ', start, end, filesize);
          const partBlob = await ajax(url, {
            headers: { Range: `bytes=${start}-${end}` },
          });
          const arrayBuffer = await partBlob.arrayBuffer();

          if (!sourceBuffer) {
            let { sourceBuffer: _buf, mediaSource: _ms } = await handleMediaSource();
            sourceBuffer = _buf;
            mediaSource = _ms;
            // video.play();
          }

          // 将分片数据添加到 SourceBuffer 对象中
          // 方法将 `ArrayBuffer、TypedArray 或 DataView` 中的媒体片段数据添加到 SourceBuffer 对象中
          sourceBuffer.appendBuffer(arrayBuffer);
        }
      }
      function decounce(func, delay = 1000) {
        let timer = null;
        return (...args) => {
          if (timer) {
            clearTimeout(timer);
          }
          timer = setTimeout(() => {
            func(...args);
          }, delay);
        };
      }

      function handleMediaSource() {
        return new Promise((resolve) => {
          const mediaSource = new MediaSource();
          console.log('isTypeSupported => ', MediaSource.isTypeSupported(mimeCodec));

          mediaSource.addEventListener('sourceopen', () => {
            // 根据给定的 MIME 类型创建一个新的 `SourceBuffer` 对象
            // SourceBuffer: 通过 `MediaSource` 对象传递到 `HTMLMediaElement` 并播放的媒体分块
            const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);

            sourceBuffer.addEventListener('error', (e) => {
              console.log('sourceBuffer error', e);
            });

            // sourceBuffer.appendBuffer(arrayBuffer);
            resolve({ sourceBuffer, mediaSource });
          });
          video.src = URL.createObjectURL(mediaSource);
        });
      }

      async function getFileSize() {
        const url = 'http://localhost:3000/filesize';
        const blob = await ajax(url);
        const size = await blob.text();
        return +size;
      }

      function ajax(url, options = {}) {
        return fetch(url, { ...options }).then((res) => res.blob());
      }
    </script>
  </body>
</html>
  • serve.js
import { serve } from 'bun';
import { createReadStream } from 'node:fs';
import { stat } from 'node:fs/promises';
import { URL } from 'node:url';

const filePath = './frag_bunny.mp4';
const fileContentType = 'video/mp4';

const { size: fileSize } = await stat(filePath);

const server = serve({
  port: 3000,
  async fetch(req) {
    const { pathname } = new URL(req.url);
    console.log(' => ', req.method, pathname);

    if (pathname === '/filesize') {
      return new Response(fileSize.toString(), {
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
      });
    }

    const range = req.headers.get('range');
    console.log('range => ', range);

    let [start, end] = [0, fileSize - 1];
    if (range) {
      const rangeStr = range.split('=')[1];

      const reg = /^bytes=\d+$/;
      if (reg.test(range)) {
        end = start = parseInt(rangeStr);
      } else {
        [start, end] = rangeStr.split('-');
        if (start === '' && end !== '') {
          start = fileSize - end;
          end = fileSize - 1;
        } else {
          start = start === '' ? fileSize - end : parseInt(start);
          end = end === '' ? fileSize - 1 : parseInt(end);
        }
      }
      console.log('start, end => ', start, end, fileSize);

      if (end > fileSize - 1 || start > fileSize - 1 || start > end || start < 0 || end < 0) {
        return new Response('请求范围不合法', { status: 416 });
      }
    }

    return new Response(createReadStream(filePath, { start, end }), {
      status: 206,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': 'Range',
        'Content-Type': fileContentType,
        'Content-Length': end - start + 1,
        'Content-Range': `bytes ${start}-${end}/${fileSize}`,
        'Accept-Ranges': 'bytes',
      },
    });
  },
});

console.log(`Listening on http://localhost:${server.port}`);

标签:fileSize,const,start,video,sourceBuffer,分片,end,加载
From: https://www.cnblogs.com/chlai/p/18134929

相关文章

  • typmorm 类似ef的懒加载
    typmorm类似ef的懒加载在TypeORM中,可以通过设置关系的eager:false属性来实现类似于EntityFrameworkCore(EF)的懒加载功能。这意味着关联的实体将默认不会立即加载,而是在访问它们时才加载。以下是一个简单的例子,演示如何在TypeORM中实现懒加载:  import{Enti......
  • 图片懒加载不完全指南
    图片懒加载不完全指南菜菜菜鸡 1人赞同了该文章图片懒加载在日常开发中,我们常用的两种图片加载方式如下:使用 img 标签加载图片;使用 cssbackground 加载图片。在这篇文章中,您将了解如何延迟加载这两种类型的图像。img标签图片懒加载对......
  • JavaScript判断图片是否已经加载完毕的方法汇总_javascript技巧
    JavaScript判断图片是否已经加载完毕的方法汇总_javascript技巧 在网上有很多关于判断图片是否已经加载完毕的文章,但是有的浏览器并不适合,下面小编给大家分享一些有关JavaScript判断图片是否已经加载完毕方法汇总,具体内容如下所示:一.onload事件通过监听图片的onload事件,可......
  • React.js 网站开发:实现滚动加载动画
    React.js网站开发:实现滚动加载动画极客前端探索者前沿技术的探索者,编码艺术的实践者 最近在开发官网的过程中,涉及到UI动画的制作,其中滚动效果的使用比较频繁,特此整理一下,以便查询和温习。平滑向上过渡动画这种往下滚动过渡渐变显示的动画是最常......
  • SpringBoot使用 nacos 会默认加载项目名配置文件
    问题描述boostrap.yml配置如下spring:application:name:cnblogscloud:nacos:config:server-addr:http://ip:8848namespace:d8b0df04-aa58-4a5b-b582-7d133b9e8b2c#命名空间IDfile-extension:yamlusern......
  • Qt程序加载Qt platform plugin 'xcb' 出错问题解决
    1.Qt程序运行环境ubuntu16.04Qt5.12.3Qt可执行程序编译后运行Qt可执行程序后出现报错报错内容:qt.qpa.plugin:CouldnotloadtheQtplatformplugin"xcb"in""eventhoughitwasfound.ThisapplicationfailedtostartbecausenoQtplatformplugincouldbe......
  • Maya 2019.2 Mtoa 无法正常加载并报错
    事件起因:在开始安装Maya2019.2时自动安装的Mtoa的版本为5.3.1,但是在插件管理器里无法启用插件,于是乎去网上下了一个低的版本5.1.1,虽然可以使用但是渲染出来的东西不能用;于是乎我又去网上下载了同样的5.3.1的独立安装包,然后安装破解(注意,这里有个坑,后续揭晓为什么),但是并无......
  • js 大文件分片上传
    html需要使用服务器的方式打开,推荐使用vscodeLiveServer插件,否则无法加载本地的worker.jsaxios和spark-md5自己去npmjs.com上找index.html<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewpo......
  • ARM DS-5 加载 ELF 文件运行
    1.1.1DS-5工程创建在使用ARMDS-5连接board(或者PFGA)之前首先需要能够扫描到相应的硬件信息,比如对应的cpu的相关信息:coresight相关组件信息,Cache信息等。创建好工程项目后按照下图黄线的指示进行扫描操作(通常是完成扫描后才会去执行“buildplatform”): 如果更换平台......
  • VUE实现 上滑加载更多
    实现HTML5页面上滑加载更多功能的方案:1.页面结构<divid="content-container"><divclass="item"v-for="(item,index)initems":key="index"><!--在这里渲染单个数据项的内容--></div></div><!--加载提示区域--......