在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 Stack用来限制一个标签是否可以设置为全屏模式:

1. 当Stack为空时,任意标签均可设置为全屏模式。

2. 当Stack非空时,栈顶标签的子标签才可以设置为全屏模式。

FullscreenElementStack类的成员函数requestFullScreenForElement就是根据上述逻辑判断参数element描述的标签是否可以设置为全屏模式的。如果可以,那么就会更新与它相关的Stack,并且在最后调用在WebKit Glue层创建一个WebViewImpl对象的成员函数enterFullScreenForElement,用来通知WebKit Glue层有一个标签要进入全屏模式。这个WebViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文。WebKit Glue层的作用,可以参考前面Chromium网页加载过程简要介绍和学习计划一文。

接下来,我们就继续分析WebViewImpl类的成员函数enterFullScreenForElement的实现,如下所示:

void WebViewImpl::enterFullScreenForElement(WebCore::Element* element)
{m_fullscreenController->enterFullScreenForElement(element);
}

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。

WebViewImpl类的成员变量m_fullscreenController指向的是一个FullscreenController对象。WebViewImpl类的成员函数enterFullScreenForElement调用这个FullscreenController对象的成员函数enterFullScreenForElement,用来通知它将参数element描述的标签设置为全屏模式。

FullscreenController类的成员函数enterFullScreenForElement的实现如下所示:

void FullscreenController::enterFullScreenForElement(WebCore::Element* element)
{// We are already transitioning to fullscreen for a different element.if (m_provisionalFullScreenElement) {m_provisionalFullScreenElement = element;return;}// We are already in fullscreen mode.if (m_fullScreenFrame) {m_provisionalFullScreenElement = element;willEnterFullScreen();didEnterFullScreen();return;}// We need to transition to fullscreen mode.if (WebViewClient* client = m_webViewImpl->client()) {if (client->enterFullScreen())m_provisionalFullScreenElement = element;}
}

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FullscreenController.cpp中。

如果参数element描述的标签所在的网页已经有标签处于全屏模式,那么FullscreenController类的成员变量m_provisionalFullScreenElement就会指向该标签,并且FullscreenController类的另外一个成员变量m_fullScreenFrame会指向一个LocalFrame对象。这个LocalFrame对象描述的就是处于全屏模式的标签所在的网页。

我们假设参数element描述的标签所在的网页还没有标签被设置为全屏模式。这时候FullscreenController类的成员函数enterFullScreenForElement会调用成员变量m_webViewImpl指向的一个WebViewImpl对象的成员函数client获得一个WebViewClient接口,然后再调用这个WebViewClient接口的成员函数enterFullScreen,用来通知它进入全屏模式。

上述WebViewClient接口是由WebKit的使用者设置的。在我们这个情景中,WebKit的使用者即Render进程中的Content模块,它设置的WebViewClient接口指向的是一个RenderViewImpl对象。这个RenderViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文,它描述的是当前正在处理的网页所加载在的一个RenderView控件。这一步实际上是通知Chromium的Content层进入全屏模式。

FullscreenController类的成员函数enterFullScreenForElement通知了Content层进入全屏模式之后,会将引发Content层进入全屏模式的标签记录在成员变量m_provisionalFullScreenElement中。在我们这个情景中,这个标签即为一个<video>标签。

接下来我们继续分析Chromium的Content层进入全屏模式的过程,也就是RenderViewImpl类的成员函数enterFullScreen的实现,如下所示:

bool RenderViewImpl::enterFullScreen() {Send(new ViewHostMsg_ToggleFullscreen(routing_id_, true));return true;
}

这个函数定义在文件external/chromium_org/content/renderer/render_view_impl.cc。

RenderViewImpl类的成员函数enterFullScreen向Browser进程发送一个类型为ViewHostMsg_ToggleFullscreen的IPC消息,用来通知它将Routing ID为routing_id_的网页所加载在的Tab设置为全屏模式。

Browser进程通过RenderViewHostImpl类的成员函数OnMessageReceived接收类型为ViewHostMsg_ToggleFullscreen的IPC消息,如下所示:

bool RenderViewHostImpl::OnMessageReceived(const IPC::Message& msg) {......bool handled = true;IPC_BEGIN_MESSAGE_MAP(RenderViewHostImpl, msg)......IPC_MESSAGE_HANDLER(ViewHostMsg_ToggleFullscreen, OnToggleFullscreen)......IPC_MESSAGE_UNHANDLED(handled = RenderWidgetHostImpl::OnMessageReceived(msg))IPC_END_MESSAGE_MAP()return handled;
}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc中。

RenderViewHostImpl类的成员函数OnMessageReceived将类型为ViewHostMsg_ToggleFullscreen的IPC消息分发给另外一个成员函数OnToggleFullscreen处理,如下所示:

void RenderViewHostImpl::OnToggleFullscreen(bool enter_fullscreen) {DCHECK_CURRENTLY_ON(BrowserThread::UI);delegate_->ToggleFullscreenMode(enter_fullscreen);// We need to notify the contents that its fullscreen state has changed. This// is done as part of the resize message.WasResized();
}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc中。

RenderViewHostImpl类的成员函数OnToggleFullscreen首先通过成员变量delegate_描述的一个RenderViewHostDelegate委托接口将浏览器窗口设置为全屏模式,然后再调用另外一个成员函数WasResized通知Render进程,浏览器窗口大小已经发生了变化,也就是它进入了全屏模式。

