简体   繁体   English

Play Framework 2.3.x ByteChunks MP3流无法播放,在浏览器中无法“滚动”

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

Using Play Framework (version 2.3.x ) (Java style), I am trying to serve an .mp3 file to the browser. 我正在尝试使用Play Framework( 2.3.x版)(Java风格),将.mp3文件提供给浏览器。 Since it is a 'large' file I have decided to go with Play's ByteChunks Object, as follows. 由于它是一个“大”文件,因此,我决定使用Play的ByteChunks对象,如下所示。

@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);
}

For clarification, my Mp3Headers file, which is responsable for setting the headers so that the browser knows what type the payload has: 为了澄清Mp3Headers ,我的Mp3Headers文件负责设置标题,以便浏览器知道有效负载的类型:

public class MP3Headers extends Action.Simple {

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

For completion, my routes file: 为了完成,我的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) 

As is to be expected, navigating to localhost:9000/test renders to a nice HTML5 audio player (see picture). 不出所料,导航到localhost:9000/test会呈现一个不错的HTML5音频播放器(参见图片)。

在Google Chrome中裁剪了localhost:9000 / test的屏幕截图

The problem I have is that 'scrolling' in the audio player does not work. 我的问题是音频播放器中的“滚动”不起作用。 If I do scroll, the music pauses, and when I let go (when I 'chose' a position in time), it continues where it first paused. 如果我滚动了,音乐就会暂停,放开时(当我“及时”选择一个位置时),音乐会在它第一次暂停的地方继续播放。

I hope that I make sense, and I hope that you guys know something more about this. 我希望我有道理,也希望你们对此有所了解。 Thanks in advance. 提前致谢。

You will need to tell your browser that your server support range requests and implement the ranges responses (ie just provide the part of the music the browser needs). 您将需要告诉浏览器您的服务器支持范围请求并实现范围响应(即仅提供浏览器需要的音乐部分)。 You can get an overview of the request/response cycle in this answer . 您可以在此答案中获得请求/响应周期的概述。

@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);
    }
}

Note that in this code the song was already loaded in song . 请注意,在此代码中,歌曲已经加载到song Also the parsing of the RANGE header is very dirty (you can get values like RANGE: ) 此外, RANGE标头的解析非常脏(您可以获取RANGE:等值)

I Found this code very easy implementation. 我发现此代码非常容易实现。

Put the below action and its private helper method in your controller. 将以下操作及其私有帮助器方法放入控制器中。

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);
}   

Complete example link is here Byte range requests in Play 2 Java Controllers 完整的示例链接在此处是Play 2 Java Controller中的字节范围请求

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM