Chromium为视频标签 video 全屏播放的过程分析

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

       在Chromium中,<video>标签有全屏和非全屏两种播放模式。在非全屏模式下,<video>标签播放的视频嵌入在网页中显示,也就是视频画面作为网页的一部分显示。在全屏模式下,我们是看不到网页其它内容的,因此<video>标签播放的视频可以在一个独立的全屏窗口中显示。这两种截然不同的播放模式,导致Chromium使用不同的方式渲染视频画面。本文接下来就详细分析<video>标签全屏播放的过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       从前面Chromium为视频标签<video>渲染视频画面的过程分析一文可以知道,在Android平台上,<video>标签指定的视频是通过系统提供的MediaPlayer进行播放的。MediaPlayer提供了一个setSurface接口,用来给MediaPlayer设置一个Surface。Surface内部有一个GPU缓冲区队列,以后MediaPlayer会将解码出来的视频画面写入到这个队列中去。

       Surface有两种获取方式。第一种方式是通过SurfaceTexture构造一个新的Surface。第二种方式是从SurfaceView内部获得。在非全屏模式下,Chromium就是通过第一种方式构造一个Surface,然后设置给MediaPlayer的。在全屏模式下,Chromium将会直接创建一个全屏的SurfaceView,然后再从这个SurfaceView内部获得一个Surface,并且设置给MediaPlayer。

       在Android平台上,SurfaceView的本质是一个窗口。既然是窗口,那么它的UI就是由系统(SurfaceFlinger)合成在屏幕上显示的。它的UI就来源于它内部的Surface描述的GPU缓冲区队列。因此,当MediaPlayer将解码出来的视频画面写入到SurfaceView内部的Surface描述的GPU缓冲区队列去时,SurfaceFlinger就会从该GPU缓冲区队列中将新写入的视频画面提取出来,并且合成在屏幕上显示。关于SurfaceView的更多知识,可以参考前面Android视图SurfaceView的实现原理分析一文。

       Surface描述的GPU缓冲区队列,是一个生产者/消息者模型。在我们这个情景中,生产者便是MediaPlayer。如果Surface是通过SurfaceTexture构造的,那么SurfaceTexture的所有者,也就是Chromium,就是消费者。消费者有责任将视频画面从GPU缓冲区队列中提取出来,并且进行渲染。渲染完成后,再交给SurfaceFlinger合成显示在屏幕中。如果Surface是从SurfaceView内部获取的,那么SurfaceView就是消费者,然后再交给SurfaceFlinger合成显示在屏幕中。

       简单来说,在非全屏模式下,<video>标签的视频画面经过MediaPlayer->Chromium->SurfaceFlinger显示在屏幕中,而在全屏模式下,经过MediaPlayer->SurfaceView->SurfaceFlinger显示在屏幕中。

       Chromium支持<video>标签在全屏和非全屏模式之间无缝切换,也就是从一个模式切换到另外一个模式的时候,不需要重新创建MediaPlayer,只需要给原先使用的MediaPlayer设置一个新的Surface即可。图1描述的是<video>标签从非全屏模式切换为全屏模式的示意图,如下所示:


图1 <video>标签从非全屏模式切换为全屏模式

      当<video>标签从非全屏模式切换为全屏模式时,Chromium会为它创建一个全屏的SurfaceView,并且将这个SurfaceView内部的Surface设置给MediaPlayer。以后MediaPlayer就不会再将解码出来的视频画面通过原先设置的SurfaceTexture交给Chromium处理,而是通过后面设置的Surface交给SurfaceView处理。

      接下来,我们就结合源代码,从<video>标签进入全屏模式开始,分析<video>标签全屏播放视频的过程。从前面Chromium为视频标签<video>创建播放器的过程分析一文可以知道,在WebKit中,<video>标签是通过HTMLMediaElement类描述的。当<video>标签进入全屏模式时,HTMLMediaElement类的成员函数enterFullscreen就会被调用,它的实现如下所示:

void HTMLMediaElement::enterFullscreen(){    WTF_LOG(Media, "HTMLMediaElement::enterFullscreen");    FullscreenElementStack::from(document()).requestFullScreenForElement(this, 0, FullscreenElementStack::ExemptIFrameAllowFullScreenRequirement);}
       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。

       在WebKit中,网页的每一个标签都可以进入全屏模式。每一个网页都对应有一个FullscreenElementStack对象。这个FullscreenElementStack对象内部有一个栈,用来记录它对应的网页有哪些标签进入了全屏模式。

       HTMLMediaElement类的成员函数enterFullscreen首先调用成员函数document获得当前正在处理的<video>标签所属的网页,然后再通过调用FullscreenElementStack类的静态成员函数from获得这个网页所对应的FullscreenElementStack对象。有了这个FullscreenElementStack对象之后,就可以调用它的成员函数requestFullScreenForElement请求将当前正在处理的<video>标签设置为全屏模式。

       FullscreenElementStack类的成员函数requestFullScreenForElement的实现如下所示:

