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

发表于 5年以前  | 总阅读数:2847 次

Chromium是通过WebKit解析网页内容的。当WebKit遇到<video>标签时,就会创建一个播放器实例。WebKit是平台无关的,而播放器实现是平台相关的。因此,WebKit并没有自己实现播放器,而仅仅是创建一个播放器接口。通过这个播放器接口,可以使用平台提供的播放器来播放视频的内容。这就简化了Chromium对视频标签的支持。本文接下来就分析Chromium为视频标签创建播放器的过程。

以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>标签通过子元素指定要播放的视频的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,还是从它的子元素获得要加载的视频的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协议地址,然后再将该地址设置为一个标签的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 ExtensionsMedia 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>标签创建一个真正的播放器。%

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录