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请求头的兼容性处理。搞定这一点,跨平台视频播放的兼容性问题也就迎刃而解了。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
禁止HTML页面滚动的操作方法
在前端开发中,禁止HTML页面滚动通常涉及到对CSS样式或Ja vaScript的使用。以下是一些常见的方法: 1 使用CSS的overflow属性 最直接的思路,是通过设置HTML或body元素的 overflow 属性为 hidden 来禁止滚动。这么一来,任何超出视口的内容都会被隐藏,滚动的
uni-app怎么做类似于淘宝的物流时间轴 uni-app步骤条组件定制实现【实战】
uni-app 里用 u-steps 实现物流时间轴,为什么总对不上实际节点? 问题根源很明确:你把一个设计用于「线性流程」的步骤条,硬生生套在了「异步事件流」的物流场景上。这就像试图用整齐划一的阅兵方阵,去展示一场状况百出的越野赛跑。 淘宝的物流时间轴,本质上是一系列独立事件的集合。每个节点都有自
如何用 JavaScript 实现用户输入五个姓名并按顺序显示在网页上
如何用 prompt() 收集五个姓名并动态渲染到页面?一份实战指南 在前端入门的实践环节里,有一个“经典关卡”:如何从用户那里收集一组数据,存起来,再漂亮地展示出来?听起来基础,但很多新手在第一关就卡住了——变量作用域混乱、DOM元素找不到、代码逻辑“断层”,这些都是常见问题。 今天,我们就以“收
关于html选择框创建占位符的问题
为HTML选择框(Select)添加“占位符”的几种思路 在表单设计中,为文本输入框设置一个灰色的提示占位符(placeholder)早已是标准操作,用户体验非常好。但轮到下拉选择框(Select)时,不少开发者会发现事情没那么简单——HTML原生并没有提供类似的placeholder属性。 最直观
uni-app怎么隐藏导航栏 uni-app自定义顶部导航栏配置【详解】
uni-app导航栏隐藏的真相:一份跨端开发的避坑指南 先直接说结论,这也是很多人试错过后的经验:na vigationBarHidden: true 确实是写法最简单、跨端最稳妥的隐藏方式,但它的生效范围仅限于小程序和H5。想在APP端真正移除原生导航栏?那必须祭出组合拳:na vigationS
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

