視頻文件保存在不為人知的地方,總之前端不能直接訪問(wèn)的位置,需要通過(guò)后端接口取出來(lái)再返回給前端。
前端這樣子播放
<video controls="controls" controls="controls">
<source src="http://ip:port/xxxxxxx" type="video/mp4" />
</video>
src=后端接口
如果后端直接這樣子寫文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-530095.html
//path為本地文件路勁
public void play(String path, HttpServletRequest request, HttpServletResponse response) {
//獲取視頻文件流
FileInputStream fileInputStream = null;
OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
fileInputStream = new FileInputStream(new File(path));
byte[] cache = new byte[1024];
response.setHeader(HttpHeaders.CONTENT_TYPE, "video/mp4");
response.setHeader(HttpHeaders.CONTENT_LENGTH, fileInputStream.available()+"");
int flag;
while ((flag = fileInputStream.read(cache)) != -1) {
outputStream.write(cache, 0, flag);
}
outputStream.flush();
outputStream.close();
}catch (Exception e){
log.error("文件傳輸錯(cuò)誤", e);
throw new RuntimeException("文件傳輸錯(cuò)誤");
} finally{
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
log.error("流釋放錯(cuò)誤", e);
}
}
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
log.error("文件流釋放錯(cuò)誤", e);
}
}
}
}
小視頻問(wèn)題不大,視頻大的話會(huì)卡頓很久,查看請(qǐng)求發(fā)現(xiàn)會(huì)先請(qǐng)求下載完整視頻后開(kāi)始播放。而且不能拖動(dòng)進(jìn)度條進(jìn)行播放。追及原因是因?yàn)関ideo會(huì)先發(fā)送一個(gè)探測(cè)請(qǐng)求獲取視頻的大小、長(zhǎng)度等信息,之后才開(kāi)始進(jìn)行播放。不能拖動(dòng)進(jìn)度條播放是因?yàn)楂@取視頻接口不支持?jǐn)帱c(diǎn)傳輸所以只能依次觀看。
http請(qǐng)求就有相關(guān)斷點(diǎn)傳輸?shù)膮f(xié)議,http協(xié)議狀態(tài)碼206就是實(shí)現(xiàn)斷點(diǎn)傳輸?shù)膮f(xié)議,Http請(qǐng)求頭部需要指定獲取數(shù)據(jù)的范圍: Range: bytes=first-end,first,開(kāi)始數(shù)據(jù)的索引位置,end,結(jié)束數(shù)據(jù)的索引位置。Http響應(yīng)需要指定范圍響應(yīng)頭:content-range bytes first-end,并且http狀態(tài)碼設(shè)置為206。
所以實(shí)現(xiàn)視頻斷點(diǎn)播放的大概思路為,先識(shí)別http請(qǐng)求頭是否攜帶range請(qǐng)求頭,如果攜帶則表示請(qǐng)求需要進(jìn)行斷點(diǎn)下載,識(shí)別出斷點(diǎn)的位置后使用隨機(jī)讀取文件類RandomAccessFile進(jìn)行一定范圍的讀寫,之后返回設(shè)置Content-Range請(qǐng)求頭表示返回?cái)?shù)據(jù)的范圍,并設(shè)置http狀態(tài)碼為206表示斷點(diǎn)傳輸。代碼如下文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-530095.html
//path為本地文件路勁
public void play(String path, HttpServletRequest request, HttpServletResponse response) {
RandomAccessFile targetFile = null;
OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
response.reset();
//獲取請(qǐng)求頭中Range的值
String rangeString = request.getHeader(HttpHeaders.RANGE);
//打開(kāi)文件
File file = new File(path);
if (file.exists()) {
//使用RandomAccessFile讀取文件
targetFile = new RandomAccessFile(file, "r");
long fileLength = targetFile.length();
long requestSize = (int) fileLength;
//分段下載視頻
if (StringUtils.hasText(rangeString)) {
//從Range中提取需要獲取數(shù)據(jù)的開(kāi)始和結(jié)束位置
long requestStart = 0, requestEnd = 0;
String[] ranges = rangeString.split("=");
if (ranges.length > 1) {
String[] rangeDatas = ranges[1].split("-");
requestStart = Integer.parseInt(rangeDatas[0]);
if (rangeDatas.length > 1) {
requestEnd = Integer.parseInt(rangeDatas[1]);
}
}
if (requestEnd != 0 && requestEnd > requestStart) {
requestSize = requestEnd - requestStart + 1;
}
//根據(jù)協(xié)議設(shè)置請(qǐng)求頭
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
response.setHeader(HttpHeaders.CONTENT_TYPE, "video/mp4");
if (!StringUtils.hasText(rangeString)) {
response.setHeader(HttpHeaders.CONTENT_LENGTH, fileLength + "");
} else {
long length;
if (requestEnd > 0) {
length = requestEnd - requestStart + 1;
response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + requestEnd + "/" + fileLength);
} else {
length = fileLength - requestStart;
response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length);
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + (fileLength - 1) + "/"
+ fileLength);
}
}
//斷點(diǎn)傳輸下載視頻返回206
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
//設(shè)置targetFile,從自定義位置開(kāi)始讀取數(shù)據(jù)
targetFile.seek(requestStart);
} else {
//如果Range為空則下載整個(gè)視頻
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=test.mp4");
//設(shè)置文件長(zhǎng)度
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength));
}
//從磁盤讀取數(shù)據(jù)流返回
byte[] cache = new byte[4096];
try {
while (requestSize > 0) {
int len = targetFile.read(cache);
if (requestSize < cache.length) {
outputStream.write(cache, 0, (int) requestSize);
} else {
outputStream.write(cache, 0, len);
if (len < cache.length) {
break;
}
}
requestSize -= cache.length;
}
} catch (IOException e) {
// tomcat原話。寫操作IO異常幾乎總是由于客戶端主動(dòng)關(guān)閉連接導(dǎo)致,所以直接吃掉異常打日志
//比如使用video播放視頻時(shí)經(jīng)常會(huì)發(fā)送Range為0- 的范圍只是為了獲取視頻大小,之后就中斷連接了
log.info(e.getMessage());
}
} else {
throw new RuntimeException("文件路勁有誤");
}
outputStream.flush();
} catch (Exception e) {
log.error("文件傳輸錯(cuò)誤", e);
throw new RuntimeException("文件傳輸錯(cuò)誤");
}finally {
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
log.error("流釋放錯(cuò)誤", e);
}
}
if(targetFile != null){
try {
targetFile.close();
} catch (IOException e) {
log.error("文件流釋放錯(cuò)誤", e);
}
}
}
}
到了這里,關(guān)于Java后端接口返回視頻流,使用video組件播放視頻,實(shí)現(xiàn)分段下載的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!