当前位置 博文首页 > Html5通过数据流方式播放视频的实现

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

    作者:peterwanghao 时间:2021-04-28 12:02

    本文介绍如何通过H5页面通过数据流的方式播放服务端的视频文件,可以兼容PC、Android和IOS环境。

    H5页面可以通过<video> 标签来播放视频。一般的方式如下:

    <!DOCTYPE HTML>
    <html>
    <body>
    
    <video src="/i/movie.mp4" controls="controls">
    your browser does not support the video tag
    </video>
    
    </body>
    </html>

    src中指定了要播放的视频的URL,为具体的视频文件路径。当将访问请求变为getVideo.do?fileId=xxx 这种形式,服务端返回字节流的时候后端实现需要一些更改。

    一般的方式是读本地文件然后写到response中,代码实现如下:

    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目前获取视频的时候请求头会带一个与断点续传有关的信息。对于ios来说,他不是一次性请求全部文件的,一般首先会请求0-1字节,这个会写在request header的"range"字段中:range:‘bytes=0-1’。
    而服务端必须满足range的要求:解析range字段,然后按照range字段的要求返回对应的数据。

    在响应头中response header至少要包含三个字段:

    • Content-Type:明确指定视频格式,有"video/mp4", “video/ogg”, "video/mov"等等。
    • Content-Range:格式是 “bytes <start>-<end>/<total>”,其中start和end必需对应request header里的range字段,total是文件总大小。
    • Content-Length:返回的二进制长度。

    断点续传实现如下:

    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 = javax.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页面:

    <!DOCTYPE HTML>
    <html>
    <body>
    
    
    <video width="100%" height="200" rel="preload" x5-video-player-type="h5" playsinline="true" webkit-playsinline="true" controls="controls">
    <source src="http://127.0.0.1:8080/XXX/getVideo.do?fileId=16" type="video/mp4">
    </video>
    
    </script>
    </body>
    </html>
    

    通过上述断点续传方式H5可正常播放视频数据流,并且支持各种平台。

    js
    下一篇:没有了