bool TextTrack::hasCue(VTTCue* cue, VTTCue::CueMatchRules match) { if (cue->startTime() < 0 || cue->endTime() < 0) return false; if (!m_cues || !m_cues->length()) return false; size_t searchStart = 0; size_t searchEnd = m_cues->length(); while (1) { ASSERT(searchStart <= m_cues->length()); ASSERT(searchEnd <= m_cues->length()); TextTrackCue* existingCue; // Cues in the TextTrackCueList are maintained in start time order. if (searchStart == searchEnd) { if (!searchStart) return false; // If there is more than one cue with the same start time, back up to first one so we // consider all of them. while (searchStart >= 2 && cue->startTime() == m_cues->item(searchStart - 2)->startTime()) --searchStart; bool firstCompare = true; while (1) { if (!firstCompare) ++searchStart; firstCompare = false; if (searchStart > m_cues->length()) return false; existingCue = m_cues->item(searchStart - 1); if (!cue->isRenderable()) continue; if (!existingCue || cue->startTime() > existingCue->startTime()) return false; if (!toVTTCue(existingCue)->isEqual(*cue, match)) continue; return true; } } size_t index = (searchStart + searchEnd) / 2; existingCue = m_cues->item(index); if (cue->startTime() < existingCue->startTime() || (match != VTTCue::IgnoreDuration && cue->startTime() == existingCue->startTime() && cue->endTime() > existingCue->endTime())) searchEnd = index; else searchStart = index + 1; } ASSERT_NOT_REACHED(); return false; }
void TextTrack::setMode(const AtomicString& mode) { ASSERT(mode == disabledKeyword() || mode == hiddenKeyword() || mode == showingKeyword()); // On setting, if the new value isn't equal to what the attribute would currently // return, the new value must be processed as follows ... if (m_mode == mode) return; // If mode changes to disabled, remove this track's cues from the client // because they will no longer be accessible from the cues() function. if (mode == disabledKeyword() && m_client && m_cues) m_client->textTrackRemoveCues(this, m_cues.get()); if (mode != showingKeyword() && m_cues) { for (size_t i = 0; i < m_cues->length(); ++i) { TextTrackCue* cue = m_cues->item(i); if (cue->isRenderable()) toVTTCue(cue)->removeDisplayTree(); } } m_mode = mode; if (m_client) m_client->textTrackModeChanged(this); }
bool TextTrackCue::operator==(const TextTrackCue& cue) const { if (cueType() != cue.cueType()) return false; if (m_endTime != cue.endTime()) return false; if (m_startTime != cue.startTime()) return false; if (m_content != cue.text()) return false; if (m_settings != cue.cueSettings()) return false; if (m_id != cue.id()) return false; if (m_textPosition != cue.position()) return false; if (m_linePosition != cue.line()) return false; if (m_cueSize != cue.size()) return false; if (align() != cue.align()) return false; return true; }
bool TextTrackCue::isEqual(const TextTrackCue& cue, CueMatchRules match) const { if (cueType() != cue.cueType()) return false; if (match != IgnoreDuration && m_endTime != cue.endTime()) return false; if (m_startTime != cue.startTime()) return false; if (m_content != cue.text()) return false; if (m_settings != cue.cueSettings()) return false; if (m_id != cue.id()) return false; if (m_textPosition != cue.position()) return false; if (m_linePosition != cue.line()) return false; if (m_cueSize != cue.size()) return false; if (align() != cue.align()) return false; return true; }
void MediaControlRootElement::updateTextTrackDisplay() { if (!m_textDisplayContainer) createTextTrackDisplay(); CueList activeCues = toParentMediaElement(m_textDisplayContainer)->currentlyActiveCues(); m_textTrackDisplay->removeChildren(); bool nothingToDisplay = true; for (size_t i = 0; i < activeCues.size(); ++i) { TextTrackCue* cue = activeCues[i].data(); ASSERT(cue->isActive()); if (!cue->track() || cue->track()->mode() != TextTrack::SHOWING) continue; String cueText = cue->text(); if (!cueText.isEmpty()) { if (!nothingToDisplay) m_textTrackDisplay->appendChild(document()->createElement(HTMLNames::brTag, false), ASSERT_NO_EXCEPTION); m_textTrackDisplay->appendChild(document()->createTextNode(cueText), ASSERT_NO_EXCEPTION); nothingToDisplay = false; } } if (!nothingToDisplay) m_textDisplayContainer->show(); else m_textDisplayContainer->hide(); }
void MediaControlTextTrackContainerElement::updateSizes(bool forceUpdate) { HTMLMediaElement* mediaElement = toParentMediaElement(this); if (!mediaElement) return; if (!document()->page()) return; IntRect videoBox; if (!mediaElement->renderer() || !mediaElement->renderer()->isVideo()) return; videoBox = toRenderVideo(mediaElement->renderer())->videoBox(); if (!forceUpdate && m_videoDisplaySize == videoBox) return; m_videoDisplaySize = videoBox; float smallestDimension = std::min(m_videoDisplaySize.size().height(), m_videoDisplaySize.size().width()); float fontSize = smallestDimension * 0.05f; if (fontSize != m_fontSize) { m_fontSize = fontSize; setInlineStyleProperty(CSSPropertyFontSize, String::number(fontSize) + "px"); } CueList activeCues = mediaElement->currentlyActiveCues(); for (size_t i = 0; i < activeCues.size(); ++i) { TextTrackCue* cue = activeCues[i].data(); cue->videoSizeDidChange(m_videoDisplaySize.size()); } }
void MediaControlTextTrackContainerElement::updateTimerFired(Timer<MediaControlTextTrackContainerElement>*) { if (!document().page()) return; if (m_textTrackRepresentation) { setInlineStyleProperty(CSSPropertyWidth, m_videoDisplaySize.size().width(), CSSPrimitiveValue::CSS_PX); setInlineStyleProperty(CSSPropertyHeight, m_videoDisplaySize.size().height(), CSSPrimitiveValue::CSS_PX); } HTMLMediaElement* mediaElement = parentMediaElement(this); if (!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); CueList activeCues = mediaElement->currentlyActiveCues(); for (size_t i = 0; i < activeCues.size(); ++i) { TextTrackCue* cue = activeCues[i].data(); cue->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant); } updateDisplay(); }
bool TextTrackCue::cueContentsMatch(const TextTrackCue& cue) const { if (cueType() != cue.cueType()) return false; if (id() != cue.id()) return false; return true; }
bool TextTrackCue::hasEquivalentStartTime(const TextTrackCue& cue) const { MediaTime startTimeVariance = MediaTime::zeroTime(); if (track()) startTimeVariance = track()->startTimeVariance(); else if (cue.track()) startTimeVariance = cue.track()->startTimeVariance(); return abs(abs(startMediaTime()) - abs(cue.startMediaTime())) <= startTimeVariance; }
bool TextTrackCue::isEqual(const TextTrackCue& cue, TextTrackCue::CueMatchRules match) const { if (cueType() != cue.cueType()) return false; if (match != IgnoreDuration && endMediaTime() != cue.endMediaTime()) return false; if (!hasEquivalentStartTime(cue)) return false; if (!cueContentsMatch(cue)) return false; return true; }
void JSTextTrackCue::visitChildren(JSCell* cell, SlotVisitor& visitor) { JSTextTrackCue* jsTextTrackCue = jsCast<JSTextTrackCue*>(cell); ASSERT_GC_OBJECT_INHERITS(jsTextTrackCue, &s_info); COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag); ASSERT(jsTextTrackCue->structure()->typeInfo().overridesVisitChildren()); Base::visitChildren(jsTextTrackCue, visitor); // Mark the cue's track root if it has one. TextTrackCue* textTrackCue = static_cast<TextTrackCue*>(jsTextTrackCue->impl()); if (TextTrack* textTrack = textTrackCue->track()) visitor.addOpaqueRoot(root(textTrack)); textTrackCue->visitJSEventListeners(visitor); }
void TextTrack::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) { aCue.SetActive(false); mCueList->RemoveCue(aCue, aRv); aCue.SetTrack(nullptr); if (mTextTrackList) { HTMLMediaElement* mediaElement = mTextTrackList->GetMediaElement(); if (mediaElement) { mediaElement->NotifyCueRemoved(aCue); } } SetDirty(); }
bool TextTrackCueGeneric::isEqual(const TextTrackCue& cue, TextTrackCue::CueMatchRules match) const { // Do not call the parent class isEqual here, because we are not cueType() == VTTCue, // and will fail that equality test. if (!TextTrackCue::isEqual(cue, match)) return false; if (cue.cueType() != TextTrackCue::Generic) return false; const TextTrackCueGeneric* other = static_cast<const TextTrackCueGeneric*>(&cue); if (m_baseFontSizeRelativeToVideoHeight != other->baseFontSizeRelativeToVideoHeight()) return false; if (m_fontSizeMultiplier != other->fontSizeMultiplier()) return false; if (m_fontName != other->fontName()) return false; if (m_foregroundColor != other->foregroundColor()) return false; if (m_backgroundColor != other->backgroundColor()) return false; return true; }
bool DataCue::cueContentsMatch(const TextTrackCue& cue) const { if (cue.cueType() != TextTrackCue::Data) return false; const DataCue* dataCue = toDataCue(&cue); RefPtr<ArrayBuffer> otherData = dataCue->data(); if ((otherData && !m_data) || (!otherData && m_data)) return false; if (m_data && m_data->byteLength() != otherData->byteLength()) return false; if (m_data && m_data->data() && memcmp(m_data->data(), otherData->data(), m_data->byteLength())) return false; const SerializedPlatformRepresentation* otherPlatformValue = dataCue->platformValue(); if ((otherPlatformValue && !m_platformValue) || (!otherPlatformValue && m_platformValue)) return false; if (m_platformValue && !m_platformValue->isEqual(*otherPlatformValue)) return false; JSC::JSValue thisValue = valueOrNull(); JSC::JSValue otherValue = dataCue->valueOrNull(); if ((otherValue && !thisValue) || (!otherValue && thisValue)) return false; if (!JSC::JSValue::strictEqual(nullptr, thisValue, otherValue)) return false; return true; }
NS_IMETHODIMP WebVTTListener::OnCue(JS::Handle<JS::Value> aCue, JSContext* aCx) { if (!aCue.isObject()) { return NS_ERROR_FAILURE; } TextTrackCue* cue = nullptr; nsresult rv = UNWRAP_OBJECT(VTTCue, &aCue.toObject(), cue); NS_ENSURE_SUCCESS(rv, rv); cue->SetTrackElement(mElement); mElement->mTrack->AddCue(*cue); return NS_OK; }
ExceptionOr<void> InbandDataTextTrack::removeCue(TextTrackCue& cue) { ASSERT(cue.cueType() == TextTrackCue::Data); m_incompleteCueMap.remove(const_cast<SerializedPlatformRepresentation*>(toDataCue(&cue)->platformValue())); return InbandTextTrack::removeCue(cue); }
void TextTrack::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) { // Bug1304948, check the aCue belongs to the TextTrack. mCueList->RemoveCue(aCue, aRv); if (aRv.Failed()) { return; } aCue.SetActive(false); aCue.SetTrack(nullptr); if (mTextTrackList) { HTMLMediaElement* mediaElement = mTextTrackList->GetMediaElement(); if (mediaElement) { mediaElement->NotifyCueRemoved(aCue); } } SetDirty(); }
bool DataCue::isEqual(const TextTrackCue& cue, TextTrackCue::CueMatchRules match) const { if (!TextTrackCue::isEqual(cue, match)) return false; if (cue.cueType() != TextTrackCue::Data) return false; return cueContentsMatch(cue); }
bool TextTrackCue::doesExtendCue(const TextTrackCue& cue) const { if (!cueContentsMatch(cue)) return false; if (endMediaTime() != cue.startMediaTime()) return false; return true; }
bool JSTextTrackCueOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, SlotVisitor& visitor) { JSTextTrackCue* jsTextTrackCue = jsCast<JSTextTrackCue*>(handle.get().asCell()); TextTrackCue* textTrackCue = static_cast<TextTrackCue*>(jsTextTrackCue->impl()); // If the cue is firing event listeners, its wrapper is reachable because // the wrapper is responsible for marking those event listeners. if (textTrackCue->isFiringEventListeners()) return true; // If the cue has no event listeners and has no custom properties, it is not reachable. if (!textTrackCue->hasEventListeners() && !jsTextTrackCue->hasCustomProperties()) return false; // If the cue is not associated with a track, it is not reachable. if (!textTrackCue->track()) return false; return visitor.containsOpaqueRoot(root(textTrackCue->track())); }
bool TextTrackCue::isEqual(const TextTrackCue& cue, TextTrackCue::CueMatchRules match) const { if (cueType() != cue.cueType()) return false; if (match != IgnoreDuration && m_endTime != cue.m_endTime) return false; if (!cueContentsMatch(cue)) return false; return true; }
bool TextTrackCueGeneric::isEqual(const TextTrackCue& cue, TextTrackCue::CueMatchRules match) const { // Do not call the parent class isEqual here, because we are not cueType() == VTTCue, // and will fail that equality test. if (!TextTrackCue::isEqual(cue, match)) return false; if (cue.cueType() != TextTrackCue::Generic) return false; return cueContentsMatch(cue); }
void TextTrackManager::NotifyCueRemoved(TextTrackCue& aCue) { WEBVTT_LOG("NotifyCueRemoved"); if (mNewCues) { mNewCues->RemoveCue(aCue); } DispatchTimeMarchesOn(); if (aCue.GetActive()) { // We remove an active cue, need to update the display. DispatchUpdateCueDisplay(); } }
TextTrackCueList* TextTrack::GetActiveCues() { if (mMode == TextTrackMode::Disabled || !mMediaElement) { return nullptr; } // If we are dirty, i.e. an event happened that may cause the sorted mCueList // to have changed like a seek or an insert for a cue, than we need to rebuild // the active cue list from scratch. if (mDirty) { mCuePos = 0; mDirty = true; mActiveCueList->RemoveAll(); } double playbackTime = mMediaElement->CurrentTime(); // Remove all the cues from the active cue list whose end times now occur // earlier then the current playback time. When we reach a cue whose end time // is valid we can safely stop iterating as the list is sorted. for (uint32_t i = 0; i < mActiveCueList->Length() && (*mActiveCueList)[i]->EndTime() < playbackTime; i++) { mActiveCueList->RemoveCueAt(i); } // Add all the cues, starting from the position of the last cue that was // added, that have valid start and end times for the current playback time. // We can stop iterating safely once we encounter a cue that does not have // valid times for the current playback time as the cue list is sorted. for (; mCuePos < mCueList->Length(); mCuePos++) { TextTrackCue* cue = (*mCueList)[mCuePos]; if (cue->StartTime() > playbackTime || cue->EndTime() < playbackTime) { break; } mActiveCueList->AddCue(*cue); } return mActiveCueList; }
// https://html.spec.whatwg.org/multipage/embedded-content.html#time-marches-on void TextTrackManager::TimeMarchesOn() { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); WEBVTT_LOG("TimeMarchesOn"); mTimeMarchesOnDispatched = false; // Early return if we don't have any TextTracks or shutting down. if (!mTextTracks || mTextTracks->Length() == 0 || mShutdown) { return; } nsISupports* parentObject = mMediaElement->OwnerDoc()->GetParentObject(); if (NS_WARN_IF(!parentObject)) { return; } nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject); if (mMediaElement && (!(mMediaElement->GetPlayedOrSeeked()) || mMediaElement->Seeking())) { WEBVTT_LOG("TimeMarchesOn seeking or post return"); return; } // Step 3. double currentPlaybackTime = mMediaElement->CurrentTime(); bool hasNormalPlayback = !mHasSeeked; mHasSeeked = false; WEBVTT_LOG("TimeMarchesOn mLastTimeMarchesOnCalled %lf currentPlaybackTime %lf hasNormalPlayback %d" , mLastTimeMarchesOnCalled, currentPlaybackTime, hasNormalPlayback); // Step 1, 2. RefPtr<TextTrackCueList> currentCues = new TextTrackCueList(window); RefPtr<TextTrackCueList> otherCues = new TextTrackCueList(window); bool dummy; for (uint32_t index = 0; index < mTextTracks->Length(); ++index) { TextTrack* ttrack = mTextTracks->IndexedGetter(index, dummy); if (ttrack && dummy) { // TODO: call GetCueListByTimeInterval on mNewCues? ttrack->UpdateActiveCueList(); TextTrackCueList* activeCueList = ttrack->GetActiveCues(); if (activeCueList) { for (uint32_t i = 0; i < activeCueList->Length(); ++i) { currentCues->AddCue(*((*activeCueList)[i])); } } } } WEBVTT_LOGV("TimeMarchesOn currentCues %d", currentCues->Length()); // Populate otherCues with 'non-active" cues. if (hasNormalPlayback) { if (currentPlaybackTime < mLastTimeMarchesOnCalled) { // TODO: Add log and find the root cause why the // playback position goes backward. mLastTimeMarchesOnCalled = currentPlaybackTime; } media::Interval<double> interval(mLastTimeMarchesOnCalled, currentPlaybackTime); otherCues = mNewCues->GetCueListByTimeInterval(interval);; } else { // Seek case. Put the mLastActiveCues into otherCues. otherCues = mLastActiveCues; } for (uint32_t i = 0; i < currentCues->Length(); ++i) { TextTrackCue* cue = (*currentCues)[i]; otherCues->RemoveCue(*cue); } WEBVTT_LOGV("TimeMarchesOn otherCues %d", otherCues->Length()); // Step 4. RefPtr<TextTrackCueList> missedCues = new TextTrackCueList(window); if (hasNormalPlayback) { for (uint32_t i = 0; i < otherCues->Length(); ++i) { TextTrackCue* cue = (*otherCues)[i]; if (cue->StartTime() >= mLastTimeMarchesOnCalled && cue->EndTime() <= currentPlaybackTime) { missedCues->AddCue(*cue); } } } WEBVTT_LOGV("TimeMarchesOn missedCues %d", missedCues->Length()); // Step 5. Empty now. // TODO: Step 6: fire timeupdate? // Step 7. Abort steps if condition 1, 2, 3 are satisfied. // 1. All of the cues in current cues have their active flag set. // 2. None of the cues in other cues have their active flag set. // 3. Missed cues is empty. bool c1 = true; for (uint32_t i = 0; i < currentCues->Length(); ++i) { if (!(*currentCues)[i]->GetActive()) { c1 = false; break; } } bool c2 = true; for (uint32_t i = 0; i < otherCues->Length(); ++i) { if ((*otherCues)[i]->GetActive()) { c2 = false; break; } } bool c3 = (missedCues->Length() == 0); if (c1 && c2 && c3) { mLastTimeMarchesOnCalled = currentPlaybackTime; WEBVTT_LOG("TimeMarchesOn step 7 return, mLastTimeMarchesOnCalled %lf", mLastTimeMarchesOnCalled); return; } // Step 8. Respect PauseOnExit flag if not seek. if (hasNormalPlayback) { for (uint32_t i = 0; i < otherCues->Length(); ++i) { TextTrackCue* cue = (*otherCues)[i]; if (cue && cue->PauseOnExit() && cue->GetActive()) { WEBVTT_LOG("TimeMarchesOn pause the MediaElement"); mMediaElement->Pause(); break; } } for (uint32_t i = 0; i < missedCues->Length(); ++i) { TextTrackCue* cue = (*missedCues)[i]; if (cue && cue->PauseOnExit()) { WEBVTT_LOG("TimeMarchesOn pause the MediaElement"); mMediaElement->Pause(); break; } } } // Step 15. // Sort text tracks in the same order as the text tracks appear // in the media element's list of text tracks, and remove // duplicates. TextTrackListInternal affectedTracks; // Step 13, 14. nsTArray<RefPtr<SimpleTextTrackEvent>> eventList; // Step 9, 10. // For each text track cue in missed cues, prepare an event named // enter for the TextTrackCue object with the cue start time. for (uint32_t i = 0; i < missedCues->Length(); ++i) { TextTrackCue* cue = (*missedCues)[i]; if (cue) { SimpleTextTrackEvent* event = new SimpleTextTrackEvent(NS_LITERAL_STRING("enter"), cue->StartTime(), cue->GetTrack(), cue); eventList.InsertElementSorted(event, CompareSimpleTextTrackEvents(mMediaElement)); affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement)); } } // Step 11, 17. for (uint32_t i = 0; i < otherCues->Length(); ++i) { TextTrackCue* cue = (*otherCues)[i]; if (cue->GetActive() || missedCues->IsCueExist(cue)) { double time = cue->StartTime() > cue->EndTime() ? cue->StartTime() : cue->EndTime(); SimpleTextTrackEvent* event = new SimpleTextTrackEvent(NS_LITERAL_STRING("exit"), time, cue->GetTrack(), cue); eventList.InsertElementSorted(event, CompareSimpleTextTrackEvents(mMediaElement)); affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement)); } cue->SetActive(false); } // Step 12, 17. for (uint32_t i = 0; i < currentCues->Length(); ++i) { TextTrackCue* cue = (*currentCues)[i]; if (!cue->GetActive()) { SimpleTextTrackEvent* event = new SimpleTextTrackEvent(NS_LITERAL_STRING("enter"), cue->StartTime(), cue->GetTrack(), cue); eventList.InsertElementSorted(event, CompareSimpleTextTrackEvents(mMediaElement)); affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement)); } cue->SetActive(true); } // Fire the eventList for (uint32_t i = 0; i < eventList.Length(); ++i) { NS_DispatchToMainThread(eventList[i].forget()); } // Step 16. for (uint32_t i = 0; i < affectedTracks.Length(); ++i) { TextTrack* ttrack = affectedTracks[i]; if (ttrack) { ttrack->DispatchAsyncTrustedEvent(NS_LITERAL_STRING("cuechange")); HTMLTrackElement* trackElement = ttrack->GetTrackElement(); if (trackElement) { trackElement->DispatchTrackRunnable(NS_LITERAL_STRING("cuechange")); } } } mLastTimeMarchesOnCalled = currentPlaybackTime; mLastActiveCues = currentCues; // Step 18. UpdateCueDisplay(); }
void MediaControlTextTrackContainerElement::updateDisplay() { if (!mediaElement().closedCaptionsVisible()) { removeChildren(); return; } // 1. If the media element is an audio element, or is another playback // mechanism with no rendering area, abort these steps. There is nothing to // render. if (isHTMLAudioElement(mediaElement())) return; // 2. Let video be the media element or other playback mechanism. HTMLVideoElement& video = toHTMLVideoElement(mediaElement()); // 3. Let output be an empty list of absolutely positioned CSS block boxes. // 4. If the user agent is exposing a user interface for video, add to // output one or more completely transparent positioned CSS block boxes that // cover the same region as the user interface. // 5. If the last time these rules were run, the user agent was not exposing // a user interface for video, but now it is, let reset be true. Otherwise, // let reset be false. // There is nothing to be done explicitly for 4th and 5th steps, as // everything is handled through CSS. The caption box is on top of the // controls box, in a container set with the -webkit-box display property. // 6. Let tracks be the subset of video's list of text tracks that have as // their rules for updating the text track rendering these rules for // updating the display of WebVTT text tracks, and whose text track mode is // showing or showing by default. // 7. Let cues be an empty list of text track cues. // 8. For each track track in tracks, append to cues all the cues from // track's list of cues that have their text track cue active flag set. CueList activeCues = video.currentlyActiveCues(); // 9. If reset is false, then, for each text track cue cue in cues: if cue's // text track cue display state has a set of CSS boxes, then add those boxes // to output, and remove cue from cues. // There is nothing explicitly to be done here, as all the caching occurs // within the TextTrackCue instance itself. If parameters of the cue change, // the display tree is cleared. // 10. For each text track cue cue in cues that has not yet had // corresponding CSS boxes added to output, in text track cue order, run the // following substeps: for (size_t i = 0; i < activeCues.size(); ++i) { TextTrackCue* cue = activeCues[i].data(); ASSERT(cue->isActive()); if (!cue->track() || !cue->track()->isRendered() || !cue->isActive()) continue; cue->updateDisplay(m_videoDisplaySize.size(), *this); } // 11. Return output. if (hasChildren()) show(); else hide(); }
void MediaControlTextTrackContainerElement::updateDisplay() { if (!mediaController()->closedCaptionsVisible()) removeChildren(); HTMLMediaElement* mediaElement = parentMediaElement(this); // 1. If the media element is an audio element, or is another playback // mechanism with no rendering area, abort these steps. There is nothing to // render. if (!mediaElement || !mediaElement->isVideo()) return; // 2. Let video be the media element or other playback mechanism. HTMLVideoElement* video = toHTMLVideoElement(mediaElement); // 3. Let output be an empty list of absolutely positioned CSS block boxes. Vector<RefPtr<HTMLDivElement>> output; // 4. If the user agent is exposing a user interface for video, add to // output one or more completely transparent positioned CSS block boxes that // cover the same region as the user interface. // 5. If the last time these rules were run, the user agent was not exposing // a user interface for video, but now it is, let reset be true. Otherwise, // let reset be false. // There is nothing to be done explicitly for 4th and 5th steps, as // everything is handled through CSS. The caption box is on top of the // controls box, in a container set with the -webkit-box display property. // 6. Let tracks be the subset of video's list of text tracks that have as // their rules for updating the text track rendering these rules for // updating the display of WebVTT text tracks, and whose text track mode is // showing or showing by default. // 7. Let cues be an empty list of text track cues. // 8. For each track track in tracks, append to cues all the cues from // track's list of cues that have their text track cue active flag set. CueList activeCues = video->currentlyActiveCues(); // 9. If reset is false, then, for each text track cue cue in cues: if cue's // text track cue display state has a set of CSS boxes, then add those boxes // to output, and remove cue from cues. // There is nothing explicitly to be done here, as all the caching occurs // within the TextTrackCue instance itself. If parameters of the cue change, // the display tree is cleared. // 10. For each text track cue cue in cues that has not yet had // corresponding CSS boxes added to output, in text track cue order, run the // following substeps: for (size_t i = 0; i < activeCues.size(); ++i) { TextTrackCue* cue = activeCues[i].data(); ASSERT(cue->isActive()); if (!cue->track() || !cue->track()->isRendered() || !cue->isActive() || cue->text().isEmpty()) continue; RefPtr<TextTrackCueBox> displayBox = cue->getDisplayTree(m_videoDisplaySize.size()); if (displayBox->hasChildNodes() && !contains(displayBox.get())) { // Note: the display tree of a cue is removed when the active flag of the cue is unset. appendChild(displayBox, ASSERT_NO_EXCEPTION, AttachNow); cue->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant); } } // 11. Return output. if (hasChildNodes()) { show(); if (mediaElement->requiresTextTrackRepresentation()) { if (!m_textTrackRepresentation) m_textTrackRepresentation = TextTrackRepresentation::create(this); mediaElement->setTextTrackRepresentation(m_textTrackRepresentation.get()); if (Page* page = document().page()) m_textTrackRepresentation->setContentScale(page->deviceScaleFactor()); m_textTrackRepresentation->update(); setInlineStyleProperty(CSSPropertyWidth, m_videoDisplaySize.size().width(), CSSPrimitiveValue::CSS_PX); setInlineStyleProperty(CSSPropertyHeight, m_videoDisplaySize.size().height(), CSSPrimitiveValue::CSS_PX); } } else { hide(); clearTextTrackRepresentation(); } }
void MediaControlTextTrackContainerElement::updateDisplay() { HTMLMediaElement* mediaElement = toParentMediaElement(this); // 1. If the media element is an audio element, or is another playback // mechanism with no rendering area, abort these steps. There is nothing to // render. if (!mediaElement->isVideo()) return; // 2. Let video be the media element or other playback mechanism. HTMLVideoElement* video = static_cast<HTMLVideoElement*>(mediaElement); // 3. Let output be an empty list of absolutely positioned CSS block boxes. Vector<RefPtr<HTMLDivElement> > output; // 4. If the user agent is exposing a user interface for video, add to // output one or more completely transparent positioned CSS block boxes that // cover the same region as the user interface. // 5. If the last time these rules were run, the user agent was not exposing // a user interface for video, but now it is, let reset be true. Otherwise, // let reset be false. // There is nothing to be done explicitly for 4th and 5th steps, as // everything is handled through CSS. The caption box is on top of the // controls box, in a container set with the -webkit-box display property. // 6. Let tracks be the subset of video's list of text tracks that have as // their rules for updating the text track rendering these rules for // updating the display of WebVTT text tracks, and whose text track mode is // showing or showing by default. // 7. Let cues be an empty list of text track cues. // 8. For each track track in tracks, append to cues all the cues from // track's list of cues that have their text track cue active flag set. CueList activeCues = video->currentlyActiveCues(); // 9. If reset is false, then, for each text track cue cue in cues: if cue's // text track cue display state has a set of CSS boxes, then add those boxes // to output, and remove cue from cues. // There is nothing explicitly to be done here, as all the caching occurs // within the TextTrackCue instance itself. If parameters of the cue change, // the display tree is cleared. // 10. For each text track cue cue in cues that has not yet had // corresponding CSS boxes added to output, in text track cue order, run the // following substeps: for (size_t i = 0; i < activeCues.size(); ++i) { TextTrackCue* cue = activeCues[i].data(); ASSERT(cue->isActive()); if (!cue->track() || !cue->track()->isRendered()) continue; RefPtr<TextTrackCueBox> displayBox = cue->getDisplayTree(); if (displayBox->hasChildNodes() && !contains(static_cast<Node*>(displayBox.get()))) // Note: the display tree of a cue is removed when the active flag of the cue is unset. appendChild(displayBox, ASSERT_NO_EXCEPTION, false); } // 11. Return output. hasChildNodes() ? show() : hide(); }
void CueTimeline::updateActiveCues(double movieTime) { // 4.8.10.8 Playing the media resource // If the current playback position changes while the steps are running, // then the user agent must wait for the steps to complete, and then must // immediately rerun the steps. if (ignoreUpdateRequests()) return; HTMLMediaElement& mediaElement = this->mediaElement(); // Don't run the "time marches on" algorithm if the document has been // detached. This primarily guards against dispatch of events w/ // HTMLTrackElement targets. if (mediaElement.document().isDetached()) return; // https://html.spec.whatwg.org/#time-marches-on // 1 - Let current cues be a list of cues, initialized to contain all the // cues of all the hidden, showing, or showing by default text tracks of the // media element (not the disabled ones) whose start times are less than or // equal to the current playback position and whose end times are greater // than the current playback position. CueList currentCues; // The user agent must synchronously unset [the text track cue active] flag // whenever ... the media element's readyState is changed back to // kHaveNothing. if (mediaElement.getReadyState() != HTMLMediaElement::kHaveNothing && mediaElement.webMediaPlayer()) currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime)); CueList previousCues; CueList missedCues; // 2 - Let other cues be a list of cues, initialized to contain all the cues // of hidden, showing, and showing by default text tracks of the media // element that are not present in current cues. previousCues = m_currentlyActiveCues; // 3 - Let last time be the current playback position at the time this // algorithm was last run for this media element, if this is not the first // time it has run. double lastTime = m_lastUpdateTime; double lastSeekTime = mediaElement.lastSeekTime(); // 4 - If the current playback position has, since the last time this // algorithm was run, only changed through its usual monotonic increase // during normal playback, then let missed cues be the list of cues in other // cues whose start times are greater than or equal to last time and whose // end times are less than or equal to the current playback position. // Otherwise, let missed cues be an empty list. if (lastTime >= 0 && lastSeekTime < movieTime) { CueList potentiallySkippedCues = m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime)); for (CueInterval cue : potentiallySkippedCues) { // Consider cues that may have been missed since the last seek time. if (cue.low() > std::max(lastSeekTime, lastTime) && cue.high() < movieTime) missedCues.append(cue); } } m_lastUpdateTime = movieTime; // 5 - If the time was reached through the usual monotonic increase of the // current playback position during normal playback, and if the user agent // has not fired a timeupdate event at the element in the past 15 to 250ms // and is not still running event handlers for such an event, then the user // agent must queue a task to fire a simple event named timeupdate at the // element. (In the other cases, such as explicit seeks, relevant events get // fired as part of the overall process of changing the current playback // position.) if (!mediaElement.seeking() && lastSeekTime < lastTime) mediaElement.scheduleTimeupdateEvent(true); // Explicitly cache vector sizes, as their content is constant from here. size_t missedCuesSize = missedCues.size(); size_t previousCuesSize = previousCues.size(); // 6 - If all of the cues in current cues have their text track cue active // flag set, none of the cues in other cues have their text track cue active // flag set, and missed cues is empty, then abort these steps. bool activeSetChanged = missedCuesSize; for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i) { if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->isActive()) activeSetChanged = true; } for (CueInterval currentCue : currentCues) { // Notify any cues that are already active of the current time to mark // past and future nodes. Any inactive cues have an empty display state; // they will be notified of the current time when the display state is // updated. if (currentCue.data()->isActive()) currentCue.data()->updatePastAndFutureNodes(movieTime); else activeSetChanged = true; } if (!activeSetChanged) return; // 7 - If the time was reached through the usual monotonic increase of the // current playback position during normal playback, and there are cues in // other cues that have their text track cue pause-on-exi flag set and that // either have their text track cue active flag set or are also in missed // cues, then immediately pause the media element. for (size_t i = 0; !mediaElement.paused() && i < previousCuesSize; ++i) { if (previousCues[i].data()->pauseOnExit() && previousCues[i].data()->isActive() && !currentCues.contains(previousCues[i])) mediaElement.pause(); } for (size_t i = 0; !mediaElement.paused() && i < missedCuesSize; ++i) { if (missedCues[i].data()->pauseOnExit()) mediaElement.pause(); } // 8 - Let events be a list of tasks, initially empty. Each task in this // list will be associated with a text track, a text track cue, and a time, // which are used to sort the list before the tasks are queued. HeapVector<std::pair<double, Member<TextTrackCue>>> eventTasks; // 8 - Let affected tracks be a list of text tracks, initially empty. HeapVector<Member<TextTrack>> affectedTracks; for (const auto& missedCue : missedCues) { // 9 - For each text track cue in missed cues, prepare an event named enter // for the TextTrackCue object with the text track cue start time. eventTasks.append( std::make_pair(missedCue.data()->startTime(), missedCue.data())); // 10 - For each text track [...] in missed cues, prepare an event // named exit for the TextTrackCue object with the with the later of // the text track cue end time and the text track cue start time. // Note: An explicit task is added only if the cue is NOT a zero or // negative length cue. Otherwise, the need for an exit event is // checked when these tasks are actually queued below. This doesn't // affect sorting events before dispatch either, because the exit // event has the same time as the enter event. if (missedCue.data()->startTime() < missedCue.data()->endTime()) { eventTasks.append( std::make_pair(missedCue.data()->endTime(), missedCue.data())); } } for (const auto& previousCue : previousCues) { // 10 - For each text track cue in other cues that has its text // track cue active flag set prepare an event named exit for the // TextTrackCue object with the text track cue end time. if (!currentCues.contains(previousCue)) { eventTasks.append( std::make_pair(previousCue.data()->endTime(), previousCue.data())); } } for (const auto& currentCue : currentCues) { // 11 - For each text track cue in current cues that does not have its // text track cue active flag set, prepare an event named enter for the // TextTrackCue object with the text track cue start time. if (!previousCues.contains(currentCue)) { eventTasks.append( std::make_pair(currentCue.data()->startTime(), currentCue.data())); } } // 12 - Sort the tasks in events in ascending time order (tasks with earlier // times first). nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare); for (const auto& task : eventTasks) { if (!affectedTracks.contains(task.second->track())) affectedTracks.append(task.second->track()); // 13 - Queue each task in events, in list order. // Each event in eventTasks may be either an enterEvent or an exitEvent, // depending on the time that is associated with the event. This // correctly identifies the type of the event, if the startTime is // less than the endTime in the cue. if (task.second->startTime() >= task.second->endTime()) { mediaElement.scheduleEvent( createEventWithTarget(EventTypeNames::enter, task.second.get())); mediaElement.scheduleEvent( createEventWithTarget(EventTypeNames::exit, task.second.get())); } else { bool isEnterEvent = task.first == task.second->startTime(); AtomicString eventName = isEnterEvent ? EventTypeNames::enter : EventTypeNames::exit; mediaElement.scheduleEvent( createEventWithTarget(eventName, task.second.get())); } } // 14 - Sort affected tracks in the same order as the text tracks appear in // the media element's list of text tracks, and remove duplicates. nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompare); // 15 - For each text track in affected tracks, in the list order, queue a // task to fire a simple event named cuechange at the TextTrack object, and, // ... for (const auto& track : affectedTracks) { mediaElement.scheduleEvent( createEventWithTarget(EventTypeNames::cuechange, track.get())); // ... if the text track has a corresponding track element, to then fire a // simple event named cuechange at the track element as well. if (track->trackType() == TextTrack::TrackElement) { HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(track.get())->trackElement(); DCHECK(trackElement); mediaElement.scheduleEvent( createEventWithTarget(EventTypeNames::cuechange, trackElement)); } } // 16 - Set the text track cue active flag of all the cues in the current // cues, and unset the text track cue active flag of all the cues in the // other cues. for (const auto& cue : currentCues) cue.data()->setIsActive(true); for (const auto& previousCue : previousCues) { if (!currentCues.contains(previousCue)) { TextTrackCue* cue = previousCue.data(); cue->setIsActive(false); cue->removeDisplayTree(); } } // Update the current active cues. m_currentlyActiveCues = currentCues; mediaElement.updateTextTrackDisplay(); }
bool TextTrack::hasCue(TextTrackCue* cue, TextTrackCue::CueMatchRules match) { if (cue->startTime() < 0 || cue->endTime() < 0) return false; if (!m_cues || !m_cues->length()) return false; size_t searchStart = 0; size_t searchEnd = m_cues->length(); while (1) { ASSERT(searchStart <= m_cues->length()); ASSERT(searchEnd <= m_cues->length()); TextTrackCue* existingCue; // Cues in the TextTrackCueList are maintained in start time order. if (searchStart == searchEnd) { if (!searchStart) return false; // If there is more than one cue with the same start time, back up to first one so we // consider all of them. while (searchStart >= 2 && cue->hasEquivalentStartTime(*m_cues->item(searchStart - 2))) --searchStart; bool firstCompare = true; while (1) { if (!firstCompare) ++searchStart; firstCompare = false; if (searchStart > m_cues->length()) return false; existingCue = m_cues->item(searchStart - 1); if (!existingCue) return false; if (existingCue->doesExtendCue(*cue)) { existingCue->setEndTime(cue->endTime(), IGNORE_EXCEPTION); return true; } if (cue->startTime() > (existingCue->startTime() + startTimeVariance())) return false; if (existingCue->isEqual(*cue, match)) return true; } } size_t index = (searchStart + searchEnd) / 2; existingCue = m_cues->item(index); if ((cue->startTime() + startTimeVariance()) < existingCue->startTime() || (match != TextTrackCue::IgnoreDuration && cue->hasEquivalentStartTime(*existingCue) && cue->endTime() > existingCue->endTime())) searchEnd = index; else searchStart = index + 1; } ASSERT_NOT_REACHED(); return false; }