RenderViewHostImpl类的成员变量delegate_描述的RenderViewHostDelegate委托接口指向的是一个WebContentsImpl对象。这个WebContentsImpl对象是Content层提供给外界的一个接口,用来描述当前正在加载的一个网页。外界通过这个接口就可以访问当前正在加载的网页。

RenderViewHostImpl类的成员函数WasResized是从父类RenderWidgetHostImpl继承下来的,它的实现如下所示:

void RenderWidgetHostImpl::WasResized() {......is_fullscreen_ = IsFullscreen();......ViewMsg_Resize_Params params;......params.is_fullscreen = is_fullscreen_;if (!Send(new ViewMsg_Resize(routing_id_, params))) {resize_ack_pending_ = false;} else {last_requested_size_ = new_size;}
}

这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。

RenderWidgetHostImpl类的成员函数WasResized主要是向Render进程发送一个类型为ViewMsg_Resize的IPC消息。这个ViewMsg_Resize的IPC消息。携带了一个is_fullscreen信息,用来告知Render进程当前正在处理的网页是否已经进入了全屏模式。

Render进程通过RenderWidget类的成员函数OnMessageReceived接收类型为ViewMsg_Resize的IPC消息,如下所示:

bool RenderWidget::OnMessageReceived(const IPC::Message& message) {bool handled = true;IPC_BEGIN_MESSAGE_MAP(RenderWidget, message)......IPC_MESSAGE_HANDLER(ViewMsg_Resize, OnResize)......IPC_MESSAGE_UNHANDLED(handled = false)IPC_END_MESSAGE_MAP()return handled;
}

这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

RenderWidget类的成员函数OnMessageReceived将类型为ViewMsg_Resize的IPC消息分发给另外一个成员函数OnResize处理,如下所示:

void RenderWidget::OnResize(const ViewMsg_Resize_Params& params) {......Resize(params.new_size, params.physical_backing_size,params.overdraw_bottom_height, params.visible_viewport_size,params.resizer_rect, params.is_fullscreen, SEND_RESIZE_ACK);......
}

这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

RenderWidget类的成员函数OnResize又调用另外一个成员函数Resize处理类型为ViewMsg_Resize的IPC消息,如下所示:

void RenderWidget::Resize(const gfx::Size& new_size,const gfx::Size& physical_backing_size,float overdraw_bottom_height,const gfx::Size& visible_viewport_size,const gfx::Rect& resizer_rect,bool is_fullscreen,ResizeAck resize_ack) {......if (compositor_) {compositor_->setViewportSize(new_size, physical_backing_size);......}......// NOTE: We may have entered fullscreen mode without changing our size.bool fullscreen_change = is_fullscreen_ != is_fullscreen;if (fullscreen_change)WillToggleFullscreen();is_fullscreen_ = is_fullscreen;if (size_ != new_size) {size_ = new_size;// When resizing, we want to wait to paint before ACK'ing the resize.  This// ensures that we only resize as fast as we can paint.  We only need to// send an ACK if we are resized to a non-empty rect.webwidget_->resize(new_size);} ......if (fullscreen_change)DidToggleFullscreen();......
}

这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

RenderWidget类的成员函数Resize一方面会通知网页的UI合成器,它负责渲染的网页的大小发生了变化,以便它修改网页UI的Viewport大小。这是通过调用成员变量compositor_指向的一个RenderWidgetCompositor对象的成员函数setViewportSize实现的。这个RenderWidgetCompositor对象的创建过程可以参考前面Chromium网页Layer Tree创建过程分析一文。

另一方面,RenderWidget类的成员函数Resize又会通知WebKit,它当前正在加载网页的大小发生了变化。这是通过调用成员变量webwidget_指向的一个WebViewImpl对象的成员函数resize实现的。这个WebViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文。

此外,RenderWidget类的成员函数Resize还会判断网页是否从全屏模式退出,或者进入全屏模式。如果是的话,那么就在通知WebKit修改网页的大小前后,RenderWidget类的成员函数Resize还会分别调用另外两个成员函数WillToggleFullscreen和DidToggleFullscreen,用来通知WebKit网页进入或者退出了全屏模式。

在我们这个情景中,网页是进入了全屏模式。接下来我们就先分析RenderWidget类的成员函数WillToggleFullscreen的实现,然后再分析另外一个成员函数DidToggleFullscreen的实现。

RenderWidget类的成员函数WillToggleFullscreen的实现如下所示:

void RenderWidget::WillToggleFullscreen() {......if (is_fullscreen_) {webwidget_->willExitFullScreen();} else {webwidget_->willEnterFullScreen();}
}

这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

从前面的分析可以知道,RenderWidget类的成员函数WillToggleFullscreen是在通知WebKit进入全屏模式之前被调用的。这时候RenderWidget类的成员变量is_fullscreen_的值为false。因此,接下来RenderWidget类的成员函数WillToggleFullscreen会调用成员变量webwidget_指向的一个WebViewImpl对象的成员函数willEnterFullScreen,用来通知WebKit它即将要进入全屏模式。

WebViewImpl类的成员函数willEnterFullScreen的实现如下所示:

void WebViewImpl::willEnterFullScreen()
{m_fullscreenController->willEnterFullScreen();
}

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。

WebViewImpl类的成员函数willEnterFullScreen调用成员变量m_fullscreenController指向的一个FullscreenController对象的成员函数willEnterFullScreen,用来通知WebKit即将要进入全屏模式,如下所示:

