springboot编写mp4视频播放接口

简单粗暴方式

直接读取指定文件,用文件流读取视频文件,输出到响应中

    @GetMapping("/display1/{fileName}")
    public void displayMp41(HttpServletRequest request, HttpServletResponse response,@PathVariable("fileName") String fileName) throws IOException {
        File file=new File("D:/Download/"+fileName+".mp4");
        if(!file.exists()){
            response.getOutputStream().close();
            return;
        }
        InputStream inStream=new FileInputStream(file);
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inStream.read(buffer)) != -1) {
            response.getOutputStream().write(buffer, 0, len);
        }
        inStream.close();
        response.getOutputStream().flush();
        response.getOutputStream().close();
    }

这种方式很尴尬,可以播放视频,然而你会发现视频自带的进度条无法拖动。。。。。。。,只能暂停播放,没办法前进,也没办法后退。。。。。。

高端优雅方式

需要加一个断点续传的规范,实现很简单

注:如果你是用的h5原生的<video>,请求头会有一个Range: bytes=589824-,表示该请求希望返回的数据是从589824位置开始即可。

实现一共两点:

(1)响应头部添加如下格式响应头

Content-Range: bytes 589824-32153693/32153694

大概就是:Content-Range: bytes 请求头指定的开始字节数-本次返回的文件字节位置/总共多少字节,值得注意的是,本次返回的文件字节位置一定要比总字节数至少少一个字节,否则视频缓存结束的最后一次数据无法播放,可能是浏览器出了异常,视频会重新播放。本次返回的文件字节位置可以不准,因为浏览器会自动记录真实拿到的字节数量,但一定要少一个字节。

(2)响应码改为206

有了这两点就可以实现正常的视频播放接口了。

优化

考虑到视频的进度条很大概览是会被拖来拖去的,导致频繁请求接口。

假设你的文件200MB,频繁的请求每次都会把整个文件读入http流中,如果用户网速慢,或者浏览器的缓存策略会阻塞http请求,慢慢从http响应中读取这部分数据,这可能就会使数据都堆积到服务器内存里(本人毫无根据瞎想的),浪费资源。

(1)因为需要指定字节位置读取视频文件,使用随机读取RandomAccessFile类来操作。

(2)既然支持分段获取数据,不如每次返回定量的字节数即可。我这里设置成每次获取1MB,浏览器播放完了会自动接着调用。根据实际情况考虑,内网环境使用可以设大一些,如果数值设置的小,这请求频率会变的很多,得不偿失。

    @GetMapping("/display/{fileName}")
    public void displayMp4(HttpServletRequest request, HttpServletResponse response, @PathVariable("fileName") String fileName) throws IOException {
        File file = new File("D:/Download/" + fileName + ".mp4");
        if (!file.exists()) {
            response.getOutputStream().close();
            return;
        }
        String range = request.getHeader("Range");
        long lenStart = 0;
        if (range != null && range.length() > 7) {
            range = range.substring(6, range.length() - 1);
            lenStart = Long.parseLong(range);
        }
        int size = 1048576;
        response.setHeader("Content-Range", "bytes " + lenStart + "-" + ((file.length() - lenStart-2 < size)?file.length()-1:lenStart+size- 1) +"/" + file.length());
        response.setHeader("Content-Type", "video/mp4");
        response.setStatus(HttpStatus.PARTIAL_CONTENT.value());//响应码206表示响应内容为部分数据,需要多次请求
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        randomAccessFile.seek(lenStart);//设置读取的开始字节数
        //视频每次返回一兆数据
        byte[] buffer = new byte[size];
        int len = randomAccessFile.read(buffer);
        if (len != -1) {
            response.getOutputStream().write(buffer, 0, len);
        }
        randomAccessFile.close();
        response.getOutputStream().flush();
        response.getOutputStream().close();
    }

 附上测试的vue代码,当然里面有丰富的video的监听事件

<template>
  <div>
    <video ref="video"
           controls
           @loadedmetadata="loadedmetadata"
           @canplay="canplay"
           @waiting="waiting"
           @timeupdate="timeupdate"
           @play="play"
           @pause="pause"
           @ended="ended"
           style="width: 400px;height: 200px;"
          >
      <source :src="getMp4Url(displayName)"  type="video/mp4">
        您的浏览器不支持 HTML5 video 标签。
    </video>
    <div>当前时长:{{formatTime(nowTime)}}</div>
    <div>总时长:{{formatTime(totalTime)}}</div>
    <div>
      <button @click="playPause">{{!displayStatus?'播放':'暂停'}}</button>
    </div>
  </div>

</template>

