Java断点续传超大视频-H5的video标签-踩坑过程笔记

背景:心血来潮想用jsp调用后端接口播放视频,因为一直不成功历尽艰难,故有此记。

一、Java代码

public void executeDown() {
	// 1-1.判断资源是否存在:
	if (file == null || !file.exists()) {
		response.setStatus(HttpServletResponse.SC_NOT_FOUND);// 不存在直接返回404
		return;
	}

	// 1-2.判断是否超出大小:(正常 and 系统配置最大 and 固定最大)
	fileSize = file.length();
	if (fileSize <= 0) {
		response.setStatus(HttpServletResponse.SC_NOT_FOUND);// 大小有误,返回404
		return;
	}
	if (fileSize > Math.min(FILE_MAXSIZE, maxSize)) {// 超出配置大小,表示无法处理了
		response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);// 返回503
		return;
	}

	// 1-3.判断是否断点续传:
	Long[] ranges = readRequestRange();
	if (ranges == null) {
		execute(); //另外一个直接回传的方法
		return;
	}
	long startByte = ranges[0], endByte = ranges[1];
	if (endByte == 0) {
		execute();
		return;
	}

	long time_log = System.currentTimeMillis();

	// 1-4.提取访问日志相关:
	fileSize = file.length();
	StringBuffer logs = "这里是一段日志内容而已,如IP、访问地址、文件大小、耗时等";//getFormatLogs();
	String state = "SUCCESS";

	// 1-5.执行返回数据:
	RandomAccessFile raf = null;
	OutputStream outPut = null;
	try {
		response.reset();
		response.resetBuffer();
		// 通知客户端允许断点续传,响应格式为:Accept-Ranges: bytes
		response.setHeader("Accept-Ranges", "bytes");
		// 通知客户端使用的是断点续传协议,即返回206状态:
		response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

		// 文件名
		String fileName = file.getName();
		// 文件类型
		String contentType = StringUtils.toString(request.getServletContext().getMimeType(fileName), "application/octet-stream");
		// 解决下载文件时文件名乱码问题
		byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);
		String downloadFileName = new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);
		response.setHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
		// 响应的格式是:
		response.setContentType(contentType);
		response.addHeader("Content-Length", String.valueOf(endByte - startByte + 1));
		// 重点1啊 [要下载的开始位置]-[结束位置]/[文件总大小]
		response.setHeader("Content-Range", "bytes " + startByte + "-" + (fileSize - 1) + "/" + fileSize);

		outPut = new BufferedOutputStream(response.getOutputStream());
		raf = new RandomAccessFile(file, "r");
		// 跳过已下载字节
		raf.seek(startByte);
		endByte = Math.min(endByte, fileSize);
		byte[] bs = new byte[1024];
		while (true) {
			long fp = raf.getFilePointer();
			if (raf.read(bs) == -1)
				break;
			outPut.write(bs);
			if (fp >= endByte)
				break;
		}
		outPut.flush();
	} catch (org.apache.catalina.connector.ClientAbortException e) {
		// 捕获此异常表示拥护停止下载
		state = "WARN:" + e.getMessage();
		// e.printStackTrace();
	} catch (Exception e) {
		//("获取文件失败,编号100093,详细:", e);
		state = "FAIL:" + e.getMessage();
	} finally {
		StreamUtils.closeCloseable(outPut);
		StreamUtils.closeCloseable(raf);
	}

	// 1-9.写访问日志:
	saveFormatLogs(new StringBuffer(logs.toString().replace("#STATE#", state).replace("#TIME#", System.currentTimeMillis() - time_log + "ms")));
}

private Long[] readRequestRange() {
	String range = RequestUtils.getRequestHeaderByKey(request, "Range");
	if (StringUtils.isBlank(range) || !range.contains("bytes=") || !range.contains("-"))
		return null;
	range = range.substring(range.lastIndexOf("=") + 1).trim();
	String[] ranges = range.split("-");
	// 判断range的类型
	long startByte = 0, endByte = 0;
	if (ranges.length == 1) {
		if (range.startsWith("-")) { // 类型一:bytes=-2343
			endByte = StringUtils.toLong(StringUtils.toString(ranges[0]).trim());
		} else if (range.endsWith("-")) { // 类型二:bytes=2343-
			startByte = StringUtils.toLong(StringUtils.toString(ranges[0]).trim());
			endByte = startByte + 1024 * 512;
		}
		endByte = Math.min(fileSize, endByte);
	} else if (ranges.length == 2) { // 类型三:bytes=22-2343
		startByte = Long.parseLong(ranges[0]);
		endByte = Long.parseLong(ranges[1]);
	}
	if (endByte == 0)
		return null;
	return new Long[] { startByte, endByte };
}

具体踩坑记录请查看文末《附件2:Java配合video标签断点续传踩坑记录

