Chromium为视频标签video全屏播放的过程分析在Chromium中,video标签有全屏和非全屏两种播放模式。在非全屏模式下,video标签播放的视频嵌入在网页中显示,也就是视频画面作为网页的一部分显示。在全屏模式下,我们是看不到网页其它内容的,因此video标签播放的视频可以在一个独立的全屏窗口中显示。这两种截然不同的播放模式,导致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的更多知识,可以参考前面文章中一文。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标签从非全屏模式切换为全屏模式的示意图,如下所示:当video标签从非全屏模式切换为全屏模式时,Chromium会为它创建一个全屏的SurfaceView,并且将这个SurfaceView内部的Surface设置给MediaPlayer。以后MediaPlayer就不会再将解码出来的视频画面通过原先设置的SurfaceTexture交给Chromium处理,而是通过后面设置的Surface交给SurfaceView处理。接下来,我们就结合源代码,从video标签进入全屏模式开始,分析video标签全屏播放视频的过程。从前面文章中一文可以知道,在WebKit中,video标签是通过HTMLMediaElement类描述的。当video标签进入全屏模式时,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获得当前正在处理的video标签所属的网页,然后再通过调用FullscreenElementStack类的静态成员函数from获得这个网页所对应的FullscreenElementStack对象。有了这个FullscreenElementStack对象之后,就可以调用它的成员函数requestFullScreenForElement请求将当前正在处理的video标签设置为全屏模式。FullscreenElementStack类的成员函数requestFullScreenForElement的实现如下所示:[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片voidFullscreenElementStack::requestFullScreenForElement(Element*element,unsignedshortflags,FullScreenCheckTypecheckType){......//TheMozillaFullScreenAPI:FullScreenAPIhasdifferentrequirements//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){Element*lastElementOnStack=m_fullScreenElementStack.last().get();if(lastElementOnStack==element||!lastElementOnStack-contains(element))break;}//Adescendantbrowsingcontext'sdocumenthasanon-emptyfullscreenelementstack.booldescendentHasNonEmptyStack=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.Letdocbeelement'snodedocument.(i.e.this)Document*currentDoc=document();//3.Letdocsbealldoc'sancestorbrowsingcontext'sdocuments(ifany)anddoc.DequeDocument*docs;do{docs.prepend(currentDoc);currentDoc=currentDoc-ownerElement()?¤tDoc-ownerElement()-document():0;}while(currentDoc);//4.Foreachdocumentindocs,runthesesubsteps:DequeDocument*::iteratorcurrent=docs.begin(),following=docs.begin();do{++following;//1.Letfollowingdocumentbethedocumentafterdocumentindocs,ornullifthereisno//suchdocument.Document*currentDoc=*current;Document*followingDoc=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'sbrowsingcontextco