Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 国产小视频在线免费,天天操天天操天天操天天操,国产成人91一区二区三区

          整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢(xún)熱線:

          什么是flash什么是html5?

          今,HTML5 可謂如眾星捧月一般,受到許多業(yè)內(nèi)巨頭的青睞。且不說(shuō)谷歌、蘋(píng)果等業(yè)內(nèi)巨頭把它描繪為互聯(lián)網(wǎng)體驗(yàn)的未來(lái),即便是以不服從標(biāo)準(zhǔn)著稱(chēng)的微軟,也向它頻頻示好, 決心在 Internet Explorer 9 中加入對(duì) HTML5 的大量支持。然而,HTML5的路途真的將一帆風(fēng)順么?本文將列舉了一些在HTML5發(fā)展和普及過(guò)程中需要解決的問(wèn)題。

             什么是HTML5?

             根據(jù)維基百科上的解釋?zhuān)琀TML5 的前身是 Web Applications 1.0,由 WHATWG 于2004年提出。2007年,它被 W3C 接納,并于2008年1月22日發(fā)布第一份正式草案。

             以下摘錄自維基百科的文字介紹了 HTML5 的特點(diǎn)和與 HTML4 的差別:

          新應(yīng)用程序接口(API)
          即時(shí)二維繪圖
          定時(shí)媒體播放
          儲(chǔ)存
          離線
          編輯
          拖放
          通訊/網(wǎng)絡(luò)
          后退按鈕管理
          MIME 和協(xié)議處理程序時(shí)表頭登記

             與 HTML 4 的不同之處

          新的解析順序
          新的元素:section, video, progress, nav, meter, time, aside, canvas
          input 元素的新屬性:日期和時(shí)間,email, url
          新的通用屬性:ping, charset, async
          全域?qū)傩裕篿d, tabindex, repeat
          移除元素:center, font, u, strike, s, frameset, frame, applet

             戰(zhàn)勝 Flash, HTML5 還需要什么?

             決定勝負(fù)的因素很多,在此分為兩部分分析。

             一、技術(shù)方面

             HTML5 與 Flash 在功能上并不是完全重疊的,比如對(duì)于攝像頭等計(jì)算機(jī)硬件的調(diào)用,仍然只能使用 Flash 或其他方法實(shí)現(xiàn)。但是 HTML5 卻引入了一些讓 Flash 不得不認(rèn)真對(duì)待的元素。其中最為人所知的和最重要的即為 canvas 和 video 標(biāo)簽。在我看來(lái),其他的新屬性跟 Flash 的競(jìng)爭(zhēng)關(guān)系很弱,只有此二者是真真正正要搶 Flash 的飯碗,而且一旦普及,將深刻的改變整個(gè)互聯(lián)網(wǎng),可謂是 HTML5 的左膀右臂。

             canvas

             在 Flash 流行之前,曾經(jīng)出現(xiàn)過(guò)很多種在網(wǎng)頁(yè)中實(shí)現(xiàn)繪圖功能的方法,其中包括著名的Java Applet 。這些方法各有千秋,相互競(jìng)爭(zhēng)不休。但是在 Macromedia 公司推出 Flash 之后,這場(chǎng)戰(zhàn)爭(zhēng)很快就結(jié)束了。

             為了對(duì)抗 Flash,又有很多新的技術(shù)被提出,其中就包括現(xiàn)在 Flash 的東家 Adobe 提出的 SVG。然而這沒(méi)有能夠阻止 Flash 迅速地被網(wǎng)民接受。基于 Flash 的動(dòng)畫(huà)、游戲等應(yīng)用幾乎是在一夜之間蔓延到了互聯(lián)網(wǎng)的每個(gè)角落。

             而今,新的挑戰(zhàn)者出現(xiàn)了,他就是 HTML5 的新標(biāo)簽 canvas。

             canvas 相比 Flash 顯然是有其優(yōu)點(diǎn)的。它不依賴(lài)于外部插件、與瀏覽器渲染引擎緊密結(jié)合、節(jié)約資源,最重要的是極大的簡(jiǎn)化了圖形和網(wǎng)頁(yè)中其他元素的交互過(guò)程。

             對(duì)于 Flash 來(lái)說(shuō),是 Flash中的元素與網(wǎng)頁(yè)中其他元素進(jìn)行交互是要消耗大量時(shí)間和資源的,另外在編程上也相當(dāng)不方便。

          而 canvas 本身就是 HTML5 的一個(gè)元素,可以像操作普通 HTML 元素一樣操作它。開(kāi)發(fā)人員可以將所有的代碼整齊地寫(xiě)在一個(gè)文件里,降低了維護(hù)與更新的難度。

             然而 canvas 也有其缺點(diǎn):

             其一,開(kāi)發(fā)者不得不編程描繪每一個(gè)點(diǎn)和矢量曲線,在旋轉(zhuǎn)縮放時(shí)更需要和矩陣變換打交道,這會(huì)增加描繪復(fù)雜圖形的難度。而在 Flash 里,圖形顯示的 API 被封裝在名為“Sprite(顯示列表)”的類(lèi)里,大部分圖形元素都繼承于此類(lèi),開(kāi)發(fā)者可以使用多種工具設(shè)計(jì)圖形,對(duì)圖形進(jìn)行旋轉(zhuǎn)放縮只需要簡(jiǎn)單的調(diào)用類(lèi) 的函數(shù)。

             其二,動(dòng)畫(huà)的實(shí)現(xiàn)存在缺憾。canvas 雖然提供了不同于傳統(tǒng)的通過(guò) div 塊實(shí)現(xiàn)動(dòng)畫(huà)的方法,但這種方法仍然非常繁瑣。開(kāi)發(fā)者必須在每一幀動(dòng)畫(huà)顯示時(shí)清空畫(huà)布,然后重畫(huà)所有元素,這必然導(dǎo)致包含大量元素的場(chǎng)景動(dòng)畫(huà)緩慢,只移動(dòng)少 量元素就要重畫(huà)整個(gè)畫(huà)布會(huì)浪費(fèi)大量資源。而且創(chuàng)建動(dòng)畫(huà)也是一件十分繁瑣的事情。相比起來(lái),F(xiàn)lash的實(shí)現(xiàn)就方便多了,雖然從最底層來(lái)說(shuō),動(dòng)畫(huà)時(shí)仍然需要 重畫(huà)整個(gè)畫(huà)布,但其被交予 Flash Player 自動(dòng)處理,無(wú)需開(kāi)發(fā)者手動(dòng)管理。基于字節(jié)碼的 Flash 在解析的過(guò)程中將會(huì)比即時(shí)編譯的 HTML5 和 JavaScript 快速。一般來(lái)說(shuō),復(fù)雜動(dòng)畫(huà)將會(huì)更流暢。另外,良好封裝的圖形類(lèi)和強(qiáng)大的設(shè)計(jì)工具使得動(dòng)畫(huà)的創(chuàng)建非常方便。

             其三,沒(méi)有提供一套方便的事件體系。開(kāi)發(fā)者也許需要通過(guò)捕獲鼠標(biāo)在 canvas 中點(diǎn)擊的坐標(biāo),判斷用戶(hù)到底點(diǎn)擊了什么圖形元素。在這個(gè)過(guò)程中可能要遍歷所有的顯示元素并判定點(diǎn)是否在圖形內(nèi),實(shí)現(xiàn)起來(lái)比較繁瑣,更不要說(shuō)實(shí)現(xiàn)事件的冒泡 和遞歸模型了。雖然今后出現(xiàn)的圖形庫(kù)可以解決這個(gè)問(wèn)題,但這實(shí)質(zhì)上相當(dāng)于使用 JavaScript 構(gòu)建了一套事件響應(yīng)模型,其效率顯然不如內(nèi)建于瀏覽器的原生事件模型高。在 Flash 中,事件也被良好封裝為類(lèi),捕獲點(diǎn)擊等事件自然不在話(huà)下,更重要的是提供了判斷兩個(gè)圖形是否有交集的事件和函數(shù),這在游戲編程中非常方便。另 外,F(xiàn)lash 的最新版本將會(huì)支持多點(diǎn)觸摸事件的響應(yīng),而 HTML 想要支持這點(diǎn)恐怕要等到 HTML6 了。

             由以上分析我們可以看出,HTML5 需要的幾個(gè)非常重要的東西:一個(gè)強(qiáng)大易用的圖形庫(kù)、硬件加速的圖形解析和重繪、一個(gè)強(qiáng)大的編 機(jī)器(IDE)

             目前已經(jīng)出現(xiàn)了基于 canvas 實(shí)現(xiàn)的游戲引擎。但是從效果上看仍然無(wú)法與 Flash 媲美。

          WebGL 的提出讓我們看到了硬件加速的希望,這將極大的改進(jìn)圖形顯示的速度。但是目前它只被少數(shù)開(kāi)發(fā)版本的瀏覽器支持。

             IDE 方面,諷刺的是恰恰是 Adobe 為 Adobe Flash CS5 添加了一個(gè)將 Flash 轉(zhuǎn)化為 canvas 的功能。在 JavaScript 方面,鑒于其為非強(qiáng)制類(lèi)型的編程語(yǔ)言,對(duì)其進(jìn)行代碼提示等非常困難,提高編程效率較難。

             如果以上三個(gè)問(wèn)題不能被良好解決,將會(huì)限制 canvas 所能實(shí)現(xiàn)的效果的豐富度,增加開(kāi)發(fā)的復(fù)雜度,從而最終阻礙其普及。

             圖為一個(gè)用 canvas 實(shí)現(xiàn)的繪圖應(yīng)用

             video

             video 標(biāo)簽可能是 Adobe 最反對(duì)的東西了,它極有可能打破 Flash 在在線視頻領(lǐng)域的壟斷地位。

             但目前的情況是作為 video 內(nèi)容的視頻存在編碼問(wèn)題,Apple 和微軟所支持的 不是開(kāi)放標(biāo)準(zhǔn),瀏覽器廠商必須為其付費(fèi)。因此,作為三大瀏覽器之一的火狐瀏覽器拒絕支持此編碼格式。谷歌雖然也收購(gòu)了一套優(yōu)質(zhì)的編碼技術(shù),但是目前沒(méi)有跡 象表明谷歌會(huì)開(kāi)放這個(gè)技術(shù)標(biāo)準(zhǔn)。

             根據(jù)最近的統(tǒng)計(jì),雖然 Google Chrome 瀏覽器和 Apple Safari 瀏覽器增長(zhǎng)很快,但瀏覽器市場(chǎng)還是主要被火狐和 IE 所統(tǒng)治。如果火狐堅(jiān)持不支持 編碼格式,video 標(biāo)簽的推廣將會(huì)十分困難。

             所以,HTML5 需要一個(gè)既開(kāi)放又優(yōu)質(zhì)的視頻編碼標(biāo)準(zhǔn)

             圖為 video 標(biāo)簽 的演示

             二、商業(yè)方面

             團(tuán)結(jié)

             要讓微軟、谷歌、蘋(píng)果這三個(gè)在很多方面存在競(jìng)爭(zhēng)關(guān)系的業(yè)界巨頭團(tuán)結(jié)一心地支持同一套標(biāo)準(zhǔn)是很困難的。

             蘋(píng)果方面對(duì) Flash 痛下殺手,微軟方面則極少參與這場(chǎng)論戰(zhàn)。至于谷歌則在支持 HTML5 的同時(shí)在 Android 中加入了 Flash 支持,甚至存在將 Flash 納入 Chorme 安全沙箱的計(jì)劃。在這種情況下,如果 Adobe 能夠良好利用三大巨頭之間的分歧并加以運(yùn)作,HTML5 的前景堪憂(yōu)。

             即便 Adobe 沒(méi)有那樣的智慧與能力挑撥三大巨頭之間的關(guān)系,三大巨頭自己就可能葬送 HTML5 的未來(lái)。前車(chē)之鑒就是大名鼎鼎的 OpenGL。這一標(biāo)準(zhǔn)成立之初的聯(lián)盟成員幾乎可以用豪華來(lái)形容,結(jié)果因?yàn)楦鱾€(gè)成員之間為了自己的利益相互爭(zhēng)吵,使得OpenGL的發(fā)展速度遠(yuǎn)不及 Direct3D,直至到目前這樣游戲市場(chǎng)幾乎被競(jìng)爭(zhēng)對(duì)手占據(jù)、應(yīng)用范圍局限在專(zhuān)業(yè)領(lǐng)域的情況。

             用戶(hù)的接受

             無(wú)論各大廠商如何宣傳,用戶(hù)的接受才是最后的檢驗(yàn)標(biāo)準(zhǔn)。目前來(lái)看 HTML5 在普通桌面領(lǐng)域可能的作為不大,與 Flash 的關(guān)系必然是長(zhǎng)期并存。原因在于用戶(hù)并不在意頁(yè)面到底使用的是什么技術(shù),而更關(guān)心最后的效果怎么樣。HTML5 的 canvas 若要達(dá)到 Flash 實(shí)現(xiàn)的相同效果所需要的難度更大,這樣限制了中小網(wǎng)站在網(wǎng)頁(yè)里使用 canvas 的積極性,如果 canvas 不能普及,就相當(dāng)于 HTML5 斷了一條腿,而 video 標(biāo)簽的編碼問(wèn)題再得不到解決,HTML5 真的就沒(méi)辦法和 Flash 競(jìng)爭(zhēng)了。

             大膽的預(yù)測(cè)

             在最后,我將對(duì) HTML5 和 Flash 的這場(chǎng)世紀(jì)之戰(zhàn)做出我自己的預(yù)測(cè)。

             首先用一個(gè)比喻描述目前的情況:

             谷歌、微軟、蘋(píng)果、Adobe 四家圍在一起打牌,其中 Adobe 是莊家,手中的牌最多最好。蘋(píng)果、微軟都很想把 Adobe 從莊家的位置上拖下來(lái),但是又不愿意合作。谷歌與 Adobe 關(guān)系曖昧,但是也有自己的打算。

             在這場(chǎng)牌局中,Adobe 幾乎是立于不敗之地的,從目前來(lái)看 Flash 被 HTML5 完全取代的可能不大,原因在于Flash 已經(jīng)占領(lǐng)了絕大部分傳統(tǒng)桌面終端的市場(chǎng),其地位幾乎無(wú)法撼動(dòng),即便 Flash 做得不夠好,但是只要沒(méi)到很不好的地步,還是無(wú)法被超越。雖然傳統(tǒng)桌面收到了新興的移動(dòng)終端的挑戰(zhàn),但是這一過(guò)程將發(fā)展得比 Flash 和 HTML5 之間的競(jìng)爭(zhēng)更緩慢。Adobe 控制著 Photoshop、Dreamweaver 和 Fireworks 等知名軟件,制作網(wǎng)頁(yè)即便可以缺少 Flash 卻無(wú)法缺少    Photoshop,即便是編制全 HTML5 的網(wǎng)頁(yè),Dreamweaver 依然是首選的網(wǎng)頁(yè)制作利器,制作 canvas 也可以使用 Flash CS5 新加入的生成 canvas 的功能。

             圖為 Adobe Flash CS5

             廣受詬病的 Flash Player 并不能給 Adobe 帶來(lái)直接的利潤(rùn),它的意義在于將富媒體應(yīng)用的市場(chǎng)和標(biāo)準(zhǔn)掌握在手中。用于制作 Flash 的編輯器才是 Adobe 真正的利潤(rùn)來(lái)源。如果在 HTML5 的時(shí)代,開(kāi)發(fā)者仍然不得不選擇 Adobe 的產(chǎn)品來(lái)制作基于 canvas 的交互頁(yè)面,那么又何必需要 Flash Player 的存在呢?Adobe 更是省下了維護(hù)一個(gè)復(fù)雜系統(tǒng)的費(fèi)用。

             至于微軟,他內(nèi)心是非常糾結(jié)的,他手里有 Internet Explorer 這張不知道是好是壞的牌,原因在于由 Internet Explorer 6 占領(lǐng)的瀏覽器市場(chǎng)份額仍然沒(méi)有被有效釋放,新版本的 Internet Explorer 不得不跟自己的前輩競(jìng)爭(zhēng)。另外,微軟也急于推廣自己的 SliverLight ,這一產(chǎn)品與 Flash 和 HTML5 都是競(jìng)爭(zhēng)關(guān)系。支持 HTML5 或多或少會(huì)對(duì) SliverLight 的推廣有所打擊。所以微軟必然不會(huì)全力支持 HTML5,而是只將它作為 SliverLight 的補(bǔ)充。

             蘋(píng)果的算盤(pán)打得很響,他要從移動(dòng)終端領(lǐng)域著手,逐步滲透到桌面領(lǐng)域。iPhone 是第一步,也是相當(dāng)成功的一步,它的存在說(shuō)明智能手機(jī)領(lǐng)域并不需要 Flash 的存在,事實(shí)上,F(xiàn)lash 在這一領(lǐng)域表現(xiàn)的確很差。但是真正關(guān)鍵的是 iPad。iPad無(wú)論是屏幕大小還是操作體驗(yàn)都更接近與普通桌面電腦,如果平板電腦被證明不需要 Flash 的存在,那么桌面電腦為什么不可以?如果大量用戶(hù)通過(guò)使用平板電腦而習(xí)慣了沒(méi)有 Flash 的互聯(lián)網(wǎng)體驗(yàn),那么 Flash 就真的沒(méi)有未來(lái)了。iPad發(fā)售之后 Adobe 與蘋(píng)果之間爭(zhēng)論的升級(jí),從側(cè)面證實(shí)蘋(píng)果和 Adobe 都看到了平板電腦將在這場(chǎng)戰(zhàn)爭(zhēng)中發(fā)揮的作用。

             但是蘋(píng)果真的能如愿以?xún)斆矗刻O(píng)果的產(chǎn)品即便銷(xiāo)售很好,也不可能做到像微軟的產(chǎn)品這樣普及。原因在于蘋(píng)果的產(chǎn)品文化就在于提供高質(zhì)量和高品位的體驗(yàn), 而這種體驗(yàn)伴隨著高價(jià)。必然只有少數(shù)人能夠用得起蘋(píng)果,必然只有使用蘋(píng)果是能夠成為一種身份和品味的象征,蘋(píng)果的產(chǎn)品才會(huì)有這么大的吸引力。難道除了蘋(píng)果 就沒(méi)有其他廠商可以提供同樣的技術(shù)了么?難道微軟不能像蘋(píng)果一樣以用戶(hù)體驗(yàn)為先么?顯然不是,重要的原因在于微軟所要提供的是一個(gè)給所有人使用的產(chǎn)品,這 一產(chǎn)品要有廣泛性,要有繼承性,而且不能太昂貴。所以微軟在用戶(hù)體驗(yàn)方面改變的動(dòng)力不足,微軟試圖在 Visita 里極大的改變用戶(hù)體驗(yàn)的方式,原來(lái)使用 XP 非常熟練的用戶(hù)到了 Visita 里就變得不知所措了。結(jié)果顯而易見(jiàn)。

             事實(shí)上,蘋(píng)果從一開(kāi)始就不打算讓所有人都用上蘋(píng)果,只要有少部分人愿意掏錢(qián)購(gòu)買(mǎi),它就能賺足夠的錢(qián)。

             到了平板電腦這里也是一樣,iPad 不可能獨(dú)自積累到足夠大的用戶(hù)群,以至于可以挑戰(zhàn)傳統(tǒng)桌面終端。更具性?xún)r(jià)比的其他廠商的產(chǎn)品將會(huì)讓更多人享受到平板電腦。而且這些平板電腦很可能會(huì)支持 Flash。這樣的話(huà),利用新興終端,改變用戶(hù)對(duì) Flash 的依賴(lài)的計(jì)劃就會(huì)失敗。

             谷歌方面,這場(chǎng)戰(zhàn)爭(zhēng)的勝負(fù)對(duì)谷歌的影響都不大,只要能把用戶(hù)留在頁(yè)面上,它并不在意到底使用的是 HTML5 還是 Flash。然而他卻最終勝負(fù)有著非同尋常的影響力。谷歌除了 YouTube 之外,很少在自己的產(chǎn)品中使用 Flash。也許谷歌認(rèn)為一個(gè)開(kāi)放的標(biāo)準(zhǔn)更容易控制。對(duì)于 Flash 這樣封閉的產(chǎn)品,雖然好用,但是谷歌很難對(duì)其發(fā)展方向有發(fā)言權(quán)。而今,Chrome 瀏覽器的迅猛發(fā)展日益增加了谷歌在 HTML 新標(biāo)準(zhǔn)中的發(fā)言權(quán)。但是這并不意味著谷歌會(huì)完全拋棄 Flash。

             Flash 可以作為谷歌牽制蘋(píng)果的重要工具,作為 Android 挑戰(zhàn) iPhone 的籌碼。谷歌正籌劃將 Flash 納入 Chrome OS 的安全沙箱。如果成功,F(xiàn)lash 飽受詬病的耗電問(wèn)題、安全問(wèn)題都能得到較好的解決。

             由此得到結(jié)論,F(xiàn)lash 氣數(shù)未盡,仍將長(zhǎng)期統(tǒng)治互聯(lián)網(wǎng)富媒體領(lǐng)域的市場(chǎng)。在移動(dòng)領(lǐng)域的發(fā)展將取決于谷歌的態(tài)度,但可以預(yù)見(jiàn)的是將會(huì)有很多困難。

             至于 HTML5,預(yù)計(jì)在1-3年內(nèi)會(huì)達(dá)到相對(duì)普及的程度,但是不會(huì)取代 Flash。他們之間甚至是可以和諧相處,取長(zhǎng)補(bǔ)短的。然而不要對(duì) HTML5 的發(fā)展速度抱有太大希望,畢竟業(yè)內(nèi)巨頭之間矛盾重重,現(xiàn)有標(biāo)準(zhǔn)能夠被各大瀏覽器無(wú)差別支持就已經(jīng)相當(dāng)困難了,想要加入任何新功能都要很久才會(huì)被廣泛支持。 這樣緩慢的發(fā)展速度如何能夠體現(xiàn)出強(qiáng)大的競(jìng)爭(zhēng)力,我們拭目以待。

          瀏覽器的內(nèi)核是指支持瀏覽器運(yùn)行的最核心的程序,分為兩個(gè)部分的,一是渲染引擎,另一個(gè)是 JS 引擎。渲染引擎在不同的瀏覽器中也不是都相同的。目前市面上常見(jiàn)的瀏覽器內(nèi)核可以分為這四種:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。這里面大家最耳熟能詳?shù)目赡芫褪?Webkit 內(nèi)核了,Webkit 內(nèi)核是當(dāng)下瀏覽器世界真正的霸主。

          本文我們就以 Webkit 為例,對(duì)現(xiàn)代瀏覽器的渲染過(guò)程進(jìn)行一個(gè)深度的剖析。

          想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub 博客。

          頁(yè)面加載過(guò)程

          在介紹瀏覽器渲染過(guò)程之前,我們簡(jiǎn)明扼要介紹下頁(yè)面的加載過(guò)程,有助于更好理解后續(xù)渲染過(guò)程。

          要點(diǎn)如下:

          • 瀏覽器根據(jù) DNS 服務(wù)器得到域名的 IP 地址;
          • 向這個(gè) IP 的機(jī)器發(fā)送 HTTP 請(qǐng)求;
          • 服務(wù)器收到、處理并返回 HTTP 請(qǐng)求;
          • 瀏覽器得到返回內(nèi)容。

          例如在瀏覽器輸入https://juejin.im/timeline,然后經(jīng)過(guò) DNS 解析,juejin.im對(duì)應(yīng)的 IP 是36.248.217.149(不同時(shí)間、地點(diǎn)對(duì)應(yīng)的 IP 可能會(huì)不同)。然后瀏覽器向該 IP 發(fā)送 HTTP 請(qǐng)求。

          服務(wù)端接收到 HTTP 請(qǐng)求,然后經(jīng)過(guò)計(jì)算(向不同的用戶(hù)推送不同的內(nèi)容),返回 HTTP 請(qǐng)求,返回的內(nèi)容如下:


          其實(shí)就是一堆 HMTL 格式的字符串,因?yàn)橹挥?HTML 格式瀏覽器才能正確解析,這是 W3C 標(biāo)準(zhǔn)的要求。接下來(lái)就是瀏覽器的渲染過(guò)程。

          瀏覽器渲染過(guò)程


          瀏覽器渲染過(guò)程大體分為如下三部分:

          1)瀏覽器會(huì)解析三個(gè)東西:

          一是 HTML/SVG/XHTML,HTML 字符串描述了一個(gè)頁(yè)面的結(jié)構(gòu),瀏覽器會(huì)把 HTML 結(jié)構(gòu)字符串解析轉(zhuǎn)換 DOM 樹(shù)形結(jié)構(gòu)。


          二是 CSS,解析 CSS 會(huì)產(chǎn)生 CSS 規(guī)則樹(shù),它和 DOM 結(jié)構(gòu)比較像。


          三是 Javascript 腳本,等到 Javascript 腳本文件加載后, 通過(guò) DOM API 和 CSSOM API 來(lái)操作 DOM Tree 和 CSS Rule Tree。


          2)解析完成后,瀏覽器引擎會(huì)通過(guò) DOM Tree 和 CSS Rule Tree 來(lái)構(gòu)造 Rendering Tree。

          • Rendering Tree 渲染樹(shù)并不等同于 DOM 樹(shù),渲染樹(shù)只會(huì)包括需要顯示的節(jié)點(diǎn)和這些節(jié)點(diǎn)的樣式信息。
          • CSS 的 Rule Tree 主要是為了完成匹配并把 CSS Rule 附加上 Rendering Tree 上的每個(gè) Element(也就是每個(gè) Frame)。
          • 然后,計(jì)算每個(gè) Frame 的位置,這又叫 layout 和 reflow 過(guò)程。

          3)最后通過(guò)調(diào)用操作系統(tǒng) Native GUI 的 API 繪制。

          接下來(lái)我們針對(duì)這其中所經(jīng)歷的重要步驟詳細(xì)闡述

          構(gòu)建 DOM

          瀏覽器會(huì)遵守一套步驟將 HTML 文件轉(zhuǎn)換為 DOM 樹(shù)。宏觀上,可以分為幾個(gè)步驟:


          瀏覽器從磁盤(pán)或網(wǎng)絡(luò)讀取 HTML 的原始字節(jié),并根據(jù)文件的指定編碼(例如 UTF-8)將它們轉(zhuǎn)換成字符串。

          在網(wǎng)絡(luò)中傳輸?shù)膬?nèi)容其實(shí)都是 0 和 1 這些字節(jié)數(shù)據(jù)。當(dāng)瀏覽器接收到這些字節(jié)數(shù)據(jù)以后,它會(huì)將這些字節(jié)數(shù)據(jù)轉(zhuǎn)換為字符串,也就是我們寫(xiě)的代碼。

          將字符串轉(zhuǎn)換成 Token,例如:<html>、<body>等。Token 中會(huì)標(biāo)識(shí)出當(dāng)前 Token 是“開(kāi)始標(biāo)簽”或是“結(jié)束標(biāo)簽”亦或是“文本”等信息

          這時(shí)候你一定會(huì)有疑問(wèn),節(jié)點(diǎn)與節(jié)點(diǎn)之間的關(guān)系如何維護(hù)?

          事實(shí)上,這就是 Token 要標(biāo)識(shí)“起始標(biāo)簽”和“結(jié)束標(biāo)簽”等標(biāo)識(shí)的作用。例如“title”Token 的起始標(biāo)簽和結(jié)束標(biāo)簽之間的節(jié)點(diǎn)肯定是屬于“head”的子節(jié)點(diǎn)。


          上圖給出了節(jié)點(diǎn)之間的關(guān)系,例如:“Hello”Token 位于“title”開(kāi)始標(biāo)簽與“title”結(jié)束標(biāo)簽之間,表明“Hello”Token 是“title”Token 的子節(jié)點(diǎn)。同理“title”Token 是“head”Token 的子節(jié)點(diǎn)。

          • 生成節(jié)點(diǎn)對(duì)象并構(gòu)建 DOM

          事實(shí)上,構(gòu)建 DOM 的過(guò)程中,不是等所有 Token 都轉(zhuǎn)換完成后再去生成節(jié)點(diǎn)對(duì)象,而是一邊生成 Token 一邊消耗 Token 來(lái)生成節(jié)點(diǎn)對(duì)象。換句話(huà)說(shuō),每個(gè) Token 被生成后,會(huì)立刻消耗這個(gè) Token 創(chuàng)建出節(jié)點(diǎn)對(duì)象。注意:帶有結(jié)束標(biāo)簽標(biāo)識(shí)的 Token 不會(huì)創(chuàng)建節(jié)點(diǎn)對(duì)象。

          接下來(lái)我們舉個(gè)例子,假設(shè)有段 HTML 文本:

          復(fù)制代碼

          <html>
          <head>
           <title>Web page parsing</title>
          </head>
          <body>
           <div>
           <h1>Web page parsing</h1>
           <p>This is an example Web page.</p>
           </div>
          </body>
          </html>
          

          上面這段 HTML 會(huì)解析成這樣:


          構(gòu)建 CSSOM

          DOM 會(huì)捕獲頁(yè)面的內(nèi)容,但瀏覽器還需要知道頁(yè)面如何展示,所以需要構(gòu)建 CSSOM。

          構(gòu)建 CSSOM 的過(guò)程與構(gòu)建 DOM 的過(guò)程非常相似,當(dāng)瀏覽器接收到一段 CSS,瀏覽器首先要做的是識(shí)別出 Token,然后構(gòu)建節(jié)點(diǎn)并生成 CSSOM。


          在這一過(guò)程中,瀏覽器會(huì)確定下每一個(gè)節(jié)點(diǎn)的樣式到底是什么,并且這一過(guò)程其實(shí)是很消耗資源的。因?yàn)闃邮侥憧梢宰孕性O(shè)置給某個(gè)節(jié)點(diǎn),也可以通過(guò)繼承獲得。在這一過(guò)程中,瀏覽器得遞歸 CSSOM 樹(shù),然后確定具體的元素到底是什么樣式。

          注意:CSS 匹配 HTML 元素是一個(gè)相當(dāng)復(fù)雜和有性能問(wèn)題的事情。所以,DOM 樹(shù)要小,CSS 盡量用 id 和 class,千萬(wàn)不要過(guò)渡層疊下去

          構(gòu)建渲染樹(shù)

          當(dāng)我們生成 DOM 樹(shù)和 CSSOM 樹(shù)以后,就需要將這兩棵樹(shù)組合為渲染樹(shù)。


          在這一過(guò)程中,不是簡(jiǎn)單的將兩者合并就行了。渲染樹(shù)只會(huì)包括需要顯示的節(jié)點(diǎn)和這些節(jié)點(diǎn)的樣式信息,如果某個(gè)節(jié)點(diǎn)是 display: none 的,那么就不會(huì)在渲染樹(shù)中顯示。

          我們或許有個(gè)疑惑:瀏覽器如果渲染過(guò)程中遇到 JS 文件怎么處理

          渲染過(guò)程中,如果遇到<script>就停止渲染,執(zhí)行 JS 代碼。因?yàn)闉g覽器有 GUI 渲染線程與 JS 引擎線程,為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,這兩個(gè)線程是互斥的關(guān)系。JavaScript 的加載、解析與執(zhí)行會(huì)阻塞 DOM 的構(gòu)建,也就是說(shuō),在構(gòu)建 DOM 時(shí),HTML 解析器若遇到了 JavaScript,那么它會(huì)暫停構(gòu)建 DOM,將控制權(quán)移交給 JavaScript 引擎,等 JavaScript 引擎運(yùn)行完畢,瀏覽器再?gòu)闹袛嗟牡胤交謴?fù) DOM 構(gòu)建。

          也就是說(shuō),如果你想首屏渲染的越快,就越不應(yīng)該在首屏就加載 JS 文件,這也是都建議將 script 標(biāo)簽放在 body 標(biāo)簽底部的原因。當(dāng)然在當(dāng)下,并不是說(shuō) script 標(biāo)簽必須放在底部,因?yàn)槟憧梢越o script 標(biāo)簽添加 defer 或者 async 屬性(下文會(huì)介紹這兩者的區(qū)別)。

          JS 文件不只是阻塞 DOM 的構(gòu)建,它會(huì)導(dǎo)致 CSSOM 也阻塞 DOM 的構(gòu)建

          原本 DOM 和 CSSOM 的構(gòu)建是互不影響,井水不犯河水,但是一旦引入了 JavaScript,CSSOM 也開(kāi)始阻塞 DOM 的構(gòu)建,只有 CSSOM 構(gòu)建完畢后,DOM 再恢復(fù) DOM 構(gòu)建。

          這是什么情況?

          這是因?yàn)?JavaScript 不只是可以改 DOM,它還可以更改樣式,也就是它可以更改 CSSOM。因?yàn)椴煌暾?CSSOM 是無(wú)法使用的,如果 JavaScript 想訪問(wèn) CSSOM 并更改它,那么在執(zhí)行 JavaScript 時(shí),必須要能拿到完整的 CSSOM。所以就導(dǎo)致了一個(gè)現(xiàn)象,如果瀏覽器尚未完成 CSSOM 的下載和構(gòu)建,而我們卻想在此時(shí)運(yùn)行腳本,那么瀏覽器將延遲腳本執(zhí)行和 DOM 構(gòu)建,直至其完成 CSSOM 的下載和構(gòu)建。也就是說(shuō),在這種情況下,瀏覽器會(huì)先下載和構(gòu)建 CSSOM,然后再執(zhí)行 JavaScript,最后在繼續(xù)構(gòu)建 DOM


          布局與繪制

          當(dāng)瀏覽器生成渲染樹(shù)以后,就會(huì)根據(jù)渲染樹(shù)來(lái)進(jìn)行布局(也可以叫做回流)。這一階段瀏覽器要做的事情是要弄清楚各個(gè)節(jié)點(diǎn)在頁(yè)面中的確切位置和大小。通常這一行為也被稱(chēng)為“自動(dòng)重排”。

          布局流程的輸出是一個(gè)“盒模型”,它會(huì)精確地捕獲每個(gè)元素在視口內(nèi)的確切位置和尺寸,所有相對(duì)測(cè)量值都將轉(zhuǎn)換為屏幕上的絕對(duì)像素。

          布局完成后,瀏覽器會(huì)立即發(fā)出“Paint Setup”和“Paint”事件,將渲染樹(shù)轉(zhuǎn)換成屏幕上的像素。

          以上我們?cè)敿?xì)介紹了瀏覽器工作流程中的重要步驟,接下來(lái)我們討論幾個(gè)相關(guān)的問(wèn)題:

          幾點(diǎn)補(bǔ)充說(shuō)明

          1.async 和 defer 的作用是什么?有什么區(qū)別?

          接下來(lái)我們對(duì)比下 defer 和 async 屬性的區(qū)別:


          其中藍(lán)色線代表 JavaScript 加載;紅色線代表 JavaScript 執(zhí)行;綠色線代表 HTML 解析。

          1)情況 1<script src="script.js"></script>

          沒(méi)有 defer 或 async,瀏覽器會(huì)立即加載并執(zhí)行指定的腳本,也就是說(shuō)不等待后續(xù)載入的文檔元素,讀到就加載并執(zhí)行。

          2)情況 2<script async src="script.js"></script> (異步下載)

          async 屬性表示異步執(zhí)行引入的 JavaScript,與 defer 的區(qū)別在于,如果已經(jīng)加載好,就會(huì)開(kāi)始執(zhí)行——無(wú)論此刻是 HTML 解析階段還是 DOMContentLoaded 觸發(fā)之后。需要注意的是,這種方式加載的 JavaScript 依然會(huì)阻塞 load 事件。換句話(huà)說(shuō),async-script 可能在 DOMContentLoaded 觸發(fā)之前或之后執(zhí)行,但一定在 load 觸發(fā)之前執(zhí)行。

          3)情況 3 <script defer src="script.js"></script>(延遲執(zhí)行)

          defer 屬性表示延遲執(zhí)行引入的 JavaScript,即這段 JavaScript 加載時(shí) HTML 并未停止解析,這兩個(gè)過(guò)程是并行的。整個(gè) document 解析完畢且 defer-script 也加載完成之后(這兩件事情的順序無(wú)關(guān)),會(huì)執(zhí)行所有由 defer-script 加載的 JavaScript 代碼,然后觸發(fā) DOMContentLoaded 事件。

          defer 與相比普通 script,有兩點(diǎn)區(qū)別:載入 JavaScript 文件時(shí)不阻塞 HTML 的解析,執(zhí)行階段被放到 HTML 標(biāo)簽解析完成之后。

          在加載多個(gè) JS 腳本的時(shí)候,async 是無(wú)順序的加載,而 defer 是有順序的加載。

          2. 為什么操作 DOM 慢?

          把 DOM 和 JavaScript 各自想象成一個(gè)島嶼,它們之間用收費(fèi)橋梁連接。——《高性能 JavaScript》

          JS 是很快的,在 JS 中修改 DOM 對(duì)象也是很快的。在 JS 的世界里,一切是簡(jiǎn)單的、迅速的。但 DOM 操作并非 JS 一個(gè)人的獨(dú)舞,而是兩個(gè)模塊之間的協(xié)作。

          因?yàn)?DOM 是屬于渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當(dāng)我們用 JS 去操作 DOM 時(shí),本質(zhì)上是 JS 引擎和渲染引擎之間進(jìn)行了“跨界交流”。這個(gè)“跨界交流”的實(shí)現(xiàn)并不簡(jiǎn)單,它依賴(lài)了橋接接口作為“橋梁”(如下圖)。


          過(guò)“橋”要收費(fèi)——這個(gè)開(kāi)銷(xiāo)本身就是不可忽略的。我們每操作一次 DOM(不管是為了修改還是僅僅為了訪問(wèn)其值),都要過(guò)一次“橋”。過(guò)“橋”的次數(shù)一多,就會(huì)產(chǎn)生比較明顯的性能問(wèn)題。因此“減少 DOM 操作”的建議,并非空穴來(lái)風(fēng)。

          3. 你真的了解回流和重繪嗎?

          渲染的流程基本上是這樣(如下圖黃色的四個(gè)步驟):

          1. 計(jì)算 CSS 樣式

          2. 構(gòu)建 Render Tree

          3.Layout – 定位坐標(biāo)和大小

          4. 正式開(kāi)畫(huà)


          注意:上圖流程中有很多連接線,這表示了 Javascript 動(dòng)態(tài)修改了 DOM 屬性或是 CSS 屬性會(huì)導(dǎo)致重新 Layout,但有些改變不會(huì)重新 Layout,就是上圖中那些指到天上的箭頭,比如修改后的 CSS rule 沒(méi)有被匹配到元素。

          這里重要要說(shuō)兩個(gè)概念,一個(gè)是 Reflow,另一個(gè)是 Repaint

          重繪:當(dāng)我們對(duì) DOM 的修改導(dǎo)致了樣式的變化、卻并未影響其幾何屬性(比如修改了顏色或背景色)時(shí),瀏覽器不需重新計(jì)算元素的幾何屬性、直接為該元素繪制新的樣式(跳過(guò)了上圖所示的回流環(huán)節(jié))。

          回流:當(dāng)我們對(duì) DOM 的修改引發(fā)了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時(shí),瀏覽器需要重新計(jì)算元素的幾何屬性(其他元素的幾何屬性和位置也會(huì)因此受到影響),然后再將計(jì)算的結(jié)果繪制出來(lái),這個(gè)過(guò)程就是回流(也叫重排)。

          我們知道,當(dāng)網(wǎng)頁(yè)生成的時(shí)候,至少會(huì)渲染一次。在用戶(hù)訪問(wèn)的過(guò)程中,還會(huì)不斷重新渲染。重新渲染會(huì)重復(fù)回流 + 重繪或者只有重繪。

          回流必定會(huì)發(fā)生重繪,重繪不一定會(huì)引發(fā)回流。重繪和回流會(huì)在我們?cè)O(shè)置節(jié)點(diǎn)樣式時(shí)頻繁出現(xiàn),同時(shí)也會(huì)很大程度上影響性能。回流所需的成本比重繪高的多,改變父節(jié)點(diǎn)里的子節(jié)點(diǎn)很可能會(huì)導(dǎo)致父節(jié)點(diǎn)的一系列回流。

          1)常見(jiàn)引起回流屬性和方法

          任何會(huì)改變?cè)貛缀涡畔?(元素的位置和尺寸大小) 的操作,都會(huì)觸發(fā)回流,

          • 添加或者刪除可見(jiàn)的 DOM 元素;
          • 元素尺寸改變——邊距、填充、邊框、寬度和高度;
          • 內(nèi)容變化,比如用戶(hù)在 input 框中輸入文字;
          • 瀏覽器窗口尺寸改變——resize 事件發(fā)生時(shí);
          • 計(jì)算 offsetWidth 和 offsetHeight 屬性;
          • 設(shè)置 style 屬性的值。

          2)常見(jiàn)引起重繪屬性和方法


          3)如何減少回流、重繪

          • 使用 transform 替代 top;
          • 使用 visibility 替換 display: none ,因?yàn)榍罢咧粫?huì)引起重繪,后者會(huì)引發(fā)回流(改變了布局);
          • 不要把節(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量。

          復(fù)制代碼

          for(let i = 0; i < 1000; i++) {
           // 獲取 offsetTop 會(huì)導(dǎo)致回流,因?yàn)樾枰カ@取正確的值
           console.log(document.querySelector('.test').style.offsetTop)
          }
          
          • 不要使用 table 布局,可能很小的一個(gè)小改動(dòng)會(huì)造成整個(gè) table 的重新布局;
          • 動(dòng)畫(huà)實(shí)現(xiàn)的速度的選擇,動(dòng)畫(huà)速度越快,回流次數(shù)越多,也可以選擇使用 requestAnimationFrame;
          • CSS 選擇符從右往左匹配查找,避免節(jié)點(diǎn)層級(jí)過(guò)多;
          • 將頻繁重繪或者回流的節(jié)點(diǎn)設(shè)置為圖層,圖層能夠阻止該節(jié)點(diǎn)的渲染行為影響別的節(jié)點(diǎn)。比如對(duì)于 video 標(biāo)簽來(lái)說(shuō),瀏覽器會(huì)自動(dòng)將該節(jié)點(diǎn)變?yōu)閳D層。

          性能優(yōu)化策略

          基于上面介紹的瀏覽器渲染原理,DOM 和 CSSOM 結(jié)構(gòu)構(gòu)建順序,初始化可以對(duì)頁(yè)面渲染做些優(yōu)化,提升頁(yè)面性能。

          • JS 優(yōu)化: <script> 標(biāo)簽加上 defer 屬性 和 async 屬性 用于在不阻塞頁(yè)面文檔解析的前提下,控制腳本的下載和執(zhí)行。
          • defer 屬性: 用于開(kāi)啟新的線程下載腳本文件,并使腳本在文檔解析完成后執(zhí)行。
          • async 屬性: HTML5 新增屬性,用于異步下載腳本文件,下載完畢立即解釋執(zhí)行代碼。
          • CSS 優(yōu)化: <link> 標(biāo)簽的 rel 屬性 中的屬性值設(shè)置為 preload 能夠讓你在你的 HTML 頁(yè)面中可以指明哪些資源是在頁(yè)面加載完成后即刻需要的,最優(yōu)的配置加載順序,提高渲染性能。

          總結(jié)

          綜上所述,我們得出這樣的結(jié)論:

          • 瀏覽器工作流程:構(gòu)建 DOM -> 構(gòu)建 CSSOM -> 構(gòu)建渲染樹(shù) -> 布局 -> 繪制。
          • CSSOM 會(huì)阻塞渲染,只有當(dāng) CSSOM 構(gòu)建完畢后才會(huì)進(jìn)入下一個(gè)階段構(gòu)建渲染樹(shù)。
          • 通常情況下 DOM 和 CSSOM 是并行構(gòu)建的,但是當(dāng)瀏覽器遇到一個(gè)不帶 defer 或 async 屬性的 script 標(biāo)簽時(shí),DOM 構(gòu)建將暫停,如果此時(shí)又恰巧瀏覽器尚未完成 CSSOM 的下載和構(gòu)建,由于 JavaScript 可以修改 CSSOM,所以需要等 CSSOM 構(gòu)建完畢后再執(zhí)行 JS,最后才重新 DOM 構(gòu)建。

          參考文章

          • https://segmentfault.com/q/1010000000640869
          • https://coolshell.cn/articles/9666.html
          • https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5c024ecbf265da616a476638
          • https://mp.weixin.qq.com/s?__biz=MzA5NzkwNDk3MQ==&mid=2650588806&idx=1&sn=408a54e7c8102fd6944c9a40b119015a&chksm=8891d6a2bfe65fb42f493fe9a4dab672dd7e440f31e753196cee0cfbc6696e4f8dd3a669e040&mpshare=1&scene=1&srcid=1228ZrXsmbZKcgCSu7zTVDwy#
          • https://juejin.im/book/5b936540f265da0a9624b04b/section/5bac3a4df265da0aa81c043c
          • https://juejin.im/book/5c47343bf265da612b13e5c0/section/5c4737375188255de8397ae3
          • https://juejin.im/book/5a8f9ddcf265da4e9f6fb959/section/5a8f9f7bf265da4e82635e46

          更多內(nèi)容,請(qǐng)關(guān)注前端之巔。

          、背景

          Web 端實(shí)時(shí)預(yù)覽 H.265 需求一直存在,但由于之前 Chrome 本身不支持 H.265 硬解,軟解性能消耗大,僅能支持一路播放,該需求被擱置。
          去年 9 月份,Chrome 發(fā)布 M106 版本,默認(rèn)開(kāi)啟 H.265 硬解,使得實(shí)時(shí)預(yù)覽支持 H.265 硬解具備可行性。

          然而 WebRTC 本身支持的視頻編碼格式僅包括 VP8、VP9、H.264、AV1,并不包含 H.265。根據(jù) w3c 發(fā)布的 2023 WebRTC Next Version Use Cases 來(lái)看,近期也沒(méi)有打算支持 H.265 的跡象,因而決定自研實(shí)現(xiàn) WebRTC 對(duì) H.265 的支持。

          2、DataChannel

          背景說(shuō)到 chrome 支持了 h265 的硬解,但 WebRTC 并不支持直接傳輸 h265 視頻流。但可以通過(guò) datachannel 來(lái)繞過(guò)這個(gè)限制

          WebRTC 的數(shù)據(jù)通道 DataChannel 是專(zhuān)門(mén)用來(lái)傳輸除音視頻數(shù)據(jù)之外的任何數(shù)據(jù)的(但并不意味著不可以傳輸音視頻數(shù)據(jù),本質(zhì)上它就是一條 socket 通道),如短消息、實(shí)時(shí)文字聊天、文件傳輸、遠(yuǎn)程桌面、游戲控制、P2P加速等。

          1)SCTP協(xié)議

          DataChannel 使用的協(xié)議是 SCTP(Stream Control Transport Protocol) (是一種與TCP、UDP同級(jí)的傳輸協(xié)議),可以直接在 IP 協(xié)議之上運(yùn)行。

          但在 WebRTC 的情況下,SCTP 通過(guò)安全的 DTLS 隧道進(jìn)行隧道傳輸,該隧道本身在 UDP 之上運(yùn)行,同時(shí)支持流控、擁塞控制、按消息傳輸、傳輸模式可配置等特性。需注意單次發(fā)送消息大小不能超過(guò) maxMessageSize(只讀, 默認(rèn)65535字節(jié))。

          2)可配置傳輸模式

          DataChannel 可以配置在不同模式中,一種是使用重傳機(jī)制的可靠傳輸模式(默認(rèn)模式),可以確保數(shù)據(jù)成功傳輸?shù)綄?duì)等端;另一種是不可靠傳輸模式,該模式下可以通過(guò)設(shè)置 maxRetransmits 指定最大傳輸次數(shù),或通過(guò) maxPacketLife 設(shè)置傳輸間隔時(shí)間實(shí)現(xiàn);

          這兩種配置項(xiàng)是互斥的,不可同時(shí)設(shè)置,當(dāng)同為null 時(shí)使用可靠傳輸模式,有一個(gè)值不為 null 時(shí)開(kāi)啟不可靠傳輸模式。

          3)支持?jǐn)?shù)據(jù)類(lèi)型

          數(shù)據(jù)通道支持 string 類(lèi)型或 ArrayBuffer 類(lèi)型,即二進(jìn)制流或字符串?dāng)?shù)據(jù)。

          后續(xù)兩種方案,都是基于 datachannel 來(lái)做

          3、方案一 WebCodecs

          官方文檔: github.com/w3c/webcode…

          思路: DataChannel 傳輸 H.265 裸流 + Webcodecs 解碼 + Canvas 渲染。即 WebRTC 的音視頻傳輸通道(PeerConnection) 不支持 H.265 編碼格式,但可采用其數(shù)據(jù)通道(DataChannel)來(lái)傳輸 H.265數(shù)據(jù),前端收到后使用 Wecodecs 解碼、Canvas 渲染。

          優(yōu)點(diǎn):

          • 直接傳輸 H.265 裸碼流,無(wú)需額外封裝,實(shí)現(xiàn)簡(jiǎn)單方便;無(wú)冗余數(shù)據(jù),傳輸效率高
          • Wecodecs 解碼延遲低,實(shí)時(shí)性很高

          缺點(diǎn):

          • 音頻需額外單獨(dú)傳輸、解碼和播放,需處理音視頻同步問(wèn)題
          • 既有 sdk 基于 video 封裝,webcodes 方案依賴(lài) canvas,既有 video 相關(guān)操作,需要全部重寫(xiě),比如截圖,錄像等操作
          • 由于線上各項(xiàng)目等歷史原因,既有 sdk 改動(dòng)大,時(shí)間上不允許

          4、方案二 MSE

          官方例子: github.com/bitmovin/ms…

          思路:Fmp4封裝 + DataChannel 傳輸 + MSE 解碼播放。即先將 H.265 視頻數(shù)據(jù)封裝成 Fmp4 格式,再通過(guò) WebRTC DataChannel 通道進(jìn)行傳輸,前端收到后采用 MSE 解碼, video 進(jìn)行播放。

          優(yōu)點(diǎn):

          • 復(fù)用 video 標(biāo)簽播放,無(wú)需單獨(dú)實(shí)現(xiàn)渲染
          • 音視頻已封裝到 Fmp4 中,web 端無(wú)需考慮音視頻同步問(wèn)題
          • 整體工作量相比 Wecodecs 小,可快速上線

          缺點(diǎn):

          • 設(shè)備端實(shí)現(xiàn) Fmp4 封裝可能存在性能問(wèn)題,因此需要云端轉(zhuǎn)發(fā)實(shí)時(shí)進(jìn)行解封裝,或者前端解封裝
          • MSE 解碼實(shí)時(shí)性不好(云端首次切片會(huì)有 1~2 秒延遲)

          相關(guān)學(xué)習(xí)資料推薦,點(diǎn)擊下方鏈接免費(fèi)報(bào)名,先碼住不迷路~】

          音視頻免費(fèi)學(xué)習(xí)地址:FFmpeg/WebRTC/RTMP/NDK/Android音視頻流媒體高級(jí)開(kāi)發(fā)

          【免費(fèi)分享】音視頻學(xué)習(xí)資料包、大廠面試題、技術(shù)視頻和學(xué)習(xí)路線圖,資料包括(C/C++,Linux,F(xiàn)Fmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以點(diǎn)擊788280672加群免費(fèi)領(lǐng)取~

          5、方案抉擇

          第一版本先以 MSE 上線。云端,前端開(kāi)發(fā)量相對(duì)少,roi 高。

          計(jì)劃第二版上 wecodecs,不僅低延遲,而且可以避免云端耗流量的問(wèn)題,節(jié)省成本。假設(shè)在第二版期間,WebRTC 官方支持了 H.265,那么直接兼容官方方案即可。


          5.1 細(xì)說(shuō) Mse 及第一版 sdk 改造

          Media Source Extensions, 媒體源擴(kuò)展。官方文檔: developer.mozilla.org/zh-CN/docs/…

          引入 MSE 之后,支持 HTML5 的 Web 瀏覽器就變成了能夠解析流協(xié)議的播放器了。

          從另一個(gè)角度來(lái)說(shuō),通過(guò)引入 MSE,HTML5 標(biāo)簽不僅可以直接播放其默認(rèn)支持的 mp4、m3u8、webm、ogg 等格式,還可以支持能夠被 (具備MSE功能的)JS 處理的視頻流格式。如此一來(lái),我們就可以通過(guò) (具備MSE功能的)JS,把一些原本不支持的視頻流格式,轉(zhuǎn)化為其支持的格式(如 H.264 的 mp4,H.265 的 fmp4)。

          比如 B站開(kāi)源的 flv.js 就是一個(gè)典型應(yīng)用場(chǎng)景。B站的 HTML5 播放器,通過(guò)使用 MSE 技術(shù),將 FLV源用 JS(flv.js) 實(shí)時(shí)轉(zhuǎn)碼成 HTML5 支持的視頻流編碼格式,提供給 HTML5 播放器播放。

          // 此 demo 來(lái)自下面鏈接的官方示例, 可以直接跑起來(lái),比較直觀
          // https://github.com/bitmovin/mse-demo/blob/main/index.html
          
          <!DOCTYPE html>
          <html lang="en">
          <head>
            <meta charset="UTF-8">
            <title>MSE Demo</title>
          </head>
          <body>
            <h1>MSE Demo</h1>
            <div>
              <video controls width="80%"></video>
            </div>
          
            <script type="text/javascript">
              (function() {
                var baseUrl = 'https://bitdash-a.akamaihd.net/content/MI201109210084_1/video/720_2400000/dash/';
                var initUrl = baseUrl + 'init.mp4';
                var templateUrl = baseUrl + 'segment_$Number$.m4s';
                var sourceBuffer;
                var index = 0;
                var numberOfChunks = 52;
                var video = document.querySelector('video');
          
                if (!window.MediaSource) {
                  console.error('No Media Source API available');
                  return;
                }
                  
                // 初始化 mse
                var ms = new MediaSource();
                video.src = window.URL.createObjectURL(ms);
                ms.addEventListener('sourceopen', onMediaSourceOpen);
          
                function onMediaSourceOpen() {
                  // codecs,初始化 sourceBuffer
                  sourceBuffer = ms.addSourceBuffer('video/mp4; codecs="avc1.4d401f"');
                  sourceBuffer.addEventListener('updateend', nextSegment);
          
                  GET(initUrl, appendToBuffer);
                  
                  // 播放
                  video.play();
                }
          
                function nextSegment() {
                  var url = templateUrl.replace('$Number$', index);
                  GET(url, appendToBuffer);
                  index++;
                  if (index > numberOfChunks) {
                    sourceBuffer.removeEventListener('updateend', nextSegment);
                  }
                }
          
                function appendToBuffer(videoChunk) {
                  if (videoChunk) {
                    // 二進(jìn)制流轉(zhuǎn)換為 Uint8Array,sourceBuffer 進(jìn)行消費(fèi)
                    sourceBuffer.appendBuffer(new Uint8Array(videoChunk));
                  }
                }
          
                function GET(url, callback) {
                  var xhr = new XMLHttpRequest();
                  xhr.open('GET', url);
                  xhr.responseType = 'arraybuffer';
          
                  xhr.onload = function(e) {
                    if (xhr.status != 200) {
                      console.warn('Unexpected status code ' + xhr.status + ' for ' + url);
                      return false;
                    }
                    // 獲取 mp4 二進(jìn)制流
                    callback(xhr.response);
                  };
          
                  xhr.send();
                }
              })();
            </script>
          </body>
          </html>
          

          通過(guò)上面的 demo,以及測(cè)試(將 dmeo 中的 fmp4 片段換成我們自己的 IPC 設(shè)備(攝像頭),H.265 類(lèi)型的)得知,chrome 可以硬解 H.265 類(lèi)型的 fmp4 片段。So,事情變得明朗了起來(lái)。大方向有了,無(wú)非就是 H.265 裸流,轉(zhuǎn)換成 fmp4 片段,chrome 底層硬解。

          5.2 fmp4 前端實(shí)時(shí)解封裝

          H.265 裸流解封裝 fmp4,調(diào)研下來(lái),如果純 js 進(jìn)行封裝,工作量挺大。嘗試用 wasm 調(diào) c++ 的庫(kù),發(fā)現(xiàn)即使解封裝性能也不大好。所以放在前端被 pass 掉了。

          5.3 fmp4 云端實(shí)時(shí)解封裝

          性能好,對(duì)前端 0 侵入。確定了云端解封裝,接下來(lái)講講這段時(shí)間開(kāi)發(fā)遇到的核心鏈路演變,及最終的流程方案。

          6、階段一

          云端實(shí)時(shí)解封裝 Fmp4,寫(xiě)死 codecs(音視頻編碼類(lèi)型) -> 前端 MSE 解碼播放 -> 播放幾秒后,失敗,MSE 會(huì)拋異常,大概意思就是你的數(shù)據(jù)不對(duì)了,前后銜接不上。

          排查下來(lái),是 MSE 處于 updating 的時(shí)候,不能進(jìn)行消費(fèi),數(shù)據(jù)直接被丟掉,導(dǎo)致后續(xù)數(shù)據(jù)銜接不上。那既然不能丟,我們就緩存下來(lái)。具體可以看下面的代碼注釋。

          具體可以看代碼注釋:

          const updating = this.sourceBuffer?.updating === true;
          const bufferQueueEmpty = this.bufferQueue.length === 0;
          
            if (!updating) {
              if (bufferQueueEmpty) {
                // 緩存隊(duì)列為空: 僅消費(fèi)本次 buffer
                this.appendBuffer(curBuffer);
              } else {
                // 緩存隊(duì)列不為空: 消費(fèi)隊(duì)列 + 本次 buffer
                this.bufferQueue.push(curBuffer);
          
                // 隊(duì)列中多個(gè) buffer 的合并
                const totalBufferByteLen = this.bufferQueue.reduce(
                  (pre, cur) => pre + cur.byteLength,
                  0
                );
                const combinedBuffer = new Uint8Array(totalBufferByteLen);
                let offset = 0;
                this.bufferQueue.forEach((array, index) => {
                  offset += index > 0 ? this.bufferQueue[index - 1].length : 0;
                  combinedBuffer.set(array, offset);
                });
          
                this.appendBuffer(combinedBuffer);
                this.bufferQueue = [];
              }
            } else {
              // mse 還在消費(fèi)上一次 buffer(處于 updating 中), 緩存本次 buffer, 否則會(huì)有丟幀問(wèn)題
              this.bufferQueue.push(curBuffer);
            }
          

          考慮到 Fmp4 數(shù)據(jù)每一幀都不可丟失,因此 datachannel 走的是可靠傳輸。

          但是測(cè)試下來(lái),發(fā)現(xiàn)了新的問(wèn)題。隨著時(shí)間的增長(zhǎng),延遲會(huì)累積增大。因?yàn)閬G包后,網(wǎng)絡(luò)層會(huì)進(jìn)行重試,重試的時(shí)間會(huì)累積進(jìn)延時(shí)。我們測(cè)試下來(lái),網(wǎng)絡(luò)情況不好的時(shí)候,延遲會(huì)高達(dá) 30 秒及以上,理論上會(huì)一直增加,如果你拉流時(shí)間足夠久的話(huà)

          7、階段二

          ok,換個(gè)思路,既然不丟幀 + 可靠傳輸帶來(lái)的延時(shí)問(wèn)題完全不能接受,那么如果換用不可靠傳輸呢?

          不可靠傳輸,意味著會(huì)丟幀。調(diào)研下來(lái),F(xiàn)mp4 可以丟掉一整個(gè)切片(一個(gè)切片包含多幀),既然如此,我們可以設(shè)計(jì)一套丟幀算法,只要判斷到一個(gè)切片是不完整的,我們就把整個(gè)切片丟掉。

          這樣的話(huà),理論上來(lái)講,最多只會(huì)有一個(gè)切片的延遲,大概在2秒左右,業(yè)務(wù)層可以接受。

          丟幀算法設(shè)計(jì)思路:在每一幀數(shù)據(jù)頭部增加 4 個(gè)字節(jié)的數(shù)據(jù),用來(lái)標(biāo)識(shí)每一幀的具體信息。

          • segNum: 2個(gè)字節(jié),大端模式,F(xiàn)mp4片段序列號(hào),從1開(kāi)始,每次加1
          • fragCount: 1個(gè)字節(jié),F(xiàn)mp4片段分片總數(shù),最小為1
          • fragSeq: 1個(gè)字節(jié),F(xiàn)mp4片段分片序列號(hào),從1開(kāi)始

          前端拿到每幀數(shù)據(jù)后,對(duì)前 4 個(gè)字節(jié)進(jìn)行解析,就能獲取到每幀數(shù)據(jù)的詳細(xì)信息。舉個(gè)例子,假如我要判斷當(dāng)前幀是否為最后一幀,只需要判斷 fragCount 是否等于 fragSeq 即可。

          算法大致流程圖:

          具體解釋一下:

          • frameQueue, 用來(lái)緩存每一幀的數(shù)據(jù),用來(lái)跟后面一幀數(shù)據(jù)進(jìn)行對(duì)比,判斷是否為完整幀
          • bufferQueue, 此隊(duì)列中的數(shù)據(jù),都是完整的切片數(shù)據(jù),保證 MSE 進(jìn)行消費(fèi)時(shí),數(shù)據(jù)沒(méi)有缺失
            /**
             * fmp4 切片隊(duì)列 frameQueue,處理丟幀,生產(chǎn) bufferQueue 內(nèi)容
             *
             * @param frameObj 每一幀的相關(guān)數(shù)據(jù)
             *      每來(lái)一幀進(jìn)行判斷
             *      buffer中加上當(dāng)前幀是否為連續(xù)幀(從第一幀開(kāi)始的連續(xù)幀)
             *        是
             *          當(dāng)前幀是否為最后一幀
             *            是 拼接buffer幀以及當(dāng)前幀,組成完整幀,放入另外一個(gè)待消費(fèi) buffer
             *            否 當(dāng)前幀入 buffer
             *        否 清空 buffer,當(dāng)前幀入 buffer
             */
          
          const frameQueueLen = this.frameQueue.length;
          const frameQueueEmpty = frameQueueLen === 0;
          
            // 單一完整分片幀單獨(dú)處理,直接進(jìn)行消費(fèi)
            if (frameObj.fragCount === 1) {
              if (!frameQueueEmpty) {
                this.frameQueue = [];
              }
              this.bufferQueue.push(frameObj.value);
              return;
            }
          
            if (frameQueueEmpty) {
              this.frameQueue.push(frameObj);
              return;
            }
          
            // 是否為首幀
            let isFirstFragSeq = this.frameQueue[0].fragSeq === 1;
            // 當(dāng)前幀加上queue幀是否為連續(xù)幀
            let isContinuousFragSeq = true;
            for (let i = 0; i < frameQueueLen; i++) {
              const isLast = i === frameQueueLen - 1;
          
              const curFragSeq = this.frameQueue[i].fragSeq;
              const nextFragSeq = isLast
                ? frameObj.fragSeq
                : this.frameQueue[i + 1].fragSeq;
          
              const curSegNum = this.frameQueue[i].segNum;
              const nextSeqNum = isLast
                ? frameObj.segNum
                : this.frameQueue[i + 1].segNum;
          
              if (curFragSeq + 1 !== nextFragSeq || curSegNum !== nextSeqNum) {
                isContinuousFragSeq = false;
                break;
              }
            }
          
            if (isFirstFragSeq && isContinuousFragSeq) {
              // 是否為最后一幀
              const isLastFrame = frameObj.fragCount === frameObj.fragSeq;
              if (isLastFrame) {
                this.frameQueue.forEach((item) => {
                  this.bufferQueue.push(item.value);
                });
                this.frameQueue = [];
                this.bufferQueue.push(frameObj.value);
              } else {
                this.frameQueue.push(frameObj);
              }
            } else {
              // 丟幀則清空 frameQueue,則代表直接丟棄整個(gè) segment 切片
              this.emit(EVENTS_ERROR.frameDropError);
              this.frameQueue = [];
              this.frameQueue.push(frameObj);
            }
          

          原本以為大功告成,結(jié)果意想不到的事情發(fā)生了。

          當(dāng)出現(xiàn)丟幀時(shí),通過(guò)上面的算法,確實(shí)是把整個(gè)切片的數(shù)據(jù)丟棄掉了,但是 MSE 此時(shí)居然再次異常了,意思也是說(shuō)數(shù)據(jù)序列不對(duì),導(dǎo)致解析失敗。

          可是用 ffplay 在本地測(cè)試(丟掉一整個(gè)切片后,是可以繼續(xù)播放的),陷入僵局,繼續(xù)排查。

          8、階段三

          話(huà)說(shuō)最近 chatgpt 不是挺火,嘗試著用了下,確實(shí)找到了原因。MSE 在消費(fèi) fmp4 數(shù)據(jù)時(shí),需要根據(jù)內(nèi)部序列號(hào)進(jìn)行索引標(biāo)識(shí),因此即使是丟掉整個(gè)切片數(shù)據(jù),還是會(huì)播放失敗。怎么辦?難道要回到不可靠傳輸?

          經(jīng)過(guò)一番權(quán)衡,最終決定,當(dāng)出現(xiàn)丟幀時(shí),前端通知云端,重新進(jìn)行切片,并且此時(shí)前端重新初始化 MSE。

          改造下來(lái)發(fā)現(xiàn),效果還不錯(cuò),我們把不可靠傳輸,datachannel 重傳次數(shù)設(shè)置為 5。

          出現(xiàn)丟幀的概率大大減小,就算出現(xiàn)丟幀,也只會(huì)有不到 2 秒的 loading,然后繼續(xù)出畫(huà)面,業(yè)務(wù)層可以接受。

          最終,經(jīng)過(guò)上面 3 個(gè)階段的改造,就有了整個(gè)鏈路圖。當(dāng)然其實(shí)還有很多細(xì)節(jié),沒(méi)有講到,比如利用 mp4box 獲取 codec, 前端定時(shí)檢查 datachannel 狀態(tài)等,就不展開(kāi)細(xì)說(shuō)了。有興趣的可以留言討論

          完整的鏈路圖,簡(jiǎn)單畫(huà)了下。

          9、總結(jié)

          目前 datachannel + MSE 的方案已經(jīng)上線,測(cè)試下來(lái),線上同時(shí)硬解 16 路沒(méi)有性能問(wèn)題。

          后續(xù)會(huì)嘗試用 webcodes 來(lái)進(jìn)行 H.265 的解析,并處理音視頻同步等問(wèn)題。徹底解決掉延時(shí)的問(wèn)題。

          下一篇準(zhǔn)備寫(xiě)日常排查 WebRTC 問(wèn)題的一些思路,也歡迎評(píng)論區(qū)聊一下日常遇到的一些問(wèn)題,下篇一起匯總。

          原文 https://juejin.cn/post/7215608036394614844?searchId=20230814145744EF4FC3208E60C3F568D8


          主站蜘蛛池模板: 人妻免费一区二区三区最新| 无码人妻品一区二区三区精99| 精品一区二区三区高清免费观看| 日韩免费无码视频一区二区三区| 最新中文字幕一区二区乱码| 国产免费一区二区视频| 国产精品一区二区久久沈樵| 波多野结衣久久一区二区| 亚洲啪啪综合AV一区| 亚洲国产精品一区二区九九| 国产一区二区三区无码免费| 成人毛片一区二区| 精品一区精品二区制服| 日韩人妻一区二区三区免费 | 亲子乱AV视频一区二区| 一区二区三区久久精品| 国产成人一区二区三区电影网站| 亚洲一区二区三区无码国产| 免费无码一区二区三区| ...91久久精品一区二区三区 | 无码人妻久久一区二区三区免费丨| 久久久无码一区二区三区| 一区二区三区四区免费视频| 久久精品动漫一区二区三区| 亚洲国产一区国产亚洲 | 老鸭窝毛片一区二区三区| 手机福利视频一区二区| 精品国产日韩一区三区| 成人在线观看一区| 精品在线一区二区| 中文字幕亚洲一区二区三区| 国产一区二区三区免费观在线| 精品乱人伦一区二区三区| 亚洲制服中文字幕第一区| 国产美女一区二区三区| 免费一本色道久久一区| 亚洲人AV永久一区二区三区久久| 一区二区精品视频| 无码精品一区二区三区在线| 亚洲一区二区三区免费在线观看| 无码一区二区三区在线|