RenderVideo.cpp   [plain text]


/*
 * Copyright (C) 2007, 2008, 2009, 2010 Apple Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"

#if ENABLE(VIDEO)
#include "RenderVideo.h"

#include "Document.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "HTMLNames.h"
#include "HTMLVideoElement.h"
#include "MediaPlayer.h"
#include "RenderView.h"

#if USE(ACCELERATED_COMPOSITING)
#include "RenderLayer.h"
#include "RenderLayerBacking.h"
#endif

using namespace std;

namespace WebCore {

using namespace HTMLNames;

RenderVideo::RenderVideo(HTMLVideoElement* video)
    : RenderMedia(video)
{
    if (video->player() && video->readyState() >= HTMLVideoElement::HAVE_METADATA)
        setIntrinsicSize(video->player()->naturalSize());
    else {
        // When the natural size of the video is unavailable, we use the provided
        // width and height attributes of the video element as the intrinsic size until
        // better values become available. If these attributes are not set, we fall back
        // to a default video size (300x150).
        if (video->hasAttribute(widthAttr) && video->hasAttribute(heightAttr))
            setIntrinsicSize(IntSize(video->width(), video->height()));
        else if (video->ownerDocument() && video->ownerDocument()->isMediaDocument()) {
            // Video in standalone media documents should not use the default 300x150
            // size since they also have audio thrown at them. By setting the intrinsic
            // size to 300x1 the video will resize itself in these cases, and audio will
            // have the correct height (it needs to be > 0 for controls to render properly).
            setIntrinsicSize(IntSize(defaultSize().width(), 1));
        }
        else
            setIntrinsicSize(defaultSize());
    }
}

RenderVideo::~RenderVideo()
{
    if (MediaPlayer* p = player()) {
        p->setVisible(false);
        p->setFrameView(0);
    }
}

IntSize RenderVideo::defaultSize()
{
    // These values are specified in the spec.
    static const int cDefaultWidth = 300;
    static const int cDefaultHeight = 150;

    return IntSize(cDefaultWidth, cDefaultHeight);
}

void RenderVideo::intrinsicSizeChanged()
{
    if (videoElement()->shouldDisplayPosterImage())
        RenderMedia::intrinsicSizeChanged();
    videoSizeChanged(); 
}


void RenderVideo::videoSizeChanged()
{
    if (!player())
        return;
    IntSize size = player()->naturalSize();
    if (size.isEmpty()) {
        if (node()->ownerDocument() && node()->ownerDocument()->isMediaDocument())
            return;
    }
    if (size != intrinsicSize()) {
        setIntrinsicSize(size);
        setPrefWidthsDirty(true);
        setNeedsLayout(true);
    }
}

void RenderVideo::imageChanged(WrappedImagePtr newImage, const IntRect* rect)
{
    RenderMedia::imageChanged(newImage, rect);

    // Cache the image intrinsic size so we can continue to use it to draw the image correctly
    // even after we know the video intrisic size but aren't able to draw video frames yet
    // (we don't want to scale the poster to the video size).
    if (videoElement()->shouldDisplayPosterImage())
        m_cachedImageSize = intrinsicSize();
}

IntRect RenderVideo::videoBox() const
{
    if (m_cachedImageSize.isEmpty() && videoElement()->shouldDisplayPosterImage())
        return IntRect();

    IntSize elementSize;
    if (videoElement()->shouldDisplayPosterImage())
        elementSize = m_cachedImageSize;
    else
        elementSize = intrinsicSize();

    IntRect contentRect = contentBoxRect();
    if (elementSize.isEmpty() || contentRect.isEmpty())
        return IntRect();

    IntRect renderBox = contentRect;
    int ratio = renderBox.width() * elementSize.height() - renderBox.height() * elementSize.width();
    if (ratio > 0) {
        int newWidth = renderBox.height() * elementSize.width() / elementSize.height();
        // Just fill the whole area if the difference is one pixel or less (in both sides)
        if (renderBox.width() - newWidth > 2)
            renderBox.setWidth(newWidth);
        renderBox.move((contentRect.width() - renderBox.width()) / 2, 0);
    } else if (ratio < 0) {
        int newHeight = renderBox.width() * elementSize.height() / elementSize.width();
        if (renderBox.height() - newHeight > 2)
            renderBox.setHeight(newHeight);
        renderBox.move(0, (contentRect.height() - renderBox.height()) / 2);
    }

    return renderBox;
}
    
void RenderVideo::paintReplaced(PaintInfo& paintInfo, int tx, int ty)
{
    MediaPlayer* mediaPlayer = player();
    bool displayingPoster = videoElement()->shouldDisplayPosterImage();

    if (displayingPoster && document()->printing() && !view()->printImages())
        return;

    if (!displayingPoster) {
        if (!mediaPlayer)
            return;
        updatePlayer();
    }

    IntRect rect = videoBox();
    if (rect.isEmpty())
        return;
    rect.move(tx, ty);
    if (displayingPoster)
        paintIntoRect(paintInfo.context, rect);
    else
        mediaPlayer->paint(paintInfo.context, rect);
}

void RenderVideo::layout()
{
    RenderMedia::layout();
    updatePlayer();
}
    
HTMLVideoElement* RenderVideo::videoElement() const
{
    ASSERT(node()->hasTagName(videoTag));
    return static_cast<HTMLVideoElement*>(node()); 
}

void RenderVideo::updateFromElement()
{
    RenderMedia::updateFromElement();
    updatePlayer();
}

void RenderVideo::updatePlayer()
{
    MediaPlayer* mediaPlayer = player();
    if (!mediaPlayer)
        return;
    if (!videoElement()->inActiveDocument()) {
        mediaPlayer->setVisible(false);
        return;
    }

#if USE(ACCELERATED_COMPOSITING)
    layer()->rendererContentChanged();
#endif
    
    IntRect videoBounds = videoBox(); 
    mediaPlayer->setFrameView(document()->view());
    mediaPlayer->setSize(IntSize(videoBounds.width(), videoBounds.height()));
    mediaPlayer->setVisible(true);
}

int RenderVideo::calcReplacedWidth(bool includeMaxWidth) const
{
    int width;
    if (isWidthSpecified())
        width = calcReplacedWidthUsing(style()->width());
    else
        width = calcAspectRatioWidth() * style()->effectiveZoom();

    int minW = calcReplacedWidthUsing(style()->minWidth());
    int maxW = !includeMaxWidth || style()->maxWidth().isUndefined() ? width : calcReplacedWidthUsing(style()->maxWidth());

    return max(minW, min(width, maxW));
}

int RenderVideo::calcReplacedHeight() const
{
    int height;
    if (isHeightSpecified())
        height = calcReplacedHeightUsing(style()->height());
    else
        height = calcAspectRatioHeight() * style()->effectiveZoom();

    int minH = calcReplacedHeightUsing(style()->minHeight());
    int maxH = style()->maxHeight().isUndefined() ? height : calcReplacedHeightUsing(style()->maxHeight());

    return max(minH, min(height, maxH));
}

int RenderVideo::calcAspectRatioWidth() const
{
    int intrinsicWidth = intrinsicSize().width();
    int intrinsicHeight = intrinsicSize().height();
    if (!intrinsicHeight)
        return 0;
    return RenderBox::calcReplacedHeight() * intrinsicWidth / intrinsicHeight;
}

int RenderVideo::calcAspectRatioHeight() const
{
    int intrinsicWidth = intrinsicSize().width();
    int intrinsicHeight = intrinsicSize().height();
    if (!intrinsicWidth)
        return 0;
    return RenderBox::calcReplacedWidth() * intrinsicHeight / intrinsicWidth;
}

int RenderVideo::minimumReplacedHeight() const 
{
    return 0; 
}

#if USE(ACCELERATED_COMPOSITING)
bool RenderVideo::supportsAcceleratedRendering() const
{
    MediaPlayer* p = player();
    if (p)
        return p->supportsAcceleratedRendering();

    return false;
}

void RenderVideo::acceleratedRenderingStateChanged()
{
    MediaPlayer* p = player();
    if (p)
        p->acceleratedRenderingStateChanged();
}
#endif  // USE(ACCELERATED_COMPOSITING)

} // namespace WebCore

#endif