Jersey REST support resume/media streaming

Dima picture Dima · Jan 19, 2013 · Viewed 13.8k times · Source

I need to support resume on Jersey REST, I'm trying to do it this way:

@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. any ideas?

Answer

Arul Dhesiaseelan picture Arul Dhesiaseelan · Jan 23, 2013

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. You can find the sample project on my Github repository which has more details. Chrome and Safari nicely leverages the range headers to stream media and you can see it in the request/response trace.

    @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.

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