富媒体资源元素
2、快速上手音视频开发
2.1、<audio>元素的常用应用指南
目前<audio>元素多用在交互简单的场景中,例如一个运营活动页面,用户希望在体验的时候伴随着背景音乐,这里的背景音乐就特别适合使用<audio>元素实现,代码如下:

当我们需要播放此音乐的时候,执行play()方法就可以了。
对于音频格式的选择,不要犹豫,一定选择MP3格式,无他,其兼容性最好,当然,其他两个常用的音频格式也并非一无是处。
目前Web中主流的音频格式就只有如下三种:
- MP3
- WAV
- OGG
其中,OGG音频格式的优点是体积特别小,因为这种音频格式是有损压缩的,照理来说体积小特别适合在Web中传输,可惜OGG格式的兼容性不佳,iOS Safari浏览器到现在都不支持它,不过,我们可以用<source>元素渐进使用它:

这样可以保证大多数的浏览器加载的是小尺寸音频,兼顾了功能和流量开销。
WAV格式的优点是:这种格式是目前唯一一种可以使用纯JavaScript代码构建的音频格式。例如,我们希望对本地的MP3文件进行剪裁或拼接,然后将其重新保存到本地,此时就离不开WAV格式。WAV格式的兼容性还可以,除IE以外的浏览器都支持它,不过它的文件尺寸通常是最大的。
于是,最终权衡兼容性、体积等因素,MP3就是最佳的Web音频使用格式了。
对了,说一个有些人可能不知道的冷知识,MP4视频也是可以作为音频文件使用的,因为MP4视频也包含ACC编码音频,不过MP4的体积比纯音频大太多了,所以不建议这么使用。
默认情况下,<audio>元素是隐藏的,特别适合背景音乐,如果希望音频文件有播放界面,则需要设置controls属性,例如:

视觉效果可以参考下图,截图自Chrome浏览器,设置了静音。

controls属性只能用在对音频播放器样式要求不高的场景。和图片元素不同,<audio>元素的样式是很难完全自定义的,因为里面的控件元素太多了,CSS规范指定的那些伪类选择器目前浏览器的支持情况并不好。
关于<audio>元素的使用,了解上面这点知识就足够了,虽然其还有几十个属性、十多种事件和数个方法,但请无视,不是说这些属性和方法没有用,而是没有机会用,由于<audio>元素在移动端尤其iOS设备中有诸多问题,导致其根本无法应对复杂的音频交互,因此,在实际生产环境中,我们都是使用Web Audio API或基于Web Audio API开发的JavaScript组件去开发音频应用的。
这样做有什么问题呢?比如很简单的preload预加载问题。<audio>元素支持一个名为preload的预加载属性,可以让页面载入后,提前加载一部分的音频数据,这样播放的时候就不需要等待与缓存。然而,就是这么个简单的属性,不知道为何在iOS Safari浏览器下是无效的(桌面端无此问题),这就导致用户点击播放按钮后,要等一会儿才出现声音,体验非常不好。通常的解决方法是,在第一次触摸的时候,音频静音,同时触发音频play(),然后很快再迅速触发pause()。
然而在Chrome 50之后的版本中,<video>或者<audio>执行play()方法后返回的是一个Promise,也就意味着play()方法变成了一个异步过程。如果先执行play()方法,然后立即执行pause()方法,则会报错:
Uncaught (in promise) DOMException:The play()request was interrupted by a call to pause().
很简单的一个预加载需求就变得特别麻烦,因此,只要音频涉及复杂交互或者多音频同时播放场景,千万不要使用<audio>元素,尤其是在移动端项目中,所以<audio>元素那些属性和方法的学习价值也就不高了。
2.2、<video>元素的常用应用指南
<video>元素的本意是播放视频,但毕竟大多数的网站都不是视频网站,因此,在实际的Web开发中,<video>多用来实现特效。例如,官网首页展示的星空大图,其实是一个视频,又例如运营活动页面的礼花绽放的效果,其实也是用视频元素实现的。
具体实现过程如下。准备一个纯黑色背景的礼花绽放的视频素材,如图所示。然后覆盖在背景素材上,实际开发中的背景就是我们的网页,这里我使用夜景照片示意,然后设置视频的混合模式为screen就可以了,代码如下:


此时就可以看到夜空中礼花绽放的动态效果了,如图所示。

