You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用HTML5 video标签+JavaBean+Servlet播放视频遇异常求助

问题排查与解决方案

先梳理下你的场景:你在JSF的.xhtml页面用HTML5 <video>标签,结合JavaBean和Servlet播放服务器端视频,但遇到了三个错误。我来逐个分析原因并给出修复方案:


你的代码片段

页面视频标签

<video controls="controls" style="margin-top: 60px;width: 70%;height: 80vh"> 
    <source src="/SPortal/VideoReader" type="video/mp4" /> 
</video>

JavaBean代码

@PostConstruct 
public void prepeareMethod() { 
    //check the type of learning object 
    try { 
        LearningObject lo = dao.getLearningObjByID(reqId); 
        String learnObjType = lo.getLearningObjTyId().getLearningObjectType(); 
        initialFile = new File(lo.getDirPath() + "\\" + lo.getFileName()); 
        InputStream targetStream = new FileInputStream(initialFile); 
        FacesContext facesContext = FacesContext.getCurrentInstance(); 
        HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(false); 
        session.setAttribute("loBytesArray", IOUtils.toByteArray(targetStream)); 
    } catch (Exception e) { 
        e.printStackTrace(); 
    } 
}

Servlet代码

@Override 
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
    byte[] pdfBytesArray = (byte[]) request.getSession().getAttribute("loBytesArray"); 
    request.getSession().removeAttribute("loBytesArray"); 
    response.setContentType("video/mp4"); 
    response.setContentLength(pdfBytesArray.length); 
    response.getOutputStream().write(pdfBytesArray); 
}

错误逐个分析与修复

1. Warning: Setting non-serializable attribute value into ViewMap: (key: lessonLearnBN, value class: com.beans.LessonLearnBN)

原因

JSF的ViewMap(视图范围的存储容器)要求存入的对象必须实现java.io.Serializable接口,你的LessonLearnBN类没有实现这个接口,导致JSF在尝试将其存入ViewMap时抛出警告。

修复方案

修改LessonLearnBN类,让它实现Serializable接口,建议同时添加序列化UID(可选但推荐,避免序列化版本冲突):

public class LessonLearnBN implements Serializable {
    // 你的类原有代码
    
    // 添加序列化UID
    private static final long serialVersionUID = 1L;
}

2. java.io.IOException: Connection closed / An established connection was aborted by the software in your host machine

原因

这个错误本质是浏览器(客户端)提前断开了与Servlet的连接,主要有两个核心诱因:

  • 没有支持Range请求:HTML5的<video>标签会发送Range请求头来分段加载视频(比如只请求视频的前10秒内容),但你的Servlet是一次性返回整个视频文件,不处理Range请求,浏览器会因为不符合预期而中断连接。
  • Session存储大文件不合理:你把整个视频文件读入Session的字节数组中,既浪费服务器内存,又可能因为Session序列化(如果服务器有集群)或者传输超时导致连接中断。

修复方案

步骤1:重构JavaBean,不再把视频文件存入Session

只把视频的ID存入Session,或者直接在视频标签的请求中传递ID,让Servlet自己去查询文件路径:

@PostConstruct 
public void prepeareMethod() { 
    try { 
        LearningObject lo = dao.getLearningObjByID(reqId); 
        String learnObjType = lo.getLearningObjTyId().getLearningObjectType();
        // 只存视频ID到Session,不存整个文件
        FacesContext facesContext = FacesContext.getCurrentInstance(); 
        HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(false); 
        session.setAttribute("currentLoId", reqId); 
    } catch (Exception e) { 
        e.printStackTrace(); 
    } 
}
步骤2:修改视频标签,传递视频ID参数
<video controls="controls" style="margin-top: 60px;width: 70%;height: 80vh"> 
    <source src="/SPortal/VideoReader?loId=#{lessonLearnBN.reqId}" type="video/mp4" /> 
</video>
步骤3:重构Servlet,支持Range请求并直接读取磁盘文件

这样既节省内存,又符合<video>标签的加载逻辑:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 从请求参数获取视频ID,查询文件路径
    String loId = request.getParameter("loId");
    if (loId == null || loId.isEmpty()) {
        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
        return;
    }

    // 从数据库查询视频文件路径(这里复用你的LearningObject逻辑)
    LearningObject lo = dao.getLearningObjByID(Integer.parseInt(loId));
    File videoFile = new File(lo.getDirPath() + "\\" + lo.getFileName());

    if (!videoFile.exists()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    long fileLength = videoFile.length();
    long start = 0;
    long end = fileLength - 1;

    // 处理Range请求
    String rangeHeader = request.getHeader("Range");
    if (rangeHeader != null) {
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        String[] ranges = rangeHeader.split("=")[1].split("-");
        start = Long.parseLong(ranges[0]);
        if (ranges.length > 1) {
            end = Long.parseLong(ranges[1]);
        }
        // 确保end不超过文件长度
        end = Math.min(end, fileLength - 1);
    }

    long contentLength = end - start + 1;
    response.setContentType("video/mp4");
    response.setHeader("Content-Length", String.valueOf(contentLength));
    response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);
    response.setHeader("Accept-Ranges", "bytes");

    // 分段读取文件并写入响应流,用try-with-resources自动关闭资源
    try (RandomAccessFile raf = new RandomAccessFile(videoFile, "r");
         OutputStream out = response.getOutputStream()) {
        raf.seek(start);
        byte[] buffer = new byte[4096]; // 4KB缓冲区,平衡性能与内存
        long bytesWritten = 0;
        while (bytesWritten < contentLength) {
            int readBytes = raf.read(buffer);
            if (readBytes == -1) break; // 文件读取完毕
            // 处理最后一段可能不足缓冲区大小的情况
            if (bytesWritten + readBytes > contentLength) {
                readBytes = (int) (contentLength - bytesWritten);
            }
            out.write(buffer, 0, readBytes);
            bytesWritten += readBytes;
        }
        out.flush();
    } catch (IOException e) {
        // 这里的异常大概率是客户端断开连接,属于正常情况,记录日志即可
        System.out.println("视频加载时客户端断开连接:" + e.getMessage());
    }
}

3. Severe: Error Rendering View[/Error.xhtml] java.lang.IllegalStateException: getOutputStream() has already been called for this response

原因

这个错误是因为同一个HTTP响应中,程序先调用了response.getOutputStream()(你的Servlet写入视频流时),之后JSF尝试渲染Error.xhtml页面时又调用了response.getWriter(),两者不能同时使用,导致冲突。通常是Servlet抛出异常后,容器转发到Error页面,但此时响应流已经被打开。

修复方案

  • 确保Servlet异常处理正确:在Servlet中如果发生异常,先设置响应状态码(比如response.sendError(SC_INTERNAL_SERVER_ERROR)),不要继续写入响应流。
  • 使用try-with-resources管理响应流:如上面的Servlet示例,用try-with-resources自动关闭输出流,避免资源泄漏。
  • 检查Error.xhtml的配置:确保Error页面是标准的JSF页面,其后台Bean没有错误地操作响应流。

内容的提问来源于stack exchange,提问作者Yara Mousa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:24:24