视频播放实现示例Demo

学习链接

vue+springboot文件分片上传与边放边播实现

同步加载、播放视频的实现 ---- range blob mediaSource

通过调试技术,我理清了 b 站视频播放很快的原理

MSE (Media Source Extensions) 上手指南

浅聊音视频的媒体扩展(Media Source Extension)

流媒体视频基础 MSE 入门 & FFmpeg 制作视频预览缩略图和 fmp4

今天又学到一种播放视频的方法,先把视频分块的都获取到,然后再使用URL.createObject(chunks)创建blobUrl,再把这个blobUrl给到video标签即可 播放视频(可以拖动视频进度条)。但是这种缺点也很明显,得把所有视频文件分块获取完成后,才能播放视频。体验上就不是很好。后面有时间可以看下MediaSource相关(能否实现边下载边播放,而不是非得等到全部下载完了再播放?)

在这里插入图片描述

VideoPlay.vue

<template>
  <div class="release_wrap">

    <el-card class="release_card">

      <el-table stripe :data="tableData" style="width: 100%" height="600px">
        <el-table-column prop="videoName" label="视频名称" min-width="280">
        </el-table-column>
        <el-table-column label="操作">
          <template slot-scope="scope">
            <el-button size="mini" type="primary" @click="playVideo(scope.$index, scope.row)">播放</el-button>
          </template>
        </el-table-column>
      </el-table>

    </el-card>

    <el-dialog :modal="false" title="视频播放" :visible.sync="dialogVisible" width="40%">
      <video :src="videoUrl" controls="controls" width="100%" @canplay="getVidDur()" id="myvideo"></video>
    </el-dialog>
    
  </div>
</template>

<script>

var video = () => {
  var videoTime = document.getElementById("myvideo");
  console.log(videoTime.duration); //获取视频时长
  console.log(videoTime.currentTime); //获取视频当前播放时间
};

export default {
  data() {
    return {
      title: "",
      videolist: "",
      //表格数据
      tableData: [],
      //弹框组件隐藏
      dialogVisible: false,
      //用于保存视频的id
      videoId: 0,
      //保存视频的名称
      videoName: '',
      videoUrl: '',
    };
  },

  created() {
    this.getVideoInfo();
  },

  methods: {
    jump_home() {
      this.$router.replace('/')
    },
    getVidDur() {
      video();
    },
    //获取video表格数据
    getVideoInfo() {
      this.$axios.get("http://127.0.0.1:9098/SelectVideo/table").then((res) => {
        this.tableData = res.data;
      });
    },
    
    // 点击播放按钮
    playVideo(i, val) {
    
      // 显示弹框
      this.dialogVisible = true;
      
      // 保存视频名字
      this.videoName = val.videoName;
      
      // 保存视频id
      this.videoId = val.id;
      
      // 发送HEAD请求获取视频的总大小
      this.$axios.get(`http://127.0.0.1:9098/SelectVideo/getVideoSizeById/${this.videoId}`).then(res => {
      
        const totalSize = res.data;
        const chunkSize = Math.ceil(totalSize / 20); // 设置分片大小为总大小的1/5

        // 定义分片传输的函数
        const loadVideoChunk = (startByte, endByte) => {
        
          return new Promise((resolve, reject) => {
          
            this.$axios.get(`http://127.0.0.1:9098/SelectVideo/policemen/${this.videoId}`, {
              headers: {
                Range: `bytes=${startByte}-${endByte}`
              },
              responseType: 'blob'
            }).then(response => {
              // 返回获取到的视频分片数据
              resolve(response.data);
            }).catch(error => {
              reject(error);
            });
          });
        };

        // 创建一个数组来保存所有分片的Promise
        const chunkPromises = [];

        // 获取所有分片的Promise
        for (let i = 0; i < 20; i++) {
          const startByte = i * chunkSize;
          const endByte = Math.min(startByte + chunkSize - 1, totalSize - 1);
          chunkPromises.push(loadVideoChunk(startByte, endByte));
        }

        // 执行所有分片请求,并在全部请求完成后开始播放视频
        Promise.all(chunkPromises).then(chunks => {
        
          // 将分片数据合并成完整的视频Blob
          const videoBlob = new Blob(chunks);
          
          const videoUrl = URL.createObjectURL(videoBlob);
          
          this.videoUrl = videoUrl;
          
        }).catch(error => {
          console.error('Failed to load video:', error);
        });

      }).catch(error => {
        console.error('Failed to get video size:', error);
      });

    },
  },
};
</script>

<style></style>

SelectVideoController

    //查询视频流的接口
    @GetMapping("/policemen/{videoId}")
    public void videoPreview(HttpServletRequest request, HttpServletResponse response, @PathVariable("videoId") String videoId) throws Exception
    {
        System.out.println(videoId);
      
        VideoUpload videoPathList = videoUploadMapper.SelectVideoId(Integer.parseInt(videoId));
        String videoPathUrl = videoPathList.getVideoUrl();
        Path filePath = Paths.get(videoPathUrl);

        if (Files.exists(filePath))
        {
            String mimeType = Files.probeContentType(filePath);
            if (StringUtils.hasText(mimeType))
            {
                response.setContentType(mimeType);
            }

            // 设置支持部分请求(范围请求)的 'Accept-Ranges' 响应头
            response.setHeader("Accept-Ranges", "bytes");

            // 从请求头中获取请求的视频片段的范围(如果提供)
            long startByte = 0;
            long endByte = Files.size(filePath) - 1;
            String rangeHeader = request.getHeader("Range");
            // System.out.println("rangeHeader:" + rangeHeader);
            if (rangeHeader != null && rangeHeader.startsWith("bytes="))
            {
                String[] range = rangeHeader.substring(6).split("-");
                startByte = Long.parseLong(range[0]);
                if (range.length == 2)
                {
                    endByte = Long.parseLong(range[1]);
                }
            }

            // System.out.println("start:" + startByte + ",end:" + endByte);
            log.info("start:" + startByte + ",end:" + endByte);

            // 设置 'Content-Length' 响应头,指示正在发送的视频片段的大小
            long contentLength = endByte - startByte + 1;
            response.setHeader("Content-Length", String.valueOf(contentLength));

            // 设置 'Content-Range' 响应头,指示正在发送的视频片段的范围
            response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + Files.size(filePath));

            // 设置响应状态为 '206 Partial Content'
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

            // 使用 'RangeFileChannel' 进行视频片段的传输,以高效地只读取文件的请求部分
            ServletOutputStream outputStream = response.getOutputStream();
            try (RandomAccessFile file = new RandomAccessFile(filePath.toFile(), "r"); FileChannel fileChannel = file.getChannel())
            {
                fileChannel.transferTo(startByte, contentLength, Channels.newChannel(outputStream));
            } finally
            {
                outputStream.close();
            }
        } else
        {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        }
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值