简体   繁体   English

Jersey REST支持简历/媒体流

[英]Jersey REST support resume/media streaming

I need to support resume on Jersey REST, I'm trying to do it this way: 我需要支持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;


}
}

I want to be able stream mp3 so the browser can seek the music, but in safari it still not working. 我希望能够流式播放MP3,以便浏览器可以搜索音乐,但在Safari中它仍然无法正常工作。 any ideas? 有任何想法吗?

Here is my take based on the solution provided here . 以下是基于此处提供的解决方案的内容。 It works correctly on different browsers. 它在不同的浏览器上正常工作。 I am able to seek the music just fine in Safari and other browsers as well. 我也可以在Safari和其他浏览器中搜索音乐。 You can find the sample project on my Github repository which has more details. 您可以在我的Github 存储库中找到具有更多详细信息的示例项目。 Chrome and Safari nicely leverages the range headers to stream media and you can see it in the request/response trace. 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();
    }

Here is the MediaStreamer implementation, which is used to stream the output in your resource method. 这是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;
    }
}

Since I was facing the same problema, I tried a more general solution[1] with a ContainerResponseFilter which will trigger when a Range header is present in the request and will work seamlessly with any media-type, entity and resource methods. 由于我遇到了同样的问题,我尝试了一个更通用的解决方案[1],其中包含一个ContainerResponseFilter ,它将在请求中出现Range标头时触发,并且可以与任何媒体类型,实体和资源方法无缝地协作。

This is the ContainerResponseFilter which will use encapsulate the output stream in a RangedOutputStream (see below): 这是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);
        }
    }

}

And here's the 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 [1] https://github.com/heruan/jersey-range-filter

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

相关问题 Firebase Firestore REST API 支持流吗 - Does Firebase Firestore REST API support streaming Etat HTTP 415-不支持的媒体类型:GET未达到Jersey REST - Etat HTTP 415 - Unsupported Media Type : GET not reaching Jersey REST 解析媒体类型&#39;application.xml&#39;泽西REST客户端时出错 - Error parsing media type 'application.xml' Jersey REST client 泽西(Jersey)REST错误,找不到媒体类型= application / json的MessageBodyWriter - Jersey REST error, MessageBodyWriter not found for media type=application/json POST到Jersey REST服务收到错误415不支持的媒体类型 - POST to Jersey REST service getting error 415 Unsupported Media Type Heroku Neo4j插件是否支持流式REST API? - Does Heroku Neo4j addon support streaming REST api? 用泽西流媒体视频 - Streaming video with Jersey 如何使用Camel Rest DSL支持媒体类型版本控制 - How to support media type versioning using Camel Rest DSL 当我尝试使用JSON数据时,POST到Jersey REST服务获取错误415不支持的媒体类型 - POST to Jersey REST service getting error 415 Unsupported Media Type when I try to consume JSON data 使用 Jersey 在 REST Web 服务中返回 JSON 时,找不到 media type=application/json 的 MessageBodyWriter - MessageBodyWriter not found for media type=application/json when returning JSON in REST web service with Jersey
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM