2013-01-19 9 views
13

muszę wspierać CV na Jersey REST, staram się zrobić to w ten sposób:Jersey REST wsparcie resume/media strumieniowe

@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 chcesz mieć możliwość strumienia mp3 więc przeglądarka może starać się muzyką , ale na safari nadal nie działa. jakieś pomysły?

Odpowiedz

21

Oto moje podejście oparte na dostarczonym rozwiązaniu here. Działa poprawnie w różnych przeglądarkach. W Safari i innych przeglądarkach jestem w stanie szukać muzyki. Możesz znaleźć przykładowy projekt na moim Githubie repository, który ma więcej szczegółów. Chrome i Safari ładnie wykorzystują nagłówki zakresów do strumieniowego przesyłania multimediów i widzisz je w śledzeniu żądania/odpowiedzi.

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

Oto implementacja MediaStreamer, która służy do przesyłania strumieniowego danych wyjściowych w metodzie zasobów.

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

Thank ty tak bardzo! dokładnie to, co potrzebne, działa jak urok! – Dima

+2

BTW, jaką wartość powinienem ustawić na chunk_size? – Dima

+1

Użyłem 1 MB fragmentów w moim przykładzie tutaj https://github.com/aruld/jersey-streaming/blob/master/src/main/java/com/aruld/jersey/streaming/MediaResource.java Jest używany, gdy klient nie wysyła górnej granicy zakresu, więc decydujemy się na podzielenie mediów na kilka bitów. –

1

Ponieważ było skierowane w tą samą problema, że ​​próbuje bardziej ogólnego rozwiązania [1] ze ContainerResponseFilter co spowoduje gdy Range nagłówek jest obecny w żądaniu i bezproblemowo z dowolny nośnik typu, podmiot i metody zasobów.

To ContainerResponseFilter który użyje hermetyzacji strumienia wyjściowego w RangedOutputStream (patrz poniżej):

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

} 

A oto 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

Powiązane problemy