Html5通过数据流方式播放视频的实现
跨平台H5视频流播放:打通PC、Android与iOS的全兼容方案
在开发需要兼容PC、Android和iOS的H5应用时,通过数据流播放服务端视频文件是个常见需求。这事听起来简单,但实际落地,尤其是要让所有平台都“买账”,还真得花点心思。今天,咱们就来捋一捋其中的关键。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
基础方案:HTML5 Video标签的常规用法
说到H5播放视频,大家第一反应肯定是
这里的 src 属性直接指向一个静态的视频文件路径。不过,实际业务中,视频文件往往需要通过后端接口动态获取,比如请求格式变成 getVideo.do?fileId=xxx 。这时候,服务端的处理逻辑就需要相应调整了。
服务端基础实现:文件读取与流式输出
最常见的后端实现,是读取本地文件,然后将字节流写入HTTP响应。代码大概长这样:
public void downFile(File downloadFile,
HttpServletResponse response,
HttpServletRequest request) throws Exception {
response.reset();
response.setContentType("video/mp4;charset=UTF-8");
InputStream in = null;
ServletOutputStream out = null;
try {
out = response.getOutputStream();
in = new FileInputStream(downloadFile);
if(in !=null){
byte[] b = new byte[1024];
int i = 0;
while((i = in.read(b)) > 0){
out.write(b, 0, i);
}
out.flush();
in.close();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(in != null) {
try { in.close(); } catch (IOException e) { }
in = null;
}
if(out != null) {
try { out.close(); } catch (IOException e) { }
out = null;
}
}
}
这个方法在PC浏览器和大部分Android手机上都工作良好,问题出在iOS的Safari浏览器上——视频很可能无法播放。问题根源在于,iOS Safari的视频请求机制有点特殊。
iOS的“特殊要求”:Range请求与断点续传
原来,iOS Safari在请求视频时,默认会启用一种类似“断点续传”的机制。它并非一次性请求整个文件,而是分块请求。具体表现是,在请求头(Request Header)里会携带一个Range字段,比如第一次请求可能是:Range: ‘bytes=0-1’。
这就要求服务端必须能够识别并正确处理这个Range头部:解析其字段值,然后精确返回所请求的字节范围数据。
相应地,在响应头(Response Header)里,我们至少要设置好三个关键字段:
- Content-Type:明确指定视频格式,例如
"video/mp4","video/ogg","video/mov"等。 - Content-Range:格式必须为
"bytes。其中- / " start和end必须与请求头中的Range字段对应,total则是文件的总大小。 - Content-Length:本次响应返回的二进制数据长度。
服务端升级:支持Range请求的完整实现
基于以上分析,我们需要将后端的下载方法升级为支持断点续传的版本。下面是一个详细的Ja va实现示例:
public void downRangeFile(File downloadFile,
HttpServletResponse response,
HttpServletRequest request) throws Exception {
if (!downloadFile.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
long fileLength = downloadFile.length();// 记录文件大小
long pastLength = 0;// 记录已下载文件大小
int rangeSwitch = 0;// 0:从头开始的全文下载;1:从某字节开始的下载(bytes=27000-);2:从某字节开始到某字节结束的下载(bytes=27000-39000)
long contentLength = 0;// 客户端请求的字节总量
String rangeBytes = "";// 记录客户端传来的形如“bytes=27000-”或者“bytes=27000-39000”的内容
RandomAccessFile raf = null;// 负责读取数据
OutputStream os = null;// 写出数据
OutputStream out = null;// 缓冲
int bsize = 1024;// 缓冲区大小
byte b[] = new byte[bsize];// 暂存容器
String range = request.getHeader("Range");
int responseStatus = 206;
if (range != null && range.trim().length() > 0 && !"null".equals(range)) {// 客户端请求的下载的文件块的开始字节
responseStatus = ja vax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT;
System.out.println("request.getHeader(\"Range\")=" + range);
rangeBytes = range.replaceAll("bytes=", "");
if (rangeBytes.endsWith("-")) {
rangeSwitch = 1;
rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));
pastLength = Long.parseLong(rangeBytes.trim());
contentLength = fileLength - pastLength;
} else {
rangeSwitch = 2;
String temp0 = rangeBytes.substring(0, rangeBytes.indexOf('-'));
String temp2 = rangeBytes.substring(rangeBytes.indexOf('-') + 1, rangeBytes.length());
pastLength = Long.parseLong(temp0.trim());
}
} else {
contentLength = fileLength;// 客户端要求全文下载
}
// 清除首部的空白行
response.reset();
// 告诉客户端允许断点续传多线程连接下载,响应的格式是:Accept-Ranges: bytes
response.setHeader("Accept-Ranges", "bytes");
// 如果是第一次下,还没有断点续传,状态是默认的 200,无需显式设置;响应的格式是:HTTP/1.1
if (rangeSwitch != 0) {
response.setStatus(responseStatus);
// 不是从最开始下载,断点下载响应号为206
// 响应的格式是:
// Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小]
switch (rangeSwitch) {
case 1: {
String contentRange = new StringBuffer("bytes ")
.append(new Long(pastLength).toString()).append("-")
.append(new Long(fileLength - 1).toString())
.append("/").append(new Long(fileLength).toString())
.toString();
response.setHeader("Content-Range", contentRange);
break;
}
case 2: {
String contentRange = range.replace("=", " ")
+ "/"
+ new Long(fileLength).toString();
response.setHeader("Content-Range", contentRange);
break;
}
default: {
break;
}
}
} else {
String contentRange = new StringBuffer("bytes ").append("0-")
.append(fileLength - 1).append("/").append(fileLength)
.toString();
response.setHeader("Content-Range", contentRange);
}
try {
response.setContentType("video/mp4;charset=UTF-8");
response.setHeader("Content-Length", String.valueOf(contentLength));
os = response.getOutputStream();
out = new BufferedOutputStream(os);
raf = new RandomAccessFile(downloadFile, "r");
try {
long outLength = 0;// 实际输出字节数
switch (rangeSwitch) {
case 0: {
}
case 1: {
raf.seek(pastLength);
int n = 0;
while ((n = raf.read(b)) != -1) {
out.write(b, 0, n);
outLength += n;
}
break;
}
case 2: {
raf.seek(pastLength);
int n = 0;
long readLength = 0;// 记录已读字节数
while (readLength <= contentLength - bsize) {// 大部分字节在这里读取
n = raf.read(b);
readLength += n;
out.write(b, 0, n);
outLength += n;
}
if (readLength <= contentLength) {// 余下的不足 1024 个字节在这里读取
n = raf.read(b, 0, (int) (contentLength - readLength));
out.write(b, 0, n);
outLength += n;
}
break;
}
default: {
break;
}
}
System.out.println("Content-Length为:" + contentLength + ";实际输出字节数:" + outLength);
out.flush();
} catch (IOException ie) {
// ignore
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
H5前端页面适配
前端页面基本无需大改,主要是确保标签的src指向我们新的支持断点续传的接口。为了更好的移动端体验,可以加上一些优化属性:
至此,一套完整的、兼容PC、Android和iOS的H5视频流播放方案就搭建完成了。核心就在于后端对HTTP Range请求头的兼容性处理。搞定这一点,跨平台视频播放的兼容性问题也就迎刃而解了。
相关攻略
跨平台H5视频流播放:打通PC、Android与iOS的全兼容方案 在开发需要兼容PC、Android和iOS的H5应用时,通过数据流播放服务端视频文件是个常见需求。这事听起来简单,但实际落地,尤其是要让所有平台都“买账”,还真得花点心思。今天,咱们就来捋一捋其中的关键。 基础方案:HTML5 Vi
解决方案: 很多朋友在初次使用HTML5的localStorage时,都会遇到一个经典的“坑”:直接存进去一个Ja vaScript对象,取出来却变成了一串看不懂的字符。这不,上面这段代码就遇到了这个问题,明明存了个对象,取出来却成了“[object Object]”这个字符串。 如果你去翻看App
如何在HTML5中利用Na vigationPreload技术提升ServiceWorker启动性能 先澄清一个常见的误解:Na vigation Preload 的核心目标,并非直接“加快 Service Worker 的启动速度”。它的精妙之处在于,当浏览器在启动 Service Worker
前端开发就业方向详解:你适合哪条路? 互联网技术这张棋盘上,棋局早已今非昔比。如果你还停留在“前端就是切图”的老印象里,那可就跟不上版本了。今天的现代前端工程师,手上握着的是 HTML、CSS、Ja vaScript 这三板斧,脑子里转的却是工程化、组件化这套组合拳。技术栈越来越宽,职业路径也越来越
在B站开始更新《前端开发轻松上手》系列视频 是时候来点新玩意儿了。接下来,我们会依据一个清晰且久经考验的路径——没错,就是 HTML、CSS 再到 Ja vaScript 这个经典三步曲——来开启一段前端开发之旅。目标很简单:让你能真正轻松上手,把那些看似复杂的代码,变成手中游刃有余的工具。 光讲理
热门专题
热门推荐
MySQL视图自增主键映射与逻辑主键生成方案详解 在数据库设计与优化实践中,视图(View)是简化复杂查询、封装业务逻辑的强大工具。然而,许多开发者在操作视图时,常希望实现类似数据表的自动主键生成功能,这在实际应用中却面临诸多限制。本文将深入解析MySQL视图与自增主键的关系,并提供切实可行的逻辑主
MySQL启动时默认字符集没生效?检查my cnf的加载顺序和位置 先明确一个关键点:MySQL启动时,并不会漫无目的地去读取所有可能的配置文件。它有一套固定的、按优先级排列的查找路径(通常是 etc my cnf、 etc mysql my cnf,最后才是 ~ my cnf),并且找到第一个
基本医疗保险的“双账户”模式:统筹与个人如何分工? 说起咱们的基本医疗保险,它的运作核心可以概括为“社会统筹与个人账户相结合”。简单来说,整个医保基金就像一个大池子,但这个池子被清晰地划分为两个部分:一个是大家共用的“统筹基金”,另一个则是属于参保人自己的“个人账户”。 那么,钱是怎么分别流入这两个
TYPE IS RECORD 语法详解与核心应用指南 在PL SQL数据库编程中,TYPE IS RECORD是定义自定义复合数据类型的关键工具。其标准语法结构为:TYPE 类型名 IS RECORD (字段名 数据类型 [DEFAULT 默认值] [NOT NULL]);。通过该语法,开发者可以灵
在定点医疗机构的选择上,政策其实给参保人留出了不小的灵活空间。获得定点资格的专科和中医医疗机构,会自动成为统筹区内所有参保人的可选范围,这为大家获取特色医疗服务提供了基础保障。 在此之外,每位参保人还能根据自身需要,再额外挑选3到5家不同层次的医疗机构。比如,你可以选择一家综合三甲医院应对复杂病情,





