当前位置: 首页
前端开发
Html5通过数据流方式播放视频的实现

Html5通过数据流方式播放视频的实现

热心网友 时间:2026-04-28
转载

跨平台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 -/"。其中 startend 必须与请求头中的 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请求头的兼容性处理。搞定这一点,跨平台视频播放的兼容性问题也就迎刃而解了。

来源:https://www.jb51.net/html5/773384.html

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
禁止HTML页面滚动的操作方法

禁止HTML页面滚动的操作方法

在前端开发中,禁止HTML页面滚动通常涉及到对CSS样式或Ja vaScript的使用。以下是一些常见的方法: 1 使用CSS的overflow属性 最直接的思路,是通过设置HTML或body元素的 overflow 属性为 hidden 来禁止滚动。这么一来,任何超出视口的内容都会被隐藏,滚动的

时间:2026-04-28 17:41
uni-app怎么做类似于淘宝的物流时间轴 uni-app步骤条组件定制实现【实战】

uni-app怎么做类似于淘宝的物流时间轴 uni-app步骤条组件定制实现【实战】

uni-app 里用 u-steps 实现物流时间轴,为什么总对不上实际节点? 问题根源很明确:你把一个设计用于「线性流程」的步骤条,硬生生套在了「异步事件流」的物流场景上。这就像试图用整齐划一的阅兵方阵,去展示一场状况百出的越野赛跑。 淘宝的物流时间轴,本质上是一系列独立事件的集合。每个节点都有自

时间:2026-04-28 17:41
如何用 JavaScript 实现用户输入五个姓名并按顺序显示在网页上

如何用 JavaScript 实现用户输入五个姓名并按顺序显示在网页上

如何用 prompt() 收集五个姓名并动态渲染到页面?一份实战指南 在前端入门的实践环节里,有一个“经典关卡”:如何从用户那里收集一组数据,存起来,再漂亮地展示出来?听起来基础,但很多新手在第一关就卡住了——变量作用域混乱、DOM元素找不到、代码逻辑“断层”,这些都是常见问题。 今天,我们就以“收

时间:2026-04-28 17:41
关于html选择框创建占位符的问题

关于html选择框创建占位符的问题

为HTML选择框(Select)添加“占位符”的几种思路 在表单设计中,为文本输入框设置一个灰色的提示占位符(placeholder)早已是标准操作,用户体验非常好。但轮到下拉选择框(Select)时,不少开发者会发现事情没那么简单——HTML原生并没有提供类似的placeholder属性。 最直观

时间:2026-04-28 17:40
uni-app怎么隐藏导航栏 uni-app自定义顶部导航栏配置【详解】

uni-app怎么隐藏导航栏 uni-app自定义顶部导航栏配置【详解】

uni-app导航栏隐藏的真相:一份跨端开发的避坑指南 先直接说结论,这也是很多人试错过后的经验:na vigationBarHidden: true 确实是写法最简单、跨端最稳妥的隐藏方式,但它的生效范围仅限于小程序和H5。想在APP端真正移除原生导航栏?那必须祭出组合拳:na vigationS

时间:2026-04-28 17:40
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜
热门教程
更多
  • 游戏攻略
  • 安卓教程
  • 苹果教程
  • 电脑教程