void TextTrack::setLanguage(const AtomicString& language) { // 11.1 language, on setting: // 1. If the value being assigned to this attribute is not an empty string or a BCP 47 language // tag[BCP47], then abort these steps. // FIXME(123926): Validate the BCP47-ness of langague. // 2. Update this attribute to the new value. TrackBase::setLanguage(language); // 3. If the sourceBuffer attribute on this track is not null, then queue a task to fire a simple // event named change at sourceBuffer.textTracks. if (m_sourceBuffer) m_sourceBuffer->textTracks()->scheduleChangeEvent(); // 4. Queue a task to fire a simple event named change at the TextTrackList object referenced by // the textTracks attribute on the HTMLMediaElement. if (mediaElement()) mediaElement()->textTracks()->scheduleChangeEvent(); }
void MediaControls::playbackStarted() { m_currentTimeDisplay->show(); m_durationDisplay->hide(); updatePlayState(); m_timeline->setPosition(mediaElement().currentTime()); updateCurrentTimeDisplay(); startHideMediaControlsTimer(); }
bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const { // Never hide for a media element without visual representation. if (!mediaElement().hasVideo() || mediaElement().isPlayingRemotely()) return false; // Don't hide if the mouse is over the controls. const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover; if (!ignoreControlsHover && m_panel->hovered()) return false; // Don't hide if the mouse is over the video area. const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover; if (!ignoreVideoHover && m_isMouseOverControls) return false; // Don't hide if focus is on the HTMLMediaElement or within the // controls/shadow tree. (Perform the checks separately to avoid going // through all the potential ancestor hosts for the focused element.) const bool ignoreFocus = behaviorFlags & IgnoreFocus; if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement()))) return false; return true; }
void MediaControls::refreshCastButtonVisibilityWithoutUpdate() { if (!shouldShowCastButton(mediaElement())) { m_castButton->setIsWanted(false); m_overlayCastButton->setIsWanted(false); return; } // The reason for the autoplay test is that some pages (e.g. vimeo.com) have // an autoplay background video, which doesn't autoplay on Chrome for Android // (we prevent it) so starts paused. In such cases we don't want to // automatically show the cast button, since it looks strange and is unlikely // to correspond with anything the user wants to do. If a user does want to // cast a paused autoplay video then they can still do so by touching or // clicking on the video, which will cause the cast button to appear. if (!mediaElement().shouldShowControls() && !mediaElement().autoplay() && mediaElement().paused()) { // Note that this is a case where we add the overlay cast button // without wanting the panel cast button. We depend on the fact // that computeWhichControlsFit() won't change overlay cast button // visibility in the case where the cast button isn't wanted. // We don't call compute...() here, but it will be called as // non-cast changes (e.g., resize) occur. If the panel button // is shown, however, compute...() will take control of the // overlay cast button if it needs to hide it from the panel. m_overlayCastButton->tryShowOverlay(); m_castButton->setIsWanted(false); } else if (mediaElement().shouldShowControls()) { m_overlayCastButton->setIsWanted(false); m_castButton->setIsWanted(true); // Check that the cast button actually fits on the bar. For the // newMediaPlaybackUiEnabled case, we let computeWhichControlsFit() // handle this. if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled() && m_fullscreenButton->getBoundingClientRect()->right() > m_panel->getBoundingClientRect()->right()) { m_castButton->setIsWanted(false); m_overlayCastButton->tryShowOverlay(); } } }
void MediaControls::updateVolume() { m_muteButton->updateDisplayType(); // Invalidate the mute button because it paints differently according to volume. if (LayoutObject* layoutObject = m_muteButton->layoutObject()) layoutObject->setShouldDoFullPaintInvalidation(); if (mediaElement().muted()) m_volumeSlider->setVolume(0); else m_volumeSlider->setVolume(mediaElement().volume()); // Update the visibility of our audio elements. // We never want the volume slider if there's no audio. // If there is audio, then we want it unless hiding audio is enabled and // we prefer to hide it. BatchedControlUpdate batch(this); m_volumeSlider->setIsWanted(mediaElement().hasAudio() && !(m_allowHiddenVolumeControls && preferHiddenVolumeControls(document()))); // The mute button is a little more complicated. If enableNewMediaPlaybackUi // is true, then we choose to hide or show the mute button to save space. // If enableNew* is not set, then we never touch the mute button, and // instead leave it to the CSS. // Note that this is why m_allowHiddenVolumeControls isn't rolled into prefer...(). if (m_allowHiddenVolumeControls) { // If there is no audio track, then hide the mute button. If there // is an audio track, then we always show the mute button unless // we prefer to hide it and the media isn't muted. If it's muted, // then we show it to let the user unmute it. In this case, we don't // want to re-hide the mute button later. m_keepMuteButton |= mediaElement().muted(); m_muteButton->setIsWanted(mediaElement().hasAudio() && (!preferHiddenVolumeControls(document()) || m_keepMuteButton)); } // Invalidate the volume slider because it paints differently according to volume. if (LayoutObject* layoutObject = m_volumeSlider->layoutObject()) layoutObject->setShouldDoFullPaintInvalidation(); }
// MediaPlayer ------------------------------------------------- void WebMediaPlayerClientImpl::load(WebMediaPlayer::LoadType loadType, const WTF::String& url, WebMediaPlayer::CORSMode corsMode) { ASSERT(!m_webMediaPlayer); // FIXME: Remove this cast LocalFrame* frame = mediaElement().document().frame(); WebURL poster = m_client->mediaPlayerPosterURL(); KURL kurl(ParsedURLString, url); m_webMediaPlayer = createWebMediaPlayer(this, kurl, frame, HTMLMediaElementEncryptedMedia::contentDecryptionModule(mediaElement())); if (!m_webMediaPlayer) return; if (mediaElement().layoutObject()) mediaElement().layoutObject()->setShouldDoFullPaintInvalidation(); #if ENABLE(WEB_AUDIO) // Make sure if we create/re-create the WebMediaPlayer that we update our wrapper. m_audioSourceProvider.wrap(m_webMediaPlayer->audioSourceProvider()); #endif m_webMediaPlayer->setVolume(mediaElement().effectiveMediaVolume()); m_webMediaPlayer->setPoster(poster); setPreload(mediaElement().effectivePreloadType()); m_webMediaPlayer->load(loadType, kurl, corsMode); if (mediaElement().isFullscreen()) m_webMediaPlayer->enterFullscreen(); }
void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*) { unsigned behaviorFlags = m_hideTimerBehaviorFlags | IgnoreFocus | IgnoreVideoHover; m_hideTimerBehaviorFlags = IgnoreNone; if (mediaElement().togglePlayStateWillPlay()) return; if (!shouldHideMediaControls(behaviorFlags)) return; makeTransparent(); }
void InbandGenericTextTrack::updateCueFromCueData(TextTrackCueGeneric* cue, GenericCueData* cueData) { cue->willChange(); cue->setStartTime(cueData->startTime(), IGNORE_EXCEPTION); double endTime = cueData->endTime(); if (std::isinf(endTime) && mediaElement()) endTime = mediaElement()->duration(); cue->setEndTime(endTime, IGNORE_EXCEPTION); cue->setText(cueData->content()); cue->setId(cueData->id()); cue->setBaseFontSizeRelativeToVideoHeight(cueData->baseFontSize()); cue->setFontSizeMultiplier(cueData->relativeFontSize()); cue->setFontName(cueData->fontName()); if (cueData->position() > 0) cue->setPosition(lround(cueData->position()), IGNORE_EXCEPTION); if (cueData->line() > 0) cue->setLine(lround(cueData->line()), IGNORE_EXCEPTION); if (cueData->size() > 0) cue->setSize(lround(cueData->size()), IGNORE_EXCEPTION); if (cueData->backgroundColor().isValid()) cue->setBackgroundColor(cueData->backgroundColor().rgb()); if (cueData->foregroundColor().isValid()) cue->setForegroundColor(cueData->foregroundColor().rgb()); if (cueData->highlightColor().isValid()) cue->setHighlightColor(cueData->highlightColor().rgb()); if (cueData->align() == GenericCueData::Start) cue->setAlign(ASCIILiteral("start"), IGNORE_EXCEPTION); else if (cueData->align() == GenericCueData::Middle) cue->setAlign(ASCIILiteral("middle"), IGNORE_EXCEPTION); else if (cueData->align() == GenericCueData::End) cue->setAlign(ASCIILiteral("end"), IGNORE_EXCEPTION); cue->setSnapToLines(false); cue->didChange(); }
void MediaControls::reset() { EventDispatchForbiddenScope::AllowUserAgentEvents allowEventsInShadow; BatchedControlUpdate batch(this); const double duration = mediaElement().duration(); m_durationDisplay->setTextContent( LayoutTheme::theme().formatMediaControlsTime(duration)); m_durationDisplay->setCurrentValue(duration); // Show everything that we might hide. // If we don't have a duration, then mark it to be hidden. For the // old UI case, want / don't want is the same as show / hide since // it is never marked as not fitting. m_durationDisplay->setIsWanted(std::isfinite(duration)); m_currentTimeDisplay->setIsWanted(true); m_timeline->setIsWanted(true); // If the player has entered an error state, force it into the paused state. if (mediaElement().error()) mediaElement().pause(); updatePlayState(); updateCurrentTimeDisplay(); m_timeline->setDuration(duration); m_timeline->setPosition(mediaElement().currentTime()); onVolumeChange(); onTextTracksAddedOrRemoved(); m_fullscreenButton->setIsWanted(shouldShowFullscreenButton(mediaElement())); refreshCastButtonVisibilityWithoutUpdate(); m_downloadButton->setIsWanted( m_downloadButton->shouldDisplayDownloadButton()); }
void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*) { unsigned behaviorFlags = m_hideTimerBehaviorFlags | IgnoreFocus | IgnoreVideoHover; m_hideTimerBehaviorFlags = IgnoreNone; if (mediaElement().paused()) return; if (!shouldHideMediaControls(behaviorFlags)) return; makeTransparent(); m_overlayCastButton->setIsWanted(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() && mediaElement() && m_cues) mediaElement()->textTrackRemoveCues(this, m_cues.get()); if (mode != showingKeyword() && m_cues) for (size_t i = 0; i < m_cues->length(); ++i) m_cues->item(i)->removeDisplayTree(); m_mode = mode; if (mediaElement()) mediaElement()->textTrackModeChanged(this); }
void MediaControls::playbackStarted() { BatchedControlUpdate batch(this); if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) { m_currentTimeDisplay->setIsWanted(true); m_durationDisplay->setIsWanted(false); } updatePlayState(); m_timeline->setPosition(mediaElement().currentTime()); updateCurrentTimeDisplay(); startHideMediaControlsTimer(); }
bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const { // Never hide for a media element without visual representation. if (!mediaElement().isHTMLVideoElement() || !mediaElement().hasVideo() || mediaElement().isPlayingRemotely()) { return false; } // Keep the controls visible as long as the timer is running. const bool ignoreWaitForTimer = behaviorFlags & IgnoreWaitForTimer; if (!ignoreWaitForTimer && m_keepShowingUntilTimerFires) return false; // Don't hide if the mouse is over the controls. const bool ignoreControlsHover = behaviorFlags & IgnoreControlsHover; if (!ignoreControlsHover && m_panel->isHovered()) return false; // Don't hide if the mouse is over the video area. const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover; if (!ignoreVideoHover && m_isMouseOverControls) return false; // Don't hide if focus is on the HTMLMediaElement or within the // controls/shadow tree. (Perform the checks separately to avoid going // through all the potential ancestor hosts for the focused element.) const bool ignoreFocus = behaviorFlags & IgnoreFocus; if (!ignoreFocus && (mediaElement().isFocused() || contains(document().focusedElement()))) { return false; } // Don't hide the media controls when a panel is showing. if (m_textTrackList->isWanted() || m_overflowList->isWanted()) return false; return true; }
void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*) { if (mediaElement().togglePlayStateWillPlay()) return; unsigned behaviorFlags = IgnoreFocus | IgnoreVideoHover; // FIXME: improve this check, see http://www.crbug.com/401177. if (!deviceSupportsMouse(document())) { behaviorFlags |= IgnoreControlsHover; } if (!shouldHideMediaControls(behaviorFlags)) return; makeTransparent(); }
void MediaControls::refreshCastButtonVisibility() { if (mediaElement().hasRemoteRoutes()) { // The reason for the autoplay test is that some pages (e.g. vimeo.com) have an autoplay background video, which // doesn't autoplay on Chrome for Android (we prevent it) so starts paused. In such cases we don't want to automatically // show the cast button, since it looks strange and is unlikely to correspond with anything the user wants to do. // If a user does want to cast a paused autoplay video then they can still do so by touching or clicking on the // video, which will cause the cast button to appear. if (!mediaElement().shouldShowControls() && !mediaElement().autoplay() && mediaElement().paused()) { showOverlayCastButton(); } else if (mediaElement().shouldShowControls()) { m_overlayCastButton->hide(); m_castButton->show(); // Check that the cast button actually fits on the bar. if (m_fullScreenButton->getBoundingClientRect()->right() > m_panel->getBoundingClientRect()->right()) { m_castButton->hide(); tryShowOverlayCastButton(); } } } else { m_castButton->hide(); m_overlayCastButton->hide(); } }
void MediaControls::reset() { const bool useNewUi = RuntimeEnabledFeatures::newMediaPlaybackUiEnabled(); BatchedControlUpdate batch(this); m_allowHiddenVolumeControls = useNewUi; const double duration = mediaElement().duration(); m_durationDisplay->setInnerText(LayoutTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION); m_durationDisplay->setCurrentValue(duration); if (useNewUi) { // Show everything that we might hide. // If we don't have a duration, then mark it to be hidden. For the // old UI case, want / don't want is the same as show / hide since // it is never marked as not fitting. m_durationDisplay->setIsWanted(std::isfinite(duration)); m_currentTimeDisplay->setIsWanted(true); m_timeline->setIsWanted(true); } updatePlayState(); updateCurrentTimeDisplay(); m_timeline->setDuration(duration); m_timeline->setPosition(mediaElement().currentTime()); updateVolume(); refreshClosedCaptionsButtonVisibility(); m_fullScreenButton->setIsWanted(shouldShowFullscreenButton(mediaElement())); refreshCastButtonVisibilityWithoutUpdate(); }
bool HTMLTrackElement::canLoadUrl(const KURL& url) { HTMLMediaElement* parent = mediaElement(); if (!parent) return false; if (url.isEmpty()) return false; if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) { WTF_LOG(Media, "HTMLTrackElement::canLoadUrl(%s) -> rejected by Content Security Policy", urlForLoggingTrack(url).utf8().data()); return false; } return true; }
bool HTMLTrackElement::canLoadUrl(const KURL& url) { HTMLMediaElement* parent = mediaElement(); if (!parent) return false; if (url.isEmpty()) return false; if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) { DVLOG(TRACK_LOG_LEVEL) << "canLoadUrl(" << urlForLoggingTrack(url) << ") -> rejected by Content Security Policy"; return false; } return true; }
void MediaControls::defaultEventHandler(Event* event) { HTMLDivElement::defaultEventHandler(event); // Add IgnoreControlsHover to m_hideTimerBehaviorFlags when we see a touch event, // to allow the hide-timer to do the right thing when it fires. // FIXME: Preferably we would only do this when we're actually handling the event // here ourselves. bool wasLastEventTouch = event->isTouchEvent() || event->isGestureEvent() || (event->isMouseEvent() && toMouseEvent(event)->fromTouch()); m_hideTimerBehaviorFlags |= wasLastEventTouch ? IgnoreControlsHover : IgnoreNone; if (event->type() == EventTypeNames::mouseover) { if (!containsRelatedTarget(event)) { m_isMouseOverControls = true; if (!mediaElement().togglePlayStateWillPlay()) { makeOpaque(); if (shouldHideMediaControls()) startHideMediaControlsTimer(); } } return; } if (event->type() == EventTypeNames::mouseout) { if (!containsRelatedTarget(event)) { m_isMouseOverControls = false; stopHideMediaControlsTimer(); } return; } if (event->type() == EventTypeNames::mousemove) { // When we get a mouse move, show the media controls, and start a timer // that will hide the media controls after a 3 seconds without a mouse move. makeOpaque(); refreshCastButtonVisibility(); if (shouldHideMediaControls(IgnoreVideoHover)) startHideMediaControlsTimer(); return; } }
void HTMLTrackElement::scheduleLoad() { WTF_LOG(Media, "HTMLTrackElement::scheduleLoad"); // 1. If another occurrence of this algorithm is already running for this text track and its track element, // abort these steps, letting that other algorithm take care of this element. if (m_loadTimer.isActive()) return; // 2. If the text track's text track mode is not set to one of hidden or showing, abort these steps. if (ensureTrack()->mode() != TextTrack::hiddenKeyword() && ensureTrack()->mode() != TextTrack::showingKeyword()) return; // 3. If the text track's track element does not have a media element as a parent, abort these steps. if (!mediaElement()) return; // 4. Run the remainder of these steps asynchronously, allowing whatever caused these steps to run to continue. m_loadTimer.startOneShot(0, FROM_HERE); }
bool HTMLTrackElement::canLoadUrl(const KURL& url) { HTMLMediaElement* parent = mediaElement(); if (!parent) return false; // 4.8.10.12.3 Sourcing out-of-band text tracks // 4. Download: If URL is not the empty string, perform a potentially CORS-enabled fetch of URL, with the // mode being the state of the media element's crossorigin content attribute, the origin being the // origin of the media element's Document, and the default origin behaviour set to fail. if (url.isEmpty()) return false; if (!document().contentSecurityPolicy()->allowMediaFromSource(url)) { WTF_LOG(Media, "HTMLTrackElement::canLoadUrl(%s) -> rejected by Content Security Policy", urlForLoggingTrack(url).utf8().data()); return false; } return true; }
LayoutSize RenderVideo::calculateIntrinsicSize() { HTMLVideoElement* video = videoElement(); // Spec text from 4.8.6 // // The intrinsic width of a video element's playback area is the intrinsic width // of the video resource, if that is available; otherwise it is the intrinsic // width of the poster frame, if that is available; otherwise it is 300 CSS pixels. // // The intrinsic height of a video element's playback area is the intrinsic height // of the video resource, if that is available; otherwise it is the intrinsic // height of the poster frame, if that is available; otherwise it is 150 CSS pixels. MediaPlayer* player = mediaElement()->player(); if (player && video->readyState() >= HTMLVideoElement::HAVE_METADATA) { LayoutSize size = player->naturalSize(); if (!size.isEmpty()) return size; } if (video->shouldDisplayPosterImage() && !m_cachedImageSize.isEmpty() && !imageResource()->errorOccurred()) return m_cachedImageSize; // 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 (video->hasAttribute(widthAttr) && video->hasAttribute(heightAttr)) return LayoutSize(video->width(), video->height()); // <video> in standalone media documents should not use the default 300x150 // size since they also have audio-only files. 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). if (video->ownerDocument() && video->ownerDocument()->isMediaDocument()) return LayoutSize(defaultSize().width(), 1); return defaultSize(); }
void HTMLTrackElement::scheduleLoad() { WTF_LOG(Media, "HTMLTrackElement::scheduleLoad"); // 1. If another occurrence of this algorithm is already running for this text track and its track element, // abort these steps, letting that other algorithm take care of this element. if (m_loadTimer.isActive()) return; // 2. If the text track's text track mode is not set to one of hidden or showing, abort these steps. if (ensureTrack()->mode() != TextTrack::hiddenKeyword() && ensureTrack()->mode() != TextTrack::showingKeyword()) return; // 3. If the text track's track element does not have a media element as a parent, abort these steps. if (!mediaElement()) return; // 4. Run the remainder of these steps in parallel, allowing whatever caused these steps to run to continue. m_loadTimer.startOneShot(0, BLINK_FROM_HERE); // 5. Top: Await a stable state. The synchronous section consists of the following steps. (The steps in the // synchronous section are marked with [X]) // FIXME: We use a timer to approximate a "stable state" - i.e. this is not 100% per spec. }
void LayoutMedia::layout() { LayoutSize oldSize = contentBoxRect().size(); LayoutImage::layout(); LayoutRect newRect = contentBoxRect(); LayoutState state(*this); Optional<LayoutUnit> newPanelWidth; // Iterate the children in reverse order so that the media controls are laid // out before the text track container. This is to ensure that the text // track rendering has an up-to-date position of the media controls for // overlap checking, see LayoutVTTCue. #if ENABLE(ASSERT) bool seenTextTrackContainer = false; #endif for (LayoutObject* child = m_children.lastChild(); child; child = child->previousSibling()) { #if ENABLE(ASSERT) if (child->node()->isMediaControls()) ASSERT(!seenTextTrackContainer); else if (child->node()->isTextTrackContainer()) seenTextTrackContainer = true; else ASSERT_NOT_REACHED(); #endif // TODO(mlamouri): we miss some layouts because needsLayout returns false in // some cases where we want to change the width of the controls because the // visible viewport has changed for example. if (newRect.size() == oldSize && !child->needsLayout()) continue; LayoutUnit width = newRect.width(); if (child->node()->isMediaControls()) { width = computePanelWidth(newRect); if (width != oldSize.width()) newPanelWidth = width; } LayoutBox* layoutBox = toLayoutBox(child); layoutBox->setLocation(newRect.location()); // TODO(foolip): Remove the mutableStyleRef() and depend on CSS // width/height: inherit to match the media element size. layoutBox->mutableStyleRef().setHeight(Length(newRect.height(), Fixed)); layoutBox->mutableStyleRef().setWidth(Length(width, Fixed)); layoutBox->forceLayout(); } clearNeedsLayout(); // Notify our MediaControls that a layout has happened. if (mediaElement() && mediaElement()->mediaControls() && newPanelWidth.has_value()) { mediaElement()->mediaControls()->notifyPanelWidthChanged( newPanelWidth.value()); } }
void LayoutMedia::notifyPositionMayHaveChanged(const IntRect& visibleRect) { // Tell our element about it. if (HTMLMediaElement* element = mediaElement()) element->notifyPositionMayHaveChanged(visibleRect); }
void MediaSource::removeSourceBuffer(SourceBuffer& buffer, ExceptionCode& ec) { LOG(MediaSource, "MediaSource::removeSourceBuffer() %p", this); Ref<SourceBuffer> protect(buffer); // 2. If sourceBuffer specifies an object that is not in sourceBuffers then // throw a NOT_FOUND_ERR exception and abort these steps. if (!m_sourceBuffers->length() || !m_sourceBuffers->contains(buffer)) { ec = NOT_FOUND_ERR; return; } // 3. If the sourceBuffer.updating attribute equals true, then run the following steps: ... buffer.abortIfUpdating(); // 4. Let SourceBuffer audioTracks list equal the AudioTrackList object returned by sourceBuffer.audioTracks. RefPtr<AudioTrackList> audioTracks = buffer.audioTracks(); // 5. If the SourceBuffer audioTracks list is not empty, then run the following steps: if (audioTracks->length()) { // 5.1 Let HTMLMediaElement audioTracks list equal the AudioTrackList object returned by the audioTracks // attribute on the HTMLMediaElement. // 5.2 Let the removed enabled audio track flag equal false. bool removedEnabledAudioTrack = false; // 5.3 For each AudioTrack object in the SourceBuffer audioTracks list, run the following steps: while (audioTracks->length()) { auto& track = *audioTracks->lastItem(); // 5.3.1 Set the sourceBuffer attribute on the AudioTrack object to null. track.setSourceBuffer(nullptr); // 5.3.2 If the enabled attribute on the AudioTrack object is true, then set the removed enabled // audio track flag to true. if (track.enabled()) removedEnabledAudioTrack = true; // 5.3.3 Remove the AudioTrack object from the HTMLMediaElement audioTracks list. // 5.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement audioTracks list. if (mediaElement()) mediaElement()->removeAudioTrack(track); // 5.3.5 Remove the AudioTrack object from the SourceBuffer audioTracks list. // 5.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not // cancelable, and that uses the TrackEvent interface, at the SourceBuffer audioTracks list. audioTracks->remove(track); } // 5.4 If the removed enabled audio track flag equals true, then queue a task to fire a simple event // named change at the HTMLMediaElement audioTracks list. if (removedEnabledAudioTrack) mediaElement()->audioTracks().scheduleChangeEvent(); } // 6. Let SourceBuffer videoTracks list equal the VideoTrackList object returned by sourceBuffer.videoTracks. RefPtr<VideoTrackList> videoTracks = buffer.videoTracks(); // 7. If the SourceBuffer videoTracks list is not empty, then run the following steps: if (videoTracks->length()) { // 7.1 Let HTMLMediaElement videoTracks list equal the VideoTrackList object returned by the videoTracks // attribute on the HTMLMediaElement. // 7.2 Let the removed selected video track flag equal false. bool removedSelectedVideoTrack = false; // 7.3 For each VideoTrack object in the SourceBuffer videoTracks list, run the following steps: while (videoTracks->length()) { auto& track = *videoTracks->lastItem(); // 7.3.1 Set the sourceBuffer attribute on the VideoTrack object to null. track.setSourceBuffer(nullptr); // 7.3.2 If the selected attribute on the VideoTrack object is true, then set the removed selected // video track flag to true. if (track.selected()) removedSelectedVideoTrack = true; // 7.3.3 Remove the VideoTrack object from the HTMLMediaElement videoTracks list. // 7.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement videoTracks list. if (mediaElement()) mediaElement()->removeVideoTrack(track); // 7.3.5 Remove the VideoTrack object from the SourceBuffer videoTracks list. // 7.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not // cancelable, and that uses the TrackEvent interface, at the SourceBuffer videoTracks list. videoTracks->remove(track); } // 7.4 If the removed selected video track flag equals true, then queue a task to fire a simple event // named change at the HTMLMediaElement videoTracks list. if (removedSelectedVideoTrack) mediaElement()->videoTracks().scheduleChangeEvent(); } // 8. Let SourceBuffer textTracks list equal the TextTrackList object returned by sourceBuffer.textTracks. RefPtr<TextTrackList> textTracks = buffer.textTracks(); // 9. If the SourceBuffer textTracks list is not empty, then run the following steps: if (textTracks->length()) { // 9.1 Let HTMLMediaElement textTracks list equal the TextTrackList object returned by the textTracks // attribute on the HTMLMediaElement. // 9.2 Let the removed enabled text track flag equal false. bool removedEnabledTextTrack = false; // 9.3 For each TextTrack object in the SourceBuffer textTracks list, run the following steps: while (textTracks->length()) { auto& track = *textTracks->lastItem(); // 9.3.1 Set the sourceBuffer attribute on the TextTrack object to null. track.setSourceBuffer(nullptr); // 9.3.2 If the mode attribute on the TextTrack object is set to "showing" or "hidden", then // set the removed enabled text track flag to true. if (track.mode() == TextTrack::Mode::Showing || track.mode() == TextTrack::Mode::Hidden) removedEnabledTextTrack = true; // 9.3.3 Remove the TextTrack object from the HTMLMediaElement textTracks list. // 9.3.4 Queue a task to fire a trusted event named removetrack, that does not bubble and is not // cancelable, and that uses the TrackEvent interface, at the HTMLMediaElement textTracks list. if (mediaElement()) mediaElement()->removeTextTrack(track); // 9.3.5 Remove the TextTrack object from the SourceBuffer textTracks list. // 9.3.6 Queue a task to fire a trusted event named removetrack, that does not bubble and is not // cancelable, and that uses the TrackEvent interface, at the SourceBuffer textTracks list. textTracks->remove(track); } // 9.4 If the removed enabled text track flag equals true, then queue a task to fire a simple event // named change at the HTMLMediaElement textTracks list. if (removedEnabledTextTrack) mediaElement()->textTracks().scheduleChangeEvent(); } // 10. If sourceBuffer is in activeSourceBuffers, then remove sourceBuffer from activeSourceBuffers ... m_activeSourceBuffers->remove(buffer); // 11. Remove sourceBuffer from sourceBuffers and fire a removesourcebuffer event // on that object. m_sourceBuffers->remove(buffer); // 12. Destroy all resources for sourceBuffer. buffer.removedFromMediaSource(); }
void MediaSource::monitorSourceBuffers() { // 2.4.4 SourceBuffer Monitoring // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#buffer-monitoring // Note, the behavior if activeSourceBuffers is empty is undefined. if (!m_activeSourceBuffers) { m_private->setReadyState(MediaPlayer::HaveNothing); return; } // http://w3c.github.io/media-source/#buffer-monitoring, change from 11 December 2014 // ↳ If the the HTMLMediaElement.readyState attribute equals HAVE_NOTHING: if (mediaElement()->readyState() == HTMLMediaElement::HAVE_NOTHING) { // 1. Abort these steps. return; } // ↳ If buffered for all objects in activeSourceBuffers do not contain TimeRanges for the current // playback position: auto begin = m_activeSourceBuffers->begin(); auto end = m_activeSourceBuffers->end(); if (std::all_of(begin, end, [](RefPtr<SourceBuffer>& sourceBuffer) { return !sourceBuffer->hasCurrentTime(); })) { // 1. Set the HTMLMediaElement.readyState attribute to HAVE_METADATA. // 2. If this is the first transition to HAVE_METADATA, then queue a task to fire a simple event // named loadedmetadata at the media element. m_private->setReadyState(MediaPlayer::HaveMetadata); // 3. Abort these steps. return; } // ↳ If buffered for all objects in activeSourceBuffers contain TimeRanges that include the current // playback position and enough data to ensure uninterrupted playback: if (std::all_of(begin, end, [](RefPtr<SourceBuffer>& sourceBuffer) { return sourceBuffer->hasFutureTime() && sourceBuffer->canPlayThrough(); })) { // 1. Set the HTMLMediaElement.readyState attribute to HAVE_ENOUGH_DATA. // 2. Queue a task to fire a simple event named canplaythrough at the media element. // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA. m_private->setReadyState(MediaPlayer::HaveEnoughData); if (m_pendingSeekTime.isValid()) completeSeek(); // 4. Abort these steps. return; } // ↳ If buffered for all objects in activeSourceBuffers contain a TimeRange that includes // the current playback position and some time beyond the current playback position, then run the following steps: if (std::all_of(begin, end, [](RefPtr<SourceBuffer>& sourceBuffer) { return sourceBuffer->hasFutureTime(); })) { // 1. Set the HTMLMediaElement.readyState attribute to HAVE_FUTURE_DATA. // 2. If the previous value of HTMLMediaElement.readyState was less than HAVE_FUTURE_DATA, then queue a task to fire a simple event named canplay at the media element. // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA. m_private->setReadyState(MediaPlayer::HaveFutureData); if (m_pendingSeekTime.isValid()) completeSeek(); // 4. Abort these steps. return; } // ↳ If buffered for at least one object in activeSourceBuffers contains a TimeRange that ends // at the current playback position and does not have a range covering the time immediately // after the current position: // NOTE: Logically, !(all objects do not contain currentTime) == (some objects contain current time) // 1. Set the HTMLMediaElement.readyState attribute to HAVE_CURRENT_DATA. // 2. If this is the first transition to HAVE_CURRENT_DATA, then queue a task to fire a simple // event named loadeddata at the media element. // 3. Playback is suspended at this point since the media element doesn't have enough data to // advance the media timeline. m_private->setReadyState(MediaPlayer::HaveCurrentData); if (m_pendingSeekTime.isValid()) completeSeek(); // 4. Abort these steps. }
void HTMLTrackElement::setReadyState(ReadyState state) { ensureTrack()->setReadinessState(static_cast<TextTrack::ReadinessState>(state)); if (HTMLMediaElement* parent = mediaElement()) return parent->textTrackReadyStateChanged(m_track.get()); }
void AudioTrack::willRemove(TrackPrivateBase* trackPrivate) { ASSERT_UNUSED(trackPrivate, trackPrivate == m_private); mediaElement()->removeAudioTrack(this); }
void MediaControls::resetHideMediaControlsTimer() { stopHideMediaControlsTimer(); if (!mediaElement().paused()) startHideMediaControlsTimer(); }