Chromium是通过WebKit解析网页内容的。当WebKit遇到<video>标签时,就会创建一个播放器实例。WebKit是平台无关的,而播放器实现是平台相关的。因此,WebKit并没有自己实现播放器,而仅仅是创建一个播放器接口。通过这个播放器接口,可以使用平台提供的播放器来播放视频的内容。这就简化了Chromium对视频标签的支持。本文接下来就分析Chromium为视频标签创建播放器的过程。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
以Android平台为例,它的SDK提供了一个MediaPlayer接口,用来播放视频。Chromium的目标就是为网页中的每一个<video>标签创建一个MediaPlayer实例,如图1所示:
图1 Chromium为<video>标签创建MediaPlayer的过程
首先,WebKit会为网页中的每一个<video>标签创建一个类型为HTMLMediaElement的DOM节点。HTMLMediaElement类内部有一个WebMediaPlayerClientImpl接口。这个WebMediaPlayerClientImpl接口指向的是是一个运行在Render进程的Content模块中的一个WebMediaPlayerAndroid对象。这些WebMediaPlayerAndroid对象归一个称为RendererMediaPayerManager的对象管理。
Render进程中的每一个WebMediaPlayerAndroid对象,在Browser进程中都有一个对应的WebMediaPlayerBridge对象。这些WebMediaPlayerBridge对象归一个称为BrowserMediaPayerManager的对象管理。每一个WebMediaPlayerBridge对象在Java层中又都对应有一个MediaPlayer对象。这些MediaPlayer对象描述的就是Android平台提供的播放器。
接下来,我们就从WebKit解析<video>标签的属性开始,分析Chromium为它们创建MediaPlayer的过程,如下所示:
void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomicString& value) { if (name == srcAttr) { // Trigger a reload, as long as the 'src' attribute is present. if (!value.isNull()) { ...... scheduleDelayedAction(LoadMediaResource); } } ...... }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
WebKit为网页的每一个标签创建了相应的DOM节点之后,就会调用这个DOM节点的成员函数parseAtrribute对它的属性进行解析。从前面Chromium网页DOM Tree创建过程分析一文可以容易知道,WebKit为<video>标签创建的DOM节点的实际类型为HTMLVideoElement。HTMLVideoElement类是从HTMLMediaElement类继承下来的,WebKit是调用后者的成同员函数parseAttribute来解析<video>的属性。
我们假设<video>标签通过src属性设置了要播放的视频文件的URL。这时候HTMLMediaElement类的成员函数parseAttribute就会调用另外一个成员函数scheduleDelayedAction为<video>标签创建播放器,如下所示:
void HTMLMediaElement::scheduleDelayedAction(DelayedActionType actionType) { ..... if ((actionType & LoadMediaResource) && !(m_pendingActionFlags & LoadMediaResource)) { prepareForLoad(); m_pendingActionFlags |= LoadMediaResource; } ...... if (!m_loadTimer.isActive()) m_loadTimer.startOneShot(0, FROM_HERE); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
从前面的调用过程可以知道,参数actionType的值等于LoadMediaResource。这时候如果HTMLMediaElement类的成员变量m_pendingActionFlags的LoadMediaResource位等于0,那么就说明WebKit还没有为当前正在解析的<video>标签创建过播放器接口。于是接下来就会做两件事情:
1. 调用另外一个成员函数prepareForLoad开始为当前正在解析的<video>标签创建图1所示的WebMediaPlayerClientImpl接口;
2. 将成员变量m_pendingActionFlags的LoadMediaResource位设置为1,表示WebKit正在为当前正在解析的<video>标签创建过播放器接口,避免接下来出现重复创建的情况。
HTMLMediaElement类的另外一个成员变量m_loadTimer描述的是一个定时器。如果这个定时器还没有被启动,那么HTMLMediaElement类的成员函数scheduleDelayedAction就会调用它的成员函数startOneShot马上进行启动。指定的启动时间单位为0,这意味着这个定时器会马上超时。超时后它将会调用HTMLMediaElement类的成员函数loadTimerFired。HTMLMediaElement类的成员函数loadTimerFired将会继续创建图1所示的WebMediaPlayerAndroid、WebMediaPlayerBridge和MediaPlayer接口。
接下来我们就先分析HTMLMediaElement类的成员函数prepareForLoad的实现,如下所示:
void HTMLMediaElement::prepareForLoad() { ...... createMediaPlayer(); ...... }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
HTMLMediaElement类的成员函数prepareForLoad主要是调用另外一个成员函数createMediaPlayer为当前正在解析的<video>标签创建一个WebMediaPlayerClientImpl接口,如下所示:
void HTMLMediaElement::createMediaPlayer() { ...... m_player = MediaPlayer::create(this); ...... }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
HTMLMediaElement类的成员函数createMediaPlayer调用MediaPlayer类的静态成员函数create创建了一个WebMediaPlayerClientImpl接口,并且保存在HTMLMediaElement类的成员变量m_player中。
MediaPlayer类的静态成员函数create的实现如下所示:
static CreateMediaEnginePlayer createMediaEngineFunction = 0; void MediaPlayer::setMediaEngineCreateFunction(CreateMediaEnginePlayer createFunction) { ASSERT(createFunction); ASSERT(!createMediaEngineFunction); createMediaEngineFunction = createFunction; } PassOwnPtr<MediaPlayer> MediaPlayer::create(MediaPlayerClient* client) { ASSERT(createMediaEngineFunction); return createMediaEngineFunction(client); }这两个函数定义在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/media/MediaPlayer.cpp中。
MediaPlayer类的静态成员函数create调用全局变量createMediaEngineFunction描述的一个函数创建一个播放器接口返回给调用者。全局变量createMediaEngineFunction描述的函数实际上是WebMediaPlayerClientImpl类的静态成员函数create,它是通过调用MediaPlayer类的静态成员函数setMediaEngineCreateFunction设置的。
WebMediaPlayerClientImpl类的静态成员函数create的实现如下所示:
PassOwnPtr<MediaPlayer> WebMediaPlayerClientImpl::create(MediaPlayerClient* client) { return adoptPtr(new WebMediaPlayerClientImpl(client)); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。
从这里可以看到,WebMediaPlayerClientImpl类的静态成员函数create返回的是一个WebMediaPlayerClientImpl对象。这个WebMediaPlayerClientImpl对象描述的就是WebKit层的播放器接口。
这一步执行完成之后,WebKit就为当前正在解析的<video>标签创建了一个类型为WebMediaPlayerClientImpl的播放器接口。回到前面分析的HTMLMediaElement类的成员函数scheduleDelayedAction中,接下来它启动的定时器就会马上执行,也就HTMLMediaElement类的成员函数loadTimerFired会马上被调用。
HTMLMediaElement类的成员函数loadTimerFired的实现如下所示:
void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) { ...... if (m_pendingActionFlags & LoadMediaResource) { if (m_loadState == LoadingFromSourceElement) loadNextSourceChild(); else loadInternal(); } m_pendingActionFlags = 0; }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
前面分析的HTMLMediaElement类的成员函数scheduleDelayedAction已经将成员变量m_pendingActionFlags的LoadMediaResource位设置为1。这时候HTMLMediaElement类的成员函数loadTimerFired就会检查HTMLMediaElement类的另外一个成员变量m_loadState的值是否等于LoadingFromSourceElement。如果等于,那么就说明当前正在解析的<video>标签通过子元素<source>指定要播放的视频的URL。否则的话,就通过属性src指定要播放的视频的URL。
前面我们假定了当前正在解析的<video>标签通过属性src指定要播放的视频的URL,因此HTMLMediaElement类的成员函数loadTimerFired接下来就会调用成员函数loadInternal继续为其创建其它的播放器接口,如下所示:
void HTMLMediaElement::loadInternal() { ...... selectMediaResource(); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
HTMLMediaElement类的成员函数loadInternal调用另外一个成员函数selectMediaResource为当前正在解析的<video>标签选择当前要播放的视频的URL。确定了当前要播放的视频的URL之后,就会加载视频元数据。有了这些元数据之后,就可以为其创建真正的播放器。
HTMLMediaElement类的成员函数selectMediaResource的实现如下所示:
void HTMLMediaElement::selectMediaResource() { ...... enum Mode { attribute, children }; ....... Mode mode = attribute; if (!fastHasAttribute(srcAttr)) { // Otherwise, if the media element does not have a src attribute but has a source // element child, then let mode be children and let candidate be the first such // source element child in tree order. if (HTMLSourceElement* element = Traversal<HTMLSourceElement>::firstChild(*this)) { mode = children; ...... } ..... } ...... if (mode == attribute) { ...... // If the src attribute's value is the empty string ... jump down to the failed step below KURL mediaURL = getNonEmptyURLAttribute(srcAttr); ...... loadResource(mediaURL, contentType, String()); ..... return; } ...... }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
HTMLMediaElement类的成员函数selectMediaResource所做的事情就要确定是从当前正在解析的<video>标签的src属性获得要加载的视频的URL,还是从它的子元素<source>获得要加载的视频的URL。
如果当前正在解析的<video>标签设置了src属性,那么就会优先从这个属性获得要加载的视频的URL。有了这个URL之后,HTMLMediaElement类的成员函数selectMediaResource就会调用另外一个成员函数loadResource加载它所描述的视频的元数据。
在我们这个情景中,当前正在解析的<video>标签设置了src属性。因此,接下来我们就继续分析HTMLMediaElement类的成员函数loadResource的实现,如下所示:
void HTMLMediaElement::loadResource(const KURL& url, ContentType& contentType, const String& keySystem) { ...... m_currentSrc = url; ...... bool attemptLoad = true; if (url.protocolIs(mediaSourceBlobProtocol)) { if (isMediaStreamURL(url.string())) { m_userGestureRequiredForPlay = false; } else { m_mediaSource = HTMLMediaSource::lookup(url.string()); if (m_mediaSource) { if (!m_mediaSource->attachToElement(this)) { // Forget our reference to the MediaSource, so we leave it alone // while processing remainder of load failure. m_mediaSource = nullptr; attemptLoad = false; } } } } if (attemptLoad && canLoadURL(url, contentType, keySystem)) { ....... if (!m_havePreparedToPlay && !autoplay() && m_preload == MediaPlayer::None) { ...... deferLoad(); } else { startPlayerLoad(); } } ...... }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
HTMLMediaElement类的成员函数loadResource首先将当前要播放的视频的URL保存在成员变量m_currentSrc中,接下来判断该URL的协议部分是否为“blob”。协议“blob”是“Binary Large OBject”的缩写,表示一组二进制数据。例如,我们手头上有一组表示一个Image的二进制数据,这时候可以调用URL.createObjectURL函数为这组二进制数据创建一个blob协议地址,然后再将该地址设置为一个<img>标签的src,这样就可以将图像显示出来。
通过blob协议,还可以描述媒体数据。在WebKit中,媒体数据可以通过Media Source或者Media Stream API描述。Media Source API的设计初衷是让JavaScript能动态产生媒体流,然后交给<video>标签播放。Media Stream API是为WebRTC设计的,不仅可以使用<video>标签播放从本地摄像头采集的图像,还可以播放从网络发送过来的实时媒体流。关于Media Source 和Media Steam API的更详细信息,可以参考Media Source Extensions和Media Capture and Streams这两篇文档。
如果当前要播放的视频的URL的协议部分为“blob”,HTMLMediaElement类的成员函数loadResource首先会检查它描述的是否是一个Media Stream。如果是的话,那么就会将HTMLMediaElement类的成员变量m_userGestureRequiredForPlay设置为false,表示后台Tab网页的视频可以自动播放。Render进程有一个“disable-gesture-requirement-for-media-playback”选项。当这个选项的值设置为false时,HTMLMediaElement类的成员变量m_userGestureRequiredForPlay就会被设置为true,表示后台Tab网页的视频不可以自动播放。不过,如果要播放的视频是一个Media Stream,那么不会受到此限制。关于Render进程的“disable-gesture-requirement-for-media-playback”启动选项的更多信息,可以参考Chrome 47 offers a flag to disable defer media playback in background tabs一文。
如果当前要播放的视频的URL的协议部分为“blob”,但是它描述的不是一个Media Stream API,那么HTMLMediaElement类的成员函数loadResource再检查它是否是一个Media Source。如果是的话,就会将该Media Source作为当前正在解析的<video>标签的播放源。在这种情况下,WebKit不需要从网络上下载媒体数据回来,只需要从指定的Media Source读取回来就可以了。这时候本地变量attemptLoad的值会被设置为false。在其余情况下,本地变量attemptLoad的值保持为初始值true。
在本地变量attemptLoad的值为true的情况下,HTMLMediaElement类的成员函数loadResource会调用另外一个成员函数canLoadURL继续检查当前要播放的视频的URL描述的内容是否为多媒体数据,以及该多媒体使用的编码方式是否被支持。如果检查通过,HTMLMediaElement类的成员函数loadResource再判断当前解析的<video>标签的是否需要preload和autoplay。如果不需要,那么就会调用成员函数deferLoad延迟加载要播放的视频的内容。否则的话,就会调用成员函数startPlayerLoad马上加载要播放的视频的内容回来。
我们假设当前解析的<video>标签设置了autoplay,因此接下来我们就继续分析HTMLMediaElement类的成员函数startPlayerLoad的实现,如下所示:
void HTMLMediaElement::startPlayerLoad() { ....... KURL requestURL = m_currentSrc; ...... m_player->load(loadType(), requestURL, corsMode()); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
从前面的分析可以知道,HTMLMediaElement类的成员变量m_player指向的是一个WebMediaPlayerClientImpl对象。HTMLMediaElement类的成员函数startPlayerLoad主要是调用这个WebMediaPlayerClientImpl对象的成员函数load加载当前要播放的视频的内容。
WebMediaPlayerClientImpl类的成员函数load的实现如下所示:
void WebMediaPlayerClientImpl::load(WebMediaPlayer::LoadType loadType, const WTF::String& url, WebMediaPlayer::CORSMode corsMode) { ...... KURL kurl(ParsedURLString, url); m_webMediaPlayer = createWebMediaPlayer(this, kurl, frame); ...... m_webMediaPlayer->load(loadType, kurl, corsMode); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。
WebMediaPlayerClientImpl类的成员函数load首先调用函数createWebMediaPlayer请求运行在当前进程(Render进程)中的Content模块创建一个播放器接口。这个Content模块的播放器接口会保存在WebMediaPlayerClientImpl类的成员变量m_webMediaPlayer中。有了Content模块的播放器接口之后,WebMediaPlayerClientImpl类的成员函数load再调用它的成员函数load请求加载当前要播放的视频的内容。
接下来我们先分析Content模块的播放器接口的创建过程,也就是函数createWebMediaPlayer的实现,如下所示:
static PassOwnPtr<WebMediaPlayer> createWebMediaPlayer(WebMediaPlayerClient* client, const WebURL& url, LocalFrame* frame) { WebLocalFrameImpl* webFrame = WebLocalFrameImpl::fromFrame(frame); ...... return adoptPtr(webFrame->client()->createMediaPlayer(webFrame, url, client)); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。
函数createWebMediaPlayer首先获得一个类型为blink::WebFrameClient的接口。这个接口是由WebKit的使用者设置给WebKit的,以便WebKit可以通过它执行一些平台相关的功能。在我们这个情景中,WebKit的使用者即为Render进程中的Content模块。Content模块中的RenderFrameImpl类实现了该接口,并且会设置给WebKit。因此,函数createWebMediaPlayer接下来就会调用它的成员函数createMediaPlayer创建一个播放器接口。
RenderFrameImpl类的成员函数createMediaPlayer的实现如下所示:
blink::WebMediaPlayer* RenderFrameImpl::createMediaPlayer( blink::WebLocalFrame* frame, const blink::WebURL& url, blink::WebMediaPlayerClient* client) { blink::WebMediaStream web_stream( blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor(url)); if (!web_stream.isNull()) return CreateWebMediaPlayerForMediaStream(url, client); #if defined(OS_ANDROID) return CreateAndroidWebMediaPlayer(url, client); #else ...... #endif // defined(OS_ANDROID) }这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。
RenderFrameImpl类的成员函数createMediaPlayer首先判断当前要播放的视频的URL描述的是否是一个Media Stream。如果是的话,那么就会调用成员函数CreateWebMediaPlayerForMediaStream创建一个类型为WebMediaPlayerMS的播放器。这个类型为WebMediaPlayerMS的播放器是在WebRTC中实现的。我们不考虑这种情况。
如果当前要播放的视频的URL描述的不是一个Media Stream,那么RenderFrameImpl类的成员函数createMediaPlayer就会调用另外一个成员函数CreateAndroidWebMediaPlayer创建另外一种类型的播放器,如下所示:
WebMediaPlayer* RenderFrameImpl::CreateAndroidWebMediaPlayer( const blink::WebURL& url, WebMediaPlayerClient* client) { GpuChannelHost* gpu_channel_host = RenderThreadImpl::current()->EstablishGpuChannelSync( CAUSE_FOR_GPU_LAUNCH_VIDEODECODEACCELERATOR_INITIALIZE); ...... scoped_refptr<StreamTextureFactory> stream_texture_factory; if (SynchronousCompositorFactory* factory = SynchronousCompositorFactory::GetInstance()) { stream_texture_factory = factory->CreateStreamTextureFactory(routing_id_); } else { scoped_refptr<webkit::gpu::ContextProviderWebContext> context_provider = RenderThreadImpl::current()->SharedMainThreadContextProvider(); ...... stream_texture_factory = StreamTextureFactoryImpl::Create( context_provider, gpu_channel_host, routing_id_); } return new WebMediaPlayerAndroid( frame_, client, weak_factory_.GetWeakPtr(), GetMediaPlayerManager(), GetCdmManager(), stream_texture_factory, RenderThreadImpl::current()->GetMediaThreadMessageLoopProxy(), new RenderMediaLog()); }这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。
RenderFrameImpl类的成员函数CreateAndroidWebMediaPlayer首先是调用RenderThreadImpl类的成员函数EstablishGpuChannelSync为接下来要创建的播放器创建一个到GPU进程的GPU通道。之所以要创建这个GPU通道,是因为Render要在GPU进程中创建一个纹理,然后将这个纹理封装为一个SurfaceTexture,作为Android平台的MediaPlayer的解码输出。也就是说,Render进程会通过这个SurfaceTexture接收Android平台的MediaPlayer的解码输出,然后再以纹理的形式渲染在网页上。GPU通道的创建过程,也就是RenderThreadImpl类的成员函数EstablishGpuChannelSync的实现,可以参考前面Chromium的GPU进程启动过程分析一文。
RenderFrameImpl类的成员函数CreateAndroidWebMediaPlayer最终创建的是一个类型为WebMediaPlayerAndroid的播放器。创建这个类型为WebMediaPlayerAndroid的播放器需要用到两个重要的对象。一个是StreamTextureFactory对象,另一个是RendererMediaPlayerManager对象。前者用来创建前面所述的纹理。后者用来管理在Render进程中创建的播放器实例。
在WebView的情况下,调用SynchronousCompositorFactory类的静态成员函数GetInstance会获得一个SynchronousCompositorFactory对象。在这种情况下,上述StreamTextureFactory对象通过调用这个SynchronousCompositorFactory对象的成员函数CreateStreamTextureFactory获得。我们不考虑这一种情况。
在独立App的情况下,上述StreamTextureFactory对象实际上是一个StreamTextureFactoryImpl对象,它是通过调用StreamTextureFactoryImpl类的静态成员函数Create创建的,如下所示:
scoped_refptr<StreamTextureFactoryImpl> StreamTextureFactoryImpl::Create( const scoped_refptr<cc::ContextProvider>& context_provider, GpuChannelHost* channel, int frame_id) { return new StreamTextureFactoryImpl(context_provider, channel, frame_id); }这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。
从这里可以看到,StreamTextureFactoryImpl类的静态成员函数Create返回的是一个StreamTextureFactoryImpl对象。
回到前面分析的RenderFrameImpl类的成员函数CreateAndroidWebMediaPlayer中,它创建了一个StreamTextureFactoryImpl对象之后,接下来又会调用另外一个成员函数GetMediaPlayerManager获得一个RendererMediaPlayerManager对象,如下所示:
RendererMediaPlayerManager* RenderFrameImpl::GetMediaPlayerManager() { if (!media_player_manager_) { media_player_manager_ = new RendererMediaPlayerManager(this); ...... } return media_player_manager_; }这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。
从这里可以看到,RenderFrameImpl类的成员函数GetMediaPlayerManager返回的是成员变量media_player_manager_指向的是一个RendererMediaPlayerManager对象。如果这个RendererMediaPlayerManager对象还没有创建,那么就会先进行创建。
再回到前面分析的RenderFrameImpl类的成员函数CreateAndroidWebMediaPlayer中,有了一个StreamTextureFactoryImpl对象和一个RendererMediaPlayerManager对象之后,它就会创建一个类型为WebMediaPlayerAndroid的播放器。
类型为WebMediaPlayerAndroid的播放器的创建过程,也就是WebMediaPlayerAndroid类的构造函数的实现,如下所示:
WebMediaPlayerAndroid::WebMediaPlayerAndroid( blink::WebFrame* frame, blink::WebMediaPlayerClient* client, base::WeakPtr<WebMediaPlayerDelegate> delegate, RendererMediaPlayerManager* player_manager, RendererCdmManager* cdm_manager, scoped_refptr<StreamTextureFactory> factory, const scoped_refptr<base::MessageLoopProxy>& media_loop, media::MediaLog* media_log) : ......, player_manager_(player_manager), ......, stream_texture_factory_(factory), ...... { ...... player_id_ = player_manager_->RegisterMediaPlayer(this); ...... TryCreateStreamTextureProxyIfNeeded(); }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的构造函数首先是将参数player_manager和factory描述的StreamTextureFactoryImpl对象和RendererMediaPlayerManager对象分别保存在成员变量player_manager_和stream_texture_factory_中。
WebMediaPlayerAndroid类的构造函数接下来又会做两件事情:
1. 调用上述RendererMediaPlayerManager对象的成员函数RegisterMediaPlayer将当前正在创建的WebMediaPlayerAndroid对象保存在其内部,并且为其分配一个ID。以后通过这个ID就可以在该RendererMediaPlayerManager对象中找到当前正在创建的WebMediaPlayerAndroid对象。
2. 调用另外一个成员函数TryCreateStreamTextureProxyIfNeeded创建一个SurfaceTexture,以便后面可以用来接收Android平台的MediaPlayer的解码输出。
关于WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded创建SurfaceTexture的过程,我们在接下来一篇文章中分析<video>标签的视频渲染过程时再分析。
这一步执行完成之后,运行在Render进程中的Content模块就创建了一个类型为WebMediaPlayerAndroid的播放器接口。这个播放器接口会返回给WebKit层的WebMediaPlayerClientImpl类的成员函数load。WebMediaPlayerClientImpl类的成员函数load获得了这个播放器接口之后,就会调用它的成员函数load加载当前要播放的视频的内容,如下所示:
void WebMediaPlayerAndroid::load(LoadType load_type, const blink::WebURL& url, CORSMode cors_mode) { ...... switch (load_type) { case LoadTypeURL: player_type_ = MEDIA_PLAYER_TYPE_URL; break; case LoadTypeMediaSource: player_type_ = MEDIA_PLAYER_TYPE_MEDIA_SOURCE; break; case LoadTypeMediaStream: CHECK(false) << "WebMediaPlayerAndroid doesn't support MediaStream on " "this platform"; return; } url_ = url; int demuxer_client_id = 0; if (player_type_ != MEDIA_PLAYER_TYPE_URL) { RendererDemuxerAndroid* demuxer = RenderThreadImpl::current()->renderer_demuxer(); demuxer_client_id = demuxer->GetNextDemuxerClientID(); media_source_delegate_.reset(new MediaSourceDelegate( demuxer, demuxer_client_id, media_loop_, media_log_)); if (player_type_ == MEDIA_PLAYER_TYPE_MEDIA_SOURCE) { media::SetDecryptorReadyCB set_decryptor_ready_cb = media::BindToCurrentLoop( base::Bind(&WebMediaPlayerAndroid::SetDecryptorReadyCB, weak_factory_.GetWeakPtr())); media_source_delegate_->InitializeMediaSource( base::Bind(&WebMediaPlayerAndroid::OnMediaSourceOpened, weak_factory_.GetWeakPtr()), base::Bind(&WebMediaPlayerAndroid::OnNeedKey, weak_factory_.GetWeakPtr()), set_decryptor_ready_cb, base::Bind(&WebMediaPlayerAndroid::UpdateNetworkState, weak_factory_.GetWeakPtr()), base::Bind(&WebMediaPlayerAndroid::OnDurationChanged, weak_factory_.GetWeakPtr())); InitializePlayer(url_, frame_->document().firstPartyForCookies(), true, demuxer_client_id); } } else { info_loader_.reset( new MediaInfoLoader( url, cors_mode, base::Bind(&WebMediaPlayerAndroid::DidLoadMediaInfo, weak_factory_.GetWeakPtr()))); info_loader_->Start(frame_); } ...... }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
类型为WebMediaPlayerAndroid的播放器只能处理普通URL描述的媒体流,以及BLOB协议描述的类型为Media Source的媒体流,不能处理类型为Media Stream的媒体流(需要由WebRTC处理)。
类型为Media Source的媒体流数据直接从指定的Media Source获得。WebMediaPlayerAndroid类的成员函数load将该Media Source封装在一个MediaSourceDelegate对象中,然后通过这个MediaSourceDelegate对象来获得要播放的视频的元数据。有了要播放的视频的元数据之后,就可以调用WebMediaPlayerAndroid类的成员函数InitializePlayer初始化当前正在处理的播放器。
我们假设当前要播放的视频的URL是一个普通的URL,它描述的媒体流的元数据要通过一个MediaInfoLoader对象从网络上下载回来。下载完成后,WebMediaPlayerAndroid类的成员函数DidLoadMediaInfo会被调用,它的实现如下所示:
void WebMediaPlayerAndroid::DidLoadMediaInfo( MediaInfoLoader::Status status, const GURL& redirected_url, const GURL& first_party_for_cookies, bool allow_stored_credentials) { ...... InitializePlayer( redirected_url, first_party_for_cookies, allow_stored_credentials, 0); ...... }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员函数DidLoadMediaInfo会用下载回来的视频元数据初始化当前正在处理的播放器。这同样是通过调用WebMediaPlayerAndroid类的成员函数InitializePlayer进行的,如下所示:
void WebMediaPlayerAndroid::InitializePlayer( const GURL& url, const GURL& first_party_for_cookies, bool allow_stored_credentials, int demuxer_client_id) { ...... player_manager_->Initialize( player_type_, player_id_, url, first_party_for_cookies, demuxer_client_id, frame_->document().url(), allow_stored_credentials); ...... }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
从前面的分析可以知道,WebMediaPlayerAndroid类的成员变量player_manager_指向的是一个RendererMediaPlayerManager对象。WebMediaPlayerAndroid类的成员函数InitializePlayer调用这个RendererMediaPlayerManager对象的成员函数Initialize对当前正在处理的播放器进行初始化,如下所示:
void RendererMediaPlayerManager::Initialize( MediaPlayerHostMsg_Initialize_Type type, int player_id, const GURL& url, const GURL& first_party_for_cookies, int demuxer_client_id, const GURL& frame_url, bool allow_credentials) { MediaPlayerHostMsg_Initialize_Params media_player_params; media_player_params.type = type; media_player_params.player_id = player_id; media_player_params.demuxer_client_id = demuxer_client_id; media_player_params.url = url; media_player_params.first_party_for_cookies = first_party_for_cookies; media_player_params.frame_url = frame_url; media_player_params.allow_credentials = allow_credentials; Send(new MediaPlayerHostMsg_Initialize(routing_id(), media_player_params)); }这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。
RendererMediaPlayerManager对象的成员函数Initialize向Browser进程发送一个类型为MediaPlayerHostMsg_Initialize的IPC消息,用来请求Browser进程为当前正在处理的类型为WebMediaPlayerAndroid的播放器创建一个由Android平台实现的播放器。
Browser进程是通过MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived接收类型为MediaPlayerHostMsg_Initialize的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_Initialize, GetMediaPlayerManager(render_frame_host), ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; }这个函数定义在文件external/chromium_org/content/browser/media/media_web_contents_observer.cc中。
MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived会将类型为MediaPlayerHostMsg_Initialize的IPC消息分发给运行在Browser进程中的一个BrowserMediaPlayerManager对象的成员函数OnInitialize处理。这个BrowserMediaPlayerManager对象负责管理在Browser进程中创建的播放器实例,它与运行在Render进程中的RendererMediaPlayerManager对象是对应的,不过后者用来管理在Render进程中创建的播放器实例。
BrowserMediaPlayerManager类的成员函数OnInitialize的实现如下所示:
void BrowserMediaPlayerManager::OnInitialize( const MediaPlayerHostMsg_Initialize_Params& media_player_params) { ...... MediaPlayerAndroid* player = CreateMediaPlayer( media_player_params, host->GetBrowserContext()->IsOffTheRecord(), this, host->browser_demuxer_android()); ...... AddPlayer(player); }
这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。
BrowserMediaPlayerManager类的成员函数OnInitialize主要是调用成员函数CreateMediaPlayer创建一个类型为MediaPlayerBridge的播放器实例,并且调用另外一个成员函数AddPlayer将这个播放器实例保存在内部。
BrowserMediaPlayerManager类的成员函数CreateMediaPlayer的实现如下所示:
MediaPlayerAndroid* BrowserMediaPlayerManager::CreateMediaPlayer( const MediaPlayerHostMsg_Initialize_Params& media_player_params, bool hide_url_log, MediaPlayerManager* manager, BrowserDemuxerAndroid* demuxer) { switch (media_player_params.type) { case MEDIA_PLAYER_TYPE_URL: { const std::string user_agent = GetContentClient()->GetUserAgent(); MediaPlayerBridge* media_player_bridge = new MediaPlayerBridge( media_player_params.player_id, media_player_params.url, media_player_params.first_party_for_cookies, user_agent, hide_url_log, manager, base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesRequested, weak_ptr_factory_.GetWeakPtr()), base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesReleased, weak_ptr_factory_.GetWeakPtr()), media_player_params.frame_url, media_player_params.allow_credentials); BrowserMediaPlayerManager* browser_media_player_manager = static_cast<BrowserMediaPlayerManager*>(manager); ContentViewCoreImpl* content_view_core_impl = static_cast<ContentViewCoreImpl*>(ContentViewCore::FromWebContents( browser_media_player_manager->web_contents_)); if (!content_view_core_impl) { // May reach here due to prerendering. Don't extract the metadata // since it is expensive. // TODO(qinmin): extract the metadata once the user decided to load // the page. browser_media_player_manager->OnMediaMetadataChanged( media_player_params.player_id, base::TimeDelta(), 0, 0, false); } else if (!content_view_core_impl->ShouldBlockMediaRequest( media_player_params.url)) { media_player_bridge->Initialize(); } return media_player_bridge; } case MEDIA_PLAYER_TYPE_MEDIA_SOURCE: { return new MediaSourcePlayer( media_player_params.player_id, manager, base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesRequested, weak_ptr_factory_.GetWeakPtr()), base::Bind(&BrowserMediaPlayerManager::OnMediaResourcesReleased, weak_ptr_factory_.GetWeakPtr()), demuxer->CreateDemuxer(media_player_params.demuxer_client_id), media_player_params.frame_url); } } NOTREACHED(); return NULL; }
这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。
BrowserMediaPlayerManager类的成员函数CreateMediaPlayer同样是只会为普通URL描述的视频以及类型为Media Source的视频创建播放器。这里我们只考虑普通URL描述的视频的情况。这时候BrowserMediaPlayerManager类的成员函数CreateMediaPlayer会创建一个类型为MediaPlayerBridge的播放器,并且在视频所加载在的网页可见的情况下,调用它的成员函数Initialize对它进行初始化,也就是获取视频元数据。如果视频所加载在的网页当前不可见,那么获取视频元数据的操作可以延后执行,也就是等到下次可见时再执行。
我们假设视频所加载在的网页当前是可见的。接下来我们就继续分析类型为MediaPlayerBridge的播放器的初始化过程,也就是MediaPlayerBridge类的成员函数Initialize的实现,如下所示:
void MediaPlayerBridge::Initialize() { ...... media::MediaResourceGetter* resource_getter = manager()->GetMediaResourceGetter(); ...... // Start extracting the metadata immediately if the request is anonymous. // Otherwise, wait for user credentials to be retrieved first. if (!allow_credentials_) { ExtractMediaMetadata(url_.spec()); return; } resource_getter->GetCookies(url_, first_party_for_cookies_, base::Bind(&MediaPlayerBridge::OnCookiesRetrieved, weak_factory_.GetWeakPtr())); }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
MediaPlayerBridge类的成员变量allow_credentials_是一个布尔变量。当它的值等于false的时候,表示视频元数据可以通过匿名方式获取。在这种情况下,MediaPlayerBridge类的成员函数Initialize就会直接调用另外一个成员函数ExtractMediaMetadata获取视频元数据。
当MediaPlayerBridge类的成员变量allow_credentials_的值等于true的时候,表示视频元数据要有凭证才可以获取。在这种情况下,MediaPlayerBridge类的成员函数Initialize先通过调用一个MediaResourceGetter对象的成员函数GetCookies获取该凭证。获得了这个凭证之后,MediaPlayerBridge类的成员函数OnCookiesRetrieved会被回调。
MediaPlayerBridge类的成员函数OnCookiesRetrieved被回调用的时候,它同样是会调用成员函数ExtractMediaMetadata去获取视频元数据。为简单起见,我们假设视频元数据可以通过匿名方式获取。因此,接下来我们就继续分析MediaPlayerBridge类的成员函数ExtractMediaMetadata的实现,以及了解视频元数据获取的过程,如下所示:
void MediaPlayerBridge::ExtractMediaMetadata(const std::string& url) { int fd; int64 offset; int64 size; if (InterceptMediaUrl(url, &fd, &offset, &size)) { manager()->GetMediaResourceGetter()->ExtractMediaMetadata( fd, offset, size, base::Bind(&MediaPlayerBridge::OnMediaMetadataExtracted, weak_factory_.GetWeakPtr())); } else { manager()->GetMediaResourceGetter()->ExtractMediaMetadata( url, cookies_, user_agent_, base::Bind(&MediaPlayerBridge::OnMediaMetadataExtracted, weak_factory_.GetWeakPtr())); } }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
MediaPlayerBridge类的成员函数ExtractMediaMetadata首先是调用成员函数InterceptMediaUrl检查当前要播放的视频是否已经存在本地,也就是之前下载过。如果是的话,就会直接从该文件读取视频元数据回来。否则的话,就会通过网络请求视频元数据。无论是哪一种方式,一旦得到视频元数据之后,就会回调用MediaPlayerBridge类的成员函数OnMediaMetadataExtracted进行后续处理。
MediaPlayerBridge类的成员函数OnMediaMetadataExtracted的实现如下所示:
void MediaPlayerBridge::OnMediaMetadataExtracted( base::TimeDelta duration, int width, int height, bool success) { if (success) { duration_ = duration; width_ = width; height_ = height; } manager()->OnMediaMetadataChanged( player_id(), duration_, width_, height_, success); }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
获取的视频元数据包括视频持续时间、宽度以及高度。这些数据会分别保存在MediaPlayerBridge类的成员变量duration_、width_和height_中。
MediaPlayerBridge类的成员函数OnMediaMetadataExtracted最后还会通知Browser进程中的BrowserMediaPlayerManager对象,当前正在处理的播放器已经获得了视频元数据。这个BrowserMediaPlayerManager对象是通过调用MediaPlayerBridge类的成员函数manager获得的,并且是通过调用它的成员函数OnMediaMetadataChanged对它进行通知的。
BrowserMediaPlayerManager类的成员函数OnMediaMetadataChanged的实现如下所示:
void BrowserMediaPlayerManager::OnMediaMetadataChanged( int player_id, base::TimeDelta duration, int width, int height, bool success) { Send(new MediaPlayerMsg_MediaMetadataChanged( RoutingID(), player_id, duration, width, height, success)); ...... }这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。
BrowserMediaPlayerManager类的成员函数OnMediaMetadataChanged主要是向Render进程发送一个类型为MediaPlayerMsg_MediaMetadataChanged的IPC消息,通知为ID为player_id的播放器已经获得了要播放的视频的元数据。
Render进程是通过RendererMediaPlayerManager类的成员函数OnMessageReceived接收类型为MediaPlayerMsg_MediaMetadataChanged的IPC消息的,如下所示:
bool RendererMediaPlayerManager::OnMessageReceived(const IPC::Message& msg) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(RendererMediaPlayerManager, msg) IPC_MESSAGE_HANDLER(MediaPlayerMsg_MediaMetadataChanged, OnMediaMetadataChanged) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; }这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。
RendererMediaPlayerManager类的成员函数OnMessageReceived将类型为MediaPlayerMsg_MediaMetadataChanged的IPC消息分发给另外一个成员函数OnMediaMetadataChanged处理,如下所示:
void RendererMediaPlayerManager::OnMediaMetadataChanged( int player_id, base::TimeDelta duration, int width, int height, bool success) { WebMediaPlayerAndroid* player = GetMediaPlayer(player_id); if (player) player->OnMediaMetadataChanged(duration, width, height, success); }这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。
RendererMediaPlayerManager类的成员函数OnMediaMetadataChanged首先根据参数player_id获得之前创建的一个类型为WebMediaPlayerAndroid的播放器,然后调用这个播放器的成员函数OnMediaMetadataChanged通知它,当前要播放的视频元数据已经获取回来了。
WebMediaPlayerAndroid类的成员函数OnMediaMetadataChanged的实现如下所示:
void WebMediaPlayerAndroid::OnMediaMetadataChanged( const base::TimeDelta& duration, int width, int height, bool success) { ...... if (ready_state_ != WebMediaPlayer::ReadyStateHaveEnoughData) { UpdateReadyState(WebMediaPlayer::ReadyStateHaveMetadata); UpdateReadyState(WebMediaPlayer::ReadyStateHaveEnoughData); } ...... }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员变量read_state_描述的播放器的状态。如果它的状态不等于WebMediaPlayer::ReadyStateHaveEnoughData,即还没有足够的数据开始播放视频,那么WebMediaPlayerAndroid类的成员函数OnMediaMetadataChanged此时就会先将状态更改为WebMediaPlayer::ReadyStateHaveMetadata,然后再更改为WebMediaPlayer::ReadyStateHaveEnoughData。这都是通过调用WebMediaPlayerAndroid类的成员函数UpdateReadyState实现的。
WebMediaPlayerAndroid类的成员函数UpdateReadyState的实现如下所示:
void WebMediaPlayerAndroid::UpdateReadyState( WebMediaPlayer::ReadyState state) { ready_state_ = state; client_->readyStateChanged(); }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员函数UpdateReadyState除了将Content模块中的播放器的状态设置为参数state描述的值之外,还会通知WebKit层中的播放器,也就是一个WebMediaPlayerClientImpl对象,它的状态发生了变化。
上述WebMediaPlayerClientImpl对象保存在WebMediaPlayerAndroid类的成员变量client_中,通过调用它的成员函数readyStateChanged可以通知它,播放器状态发生了变化,如下所示:
void WebMediaPlayerClientImpl::readyStateChanged() { m_client->mediaPlayerReadyStateChanged(); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。
从前面的分析可以知道,WebMediaPlayerClientImpl类的成员变量m_client指向的是一个HTMLMediaElement对象。这个HTMLMediaElement对象描述的就是要播放视频的<video>标签。WebMediaPlayerClientImpl类的成员函数readyStateChanged调用这个HTMLMediaElement对象的成员函数mediaPlayerReadyStateChanged通知它,播放器状态发生了变化,如下所示:
void HTMLMediaElement::mediaPlayerReadyStateChanged() { setReadyState(static_cast<ReadyState>(webMediaPlayer()->readyState())); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
HTMLMediaElement类的成员函数mediaPlayerReadyStateChanged首先获得Content模块中的播放器的当前状态,然后再调用另外一个成员函数setReadyState将这个状态保存在内部。从前面的分析可以知道,Content模块中的播放器的当前状态为WebMediaPlayer::ReadyStateHaveEnoughData,对应于在WebKit中定义的状态HAVE_ENOUGH_DATA。
HTMLMediaElement类的成员函数setReadyState的实现如下所示:
void HTMLMediaElement::setReadyState(ReadyState state) { ...... bool tracksAreReady = textTracksAreReady(); ...... if (tracksAreReady) m_readyState = newState; else { // If a media file has text tracks the readyState may not progress beyond HAVE_FUTURE_DATA until // the text tracks are ready, regardless of the state of the media file. if (newState <= HAVE_METADATA) m_readyState = newState; else m_readyState = HAVE_CURRENT_DATA; } ...... updatePlayState(); ...... }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
HTMLMediaElement类的成员变量m_readyState用来描述WebKit层中的播放器的状态。HTMLMediaElement类的成员函数setReadyState首先调用成员函数textTracksAreReady检查当前要播放的视频是否存在类型为Text的Track,也就是字幕。如果存在,并且这些Track都已经Ready,那么它的返回值就会等于true。另一方面,如果不存在类型为Text的Track,那么调用HTMLMediaElement类的成员函数textTracksAreReady得到的返回值也会等于true。
在HTMLMediaElement类的成员函数textTracksAreReady的返回值等于true的情况下,HTMLMediaElement类的成员函数setReadyState才会将WebKit层中的播放器的状态设置为参数state的值,也就是将它的状态保持与Content模块中的播放器一致。否则的话,至多会将WebKit层中的播放器的状态设置为HAVE_CURRENT_DATA。这个HAVE_CURRENT_DATA状态不能让播放器开始播放视频。
我们假设当前要播放的视频不存在类型为Text的Track。因此,这时候WebKit层中的播放器的状态将会被设置为HAVE_ENOUGH_DATA,也就是HTMLMediaElement类的成员变量m_readyState会被设置为HAVE_ENOUGH_DATA。
HTMLMediaElement类的成员函数setReadyState最后调用成员函数updatePlayState开始播放视频,如下所示:
void HTMLMediaElement::updatePlayState() { ...... bool shouldBePlaying = potentiallyPlaying(); bool playerPaused = m_player->paused(); ..... if (shouldBePlaying) { ...... if (playerPaused) { ...... m_player->play(); } ...... } ...... }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。
HTMLMediaElement类的成员函数updatePlayState首先调用另外一个成员函数potentiallyPlaying检查WebKit层中的播放器的状态。如果WebKit层中的播放器的状态表明它已经获得了足够的视频数据,并且视频还没有播放结束,以及没有被用户中止,也没有出现错误等,那么HTMLMediaElement类的成员函数potentiallyPlaying的返回值就会等于true。在这种情况下,如果Content模块中的播放器目前处于暂停状态,那么就可以通知它开始播放视频了。这是通过调用HTMLMediaElement类的成员变量m_player指向的一个WebMediaPlayerClientImpl对象的成员函数play实现的。
从前面的分析可以知道,在我们这个情景中,通知Content模块中的播放器开始播放视频的条件是满足的,因此接下来我们就继续分析WebMediaPlayerClientImpl类的成员函数play的实现,如下所示:
void WebMediaPlayerClientImpl::play() { if (m_webMediaPlayer) m_webMediaPlayer->play(); }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebMediaPlayerClientImpl.cpp中。
从前面的分析可以知道,WebMediaPlayerClientImpl类的成员变量m_webMediaPlayer指向的是一个WebMediaPlayerAndroid对象。这个WebMediaPlayerAndroid对象描述的就是Content模块中的播放器实例。WebMediaPlayerClientImpl类的成员函数play调用这个WebMediaPlayerAndroid对象的成员函数play通知它开始播放视频。
WebMediaPlayerAndroid类的成员函数play的实现如下所示:
void WebMediaPlayerAndroid::play() { ...... TryCreateStreamTextureProxyIfNeeded(); // There is no need to establish the surface texture peer for fullscreen // video. if (hasVideo() && needs_establish_peer_ && !player_manager_->IsInFullscreen(frame_)) { EstablishSurfaceTexturePeer(); } if (paused()) player_manager_->Start(player_id_); ...... }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员函数play首先调用成员函数TryCreateStreamTextureProxyIfNeeded检查当前正在处理的WebMediaPlayerAndroid对象是否已经创建过一个纹理对象。如果还没有创建,那么就会请求GPU进程进行创建。
WebMediaPlayerAndroid类的成员函数play接下来又检查是否需要将上述纹理对象封装为一个SurfaceTexture,然后再将该SurfaceTexture设置为Android平台的MediaPlayer的解码输出。在满足以下两个条件时,就需要进行封装和设置,也就是调用另外一个成员函数EstablishSurfaceTexturePeer:
1. 要播放的媒体包含有视频,即调用成员函数hasVideo得到的返回值等于true。
2. 要播放的视频不是全屏模式,这时候成员变量needs_establish_peer_的值等于true,以及调用调用成员变量player_manager_指向的RendererMediaPlayerManager对象的成员函数IsInFullscreen得到的返回值等于false。
在我们这个情景中,上述两个条件都是满足的。不过,WebMediaPlayerAndroid类的TryCreateStreamTextureProxyIfNeeded和EstablishSurfaceTexturePeer的实现我们在接下来的一篇文章中再详细分析。
WebMediaPlayerAndroid类的成员函数play最后检查当前正在处理的WebMediaPlayerAndroid对象描述的播放器是否处于暂停状态。如果是的话,那么就会调用成员变量player_manager_指向的RendererMediaPlayerManager对象的成员函数Start启动该播放器。
接下来我们就继续分析RendererMediaPlayerManager类的成员函数Start的实现,以便了解类型为WebMediaPlayerAndroid的播放器的启动过程,如下所示:
void RendererMediaPlayerManager::Start(int player_id) { Send(new MediaPlayerHostMsg_Start(routing_id(), player_id)); }这个函数定义在文件external/chromium_org/content/renderer/media/android/renderer_media_player_manager.cc中。
RendererMediaPlayerManager类的成员函数Start向Browser进程发送一个类型为MediaPlayerHostMsg_Start的IPC消息,用来请求启动ID值为player_id的播放器。
Browser进程通过MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived接收类型为MediaPlayerHostMsg_Start的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_Start, GetMediaPlayerManager(render_frame_host), BrowserMediaPlayerManager::OnStart) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; }这个函数定义在文件external/chromium_org/content/browser/media/media_web_contents_observer.cc中。
MediaWebContentsObserver类的成员函数OnMediaPlayerMessageReceived会将类型为MediaPlayerHostMsg_Start的IPC消息分发给运行在Browser进程中的一个BrowserMediaPlayerManager对象的成员函数OnStart处理,如下所示:
void BrowserMediaPlayerManager::OnStart(int player_id) { MediaPlayerAndroid* player = GetPlayer(player_id); ...... player->Start(); ...... }这个函数定义在文件external/chromium_org/content/browser/media/android/browser_media_player_manager.cc中。
BrowserMediaPlayerManager类的成员函数OnStart首先调用成员函数GetPlayer获得与参数player_id对应的一个MediaPlayerBridge对象,然后调用这个MediaPlayerBridge对象的成员函数Start启动它所描述的播放器。注意,这里获得的MediaPlayerBridge对象使用一个类型为MediaPlayerAndroid的指针引用,这是因为MediaPlayerBridge类是从类MediaPlayerAndroid继承下来的。
接下来我们就继续分析MediaPlayerBridge类的成员函数Start的实现,如下所示:
void MediaPlayerBridge::Start() { if (j_media_player_bridge_.is_null()) { pending_play_ = true; Prepare(); } else { if (prepared_) StartInternal(); else pending_play_ = true; } }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
MediaPlayerBridge类的成员变量j_media_player_bridge_是一个类型为jobject的引用,它是用来指向在Java层创建的一个MediaPlayerBridge对象的。如果这个MediaPlayerBridge对象还没有创建,那么MediaPlayerBridge类的成员变量j_media_player_bridge_的值就会等于NULL。这种情况说明我们还没有为<video>标签创建一个真正的播放器。这个真正在的播放器是由Android系统提供的。这时候就会调用另外一个成员函数Prepare在Java层创建这个由Android系统提供的播放器,并且让这个播放器准备就绪播放。
一旦在Java层就准好由Android系统提供的播放器,那么Chromium会通过JNI回到C/C++层,调用MediaPlayerBridge类的成员函数StartInternal记动该播放器,并且将MediaPlayerBridge类的成员变量prepared的值设置为true。
另一方面,如果此时已经在Java层准备就绪好Android系统提供的播放器,那么MediaPlayerBridge类的成员函数Start就会直接调用另外一个成员函数StartInternal启动该播放器。
还存在第三种情况,MediaPlayerBridge类的成员变量j_media_player_bridge_已经指向一个Java层的MediaPlayerBridge对象,但是还没有准备好Android系统提供的播放器。这时候MediaPlayerBridge类的成员函数Start就会将另外一个成员变量pending_play_的值设置为true,表示等到Android系统提供的播放器准备就绪后,要启动该播放器。
我们假设此时MediaPlayerBridge类的成员变量j_media_player_bridge_还没有指向一个Java层的MediaPlayerBridge对象,接下来MediaPlayerBridge类的成员函数Prepare就会被调用,它的实现如下所示:
void MediaPlayerBridge::Prepare() { ...... CreateJavaMediaPlayerBridge(); ...... SetDataSource(url_.spec()); }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
MediaPlayerBridge类的成员函数Prepare首先调用成员函数CreateJavaMediaPlayerBridge创建一个Java层的MediaPlayerBridge对象,然后再调用另外一个成员函数SetDataSource在Java层创建一个由Android系统实现的播放器,并且将要播放的视频的URL设置给该播放器。
MediaPlayerBridge类的成员函数CreateJavaMediaPlayerBridge的实现如下所示:
void MediaPlayerBridge::CreateJavaMediaPlayerBridge() { JNIEnv* env = base::android::AttachCurrentThread(); ...... j_media_player_bridge_.Reset(Java_MediaPlayerBridge_create( env, reinterpret_cast<intptr_t>(this))); ...... }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
MediaPlayerBridge类的成员函数CreateJavaMediaPlayerBridge通过JNI调用Java层的MediaPlayerBridge类的静态成员函数create创建了一个Java层的MediaPlayerBridge对象,并且保存在成员变量j_media_player_bridge_中。
Java层的MediaPlayerBridge类的静态成员函数create的实现如下所示:
public class MediaPlayerBridge { ...... private long mNativeMediaPlayerBridge; @CalledByNative private static MediaPlayerBridge create(long nativeMediaPlayerBridge) { return new MediaPlayerBridge(nativeMediaPlayerBridge); } protected MediaPlayerBridge(long nativeMediaPlayerBridge) { mNativeMediaPlayerBridge = nativeMediaPlayerBridge; } ...... }这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java中。
从这里就可以看到,Java层的MediaPlayerBridge类的静态成员函数create创建的是一个Java层的MediaPlayerBridge对象。在创建这个MediaPlayerBridge对象对象的时候,需要使用到参数nativeMediaPlayerBridge。
从前面的调用过程可以知道,参数nativeMediaPlayerBridge描述的是C++层的一个MediaPlayerBridge对象的地址。这个地址会保存在Java层创建的MediaPlayerBridge对象的成员变量mNativeMediaPlayerBridge中。通过这种方式,就可以让Java层的MediaPlayerBridge与C++层的MediaPlayerBridge对象一一对应起来。
这一步执行完成后,回到前面分析C++层的MediaPlayerBridge类的成员函数Prepare中,它接下来调用另外一个成员函数SetDataSource在Java层创建一个由Android系统提供的播放器,以及将要播放的视频的URL设置给该播放器,如下所示:
void MediaPlayerBridge::SetDataSource(const std::string& url) { ...... JNIEnv* env = base::android::AttachCurrentThread(); ...... int fd; int64 offset; int64 size; if (InterceptMediaUrl(url, &fd, &offset, &size)) { if (!Java_MediaPlayerBridge_setDataSourceFromFd( env, j_media_player_bridge_.obj(), fd, offset, size)) { ...... return; } } else { // Create a Java String for the URL. ScopedJavaLocalRef<jstring> j_url_string = ConvertUTF8ToJavaString(env, url); ...... ScopedJavaLocalRef<jstring> j_cookies = ConvertUTF8ToJavaString( env, cookies_); ScopedJavaLocalRef<jstring> j_user_agent = ConvertUTF8ToJavaString( env, user_agent_); if (!Java_MediaPlayerBridge_setDataSource( env, j_media_player_bridge_.obj(), j_context, j_url_string.obj(), j_cookies.obj(), j_user_agent.obj(), hide_url_log_)) { ...... return; } } ...... if (!Java_MediaPlayerBridge_prepareAsync(env, j_media_player_bridge_.obj())) OnMediaError(MEDIA_ERROR_FORMAT); }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
C++层的MediaPlayerBridge类的成员函数SetDataSource首先调用成员函数InterceptMediaUrl检查参数url描述的URL对应的视频是否已经在本地以文件的形式存在。如果存在,那么就通过JNI调用Java层的MediaPlayerBridge类的成员函数setDataSourceFromFd将该视频文件设置为Android系统提供的播放器的视频源。
另一方面,如果参数url描述的URL对应的视频在本地不存在,那么C++层的MediaPlayerBridge类的成员函数SetDataSource就会通过JNI调用Java层的MediaPlayerBridge类的成员函数setDataSource,以便将要播放的视频的URL设置给Android系统提供的播放器。一起设置给Android系统提供的播放器的数据还包括当前网页所属网站的Cookie,以及用户凭证等。这是因为Android系统提供的播放器从服务器下载视频时,可能需要用到这些信息。
设置好Android系统提供的播放器的视频源之后,C++层的MediaPlayerBridge类的成员函数SetDataSource最后还会调用Java层的MediaPlayerBridge类的成员函数prepareAsync,目的是让Android系统提供的播放器就准备就绪播放。
我们假设参数url描述的URL对应的视频在本地不存在,接下来Java层的MediaPlayerBridge类的成员函数setDataSource就会被调用,如下所示:
public class MediaPlayerBridge { ...... @CalledByNative protected boolean setDataSource( Context context, String url, String cookies, String userAgent, boolean hideUrlLog) { Uri uri = Uri.parse(url); HashMap<String, String> headersMap = new HashMap<String, String>(); ...... if (!TextUtils.isEmpty(cookies)) headersMap.put("Cookie", cookies); if (!TextUtils.isEmpty(userAgent)) headersMap.put("User-Agent", userAgent); ...... try { getLocalPlayer().setDataSource(context, uri, headersMap); return true; } catch (Exception e) { return false; } } ...... }这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java中。
MediaPlayerBridge类的成员函数setDataSource首先将参数url描述的URL封装成一个URI对象,接着又将参数cookies和userAgent描述的Cookie和用户凭证保存一个Hash Map中。
准备好上述视频源相关的信息后,MediaPlayerBridge类的成员函数setDataSource就调用成员函数getLocalPlayer获得一个由Android系统提供的播放器,如下所示:
public class MediaPlayerBridge { ...... protected MediaPlayer getLocalPlayer() { if (mPlayer == null) { mPlayer = new MediaPlayer(); } return mPlayer; } ...... }这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java中。
从这里就可以看到,MediaPlayerBridge类的成员函数getLocalPlayer返回的是成员变量mPlayer指向的一个MediaPlayer对象。这个MediaPlayer对象描述的就是Android系统提供的播放器。如果这个播放器还没有创建,那么MediaPlayerBridge类的成员函数getLocalPlayer会先创建。
回到MediaPlayerBridge类的成员函数setDataSource中,接下来它就会将前面准备好的视频源信息设置给Android系统提供的播放器,这是通过调用MediaPlayer类的成员函数setDataSource实现的。这是一个Android SDK接口,它的具体用法可以参考SDK文档。
这一步执行完成后,回到C++层的MediaPlayerBridge类的成员函数Prepare中,它接下来继续调用Java层的MediaPlayerBridge类的成员函数prepareAsync让前面获得的由Android系统提供的播放器进入就绪状态,如下所示:
public class MediaPlayerBridge { ...... @CalledByNative protected boolean prepareAsync() { try { getLocalPlayer().prepareAsync(); } catch (IllegalStateException e) { Log.e(TAG, "Unable to prepare MediaPlayer.", e); return false; } return true; } ...... }
这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java中。
MediaPlayerBridge类的成员函数prepareAsync调用MediaPlayer类的成员函数prepareAsync让前面创建的由Android系统提供的播放器异步进入就绪状态。
C++层的MediaPlayerBridge类有一个成员变量listener_,它指向C++层的一个MediaPlayerListener对象。调用C++层的MediaPlayerBridge类的成员函数SetMediaPlayerListener可以为这个MediaPlayerListener对象在Java层创建一个对应的MediaPlayerListener对象,如下所示:
void MediaPlayerBridge::SetMediaPlayerListener() { jobject j_context = base::android::GetApplicationContext(); ...... listener_->CreateMediaPlayerListener(j_context, j_media_player_bridge_.obj()); }这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java中。
C++层的MediaPlayerBridge类的成员函数SetMediaPlayerListener通过调用C++层的MediaPlayerListener类的成员函数CreateMediaPlayerListener为其成员变量listener_创建一个Java层的MediaPlayerListener对象,如下所示:
void MediaPlayerListener::CreateMediaPlayerListener( jobject context, jobject media_player_bridge) { JNIEnv* env = AttachCurrentThread(); ...... j_media_player_listener_.Reset( Java_MediaPlayerListener_create( env, reinterpret_cast<intptr_t>(this), context, media_player_bridge)); }这个函数定义在文件external/chromium_org/media/base/android/media_player_listener.cc中。
这个Java层的MediaPlayerListener对象是通过调用Java层的MediaPlayerListener类的静态成员函数create创建的,并且保存在C++层的MediaPlayerListener类的成员变量j_media_player_listener_中。
Java层的MediaPlayerListener类的静态成员函数create的实现如下所示:
class MediaPlayerListener implements MediaPlayer.OnPreparedListener, ...... // Used to determine the class instance to dispatch the native call to. private long mNativeMediaPlayerListener = 0; ...... private MediaPlayerListener(long nativeMediaPlayerListener, Context context) { mNativeMediaPlayerListener = nativeMediaPlayerListener; ...... } @CalledByNative private static MediaPlayerListener create(long nativeMediaPlayerListener, Context context, MediaPlayerBridge mediaPlayerBridge) { final MediaPlayerListener listener = new MediaPlayerListener(nativeMediaPlayerListener, context); ...... mediaPlayerBridge.setOnPreparedListener(listener); ...... return listener; } ...... }
这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerListener.java中。
从前面的调用过程可以知道,参数nativeMediaPlayerListener描述的是C++层的一个MediaPlayerListener对象的地址。MediaPlayerListener类的静态成员函数create使用这个地址创建了一个Java层的MediaPlayerListener对象,并且将该地址保存创建出来的Java层的MediaPlayerListener对象的成员变量mNativeMediaPlayerListener中。通过这种方式,就使得C++层的MediaPlayerListener对象与Java层的MediaPlayerListener对象一一对应。
参数mediaPlayerBridge指向的是一个Java层的MediaPlayerBridge对象。这个MediaPlayerBridge是我们在前面创建的,它内部包含了一个由Android系统提供的播放器。MediaPlayerListener类的静态成员函数create调用这个MediaPlayerBridge对象的成员函数setOnPreparedListener,用来监听它内部的播放器的准备就绪事件,如下所示:
public class MediaPlayerBridge { ...... protected void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) { getLocalPlayer().setOnPreparedListener(listener); } ...... }
这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java中。
一旦MediaPlayerBridge类内部维护的播放器进入准备就绪状态,参数listener指向的MediaPlayerListener对象就会获得通知,表现为它的成员函数onPrepared被调用,如下所示:
class MediaPlayerListener implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnVideoSizeChangedListener, MediaPlayer.OnErrorListener, AudioManager.OnAudioFocusChangeListener { ...... @Override public void onPrepared(MediaPlayer mp) { nativeOnMediaPrepared(mNativeMediaPlayerListener); } ...... }这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerListener.java中。
MediaPlayerListener类的成员函数onPrepared通过调用另外一个成员函数nativeOnMediaPrepared通知成员变量mNativeMediaPlayerListener描述的一个C++层的MediaPlayerListener对象,前面在Java层创建的由Android系统提供的播放器已经进入准备就绪状态。
MediaPlayerListener类的成员函数nativeOnMediaPrepared是一个JNI函数,它是由C++层的函数Java_com_android_org_chromium_media_MediaPlayerListener_nativeOnMediaPrepared实现,如下所示:
__attribute__((visibility("default"))) void Java_com_android_org_chromium_media_MediaPlayerListener_nativeOnMediaPrepared(JNIEnv* env, jobject jcaller, jlong nativeMediaPlayerListener) { MediaPlayerListener* native = reinterpret_cast<MediaPlayerListener*>(nativeMediaPlayerListener); CHECK_NATIVE_PTR(env, jcaller, native, "OnMediaPrepared"); return native->OnMediaPrepared(env, jcaller); }这个函数定义在文件out/target/product/generic/obj/GYP/shared_intermediates/media/jni/MediaPlayerListener_jni.h中。
函数Java_com_android_org_chromium_media_MediaPlayerListener_nativeOnMediaPrepared首先将参数nativeMediaPlayerListener转换为一个C++层的MediaPlayerListener对象,然后再调用这个MediaPlayerListener对象的成员函数OnMediaPrepared,用来通知它前面在Java层创建的由Android系统提供的播放器已经进入准备就绪状态,如下所示:
void MediaPlayerListener::OnMediaPrepared( JNIEnv* /* env */, jobject /* obj */) { task_runner_->PostTask(FROM_HERE, base::Bind( &MediaPlayerBridge::OnMediaPrepared, media_player_)); }这个函数定义在文件external/chromium_org/media/base/android/media_player_listener.cc中。
MediaPlayerListener类的成员变量task_runner_描述的是Browser进程主线程的消息队列,MediaPlayerListener的成员函数OnMediaPrepared向这个消息队列发送一个Task,这个Task绑定了MediaPlayerBridge类的成员函数OnMediaPrepared。这意味着接下来MediaPlayerBridge类的成员函数OnMediaPrepared会在Browser进程主线程被调用,如下所示:
void MediaPlayerBridge::OnMediaPrepared() { ...... prepared_ = true; ...... if (pending_play_) { StartInternal(); pending_play_ = false; } ...... }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
MediaPlayerBridge类的成员函数OnMediaPrepared首先将成员变量prepared_的值设置为true,表示当前正在处理的MediaPlayerBridge对象所描述的播放器已经准备就绪。
MediaPlayerBridge类的成员函数OnMediaPrepared接下来还会检查另外一个成员变量pending_play_的值是否等于true。如果等于true,那么就说明当前正在处理的MediaPlayerBridge对象所描述的播放器正在等待被启动。这时候就可以调用MediaPlayerBridge类的另外一个成员函数StartInternal进行启动,如下所示:
void MediaPlayerBridge::StartInternal() { JNIEnv* env = base::android::AttachCurrentThread(); Java_MediaPlayerBridge_start(env, j_media_player_bridge_.obj()); ...... }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
MediaPlayerBridge类的另外一个成员函数StartInternal调用Java层的MediaPlayerBridge类的成员函数start启动由Android系统提供的播放器,如下所示:
public class MediaPlayerBridge { ...... @CalledByNative protected void start() { getLocalPlayer().start(); } ...... }这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java中。
MediaPlayerBridge类的成员函数start首先调用成员函数getLcoalPlayer获得一个MediaPlayer对象。这个MediaPlayer对象描述的即为由Android系统提供的播放器。有了这个MediaPlayer对象之后,MediaPlayerBridge类的成员函数start就调用它的成员函数start,这样就可以将它描述的播放器启动起来。
至此,我们就分析完成了Chromium为视频标签<video>创建播放器的过程。这个过程一共创建了四个播放器相关的接口:
1. WebMediaPlayerClientImpl
2. WebMediaPlayerAndroid
3. WebMediaBridgeBridge
4. MediaPlayer
其中,WebMediaPlayerClientImpl在WebKit中使用,它通过WebMediaPlayerAndroid实现播放器功能。WebMediaPlayerAndroid在Render进程中使用,它通过WebMediaBridgeBridge实现播放器功能。WebMediaBridgeBridge在Browser进程中使用,它通过MediaPlayer实现播放器功能。
MediaPlayer是Android系统提供的播放器实现,这意味着在Chromium for Android上,网页中的<video>标签指定的视频是由Android系统提供的播放器进行播放的。在非全屏模式下,视频的内容需要作为网页的一部分进行显示。这意味着播放器需要将解码后得到的视频图像交回给Chromium,然后Chromium再将这些视频图像与网页的其它元素一起渲染到屏幕中去。在接下来一篇文章中,我们就详细分析这个渲染过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。