我想从上面发布我的改进解决方案。希望这对某人有用。
所以基本上问题似乎是我忽略了 iPad 不喜欢的“Range”http 请求标头。简而言之,此标头意味着客户端只需要响应的特定部分(在本例中为字节范围)。
iPad html 视频请求如下所示:
[INFO] RequestLogger Accept:*/*
[INFO] RequestLogger Accept-Encoding:identity
[INFO] RequestLogger Connection:keep-alive
[INFO] RequestLogger Host:mars:8080
[INFO] RequestLogger If-Modified-Since:Wed, 10 Oct 2012 22:27:38 GMT
[INFO] RequestLogger Range:bytes=0-1
[INFO] RequestLogger User-Agent:AppleCoreMedia/1.0.0.9B176 (iPad; U; CPU OS 5_1 like Mac OS X; en_us)
[INFO] RequestLogger X-Playback-Session-Id:BC3B397D-D57D-411F-B596-931F5AD9879F
这意味着 iPad 只需要第一个字节。如果您忽略此标头并仅发送带有完整正文的 200 响应,则视频将无法播放。因此,您需要发送 206 响应(部分响应)并设置以下响应标头:
[INFO] RequestLogger Content-Range:bytes 0-1/357772702
[INFO] RequestLogger Content-Length:2
这意味着“我正在向您发送 357772702 个可用字节中的第 0 到 1 个字节”。
当您实际开始播放视频时,下一个请求将如下所示(除了范围标头之外的所有内容都被省略):
[INFO] RequestLogger Range:bytes=0-357772701
所以我的改进解决方案如下所示:
OutputStream os = response.getOutputStream("video/mp4");
try {
String range = request.getHeader("Range");
/** if there is no range requested we will just send everything **/
if( range == null) {
InputStream is = new BufferedInputStream( new FileInputStream(f));
try {
IOUtils.copy(is, os);
response.setStatus(200);
} finally {
is.close();
}
return true;
}
requestLogger.info("Range response _______________________");
String[] ranges = range.split("=")[1].split("-");
int from = Integer.parseInt(ranges[0]);
/**
* some clients, like chrome will send a range header but won't actually specify the upper bound.
* For them we want to send out our large video in chunks.
*/
int to = HTTP_DEFAULT_CHUNK_SIZE + from;
if( to >= f.length()) {
to = (int) (f.length() - 1);
}
if( ranges.length == 2) {
to = Integer.parseInt(ranges[1]);
}
int len = to - from + 1 ;
response.setStatus(206);
response.setHeader("Accept-Ranges", "bytes");
String responseRange = String.format("bytes %d-%d/%d", from, to, f.length());
response.setHeader("Content-Range", responseRange);
response.setDateHeader("Last-Modified", new Date().getTime());
response.setContentLength(len);
requestLogger.info("Content-Range:" + responseRange);
requestLogger.info("length:" + len);
long start = System.currentTimeMillis();
RandomAccessFile raf = new RandomAccessFile(f, "r");
raf.seek(from);
byte[] buf = new byte[IO_BUFFER_SIZE];
try {
while( len != 0) {
int read = raf.read(buf, 0, buf.length > len ? len : buf.length);
os.write(buf, 0, read);
len -= read;
}
} finally {
raf.close();
}
logger.info("r/w took:" + (System.currentTimeMillis() - start));
} finally {
os.close();
}
这个解决方案比我的第一个解决方案更好,因为它处理“范围”请求的所有情况,这似乎是像 Chrome 这样的客户端能够支持在视频中跳过的先决条件(此时他们将为此发出范围请求)视频中的点)。
但它仍然不完美。进一步的改进将是正确设置“Last-Modified”标头,并正确处理客户端请求无效范围或字节以外的其他内容的范围。