video标签学习 xgplayer视频播放器分段播放mp4

news2024/11/20 20:30:54

文章目录

    • 学习链接
    • 目标
      • video标签自带视频和制作的视频区别
      • video标签的src属性
        • 本地视频文件
          • 前端代码
          • 播放效果
        • 服务器视频文件
          • 示例1
            • 后端代码
            • 前端代码
            • 播放效果
          • 示例2
            • 后端代码
            • 前端代码
            • 播放效果
          • 示例3
            • 后端配置
            • 前端代码
            • 播放效果
      • video对象
        • video对象创建和获取
        • video的属性
        • video的方法
        • video的事件
        • 示例
      • 实现一个video播放器(空)
      • xgplayer分段播放mp4视频
        • 安装依赖
        • App.vue
        • 后台代码
          • NonStaticResourceHttpRequestHandler
          • FileRestController
          • WebConfig
        • 效果
      • 报错原因

学习链接

详解video对象,看完必会,你也能写一个视频播放器

实现HTML5的video标签视频播放器

HTML5 的 video 标签,实现简易播放器

原生 JS 实现视频弹幕功能

关于 video 播放的新探索
西瓜视频播放器(HTML5)(这个播放器可以支持mp4视频分段播放)

下面报错的原因找到了,参考:Java后端接口返回视频流,使用video组件播放视频,实现分段下载

目标

HTNL5的新对象—video对象

  • 学习video标签,掌握video标签的常用属性
  • 学习video对象,掌握video对象的常用属性、方法
  • 学习video对象的事件,了解事件的触发时机,触发顺序
  • 做一个视频播放器

video标签自带视频和制作的视频区别

  • video标签自带的视频播放器,控件不灵活,不能根据需求自定义更改控件
  • 各浏览器样式不统一

video标签的src属性

video标签是HTML5的新标签,video标签用于定义视频,可以载入视频,在页面上展示播放视频,并用播放 暂停 音量等等控件来控制视频

  • src:接收要播放的视频的url地址,这个url可以是本地ur,也可以是远程服务器的资源地址

  • controls属性:给video标签加上了这个属性,就会给用户展示播放音量等控件

  • autoplay属性:video标签设嚣该属性,就会自动播放(但是,必须同时设置muted,否则不会自动播放)

  • muted属性: video上出现了该属性,视频会被静音

  • loop属性:video标签出现该属性,在视频播放结束后会循环播放(默认情况下,视频播放完了就会就停止)

  • poster属性:设置视频摇放前显示图像(相当于视频封面,默认情况下,在视频未播放前,会显示视频的第一帧)

  • width属性:设置视频的宽(可同时设置高度)

首先,我们先来看下 video 最基础的用法:

  • 使用 src 属性
<video src="http://v2v.cc/~j/theora_testsuite/320x240.ogg" controls>
你的浏览器不支持 <code>video</code> 标签。
</video>
  • 使用 source 标签
<video controls>
  <source src="foo.ogg" type="video/ogg">
  <source src="foo.mp4" type="video/mp4">
  Your browser does not support the <code>video</code> element.
</video>

这是 MDN 关于 video 给出的基本用例。在这里我们简单介绍下两种方法的不同,src 只能赋予 video 一个播放地址,当浏览器不支持这种视频格式的解码时就会出现错误,导致视频播放失败。为了解决这个问题才有了 source 标签,利用多个 source 标签引入不同格式的视频,从上到下解析直到遇到看上述代码当浏览器不支持 ogg 格式时,浏览器会自动播放 foo.mp4。

本地视频文件

前端代码

在这里插入图片描述

播放效果

在这里插入图片描述

服务器视频文件

示例1
后端代码

在这里插入图片描述

@RestController
public class VideoController {

    @Autowired
    private HttpServletResponse response;

    @GetMapping("getVideo")
    public void getVideo(String videoName) throws Exception {

        System.out.println("请求过来了...");

        FileSystemResourceLoader fileResourceLoader = new FileSystemResourceLoader();
        Resource resource = fileResourceLoader.getResource("D:\\Projects\\vue-springboot\\src\\main\\resources\\video\\" + videoName);

        int available = resource.getInputStream().available();
        response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(available));

        ServletOutputStream outputStream = response.getOutputStream();
        StreamUtils.copy(resource.getInputStream(), outputStream);