void FullscreenController::willEnterFullScreen()
{if (!m_provisionalFullScreenElement)return;// Ensure that this element's document is still attached.Document& doc = m_provisionalFullScreenElement->document();if (doc.frame()) {......m_fullScreenFrame = doc.frame();}m_provisionalFullScreenElement.clear();
}

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FullscreenController.cpp中。

从前面的分析可以知道,此时FullscreenController类的成员变量m_provisionalFullScreenElement的值不等于NULL,它指向了一个<video>标签。FullscreenController类的成员函数willEnterFullScreen找到这个<video>标签所在的Document,并且获得与这个Document关联的一个LocalFrame对象,保存在成员变量m_fullScreenFrame中,表示当前处于全屏模式的网页。

这一步执行完成后,回到前面分析的RenderWidget类的成员函数Resize,它在通知了WebKit修改当前正在加载的网页的大小之后,会调用另外一个成员函数DidToggleFullscreen,用来通知WebKit已经进入了全屏模式,如下所示:

void RenderWidget::DidToggleFullscreen() {......if (is_fullscreen_) {webwidget_->didEnterFullScreen();} else {webwidget_->didExitFullScreen();}
}

这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

从前面的分析可以知道,此时RenderWidget类的成员变量is_fullscreen_的值已经被设置为true。因此,RenderWidget类的成员函数DidToggleFullscreen接下来就会调用成员变量webwidget_指向的一个WebViewImpl对象的成员函数didEnterFullScreen,用来通知WebKit它已经进入全屏模式,如下所示:

void WebViewImpl::didEnterFullScreen()
{m_fullscreenController->didEnterFullScreen();
}

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。

WebViewImpl类的成员函数didEnterFullScreen调用成员变量m_fullscreenController指向的一个FullscreenController对象的成员函数didEnterFullScreen,用来通知WebKit已经进入全屏模式,如下所示:

