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

文章目录

    • 学习链接
    • 目标
      • 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 {@Autowiredprivate 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";@Overrideprotected Resource getResource(HttpServletRequest request) {// 获取视频路径对象final Path filePath = (Path) request.getAttribute(filepath);// 用 FileSystemResource 加载资源return new FileSystemResource(filePath);}}
@RestController
@RequestMapping("video")
public class FileRestController {@Autowiredprivate 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)) {// 设置 responseresponse.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 {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").maxAge(3600).allowCredentials(true).allowedOrigins("*").allowedMethods("*").allowedHeaders("*").exposedHeaders("token","Authorization");}@Overridepublic 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 = truevideo.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";@Overrideprotected Resource getResource(HttpServletRequest request) {// 获取视频路径对象final Path filePath = (Path) request.getAttribute(filepath);// 用 FileSystemResource 加载资源return new FileSystemResource(filePath);}@Overridepublic void afterSingletonsInstantiated() {this.setSupportedMethods(HttpMethod.GET.name(),HttpMethod.POST.name(), HttpMethod.HEAD.name());}}
FileRestController
@RestController
@RequestMapping("video")
public class FileRestController {@Autowiredprivate 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)) {// 设置 responseresponse.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 {@Overridepublic 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://xiahunao.cn/news/254215.html

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

相关文章

spring cloud Alibaba之Nacos Discovery--服务治理 (二)

接着上一篇文章 搭建的微服务环境, 实现nacos 注册中心实战操作案例 一. 服务治理介绍 先来思考一个问题 通过上一章的操作&#xff0c;我们已经可以实现微服务之间的调用。但是我们把服务提供者的网络地址 &#xff08;ip&#xff0c;端口&#xff09;等硬编码到了代码中&a…

PHPMySQL基础(五):模拟登录后跳转+会话存储功能实现

PHP&MySQL基础&#xff08;一&#xff09;:创建数据库并通过PHP进行连接_长风沛雨的博客-CSDN博客 PHP&MySQL基础&#xff08;二&#xff09;:通过PHP对MySQL进行增、删、改、查_长风沛雨的博客-CSDN博客 PHP&MySQL基础&#xff08;三&#xff09;:处理查询SQL返…

excel用条件格式设置隔行变色

1、选中要设置隔行变色的区域 2、点击条件格式—>新建规则—>使用公式确定要设置格式额单元格 3、输入公式 mod(row(),2) 0&#xff0c;点击格式按钮&#xff0c;选择填充的颜色&#xff0c;点击确定。如下图所示&#xff1a; 4、在新建格式规则窗口中点击确定&#x…

Web|设置隔行变色的单元格

问题 表格在日常生活中使用的非常的多,比如excel就是专门用来创建表格的工具,表格就是用来表示一些格式化的数据的,比如:课程表、银行对账单。在网页中也可以来创建出不同的表格。在HTML中,使用table标签来创建一个表格,在table标签中使用tr来表示表格中的一行,有几行就…

Excel小技巧,隔行变色,多行变色

Excel小技巧&#xff0c;隔行变色&#xff0c;多行变色&#xff0c;间隔色&#xff0c;关键在于公式&#xff1a; mod(row(),x)y 其中x指期望的每隔&#xff08;x-1&#xff09;行变色&#xff0c;y等于每隔的第(y1)行&#xff1b; 比如&#xff1a;mod(row(),2)0 用来设置每…

[蓝桥杯/java/算法]A——隔行变色

&#x1f9d1;‍&#x1f393;个人介绍&#xff1a;大二软件生&#xff0c;现学JAVA、Linux、MySQL、算法 &#x1f4bb;博客主页&#xff1a;渡过晚枫渡过晚枫 &#x1f453;系列专栏&#xff1a;[编程神域 C语言]&#xff0c;[java/初学者]&#xff0c;[蓝桥杯] &#x1f4d6…

前端 | 设置隔行变色的单元格

欢迎点击「算法与编程之美」↑关注我们&#xff01; 本文首发于微信公众号&#xff1a;"算法与编程之美"&#xff0c;欢迎关注&#xff0c;及时了解更多此系列文章。 问题描述 表格在日常生活中使用的非常的多&#xff0c;比如excel就是专门用来创建表格的工具&#…

java中设置基偶隔行换色_excel2010如何设置隔行变色

在使用excel的过程中&#xff0c;一定见过别人编排的excel文档&#xff0c;隔行添加单元格背景颜色&#xff0c;也就是通常所说的隔行变色效果。下面就跟学习啦小编一起看看吧。 excel2010设置隔行变色的步骤 第一步&#xff0c;启动Excel2010&#xff0c;打开相应的工作簿文档…

python如何实现隔行换色_Excel2010如何实现隔行设置背景色

当Excel表格中的数据很多&#xff0c;很有可能会出现看错行的现象&#xff0c;为防止看错行&#xff0c;可以隔行设置背景色。可以通过套用表格格式或者条件格式来达到隔行变色的效果 方法一&#xff1a;通过套用表格格式 1、选中要处理的单元格或者列。 说明&#xff1a;选择列…

Excel 隔行变色

2019独角兽企业重金招聘Python工程师标准>>> 在日常办公Excel操作中&#xff0c;经过遇到需要将表格不同行或者不同列之间标记为不同的颜色来区分。如果只有少量可以直接进行操作&#xff0c;当数量较大时再一行一行或者一列一列操作就显得不够专业了。那么该如何快…

【测试基础01】

本期参考文献: 链接 一、安装mysql 1、安装mysql可以参考链接: 文章 2、安装mysql与python的工具 进行校验&#xff0c;查看是否安装成功 二、创建库 mycursor mydb.cursor() mycursor.execute("CREATE DATABASE ck") 执行语句创建库在mysql库里可以看到…

微信如何群删好友 微信群删好友的方法教程

微信作为如今的日常通讯软件&#xff0c;可以说是融入了生活中的方方面面&#xff0c;而有些人在使用过程中添加了很多好友&#xff0c;但对于一些不怎么熟悉的好友可以选择删除&#xff0c;不过要是好友太多一个一个删也很麻烦&#xff0c;那么&#xff0c;微信如何群删好友?…

终于!微信正式版群聊可折叠:满屏群信息成为历史

9月26日消息&#xff0c;微信今日发布了iOS端的8.0.14版本&#xff0c;微信群聊终于可折叠了。 小雷使用iPadOS上的微信进行了测试&#xff0c;在群聊界面的菜单中&#xff0c;会出现一个“折叠该群聊”的选项&#xff0c;勾选即可折叠该群聊。折叠后&#xff0c;“折叠的群聊”…

微信怎么找群聊?找回微信群聊只需要这样…

如今社会中的日常生活已经离不开微信&#xff0c;微信是社交界的一股清流&#xff0c;聊天、视频、语音等等功能&#xff0c;而且支付功能的用途范围在国内也甚是广泛。微信怎么找群聊&#xff1f;是的&#xff0c;当多个好友之间相互认识&#xff0c;或者都有一个共同点的时候…

【微信机器人】可做自动回复,自动接收转账,群聊机器人。

前言&#xff1a; 目前市面上的微信机器人项目少之又少&#xff0c;并且大多数的不可用。比如用抓取网页微信接口&#xff0c;但大多数人的账号没有使用网页微信的权限。又或者价格昂贵&#xff0c;如使用微信pad协议。于是便开发一个通过Hook微信的DLL文件&#xff0c;修改其…

半群与群

半群与独异点 半群与独异点的定义 定义11.1 (1)设V<S,>是代数系统,为二元运算,如果运算是可结合的,则称V为半群。    (2)设V<S,>是半群,若e∈S是关于运算的单位元,则称V是含幺半群,也叫做独异点。有时也将独异点V记作V<S,,e>. 例11.1 (1)<Z,&…

JS中手撕防抖函数和节流函数

1.防抖函数 1.1定义 说明&#xff1a;在一定时间内&#xff0c;频繁执行事件&#xff0c;只执行最后一次函数。(英雄联盟回城) 1.2步骤&#xff1a; 声明定时器函数判断是否有定时器函数&#xff0c;如果有定时器函数的话就清除定时器。。如果没有定时器函数的话&#xff0…

微服务Spring Cloud 02------使用Eureka实现注册中心(1)

1.Eureka简介 Eureka是Spring Cloud中的一个负责服务注册与发现的组件。遵循着CAP理论中的A(可用性)和P(分区容错性)。 Eureka是Netflix中的一个开源框架。它和 Zookeeper、Consul一样&#xff0c;都是用于服务注册管理的&#xff0c;同样&#xff0c;Spring-Cloud 还集成了Zo…

中台分类

大家好&#xff0c;我是易安&#xff0c;之前我们谈到过中台的概念&#xff0c;以及如何落地中台。今天我就带你一起看一看&#xff0c;行业常见的中台分类。 业务中台与数据中台 业务中台 业务这个词&#xff0c;其实是有些宽泛的&#xff0c;我听到很多人口中说的业务都不是…

NeRF-SLAM代码记录

前言 没运行成功,尤其是编译gtsam部分,每一步都有错,又是讨厌c++第一天。 这一行编译到92% 就会报错 python/CMakeFiles/gtsam_py.dir/build.make:250: recipe for target python/CMakeFiles/gtsam_py.dir/linear.cpp.o failed make[2