        outputStream.flush();
        outputStream.close();

    }

}
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <video src="http://127.0.0.1:8085/getVideo?videoName=rocketmq.mp4" controls  poster="./poster2.jpg" width="400"></video>
    <span style="background-color: yellow;" >1</span>
</body>
</html>
播放效果

在这里插入图片描述

  • 虽然视频能播放,但是不能拖动视频播放进度条(这是个硬伤)

  • 一个视频发了好几个请求

  • 并且后台,时不时就报下面的错误

org.apache.catalina.connector.ClientAbortException: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
	at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:351)
	at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:776)
	at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:681)
	at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:386)
	at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:364)
	at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:96)
	at org.springframework.util.StreamUtils.copy(StreamUtils.java:143)
	at com.zzhua.video.VideoController.getVideo(VideoController.java:36)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException: 你的主机中的软件中止了一个已建立的连接。
	at sun.nio.ch.SocketDispatcher.write0(Native Method)
	at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
	at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
	at sun.nio.ch.IOUtil.write(IOUtil.java:65)
	at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
	at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:140)
	at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
	at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:152)
	at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1261)
	at org.apache.tomcat.util.net.SocketWrapperBase.doWrite(SocketWrapperBase.java:793)
	at org.apache.tomcat.util.net.SocketWrapperBase.writeBlocking(SocketWrapperBase.java:563)
	at org.apache.tomcat.util.net.SocketWrapperBase.write(SocketWrapperBase.java:501)
	at org.apache.coyote.http11.Http11OutputBuffer$SocketOutputBuffer.doWrite(Http11OutputBuffer.java:538)
	at org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:73)
	at org.apache.coyote.http11.Http11OutputBuffer.doWrite(Http11OutputBuffer.java:190)
	at org.apache.coyote.Response.doWrite(Response.java:601)
	at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:339)
	... 61 common frames omitted
示例2
后端代码
@Component
public class NonStaticResourceHttpRequestHandler extends ResourceHttpRequestHandler {
    // 定义视频路径
    public String filepath = "filepath";

    @Override
    protected Resource getResource(HttpServletRequest request) {
       // 获取视频路径对象
        final Path filePath = (Path) request.getAttribute(filepath);
        // 用 FileSystemResource 加载资源
        return new FileSystemResource(filePath);
    }

}
@RestController
@RequestMapping("video")
public class FileRestController {

 	@Autowired
    private NonStaticResourceHttpRequestHandler nonStaticResourceHttpRequestHandler;

    @GetMapping("/getVideo")
    public void getVideo(HttpServletRequest request, HttpServletResponse response) throws Exception {

        System.out.println("======================>");
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            System.out.println(headerName + ":" + request.getHeader(headerName));
        }
        System.out.println("<======================");

        //sourcePath 是获取编译后 resources 文件夹的绝对地址,获得的原始 sourcePath 以/开头,所以要用 substring(1) 去掉第一个字符/
        //realPath 即视频所在的完整地址
        String sourcePath = this.getClass().getClassLoader().getResource("").getPath().substring(1);
        String realPath = sourcePath + "video/" + request.getParameter("videoName");
        Path filePath = Paths.get(realPath);
        if (Files.exists(filePath)) {
        	// 利用 Files.probeContentType 获取文件类型
            String mimeType = Files.probeContentType(filePath);
            if (!StringUtils.isEmpty(mimeType)) {
            	// 设置 response
                response.setContentType(mimeType);
            }
            request.setAttribute(nonStaticResourceHttpRequestHandler.filepath, filePath);
            // 利用 ResourceHttpRequestHandler.handlerRequest() 实现返回视频流
            nonStaticResourceHttpRequestHandler.handleRequest(request, response);
        } else {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        }
    }
}
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <video src="http://127.0.0.1:8085/video/getVideo?videoName=rocketmq.mp4" controls  poster="./poster2.jpg" width="400"></video>
    <span style="background-color: yellow;" >1</span>
</body>
</html>
播放效果
  • 视频能够正常播放,并且能够拖动视频进度条,但是一拖动进度条,后台就报错(见下图)
  • 前端的video标签默认会分段请求,会携带Range请求头(见下图)

在这里插入图片描述

在这里插入图片描述

示例3

上面的分段请求,我感觉是不是哪里写的有问题,接下来使用一些springboot自带的分段资源处理,它没有报错,后面需看看源码是为什么?