<script>
import config from "@/config";
export default {
  name: "VideoIndex",
  data(){
    return{
      displayName: '最伟大的作品',
      displayStatus:false,
      nowTime: 0,//当前正在播放的时间,单位:秒,带三位小数
      totalTime:0,//视频的总长度,单位:秒,带三位小数
      videoWidth:0,//视频宽度
      videoHeight:0,//视频宽度
    }
  },
  mounted(){
    // this.$refs.video.onloadstart =(e)=> {
    //   //在浏览器开始寻找指定视频/音频(audio/video)触发
    //   console.log("onloadstart",e)
    // }
    // this.$refs.video.onprogress =(e)=> {
    //   //在浏览器下载指定的视频/音频(audio/video)时触发
    //   console.log("onprogress",e)
    // }
    // this.$refs.video.ondurationchange =(e)=> {
    //   //事件在视频/音频(audio/video)的时长发生变化时触发
    //   console.log("ondurationchange",e)
    // }
    // this.$refs.video.onloadeddata =(e)=> {
    //   //事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频(audio/video)的下一帧时触发
    //   console.log("onloadeddata",e)
    // }
    // this.$refs.video.oncanplaythrough =(e)=> {
    //   //可以正常播放且无需停顿和缓冲时触发
    //   console.log("oncanplaythrough",e)
    // }
  },
  methods:{
    getMp4Url(name){
      return config.BASE_URL+"/video/display/"+name
    },
    playPause(){//播放状态切换
      if(this.$refs.video.paused){
        this.$refs.video.play();
      }else{
        this.$refs.video.pause();
      }
    },
    waiting(){//转圈的时候才会调用,秒加载好像不会触发
      console.log("加载中");
    },
    loadedmetadata(){
      this.totalTime=this.$refs.video.duration;
      console.log("获取视频总时间长度:"+this.formatTime(this.totalTime));
    },
    canplay(){
      //表示视频已经加载好了
      //这可以获取视频真是高度和宽度,
      this.videoWidth=this.$refs.video.videoWidth
      this.videoHeight=this.$refs.video.videoHeight
      console.log("视频已准备好了,可以播放,宽度:"+this.videoWidth+",高度:"+this.videoHeight)
    },
    play(){
      this.displayStatus=true;
      console.log("开始播放");
    },
    pause(){
      console.log("暂停播放");
      this.displayStatus=false;
    },
    ended(){
      console.log("播放结束");
    },
    timeupdate(){ //播放的时间戳更新
      this.nowTime=this.$refs.video.currentTime
    },
    formatTime(time){
      let temp=time; //302.432s
      let s= Math.ceil(temp%60); //0.01会进位+1
      temp=temp/60;
      let m=Math.floor(temp%60);
      let h=Math.floor(temp/60);
      return `${h>9?h:("0"+h)}:${m>9?m:("0"+m)}:${s>9?s:("0"+s)}`
    },

  },
}
</script>

<style scoped>

</style>

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/50773.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

掌握文件锁:使用flock实现多个进程之间的无缝文件同步

使用flock实现多个进程之间的无缝文件同步&#xff1f; 博主简介一、引言二、文件锁的概述2.1、定义文件锁2.2、文件锁的种类2.3、文件锁的作用 三、使用flock实现文件锁3.1、flock的简介3.2、flock的使用方法3.3、flock文件锁命令3.4、flock对文件同步的帮助 四、实现多个进程…

GRE TAP的工作原理与5G工业物联网中的应用

随着互联网新技术的发展以及智能化水平的提高&#xff0c;各企业对实时数据传输的需求也在不断提升&#xff0c;企业愈发重视数据中心的建设&#xff0c;以保障企业内网数据安全。 GRE&#xff08;Generic Routing Encapsulation&#xff0c;通用路由封装&#xff09;协议属于…

物理机安装ESXI时遇到No Network Adapters

前不久在虚拟机下安装完成了ESXI&#xff0c;果断地使用了&#xff0c;确实很不错了&#xff0c; 配合我上次发的密匙&#xff08;https://www.cnntt.com/archives/5556&#xff09;妥妥爽。 虚拟机中试玩了一下&#xff0c;就开始布置到我的物理机上了&#xff0c;毕竟我以后…

【LeetCode】142.环形链表Ⅱ

题目 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部…

无涯教程-jQuery - Highlight方法函数

Highlight 效果可以与effect()方法一起使用。这将以特定的颜色突出显示元素的背景&#xff0c;默认为黄色(yellow)。 Highlight - 语法 selector.effect( "highlight", {arguments}, speed ); 这是所有参数的描述- color - 高亮显示颜色。默认值为"#fff…

(八九)如何与InfluxDB交互InfluxDB HTTP API

以下内容来自 尚硅谷&#xff0c;写这一系列的文章&#xff0c;主要是为了方便后续自己的查看&#xff0c;不用带着个PDF找来找去的&#xff0c;太麻烦&#xff01; 第 8 章 前言&#xff1a;如何与InfluxDB交互 1、InfluxDB启动后&#xff0c;会向外提供一套HTTP API。外部程…

【Rust教程 | 基础系列 | Cargo工具】Cargo介绍及使用

文章目录 前言一&#xff0c;Cargo介绍1&#xff0c;Cargo安装2&#xff0c;创建Rust项目2&#xff0c;编译项目&#xff1a;3&#xff0c;运行项目&#xff1a;4&#xff0c;测试项目&#xff1a;5&#xff0c;更新项目的依赖&#xff1a;6&#xff0c;生成项目的文档&#xf…

