Web 视频播放前前后后那些事(1)

这是因为当时没有其他方法可以在浏览器上流式传输视频。作为用户,您可以选择安装Flash或Silverlight之类的第三方插件,还是根本无法播放任何视频。

为了填补这个空白,WHATWG 开始研究 HTML 标准的新版本,其中包括本地的视频和音频播放。苹果公司在其产品上禁用 Flash 后,这一趋势更加明显。而该标准后面成为现在的 HTML5 的标准。

因此,HTML5除其他新的标签外,将 <video> 标签带到当前 Web。

这个新标签允许您直接从HTML链接到视频,就像标签对图像所做的一样。

但是从媒体网站的角度来看,使用简单的类似于 img 的标记似乎不足以替代我们的优质Flash:

  • 我们可能想即时切换多种视频质量(例如YouTube)以避免缓冲问题

  • 直播是另一个用例,看起来很难实现

  • 像Netflix一样,在内容流式传输时根据用户的喜好更新内容的音频语言又如何呢?

值得庆幸的是,由于HTML5规范带来的好处,所有这些问题都可以在大多数浏览器上以本地方式解决。本文将详细介绍当今的技术是如何做到的。

Video 标签

如前文所述,在HTML5中,链接到页面中的视频非常简单。您只需在页面中添加具有很少属性的视频标签即可。

My Video

HTML 将允许您的页面直接在支持相应编解码器的任何浏览器上播放some_video.mp4。

类似像这样:

HTML5 视频标签还提供了各种API,例如播放,暂停,搜索或更改视频播放的速度。

这些API可通过JavaScript直接访问:

//pause the video

myVideo.pause()

// seek to 10 seconds

myVideo.currentTime = 10;

但是,我们今天在网络上看到的大多数视频显示的行为比上面说的的行为复杂得多。例如,在视频质量和直播流之间切换将会非常麻烦。

所有这些网站实际上仍然使用video标签。但是,它们不只是在src属性中设置视频文件,而是使用功能更强大的Web API(Media Source Extensions)。

Media Source Extensions

“Media Source Extensions”(通常简称为“ MSE”)是 W3C 的一种规范,当今大多数浏览器都在实现。它的创建是为了直接使用 HTML 和 JavaScript 允许那些复杂的媒体使用案例。

这些“扩展”将 MediaSource 对象添加到 JavaScript。顾名思义,这将是视频的源,或更简单地说,这是代表我们视频数据的对象。

如前文所述,我们仍然使用 HTML5 视频标签。也许更令人惊讶的是,我们仍然使用它的 src 属性。仅这次,我们不添加视频链接,我们正在添加一个链接到 MediaSource 对象。

您可能对最后一句话感到困惑。我们在这里不是在讨论URL,而是在讨论 JavaScript 语言的抽象概念,如何将其称为 HTML 中定义的视频标签上的 URL?

为了允许这种用例,W3C定义了 URL.createObjectURL 静态方法。该API允许创建一个URL,该URL实际上将不引用在线可用资源,而是直接引用在客户端上创建的JavaScript对象。

因此,这是将MediaSource附加到视频标签的方式:

const videoTag = document.getElementById(“my-video”);

// creating the MediaSource, just with the “new” keyword, and the URL for it

const myMediaSource = newMediaSource();

const url = URL.createObjectURL(myMediaSource);

// attaching the MediaSource to the video tag

videoTag.src = url;

就是这样!现在,您已经知道流媒体平台如何在 Web 上播放视频!

… just kidding。所以现在有了 MediaSource,但是我们应该怎么做呢?

MSE规范不止于此。它还定义了另一个概念,即SourceBuffers。

Source Buffers

视频实际上并没有直接“推送”到 MediaSource 中进行播放,而是使用 SourceBuffers。

MediaSource 包含一个或多个实例。每个都与一种内容类型相关联。