二、HTML代码

HTML5中video元素详解

1、使用示例

  • 最简单的:

    <video class="ep-video" id="ep-video" controls="controls" autoplay 
    src="https://www.runoob.com/try/demo_source/mov_bbb.mp4"></video>
    
  • 推荐使用:

    <video class="none ep-video" controls autoplay>
    	<source src="http..." type="video/mp4">
    	<source src="http..." type="video/ogg">
    	<source src="http..." type="video/webm">
    	<object data="http..." width="320" height="360">
    		<embed src="http..." width="320" height="360">
    	</object>
    	<p>你的浏览器不支持 <code>video</code> 标签.</p>
    </video>
    
  • 附加的:

    <video
        controls
        autoplay
        loop
        preload="auto"
        poster="img/popup-img.png"
        webkit-playsinline="true"
        playsinline="true"
        x5-video-player-type="h5"
        x5-video-player-fullscreen="true"
        x-webkit-airplay="allow" 
        x5-video-orientation="portraint"
        style="object-fit:fill">
        <!-- ...... -->
    </video>
    

具体参见《附件1:video特殊参数说明》:

2、相关JS方法

  • 获取对象(与js同,老猿跳过)

    let video = document.querySelector('.player'); //class name is player
    let video = document.getElementById('myvideo'); //id is myvideo
    let video = $('.player')[0];
    let video = $('#myvideo')[0];
    
  • 监听事件
    事件请查看《video标签所有事件

    video.addEventListener('error', myMethod);
    video.onerror = myMethod;
    

    使用jQuery:(新大陆)

    var event = 'progress suspend abort error emptied stalled play pause loadedmetadata waiting canplay canplaythrough seeking seeked timeupdate ended ratechange druationchange volumechange';
    $(video).bind(event, function(event) {
    	console.log(event.type); //type 是指触发了哪个事件
    	if (event.type == 'error') {
    		console.log(event);
    		console.log(video.error);
    		//PIPELINE_ERROR_READ: FFmpegDemuxer: data source error
    	}
    });
    

八、附件1:video特殊参数说明

属性说明
controls显示标准的 HTML5 视频/音频播放器控制条、控制按钮。
autoplay让文件自动播放。
loop让文件循环播放。
preload属性是用来缓存大体积文件的。它有三个可选值:“none” 不缓存、“auto” 缓存、“metadata” 只缓存文件元信息
poster视频封面
webkit-playsinlin=“true”这个属性在 ios 10中设置有用,其他的目前还不起作用,让视频在小窗内播放,也就是不是全屏播放
playsinline=“true”IOS微信浏览器支持小窗内播放
x5-video-player-type=“h5”启用H5播放器,是wechat安卓版特性
x5-video-player-fullscreen=“true”全屏设置,设置为 true 是防止横屏
x5-video-orientation=“portraint”播放器屏幕的方向,landscape横屏,portraint竖屏,默认值为竖屏。
source标签是为了能够兼容各种浏览器对不同媒体类型的支持,我们可以用多个source元素来提供多个不同的媒体类型。支持mp4格式视频流的浏览器可以播放mp4文件,如果不支持,可以播放ogg文件。
codecs=dirac, speex是用来指定播放使用的解码器(codecs); 这样就可以更精确的让浏览器如何播放提供的视频。

九、附件2:Java配合video标签断点续传踩坑记录

1、笔者踩坑

搜索了好多文章,都是说用 startByte - endByte ,结果看了别的网络请求的数据才发现,end应该是size-1,一试还真的是。

之前以为各种header或Access的问题,导致浪费了两三天,emmm…

  • 错误写法:

    // [要下载的开始位置]-[结束位置]/[文件总大小]
    response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + fileSize);
    
  • 正确写法:

    response.setHeader("Content-Range", "bytes " + startByte + "-" + (fileSize - 1) + "/" + fileSize);
    

原理也没去查,也没搞明白,反正size-1是正确的,啊哈~

2、网友踩坑

看文章发现其他网友还有以下踩坑:

  • 没有传headers:
    // 通知客户端允许断点续传,响应格式为:Accept-Ranges: bytes
    response.setHeader("Accept-Ranges", "bytes");
    // 通知客户端使用的是断点续传协议,即返回206状态:
    response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    
  • 没有传标识:
    response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
    // 响应的格式是:
    response.setContentType(contentType);
    response.addHeader("Content-Length", String.valueOf(endByte - startByte + 1));
    // [要下载的开始位置]-[结束位置]/[文件总大小]
    response.setHeader("Content-Range", "bytes " + startByte + "-" + (fileSize - 1) + "/" + fileSize);
    
    

3、附文件大众读法

int b;
while (raf.getFilePointer() < endByte) {
	if ((b = raf.read()) == -1)
		continue;
	outPut.write(b);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值