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:

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

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

return fetch(url)

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

.then(function(arrayBuffer) {

audioSourceBuffer.appendBuffer(arrayBuffer);

});

}

const bandwidth = estimateBandwidth();

const quality = fromBandwidthToQuality(bandwidth);

const language = getUsersLanguage();

pushAudioSegment(0, language, quality)

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

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

如您所见,现在有很多方法可以定义相同的内容。

这揭示了分开的视频和音频段相对于整个文件的另一个优点。对于后者,我们将不得不在服务器端结合各种可能性,这可能会占用更多空间:

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

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

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

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

总结

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端面试题汇总

JavaScript

前端资料汇总

710701231361)]
[外链图片转存中…(img-7wJPBGs8-1710701231362)]
[外链图片转存中…(img-3kqYxyHB-1710701231362)]

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

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
[外链图片转存中…(img-zIVSCqDg-1710701231362)]

总结

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端面试题汇总

JavaScript

前端资料汇总

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