Beispiel für die Implementierung der Videowiedergabe, Demo

Lernlink

Vue + Springboot-Dateisegmentierungs-Upload und -Wiedergabeimplementierung

Implementierung des synchronen Ladens und Abspielens von Videos ---- Bereich Blob MediaSource

Durch Debugging-Technologie habe ich das Prinzip geklärt, wie schnell die Videowiedergabe an Station b ist.

MSE (Media Source Extensions) Erste Schritte

Medienquellenerweiterung für Qianchao Audio und Video

Erste Schritte mit den Grundlagen zum Streamen von Videos, MSE und FFmpeg, Erstellen von Miniaturansichten für die Videovorschau und fmp4

Heute habe ich eine andere Möglichkeit kennengelernt, ein Video abzuspielen. Zuerst alle Videoblöcke abrufen, dann mit URL.createObject(chunks) eine blobUrl erstellen und diese blobUrl dann dem Video-Tag übergeben, um das Video abzuspielen (Sie können das Video ziehen). Fortschrittsanzeige). Dieser Mangel liegt jedoch auch auf der Hand: Alle Videodateien müssen in Blöcken abgerufen werden, bevor das Video abgespielt werden kann. Die Erfahrung ist nicht sehr gut. Wenn Sie später Zeit haben, können Sie einen Blick auf MediaSource werfen (können Sie es während des Downloads abspielen, anstatt mit dem Abspielen warten zu müssen, bis alles heruntergeladen ist?)

Fügen Sie hier eine Bildbeschreibung ein

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>

Wählen SieVideoController aus

    //查询视频流的接口
    @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());
        }
    }

Guess you like

Origin blog.csdn.net/qq_16992475/article/details/132371999