【问题标题】:Jersey REST support resume/media streamingJersey REST 支持简历/媒体流
【发布时间】:2013-01-19 01:50:29
【问题描述】:

我需要支持 Jersey REST 上的简历,我正在尝试这样做:

@Path("/helloworld")
public class RestServer {

@GET

@Path("say")
@Produces("audio/mp3")
public Response getMessage(@HeaderParam("Range") String r ) throws IOException{
    String str="/Users/dima/Music/crazy_town_-_butterfly.mp3";

    System.out.println(r);
    RandomAccessFile f=new RandomAccessFile(str, "r");

    int off=0;
    int to=(int)f.length();
    byte[] data ;
    if(r!=null){
        String from=r.split("=")[1].split("-")[0];
        String t=r.split("=")[1].split("-")[1];
        off=Integer.parseInt(from);
        to=Integer.parseInt(t);

    }
    data= new byte[to-off];
    f.readFully(data, off, to-off);

    ResponseBuilder res=Response.ok(data)
            .header("Accept-Ranges","bytes")
            .header("Content-Range:", "bytes "+off+"-"+to+"/"+data.length)
            .header("Pragma", "no-cache");;

            if(r==null){
                res=res.header("Content-Length", data.length);
            }
            f.close();

            Response ans=res.build();

            return ans;


}
}

我希望能够流式传输 mp3,以便浏览器可以搜索音乐,但在 safari 中它仍然无法正常工作。有什么想法吗?