但是通过下面的示例可以看出来

  • 前端video标签,它会自动分段请求
  • 后台需要根据前端的Range请求头,将指定分段的数据返回给前端
  • 后台没有示例1和示例2中的报错
  • 观察发送的请求头和响应头,发现前端发送的请求头里:Range: bytes=15269888-,它总是没有后面的这个值(只有开始的值),然后后台响应的是:Content-Range: bytes 15269888-119755709/119755710,也不知道他两是为啥这样交互?每次服务器都只有一个开始的位置,然后从这个开始的位置到文件的末尾,每次都这样写给浏览器的话,这不是有问题吗?
  • ResourceHttpRequestHandler 会交给 ResourceRegionHttpMessageConverter
  • 哦,整明白了,拖动进度跳的时候,这个源码也会报错,只是,我把这个使用异常处理器抓住了,并且打印出来了,而异常处理器没有抓住源码中ResourceHttpRequestHandler的异常而已。
后端配置

就是作为静态资源暴露出去

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
                .addMapping("/**")
                .maxAge(3600)
                .allowCredentials(true)
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .exposedHeaders("token","Authorization")
        ;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
      
        registry.addResourceHandler("/video/**")
                .addResourceLocations("file:/D:\\Projects\\vue-springboot\\src\\main\\resources\\video\\");

    }
}
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <video src="http://127.0.0.1:8085/video/rocketmq.mp4" controls  poster="./poster2.jpg" width="400"></video>
    <span style="background-color: yellow;" >1</span>
</body>
</html>
播放效果

在这里插入图片描述

video对象

video对象创建和获取

video对象的创建:就是创建一个video元素

const oVideo = document.createDocument( "video")

video对象的获取:就是获取video元素

const oVideo - document.getElementById('video')

video的属性

  • src属性:媒体文件的地址
  • duration属性:返回视频的总时长︰单位为秒(注意:需要监听到loadeddata事件发生后,才能获取)
  • currentTime属性:设置或返回视频的当前播放事件单位也是秒
  • volume属性:设置或返回视频的音量,0 ~ 1 0.1 0.5 1 最大值1 代表音量最大 最小0 代表静音
  • muted属性:是设置或返回视频是否静音,值为 true false,与volume属性不相关
  • ended属性:视频是否播放完
  • paused属性:设置或返回视频是否暂停,值为 true false
  • playbackRate属性:设置或返回播放速度,默认值为1,即正常速度播放。

video的方法

  • play():播放视频

  • pause():暂停播放

video的事件

  • loadstart: 媒体数据开始加载时 触发
  • loadeddata: 媒体数据加载完成
  • canplay: 当浏览器判定视频可以播放了
  • error: 视频加载发生错误
  • play: 当视频启动播放时触发
  • pause: 当视频被暂停时触发
  • timeupdate:播放中,当前播放位置发生改变时触发(视频在播放中时,会一直触发)
  • volumechange:当音量大小发生变化时触发

示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <video id="video" controls  poster="./poster.jpg" width="400"></video>
    <p>
        <button id="play">播放</button>
        <button id="pause">暂停</button>
    </p>
</body>
<script>
    let video = document.querySelector('#video')
    let playBtn = document.querySelector('#play')
    let pauseBtn = document.querySelector('#pause')

    video.src = 'http://127.0.0.1:8085/video/rocketmq.mp4'

    playBtn.onclick = () => {
        video.muted = true
        video.play()
    }

    pauseBtn.onclick = () => {
        video.pause()
    }

    video.addEventListener('loadstart',function(){
        console.log('开始加载...');
    })
    video.addEventListener('loadeddata',function(){
        console.log('加载完成...');
    })
    video.addEventListener('canplay',function(){
        console.log('视频可以播放了...');
    })
    video.addEventListener('play',function(){
        console.log('视频启动播放...',video.paused);
    })
    video.addEventListener('pause',function(){
        console.log('视频暂停播放...',video.paused);
    })
    video.addEventListener('timeupdate',function(){
        console.log('视频播放中',video.currentTime);
    })
    video.addEventListener('volumechange',function(){
        console.log('音量修改了',video.volume);
    })
    video.addEventListener('error',function(){
        console.log('视频加载出错了',video.volume);
    })
</script>
</html>

在这里插入图片描述

