1. 程式人生 > >Webkit中HTML5 Video的實現分析-HTMLMediaElement & MediaPlayer

Webkit中HTML5 Video的實現分析-HTMLMediaElement & MediaPlayer

以下為針對Webkit中HTML5 Video實現原理的分析和學習總結。至於其中的UML圖檔,可以到下面的github連結中下載。

首先預覽一下Webkit中和Video相關的主要的類的層次結構:


其中主要的類為HTMLMediaElement, MediaPlayer以及RenderVideo(左下角).

HTMLMediaElement代表了DOM結點,其繼承關係可以追溯到Node,也就是DOM的結點。網頁中Video/Audio元件的解析就是由它的建立開始。JavaScript的視訊操作API也是以它為物件的。

MediaPlayer代表了媒體檔案的播放器功能。它起得是一個橋接(bridge mode)的功能,具體的實現交由繼承自MediaPlayerPrivateInterface的類來完成。也就是說,真正實現一個播放控制是由MediaPlayerPrivate來完成的。擴充套件播放器也要從這部分入手。

RenderMediaRenderVideo代表的是瀏覽器生成的渲染樹中的結點,負責處理繪製相關的工作(如位置、大小及重繪等)。它們的繼承關係可以追溯到RenderObject,其中還有一個層次是RenderReplaced,這裡有一個Replaced Element(可替換元件)的概念,查一下HTML的資料就可以了。

真正要展現它們的類圖,其層次是非常多的。所以只是按需展開就可以了。

根據學習的原則,我們需要逐個突破。我們先來考察一下HTMLMediaElement和MediaPlayer的互動關係。

首先,我們要從HTMLMediaElement對視訊播放涉及的狀態有個基本瞭解,下面是個簡要的播放狀態圖:

HTMLMediaElement還定了一個ReadyState來表示資料狀態:

它提供setReadyState方法供MediaPlayerPrivate等呼叫來改變相應的狀態。有關Ready States可以參考:

當建立一個HTMLMediaElement元件後,在解析節點屬性時會呼叫parseAttribute方法:

上圖亂碼是因去除Visual Paradigm的浮水印產生亂碼,中文是"載入相應的音訊或視訊"。這裡呼叫scheduleLoad就會觸發Webkit建立一個播放器了。也就是MediaPlayer的建立。但MediaPlayer的建立的時機很多,因為頁面有多種方式可以新新增一個video標籤或者src標籤,又或者頁面的取消載入後的恢復等多種情況。下面就列出可能呼叫MediaPlayer::Create的情況:

對應的,下面釋放MediaPlayer的時機:


MediaPlayer會呼叫MediaPlayerPrivate析構釋放已建立的播放器。

而播放觸發的條件也比較多,可以是指autoplay屬性的視訊,也可以是JavaScript執行了play()(load指令有些不同)等。如下圖所示:

其中UML使用Visual Paradigm繪製,可以到下面的GitHub下載:

MediaPlayerFactory

MediaPlayerFactory像是一個解碼庫一樣,初始化時,各個可用的播放器(MediaPlayerPrivate)向它註冊。當需要解碼時,由它給出一個合適的播放器(呼叫MediaPlayerPrivate::constructor,MediaPlayerPrivate::create)建立一個例項[MediaPlayer:: loadWithNextMediaEngine執行])。所以MediaPlayerMediaPlayerPrivate應當是一對一的關係。

installedMediaEngines()會有一個靜態的installed engines向量(Vector<MediaPlayerFactory*>),依據不同的OS,呼叫不同的MediaPlayerPrivateXXXregisterMediaEngine來註冊新的Media Engine以支援不同的編碼。如下圖所示。

bestMediaEngineForTypeAndCodecs()在當視訊的MIME type指定後,在MediaPlayer::loadWithNextMediaEngine中會呼叫它來獲取合適的engine進行播放,否則直接使用nextMediaEngine()來獲得播放有的engine.

詳細的程式碼在MediaPlayer.cpp中。

以下是MediaPlayerPrivateQtsupportsType的程式碼片段:

