Webkit中HTML5 Video的实现分析-HTMLMediaElement & MediaPlayer

以下为针对Webkit中HTML5 Video实现原理的分析和学习总结。至于其中的UML图档,可以到下面的github链接中下载。

首先预览一下Webkit中和Video相关的主要的类的层次结构:


其中主要的类为HTMLMediaElement, MediaPlayer以及RenderVideo(左下角).

HTMLMediaElement代表了DOM结点,其继承关系可以追溯到Node,也就是DOM的结点。网页中Video/Audio元件的解析就是由它的创建开始。JavaScript的视频操作API也是以它为对象的。

MediaPlayer代表了媒体文件的播放器功能。它起得是一个桥接(bridge mode)的功能,具体的实现交由继承自MediaPlayerPrivateInterface的类来完成。也就是说,真正实现一个播放控制是由MediaPlayerPrivate来完成的。扩展播放器也要从这部分入手。

RenderMediaRenderVideo代表的是浏览器生成的渲染树中的结点,负责处理绘制相关的工作(如位置、大小及重绘等)。它们的继承关系可以追溯到RenderObject,其中还有一个层次是RenderReplaced,这里有一个Replaced Element(可替换组件)的概念,查一下HTML的资料就可以了。

真正要展现它们的类图,其层次是非常多的。所以只是按需展开就可以了。

 

根据学习的原则,我们需要逐个突破。我们先来考察一下HTMLMediaElement和MediaPlayer的互动关系。

首先,我们要从HTMLMediaElement对视频播放涉及的状态有个基本了解,下面是个简要的播放状态图:

 

HTMLMediaElement还定了一个ReadyState来表示数据状态:

它提供setReadyState方法供MediaPlayerPrivate等调用来改变相应的状态。有关Ready States可以参考:

   4.8.10.7 Ready States

 

当创建一个HTMLMediaElement元件后,在解析节点属性时会调用parseAttribute方法:

上图乱码是因去除Visual Paradigm的浮水印产生乱码,中文是"加载相应的音频或视频"。这里调用scheduleLoad就会触发Webkit创建一个播放器了。也就是MediaPlayer的创建。但MediaPlayer的创建的时机很多,因为页面有多种方式可以新添加一个video标签或者src标签,又或者页面的取消加载后的恢复等多种情况。下面就列出可能调用MediaPlayer::Create的情况:

对应的,下面释放MediaPlayer的时机:


MediaPlayer会调用MediaPlayerPrivate析构释放已创建的播放器。

而播放触发的条件也比较多,可以是指autoplay属性的视频,也可以是JavaScript执行了play()(load指令有些不同)等。如下图所示:

 

其中UML使用Visual Paradigm绘制,可以到下面的GitHub下载:

   https://github.com/HorkyChen/WebKit-Video-Documentation

 

MediaPlayerFactory

MediaPlayerFactory像是一个解码库一样,初始化时,各个可用的播放器(MediaPlayerPrivate)向它注册。当需要解码时,由它给出一个合适的播放器(呼叫MediaPlayerPrivate::constructor,MediaPlayerPrivate::create)创建一个实例[MediaPlayer:: loadWithNextMediaEngine执行])。所以MediaPlayerMediaPlayerPrivate应当是一对一的关系。

installedMediaEngines()会有一个静态的installed engines向量(Vector<MediaPlayerFactory*>),依据不同的OS,调用不同的MediaPlayerPrivateXXXregisterMediaEngine来注册新的Media Engine以支持不同的编码。如下图所示。

bestMediaEngineForTypeAndCodecs()在当视频的MIME type指定后,在MediaPlayer::loadWithNextMediaEngine中会调用它来获取合适的engine进行播放,否则直接使用nextMediaEngine()来获得播放有的engine.

详细的代码在MediaPlayer.cpp中。

 

以下是MediaPlayerPrivateQtsupportsType的代码片段:

  1. MediaPlayer::SupportsType MediaPlayerPrivateQt::supportsType(const String& mime, const String& codec)  
  2. {  
  3.     if (!mime.startsWith("audio/") && !mime.startsWith("video/"))  
  4.         return MediaPlayer::IsNotSupported;  
  5.    ……  
  6.    if (QMediaPlayer::hasSupport(mime, codecListTrimmed) >= QtMultimediaKit::ProbablySupported)  
  7.         return MediaPlayer::IsSupported;  
  8.   
  9.     return MediaPlayer::MayBeSupported;  
  10. }  

 