为了简单起见,我们只说三种可能的类型:

  • 音讯

  • 视频

  • 音频和视频

实际上,“类型”是由其MIME类型定义的,其中还可能包含有关所使用的媒体编解码器的信息

SourceBuffers 都链接到单个 MediaSource,并且每个都将用于直接将 JavaScript 中的视频数据添加到 HTML5 视频标签中。

例如,一个常见的用例是在 MediaSource 上有两个源缓冲区:一个用于视频数据,另一个用于音频:

将视频和音频分离,还可以在服务器端分别对其进行管理。这样做会带来一些优势,我们将在后面看到。它是这样工作的:

const videoTag = document.getElementById(“my-video”);

const myMediaSource = new MediaSource();

const url = URL.createObjectURL(myMediaSource);

videoTag.src = url;

// 1. add source buffers

const audioSourceBuffer = myMediaSource

.addSourceBuffer(‘audio/mp4; codecs=“mp4a.40.2”’);

const videoSourceBuffer = myMediaSource

.addSourceBuffer(‘video/mp4; codecs=“avc1.64001e”’);

// 2. download and add our audio/video to the SourceBuffers

// for the audio SourceBuffer

fetch(“http://server.com/audio.mp4”).then(function(response) {

// The data has to be a JavaScript ArrayBuffer

return response.arrayBuffer();

}).then(function(audioData) {

audioSourceBuffer.appendBuffer(audioData);

});

// the same for the video SourceBuffer

fetch(“http://server.com/video.mp4”).then(function(response) {

// The data has to be a JavaScript ArrayBuffer

return response.arrayBuffer();

}).then(function(videoData) {

videoSourceBuffer.appendBuffer(videoData);

});

瞧!

现在,我们可以将视频和音频数据手动手动添加到我们的视频标签中。

现在该写音频和视频数据本身了。在上一个示例中,您可能已经注意到音频和视频数据为mp4格式

“ mp4”是一种视频容器格式(container format),它包含相关的媒体数据,还包含多个元数据,例如描述其中包含的媒体的开始时间和持续时间。

MSE规范没有规定浏览器必须理解哪种格式。对于视频数据,两个最常见的是 mp4 和 webm 文件。到目前为止,前者是众所周知的,后者是由Google赞助的,并且基于可能更为知名的Matroska格式(“ .mkv”文件)。

两者在大多数浏览器中均受良好支持。

切片

尽管如此,这里仍然有许多问题没有答案:

  • 我们是否必须等待所有内容下载完毕,才能将其推送到SourceBuffer(因此可以播放)?

  • 我们如何在多种品质或语言之间切换?

  • 由于媒体尚未制作完,如何播放直播内容?

在上一章的示例中,我们有一个文件代表整个音频,一个文件代表整个视频。这对于真正简单的用例就足够了,但是如果您想了解大多数流媒体网站提供的复杂性(切换语言,质量,播放实时内容等),则还不够。

在更高级的视频播放器中实际发生的是将视频和音频数据分为多个“片段”。这些片段的大小可以不同,但通常代表2到10秒的内容。

然后,所有这些视频/音频片段将形成完整的视频/音频内容。这些数据的“切片”为我们之前的示例增加了全新的灵活性:我们不必一次推送全部内容,而是可以逐步推送多个分片。

这是一个简化示例:

// … (definition of the MediaSource and its SourceBuffers)

/**

  • Fetch a video or an audio segment, and returns it as an ArrayBuffer, in a

  • Promise.

  • @param {string} url

  • @returns {Promise.}

*/

function fetchSegment(url) {

return fetch(url).then(function(response) {

return response.arrayBuffer();

});

}

// fetching audio segments one after another (notice the URLs)