【问题讨论】:

    标签: rest streaming jersey media resume


    【解决方案1】:

    这是我基于here 提供的解决方案的看法。它可以在不同的浏览器上正常工作。我也可以在 Safari 和其他浏览器中很好地寻找音乐。您可以在我的 Github repository 上找到示例项目,其中包含更多详细信息。 Chrome 和 Safari 很好地利用了范围标头来流式传输媒体,您可以在请求/响应跟踪中看到它。

        @GET
        @Produces("audio/mp3")
        public Response streamAudio(@HeaderParam("Range") String range) throws Exception {
            return buildStream(audio, range);
        }
    
        private Response buildStream(final File asset, final String range) throws Exception {
            // range not requested : Firefox, Opera, IE do not send range headers
            if (range == null) {
                StreamingOutput streamer = new StreamingOutput() {
                    @Override
                    public void write(final OutputStream output) throws IOException, WebApplicationException {
    
                        final FileChannel inputChannel = new FileInputStream(asset).getChannel();
                        final WritableByteChannel outputChannel = Channels.newChannel(output);
                        try {
                            inputChannel.transferTo(0, inputChannel.size(), outputChannel);
                        } finally {
                            // closing the channels
                            inputChannel.close();
                            outputChannel.close();
                        }
                    }
                };
                return Response.ok(streamer).header(HttpHeaders.CONTENT_LENGTH, asset.length()).build();
            }
    
            String[] ranges = range.split("=")[1].split("-");
            final int from = Integer.parseInt(ranges[0]);
            /**
             * Chunk media if the range upper bound is unspecified. Chrome sends "bytes=0-"
             */
            int to = chunk_size + from;
            if (to >= asset.length()) {
                to = (int) (asset.length() - 1);
            }
            if (ranges.length == 2) {
                to = Integer.parseInt(ranges[1]);
            }
    
            final String responseRange = String.format("bytes %d-%d/%d", from, to, asset.length());
            final RandomAccessFile raf = new RandomAccessFile(asset, "r");
            raf.seek(from);
    
            final int len = to - from + 1;
            final MediaStreamer streamer = new MediaStreamer(len, raf);
            Response.ResponseBuilder res = Response.status(Status.PARTIAL_CONTENT).entity(streamer)
                    .header("Accept-Ranges", "bytes")
                    .header("Content-Range", responseRange)
                    .header(HttpHeaders.CONTENT_LENGTH, streamer.getLenth())
                    .header(HttpHeaders.LAST_MODIFIED, new Date(asset.lastModified()));
            return res.build();
        }
    

    这是 MediaStreamer 的实现,用于在资源方法中流式传输输出。

    public class MediaStreamer implements StreamingOutput {
    
        private int length;
        private RandomAccessFile raf;
        final byte[] buf = new byte[4096];
    
        public MediaStreamer(int length, RandomAccessFile raf) {
            this.length = length;
            this.raf = raf;
        }
    
        @Override
        public void write(OutputStream outputStream) throws IOException, WebApplicationException {
            try {
                while( length != 0) {
                    int read = raf.read(buf, 0, buf.length > length ? length : buf.length);
                    outputStream.write(buf, 0, read);
                    length -= read;
                }
            } finally {
                raf.close();
            }
        }
    
        public int getLenth() {
            return length;
        }
    }
    

    【讨论】:

    • 非常感谢!正是我所需要的,就像一个魅力!
    • 顺便说一句,我应该为 chunk_size 设置什么值?
    • 我在这里的示例中使用了 1MB 块 github.com/aruld/jersey-streaming/blob/master/src/main/java/com/… 当客户端没有发送范围上限时使用它,因此我们决定将媒体分成几位。
    • 天哪,我希望我有这个用于码头。我不知道如何转换它。
    • @thouliha Checkout jersey2 branch 使用 Jetty。
    【解决方案2】:

    因为我遇到了同样的问题,所以我尝试了一个更通用的解决方案[1],它带有 ContainerResponseFilter,当请求中存在 Range 标头时将触发该解决方案,并且可以与任何媒体类型、实体无缝协作和资源方法。

    这是ContainerResponseFilter,它将使用将输出流封装在RangedOutputStream 中(见下文):

    public class RangeResponseFilter implements ContainerResponseFilter {
    
        private static final String RANGE = "Range";
    
        private static final String ACCEPT_RANGES = "Accept-Ranges";
    
        @Override
        public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
                throws IOException {
            if (requestContext.getHeaders().containsKey(RANGE)) {
                String rangeHeader = requestContext.getHeaderString(RANGE);
                String contentType = responseContext.getMediaType().toString();
                OutputStream originOutputStream = responseContext.getEntityStream();
                RangedOutputStream rangedOutputStream = new RangedOutputStream(originOutputStream, rangeHeader, contentType, responseContext.getHeaders());
                responseContext.setStatus(Status.PARTIAL_CONTENT.getStatusCode());
                responseContext.getHeaders().putSingle(ACCEPT_RANGES, rangedOutputStream.getAcceptRanges());
                responseContext.setEntityStream(rangedOutputStream);
            }
        }
    
    }
    

    这是RangedOutputStream:

    public class RangedOutputStream extends OutputStream {
    
        public class Range extends OutputStream {
    
            private ByteArrayOutputStream outputStream;
    
            private Integer from;
    
            private Integer to;
    
            public Range(Integer from, Integer to) {
                this.outputStream = new ByteArrayOutputStream();
                this.from = from;
                this.to = to;
            }
    
            public boolean contains(Integer i) {
                if (this.to == null) {
                    return (this.from <= i);
                }
                return (this.from <= i && i <= this.to);
            }
    
            public byte[] getBytes() {
                return this.outputStream.toByteArray();
            }
    
            public Integer getFrom() {
                return this.from;
            }
    
            public Integer getTo(Integer ifNull) {
                return this.to == null ? ifNull : this.to;
            }
    
            @Override
            public void write(int b) throws IOException {
                this.outputStream.write(b);
            }
    
        }
    
        private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
                .toCharArray();
    
        private static final String BOUNDARY_LINE_FORMAT = "--%s";
    
        private static final String CONTENT_TYPE_LINE_FORMAT = "Content-Type: %s";
    
        private static final String CONTENT_RANGE_FORMAT = "%s %d-%d/%d";
    
        private static final String CONTENT_RANGE_LINE_FORMAT = "Content-Range: " + CONTENT_RANGE_FORMAT;
    
        private static final String EMPTY_LINE = "\r\n";
    
        private OutputStream outputStream;
    
        private String boundary;
    
        private String accept;
    
        private String contentType;
    
        private boolean multipart;
    
        private boolean flushed = false;
    
        private int pos = 0;
    
        List<Range> ranges;
    
        MultivaluedMap<String, Object> headers;
    
        public RangedOutputStream(OutputStream outputStream, String ranges, String contentType, MultivaluedMap<String, Object> headers) {
            this.outputStream = outputStream;
            this.ranges = new ArrayList<>();
            String[] acceptRanges = ranges.split("=");
            this.accept = acceptRanges[0];
            for (String range : acceptRanges[1].split(",")) {
                String[] bounds = range.split("-");
                this.ranges.add(new Range(Integer.valueOf(bounds[0]), bounds.length == 2 ? Integer.valueOf(bounds[1]) : null ));
            }
            this.headers = headers;
            this.contentType = contentType;
            this.multipart = this.ranges.size() > 1;
            this.boundary = this.generateBoundary();
        }
    
        private String generateBoundary() {
            StringBuilder buffer = new StringBuilder();
            Random rand = new Random();
            int count = rand.nextInt(11) + 30;
            for (int i = 0; i < count; i++) {
                buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
            }
            return buffer.toString();
        }
    
        public boolean isMultipart() {
            return this.multipart;
        }
    
        public String getBoundary() {
            return this.boundary;
        }
    
        public String getAcceptRanges() {
            return this.accept;
        }
    
        public String getContentRange(int index) {
            Range range = this.ranges.get(index);
            return String.format(CONTENT_RANGE_LINE_FORMAT, this.accept, range.getFrom(), range.getTo(this.pos), this.pos);
        }
    
        @Override
        public void write(int b) throws IOException {
            for (Range range : this.ranges) {
                if (range.contains(this.pos)) {
                    range.write(b);
                }
            }
            this.pos++;
        }
    
        @Override
        public void flush() throws IOException {
            if (this.flushed) {
                return;
            }
            if (this.multipart) {
                this.headers.putSingle(HttpHeaders.CONTENT_TYPE, String.format("multipart/byteranges; boundary=%s", this.boundary));
                for (Range range : this.ranges) {
                    this.outputStream.write(String.format(BOUNDARY_LINE_FORMAT + EMPTY_LINE, this.boundary).getBytes());
                    this.outputStream.write(String.format(CONTENT_TYPE_LINE_FORMAT + EMPTY_LINE, this.contentType).getBytes());
                    this.outputStream.write(
                            String.format(CONTENT_RANGE_LINE_FORMAT + EMPTY_LINE, this.accept, range.getFrom(), range.getTo(this.pos), this.pos)
                                    .getBytes());
                    this.outputStream.write(EMPTY_LINE.getBytes());
                    this.outputStream.write(range.getBytes());
                    this.outputStream.write(EMPTY_LINE.getBytes());
                }
                this.outputStream.write(String.format(BOUNDARY_LINE_FORMAT, this.boundary + "--").getBytes());
            } else {
                Range range = this.ranges.get(0);
                this.headers.putSingle("Content-Range", String.format(CONTENT_RANGE_FORMAT, this.accept, range.getFrom(), range.getTo(this.pos), this.pos));
                this.outputStream.write(range.getBytes());
            }
            this.flushed = true;
        }
    
    }
    

    [1]https://github.com/heruan/jersey-range-filter

    【讨论】:

      猜你喜欢
      • 2015-08-21
      • 1970-01-01
      • 2015-04-23
      • 1970-01-01
      • 2013-03-14
      • 2016-06-06
      • 1970-01-01
      相关资源
      最近更新 更多