实现一个video播放器(空)

xgplayer分段播放mp4视频

安装依赖

npm i xgplayer-mp4@3.0.1 --save
npm i xgplayer@3.0.2 --save

App.vue

<template>
  <div id="mse"></div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

import Player from "xgplayer"
import Mp4Plugin from "xgplayer-mp4"
import "xgplayer/dist/index.min.css"

onMounted(() => {
  const player = new Player({
    url:'http://127.0.0.1:8085/video/getVideo?videoName=rocketmq.mp4',
    id:'mse',
    autoplay: true,
    width: 800,
    // height: window.innerHeight,
    plugins: [Mp4Plugin],
    mp4plugin: {
      maxBufferLength: 30,
      minBufferLength: 10,
      reqOptions: {
        mode: 'cors',
        method: 'POST',
        headers: { // 需要带的自定义请求头
          'x-test-header': 'rrrr'
        },
      }
      // ... 其他配置
    }
  })
  window.player = player
})

</script>

后台代码

NonStaticResourceHttpRequestHandler

因为发现xgplayer在发送分段请求的时候,请求分段视频资源的请求方法为POST请求,因此,需要添加POST请求方法的支持(原来这里只支持head和get)。

@Component
public class NonStaticResourceHttpRequestHandler extends ResourceHttpRequestHandler implements SmartInitializingSingleton {
    // 定义视频路径
    public String filepath = "filepath";

    @Override
    protected Resource getResource(HttpServletRequest request) {
    	// 获取视频路径对象
        final Path filePath = (Path) request.getAttribute(filepath);
        // 用 FileSystemResource 加载资源
        return new FileSystemResource(filePath);
    }

    @Override
    public void afterSingletonsInstantiated() {
        this.setSupportedMethods(HttpMethod.GET.name(),HttpMethod.POST.name(), HttpMethod.HEAD.name());

    }

}
FileRestController
@RestController
@RequestMapping("video")
public class FileRestController {

 	@Autowired
    private NonStaticResourceHttpRequestHandler nonStaticResourceHttpRequestHandler;

    @RequestMapping("/getVideo")
    public void getVideo(HttpServletRequest request, HttpServletResponse response) throws Exception {

        System.out.println("======================>");
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            System.out.println(headerName + ":" + request.getHeader(headerName));
        }
        System.out.println("<======================");

        //sourcePath 是获取编译后 resources 文件夹的绝对地址,获得的原始 sourcePath 以/开头,所以要用 substring(1) 去掉第一个字符/
        //realPath 即视频所在的完整地址
        String sourcePath = this.getClass().getClassLoader().getResource("").getPath().substring(1);
        String realPath = sourcePath + "video/" + request.getParameter("videoName");
        Path filePath = Paths.get(realPath);
        if (Files.exists(filePath)) {
        	// 利用 Files.probeContentType 获取文件类型
            String mimeType = Files.probeContentType(filePath);
            if (!StringUtils.isEmpty(mimeType)) {
            	// 设置 response
                response.setContentType(mimeType);
            }
            request.setAttribute(nonStaticResourceHttpRequestHandler.filepath, filePath);
            // 利用 ResourceHttpRequestHandler.handlerRequest() 实现返回视频流
            nonStaticResourceHttpRequestHandler.handleRequest(request, response);
        } else {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        }
    }
}

WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
                .addMapping("/**")
                .maxAge(36000)
                .allowCredentials(true)
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .exposedHeaders("token","Authorization")
        ;
    }

  
}

效果

前面,我们通过原始的video表情去请求后台,发现的问题:每次拖动请求头,它发出的请求的Range范围总是从指定的范围到文件末尾,每个请求都这样搞的话,后台就要不断的写。下面使用的xgplayer就可以请求指定部分的视频资源来播放。

  • 可以在下图中看到,在拖动视频进度条的时候,可以看到会有分段请求发出,并且使用了Range请求头请求指定部分的视频资源,后台将会根据此Range请求头将对应部分的视频数据发送给前端
  • 每次请求都携带了自定义请求头
  • 还要注意一点就是:使用xgplayer后,它还是会发一个http://127.0.0.1:8085/video/getVideo?videoName=rocketmq.mp4的get请求,但是后面的请求,都是用的分段请求去请求指定分段的视频部分了。
  • 还是会出现上面示例2的报错,但至少不是每次请求的Range都到文件末尾去,并且整个过程都能正常播放。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