fetchSegment(“http://server.com/audio/segment0.mp4”)

.then(function(audioSegment0) {

audioSourceBuffer.appendBuffer(audioSegment0);

})

.then(function() {

return fetchSegment(“http://server.com/audio/segment1.mp4”);

})

.then(function(audioSegment1) {

audioSourceBuffer.appendBuffer(audioSegment1);

})

.then(function() {

return fetchSegment(“http://server.com/audio/segment2.mp4”);

})

.then(function(audioSegment2) {

audioSourceBuffer.appendBuffer(audioSegment2);

})

// …

// same thing for video segments

fetchSegment(“http://server.com/video/segment0.mp4”)

.then(function(videoSegment0) {

videoSourceBuffer.appendBuffer(videoSegment0);

});

// …

这意味着我们在服务器端也有那些多个段。在前面的示例中,我们的服务器至少包含以下文件:

./audio/

├── segment0.mp4

├── segment1.mp4

└── segment2.mp4

./video/

└── segment0.mp4

注意:音频或视频文件可能不会在服务器端真正进行切片,客户端可能会使用Range HTTP标头代替来获取切片的文件(或者,实际上,服务器可能会根据您的请求进行任何操作您返回具体内容)。

但是,这些情况是实现细节。在这里,我们将始终认为服务器端具有这些分片文件。

所有这些意味着, 我们不必等待整个音频或视频内容下载就可以开始播放。我们通常只需要第一部分。

当然,大多数播放器并不像我们在此处那样为每个视频和音频段手动执行此逻辑,但是他们遵循相同的想法:依次下载段并将其推入源缓冲区。

看到这种逻辑在现实生活中发生的一种有趣方式是,可以在Firefox / Chrome / Edge上打开网络监视器(在Linux或Windows上,键入“ Ctrl + Shift + i”,然后转到“网络”标签,在Mac上应依次为Cmd + Alt + i和“网络”),然后在您喜欢的流媒体网站中启动视频。

您应该可以看到各种视频和音频片段正在快速下载:

顺便说一句,您可能已经注意到,我们的段只是\被推送到源缓冲区中,而没有指示 WHERE, 参考时间正确的位置的地方进行添加。

实际上,片段的容器确实定义了应将它们放入整个媒体的时间。这样,我们不必在JavaScript中立即进行同步。

自适应码流 Adaptive Streaming

许多视频播放器具有“自动播放清晰度”功能,根据用户的网络和处理能力自动选择具体视频质量。

这是称为自适应流的网络播放器的核心问题。

借助媒体分片的概念,也可以启用此行为。

在服务器端,段实际上是用多种质量编码的。例如,我们的服务器可能存储了以下文件:

./audio/

├── ./128kbps/

| ├── segment0.mp4

| ├── segment1.mp4

| └── segment2.mp4

└── ./320kbps/

├── segment0.mp4

├── segment1.mp4

└── segment2.mp4

./video/

├── ./240p/

| ├── segment0.mp4

| ├── segment1.mp4

| └── segment2.mp4

└── ./720p/

├── segment0.mp4

├── segment1.mp4

└── segment2.mp4

然后,网络播放器将随着网络或CPU条件的变化自动选择正确的段进行下载。

这完全是用JavaScript完成的。例如,对于音频片段,它可能看起来像这样:

/**

  • Push audio segment in the source buffer based on its number

  • and quality

  • @param {number} nb

  • @param {string} language

  • @param {string} wantedQuality

  • @returns {Promise}

*/

function pushAudioSegment(nb, wantedQuality) {

// The url begins to be a little more complex here:

const url = “http://my-server/audio/” +

wantedQuality + “/segment” + nb + “.mp4”);

return fetch(url)

.then((response) => response.arrayBuffer());

.then(function(arrayBuffer) {

audioSourceBuffer.appendBuffer(arrayBuffer);

});

}

/**

  • Translate an estimated bandwidth to the right audio

  • quality as defined on server-side.

  • @param {number} bandwidth

  • @returns {string}

*/

function fromBandwidthToQuality(bandwidth) {

return bandwidth > 320e3 ? “320kpbs” : “128kbps”;

}

// first estimate the bandwidth. Most often, this is based on

// the time it took to download the last segments

const bandwidth = estimateBandwidth();

const quality = fromBandwidthToQuality(bandwidth);

pushAudioSegment(0, quality)

.then(() => pushAudioSegment(1, quality))

.then(() => pushAudioSegment(2, quality));

如您所见,我们将不同质量的段组合在一起没有问题,这里的 JavaScript 方面一切都是透明的。在任何情况下,容器文件都包含足够的信息,以使此过程平稳运行。

切换语言

在更复杂的网络视频播放器上,例如 Netflix,Amazon Prime Video 或 MyCanal 上的视频播放器,还可以根据用户设置在多种音频语言之间进行切换。

既然您知道了什么,对您来说,完成此功能的方法应该看起来很简单。

像自适应流一样,我们在服务器端也有许多段:

./audio/

├──./esperanto/

| ├──segment0.mp4

| ├──segment1.mp4

| └──segment2.mp4

└── ./french/

├──segment0.mp4

├──segment1.mp4

└──segment2.mp4

./video/

├──segment0.mp4

├──segment1.mp4

└── segment2.mp4

这次,视频播放器必须不根据客户端的功能而是根据用户的喜好在语言之间进行切换。

对于音频段,这是客户端上的代码:

// …

/**

  • Push audio segment in the source buffer based on its number and language.

  • @param {number} nb

  • @param {string} language

  • @returns {Promise}

*/

function pushAudioSegment(nb, language) {

// construct dynamically the URL of the segment

// and push it to the SourceBuffer

const url = “http://my-server/audio/” +

language + “/segment” + nb + “.mp4”

return fetch(url);

.then((response) => response.arrayBuffer());

.then(function(arrayBuffer) {

audioSourceBuffer.appendBuffer(arrayBuffer);

});

}

// recuperate in some way the user’s language

const language = getUsersLanguage();

pushAudioSegment(0, language)

.then(() => pushAudioSegment(1, language))

.then(() => pushAudioSegment(2, language));

您可能还希望在切换语言时“清除”以前的SourceBuffer的内容,以避免混合多种语言的音频内容。

这可以通过SourceBuffer.prototype.remove方法完成,该方法以秒为单位的开始和结束时间:

audioSourceBuffer.remove(0, 40);

当然,也可以将自适应流和多种语言结合在一起。我们可以这样组织服务器:

./audio/

├──./esperanto/

| ├──./128kbps/

| | ├──segment0.mp4

| | ├──segment1.mp4

| | └──segment2.mp4

| └── …/320kbps/

| ├──segment0.mp4

| ├──segment1.mp4

| └──segment2.mp4└──./

french/

├──./128kbps/

| ├──segment0.mp4

| ├──segment1.mp4

| └──segment2.mp4

└── ./320kbps/

├──segment0.mp4

├──segment1.mp4

└──segment2.mp4

./video/

├──./240p/

| ├──segment0.mp4

| ├──segment1.mp4

| └──segment2.mp4

└── ./720p/

├──segment0.mp4

├──segment1.mp4

└──segment2.mp4

而我们的客户将不得不同时管理语言和网络条件:

/**

  • Push audio segment in the source buffer based on its number, language and quality

  • @param {number} nb

  • @param {string} language

  • @param {string} wantedQuality

  • @returns {Promise}

*/

function pushAudioSegment(nb, language, wantedQuality) {

// The url begins to be a little more complex here:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

分享一些简单的前端面试题以及学习路线给大家,狂戳这里即可获取

be a little more complex here:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-27kjYXMP-1712985418897)]

[外链图片转存中…(img-5ZqflT5m-1712985418897)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-geFx6ZHY-1712985418898)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

分享一些简单的前端面试题以及学习路线给大家,狂戳这里即可获取

[外链图片转存中…(img-Mc3v7NLl-1712985418898)]

  • 23
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值