先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
正文
在上一章的示例中,我们有一个文件代表整个音频,一个文件代表整个视频。这对于真正简单的用例就足够了,但是如果您想了解大多数流媒体网站提供的复杂性(切换语言,质量,播放实时内容等),则还不够。
在更高级的视频播放器中实际发生的是将视频和音频数据分为多个“片段”。这些片段的大小可以不同,但通常代表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));
如您所见,现在有很多方法可以定义相同的内容。
这揭示了分开的视频和音频段相对于整个文件的另一个优点。对于后者,我们将不得不在服务器端结合各种可能性,这可能会占用更多空间:
segment0_video_240p_audio_esperanto_128kbps.mp4
segment0_video_240p_audio_esperanto_320kbps.mp4
segment0_video_240p_audio_french_128kbps.mp4
segment0_video_240p_audio_french_320kbps.mp4
segment0_video_720p_audio_esperanto_128kbps.mp4
segment0_video_720p_audio_esperanto_320kbps.mp4
segment0_video_720p_audio_french_128kbps.mp4
segment0_video_720p_audio_french_320kbps.mp4
segment1_video_240p_audio_esperanto_128kbps.mp4
segment1_video_240p_audio_esperanto_320kbps.mp4
segment1_video_240p_audio_french_128kbps.mp4
segment1_video_240p_audio_french_320kbps.mp4
segment1_video_720p_audio_esperanto_128kbps.mp4
segment1_video_720p_audio_esperanto_320kbps.mp4
segment1_video_720p_audio_french_128kbps.mp4
segment1_video_720p_audio_french_320kbps.mp4
segment2_video_240p_audio_esperanto_128kbps.mp4
segment2_video_240p_audio_esperanto_320kbps.mp4
segment2_video_240p_audio_french_128kbps.mp4
segment2_video_240p_audio_french_320kbps.mp4
segment2_video_720p_audio_esperanto_128kbps.mp4
segment2_video_720p_audio_esperanto_320kbps.mp4
segment2_video_720p_audio_french_128kbps.mp4
segment2_video_720p_audio_french_320kbps.mp4
Here we have more files, with a lot of redundancy (the
这里我们有更多的文件,并且有很多冗余(多个文件中包含完全相同的视频数据)。
如您所见,在服务器端效率很低。但这在客户端也很不利,因为切换音频语言可能会导致您也重新下载视频(带宽成本很高)。
直播
我们还没有谈论直播。
网络上的直播流媒体(twitch.tv,YouTube实时流媒体…)变得非常普遍,并且由于我们的视频和音频文件已分段,因此再次大大简化了这一过程。
为了说明它基本上以最简单的方式工作,让我们考虑一个4秒钟前才开始直播传输的 YouTube 频道。
如果我们的片段长2秒,那么我们应该已经在YouTube的服务器上生成了两个音频片段和两个视频片段:
-
两个代表从0秒到2秒的内容(1个音频+ 1个视频)
-
两个代表2秒到4秒(同样是1个音频+ 1个视频)
./audio/
├──segment0s.mp4
└── segment2s.mp4
./video/
├──segment0s.mp4
└── segment2s.mp4
在5秒钟时,我们还没有时间生成下一个片段,因此,到目前为止,服务器具有完全相同的可用内容。
6秒钟后,可以生成一个新的段,我们现在有:
./audio/
├──segment0s.mp4
├──segment2s.mp4
└── segment4s.mp4
./video/
├──segment0s.mp4
├──segment2s.mp4
└── segment4s.mp4
在服务器端,这是很合乎逻辑的,实时内容实际上并不是真正连续的,它们像非实时内容一样进行分段,但是随着时间的流逝,分段会逐渐出现。
现在,我们如何从JS中知道服务器上某个时间点可用的段?
我们可能只在客户端上使用一个时钟,然后随着时间的流逝推断出新的段在服务器端变得可用。
我们将遵循“ segmentX.mp4 ”的命名方案,并且每次都将从上次下载的“ X”开始递增(“ segment0.mp4”,然后是2秒后的“ Segment1.mp4”等)。
但是,在许多情况下,这可能变得太不精确:媒体段的持续时间可能可变,服务器在生成媒体段时可能会有延迟,它可能希望删除太旧以至于无法节省空间的段…
作为客户端,您想请求最新的分片,只要它们可用,同时仍避免在尚未生成细分市场时过早请求它们(这将导致404 HTTP错误)。
通常通过使用传输协议(有时也称为流媒体协议)解决此问题。
传输协议
对于本文,深入解释不同的传输协议可能太冗长。我们只说其中大多数具有相同的核心概念:Manifest。
Manifest是描述哪些段可用的服务器上的文件。
借助它,您可以Manifest中了解到的大多数信息:
-
内容在服务器上可用的语言以及在服务器上的可用位置(例如,“在哪个URL”)
-
提供不同的音频和视频质量
-
当然,在直播流媒体的情况下,哪些细分可用
Web中使用的最常见的传输协议是:
DASH
YouTube,Netflix或 Amazon Prime Video(及许多其他公司)使用的 DASH。DASH 的清单称为“Media Presentation Description”(或MPD),是其基本XML。
最后
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
/imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9tZUc2Vm8wTWV2aWFiOE5XN21taWIzZWR1ZVVkNW9EdFFGcnlZeHRzQUZUUzgxVFZPUmFTQ2ljRmp5ZFJUcnpxMGtoWFoyVGhlQ3hBQ3ZTZ1dZTXlBWFNFdy82NDA?x-oss-process=image/format,png)
借助它,您可以Manifest中了解到的大多数信息:
-
内容在服务器上可用的语言以及在服务器上的可用位置(例如,“在哪个URL”)
-
提供不同的音频和视频质量
-
当然,在直播流媒体的情况下,哪些细分可用
Web中使用的最常见的传输协议是:
DASH
YouTube,Netflix或 Amazon Prime Video(及许多其他公司)使用的 DASH。DASH 的清单称为“Media Presentation Description”(或MPD),是其基本XML。
最后
[外链图片转存中…(img-zivq2v1n-1713409586864)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-IvY74JMU-1713409586865)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!