Nacos的搭建及服务调用

文章目录 一、搭建Nacos服务1、Nacos2、安装Nacos3、Docker安装Nacos 二、OpenFeign和Dubbo远程调用Nacos的服务1、搭建SpringCloudAlibaba的开发环境1.1 构建微服务聚合父工程1.2 创建子模块cloud-provider-payment80011.3 创建子模块cloud-consumer-order80 2、远程服务调用O…

Caffeine本地缓存技术

说明&#xff1a;Caffeine是本地缓存方案&#xff0c;在所有本地缓存中命中率最佳&#xff0c;参考下图&#xff08;引自http://t.csdn.cn/oiQlH&#xff09;&#xff0c;本文介绍Caffeine在SpringBoot项目中的应用。 使用 例如现在有两个接口&#xff0c;一个查询所有用户&am…

分析npm run serve之后发生了什么?

首先需要明白的是&#xff0c;当你在终端去运行 npm run ****&#xff0c;会是什么过程。 根据上图的一个流程&#xff0c;就可以衍生出很多问题。 1&#xff0c;为什么不直接运行vue-cli-service serve? 因为直接运行 vue-cli-service serve&#xff0c;会报错&#xff0c…

数字光源控制器报警说明

Revision Sheet: Rev Data Author Description 1.0 20230729 Shuangyi 数字光源控制器报警说明 V1.0 一.报警说明 当我们所连接的光源负载超出光源控制器本身驱动能力的时候&#xff0c;我们会对控制器进行保护&#xff0c;从以下方式可知道过流的情况&#xff0c;如…

【信号去噪】基于马氏距离和EDF统计(IEE-TSP)的基于小波的多元信号去噪方法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

数据可视化(2)

1.柱状图 #柱状图 #bar(x,height,width,*,aligncenter,**kwargs) #height柱子的高度&#xff0c;即y轴上的数据 #width数组的宽度&#xff0c;默认值0.8 #*表示后面的参数为匿名关键字&#xff0c;必须传入参数 #kwargs关键字参数x[1,2,3,4,5] height[random.randint(10,100)f…

【学习笔记】视频检测方法调研

目录 1 引言2 方法2.1 视频目标跟踪2.1.1 生成式模型方法2.1.2 判别式模型方法2.1.2.1 基于相关滤波跟踪2.1.2.2 基于深度学习跟踪 2.2 视频异常检测2.2.1 基于重构方法2.2.2 基于预测方法2.2.3 基于分类方法2.2.4 基于回归方法 2.3 深度伪造人脸视频检测2.3.1 基于RNN时空融合…

WIZnet W6100-EVB-Pico DHCP 配置教程(三)

前言 在上一章节中我们讲了网络信息配置&#xff0c;那些网络信息的配置都是用户手动的去配置的&#xff0c;为了能跟电脑处于同一网段&#xff0c;且电脑能成功ping通板子&#xff0c;我们不仅要注意子网掩码&#xff0c;对于IP地址主机位和网络位的划分&#xff0c;而且还要注…

【LeetCode】二叉树的前序,中序,后序遍历

此题用递归做比较容易&#xff0c;然后根据前中后的遍历特点&#xff1a; 前序是根左右&#xff0c; 中序是左根右&#xff0c; 后序是左右根。 前序遍历&#xff1a;做题入口 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer…

求分享如何批量压缩视频的容量的方法

视频内存过大&#xff0c;不但特别占内存&#xff0c;而且还会使手机电脑出现卡顿的现象&#xff0c;除此之外&#xff0c;如果我们想发送这些视频文件可能还会因为内存太大无法发送。因此&#xff0c;我们可以批量地压缩视频文件的内存大小&#xff0c;今天小编要来分享一招&a…

VSCode配置之C++ SQLite3极简配置方案

背景 最近在学习《深入应用C11: 代码优化与工程级应用》&#xff0c;其中第13章说到SQLite库&#xff0c;查询网上诸多教程&#xff0c;发现比较容易出现bug且配置较为麻烦&#xff0c;故记录此次简化版方案&#xff0c;以供参考。 软件环境 SQLite 3.42.0 版本&#xff08;仅…

解读分布式锁(redis实现方案)

1.导读 分布式锁是一种用于分布式系统中的并发控制机制&#xff0c;它用于确保在多个节点或多个进程之间的并发操作中&#xff0c;某些关键资源或代码块只能被一个节点或进程同时访问。分布式锁的目的是避免多个节点同时修改共享资源而导致的数据不一致或冲突的问题。通俗的来…

【MySQL】索引与B+树

【MySQL】索引与B树 索引概念前导硬件软件方面 索引的理解单个page多个page引入B树B树的特征为什么B树做索引优于其他数据结构&#xff1f;聚簇索引与非聚簇索引辅助索引 索引的创建主键索引的创建和查看唯一键索引的创建和查看普通索引的创建和查看复合索引全文索引索引的其他…