void FullscreenController::didEnterFullScreen()
{if (!m_fullScreenFrame)return;if (Document* doc = m_fullScreenFrame->document()) {if (FullscreenElementStack::isFullScreen(*doc)) {......if (RuntimeEnabledFeatures::overlayFullscreenVideoEnabled()) {Element* element = FullscreenElementStack::currentFullScreenElementFrom(*doc);ASSERT(element);if (isHTMLMediaElement(*element)) {HTMLMediaElement* mediaElement = toHTMLMediaElement(element);if (mediaElement->webMediaPlayer() && mediaElement->webMediaPlayer()->canEnterFullscreen()// FIXME: There is no embedder-side handling in layout test mode.&& !isRunningLayoutTest()) {mediaElement->webMediaPlayer()->enterFullscreen();}.......}}}}
}

这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/FullscreenController.cpp中。

从前面的分析可以知道,FullscreenController类的成员变量m_fullScreenFrame的值不等于NULL。它指向了一个LocalFrame对象。这个LocalFrame对象描述的就是当前被设置为全屏模式的标签所在的网页。通过这个LocalFrame对象可以获得一个Document对象。

有了这个Document对象之后,FullscreenController类的成员函数didEnterFullScreen就会检查它的Fullscreen Element Stack栈顶标签。如果这是一个<video>标签,并且已经为这个<video>标签创建过MediaPlayer接口,以及这个MediaPlayer接口允许进入全屏模式,那么FullscreenController类的成员函数didEnterFullScreen就会调用这个MediaPlayer接口的成员函数enterFullScreen,让其进入全屏模式。

从前面Chromium为视频标签<video>创建播放器的过程分析一文可以知道,WebKit为<video>标签创建的MediaPlayer接口指向的是一个WebMediaPlayerAndroid对象。因此,FullscreenController类的成员函数didEnterFullScreen调用的是WebMediaPlayerAndroid类的成员函数enenterFullScreen让<video>标签的播放器进入全屏模式。

WebMediaPlayerAndroid类的成员函数enenterFullScreen的实现如下所示:

void WebMediaPlayerAndroid::enterFullscreen() {if (player_manager_->CanEnterFullscreen(frame_)) {player_manager_->EnterFullscreen(player_id_, frame_);......}
}

这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

从前面Chromium为视频标签<video>创建播放器的过程分析一文可以知道,WebMediaPlayerAndroid类的成员变量player_manager_指向的是一个RendererMediaPlayerManager对象。这个RendererMediaPlayerManager对象负责管理为当前正在处理的网页中的所有<video>标签创建的播放器实例。

WebMediaPlayerAndroid类的成员函数enenterFullScreen首先调用上述RendererMediaPlayerManager对象的成员函数CanEnterFullscreen检查与当前正在处理的播放器实例关联的<video>标签所在的网页是否已经进入了全屏模式。如果已经进入,那么就会继续调用RendererMediaPlayerManager对象的成员函数EnterFullscreen使得当前正在处理的播放器实例进入全屏模式。

从前面的分析可以知道,与当前正在处理的播放器实例关联的<video>标签所在的网页已经进入了全屏模式。因此,接下来RendererMediaPlayerManager类的成员函数EnterFullscreen就会被调用,如下所示:

void RendererMediaPlayerManager::EnterFullscreen(int player_id,blink::WebFrame* frame) {pending_fullscreen_frame_ = frame;Send(new MediaPlayerHostMsg_EnterFullscreen(routing_id(), player_id));
}

这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。

RendererMediaPlayerManager类的成员函数EnterFullscreen主要是向Browser进程发送一个类型为MediaPlayerHostMsg_EnterFullscreen的IPC消息,通知它将ID为player_id的播放器设置为全屏模式。

Browser进程通过MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived接收类型为MediaPlayerHostMsg_EnterFullscreen的IPC消息,如下所示:

bool MediaWebContentsObserver::OnMediaPlayerMessageReceived(const IPC::Message& msg,RenderFrameHost* render_frame_host) {bool handled = true;IPC_BEGIN_MESSAGE_MAP(MediaWebContentsObserver, msg)IPC_MESSAGE_FORWARD(MediaPlayerHostMsg_EnterFullscreen,GetMediaPlayerManager(render_frame_host),BrowserMediaPlayerManager::OnEnterFullscreen)......IPC_MESSAGE_UNHANDLED(handled = false)IPC_END_MESSAGE_MAP()return handled;
}

这个函数定义在文件external/chromium_org/content/browser/media/media_web_contents_observer.cc中。

MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived首先调用成员函数GetMediaPlayerManager获得一个BrowserMediaPlayerManager对象,然后调用这个BrowserMediaPlayerManager对象的成员函数OnEnterFullscreen处理类型为MediaPlayerHostMsg_EnterFullscreen的IPC消息,如下所示:

void BrowserMediaPlayerManager::OnEnterFullscreen(int player_id) {......if (video_view_.get()) {fullscreen_player_id_ = player_id;video_view_->OpenVideo();return;} else if (!ContentVideoView::GetInstance()) {......video_view_.reset(new ContentVideoView(this));base::android::ScopedJavaLocalRef<jobject> j_content_video_view =video_view_->GetJavaObject(base::android::AttachCurrentThread());if (!j_content_video_view.is_null()) {fullscreen_player_id_ = player_id;return;}}.....
}

这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。

当BrowserMediaPlayerManager类的成员变量video_view_的值不等于NULL时,它指向一个ContentVideoView对象。这个ContentVideoView对象描述的是一个全屏播放器窗口。

在Browser进程中,一个网页对应有一个BrowserMediaPlayerManager对象。同一个网页中的所有<video>标签在设置为全屏模式时,使用的是同一个全屏播放器窗口。不同的网页的<video>标签在设置为全屏模式时,使用不同的全屏播放器窗口。在同一时刻,Browser进程只能存在一个全屏播放器窗口。

BrowserMediaPlayerManager类的成员函数OnEnterFullscreen的执行逻辑如下所示:

1. 如果当前正在处理的BrowserMediaPlayerManager对象的成员变量video_view_的值不等于NULL,也就是它指向了一个ContentVideoView对象,那么就说明Browser进程已经为当前正在处理的BrowserMediaPlayerManager对象创建过全屏播放器窗口了。这时候就会将参数player_id的值保存在另外一个成员变量fullscreen_player_id_中,表示当前正在处理的BrowserMediaPlayerManager对象所管理的全屏播放器窗口正在被ID为player_id的播放器使用。同时,会调用上述ContentVideoView对象的成员函数OpenVideo,让其全屏播放当前被设置为全屏模式的<video>标签的视频。

2. 如果当前正在处理的BrowserMediaPlayerManager对象的成员变量video_view_的值NULL,并且Browser进程当前没有为其它网页的<video>标签全屏播放视频(这时候调用ContentVideoView类的静态成员函数GetInstance获得的返回值为NULL),那么就会为当前正在处理的BrowserMediaPlayerManager对象创建一个全屏播放窗口,也就是创建一个ContentVideoView对象(这时候调用ContentVideoView类的静态成员函数GetInstance将会返回该ContentVideoView对象),并且保存在其成员变量video_view_中。

C++层ContentVideoView对象在创建的过程中,又会在Java层创建一个对应的ContentVideoView对象。这个Java层ContentVideoView对象如果能成功创建,那么就可以通过调用其对应的C++层ContentVideoView对象的成员函数GetJavaObject获得。这时候说明Browser进程成功创建了一个全屏播放器窗口。这个全屏播放器窗口实际上就是一个SurfaceView对象。

一个SurfaceView对象刚创建出来的时候,它描述的窗口没有创建出来。Browser进程需要等它描述的窗口创建出来之后,才使用它全屏播放当前被设置为全屏模式的<video>标签的视频。因此,BrowserMediaPlayerManager类的成员函数OnEnterFullscreen在创建了一个SurfaceView对象之后,只做了一件简单的事情,就是记录当前是哪一个播放器进入了全屏模式,即将参数player_id的值保存在当前正在处理的BrowserMediaPlayerManager对象的成员变量fullscreen_player_id_中。

接下来,我们就继续分析全屏播放器窗口的创建过程,以及Browser进程使用它来全屏播放当前设置为全屏模式的<video>标签的视频的过程。

全屏播放器窗口是在C++层ContentVideoView对象的创建过程当中创建出来的,因此,我们就从C++层ContentVideoView类的构造函数开始,分析全屏播放器窗口的创建过程,如下所示:

ContentVideoView::ContentVideoView(BrowserMediaPlayerManager* manager): manager_(manager),weak_factory_(this) {DCHECK(!g_content_video_view);j_content_video_view_ = CreateJavaObject();g_content_video_view = this;......
}

这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。

C++层ContentVideoView类的构造函数会调用另外一个成员函数CreateJavaObject在Java层创建一个对应的ContentVideoView对象,并且保存在成员变量j_content_video_view_中。

与此同时,C++层ContentVideoView类的构造函数会将当前正在创建的ContentVideoView对象保存一个静态成员变量g_content_video_view_中,表示Browser进程当前已经存在一个全屏窗口。根据我们前面的分析,这样就会阻止其它网页全屏播放它们的<video>标签的视频。这个静态成员变量g_content_video_view_的值可以通过调用前面提到的C++层ContentVideoView类的静态成员函数GetInstance获得,如下所示:

ContentVideoView* ContentVideoView::GetInstance() {return g_content_video_view;
}

这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。

回到C++层的ContentVideoView类的构造函数中,我们主要关注Java层的ContentVideoView对象的创建过程,因此接下来我们继续分析C++层的ContentVideoView类的成员函数CreateJavaObject的实现,如下所示:

JavaObjectWeakGlobalRef ContentVideoView::CreateJavaObject() {ContentViewCoreImpl* content_view_core = manager_->GetContentViewCore();JNIEnv* env = AttachCurrentThread();bool legacyMode = CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableOverlayFullscreenVideoSubtitle);return JavaObjectWeakGlobalRef(env,Java_ContentVideoView_createContentVideoView(env,content_view_core->GetContext().obj(),reinterpret_cast<intptr_t>(this),content_view_core->GetContentVideoViewClient().obj(),legacyMode).obj());
}

这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。

C++层的ContentVideoView类的成员函数CreateJavaObject主要是通过JNI接口Java_ContentVideoView_createContentVideoView调用Java层的ContentVideoView类的静态成员函数createContentVideoView创建一个Java层的ContentVideoView对象,如下所示:

public class ContentVideoView extends FrameLayoutimplements SurfaceHolder.Callback, ViewAndroidDelegate {......@CalledByNativeprivate static ContentVideoView createContentVideoView(Context context, long nativeContentVideoView, ContentVideoViewClient client,boolean legacy) {......ContentVideoView videoView = null;if (legacy) {videoView = new ContentVideoViewLegacy(context, nativeContentVideoView, client);} else {videoView = new ContentVideoView(context, nativeContentVideoView, client);}if (videoView.getContentVideoViewClient().onShowCustomView(videoView)) {return videoView;}return null;}......
}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。

从前面的调用过程可以知道,当Browser进程设置了kDisableOverlayFullscreenVideoSubtitle(disable-overlay-fullscreen-video-subtitle)启动选项时,参数legacy的值就会等于true时,表示禁止在全屏播放器窗口上面显示播放控制控件以及字幕。这时候全屏播放器窗口通过一个ContentVideoViewLegacy对象描述。另一方面,如果参数legacy的值等于false,那么全屏播放器窗口将会通过一个ContentVideoView对象描述。

一个ContentVideoViewLegacy对象或者一个ContentVideoView对象描述的全屏播放器窗口实际上是一个SurfaceView。这个SurfaceView只有添加到浏览器窗口之后,才能显示在屏幕中。为了将该全屏播放器窗口显示出来,ContentVideoView类的静态成员函数createContentVideoView将会通过调用前面创建的ContentVideoViewLegacy对象或者ContentVideoView对象的成员函数getContentVideoViewClient获得一个ContentVideoViewClient接口。有了这个ContentVideoViewClient接口之后,就可以调用它的成员函数onShowCustomView将刚才创建出来的全屏播放器窗口显示出来了。

我们假设参数legacy的值等于false。这意味着全屏播放器窗口是通过一个ContentVideoView对象描述的。接下来我们就继续分析这个ContentVideoView对象的创建过程,即ContentVideoView类的构造函数的实现,以便了解它内部的SurfaceView的创建过程,如下所示:

public class ContentVideoView extends FrameLayoutimplements SurfaceHolder.Callback, ViewAndroidDelegate {......// This view will contain the video.private VideoSurfaceView mVideoSurfaceView;......protected ContentVideoView(Context context, long nativeContentVideoView,ContentVideoViewClient client) {super(context);......mVideoSurfaceView = new VideoSurfaceView(context);showContentVideoView();......}......
}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。

ContentVideoView类的构造函数会创建一个VideoSurfaceView对象,并且保存在成员变量mVideoSurfaceView中。

VideoSurfaceView类是从SurfaceView类继承下来的,如下所示:

public class ContentVideoView extends FrameLayoutimplements SurfaceHolder.Callback, ViewAndroidDelegate {......private class VideoSurfaceView extends SurfaceView {......}......
}

这个类定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。

ContentVideoView类的构造函数在创建了一个VideoSurfaceView对象,接下来会调用另外一个成员函数showContentVideoView将它作为当前正在创建的ContentVideoView对象的子View,如下所示:

public class ContentVideoView extends FrameLayoutimplements SurfaceHolder.Callback, ViewAndroidDelegate {......protected void showContentVideoView() {mVideoSurfaceView.getHolder().addCallback(this);this.addView(mVideoSurfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT,Gravity.CENTER));......}......
}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。

ContentVideoView类的成员函数showContentVideoView将前面创建的VideoSurfaceView对象作为当前正在创建的ContentVideoView对象的子View之外,还会使用后者监听前者的surfaceCreated事件,也就是它描述的窗口创建完成事件。

当前正在创建的ContentVideoView对象实现了SurfaceHolder.Callback接口,一旦前面创建的VideoSurfaceView对象发出surfaceCreated事件通知,那么前者的成员函数surfaceCreated就会被调用,如下所示:

public class ContentVideoView extends FrameLayoutimplements SurfaceHolder.Callback, ViewAndroidDelegate {......@Overridepublic void surfaceCreated(SurfaceHolder holder) {mSurfaceHolder = holder;openVideo();}......
}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。

ContentVideoView类的成员函数surfaceCreated会将参数holder描述的一个SurfaceHolder对象保存在成员变量mSurfaceHolder中。后面可以通过这个SurfaceHolder对象获得前面创建的VideoSurfaceView对象内部的一个Surface。这个Surface将会用来接收MediaPlayer的解码输出。

接下来,ContentVideoView类的成员函数surfaceCreated就会调用另外一个成员函数openVideo将上述Surface设置为MediaPlayer的解码输出,如下所示:

public class ContentVideoView extends FrameLayoutimplements SurfaceHolder.Callback, ViewAndroidDelegate {......@CalledByNativeprotected void openVideo() {if (mSurfaceHolder != null) {mCurrentState = STATE_IDLE;if (mNativeContentVideoView != 0) {nativeRequestMediaMetadata(mNativeContentVideoView);nativeSetSurface(mNativeContentVideoView,mSurfaceHolder.getSurface());}}}......
}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。

ContentVideoView类的成员变量mNativeContentVideoView描述的是前面在C++层创建的一个ContentVideoView对象。ContentVideoView类的成员函数openVideo主要是做两件事情:

1. 获取要播放的视频的元数据,以便用来初始化全屏播放器窗口。

2. 将前面创建的VideoSurfaceView对象内部维护的Surface设置为MediaPlayer的解码输出。

这两件事情分别是通过调用成员函数nativeRequestMediaMetadata和nativeSetSurface完成的。当它们完成之后,<video>标签的视频可以开始全屏播放了。接下来我们继续分析它们的实现。

ContentVideoView类的成员函数nativeRequestMediaMetadata是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_content_browser_ContentVideoView_nativeRequestMediaMetadata实现,如下所示:

voidJava_com_android_org_chromium_content_browser_ContentVideoView_nativeRequestMediaMetadata(JNIEnv*env,jobject jcaller,jlong nativeContentVideoView) {ContentVideoView* native =reinterpret_cast<ContentVideoView*>(nativeContentVideoView);CHECK_NATIVE_PTR(env, jcaller, native, "RequestMediaMetadata");return native->RequestMediaMetadata(env, jcaller);
}

这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentVideoView_jni.h中。

从前面的调用过程可以知道,参数nativeContentVideoView描述的是一个C++层ContentVideoView对象,函数Java_com_android_org_chromium_content_browser_ContentVideoView_nativeRequestMediaMetadata调用它的成员函数RequestMediaMetadata获取要播放的视频的元数据,如下所示:

void ContentVideoView::RequestMediaMetadata(JNIEnv* env, jobject obj) {base::MessageLoop::current()->PostTask(FROM_HERE,base::Bind(&ContentVideoView::UpdateMediaMetadata,weak_factory_.GetWeakPtr()));
}

这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。

ContentVideoView类的成员函数RequestMediaMetadata向当前线程的消息队列发送一个Task。这个Task绑定了ContentVideoView类的另外一个成员函数UpdateMediaMetadata。这意味着接下来ContentVideoView类的成员函数UpdateMediaMetadata会在当前线程中调用,用来获取要播放的视频的元数据。

ContentVideoView类的成员函数UpdateMediaMetadata的实现如下所示:

void ContentVideoView::UpdateMediaMetadata() {JNIEnv* env = AttachCurrentThread();ScopedJavaLocalRef<jobject> content_video_view = GetJavaObject(env);if (content_video_view.is_null())return;media::MediaPlayerAndroid* player = manager_->GetFullscreenPlayer();if (player && player->IsPlayerReady()) {Java_ContentVideoView_onUpdateMediaMetadata(env, content_video_view.obj(), player->GetVideoWidth(),player->GetVideoHeight(),static_cast<int>(player->GetDuration().InMilliseconds()),player->CanPause(),player->CanSeekForward(), player->CanSeekBackward());}
}

这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。

ContentVideoView类的成员函数UpdateMediaMetadata首先调用另外一个成员函数GetJavaObject获得与当前正在处理的C++层ContentVideoView对象对应的Java层ContentVideoView对象。

ContentVideoView类的成员函数UpdateMediaMetadata接下来又通过调用成员变量manager_指向的一个BrowserMediaPlayerManager对象的成员函数GetFullscreenPlayer获得当前处于全屏模式的播放器,如下所示:

MediaPlayerAndroid* BrowserMediaPlayerManager::GetFullscreenPlayer() {return GetPlayer(fullscreen_player_id_);
}

这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。

从前面的分析可以知道,BrowserMediaPlayerManager类的成员变量fullscreen_player_id_记录了当前处于全屏模式的播放器的ID。有了这个ID之后,就可以调用另外一个成员函数GetPlayer获得一个对应的MediaPlayerBridge对象。这个MediaPlayerBridge对象描述的就是当前处于全屏模式的播放器。

回到ContentVideoView类的成员函数UpdateMediaMetadata中,它获得的全屏播放器之后,实际上就是<video>标签进入全屏模式之前所使用的那个播放器。这意味着<video>标签在全屏和非全屏模式下,使用的是同一个播放器,区别只在于播放器在两种模式下使用的UI不一样。这个播放器之前已经获得了要播放的视频的元数据,并且保存在了内部,因此这里就不需要从网络上重新获取。

有了要播放的视频的元数据之后,ContentVideoView类的成员函数UpdateMediaMetadata就通过JNI接口Java_ContentVideoView_onUpdateMediaMetadata调用前面获得的Java层ContentVideoView对象的成员函数onUpdateMediaMetadata,让其初始化全屏播放器窗口,如下所示:

public class ContentVideoView extends FrameLayoutimplements SurfaceHolder.Callback, ViewAndroidDelegate {......@CalledByNativeprotected void onUpdateMediaMetadata(int videoWidth,int videoHeight,int duration,boolean canPause,boolean canSeekBack,boolean canSeekForward) {mDuration = duration;.....onVideoSizeChanged(videoWidth, videoHeight);}......
}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。

参数duration描述的就是要播放的视频的总时长,它会被记录在ContentVideoView类的成员变量mDuration中。

另外两个参数videoWidth和videoHeight描述的是要播放的视频的宽和高,ContentVideoView类的成员函数onUpdateMediaMetadata将它们传递给另外一个成员函数onVideoSizeChanged,用来初始化全屏播放器窗口,如下所示:

public class ContentVideoView extends FrameLayoutimplements SurfaceHolder.Callback, ViewAndroidDelegate {......@CalledByNativeprivate void onVideoSizeChanged(int width, int height) {mVideoWidth = width;mVideoHeight = height;// This will trigger the SurfaceView.onMeasure() call.mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);}......
}

这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。

ContentVideoView类的成员函数onVideoSizeChanged将视频的宽度和高度分别记录在成员变量mVideoWidth和mVideoHeight中,然后再将它们设置为用来显示视频画面的VideoSurfaceView的大小。

这一步执行完成后,全屏播放器窗口就初始化完毕。回到前面分析的ContentVideoView类的成员函数openVideo中,它接下来将上述VideoSurfaceView内部使用的Surface设置为MediaPlayer的解码输出。这是通过调用ContentVideoView类的成员函数nativeSetSurface实现的。

ContentVideoView类的成员函数nativeSetSurface是一个JNI方法,它由C++层的函数Java_com_android_org_chromium_content_browser_ContentVideoView_nativeSetSurface实现,如下所示:

voidJava_com_android_org_chromium_content_browser_ContentVideoView_nativeSetSurface(JNIEnv*env,jobject jcaller,jlong nativeContentVideoView,jobject surface) {ContentVideoView* native =reinterpret_cast<ContentVideoView*>(nativeContentVideoView);CHECK_NATIVE_PTR(env, jcaller, native, "SetSurface");return native->SetSurface(env, jcaller, surface);
}

这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/content/jni/ContentVideoView_jni.h中。

参数nativeContentVideoView描述的是一个C++层ContentVideoView对象,另外一个参数surface描述的就是前面提到的用来显示视频画面的VideoSurfaceView内部使用的Surface。

函数Java_com_android_org_chromium_content_browser_ContentVideoView_nativeSetSurface参数nativeContentVideoView描述的C++层ContentVideoView对象的成员函数SetSurface重新给当前处于全屏模式的播放器设置一个Surface,如下所示:

void ContentVideoView::SetSurface(JNIEnv* env, jobject obj,jobject surface) {manager_->SetVideoSurface(gfx::ScopedJavaSurface::AcquireExternalSurface(surface));
}

这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。

ContentVideoView类的成员函数SetSurface调用成员变量manager_指向的一个BrowserMediaPlayerManager对象的成员函数SetVideoSurface重新给当前处于全屏模式的播放器设置一个Surface,如下所示:

void BrowserMediaPlayerManager::SetVideoSurface(gfx::ScopedJavaSurface surface) {MediaPlayerAndroid* player = GetFullscreenPlayer();......player->SetVideoSurface(surface.Pass());......
}

这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。

BrowserMediaPlayerManager类的成员函数SetVideoSurface首先调用另外一个成员函数GetFullscreenPlayer获得一个MediaPlayerBridge对象。这个MediaPlayerBridge对象被一个MediaPlayerAndroid指针引用(MediaPlayerAndroid是MediaPlayerBridge的父类),它描述的便是当前处于全屏模式的播放器。

获得了当前处于全屏模式的播放器之后,就可以调用它的成员函数SetVideoSurface将参数surface描述的一个Surface设置为它的解码输出。这个设置过程,也就是MediaPlayerBridge类的成员函数SetVideoSurface的实现,可以参考前面Chromium为视频标签<video>渲染视频画面的过程分析一文。它实际上就是给在Java层创建的一个MediaPlayer重新设置一个Surface作为解码输出。这个Surface是由一个VideoSurfaceView提供的。这个VideoSurfaceView实际上就是一个SurfaceView。SurfaceView会自己将MediaPlayer的解码输出交给系统渲染。因此就不再需要Chromium参与这个渲染过程。

回忆<video>标签在进入全屏模式之前,Chromium会为它会创建一个纹理,然后用这个纹理创建一个SurfaceTexture。这个SurfaceTexture最终又会封装在一个Surface中。这个Surface就设置为MediaPlayer的解码输出。这时候MediaPlayer的解码输出需要由Chromium来处理,也就是渲染在浏览器窗口中。当浏览器窗口被系统渲染在屏幕上时,我们就可以看到MediaPlayer输出的视频画面了。这个过程可以参考前面Chromium为视频标签<video>渲染视频画面的过程分析一文。

这样,我们就分析完成<video>标签全屏播放视频的过程了。从这个分析过程我们就可以知道,<video>标签在全屏模式和非全屏模式下使用的都是相同的MediaPlayer,区别只在于这个MediaPlayer将视频画面渲染在不同的Surface之上。因此,<video>标签可以在全屏模式和非全屏模式之间进行无缝的播放切换。

至此,我们也分析完成了video标签在Chromium中的实现。视频在互联网中将扮演着越来越重要的角色。以前浏览器主要是通过Flash插件来支持视频播放。Flash插件有着臭名昭著的安全问题和Crash问题。因此,随着HTML5的出现,浏览器逐步转向使用<video>标签来支持视频播放。这不仅在产品上带来更好的体验(无需用户安装插件),而且在技术上也更加稳定。基于上述理由,理解<video>标签的实现原理就显得尤为重要。重新学习可以参考前面Chromium视频标签<video>简要介绍和学习计划一文。更多信息,可以关注老罗的新浪微博:http://weibo.com/shengyangluo。

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

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

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 在Chr ...

  2. 微信内置浏览器video标签自动全屏播放以及层级过高问题

    转载自:微信内置浏览器video标签自动全屏播放以及层级过高问题 - 程序员大本营 今天事用html5的<video>标签做微信内置浏览器视频播放页面时,需要在视频层级上显示类似弹幕的对话 ...

  3. 设置网页打开默认全屏_微信公众号里的视频不能进行全屏播放的解决方法

    我们手机版的微信视频,发现视频播放只能竖屏播放,点击右下角对放大按钮也是如此,网页版也是小窗口播放,而视频这样显示特别影响我们的观看感受, 下面说下微信公众号里的视频不能进行全屏播放的解决方法. 1. ...

  4. Html5原生video标签禁止全屏播放的实现

    制作移动端H5,需要添加视频,点击播放的时候会自动全屏播放,记录一下处理局部播放的问题. <video id="pageVideo"x5-playsinline=" ...

  5. Chromium为视频标签video创建播放器的过程分析

    Chromium是通过WebKit解析网页内容的.当WebKit遇到<video>标签时,就会创建一个播放器实例.WebKit是平台无关的,而播放器实现是平台相关的.因此,WebKit并没 ...

  6. h5 video全屏播放

    相关链接: H5 video 的使用 H5 video开发问题及相关知识点 由于全屏播放在移动web上的兼容问题,尤其是在iOS上,让全屏本应该比较简单的操作,根据业务的需求,有时候会有些复杂.这里查 ...

  7. Video 全屏播放、禁止拖动进度条、禁止下载

    ​ 全屏播放 ios:默认全屏播放模式,不做处理: 安卓:默认小屏播放模式,特殊处理,以下是实现代码: /** * @description 安卓全屏播放模式 * @video DOM节点 */ fu ...

  8. Android WebView播放视频(包括全屏播放)

    最近项目开发中用到了WebView播放视频的功能,总结了开发中犯过的错误,这些错误在开发是及容易遇到的,所以我这里总结了一下,希望大家看到后不要再犯类似的错误,尽可能提高开发效率: 这个Demo我这里 ...

  9. 3d 视频切换到全屏播放

    项目:3d场景中播放一个小窗口的视频,点击视频,放大为屏幕上的全屏视频.再点击视频,缩小为原来的小窗口的视频继续播放视频. 1.Unity3D 中,在三维场景中呈现播放视频,将视频组件放到相应的Pla ...

最新文章

  1. OpenResty 最佳实践
  2. 专题 12 IPC之消息队列
  3. USACO2.4のP1522-牛的旅行(Cow Tours)【最短路Flody】
  4. swagger生成示例_生成器设计模式示例
  5. React开发(208):react代码分割在嵌套组件中更新 Context
  6. 工作199:获取接口token
  7. 全参考客观视频质量评价方法 (MSE, PSNR,SSIM)原理
  8. VB.NET 按键代码 及组合键
  9. 2010.2--ip redirects 和 ip directed-broadcast含义
  10. 魔兽世界mysql启动不了_WOW 魔兽世界单机版 3.3 不能启动服务器的解决方案 | 学步园...
  11. 近五年计算机考研国家线、自划线汇总!
  12. Oracle Wallet Manager
  13. 不义联盟网站无法连接服务器,不义联盟2总是显示无法连接网络
  14. Android自动接听来电并录音
  15. python学习__tsv文件写入多余空行问题
  16. Android canvas
  17. 执行董事和CEO有什么区别
  18. 24岁博士毕业、47岁当选院士的他,正式担任上海交通大学校长!
  19. Android 报错Failed to load native library: XXXX_so
  20. Stata:图示连续变量的连续边际效应

热门文章

  1. etal斜体吗 参考文献_期刊论文参考文献着录注意问题
  2. 深剖VR,AR和MR三者之间关系
  3. k线符号图解大全_股市k线图各种符号意义?k线符号图解大全!
  4. 计算机内存清理器,轻量便捷的内存清理工具PC版
  5. 吴恩达机器学习笔记(五)正则化Regularization
  6. 戴尔联手九大云计算伙伴成立云联盟:通吃混合云
  7. 全球及中国TNFα抑制剂行业竞争调查分析及项目可行性研究报告2021-2027年
  8. 3D-LaneNet:端到端三维多车道检测ICCV2019
  9. 穿透多层代理获得真实ip
  10. xx-xx-xx-xx转换成x年x月x日星期x