void FullscreenElementStack::requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType){    ......    // The Mozilla Full Screen API <https://wiki.mozilla.org/Gecko:FullScreenAPI> has different requirements    // for full screen mode, and do not have the concept of a full screen element stack.    bool inLegacyMozillaMode = (flags & Element::LEGACY_MOZILLA_REQUEST);    do {        ......        // 1. If any of the following conditions are true, terminate these steps and queue a task to fire        // an event named fullscreenerror with its bubbles attribute set to true on the context object's        // node document:        ......        // The context object's node document fullscreen element stack is not empty and its top element        // is not an ancestor of the context object. (NOTE: Ignore this requirement if the request was        // made via the legacy Mozilla-style API.)        if (!m_fullScreenElementStack.isEmpty() && !inLegacyMozillaMode) {            Element* lastElementOnStack = m_fullScreenElementStack.last().get();            if (lastElementOnStack == element || !lastElementOnStack->contains(element))                break;        }        // A descendant browsing context's document has a non-empty fullscreen element stack.        bool descendentHasNonEmptyStack = false;        for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) {            ......            if (fullscreenElementFrom(*toLocalFrame(descendant)->document())) {                descendentHasNonEmptyStack = true;                break;            }        }        if (descendentHasNonEmptyStack && !inLegacyMozillaMode)            break;        ......        // 2. Let doc be element's node document. (i.e. "this")        Document* currentDoc = document();        // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc.        Deque<Document*> docs;        do {            docs.prepend(currentDoc);            currentDoc = currentDoc->ownerElement() ? ¤tDoc->ownerElement()->document() : 0;        } while (currentDoc);        // 4. For each document in docs, run these substeps:        Deque<Document*>::iterator current = docs.begin(), following = docs.begin();        do {            ++following;            // 1. Let following document be the document after document in docs, or null if there is no            // such document.            Document* currentDoc = *current;            Document* followingDoc = following != docs.end() ? *following : 0;            // 2. If following document is null, push context object on document's fullscreen element            // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute            // set to true on the document.            if (!followingDoc) {                from(*currentDoc).pushFullscreenElementStack(element);                addDocumentToFullScreenChangeEventQueue(currentDoc);                continue;            }            // 3. Otherwise, if document's fullscreen element stack is either empty or its top element            // is not following document's browsing context container,            Element* topElement = fullscreenElementFrom(*currentDoc);            if (!topElement || topElement != followingDoc->ownerElement()) {                // ...push following document's browsing context container on document's fullscreen element                // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute                // set to true on document.                from(*currentDoc).pushFullscreenElementStack(followingDoc->ownerElement());                addDocumentToFullScreenChangeEventQueue(currentDoc);                continue;            }            // 4. Otherwise, do nothing for this document. It stays the same.        } while (++current != docs.end());        // 5. Return, and run the remaining steps asynchronously.        // 6. Optionally, perform some animation.        ......        document()->frameHost()->chrome().client().enterFullScreenForElement(element);        // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen.        return;    } while (0);    ......}

       这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/FullscreenElementStack.cpp中。

       FullscreenElementStack类的成员函数requestFullScreenForElement主要是用来为网页中的每一个Document建立一个Stack。这个Stack记录了Document中所有请求设置为全屏模式的标签。我们通过图2所示的例子说明FullscreenElementStack类的成员函数requestFullScreenForElement的实现:


图2 Fullscreen Stack for Document

       图2所示的网页包含了两个Document:Doc1和Doc2。其中,Doc1通过<iframe>标签加载了Doc2,后者里面有一个<video>标签。当Doc2里面的<video>标签被设置为全屏模式时,Doc1的Stack会被压入一个<iframe>标签和一个<video>标签,其中,<iframe>标签代表的是Doc1,Doc2的Stack会被压入一个<video>标签。

       注释中提到,Mozilla定义的Fullscreen API没有Fullscreen Element Stack的概念。没有Fullscreen Element Stack,意味着网页的标签在任意情况下都可以设置为全屏模式。不过,非Mozilla定义的Fullscreen API是要求Fullscreen Element Stack的。Fullscreen Element St

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值