有对应的演示页面,你可以通过在浏览器中输入https://www.htmlapi.cn/6/2-1.html访问来体验。
在上面的例子中,<video>元素出现了一个令不少人感觉陌生的属性playsinline,值得专门讲一下。
2.2.1、playsinline属性
playsinline属性可以让视频像图片一样,安安静静地内嵌在Web网页中。如果不设置该属性,那么用户在触碰视频的时候会调用系统内置的播放进行交互,显然,把视频当作炫酷动效的这种场景是不需要出现播放进度或者下载按钮这样的内置交互功能的,所以playsinline属性比看起来的要实用得多。
不过在实际使用的时候,考虑到各种内嵌的WebView类型,会有很多私有的设置,下面这段是我经常使用的属性组合,用了很多年,有些设置怕是已经没什么用了:

不过playsinline属性并不是万能的,部分Android手机内置的默认浏览器无视playsinline属性,自以为是地对<video>元素进行了自定义处理,给前端开发带来了不必要的困扰,例如:
- 无法隐藏下载、全屏等按钮,如图所示。
- 无法实现圆角效果。
- 无法在视频上显示弹幕。

这个问题不好解决,或者说解决成本比较高,等于要再造一个视频播放器,具体来说就是使用JavaScript对视频资源进行解码,然后将<canvas>元素作为播放容器,使用Web Audio API播放音频来模拟视频播放效果。
其中,MPEG1视频&MP2音频解码可以使用JSmpeg,H.264视频解码可以使用Broadway或者使用mp4box.js,当然,如果不考虑兼容性,音视频最好的编解码解决方案一定是Webcodecs API,这又是一块成体系的知识,为了不让读者望而生畏,我做了个演示页面,你可以通过在浏览器中输入https://www.htmlapi.cn/6/2-3.html访问来体验,感兴趣的读者可以了解一下,这里就不继续展开详述了。
2.2.2、视频格式
适合在Web中传播的视频格式只有两个,一个是WEBM,另外一个是MP4。
WEBM格式最大的优点是开源的编码格式,在很早的时候就支持使用纯JavaScript代码生成了,可以说在Webcodecs API出现之前,它是浏览器生成视频最好的技术路径之一,很成熟,我特意做了个演示页面供大家参考,感兴趣了解的读者可以通过在浏览器中输入https://www.htmlapi.cn/6/2-2.html访问来体验。
可惜,不知为何,iOS Safari对WEBM格式的视频的支持一直不太好,影响了此视频格式在Web中的传播。因此,目前在Web中主流的视频格式就是MP4格式,并且是H.264编码的视频格式,H.264编码又包括avc1.42001f、avc1.4d0028等10种编码,还有包含视频元信息的Moov盒子,Moov盒子是最外层的盒子,里面还有其他盒子,这些盒子数量多(20多个)、层级深(多达6层),颇为复杂。另外,Moov盒子的位置不同还会影响Web的性能,如果在生成MP4视频的时候,把Moov盒子放在了后面,就会出现三次请求的问题,如图所示。

原理如图所示。

可以使用ffmpeg批量处理:

也可以使用handbrake这个免费的开源软件进行优化,勾选图中箭头所示的选项,然后重新导出一遍即可。

总之,视频领域的水很深,需要长期的积累和技能训练才能在这一领域有所建树。当然,如果你只是应付日常开发需求,那么本书介绍的这点知识就足够用了。
2.2.3、其他说明
<audio>元素支持的属性和方法<video>元素也都支持,实用程度差不多,唯一不同的就是<video>元素多了一个poster属性,这个属性的作用很简单,设置视频的预览封面图,代码如下:

需要注意的是,此封面图的尺寸要尽可能小,千万不要使用好几MB的大图,那没有任何意义,因为一旦视频成功预加载数据,封面图就会被视频第一帧的画面替换掉,图片如果太大,恐怕封面图还没加载好,视频画面就加载出来了。
至于如下表所示的事件,大家大致了解一下即可,当需要使用<video>元素做一些事情的时候,可以翻到这一页看看。比如,我们希望获取视频任意位置的画面帧,则可以使用timeupdate事件,代码如下:



如果你有播放视频的需求,同时对视觉表现有要求,那么不要犹豫,直接使用Video.js(https://github.com/videojs/video.js)这个项目。
应该没有遗漏了,接下来就针对性地讲讲自动播放、倍速播放、画中画及外挂字幕的问题。
2.3、聊聊音视频的自动播放
如何让音频文件自动播放呢?答案是没有办法!除非将音量设置为0,或者设置了静音(设置muted属性),否则绝无可能。
其实在浏览器支持HTML 5<audio>、<video>元素的很长一段时间(大约有7~8年)里,设置自动播放的autoplay属性一直是生效的,在2017年、2018年前后,从移动端开始,便禁用了自动播放,再然后是桌面端浏览器也禁用了这一功能。我还记得是2018年,当时Chrome禁用了自动播放,遭到了很多开发者抗议,后来又恢复autoplay(给大家缓冲时间),随后没多久Chrome又不支持自动播放了,然后就永远禁用自动播放了。
一开始禁用自动播放的时候,还是有一些很极客的手段绕过限制的,例如<img>加载完毕触发,不过很快这些手段全都无效了。目前,对于Web网页,如果你希望媒体资源自动播放声音,必须要点击(如以下代码所示)或者触摸页面,或者以键盘访问页面,否则浏览器是不会自动播放的,使用JavaScript代码触发play()方法也不行。

原因不难理解,网页在没有警告的情况下自发地发出声音,显然会让用户不愉快,体验不太好。因此,浏览器通常只允许在特定情况下成功地进行自动播放。对于视频而言,如果没有音轨,或者静音,是有可能触发自动播放的,但也不一定,要看浏览器的策略。
浏览器提供了专门的API用来判断音视频元素是否支持自动播放,该API名为Navigator.getAutoplayPolicy(),假设视频元素的DOM对象变量是video,则我们可以进行类似下面的判断处理:


不过目前getAutoplayPolicy()方法的兼容性还不够好,只有Firefox浏览器支持,故请谨慎使用。
另外,使用Web Audio API创建的音频资源也遵循浏览器的自动播放限制策略。
2.4、解密倍速播放的实现
目前所有的视频播放控件都会有一个倍速设置的选项,如图所示,请问你知道该选项是如何实现的吗?

看起来很高大上,其实很简单,使用playbackRate属性控制一下就好了。
playbackRate属性是一个可读写的控制音视频播放速率的属性,语法如下:

速率范围
Gecko内核浏览器的速率范围是0.25到4.0,超出这个范围就静音。对于Chrome浏览器,我自己实地测试了一下,速率上限居然可以达到16,如图所示。

2.5、视频的画中画技术
很多看起来很"高大上"的技术实现其实都出乎意料地简单,视频的画中画效果就是其一。
所谓"画中画",指的是可以让视频元素从Web网页中脱离出来,直接作为一个独立的软件窗口在操作系统中显示,例如右键点击任意一个Web视频元素,就可以看到菜单选项中有一个名为"画中画"的选项,如图所示。

点击之,就可以看到视频窗口变小,同时定位于显示器窗口的最下角,位置可移动,尺寸可拉伸,就算浏览器窗口最小化,视频依然存在,如图所示。

于是,我们可以一边写代码,一边看网页视频。实现这么一个高级效果,也只需要两三行代码。
假设变量video就是我们的<video>视频元素,则要进入画中画模式,直接输入一行代码:
退出画中画模式,也只用一行代码:

为了方便我们监听视频的播放状态,还提供了2个事件API接口,如下:

最后,还提供了一个PictureInPictureWindow对象,也就是画中画窗口对象,包含width、height和onresize等属性,例如:


width、height表示小窗口现在的高度和宽度,onresize属性值是一个函数,可以监听小窗口尺寸的改变。PictureInPictureWindow对象的获取在画中画响应事件的event对象中,例如:

以上,基本上就是HTML video Picture-in-Picture画中画技术的全部应用程序编程接口了,两个方法(进入和退出)、两个事件(进入和退出)及一个对象(画中画窗体对象)。
相信大家学起来一定都毫无压力。
还有两点需要注意:
- 可以使用document.pictureInPictureElement判断当前浏览器是否进入了画中画模式,返回当前的画中画元素是什么。
- 还支持名为:picture-in-picture的CSS伪类,可以匹配进入画中画模式的视频元素。
2.6、<track>元素与WebVTT字幕的显示
HTML 5 Video视频支持外挂字幕,文件后缀名是.vtt,被称为WebVTT格式,是专门的Web字幕格式。其使用很简单,用一个<track>元素即可,例如:

此时,在Chrome浏览器下就会出现如图6-37所示的效果,见画面截图下方的字幕。

你可以通过在浏览器中输入https://www.htmlapi.cn/6/2-4.html访问来体验外挂字幕效果。
其中,加载的.vtt文件内容如下:

以上就是一个文本文件且格式很简单,一开始声明一下WebVTT,然后设置视频时间范围,下一行就是字幕内容,时间可以精确到毫秒,但通常0.5秒足矣。
实际上WebVTT规范可以有更多规则,具体参见官网文档(https://w3c.github.io/webvtt/)。
.vtt文件的MIME type是text/vtt。在Chrome和Firefox浏览器下,.vtt字幕是可以无障碍加载显示的,但是对于IE10+浏览器,虽然也支持.vtt字幕,但是,却需要定义MIME type,否则会无视WebVTT格式。比较简单的方式就是在字幕所在文件夹下面添加一个.htaccess文件,里面写上AddType text/vtt.vtt。
通常我们保存在电脑中的外挂字幕都不是VTT格式的而是常见的如SRT格式的,当需要将这些格式用在Web中时,使用工具转换一下就可以了,可以在搜索引擎中搜索srt2ttf找到对应的工具。
2.6.1、深入了解<track>元素
<track>元素完整的写法如下:

这里出现了几个属性,分别是kind、label、srclang和default。其中:
-
(1)kind
kind属性用来表明文字轨迹的作用,默认值是subtitles,也就是说,如果没有添加kind属性,kind属性值会被认为是subtitles;但是,如果有kind属性,但属性值不合法,则其会被认为是metadata。其中,合法的属性值包括下面这些。
- subtitles:就是我们平常看电影、看动漫时下面出现的字幕,一般是翻译或者采访时口音不清的内容的字幕显示。有时候还会标注一些说明,例如显示人物姓名和身份、当前场景地等。
- captions:这里的captions专指隐藏式字幕(ClosedCaptioning, CC),释义说明如下。
隐藏式字幕是电视节目或影碟中为有特殊情况或者需求的 观众而准备的字幕,例如观众有听力障碍,或者需要在无音条件下观赏节目。此时字幕中可使用一些解释性的语言来描述节目内容。
根据我的测试,从效果而言,subtitles和captions几乎看不到任何区别,应该是在语义上有区别,或者字幕性质上有区别。subtitles主要对人说的话进行翻译或确认;而captions不仅需要对人说的话的内容进行提示,紧张的背景音乐,或者汽车吱吱作响的刹车声都需要在字幕中描述出来。这样,即使静音用户也能知道视频里到底发生了什么。我想,经常看国外影视作品的小伙伴肯定会有类似的字幕体验,有的只有对话字幕,有的还包括其他信息,这就是subtitles和captions的区别。
如果我们对<video>元素设置controls属性,则会看到"CC"标示按钮,如图所示。

点击按钮,我们不仅可以切换字幕(需要同时设置多个<track>元素),还可以关闭字幕,如图所示。

- descriptions:对视频内容的文本描述,可以让盲人用户知道这个视频描述了什么,虽然他看不见。如果设置kind属性值为descriptions,则VTT文件内容不会在屏幕上出现。在视频地址不可见的场景中,其也有类似作用。
- chapters:用户浏览媒体资源时出现的章节标题。
- metadata:元信息。用户不可见,它是给脚本用的。例如,自定义字幕效果,但又不希望视频自己把字幕效果呈现出来。
-
(2)label
点击"CC"按钮选择字幕时出现的文字。可参见上图所示的字幕关闭截图。其中的"中文字幕"和"中文字幕(修正)"就是使用label属性设置的。
-
(3)srclang
VTT文本信息使用的语言。例如,中文zh,英文en。MDN上说,如果kind属性被设置为subtitles,那么srclang必须被定义。但是,根据我的测试,不定义srclang也没有任何异常,大家可以注意一下。
还有一点,我以为srclang和HTML页面的lang设置有关,如会自动选中对应语言的字幕,测试后发现我想多了。
-
(4)default
我开始以为default是默认优先显示的字幕,后来发现不是。default指的是默认会显示的字幕。例如,如果两个<track>元素都没有default属性,则都不显示,需要用户手动调出。另外,default只能出现在一个<track>元素上。
一个<video>视频标签中可以有多个<track>元素,不同<track>元素的kind等属性值也都可以不一样。
2.6.2、视频字幕的样式控制
CSS中有专门的伪元素::cue可以控制字幕的样式。可以控制的CSS属性包括:
- color
- opacity
- visibility
- text-decoration及相关属性
- text-shadow
- background及相关属性
- outline及相关属性
- font及相关属性,包括line-height
- white-space
- text-combine-upright
- ruby-position
例如有如下相关设置:

则字幕直接就是白字黑边效果,如图所示。

除此之外,WebVTT还支持一些HTML标签进行样式控制,常见的有声音<v>标签、颜色<c>标签、加粗<b>标签、倾斜<i>标签(如图所示)、下画线<u>标签、<ruby>标签、<lang>标签等。

其中声音<v>标签比较常见,我们可以通过设置属性区别是谁说的话,例如:

还可以用如下CSS代码进行红绿颜色区分:

此时字幕效果就是,韩梅梅说的话是红色的,而李雷说的话则是绿色的。
我们还可以直接用标签控制样式(注意,仅支持上面提到的这几种标签),例如:

或者类名:

其中的规则还是挺多的,限于篇幅,就不一一展开介绍了。基本上,上面这几种情况已经够用了。