我需要支持泽西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它仍然没有工作。 有任何想法吗?
下面是根据所提供的解决方案我拿这里 。 它正常工作在不同的浏览器。 我能够寻求音乐刚刚在Safari和其他浏览器精也。 你可以找到我的Github上的示例项目库中具有更多的细节。 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;
}
}
由于我面临着同样的problema,我尝试了更通用的解决方案[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