RenderVideo的绘制操作

RenderVideo在绘制时重点是得出正确的大小。主要函数包括calculateInstrinicSize来得出大小,updatePlayer依据当前缩放比例计算出新的大小尺寸,paintReplaced则依据metric进行绘制操作(得出一个rect,再传给player)。

下面是可能会涉及元素更新的流程:

 

其核心函数是calculateIntrinsicSize(): (所谓Intrinsic Size来自于CSS规范).

当paintReplaced函数(因为RenderVideo继承自RenderReplaced)拿到Instrinic Size后会着手计算绘制区域以便进行绘制:

实际的绘制都会交由Media Player处理,而Media Player则交由它的m_private,也就是具体的播放器来处理。

这里有两个重点:

  1.如果视频指定了poster,则绘制poster仅在RenderVideo中就完成了,并不会交给MediaPlayer控件来处理。(RenderVideo本身也是继承自RenderImage,很容易处理图片的问题。)

  2. 绘制MediaPlayer时,注意图层的问题。这会影响到网页的外观,有些网页为了美观,会在视频外加一些修饰用的框,这些框会因为paddings或margins计算上的问题与视频重叠。这时Media Player所在图层不同,表现来的外观就不一样了。(乐视的午间道在HTML5 Video模式就有这个问题. Chrome中将UA改为iPad就可以看到.)

 

转载请注明出处:http://blog.csdn.net/horkychen

明确主要类的生命周期有助于更好理解Webkit中视频的工作流程。下图展示了类间的引用关系。

如HTMLVideoElement与一个MediaPlayer对应,MediaPlayer与MediaPlayerPrivate一一对应,它们都是一个组合关系,当所有者自身被析构时,会自动释放引用的实例。这个过程都是使用OwnPtr智能指针来实现的。

  OwnPtr赋值操作的代码:

       OwnPtr& operator=(std::nullptr_t) { clear(); return *this; }

 

下面是HTMLMediaElement (HTMLVideoElement)中维护MediaPlayer的主要状态 (只关注于创建与释放过程):

当每次因为src属性变化或新增节点等情况,最终会呼叫createMediaPlayer释放原先的实例,再重新创建一个新的MediaPlayer实例。

 

最后是MediaPlayer维护MediaPlayerPrivate(m_private)的状态图。同上图一相似。

MediaPlayer同MediaPlayerPrivateInterface,再同播放控件的交互过程应当要简单、清晰。MediaPlayer通过自身的状态(Network States 和 Ready States)来控制操作的步骤。MediaPlayerPrivateInterface具体到不同的平台和视频格式,使用的具体的播放控件会不同。


这里仅贴一张时序图,其中没有特别区分HTMLVideoElement,使用的播放控件是Webkit默认的QuickTime组件。在调试时,最好要将UA调成iPad版本,不然服务器端可能提供的是Flash视频,就无从调试H5 Video了。

 


从代码中可以看到,WebKit将视频控件在WebCore中创建,而不是丢到前端实现。另外在加载完成后,readyState是HAVE_FUTURE_DATA后,会自动触发play()操作。

当视频元件是动态创建时,会使用scheduleLoad执行加载操作。而scheduleLoad是一个Timer,不会立即触发load函数,此时如果JS脚本执行了play()其实是无效的,实际执行的会由MediaPlayer调用NullMediaPlayer完成,当然也是无效的。只有当加载数据后,readyState改变,再由回馈到HTMLMediaElement,然后触发后面的play操作。

 

但当新的video元件创建后,不调用play()时为什么没有自动播放呢? 何时才真正进行播放,主要受到HTMLMediaElement的成员变量m_paused来控制的(虽然也涉及其它好几个状态,但m_paused却在这时起着决定性作用)。可以通过研究HTMLMediaElement::updateReadyState()中观察到(potentiallyPlaying函数)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值