后面都是连续的请求

在这里插入图片描述

报错原因

报错的原因找到了,参考:Java后端接口返回视频流,使用video组件播放视频,实现分段下载
tomcat原话:写操作IO异常几乎总是由于客户端主动关闭连接导致,所以直接吃掉异常打日志,比如使用video播放视频时经常会发送Range为0- 的范围只是为了获取视频大小,之后就中断连接了,那就是说这个报错是浏览器故意断开连接导致连接中断,而不是后台的原因。

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

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

相关文章

chatgpt赋能python:Python模块安装方法全解析

Python模块安装方法全解析 Python是一种功能强大的编程语言&#xff0c;拥有大量的开源库&#xff0c;这些库是在各种应用程序中使用的重要组件&#xff0c;它们能加速开发过程。不管你是初学者、中级者还是高级者&#xff0c;总会遇到需要安装第三方库的情况。但是安装库是一…

《Java并发编程实战》课程笔记(四)

互斥锁 原子性问题到底该如何解决呢&#xff1f; “同一时刻只有一个线程执行”这个条件非常重要&#xff0c;我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的&#xff0c;那么&#xff0c;无论是单核 CPU 还是多核 CPU&#xff0c;就都能保证原子性了。 锁模型 …

Python连接达梦数据库

python如果想连接达梦数据库&#xff0c;必须要安装dmPython。 简介&#xff1a;dmPython 是 DM 提供的依据 Python DB API version 2.0 中 API 使用规定而开发的数据库访问接口。dmPython 实现这些 API&#xff0c;使 Python 应用程序能够对 DM 数据库进行访问。 dmPython 通…

数据库服务器

数据库服务器&#xff0c;联系Web服务器与DBMS的中间件是负责处理所有的应用程序服务器&#xff0c;包括在web服务器和后台的应用程序或数据库之间的事务处理和数据访问。 基本信息 中文名 数据库服务器 外文名 database server 功能 数据库服务器建立在数据库系统基础上&a…

系统漏洞利用与提权

任务二&#xff1a;系统漏洞利用与提权 任务环境说明&#xff1a; 服务器场景&#xff1a;PYsystem0033 服务器场景操作系统&#xff1a;Ubuntu 服务器场景用户名:未知 密码&#xff1a;未知 1.使用nmap扫描靶机系统&#xff0c;将靶机开放的端口号按从小到大的顺序作为F…

解决Vmware上的kali找不到virtualbox上的靶机的问题

解决kali找不到靶场ip问题的完整方法 1.配置靶机2.配置kali的虚拟网络3.配置kali中的eth0网络 1.配置靶机 靶机部署在Virtualbox上对其进行网络配置&#xff0c;选择连接方式为仅主机&#xff08;Host-Only&#xff09;网络。 2.配置kali的虚拟网络 在编辑中选择虚拟网络配…

chatgpt赋能python:Python中浮点数的表示方法

Python中浮点数的表示方法 在Python中&#xff0c;浮点数是一种数字类型&#xff0c;用于表示带有小数点的数值。但是&#xff0c;由于计算机在表示浮点数时存在精度限制&#xff0c;因此需要特别注意。本文将介绍Python中浮点数的表示方法及其可能导致的错误。 Python中浮点…

陕西发布!陕西省重点实验室申报条件类别、认定程序要求

本文整理了陕西省重点实验室申报条件&#xff0c;认定材料等相关内容&#xff0c;感兴趣的朋友快跟小编一起来看看吧&#xff01; 一、总体思路 本次省重点实验室布局建设工作以填补我省优势学科领域下无省级及以上科学与工程研究类科技创新基地的空白为主,同时兼顾前沿、新兴、…

MySQL基础- 多表查询 和 事务

目录 多表查询多表关系多表查询概述多表查询的分类内连接外连接自连接联合查询union&#xff0c;union all子查询标量子查询列子查询行子查询表子查询 综合练习小结 事务事务简介事务的操作四大特性ACID并发事务问题事务的隔离级别小结 多表查询 之前的SQL语句里的DQL只能进行…

数字图像学笔记 —— 18. 图像抖动算法

