帮助
|
留言交流
|
首 页
阅览室
馆友
我的图书馆
来自:
网络学习天空
>
馆藏分类
配色:
字号:
大
中
小
Chromium为视频标签全屏播放的过程分析
2016-10-22 | 阅:
转:
|
分享
Chromium为视频标签
全屏播放的过程分析
在Chromium中,
标签有全屏和非全屏两种播放模式。在非全屏模式下,
标签播放的视频嵌入在网页中显示,也就是视频画面作为网页的一部分显示。在全屏模式下,我们是看不到网页其它内容的,因此
标签播放的视频可以在一个独立的全屏窗口中显示。这两种截然不同的播放模式,导致Chromium使用不同的方式渲染视频画面。本文接下来就详细分析
标签全屏播放的过程。
从前面文章中一文可以知道,在Android平台上,
标签指定的视频是通过系统提供的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的更多知识,可以参考前面文章中一文。
Surface描述的GPU缓冲区队列,是一个生产者/消息者模型。在我们这个情景中,生产者便是MediaPlayer。如果Surface是通过SurfaceTexture构造的,那么SurfaceTexture的所有者,也就是Chromium,就是消费者。消费者有责任将视频画面从GPU缓冲区队列中提取出来,并且进行渲染。渲染完成后,再交给SurfaceFlinger合成显示在屏幕中。如果Surface是从SurfaceView内部获取的,那么SurfaceView就是消费者,然后再交给SurfaceFlinger合成显示在屏幕中。
简单来说,在非全屏模式下,
标签的视频画面经过MediaPlayer->Chromium->SurfaceFlinger显示在屏幕中,而在全屏模式下,经过MediaPlayer->SurfaceView->SurfaceFlinger显示在屏幕中。
Chromium支持
标签在全屏和非全屏模式之间无缝切换,也就是从一个模式切换到另外一个模式的时候,不需要重新创建MediaPlayer,只需要给原先使用的MediaPlayer设置一个新的Surface即可。图1描述的是
标签从非全屏模式切换为全屏模式的示意图,如下所示:
当
标签从非全屏模式切换为全屏模式时,Chromium会为它创建一个全屏的SurfaceView,并且将这个SurfaceView内部的Surface设置给MediaPlayer。以后MediaPlayer就不会再将解码出来的视频画面通过原先设置的SurfaceTexture交给Chromium处理,而是通过后面设置的Surface交给SurfaceView处理。
接下来,我们就结合源代码,从
标签进入全屏模式开始,分析
标签全屏播放视频的过程。从前面文章中一文可以知道,在WebKit中,
标签是通过HTMLMediaElement类描述的。当
标签进入全屏模式时,HTMLMediaElement类的成员函数enterFullscreen就会被调用,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidHTMLMediaElement::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获得当前正在处理的
标签所属的网页,然后再通过调用FullscreenElementStack类的静态成员函数from获得这个网页所对应的FullscreenElementStack对象。有了这个FullscreenElementStack对象之后,就可以调用它的成员函数requestFullScreenForElement请求将当前正在处理的
标签设置为全屏模式。
FullscreenElementStack类的成员函数requestFullScreenForElement的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidFullscreenElementStack::requestFullScreenForElement(Elementelement,unsignedshortflags,FullScreenCheckTypecheckType)
{
......
//TheMozillaFullScreenAPI
hasdifferentrequirements
//forfullscreenmode,anddonothavetheconceptofafullscreenelementstack.
boolinLegacyMozillaMode=(flags&Element::LEGACY_MOZILLA_REQUEST);
do{
......
//1.Ifanyofthefollowingconditionsaretrue,terminatethesestepsandqueueatasktofire
//aneventnamedfullscreenerrorwithitsbubblesattributesettotrueonthecontextobject''s
//nodedocument:
......
//Thecontextobject''snodedocumentfullscreenelementstackisnotemptyanditstopelement
//isnotanancestorofthecontextobject.(NOTE:Ignorethisrequirementiftherequestwas
//madeviathelegacyMozilla-styleAPI.)
if(!m_fullScreenElementStack.isEmpty()&&!inLegacyMozillaMode){
ElementlastElementOnStack=m_fullScreenElementStack.last().get();
if(lastElementOnStack==element||!lastElementOnStack->contains(element))
break;
}
//Adescendantbrowsingcontext''sdocumenthasanon-emptyfullscreenelementstack.
booldescendentHasNonEmptyStack=false;
for(Framedescendant=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.Letdocbeelement''snodedocument.(i.e."this")
DocumentcurrentDoc=document();
//3.Letdocsbealldoc''sancestorbrowsingcontext''sdocuments(ifany)anddoc.
Deque
docs;
do{
docs.prepend(currentDoc);
currentDoc=currentDoc->ownerElement()?¤tDoc->ownerElement()->document():0;
}while(currentDoc);
//4.Foreachdocumentindocs,runthesesubsteps:
Deque
::iteratorcurrent=docs.begin(),following=docs.begin();
do{
++following;
//1.Letfollowingdocumentbethedocumentafterdocumentindocs,ornullifthereisno
//suchdocument.
DocumentcurrentDoc=current;
DocumentfollowingDoc=following!=docs.end()?following:0;
//2.Iffollowingdocumentisnull,pushcontextobjectondocument''sfullscreenelement
//stack,andqueueatasktofireaneventnamedfullscreenchangewithitsbubblesattribute
//settotrueonthedocument.
if(!followingDoc){
from(currentDoc).pushFullscreenElementStack(element);
addDocumentToFullScreenChangeEventQueue(currentDoc);
continue;
}
//3.Otherwise,ifdocument''sfullscreenelementstackiseitheremptyoritstopelement
//isnotfollowingdocument''sbrowsingcontextcontainer,
ElementtopElement=fullscreenElementFrom(currentDoc);
if(!topElement||topElement!=followingDoc->ownerElement()){
//...pushfollowingdocument''sbrowsingcontextcontainerondocument''sfullscreenelement
//stack,andqueueatasktofireaneventnamedfullscreenchangewithitsbubblesattribute
//settotrueondocument.
from(currentDoc).pushFullscreenElementStack(followingDoc->ownerElement());
addDocumentToFullScreenChangeEventQueue(currentDoc);
continue;
}
//4.Otherwise,donothingforthisdocument.Itstaysthesame.
}while(++current!=docs.end());
//5.Return,andruntheremainingstepsasynchronously.
//6.Optionally,performsomeanimation.
......
document()->frameHost()->chrome().client().enterFullScreenForElement(element);
//7.Optionally,displayamessageindicatinghowtheusercanexitdisplayingthecontextobjectfullscreen.
return;
}while(0);
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/FullscreenElementStack.cpp中。
FullscreenElementStack类的成员函数requestFullScreenForElement主要是用来为网页中的每一个Document建立一个Stack。这个Stack记录了Document中所有请求设置为全屏模式的标签。我们通过图2所示的例子说明FullscreenElementStack类的成员函数requestFullScreenForElement的实现:
图2所示的网页包含了两个Document:Doc1和Doc2。其中,Doc1通过
标签加载了Doc2,后者里面有一个
标签。当Doc2里面的
标签被设置为全屏模式时,Doc1的Stack会被压入一个
标签和一个
标签,其中,
标签代表的是Doc1,Doc2的
Stack会被压入一个
标签。
注释中提到,Mozilla定义的FullscreenAPI没有FullscreenElementStack的概念。没有FullscreenElementStack,意味着网页的
标签在任意情况下都可以设置为全屏模式。不过,非Mozilla定义的FullscreenAPI是要求FullscreenElementStack的。FullscreenElement
Stack用来限制一个标签是否可以设置为全屏模式:
1.当Stack为空时,任意标签均可设置为全屏模式。
2.当Stack非空时,栈顶标签的子标签才可以设置为全屏模式。
FullscreenElementStack类的成员函数requestFullScreenForElement就是根据上述逻辑判断参数element描述的标签是否可以设置为全
屏模式的。如果可以,那么就会更新与它相关的Stack,并且在最后调用在WebKitGlue层创建一个WebViewImpl对象的成员函数
enterFullScreenForElement,用来通知WebKitGlue层有一个标签要进入全屏模式。这个WebViewImpl对象的创建过程可以参考前面Chromium网
页FrameTree创建过程分析一文。WebKitGlue层的作用,可以参考前面文章中一文。
接下来,我们就继续分析WebViewImpl类的成员函数enterFullScreenForElement的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidWebViewImpl::enterFullScreenForElement(WebCore::Elementelement)
{
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的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidFullscreenController::enterFullScreenForElement(WebCore::Elementelement)
{
//Wearealreadytransitioningtofullscreenforadifferentelement.
if(m_provisionalFullScreenElement){
m_provisionalFullScreenElement=element;
return;
}
//Wearealreadyinfullscreenmode.
if(m_fullScreenFrame){
m_provisionalFullScreenElement=element;
willEnterFullScreen();
didEnterFullScreen();
return;
}
//Weneedtotransitiontofullscreenmode.
if(WebViewClientclient=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网页FrameTree创建过程分
析一文,它描述的是当前正在处理的网页所加载在的一个RenderView控件。这一步实际上是通知Chromium的Content层进入全屏模式。
FullscreenController类的成员函数enterFullScreenForElement通知了Content层进入全屏模式之后,会将引发Content层进入全屏模
式的标签记录在成员变量m_provisionalFullScreenElement中。在我们这个情景中,这个标签即为一个
标签。
接下来我们继续分析Chromium的Content层进入全屏模式的过程,也就是RenderViewImpl类的成员函数enterFullScreen的实现,如下所
示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderViewImpl::enterFullScreen(){
Send(newViewHostMsg_ToggleFullscreen(routing_id_,true));
returntrue;
}
这个函数定义在文件external/chromium_org/content/renderer/render_view_impl.cc。
RenderViewImpl类的成员函数enterFullScreen向Browser进程发送一个类型为ViewHostMsg_ToggleFullscreen的IPC消息,用来通知它
将RoutingID为routing_id_的网页所加载在的Tab设置为全屏模式。
Browser进程通过RenderViewHostImpl类的成员函数OnMessageReceived接收类型为ViewHostMsg_ToggleFullscreen的IPC消息,如下所
示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderViewHostImpl::OnMessageReceived(constIPC::Message&msg){
......
boolhandled=true;
IPC_BEGIN_MESSAGE_MAP(RenderViewHostImpl,msg)
......
IPC_MESSAGE_HANDLER(ViewHostMsg_ToggleFullscreen,OnToggleFullscreen)
......
IPC_MESSAGE_UNHANDLED(
handled=RenderWidgetHostImpl::OnMessageReceived(msg))
IPC_END_MESSAGE_MAP()
returnhandled;
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc中。
RenderViewHostImpl类的成员函数OnMessageReceived将类型为ViewHostMsg_ToggleFullscreen的IPC消息分发给另外一个成员函数
OnToggleFullscreen处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRenderViewHostImpl::OnToggleFullscreen(boolenter_fullscreen){
DCHECK_CURRENTLY_ON(BrowserThread::UI);
delegate_->ToggleFullscreenMode(enter_fullscreen);
//Weneedtonotifythecontentsthatitsfullscreenstatehaschanged.This
//isdoneaspartoftheresizemessage.
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继承下来的,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRenderWidgetHostImpl::WasResized(){
......
is_fullscreen_=IsFullscreen();
......
ViewMsg_Resize_Paramsparams;
......
params.is_fullscreen=is_fullscreen_;
if(!Send(newViewMsg_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消息,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRenderWidget::OnMessageReceived(constIPC::Message&message){
boolhandled=true;
IPC_BEGIN_MESSAGE_MAP(RenderWidget,message)
......
IPC_MESSAGE_HANDLER(ViewMsg_Resize,OnResize)
......
IPC_MESSAGE_UNHANDLED(handled=false)
IPC_END_MESSAGE_MAP()
returnhandled;
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
RenderWidget类的成员函数OnMessageReceived将类型为ViewMsg_Resize的IPC消息分发给另外一个成员函数OnResize处理,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRenderWidget::OnResize(constViewMsg_Resize_Params¶ms){
......
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消息,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRenderWidget::Resize(constgfx::Size&new_size,
constgfx::Size&physical_backing_size,
floatoverdraw_bottom_height,
constgfx::Size&visible_viewport_size,
constgfx::Rect&resizer_rect,
boolis_fullscreen,
ResizeAckresize_ack){
......
if(compositor_){
compositor_->setViewportSize(new_size,physical_backing_size);
......
}
......
//NOTE:Wemayhaveenteredfullscreenmodewithoutchangingoursize.
boolfullscreen_change=is_fullscreen_!=is_fullscreen;
if(fullscreen_change)
WillToggleFullscreen();
is_fullscreen_=is_fullscreen;
if(size_!=new_size){
size_=new_size;
//Whenresizing,wewanttowaittopaintbeforeACK''ingtheresize.This
//ensuresthatweonlyresizeasfastaswecanpaint.Weonlyneedto
//sendanACKifweareresizedtoanon-emptyrect.
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对象的创建过程可以参考前面文章中一文。
另一方面,RenderWidget类的成员函数Resize又会通知WebKit,它当前正在加载网页的大小发生了变化。这是通过调用成员变量
webwidget_指向的一个WebViewImpl对象的成员函数resize实现的。这个WebViewImpl对象的创建过程可以参考前面Chromium网页FrameTree创
建过程分析一文。
此外,RenderWidget类的成员函数Resize还会判断网页是否从全屏模式退出,或者进入全屏模式。如果是的话,那么就在通知WebKit修
改网页的大小前后,RenderWidget类的成员函数Resize还会分别调用另外两个成员函数WillToggleFullscreen和DidToggleFullscreen,用来通
知WebKit网页进入或者退出了全屏模式。
在我们这个情景中,网页是进入了全屏模式。接下来我们就先分析RenderWidget类的成员函数WillToggleFullscreen的实现,然后再分
析另外一个成员函数DidToggleFullscreen的实现。
RenderWidget类的成员函数WillToggleFullscreen的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRenderWidget::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的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidWebViewImpl::willEnterFullScreen()
{
m_fullscreenController->willEnterFullScreen();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。
WebViewImpl类的成员函数willEnterFullScreen调用成员变量m_fullscreenController指向的一个FullscreenController对象的成员函
数willEnterFullScreen,用来通知WebKit即将要进入全屏模式,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidFullscreenController::willEnterFullScreen()
{
if(!m_provisionalFullScreenElement)
return;
//Ensurethatthiselement''sdocumentisstillattached.
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,它指向了一个
标签。FullscreenController类的成员函数willEnterFullScreen找到这个
标签所在的Document,并且获得与这个Document关联
的一个LocalFrame对象,保存在成员变量m_fullScreenFrame中,表示当前处于全屏模式的网页。
这一步执行完成后,回到前面分析的RenderWidget类的成员函数Resize,它在通知了WebKit修改当前正在加载的网页的大小之后,会调
用另外一个成员函数DidToggleFullscreen,用来通知WebKit已经进入了全屏模式,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRenderWidget::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它已
经进入全屏模式,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidWebViewImpl::didEnterFullScreen()
{
m_fullscreenController->didEnterFullScreen();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。
WebViewImpl类的成员函数didEnterFullScreen调用成员变量m_fullscreenController指向的一个FullscreenController对象的成员函
数didEnterFullScreen,用来通知WebKit已经进入全屏模式,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidFullscreenController::didEnterFullScreen()
{
if(!m_fullScreenFrame)
return;
if(Documentdoc=m_fullScreenFrame->document()){
if(FullscreenElementStack::isFullScreen(doc)){
......
if(RuntimeEnabledFeatures::overlayFullscreenVideoEnabled()){
Elementelement=FullscreenElementStack::currentFullScreenElementFrom(doc);
ASSERT(element);
if(isHTMLMediaElement(element)){
HTMLMediaElementmediaElement=toHTMLMediaElement(element);
if(mediaElement->webMediaPlayer()&&mediaElement->webMediaPlayer()->canEnterFullscreen()
//FIXME:Thereisnoembedder-sidehandlinginlayouttestmode.
&&!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就会检查它的FullscreenElementStack栈顶标
签。如果这是一个
标签,并且已经为这个
标签创建过MediaPlayer接口,以及这个MediaPlayer接口允许进入全屏模式,那么
FullscreenController类的成员函数didEnterFullScreen就会调用这个MediaPlayer接口的成员函数enterFullScreen,让其进入全屏模式。
从前面文章中一文可以知道,WebKit为
标签创建的MediaPlayer接口指向的是一个
WebMediaPlayerAndroid对象。因此,FullscreenController类的成员函数didEnterFullScreen调用的是WebMediaPlayerAndroid类的成员函数
enenterFullScreen让
标签的播放器进入全屏模式。
WebMediaPlayerAndroid类的成员函数enenterFullScreen的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidWebMediaPlayerAndroid::enterFullscreen(){
if(player_manager_->CanEnterFullscreen(frame_)){
player_manager_->EnterFullscreen(player_id_,frame_);
......
}
}
这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
从前面文章中一文可以知道,WebMediaPlayerAndroid类的成员变量player_manager_指向
的是一个RendererMediaPlayerManager对象。这个RendererMediaPlayerManager对象负责管理为当前正在处理的网页中的所有
标签创建
的播放器实例。
WebMediaPlayerAndroid类的成员函数enenterFullScreen首先调用上述RendererMediaPlayerManager对象的成员函数
CanEnterFullscreen检查与当前正在处理的播放器实例关联的
标签所在的网页是否已经进入了全屏模式。如果已经进入,那么就会继续
调用RendererMediaPlayerManager对象的成员函数EnterFullscreen使得当前正在处理的播放器实例进入全屏模式。
从前面的分析可以知道,与当前正在处理的播放器实例关联的
标签所在的网页已经进入了全屏模式。因此,接下来
RendererMediaPlayerManager类的成员函数EnterFullscreen就会被调用,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidRendererMediaPlayerManager::EnterFullscreen(intplayer_id,
blink::WebFrameframe){
pending_fullscreen_frame_=frame;
Send(newMediaPlayerHostMsg_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消息,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolMediaWebContentsObserver::OnMediaPlayerMessageReceived(
constIPC::Message&msg,
RenderFrameHostrender_frame_host){
boolhandled=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()
returnhandled;
}
这个函数定义在文件external/chromium_org/content/browser/media/media_web_contents_observer.cc中。
MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived首先调用成员函数GetMediaPlayerManager获得一个
BrowserMediaPlayerManager对象,然后调用这个BrowserMediaPlayerManager对象的成员函数OnEnterFullscreen处理类型为
MediaPlayerHostMsg_EnterFullscreen的IPC消息,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidBrowserMediaPlayerManager::OnEnterFullscreen(intplayer_id){
......
if(video_view_.get()){
fullscreen_player_id_=player_id;
video_view_->OpenVideo();
return;
}elseif(!ContentVideoView::GetInstance()){
......
video_view_.reset(newContentVideoView(this));
base::android::ScopedJavaLocalRef
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对象。同一个网页中的所有
标签在设置为全屏模式时,使
用的是同一个全屏播放器窗口。不同的网页的
标签在设置为全屏模式时,使用不同的全屏播放器窗口。在同一时刻,Browser进程只能
存在一个全屏播放器窗口。
BrowserMediaPlayerManager类的成员函数OnEnterFullscreen的执行逻辑如下所示:
1.如果当前正在处理的BrowserMediaPlayerManager对象的成员变量video_view_的值不等于NULL,也就是它指向了一个
ContentVideoView对象,那么就说明Browser进程已经为当前正在处理的BrowserMediaPlayerManager对象创建过全屏播放器窗口了。这时候就
会将参数player_id的值保存在另外一个成员变量fullscreen_player_id_中,表示当前正在处理的BrowserMediaPlayerManager对象所管理的全
屏播放器窗口正在被ID为player_id的播放器使用。同时,会调用上述ContentVideoView对象的成员函数OpenVideo,让其全屏播放当前被设置
为全屏模式的
标签的视频。
2.如果当前正在处理的BrowserMediaPlayerManager对象的成员变量video_view_的值NULL,并且Browser进程当前没有为其它网页的
标签全屏播放视频(这时候调用ContentVideoView类的静态成员函数GetInstance获得的返回值为NULL),那么就会为当前正在处理的
BrowserMediaPlayerManager对象创建一个全屏播放窗口,也就是创建一个ContentVideoView对象(这时候调用ContentVideoView类的静态成员
函数GetInstance将会返回该ContentVideoView对象),并且保存在其成员变量video_view_中。
C++层ContentVideoView对象在创建的过程中,又会在Java层创建一个对应的ContentVideoView对象。这个Java层ContentVideoView对
象如果能成功创建,那么就可以通过调用其对应的C++层ContentVideoView对象的成员函数GetJavaObject获得。这时候说明Browser进程成功创
建了一个全屏播放器窗口。这个全屏播放器窗口实际上就是一个SurfaceView对象。
一个SurfaceView对象刚创建出来的时候,它描述的窗口没有创建出来。Browser进程需要等它描述的窗口创建出来之后,才使用它全屏
播放当前被设置为全屏模式的
标签的视频。因此,BrowserMediaPlayerManager类的成员函数OnEnterFullscreen在创建了一个
SurfaceView对象之后,只做了一件简单的事情,就是记录当前是哪一个播放器进入了全屏模式,即将参数player_id的值保存在当前正在处理
的BrowserMediaPlayerManager对象的成员变量fullscreen_player_id_中。
接下来,我们就继续分析全屏播放器窗口的创建过程,以及Browser进程使用它来全屏播放当前设置为全屏模式的
标签的视频的
过程。
全屏播放器窗口是在C++层ContentVideoView对象的创建过程当中创建出来的,因此,我们就从C++层ContentVideoView类的构造函数开
始,分析全屏播放器窗口的创建过程,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
ContentVideoView::ContentVideoView(
BrowserMediaPlayerManagermanager)
: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进程当前已经存在一个全屏窗口。根据我们前面的分析,这样就会阻止其它网页全屏播放它们的
标签的视频。这个静态成员变量g_content_video_view_的值可以通过调用前面提到的C++层ContentVideoView类的静态成员函数
GetInstance获得,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
ContentVideoViewContentVideoView::GetInstance(){
returng_content_video_view;
}
这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。
回到C++层的ContentVideoView类的构造函数中,我们主要关注Java层的ContentVideoView对象的创建过程,因此接下来我们继续分析
C++层的ContentVideoView类的成员函数CreateJavaObject的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
JavaObjectWeakGlobalRefContentVideoView::CreateJavaObject(){
ContentViewCoreImplcontent_view_core=manager_->GetContentViewCore();
JNIEnvenv=AttachCurrentThread();
boollegacyMode=CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableOverlayFullscreenVideoSubtitle);
returnJavaObjectWeakGlobalRef(
env,
Java_ContentVideoView_createContentVideoView(
env,
content_view_core->GetContext().obj(),
reinterpret_cast
(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对象,如下所示:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassContentVideoViewextendsFrameLayout
implementsSurfaceHolder.Callback,ViewAndroidDelegate{
......
@CalledByNative
privatestaticContentVideoViewcreateContentVideoView(
Contextcontext,longnativeContentVideoView,ContentVideoViewClientclient,
booleanlegacy){
......
ContentVideoViewvideoView=null;
if(legacy){
videoView=newContentVideoViewLegacy(context,nativeContentVideoView,client);
}else{
videoView=newContentVideoView(context,nativeContentVideoView,client);
}
if(videoView.getContentVideoViewClient().onShowCustomView(videoView)){
returnvideoView;
}
returnnull;
}
......
}
这个函数定义在文件
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的创建过程,如下所示:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassContentVideoViewextendsFrameLayout
implementsSurfaceHolder.Callback,ViewAndroidDelegate{
......
//Thisviewwillcontainthevideo.
privateVideoSurfaceViewmVideoSurfaceView;
......
protectedContentVideoView(Contextcontext,longnativeContentVideoView,
ContentVideoViewClientclient){
super(conwww.sm136.comtext);
......
mVideoSurfaceView=newVideoSurfaceView(context);
showContentVideoView();
......
}
......
}
这个函数定义在文件
external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
ContentVideoView类的构造函数会创建一个VideoSurfaceView对象,并且保存在成员变量mVideoSurfaceView中。
VideoSurfaceView类是从SurfaceView类继承下来的,如下所示:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassContentVideoViewextendsFrameLayout
implementsSurfaceHolder.Callback,ViewAndroidDelegate{
......
privateclassVideoSurfaceViewextendsSurfaceView{
......
}
......
}
这个类定义在文件
external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ContentVideoView.java中。
ContentVideoView类的构造函数在创建了一个VideoSurfaceView对象,接下来会调用另外一个成员函数showContentVideoView将它作为
当前正在创建的ContentVideoView对象的子View,如下所示:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassContentVideoViewextendsFrameLayout
implementsSurfaceHolder.Callback,ViewAndroidDelegate{
......
protectedvoidshowContentVideoView(){
mVideoSurfaceView.getHolder().addCallback(this);
this.addView(mVideoSurfaceView,newFrameLayout.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就会被调用,如下所示:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassContentVideoViewextendsFrameLayout
implementsSurfaceHolder.Callback,ViewAndroidDelegate{
......
@Override
publicvoidsurfaceCreated(SurfaceHolderholder){
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的解
码输出,如下所示:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassContentVideoViewextendsFrwww.shanxiwang.netameLayout
implementsSurfaceHolder.Callback,ViewAndroidDelegate{
......
@CalledByNative
protectedvoidopenVideo(){
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完成的。当它们完成之后,
标签的视频
可以开始全屏播放了。接下来我们继续分析它们的实现。
ContentVideoView类的成员函数nativeRequestMediaMetadata是一个JNI方法,它由C++层的函数
Java_com_android_org_chromium_content_browser_ContentVideoView_nativeRequestMediaMetadata实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
void
Java_com_android_org_chromium_content_browser_ContentVideoView_nativeRequestMediaMetadata(JNIEnv
env,
jobjectjcaller,
jlongnativeContentVideoView){
ContentVideoViewnative=
reinterpret_cast
(nativeContentVideoView);
CHECK_NATIVE_PTR(env,jcaller,native,"RequestMediaMetadata");
returnnative->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
获取要播放的视频的元数据,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidContentVideoView::RequestMediaMetadata(JNIEnvenv,jobjectobj){
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的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidContentVideoView::UpdateMediaMetadata(){
JNIEnvenv=AttachCurrentThread();
ScopedJavaLocalRef
content_video_view=GetJavaObject(env);
if(content_video_view.is_null())
return;
media::MediaPlayerAndroidplayer=manager_->GetFullscreenPlayer();
if(player&&player->IsPlayerReady()){
Java_ContentVideoView_onUpdateMediaMetadata(
env,content_video_view.obj(),player->GetVideoWidth(),
player->GetVideoHeight(),
static_cast
(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获得当前处于全屏模式的播放器,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
MediaPlayerAndroidBrowserMediaPlayerManager::GetFullscreenPlayer(){
returnGetPlayer(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中,它获得的全屏播放器之后,实际上就是
标签进入全屏模式之前所
使用的那个播放器。这意味着
标签在全屏和非全屏模式下,使用的是同一个播放器,区别只在于播放器在两种模式下使用的UI不一样。
这个播放器之前已经获得了要播放的视频的元数据,并且保存在了内部,因此这里就不需要从网络上重新获取。
有了要播放的视频的元数据之后,ContentVideoView类的成员函数UpdateMediaMetadata就通过JNI接口
Java_ContentVideoView_onUpdateMediaMetadata调用前面获得的Java层ContentVideoView对象的成员函数onUpdateMediaMetadata,让其初始
化全屏播放器窗口,如下所示:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassContentVideoViewextendsFrameLayout
implementsSurfaceHolder.Callback,ViewAndroidDelegate{
......
@CalledByNative
protectedvoidonUpdateMediaMetadata(
intvideoWidth,
intvideoHeight,
intduration,
booleancanPause,
booleancanSeekBack,
booleancanSeekForward){
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,用来初始化全屏播放器窗口,如下所示:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicclassContentVideoViewextendsFrameLayout
implementsSurfaceHolder.Callback,ViewAndroidDelegate{
......
@CalledByNative
privatevoidonVideoSizeChanged(intwidth,intheight){
mVideoWidth=width;
mVideoHeight=height;
//ThiswilltriggertheSurfaceView.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实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
void
Java_com_android_org_chromium_content_browser_ContentVideoView_nativeSetSurface(JNIEnv
env,
jobjectjcaller,
jlongnativeContentVideoView,
jobjectsurface){
ContentVideoViewnative=
reinterpret_cast
(nativeContentVideoView);
CHECK_NATIVE_PTR(env,jcaller,native,"SetSurface");
returnnative->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,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidContentVideoView::SetSurface(JNIEnvenv,jobjectobj,
jobjectsurface){
manager_->SetVideoSurface(
gfx::ScopedJavaSurface::AcquireExternalSurface(surface));
}
这个函数定义在文件external/chromium_org/content/browser/android/content_video_view.cc中。
ContentVideoView类的成员函数SetSurface调用成员变量manager_指向的一个BrowserMediaPlayerManager对象的成员函数
SetVideoSurface重新给当前处于全屏模式的播放器设置一个Surface,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidBrowserMediaPlayerManager::SetVideoSurface(
gfx::ScopedJavaSurfacesurface){
MediaPlayerAndroidplayer=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的实现,可以参考前面文章中一文。它实际上就是给在Java层
创建的一个MediaPlayer重新设置一个Surface作为解码输出。这个Surface是由一个VideoSurfaceView提供的。这个VideoSurfaceView实际上就
是一个SurfaceView。SurfaceView会自己将MediaPlayer的解码输出交给系统渲染。因此就不再需要Chromium参与这个渲染过程。
回忆
标签在进入全屏模式之前,Chromium会为它会创建一个纹理,然后用这个纹理创建一个SurfaceTexture。这个
SurfaceTexture最终又会封装在一个Surface中。这个Surface就设置为MediaPlayer的解码输出。这时候MediaPlayer的解码输出需要由
Chromium来处理,也就是渲染在浏览器窗口中。当浏览器窗口被系统渲染在屏幕上时,我们就可以看到MediaPlayer输出的视频画面了。这个过
程可以参考前面文章中一文。
这样,我们就分析完成
标签全屏播放视频的过程了。从这个分析过程我们就可以知道,
标签在全屏模式和非全屏模式下
使用的都是相同的MediaPlayer,区别只在于这个MediaPlayer将视频画面渲染在不同的Surface之上。因此,
标签可以在全屏模式和非
全屏模式之间进行无缝的播放切换。
至此,我们也分析完成了video标签在Chromium中的实现。视频在互联网中将扮演着越来越重要的角色。以前浏览器主要是通过Flash插
件来支持视频播放。Flash插件有着臭名昭著的安全问题和Crash问题。因此,随着HTML5的出现,浏览器逐步转向使用
标签来支持视频
播放。这不仅在产品上带来更好的体验(无需用户安装插件),而且在技术上也更加稳定。基于上述理由,理解
标签的实现原理就显得
尤为重要。
献花(
0
)
+1
(本文系
网络学习天...
首藏
)
类似文章
更多
用MediaPlayer TextureView封装一个完美实现全屏、小窗口的视频播放器
android4.4 browser渲染机制浅析
用SurfaceView和MediaPlayer做一个Android视频播放器
MediaPlayer 用法(一) - 一切皆有可能 - JavaEye技术网站
Android多媒体学习十二:Android中Video的三种播放方式的实现
从Chrome源码看浏览器如何构建DOM树
SurfaceHolder.Callback
IF函数运用实例「全屏时用“超清模式”观看」
通配符与SUMIF函数连用,实现模糊计算求和(完整版全屏→
发表评论: