断点续传下载文件,多媒体在线播放

断点续传

断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。

关键点

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。
客户端在 HTTP 请求中体现在:

Range: bytes=17563648-

服务器收到这个请求以后,返回的信息如下:

Accept-Ranges: bytes
Content-Range: bytes 17563648-36057953/36057954
Content-Length: 18494306

HTTP 响应码应变为 206,如下所示:
avatar

实现,以 Spring Boot 1.5.14.RELEASE 为例

实现方法如下,关键点在于 HTTP 响应头设置,其他注意点已在注释中说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* 文件下载,断点续传
*
* @param request
* @param response
* @param fileName
* @return
*/
public boolean download(HttpServletRequest request, HttpServletResponse response, String fileName) {
//文件目录
Calendar calendar = Calendar.getInstance();
File serverDir = new File(System.getProperty("user.dir") + File.separator
+ "uploads" + File.separator
+ calendar.get(Calendar.YEAR) + File.separator
+ (calendar.get(Calendar.MONTH) + 1));
File file = new File(serverDir + File.separator + fileName);

//下载开始位置
long startByte = 0;
//下载结束位置
long endByte = file.length() - 1;

//获取下载范围
String range = request.getHeader("range");
if (range != null && range.contains("bytes=") && range.contains("-")) {
range = range.substring(range.lastIndexOf("=") + 1).trim();
String rangeArray[] = range.split("-");
if (rangeArray.length == 1) {
//Example: bytes=1024-
if (range.endsWith("-")) {
startByte = Long.parseLong(rangeArray[0]);
} else { //Example: bytes=-1024
endByte = Long.parseLong(rangeArray[0]);
}
}
//Example: bytes=2048-4096
else if (rangeArray.length == 2) {
startByte = Long.parseLong(rangeArray[0]);
endByte = Long.parseLong(rangeArray[1]);
}
}

long contentLength = endByte - startByte + 1;
String contentType = request.getServletContext().getMimeType(fileName);

//HTTP 响应头设置
//断点续传,HTTP 状态码必须为 206,否则不设置,如果非断点续传设置 206 状态码,则浏览器无法下载
if (range != null) {
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
}
response.setContentType(contentType);
response.setHeader("Content-Type", contentType);
response.setHeader("Content-Length", String.valueOf(contentLength));
response.setHeader("Accept-Ranges", "bytes");
//Content-Range: 下载开始位置-下载结束位置/文件大小
response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + file.length());
//Content-disposition: inline; filename=xxx.xxx 表示浏览器内嵌显示该文件
//Content-disposition: attachment; filename=xxx.xxx 表示浏览器下载该文件
response.setHeader("Content-Disposition", "inline; filename=" + fileName);

//传输文件流
BufferedOutputStream outputStream = null;
RandomAccessFile randomAccessFile = null;
//已传送数据大小
long transmittedLength = 0;
try {
//以只读模式设置文件指针偏移量
randomAccessFile = new RandomAccessFile(file, "r");
randomAccessFile.seek(startByte);

outputStream = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[4096];
int len;
while (transmittedLength < contentLength && (len = randomAccessFile.read(buff)) != -1) {
outputStream.write(buff, 0, len);
transmittedLength += len;
}

outputStream.flush();
response.flushBuffer();
logger.info("下载完毕: {}-{}: {}", startByte, endByte, transmittedLength);
return true;

} catch (IOException e) {
logger.info("下载停止: {}-{}: {}", startByte, endByte, transmittedLength);
} finally {
try {
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}

多媒体播放

实现断点续传是多媒体播放的关键点

HTML 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Video</title>
</head>
<body>
<video src="http://127.0.0.1:8001/demo/download/o_1cjkl6f5o1lslun02dv1mn3nup2n.mp4" controls="controls">
Your browser does not support the video tag.
</video>
<br/>
<embed src="http://27.0.0.1:8001/demo/download/%E5%A6%82%E6%AD%8C.mp3"/>
</body>
</html>

效果图

avatar
avatar

参考与感谢

钟华 : 用 Java 实现断点续传 (HTTP)

If these articles are helpful to you, you can donate comment here.