文章目录 为什么需要图像抖动图像抖动算法实现的基本思路常见图像抖动算法实现Floyd-Steinberg 抖动算法Atkinson 抖动算法算法实现 为什么需要图像抖动 在数字图像中&#xff0c;为了表示数字图像的细节&#xff0c;像素的颜色深度信息最少也是8位&#xff0c;即 0 − 256 0…

Linux:centos:周期性计划任务管理《crontab》

crontab常用基础属性 -e 编辑计划任务 -l 查看计划任务 -r 删除计划任务 -u 指定用户的计划任务 首先创建一个名为test的用户名 crontab时间规定 格式&#xff1a;分钟 小时 日期 月份 星期 命令 分钟-- 0-59整数 小时 -- 0-23整数 日期 -- 1--31 整数 月份 -- 1-12 整数 星期…

C++ queue类成员介绍

目录 &#x1f914;queue模板介绍&#xff1a; &#x1f914;queue特点&#xff1a; &#x1f914;queue内存图解&#xff1a; &#x1f914; queue的成员函数 &#x1f50d;queue构造函数&#xff1a; &#x1f50d;queue赋值函数&#xff1a; &#x1f50d;queue判断函…

黑马Redis视频教程实战篇(三)

目录 一、优惠券秒杀 1.1 全局唯一ID 1.2 Redis实现全局唯一ID 1.3 添加优惠卷 1.4 实现秒杀下单 1.5 库存超卖问题分析 1.6 代码实现乐观锁解决超卖问题 1.7 优惠券秒杀-一人一单 1.8 集群环境下的并发问题 二、分布式锁 2.1 基本原理和实现方式对比 2.2 Redis分布…

js常见面试笔试题

一.js实现距离最近的回文数 给定一个整数 n &#xff0c;你需要找到与它最近的回文数&#xff08;不包括自身&#xff09;。 “最近的”定义为两个整数差的绝对值最小。 示例 1: 输入: "123" 输出: "121" function findNearestPalindrome…

Jenkins+Python自动化测试之持续集成详细教程

前言 今天呢笔者想和大家来聊聊JenkinsPython自动化测试持续集成&#xff0c;废话呢就不多说了哟咱们直接进入主题哟。 一、Jenkins安装 ​ Jenkins是一个开源的软件项目&#xff0c;是基于java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供…

I.MX RT1170加密启动详解(2):Authenticated HAB认证原理

文章目录 1 基础2 使能过程3 Boot flow 1 基础 HAB认证是基于RSA或ECDSA算法的公钥密码学&#xff0c;它用一系列的私钥对image进行加密&#xff0c;然后BootROM在上电后用对应的公钥验证加密的镜像是否被修改。这个密钥结构就是PKI(Public Key Infrastructure)树 (1)normal …

chatgpt赋能python:Python中画笔颜色的函数介绍

Python中画笔颜色的函数介绍 在Python中&#xff0c;我们可以使用turtle模块来绘制图形&#xff0c;其中画笔颜色是非常重要的一部分。画笔颜色可以决定图形的风格和色调&#xff0c;是图形表现的关键因素之一。Python中提供了几种方法来设置画笔颜色。 1. 设置画笔颜色的函数…

ENU、EPSG坐标系科普(三维重建)

ENU和EPSG实际上代表了两个不同的概念&#xff0c;这两者并不是直接对比的。 1. ENU坐标系&#xff1a;ENU坐标系是一种本地切面坐标系&#xff0c;用于表示与地理位置相关的空间数据。在ENU坐标系中&#xff0c;E代表东&#xff08;East&#xff09;&#xff0c;N代表北&…

RabbittMQ快速实战和集群架构

介绍对比: Kafka&#xff1a;topic不能太多&#xff0c;一个缺点&#xff0c;影响Kafka的吞吐量 集群搭建&#xff1a;【单个也是一个集群&#xff08;特殊&#xff09;】 集群搭建&#xff1a;https://blog.csdn.net/p393975269/article/details/129830252 1&#xff1a;默认…

【明解STM32】中断系统理论基础知识篇之中断寄存器功能原理

目录 一、前言 二、寄存器概述 三、NVIC寄存器组 四、SCB寄存器组 五、中断屏蔽寄存器组 六、总结 一、前言 在之前的STM32的中断系统理论基础知识之基本原理及NVIC中&#xff0c;分别中断的基本原理&#xff0c;中断的管理机制和中断的处理流程进行了较为详细的论述&…