最近在做一个在线播放视频流的程序,安卓和电脑上都可以正常的播放。可以唯独ios上无法正常播放。开始在网上找了很多资料觉得不是很到位,所以在这里记录一下。
刚开始时代码返回的视频流是在一个请求里全部返回的,而苹果的浏览器会先发一次探测请求来获取文件大小,之后再发送多次请求来分段取数据流的数据,其实这里就是一个分段上传的思想(Accept-Ranges)。有两个很重要的点就是,
-
第一:需要根据请求内容的不同做出不同的响应,第一次探测请求需要返回200,后面的请求需要返回206和具体数据
-
第二:contentType必须设置为video/mp4。
下面是可以直接使用的代码:
private void sendVideo(HttpServletRequest request, HttpServletResponse response, File file, String fileName) throws FileNotFoundException, IOException {
RandomAccessFile randomFile = new RandomAccessFile(file, "r");//只读模式
long contentLength = randomFile.length();
String range = request.getHeader("Range");
int start = 0, end = 0;
if(range != null && range.startsWith("bytes=")){
String[] values = range.split("=")[1].split("-");
start = Integer.parseInt(values[0]);
if(values.length > 1){
end = Integer.parseInt(values[1]);
}
}
int requestSize = 0;
if(end != 0 && end > start){
requestSize = end - start + 1;
} else {
requestSize = Integer.MAX_VALUE;
}
byte[] buffer = new byte[4096];
response.setContentType("video/mp4");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("ETag", fileName);
response.setHeader("Last-Modified", new Date().toString());
//第一次请求只返回content length来让客户端请求多次实际数据
if(range == null){
response.setHeader("Content-length", contentLength + "");
}else{
//以后的多次以断点续传的方式来返回视频数据
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);//206
long requestStart = 0, requestEnd = 0;
String[] ranges = range.split("=");
if(ranges.length > 1){
String[] rangeDatas = ranges[1].split("-");
requestStart = Integer.parseInt(rangeDatas[0]);
if(rangeDatas.length > 1){
requestEnd = Integer.parseInt(rangeDatas[1]);
}
}
long length = 0;
if(requestEnd > 0){
length = requestEnd - requestStart + 1;
response.setHeader("Content-length", "" + length);
response.setHeader("Content-Range", "bytes " + requestStart + "-" + requestEnd + "/" + contentLength);
}else{
length = contentLength - requestStart;
response.setHeader("Content-length", "" + length);
response.setHeader("Content-Range", "bytes "+ requestStart + "-" + (contentLength - 1) + "/" + contentLength);
}
}
ServletOutputStream out = response.getOutputStream();
int needSize = requestSize;
randomFile.seek(start);
while(needSize > 0){
int len = randomFile.read(buffer);
if(needSize < buffer.length){
out.write(buffer, 0, needSize);
} else {
out.write(buffer, 0, len);
if(len < buffer.length){
break;
}
}
needSize -= buffer.length;
}
randomFile.close();
out.close();
}
这边会有个报错,不影响视频播放,觉得影响日志查看的话可以这样处理:
try {
out.write(buffer, 0, len);
if(len < buffer.length){
break;
}
} catch (Exception e) {
String simplename = e.getClass().getSimpleName();
if("ClientAbortException".equals(simplename)){
//这个打印就是个说明,如果不这样写就会报以下错误,
//这个是网上目前的解决办法,如果有其他的解决办法,在改掉
//ClientAbortException: java.net.SocketException:
//Connection reset by peer: socket write error
// logger.debug("视频播放时客户端请求太快了");
}else {
e.printStackTrace();
}
}