< audio src = "http://www.test.com/test.mp3" controls = "controls" > Your browser does not support the audio element. </ audio > </ body > </ html > 复制代码

按照一般思路,直接把文件的字节数组或者字节流响应给客户端就能实现。然而safari采用的是Range请求(不了解Range请求请自己百度),分段下载资源。它首先获取资源的Content-Length,然后根据资源的大小,分多次请求下载。比如资源的大小是100,第一次下载0-50,第二次下载51-100。我猜测这应该就是流式视频的原理。对于用户而言可以边播放,边下载。这无疑大大提高了用户体验。

下面是java的具体代码实现(chrome同样支持)。修改之后也适用于视频播放(参考自: blog.csdn.net/u010120886/… ,同时我做了部分修改):

    @GetMapping("/download")
    public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String range = request.getHeader("Range");
        byte [] bytes = getBytes(); // 这个函数是一个伪函数,它既可以表示从本地文件获取到bytes数组,也可以表示从其它服务下载文件获取到bytes数组;同时由于客户端会发起多次请求,建议缓存
        long contentLength = bytes.length;
        int start = 0, end = 0;
        if(range != null && range.startsWith("bytes=")){
            String[] values = range.split("=")[1].split("-");
            start = Integer.parseInt(values[0]);
            if(values.length > 1){
                end = Integer.parseInt(values[1]);
        int requestSize = 0;
        if(end != 0 && end > start){
            requestSize = end - start + 1;
        } else {
            requestSize = Integer.MAX_VALUE;
        final int bufferSize = 4096;
        byte[] buffer = new byte[bufferSize];
        response.setContentType("audio/mpeg"); // 这是mp3文件的类型,如果是其它资源,可以做相应的修改
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("ETag", sessionId); // ETag自己定义,缓存的作用,用来标识资源
        response.setHeader("Last-Modified", new Date().toString());
        //第一次请求只返回content length来让客户端请求多次实际数据
        if(range == null){
            response.setHeader("Content-length", contentLength + "");
            response.getWriter().flush();
            response.getWriter().close();
            return;
        }else{
            //以后的多次以断点续传的方式来返回视频数据
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);//206
            long requestStart = 0, requestEnd = 0;
            String[] ranges = range.split("=");
            if(ranges.length > 1){
                String[] rangeDatas = ranges[1].split("-");
                requestStart = Integer.parseInt(rangeDatas[0]);
                if(rangeDatas.length > 1){
                    requestEnd = Integer.parseInt(rangeDatas[1]);
            long length = 0;
            if(requestEnd > 0){
                length = requestEnd - requestStart + 1;
                response.setHeader("Content-length", "" + length);
                response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength);
            }else{
                length = contentLength - requestStart;
                response.setHeader("Content-length", "" + length);
                response.setHeader("Content-Range", "bytes "+ requestStart + "-" + (contentLength - 1) + "/" + contentLength);
        // 下面的代码表示流式响应,原代码是通过文件的随机读写函数,循环读取字节到buffer中
        // 我这里是在已经获取到所有的字节的情况下。本质上是一样的
        ServletOutputStream out = response.getOutputStream();
        int needSize = requestSize;
        if (end != 0) {
            bytes = ArrayUtils.subarray(bytes, start, end + 1);
        int i = 0;
        while(needSize > 0){
            int beginIndex = i * bufferSize;
            int endIndex = (i + 1) * bufferSize;
            if (endIndex > bytes.length) {
                endIndex = bytes.length;
            buffer = ArrayUtils.subarray(bytes, beginIndex, endIndex);
            int len = endIndex - beginIndex;
            if(needSize < buffer.length){
                out.write(buffer, 0, needSize);
            } else {
                out.write(buffer, 0, len);
                if(len < buffer.length){
                    break;
            needSize -= buffer.length;
        out.close();
复制代码
  • 在 React Router 中使用 JWT
  • 我是怎么在掘金找到老婆的
  • 开发者故事 #8 微软 New Bing AI 申请与使用保姆级教程
  • 复盘:前端岗位的寒冬,用这3点进行自救
  • 手把手教你注册和使用ChatGPT
  • ChatGPT保姆级教程,一分钟学会使用ChatGPT!
  • 在项目中用ts封装axios,一次封装整个团队受益😁
  • 私信