全部博文(24)
分类: 嵌入式
2014-12-25 22:23:01
***如果不知道DOM,Render是什么,那此文应该很难看懂。如果还未用过HTML5的video元素,那可以写一个html文档看看效果。总之,这篇文章需要有一定的基础才能理解,其中代码偏多,但我觉得代码最能说明问题。我在这里主要是为了理清与video标签相关的几个类调用关系,并不对细节进行梳理。文中言论不一定都正确,欢迎指正。
环境:qtwebkit-opensource-5.3.2
Video标签在DOM树当中对应的类为HTMLVideoElement,而HTMLVideoElement继承自HTMLMediaElement,在HTMLMediaElement类中几乎处理了所有与媒体播放相关的事情,所以接下来HTMLMediaElement将是分析的重点……
先从MediaPlayer的创建函数开始:
void HTMLMediaElement::createMediaPlayer()
{
……
m_player = MediaPlayer::create(this);
……
}
在Webkit当中创建某一个类实例都是通过类似 MediaPlayer::create(…)这样的方式创建的,参数this指向的是HTMLMediaElement类或者是其子类。 将这个this指针传给MediaPlayer::create用来指向对应于这个MediaPlayer的客户(MediaPlayerClient*),这个参数后面会使用到的,这里知道它是MediaPlayerClient指针类型就可以了。
MediaPlayer既然被创建了,那接下来就是加载资源,播放,暂停等等一系列可能的操作了,查看
MediaPlayer::load(),
MediaPlayer::play(),
MediaPlayer::pause(),
MediaPlayer::prepareToPlay()
……
发现其中都是通过m_private又调用到了另外一个类的对应函数当中去了,而m_private是MediaPlayerPrivateInterface的指针,它是一个接口类,其中定义的是MediaPlayer播放所需要的接口,具体实现要针对每个平台。例如:
MediaPlayerPrivateGStreamer
MediaPlayerPrivateWinCE
MediaPlayerPrivateQt
我的分析是针对Qt平台进行的,所以就看MediaPlayerPrivateQt类。在MediaPlayerPrivateQt当中实例化了一个QMediaPlayer类,QMediaPlayer类是Qt平台提供的媒体播放器,他会实现数据加载,播放,暂停等一系列操作,这里只需要大概某清它提供给用户调用的接口函数就可以了,其内部实现已经与Webkit关系不大了。
DOM相关的类关系已经明白了,Render呢?视频如何绘制上去,控制按钮如何绘制上去呢?
视频的显示要从MediaPlayerPrivateQt::present(const QVideoFrame& frame)函数开始:
bool MediaPlayerPrivateQt::present(const QVideoFrame& frame)
{
// 保存视频帧,并调用MediaPlayer::repaint()
m_currentVideoFrame = frame;
m_webCorePlayer->repaint();
return true;
}
present是QMediaPlayer的回调,当新的视频帧准备好后就会通过frame参数传递过来,然后保存到currentVideoFrame中,随后调用了m_webCorePlayer->repaint(), 而这里的m_webCorePlayer则指向使用当前这个MediaPlayerPrivateQt实例的MediaPlayer实例,
void MediaPlayer::repaint()
{
if (m_mediaPlayerClient) // HTMLVideoElement
m_mediaPlayerClient->mediaPlayerRepaint(this);
}
这里又调用了m_mediaPlayerClient->mediaPlayerRepaint(…),m_mediaPlayerClient就是前面创建MediaPlayer实例时传递进来的MediaPlayerClient指针,即HTMLMediaElement指针(其实应该是HTMLVideoElement指针更确切)。那就看HTMLMediaElement::mediaPlayerRepaint:
void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*)
{
beginProcessingMediaPlayerCallback();
updateDisplayState();
if (renderer())
{
// 调用了render的repaint()
renderer()->repaint();
}
endProcessingMediaPlayerCallback();
}
其中最关键一句是renderer()->repaint(),renderer()返回的是RenderVideo指针,好吧,那就看RenderVideo::repaint(),但其实RenderVideo类没有实现repaint()函数,但是他实现了paintReplaced(...)函数:
void RenderVideo::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
MediaPlayer* mediaPlayer = mediaElement()->player();
……
if (displayingPoster)
{
paintIntoRect(paintInfo.context, rect);
}
else if (document()->view() && document()->view()->paintBehavior() & PaintBehaviorFlattenCompositingLayers)
{
mediaPlayer->paintCurrentFrameInContext(paintInfo.context, pixelSnappedIntRect(rect));
}
else
{
mediaPlayer->paint(paintInfo.context, pixelSnappedIntRect(rect));
}
}
在paintReplaced (…)当中最后要么调用mediaPlayer->paintCurrentFrameInContext(…),要么调用 mediaPlayer->paint(…),不过在MediaPlayerPrivateQt当中最终都会调用到paintCurrentFrameInContext(…)函数将视频帧绘制上去。
来看看各个类函数的调用关系:
MediaPlayerPrivateQt::present
MediaPlayer::repaint
HTMLMediaElement::mediaPlayerRepaint
renderer()->repaint(); -à RenderObject::repaint
RenderReplaced::paint
RenderVideo::paintReplaced
MediaPlayer ::paint
MediaPlayerPrivateQt::paint
MediaPlayerPrivateQt::paint
MediaPlayerPrivateQt::paintCurrentFrameInContext
绕了一大圈,从MediaPlayerPrivateQt开始,转了一圈,又回到了MediaPlayerPrivateQt当中来了。
视频帧绘制结束了,那按钮如何绘制的呢?从视频帧的绘制函数
void MediaPlayerPrivateQt::paintCurrentFrameInContext(GraphicsContext* context, const IntRect& rect)当中我们看不出任何有绘制按钮、进度或时间的代码,看来没有办法从这里下手。那就转来看看RenderVideo类,同样的结果,我仍然无从下手,有点盲目了。
后来我尝试着打开了RenderThemeQt.cpp,在里面搜索了一下controls和media,竟然真的搜出来很多包含了media的函数来
paintMediaCurrentTime
paintMediaFullscreenButton
paintMediaMuteButton
paintMediaPlayButton
看到这几个函数,已经足矣让我确定,这就是我要找的绘制按钮的地方了。仔细查看了代码,比如paintMediaPlayButton函数
bool RenderThemeQt::paintMediaPlayButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
// 从RenderObject*到mediaElement*,这两个一般情况下会一一对应,如果不对应那返回false,同理拿着Element可以通过renderer()取到RenderObject的实例指针
HTMLMediaElement* mediaElement = toParentMediaElement(o);
if (!mediaElement)
return false;
// PaintInfo里面包含了绘图上下文
QSharedPointer
if (p.isNull() || !p->isValid())
return true;
// setRenderHint什么作用暂且不知道
p->painter->setRenderHint(QPainter::Antialiasing, true);
// paintMediaBackground看字面意思应该是绘制媒体播放的背景
paintMediaBackground(p->painter, r);
// transformer不知道用来做什么,好像后面没有用到
WorldMatrixTransformer transformer(p->painter, o, r);
// setBrush设置画笔颜色
p->painter->setBrush(getMediaControlForegroundColor(o));
if (mediaElement->canPlay()) {
// 如果能播放,就画播放按钮(向右的三角形)
const QPointF playPolygon[3] = { QPointF(0, 0), QPointF(100, 50), QPointF(0, 100)};
p->painter->drawPolygon(playPolygon, 3);
} else {
// 如果不有播放,则画暂停按钮(双竖杠)
p->painter->drawRect(0, 0, 30, 100);
p->painter->drawRect(70, 0, 30, 100);
}
bool RenderThemeQt::paintMediaMuteButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
{
printf("RenderThemeQt::paintMediaMuteButton\n");
HTMLMediaElement* mediaElement = toParentMediaElement(o);
if (!mediaElement)
return false;
QSharedPointer
if (p.isNull() || !p->isValid())
return true;
p->painter->setRenderHint(QPainter::Antialiasing, true);
paintMediaBackground(p->painter, r);
// 绘制朝右喇叭图标
WorldMatrixTransformer transformer(p->painter, o, r);
const QPointF speakerPolygon[6] = { QPointF(20, 30), QPointF(50, 30), QPointF(80, 0),
QPointF(80, 100), QPointF(50, 70), QPointF(20, 70)};
p->painter->setBrush(mediaElement->muted() ? Qt::darkRed : getMediaControlForegroundColor(o));
p->painter->drawPolygon(speakerPolygon, 6);
return false;
}
还有几个函数也是用于video标签的控制栏绘制的,这里不一一分析。看到RenderThemeQt后,又发现从RenderThemeQt派生出来的两个子类
RenderThemeQStyle
RenderThemeQtMobile
按我的想法,RenderThemeQt是从RenderTheme继承而来,那么这两个类应该是两种不同主题风格的实现,但是我后来只找到了使用RenderThemeQtMobile的代码,却没有到RenderThemeQStyle的地方。
static PassRefPtr
{
if (themeFactory)
return themeFactory(page);
return RenderThemeQtMobile::create(page);
}
此函数在RenderTheme.cpp当中实现,如果themeFactory为空,则直接调用RenderThemeQtMobile::create,我在这里加了打印,确实来了,看来themeFactory的确是空,具体什么情况会不为空,还要再详细分析分析,这里暂且跳过。
从RenderTheme到RenderThemeQt,再到RenderThemeQtMobile,仔细看看其中的代码,不难发现,其中实现了大部分与控件相关的绘制函数:
bool RenderThemeQtMobile::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& r)
bool RenderThemeQtMobile::paintProgressBar(RenderObject* o, const PaintInfo& pi, const IntRect& r)
bool RenderThemeQtMobile::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& r)
bool RenderThemeQtMobile::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& r)
……
bool RenderThemeQt::paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& r)
bool RenderThemeQt::paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& r)
……
RenderTheme是接口类,具体实现由RenderThemeQt/RenderThemeQtMobile来完成,如果需要知道这里具体都能绘制哪些控件,查看RenderTheme接口类就可以了。这里我为了说明RenderTheme与RenderObject的关系,从RenderTheme当中找了两个接口函数全局搜索了一下:
paintBorderOnly
void RenderBox::paintBoxDecorations(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
paintDecorations
void RenderBox::paintBoxDecorations(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
这两个函数都出现在RenderBox::paintBoxDecorations里面,万能的RenderBox出现了,之所以说他万能,是因为页面上的大多数元素的渲染都与RenderBox有关系。paintBoxDecorations(…)就是用来绘制一些修饰的东西,将播放控制按钮,进度,时间这些都作为修饰来绘制是完全可以理解的的。仔细查看RenderBox::paintBoxDecorations(…)函数,可以看到
theme()->paint(…)
theme()->paintBorderOnly(…)
theme()->paintDecorations(…)
……
好多函数都在其中被调用了,看来这就播放控制按钮绘制的地方了。但还有一个问题:这些按钮在DOM树和Render树当中是如何表示的?
HTMLMediaElement当中实例化了一个MediaControls类,这个就是用来表示播放控制按钮的,具体如何实现的,以及如何绘制,什么时候绘制,目前我还不知道。