[cpp] view plaincopyprint?
  1. MediaPlayer::SupportsType MediaPlayerPrivateQt::supportsType(const String& mime, const String& codec)  
  2. {  
  3.     if (!mime.startsWith("audio/") && !mime.startsWith("video/"))  
  4.         return MediaPlayer::IsNotSupported;  
  5.    ……  
  6.    if (QMediaPlayer::hasSupport(mime, codecListTrimmed) >= QtMultimediaKit::ProbablySupported)  
  7.         return MediaPlayer::IsSupported;  
  8.     return MediaPlayer::MayBeSupported;  
  9. }  


RenderVideo的繪製操作

RenderVideo在繪製時重點是得出正確的大小。主要函式包括calculateInstrinicSize來得出大小,updatePlayer依據當前縮放比例計算出新的大小尺寸,paintReplaced則依據metric進行繪製操作(得出一個rect,再傳給player)。

下面是可能會涉及元素更新的流程:

其核心函式是calculateIntrinsicSize(): (所謂Intrinsic Size來自於CSS規範).

當paintReplaced函式(因為RenderVideo繼承自RenderReplaced)拿到Instrinic Size後會著手計算繪製區域以便進行繪製:

實際的繪製都會交由Media Player處理,而Media Player則交由它的m_private,也就是具體的播放器來處理。

這裡有兩個重點:

  1.如果視訊指定了poster,則繪製poster僅在RenderVideo中就完成了,並不會交給MediaPlayer控制元件來處理。(RenderVideo本身也是繼承自RenderImage,很容易處理圖片的問題。)

  2. 繪製MediaPlayer時,注意圖層的問題。這會影響到網頁的外觀,有些網頁為了美觀,會在視訊外加一些修飾用的框,這些框會因為paddings或margins計算上的問題與視訊重疊。這時Media Player所在圖層不同,表現來的外觀就不一樣了。(樂視的午間道在HTML5 Video模式就有這個問題. Chrome中將UA改為iPad就可以看到.)

明確主要類的生命週期有助於更好理解Webkit中視訊的工作流程。下圖展示了類間的引用關係。

如HTMLVideoElement與一個MediaPlayer對應,MediaPlayer與MediaPlayerPrivate一一對應,它們都是一個組合關係,當所有者自身被析構時,會自動釋放引用的例項。這個過程都是使用OwnPtr智慧指標來實現的。

 OwnPtr賦值操作的程式碼:

       OwnPtr& operator=(std::nullptr_t) { clear(); return *this; }

下面是HTMLMediaElement (HTMLVideoElement)中維護MediaPlayer的主要狀態 (只關注於建立與釋放過程):

當每次因為src屬性變化或新增節點等情況,最終會呼叫createMediaPlayer釋放原先的例項,再重新建立一個新的MediaPlayer例項。

最後是MediaPlayer維護MediaPlayerPrivate(m_private)的狀態圖。同上圖一相似。

MediaPlayer同MediaPlayerPrivateInterface,再同播放控制元件的互動過程應當要簡單、清晰。MediaPlayer通過自身的狀態(Network States 和 Ready States)來控制操作的步驟。MediaPlayerPrivateInterface具體到不同的平臺和視訊格式,使用的具體的播放控制元件會不同。

這裡僅貼一張時序圖,其中沒有特別區分HTMLVideoElement,使用的播放控制元件是Webkit預設的QuickTime元件。在除錯時,最好要將UA調成iPad版本,不然伺服器端可能提供的是Flash視訊,就無從除錯H5 Video了。


從程式碼中可以看到,WebKit將視訊控制元件在WebCore中建立,而不是丟到前端實現。另外在載入完成後,readyState是HAVE_FUTURE_DATA後,會自動觸發play()操作。

當視訊元件是動態建立時,會使用scheduleLoad執行載入操作。而scheduleLoad是一個Timer,不會立即觸發load函式,此時如果JS指令碼執行了play()其實是無效的,實際執行的會由MediaPlayer呼叫NullMediaPlayer完成,當然也是無效的。只有當載入資料後,readyState改變,再由回饋到HTMLMediaElement,然後觸發後面的play操作。

但當新的video元件建立後,不呼叫play()時為什麼沒有自動播放呢? 何時才真正進行播放,主要受到HTMLMediaElement的成員變數m_paused來控制的(雖然也涉及其它好幾個狀態,但m_paused卻在這時起著決定性作用)。可以通過研究HTMLMediaElement::updateReadyState()中觀察到(potentiallyPlaying函式)。