Chinaunix首页 | 论坛 | 博客
  • 博客访问: 168061
  • 博文数量: 24
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 399
  • 用 户 组: 普通用户
  • 注册时间: 2013-03-04 15:36
文章分类

全部博文(24)

文章存档

2017年(2)

2015年(5)

2014年(9)

2013年(8)

我的朋友

分类: 嵌入式

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_privateMediaPlayerPrivateInterface的指针,它是一个接口类,其中定义的是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;

}

presentQMediaPlayer的回调,当新的视频帧准备好后就会通过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,在里面搜索了一下controlsmedia,竟然真的搜出来很多包含了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 p = getStylePainter(paintInfo);

    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 p = getStylePainter(paintInfo);

    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 createTheme(Page* page)

{

    if (themeFactory)

        return themeFactory(page);

         return RenderThemeQtMobile::create(page);

}

此函数在RenderTheme.cpp当中实现,如果themeFactory为空,则直接调用RenderThemeQtMobile::create,我在这里加了打印,确实来了,看来themeFactory的确是空,具体什么情况会不为空,还要再详细分析分析,这里暂且跳过。

         RenderThemeRenderThemeQt,再到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接口类就可以了。这里我为了说明RenderThemeRenderObject的关系,从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类,这个就是用来表示播放控制按钮的,具体如何实现的,以及如何绘制,什么时候绘制,目前我还不知道。

阅读(2254) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~