springboot编写mp4视频播放接口

news2024/11/17 7:23:55

简单粗暴方式

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

    @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.coloradmin.cn/o/810056.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数学分析:流形1

光滑道路是一个映射&#xff0c;但我们通过光滑道路的这个名词&#xff0c;可以想象成一个曲线。然后这个曲线上就会有一个速度的概念&#xff0c;这个速度在不同道路上&#xff08;但都经过同一个点x_0&#xff09;会有不同的方向&#xff0c;他们组成的空间就是切空间。速度就…

The user specified as a definer (‘mysql.infoschema‘@‘localhost‘) does not exist

连接上报无法刷新浏览器 use mysql; show tables; ERROR 1449 (HY000): The user specified as a definer (mysql.infoschemalocalhost) does not existselect user,host from user; 删除 drop user ‘mysql.infoschema’‘127.0.0.1’; 重现创建 create user ‘mysql.infosc…

《视觉SLAM十四讲》笔记(4-6)

文章目录 4 李群与李代数4.1 李群与李代数基础4.1.1 群4.1.2 李代数的引出4.1.3 李代数的定义 4.2 指数与对数映射4.3 李代数求导与扰动模型 5 相机与图像5.1 相机模型5.1.1 针孔相机模型5.1.2 畸变5.1.3 双目相机5.1.4 RGB-D相机 6 非线性优化 4 李群与李代数 为了解决什么样…

Redis学习路线(6)—— Redis的分布式锁

一、分布式锁的模型 &#xff08;一&#xff09;悲观锁&#xff1a; 认为线程安全问题一定会发生&#xff0c;因此在操作数据之前先获取锁&#xff0c;确保线程串行执行。例如Synchronized、Lock都属于悲观锁。 优点&#xff1a; 简单粗暴缺点&#xff1a; 性能略低 &#x…

KWP2000协议和OBD-K线

KWP2000最初是基于K线的诊断协议&#xff0c; 但是由于后来无法满足越来越复杂的需求&#xff0c;以及自身的局限性&#xff0c;厂商又将这套应用层协议移植到CAN上面&#xff0c;所以有KWP2000-K和KWP2000-CAN两个版本。 这篇文章主要讲基于K线的早期版本协议&#xff0c;认…

【数据结构】无头+单向+非循环链表(SList)(增、删、查、改)详解

一、链表的概念及结构 1、链表的概念 之前学习的顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;而链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的&#xff0c;可以实现更加…

Element UI如何自定义样式

简介 Element UI是一套非常完善的前端组件库&#xff0c;但是如何个性化定制其中的组件样式呢&#xff1f;今天我们就来聊一聊这个 举例 就拿最常见的按钮el-button来举例&#xff0c;一般来说默认是蓝底白字。效果图如下 可是我们想个性化定制&#xff0c;让他成为粉底红字应…

在windows上安装minio

1、下载windows版的minio&#xff1a; https://dl.min.io/server/minio/release/windows-amd64/minio.exe 2、在指定位置创建一个名为minio文件夹&#xff0c;然后再把下载好的文件丢进去&#xff1a; 3、右键打开命令行窗口&#xff0c;然后执行如下命令&#xff1a;(在minio.…

【数据结构】栈(Stack)的实现 -- 详解

一、栈的概念及结构 1、概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在表尾进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出 LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈…

Android Glide预处理preload原始图片到成品resource 预加载RecyclerViewPreloader,Kotlin

Android Glide预处理preload原始图片到成品resource & 预加载RecyclerViewPreloader&#xff0c;Kotlin <uses-permission android:name"android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name"android.permission.READ_MED…

集合的概述

基本的集合有六种&#xff0c;分别是Vector、ArrayList、LinkedList、TreeSet、HashSet、LinkedHashSet 其中Vector、ArrayList、LinkedList实现了List接口&#xff0c;LinkedHashSet实现了HashSet接口&#xff0c;TreeSet、HashSet实现了Set接口 List和Set又实现了Collectio…

Anaconda原理解析及使用

anaconda想必大家都不陌生&#xff0c;属于使用python的重要工具&#xff0c;更是学习机器学习、深度学习的必备工具。在搭建环境过程中&#xff0c;感觉出现的许多问题根源在于对于anaconda的基本原理理解不到位&#xff0c;导致许多无效操作。为此&#xff0c;我重温了一遍an…

TypeScript实战篇 - TS实战:花田APP的架构

目录 TS实现花田APP的聊天Node端 整体架构 项目拆分 项目的特点 模型层 所有系统都是模型的外设 模型层的优势 TS实现花田APP的聊天Node端 整体架构 项目拆分 代号&#xff1a;huatian 5个独立的npm包 huatian/ui 花田的主项目huatian/component 花田组件库huatian/…

❤️创意网页:高考加油倒计时网页文字加多版 - 增加祝福语句和下雪背景效果

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;欢迎踏入…

Emacs之改造最快的文件搜索工具fd-dired(基于fd命令)(一百二十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

RAID相关知识

简介 RAID &#xff08; Redundant Array of Independent Disks &#xff09;即独立磁盘冗余阵列&#xff0c;通常简称为磁盘阵列。RAID技术将多个单独的物理硬盘以不同的方式组合成一个逻辑磁盘&#xff0c;从而提高硬盘的读写性能和数据安全性。 数据组织形式 分块&#x…

Flowable-任务-手动任务

定义 手动任务是预期在没有任何业务流程执行引擎或任何应用程序的帮助下执行的任务&#xff0c;它用于建 模那些引擎不需要知道的人所做的工作&#xff0c;以及那些不存在已知系统或 UI 界面的人所做的工作&#xff0c;一 般完善流程结构描述&#xff0c;不被引擎执行。例如&a…

cookie登录b站获取cookie登录billbill教程

利用cookie免账号密码登录b站cookie登录哔哩哔哩cookie登录billbill教程 1.获取cookie 以Edge浏览器为例&#xff0c;随便找一个人私聊&#xff0c;按下F12&#xff0c;选到网络(network)&#xff0c;在筛选器里填send_msg&#xff0c;如下图所示。如果没有网络(network)&…

@TableId(type = IdType.ASSIGN_ID)

最近一直在使用mybatis plus ,上篇说没有添加ID 那不得学习一把 本来想不去添加主键&#xff0c;但是暂时还没发现mybatis plus增么 可以不设置主键的情况下修改&#xff0c;想想还是不行&#xff0c;主要我不想去多写代码&#xff08;肯定不是因为懒&#xff09;&#xff0c;…

巨人互动|Google海外户Google SEO常见术语

随着越来越多的人开始建立网站和在线业务&#xff0c;谷歌搜索引擎优化&#xff08;SEO&#xff09;变得越来越重要。要在谷歌上获得更高的排名&#xff0c;您需要掌握许多不同的术语和技术。在本篇文章中&#xff0c;我们将介绍一些常见的谷歌SEO术语&#xff0c;以帮助您了解…