一、为什么用Canvas来播放视频

1、用更高帧频来替代视频本身的timeupdate事件。因为视频的timeupdate事件是在浏览器保证视频正常加载并正常渲染的情况下才去考虑触发的,因此就有了250ms触发一次timeupdate事件的说法,也就是一秒四帧。当然,视频播放肯定会比这个帧频高很多。

2、避免在小屏幕iOS设备上全屏播放视频。苹果的Safari Developer Library中《Safari HTML5 Audio and Video Guide》提到,当浏览器在诸如iPhone和iPod这样的小屏幕iOS设备上播放视频的时候,会全屏播放。所以我们尝试用Canvas来替代Video播放视频,以避开视频自动全屏的问题,因为你有时候想保留自己的控制条等斯交互UI。

二、怎么用Canvas来播放视频

思路是这样的:视频元素是必不可少的,但是它仅仅只是被创建但并不显示,甚至都没有必要加到Dom树里更没有必要渲染,省下的资源用来渲染Canvas倒不错。为了能在Canvas中流畅地播放视频,我们要用到requestAnimationFrame方法,关于这个方法的更多细节请自行百度一下吧,很多文章会告诉你,我们为什么不使用setInterval和setTimeout而使用被他们催生出来的requestAnimationFrame。requestAnimationFrame大约按60fps的帧频调用,这已经足够流畅了。下面来看一下这个Demo的HTML代码吧:

canvasPlayVideo.html :

<body>
	<button id="playBtn">play</button><br/>
	<canvas id="canvasVideoPlayer" width="500" height="300"></canvas>
</body>

只有一个播放按钮和一个Canvas元素。接下来我们用JavaScript来创建我们的Video元素:

canvasPlayVideo.js  part_1 :

	var canvas = $('#canvasVideoPlayer')[0],
		context = canvas.getContext('2d'),
		$playBtn = $('#playBtn'),
		video = document.createElement('video'),
		$video = $(video),
	$video.on('loadeddata', function(){
		console.log('loaded video data!');
		canvas.width = video.videoWidth;
		canvas.height = video.videoHeight;
	}).on('timeupdate',function(event){
		console.log('timeupdate: '+video.currentTime);
	});

	video.preload = 'auto';
	video.src = 'http://www.w3schools.com/html/mov_bbb.mp4';

在上面这部分代码中,我们用document.createElement方法创建了一个video元素,当然你也可以用jQuery的方法:

$video = $('<video />');
video = $video[0];

之后我们监听了video的loadeddata事件,其实我们在loadedmetadata事件触发时就可以获取到视频的尺寸,这里只是为了保证canvas尺寸被改变后立即可以播放。同时监听video的timeupdate事件的目的是方便我们在控制台与requestAnimationFrame的触发频率作对比。最后两行代码,允许视频预加载,并赋值视频源。接下来看看我们在视频真正播放时候要做的事情:

canvasPlayVideo.js part_2 :

	$playBtn.on('click',function(){
		if(video.paused){
			$playBtn.text('Pause');
			video.play();
			window.RAF(playVideo);
		} else {
			video.pause();
			$playBtn.text('Play');
		}
	});

	function playVideo() {
		if(video.ended || video.paused) return;
		context.drawImage(video, 0, 0, canvas.width, canvas.height);
		console.log('requestAnimationFrame: '+video.currentTime);
		window.RAF(playVideo);
	};

这部分代码定义了$playBtn的开关功能。注意在视频调用video.play();方法后我们调用了window.RAF(playVideo);方法。我们在playVideo中的定义就是每帧要做的事情,即把当前帧的视频画面绘制到canvas上。那么window.RAF()就是保证能按60fps帧频调用playVideo的方法,只不过我们把window.requestAnimationFrame赋值给了window.RAF,这么做是出于兼容性的考虑,这个方法封装到RAF.js文件中,

RAF.js :

;window.RAF = (function(){
	return 	window.requestAnimationFrame		||
			window.webkitRequestAnimationFrame	||
			window.mozRequestAnimationFrame		||
			window.msRequestAnimationFrame		||
			function(callback,element){
				window.setTimeout(function(){
					callback(+new Date(),element);
				},1000 / 60);
			};
})();

至此,在canvas中播放视频的核心代码就是这些了。实际的效果如下(ps: 我以前怎么就没想到用iframe把demo嵌进来???):

三、不要用Canvas来播放视频

好吧,我承认,前面说的“为什么用Canvas来播放视频”中的两点论据都是扯蛋的。

首先第1点,我们并不需要专门创建一个canvas来获取更高的帧频。如果真有这种需求也应该用requestAnimationFrame方法直接替换掉对视频timeupdate事件的监听就好了。比如我们想做个视频中的人脸识别,或者我们想给视频加个滤镜,这些东西想想就让人兴奋。

第2点,不要妄想在移动端用canvas播放视频了,至少目前别妄想。在移动端,只要你不渲染video,那么你就不会获取到这个视频的画面。也就是说你至少要让这个视频出现在Dom树中并正常渲染播放,canvas.drawImage(video……)才能取到画面。那,只要它display:block;并播放了,iPhone上就会全屏,但这显然是矛盾的。苹果也说了,最适合播放视频的元素是video而不是canvas。而android上的chrome等浏览器同样无法获取到未被渲染的视频画面,这很容易说得通,我为什么要去渲染一个根本看不见的东西而消耗我移动平台本来就少得可怜的资源?

 

好了,就到这里,以失败告终!

如果有人解决了,记得告诉我,我会感激不尽的!