MediaControlTextTrackContainerElement.cpp [plain text]
#include "config.h"
#include "MediaControlTextTrackContainerElement.h"
#if ENABLE(VIDEO)
#include "DOMTokenList.h"
#include "ElementChildIterator.h"
#include "EventHandler.h"
#include "EventNames.h"
#include "Frame.h"
#include "FullscreenManager.h"
#include "GraphicsContext.h"
#include "HTMLVideoElement.h"
#include "ImageBuffer.h"
#include "LocalizedStrings.h"
#include "Logging.h"
#include "PODInterval.h"
#include "Page.h"
#include "PageGroup.h"
#include "RenderLayer.h"
#include "RenderVideo.h"
#include "RenderView.h"
#include "Settings.h"
#include "ShadowRoot.h"
#include "StyleProperties.h"
#include "TextTrackCueGeneric.h"
#include "TextTrackList.h"
#include "VTTRegionList.h"
#include <wtf/IsoMallocInlines.h>
#include <wtf/Language.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(MediaControlTextTrackContainerElement);
using namespace HTMLNames;
Ref<MediaControlTextTrackContainerElement> MediaControlTextTrackContainerElement::create(Document& document, HTMLMediaElement& mediaElement)
{
auto element = adoptRef(*new MediaControlTextTrackContainerElement(document, mediaElement));
element->hide();
return element;
}
MediaControlTextTrackContainerElement::MediaControlTextTrackContainerElement(Document& document, HTMLMediaElement& element)
: HTMLDivElement(divTag, document)
, m_mediaElement(makeWeakPtr(&element))
{
static MainThreadNeverDestroyed<const AtomString> webkitMediaTextTrackContainerName("-webkit-media-text-track-container", AtomString::ConstructFromLiteral);
setPseudo(webkitMediaTextTrackContainerName);
}
RenderPtr<RenderElement> MediaControlTextTrackContainerElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
{
return createRenderer<RenderBlockFlow>(*this, WTFMove(style));
}
static bool compareCueIntervalForDisplay(const CueInterval& one, const CueInterval& two)
{
return one.data()->isPositionedAbove(two.data());
};
void MediaControlTextTrackContainerElement::updateDisplay()
{
if (m_mediaElement && !m_mediaElement->closedCaptionsVisible())
removeChildren();
if (!m_mediaElement || !m_mediaElement->isVideo() || m_videoDisplaySize.size().isEmpty())
return;
HTMLVideoElement& video = downcast<HTMLVideoElement>(*m_mediaElement);
CueList activeCues = video.currentlyActiveCues();
if (countChildNodes() < activeCues.size())
removeChildren();
activeCues.removeAllMatching([] (CueInterval& cueInterval) {
RefPtr<TextTrackCue> cue = cueInterval.data();
return !cue->track()
|| !cue->track()->isRendered()
|| cue->track()->mode() == TextTrack::Mode::Disabled
|| !cue->isActive()
|| !cue->isRenderable();
});
std::sort(activeCues.begin(), activeCues.end(), &compareCueIntervalForDisplay);
if (m_mediaElement->closedCaptionsVisible()) {
for (auto& interval : activeCues) {
auto cue = interval.data();
cue->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant);
if (is<VTTCue>(*cue))
processActiveVTTCue(downcast<VTTCue>(*cue));
else {
auto displayBox = cue->getDisplayTree(m_videoDisplaySize.size(), m_fontSize);
if (displayBox->hasChildNodes() && !contains(displayBox.get()))
appendChild(*displayBox);
}
}
}
if (hasChildNodes())
show();
else
hide();
updateTextTrackRepresentationIfNeeded();
updateTextTrackStyle();
m_needsGenerateTextTrackRepresentation = true;
}
void MediaControlTextTrackContainerElement::updateTextTrackRepresentationImageIfNeeded()
{
if (!m_needsGenerateTextTrackRepresentation)
return;
m_needsGenerateTextTrackRepresentation = false;
if (m_textTrackRepresentation)
m_textTrackRepresentation->update();
}
void MediaControlTextTrackContainerElement::processActiveVTTCue(VTTCue& cue)
{
DEBUG_LOG(LOGIDENTIFIER, "adding and positioning cue: \"", cue.text(), "\", start=", cue.startTime(), ", end=", cue.endTime(), ", line=", cue.line());
Ref<TextTrackCueBox> displayBox = *cue.getDisplayTree(m_videoDisplaySize.size(), m_fontSize);
if (auto region = cue.track()->regions()->getRegionById(cue.regionId())) {
Ref<HTMLDivElement> regionNode = region->getDisplayTree();
if (!contains(regionNode.ptr()))
appendChild(region->getDisplayTree());
region->appendTextTrackCueBox(WTFMove(displayBox));
} else {
if (displayBox->hasChildNodes() && !contains(displayBox.ptr())) {
appendChild(displayBox);
}
}
}
void MediaControlTextTrackContainerElement::updateActiveCuesFontSize()
{
if (!document().page())
return;
if (!m_mediaElement)
return;
float smallestDimension = std::min(m_videoDisplaySize.size().height(), m_videoDisplaySize.size().width());
float fontScale = document().page()->group().captionPreferences().captionFontSizeScaleAndImportance(m_fontSizeIsImportant);
m_fontSize = lroundf(smallestDimension * fontScale);
for (auto& activeCue : m_mediaElement->currentlyActiveCues()) {
RefPtr<TextTrackCue> cue = activeCue.data();
if (cue->isRenderable())
cue->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant);
}
}
void MediaControlTextTrackContainerElement::updateTextStrokeStyle()
{
if (!document().page())
return;
if (!m_mediaElement)
return;
String language;
if (auto* tracks = m_mediaElement->textTracks()) {
for (unsigned i = 0; i < tracks->length(); ++i) {
auto track = tracks->item(i);
if (track && track->mode() == TextTrack::Mode::Showing) {
language = track->validBCP47Language();
break;
}
}
}
float strokeWidth;
bool important;
if (document().page()->group().captionPreferences().captionStrokeWidthForFont(m_fontSize, language, strokeWidth, important))
setInlineStyleProperty(CSSPropertyStrokeWidth, strokeWidth, CSSUnitType::CSS_PX, important);
}
void MediaControlTextTrackContainerElement::updateTextTrackRepresentationIfNeeded()
{
if (!m_mediaElement)
return;
auto requiresTextTrackRepresentation = m_mediaElement->requiresTextTrackRepresentation();
if (!hasChildNodes() || !requiresTextTrackRepresentation) {
if (m_textTrackRepresentation) {
if (!requiresTextTrackRepresentation)
clearTextTrackRepresentation();
else
m_textTrackRepresentation->setHidden(true);
}
return;
}
if (!m_textTrackRepresentation) {
ALWAYS_LOG(LOGIDENTIFIER);
m_textTrackRepresentation = TextTrackRepresentation::create(*this);
if (document().page())
m_textTrackRepresentation->setContentScale(document().page()->deviceScaleFactor());
m_mediaElement->setTextTrackRepresentation(m_textTrackRepresentation.get());
}
m_textTrackRepresentation->setHidden(false);
}
void MediaControlTextTrackContainerElement::clearTextTrackRepresentation()
{
if (!m_textTrackRepresentation)
return;
ALWAYS_LOG(LOGIDENTIFIER);
m_textTrackRepresentation = nullptr;
if (m_mediaElement)
m_mediaElement->setTextTrackRepresentation(nullptr);
}
void MediaControlTextTrackContainerElement::updateTextTrackStyle()
{
if (m_textTrackRepresentation) {
setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
setInlineStyleProperty(CSSPropertyWidth, m_videoDisplaySize.size().width(), CSSUnitType::CSS_PX);
setInlineStyleProperty(CSSPropertyHeight, m_videoDisplaySize.size().height(), CSSUnitType::CSS_PX);
setInlineStyleProperty(CSSPropertyLeft, 0, CSSUnitType::CSS_PX);
setInlineStyleProperty(CSSPropertyTop, 0, CSSUnitType::CSS_PX);
return;
}
removeInlineStyleProperty(CSSPropertyPosition);
removeInlineStyleProperty(CSSPropertyWidth);
removeInlineStyleProperty(CSSPropertyHeight);
removeInlineStyleProperty(CSSPropertyLeft);
removeInlineStyleProperty(CSSPropertyTop);
}
void MediaControlTextTrackContainerElement::enteredFullscreen()
{
updateTextTrackRepresentationIfNeeded();
updateSizes(ForceUpdate::Yes);
}
void MediaControlTextTrackContainerElement::exitedFullscreen()
{
clearTextTrackRepresentation();
updateSizes(ForceUpdate::Yes);
}
bool MediaControlTextTrackContainerElement::updateVideoDisplaySize()
{
if (!document().page())
return false;
if (!m_mediaElement)
return false;
IntRect videoBox;
if (m_textTrackRepresentation) {
videoBox = m_textTrackRepresentation->bounds();
float deviceScaleFactor = document().page()->deviceScaleFactor();
videoBox.setWidth(videoBox.width() * deviceScaleFactor);
videoBox.setHeight(videoBox.height() * deviceScaleFactor);
} else {
if (!is<RenderVideo>(m_mediaElement->renderer()))
return false;
videoBox = downcast<RenderVideo>(*m_mediaElement->renderer()).videoBox();
}
if (m_videoDisplaySize == videoBox)
return false;
m_videoDisplaySize = videoBox;
return true;
}
void MediaControlTextTrackContainerElement::updateSizes(ForceUpdate force)
{
if (!updateVideoDisplaySize() && force != ForceUpdate::Yes)
return;
if (!document().page() || !m_mediaElement)
return;
m_mediaElement->syncTextTrackBounds();
updateActiveCuesFontSize();
updateTextStrokeStyle();
for (auto& activeCue : m_mediaElement->currentlyActiveCues())
activeCue.data()->recalculateStyles();
m_taskQueue.enqueueTask([this] () {
updateDisplay();
});
}
RefPtr<Image> MediaControlTextTrackContainerElement::createTextTrackRepresentationImage()
{
if (!hasChildNodes())
return nullptr;
RefPtr<Frame> frame = document().frame();
if (!frame)
return nullptr;
document().updateLayout();
auto* renderer = this->renderer();
if (!renderer)
return nullptr;
if (!renderer->hasLayer())
return nullptr;
RenderLayer* layer = downcast<RenderLayerModelObject>(*renderer).layer();
float deviceScaleFactor = 1;
if (Page* page = document().page())
deviceScaleFactor = page->deviceScaleFactor();
IntRect paintingRect = IntRect(IntPoint(), layer->size());
std::unique_ptr<ImageBuffer> buffer(ImageBuffer::create(paintingRect.size(), RenderingMode::Unaccelerated, deviceScaleFactor));
if (!buffer)
return nullptr;
auto paintFlags = RenderLayer::paintLayerPaintingCompositingAllPhasesFlags();
paintFlags.add(RenderLayer::PaintLayerFlag::TemporaryClipRects);
layer->paint(buffer->context(), paintingRect, LayoutSize(), { PaintBehavior::FlattenCompositingLayers, PaintBehavior::Snapshotting }, nullptr, paintFlags);
return ImageBuffer::sinkIntoImage(WTFMove(buffer));
}
void MediaControlTextTrackContainerElement::textTrackRepresentationBoundsChanged(const IntRect&)
{
updateTextTrackRepresentationIfNeeded();
updateSizes();
}
void MediaControlTextTrackContainerElement::hide()
{
setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
}
void MediaControlTextTrackContainerElement::show()
{
removeInlineStyleProperty(CSSPropertyDisplay);
}
bool MediaControlTextTrackContainerElement::isShowing() const
{
const StyleProperties* propertySet = inlineStyle();
return (!propertySet || !propertySet->getPropertyCSSValue(CSSPropertyDisplay));
}
#if !RELEASE_LOG_DISABLED
const Logger& MediaControlTextTrackContainerElement::logger() const
{
if (!m_logger)
m_logger = &document().logger();
return *m_logger;
}
const void* MediaControlTextTrackContainerElement::logIdentifier() const
{
if (!m_logIdentifier && m_mediaElement)
m_logIdentifier = m_mediaElement->logIdentifier();
return m_logIdentifier;
}
WTFLogChannel& MediaControlTextTrackContainerElement::logChannel() const
{
return LogMedia;
}
#endif // !RELEASE_LOG_DISABLED
}
#endif // ENABLE(VIDEO)