在“loadedmetadata”事件触发后,接下来的重要事件就是“canplay”,这个是浏览器用来确认是否已经装载足够的媒体信息到浏览器中能播放一个事件。如果preload属性设置为“auto”,则在数据装载后大概几秒,“canplay”事件就会触发;如果preload设置为“metadata”或者是“none”,则直到播放开始时才会触发。这个规则对Chrome浏览器来说是例外的,在Chrome中,即使只是加载元数据阶段,“canplay”事件也会触发。
同时也有仅接着的事件叫“canplaythrough”,这个事件其实是给浏览器根据当前网络状况去判断是否已经加载了足够的媒体片断而支撑基本的播放。所以这个事件要在数据被预加载大概几秒后才会触发。
在实际情况下,“canplaythrough”事件基本是没啥作用的,因为Safari根本不会触发这个事件,而Opera和Chrome则在“canplay”事件触发后马上就触发这个事件,只有FireFox和IE 10实现了这个事件。
所以实际上,开发者最应该关心的是监视“progress”事件,以了解媒体到底加载了多少(必要的时候可以计算媒体的下载速度)。
Progress事件
接下来我们重点学习下progress事件。该事件在数据正在下载的时候会触发。所以当preload设置为none的时候,progress事件在知道播放事件真正开始前是不会触发的。如果preload设置为“metadata”,则该事件会短暂触发大概几秒,然后停止,直到真正的播放行为开始时才触发;如果preload设置为“auto”,则会触发一直直到整个媒体文件下载完毕。
无论preload如何设置,一旦用户开始进行播放的行为,则浏览器会开始下载整个媒体文件,则会持续触发progress事件,一直直到整个文件下载完毕,即使视频被暂停。
当数据下载后,则我们需要了解如何用时间表达这个progress的事件,则对接下来深入了解progress事件是十分重要的。当数据开始加载时,会创建表示媒体播放时间的范围,比如一旦头10秒的数据已经加载,则以数组的方式记录了开始和结束时间,如下的方式表示:
当然,实际上是会有多个时间范围存在的,比如用户手工使用播放器的进度条去移动到想要的位置,则浏览器会忽略当前的时间范围而加载新的部分而不是象 Flash那样重新加载两个时间点之间的部分。
比如我们加载10秒的视频后,跳到两分钟后的片断继续播放另外的10秒,则有两个时间范围,用如下的方式表达:
[0,10],
[120,130]
]
假设用户这个时候又回看旧的片断,则继续又增加一个时间范围的数组,如:
[0,10],
[60,70],
[120,130]
]
如果从60秒开始看到130秒结束,则最后的时间范围合拼为:
[0,10],
[60,130]
]
上面的例子只是简单说明在播放媒体中,时间的范围是如何运作的,当然实际上的数据保存不是这个样子。实际上媒体都有一个.buffered对象以表示时间范围。.buffered对象有一个.length长度属性表示有多少段时间范围,并且有对应的start()和end()方法,所以我们可以将buffered的数据转换为二维数组,如下代码所示:
for(var i = 0; i < media.buffered.length; i ++)
{
ranges.push([
media.buffered.start(i),
media.buffered.end(i)
]);
最后,我们可以自定义progress事件如下:
{
var ranges = [];
for(var i = 0; i < media.buffered.length; i ++)
{
ranges.push([
media.buffered.start(i),
media.buffered.end(i)
]);
}
}, false);
有了这个方法,则我们可以针对progress事件进行一些定制开发。比如我们可以实现一个简单的播放视频,并且提供一个额外的进度条,在视频加载过程中能看到其进度。其实际的运行效果请参考:http://jspro.brothercake.com/media-events/progress.html ,下面是一个运行效果截图:
下面选取其中的代码片断讲解其核心原理:
{
//获得buffered数据
var ranges = [];
for(var i = 0; i < media.buffered.length; i ++)
{
ranges.push([
media.buffered.start(i),
media.buffered.end(i)
]);
}
//获得在容器中的当前进度
var spans = progress.getElementsByTagName('span');
//如果还没有加载完毕则继续加载
while(spans.length < media.buffered.length)
{
progress.appendChild(document.createElement('span'));
}
while(spans.length > media.buffered.length)
{
progress.removeChild(progress.lastChild);
}
for(var i = 0; i < media.buffered.length; i ++)
{
spans[i].style.left = Math.round
(
(100 / media.duration) *
ranges[i][0]
)
+ '%';
spans[i].style.width = Math.round
(
(100 / media.duration) *
(ranges[i][1] - ranges[i][0])
)
+ '%';
}
}
在上面的代码段中,使用的是设置的宽度去代表进度条的每一个格的宽度,首先获得buffered数据,存放到数据ranges中,然后判断媒体文件是否加载完毕,如果还没加载完毕,则继续往DOM中添加标签,而这个span标签的宽度和样式则是通过上面的代码按百分比计算出来。
最后,用户可以通过http://jspro.brothercake.com/media-events/events.html的例子,学习到本文中提到的在媒体加载前、加载中和加载后浏览器的相关事件,在这个例子中会输出相关的日志。