当前位置: 首页
前端开发
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文档版权声明结构与基础脚手架代码质量解析

版权声明需用语义化``标签,©符号使用`©`实体;年份通过服务端、构建或JS分层兜底;`rel= "license "`仅指向真实许可证文件才有效。

时间:2026-07-07 07:02
基于HTML模板的组件化全栈交互方案

基于HTML模板的组件化全栈交互方案

纯HTML模板仅作静态结构,全栈交互需后端注入数据、前端JS激活模板及用户事件触发双向链路。``标签惰性,不执行脚本或加载资源,需通过克隆填充数据并手动绑定事件实现交互,父子传值依赖JS对象与自定义事件。

时间:2026-07-07 07:02
Bootstrap修改Badge徽章边框为圆点样式的方法

Bootstrap修改Badge徽章边框为圆点样式的方法

将BootstrapBadge改为圆点:自定义类设置等宽高、border-radius:50%、padding:0、font-size:0;Bootstrap5 2+需加display:inline-block;注意垂直对齐,避免min-width干扰,背景色用background-color更可控。

时间:2026-07-07 07:02
Bootstrap响应式多栏Footer社交图标排版实现

Bootstrap响应式多栏Footer社交图标排版实现

使用Bootstrap5的grid系统实现响应式多栏Footer,按断点设置列宽;以FontAwesome处理社交图标,避免使用img;利用list-unstyled组织链接列表;借助flex-column容器配合mt-auto使版权行自动贴底,避免fixed-bottom固定定位的弊端。

时间:2026-07-07 07:02
如何用Shadow DOM隔离组件内外事件分发

如何用Shadow DOM隔离组件内外事件分发

ShadowDOM内部事件默认不穿透影子边界,需显式设置composed:true才能被外部监听。此时event target指向宿主元素,真实触发源需通过event composedPath()[0]获取。原生事件手动派发时也必须声明composed:true。应避免在宿主上依赖event target做逻辑分支,建议在shadowroot内绑定事件或使用

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