Play Framework 2.3.x ByteChunks MP3 streaming 没有播放功能,在浏览器中不'scrollable'

Play Framework 2.3.x ByteChunks MP3 streaming has no playback, is not 'scrollable' in the browser

使用 Play Framework(版本 2.3.x)(Java 样式),我正在尝试向浏览器提供 .mp3 文件。由于它是一个 'large' 文件,我决定使用 Play 的 ByteChunks Object,如下所示。

@With(MP3Headers.class)
public static Result test() {

    Chunks<byte[]> chunks = new ByteChunks() {
        public void onReady(Chunks.Out<byte[]> out) {
            try {
                byte[] song = Files.readAllBytes(Paths.get("public/mp3/song.mp3"));
                out.write(song);
            } catch(Exception e) {
                e.printStackTrace();
            } finally {
                out.close();
            }
        }
    };

    return ok(chunks);
}

为了澄清,我的 Mp3Headers 文件负责设置 headers 以便浏览器知道负载的类型:

public class MP3Headers extends Action.Simple {

    public Promise<Result> call(Http.Context ctx) throws Throwable {
        ctx.response().setContentType("audio/mpeg");
        return delegate.call(ctx);
    }
}

为了完成,我的 routes 文件:

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index()

GET     /test                       controllers.Application.test()

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.at(path="/public", file) 

正如预期的那样,导航到 localhost:9000/test 会呈现一个不错的 HTML5 音频播放器(见图)。

我遇到的问题是音频播放器中的 'scrolling' 不工作。如果我确实滚动,音乐会暂停,当我放手时(当我 'chose' 一个时间位置时),它会从第一次暂停的地方继续。

希望我说得有道理,也希望你们对此有所了解。提前致谢。

您需要告诉您的浏览器您的服务器支持范围请求并实现范围响应(即只提供浏览器需要的音乐部分)。您可以在 this answer.

中大致了解 request/response 周期
@With(MP3Headers.class)
public static Result test() {
    final int begin, end;
    final boolean isRangeReq;
    response().setHeader("Accept-Ranges", "bytes");
    if (request().hasHeader("RANGE")) {
        isRangeReq = true;
        String[] range = request().getHeader("RANGE").split("=")[1].split("-");
        begin = Integer.parseInt(range[0]);
        if (range.length > 1) {
            end = Integer.parseInt(range[1]);
        } else {
            end = song.length-1;
        }
        response().setHeader("Content-Range", String.format("bytes %d-%d/%d", begin, end, song.length));
    } else {
        isRangeReq = false;
        begin = 0;
        end = song.length - 1;
    }

    Chunks<byte[]> chunks = new ByteChunks() {
        public void onReady(Chunks.Out<byte[]> out) {
            if(isRangeReq) {
                out.write(Arrays.copyOfRange(song, begin, end));
            } else {
                out.write(song);
            }
            out.close();
        }
    };
    response().setHeader("Content-Length", (end - begin + 1) + "");
    if (isRangeReq) {
        return status(206, chunks);
    } else {
        return status(200, chunks);
    }
}

请注意,在此代码中,歌曲已在 song 中加载。 RANGE header 的解析也很脏(你可以得到像 RANGE: 这样的值)

我发现这段代码很容易实现。

将以下操作及其私有辅助方法放入您的控制器中。

Controller Action

public static Result file(Long id, String filename) throws IOException {

Item item = Item.fetch(id);
File file = item.getFile();

if(file== null || !file.exists()) {
  Logger.error("File no longer exist item"+id+" filename:"+filename);           
  return notFound();
}

String rangeheader = request().getHeader(RANGE);
if(rangeheader != null) {

  String[] split = rangeheader.substring("bytes=".length()).split("-");
  if(Logger.isDebugEnabled()) { Logger.debug("Range header is:"+rangeheader); }

  if(split.length == 1) {
    long start = Long.parseLong(split[0]);
    long length = file.length()-1l;             
    return stream(start, length, file);                             
  } else {
    long start = Long.parseLong(split[0]);
    long length = Long.parseLong(split[1]);
    return stream(start, length, file);
  }
}

// if no streaming is required we simply return the file as a 200 OK
if(Play.isProd()) {
  response().setHeader("Cache-Control", "max-age=3600, must-revalidate");           
}
return ok(file);
}

Stream Helper method

private static Result stream(long start, long length, File file) throws IOException {

FileInputStream fis = new FileInputStream(file);
fis.skip(start);
response().setContentType(MimeTypes.forExtension("mp4").get());
response().setHeader(CONTENT_LENGTH, ((length - start) +1l)+"");
response().setHeader(CONTENT_RANGE, String.format("bytes %d-%d/%d", start, length,file.length()));

response().setHeader(ACCEPT_RANGES, "bytes");
response().setHeader(CONNECTION, "keep-alive");
return status(PARTIAL_CONTENT, fis);
}   

完整示例 link 在这里 Byte range requests in Play 2 Java Controllers