2013-01-19 21 views
13

Ho bisogno di sostenere curriculum sul Jersey REST, sto cercando di fare in questo modo:Jersey REST supporto resume/streaming media

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


} 
} 

io voglio essere in grado flusso mp3 in modo che il browser può cercare la musica , ma in safari non funziona ancora. qualche idea?

risposta

21

Ecco la mia versione basata sulla soluzione fornita here. Funziona correttamente su diversi browser. Sono in grado di cercare la musica bene anche in Safari e in altri browser. Puoi trovare il progetto di esempio sul mio Github repository che ha più dettagli. Chrome e Safari sfruttano le intestazioni della gamma per trasmettere contenuti multimediali e possono essere visualizzati nella traccia richiesta/risposta.

Ecco l'implementazione di MediaStreamer, che viene utilizzata per trasmettere l'output nel metodo di risorsa.

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

Grazie tu così tanto! esattamente quello di cui avevo bisogno, funziona come un incantesimo! – Dima

+2

BTW, quale valore devo impostare su chunk_size? – Dima

+1

Ho usato pezzi da 1 MB nel mio campione qui https://github.com/aruld/jersey-streaming/blob/master/src/main/java/com/aruld/jersey/streaming/MediaResource.java Si usa quando il cliente non ha inviato il limite superiore dell'intervallo, quindi decidiamo di inserire il media in diversi bit. –

1

Poiché ero di fronte lo stesso Problema, ho cercato una soluzione più generale [1] con un ContainerResponseFilter che innescano quando un'intestazione Range è presente nella richiesta e funziona perfettamente con qualsiasi tipo di supporto, entità e metodi di risorse.

Questa è la ContainerResponseFilter che utilizzerà incapsulare il flusso di uscita in un RangedOutputStream (vedi sotto):

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

} 

Ed ecco la 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

Problemi correlati