| Index: Source/core/html/HTMLMediaElement.cpp
|
| diff --git a/Source/core/html/HTMLMediaElement.cpp b/Source/core/html/HTMLMediaElement.cpp
|
| index cec3e8f28f9a7b68d9fdd7a07390770f9473bf65..c0269508f989d9d66968ca6cfa968e0b0e3dbb9d 100644
|
| --- a/Source/core/html/HTMLMediaElement.cpp
|
| +++ b/Source/core/html/HTMLMediaElement.cpp
|
| @@ -54,9 +54,13 @@
|
| #include "core/html/MediaKeyEvent.h"
|
| #include "core/html/TimeRanges.h"
|
| #include "core/html/shadow/MediaControls.h"
|
| +#include "core/html/track/AudioTrack.h"
|
| +#include "core/html/track/AudioTrackList.h"
|
| #include "core/html/track/InbandTextTrack.h"
|
| #include "core/html/track/TextTrackCueList.h"
|
| #include "core/html/track/TextTrackList.h"
|
| +#include "core/html/track/VideoTrack.h"
|
| +#include "core/html/track/VideoTrackList.h"
|
| #include "core/loader/FrameLoader.h"
|
| #include "core/rendering/RenderLayerCompositor.h"
|
| #include "core/rendering/RenderVideo.h"
|
| @@ -302,6 +306,8 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum
|
| , m_haveVisibleTextTrack(false)
|
| , m_processingPreferenceChange(false)
|
| , m_lastTextTrackUpdateTime(-1)
|
| + , m_audioTracks(nullptr)
|
| + , m_videoTracks(nullptr)
|
| , m_textTracks(nullptr)
|
| , m_ignoreTrackDisplayUpdate(0)
|
| #if ENABLE(WEB_AUDIO)
|
| @@ -335,12 +341,12 @@ HTMLMediaElement::~HTMLMediaElement()
|
| m_asyncEventQueue->close();
|
|
|
| setShouldDelayLoadEvent(false);
|
| + if (m_audioTracks)
|
| + m_audioTracks->clearOwner();
|
| + if (m_videoTracks)
|
| + m_videoTracks->clearOwner();
|
| if (m_textTracks)
|
| - m_textTracks->clearOwner();
|
| - if (m_textTracks) {
|
| - for (unsigned i = 0; i < m_textTracks->length(); ++i)
|
| - m_textTracks->item(i)->clearClient();
|
| - }
|
| + m_textTracks->clearOwnerAndClients();
|
|
|
| if (m_mediaController) {
|
| m_mediaController->removeMediaElement(this);
|
| @@ -663,14 +669,38 @@ void HTMLMediaElement::prepareForLoad()
|
|
|
| // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps
|
| if (m_networkState != NETWORK_EMPTY) {
|
| + // 4.1 - Queue a task to fire a simple event named emptied at the media element.
|
| + scheduleEvent(EventTypeNames::emptied);
|
| +
|
| + // 4.2 - If a fetching process is in progress for the media element, the user agent should stop it.
|
| m_networkState = NETWORK_EMPTY;
|
| +
|
| + // 4.3 - Forget the media element's media-resource-specific tracks.
|
| + forgetResourceSpecificTracks();
|
| +
|
| + // 4.4 - If readyState is not set to HAVE_NOTHING, then set it to that state.
|
| m_readyState = HAVE_NOTHING;
|
| m_readyStateMaximum = HAVE_NOTHING;
|
| - refreshCachedTime();
|
| +
|
| + // 4.5 - If the paused attribute is false, then set it to true.
|
| m_paused = true;
|
| +
|
| + // 4.6 - If seeking is true, set it to false.
|
| m_seeking = false;
|
| +
|
| + // 4.7 - Set the current playback position to 0.
|
| + // Set the official playback position to 0.
|
| + // If this changed the official playback position, then queue a task to fire a simple event named timeupdate at the media element.
|
| +
|
| + // 4.8 - Set the initial playback position to 0.
|
| + // FIXME: Make this less subtle. The position only becomes 0 because of the createMediaPlayer() call
|
| + // above.
|
| + refreshCachedTime();
|
| invalidateCachedTime();
|
| - scheduleEvent(EventTypeNames::emptied);
|
| +
|
| + // 4.9 - Set the timeline offset to Not-a-Number (NaN).
|
| + // 4.10 - Update the duration attribute to Not-a-Number (NaN).
|
| +
|
| updateMediaController();
|
| if (RuntimeEnabledFeatures::videoTrackEnabled())
|
| updateActiveTextTrackCues(0);
|
| @@ -694,6 +724,9 @@ void HTMLMediaElement::prepareForLoad()
|
| // 2 - Asynchronously await a stable state.
|
|
|
| m_playedTimeRanges = TimeRanges::create();
|
| +
|
| + // FIXME: Investigate whether these can be moved into m_networkState != NETWORK_EMPTY block above
|
| + // so they are closer to the relevant spec steps.
|
| m_lastSeekTime = 0;
|
| m_duration = numeric_limits<double>::quiet_NaN();
|
|
|
| @@ -1364,6 +1397,7 @@ void HTMLMediaElement::noneSupported()
|
| m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
|
|
|
| // 6.2 - Forget the media element's media-resource-specific text tracks.
|
| + forgetResourceSpecificTracks();
|
|
|
| // 6.3 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
|
| m_networkState = NETWORK_NO_SOURCE;
|
| @@ -1437,12 +1471,18 @@ void HTMLMediaElement::mediaLoadingFailed(MediaPlayer::NetworkState error)
|
| // If we failed while trying to load a <source> element, the movie was never parsed, and there are more
|
| // <source> children, schedule the next one
|
| if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) {
|
| -
|
| + // resource selection algorithm
|
| + // Step 9.Otherwise.9 - Failed with elements: Queue a task, using the DOM manipulation task source, to fire a simple event named error at the candidate element.
|
| if (m_currentSourceNode)
|
| m_currentSourceNode->scheduleErrorEvent();
|
| else
|
| WTF_LOG(Media, "HTMLMediaElement::setNetworkState - error event not sent, <source> was removed");
|
|
|
| + // 9.Otherwise.10 - Asynchronously await a stable state. The synchronous section consists of all the remaining steps of this algorithm until the algorithm says the synchronous section has ended. (Steps in synchronous sections are marked with ⌛.)
|
| +
|
| + // 9.Otherwise.11 Forget the media element's media-resource-specific tracks.
|
| + forgetResourceSpecificTracks();
|
| +
|
| if (havePotentialSourceChild()) {
|
| WTF_LOG(Media, "HTMLMediaElement::setNetworkState - scheduling next <source>");
|
| scheduleNextSourceChild();
|
| @@ -1574,7 +1614,12 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
|
| }
|
|
|
| if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) {
|
| + createPlaceholderTracksIfNecessary();
|
| +
|
| prepareMediaFragmentURI();
|
| +
|
| + selectInitialTracksIfNecessary();
|
| +
|
| scheduleEvent(EventTypeNames::durationchange);
|
| if (isVideo())
|
| scheduleEvent(EventTypeNames::resize);
|
| @@ -2452,7 +2497,99 @@ bool HTMLMediaElement::canPlay() const
|
| return paused() || ended() || m_readyState < HAVE_METADATA;
|
| }
|
|
|
| -void HTMLMediaElement::mediaPlayerDidAddTrack(WebInbandTextTrack* webTrack)
|
| +AudioTrackList* HTMLMediaElement::audioTracks()
|
| +{
|
| + ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
|
| +
|
| + if (!m_audioTracks)
|
| + m_audioTracks = AudioTrackList::create(this);
|
| +
|
| + return m_audioTracks.get();
|
| +}
|
| +
|
| +void HTMLMediaElement::didEnabledAudioTrackChange(const AtomicString& audioTrackID, bool enabled)
|
| +{
|
| + WTF_LOG(Media, "HTMLMediaElement::didEnabledAudioTrackChange('%s', %d)", audioTrackID.ascii().data(), enabled);
|
| + ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
|
| + ASSERT(!audioTrackID.isEmpty());
|
| +
|
| + if (webMediaPlayer())
|
| + webMediaPlayer()->enabledAudioTrackChange(audioTrackID, enabled);
|
| +}
|
| +
|
| +VideoTrackList* HTMLMediaElement::videoTracks()
|
| +{
|
| + ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
|
| +
|
| + if (!m_videoTracks)
|
| + m_videoTracks = VideoTrackList::create(this);
|
| +
|
| + return m_videoTracks.get();
|
| +}
|
| +
|
| +void HTMLMediaElement::didSelectedVideoTrackChange(const AtomicString& unselectedTrackID, const AtomicString& selectedTrackID)
|
| +{
|
| + WTF_LOG(Media, "HTMLMediaElement::didSelectedVideoTrackChange('%s', '%s')", unselectedTrackID.ascii().data(), selectedTrackID.ascii().data());
|
| + ASSERT(RuntimeEnabledFeatures::audioVideoTracksEnabled());
|
| + ASSERT(!unselectedTrackID.isEmpty() || !selectedTrackID.isEmpty());
|
| +
|
| + if (webMediaPlayer())
|
| + webMediaPlayer()->selectedVideoTrackChange(unselectedTrackID, selectedTrackID);
|
| +}
|
| +
|
| +void HTMLMediaElement::mediaPlayerDidAddVideoTrack(const AtomicString& id, const AtomicString& kind, const AtomicString& label, const AtomicString& language, bool selected)
|
| +{
|
| + WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDidAddVideoTrack('%s', '%s', '%s', '%s', %d)",
|
| + id.ascii().data(), kind.ascii().data(), label.ascii().data(), language.ascii().data(), selected);
|
| + ASSERT(!id.isEmpty());
|
| +
|
| + RefPtr<VideoTrack> videoTrack = VideoTrack::create(videoTracks(), id, kind, label, language);
|
| + videoTracks()->add(videoTrack.get());
|
| +
|
| + if (selected)
|
| + videoTrack->setSelected(true);
|
| +}
|
| +
|
| +void HTMLMediaElement::mediaPlayerDidRemoveVideoTrack(const AtomicString& id)
|
| +{
|
| + WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDidRevmoeVideoTrack('%s')", id.ascii().data());
|
| + ASSERT(!id.isEmpty());
|
| +
|
| + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
|
| + return;
|
| +
|
| + videoTracks()->remove(id);
|
| +}
|
| +
|
| +void HTMLMediaElement::mediaPlayerDidAddAudioTrack(const AtomicString& id, const AtomicString& kind, const AtomicString& label, const AtomicString& language, bool enabled)
|
| +{
|
| + WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDidAddAudioTrack('%s', '%s', '%s', '%s', %d)",
|
| + id.ascii().data(), kind.ascii().data(), label.ascii().data(), language.ascii().data(), enabled);
|
| + ASSERT(!id.isEmpty());
|
| +
|
| + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
|
| + return;
|
| +
|
| + AtomicString kindString;
|
| + RefPtr<AudioTrack> audioTrack = AudioTrack::create(audioTracks(), id, kind, label, language);
|
| + audioTracks()->add(audioTrack.get());
|
| +
|
| + if (enabled)
|
| + audioTrack->setEnabled(true);
|
| +}
|
| +
|
| +void HTMLMediaElement::mediaPlayerDidRemoveAudioTrack(const AtomicString& id)
|
| +{
|
| + WTF_LOG(Media, "HTMLMediaElement::mediaPlayerDidRevmoeAudioTrack('%s')", id.ascii().data());
|
| + ASSERT(!id.isEmpty());
|
| +
|
| + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
|
| + return;
|
| +
|
| + audioTracks()->remove(id);
|
| +}
|
| +
|
| +void HTMLMediaElement::mediaPlayerDidAddTextTrack(WebInbandTextTrack* webTrack)
|
| {
|
| if (!RuntimeEnabledFeatures::videoTrackEnabled())
|
| return;
|
| @@ -2484,10 +2621,10 @@ void HTMLMediaElement::mediaPlayerDidAddTrack(WebInbandTextTrack* webTrack)
|
| // 9. Fire an event with the name addtrack, that does not bubble and is not cancelable, and that uses the TrackEvent
|
| // interface, with the track attribute initialized to the text track's TextTrack object, at the media element's
|
| // textTracks attribute's TextTrackList object.
|
| - addTrack(textTrack.get());
|
| + addTextTrack(textTrack.get());
|
| }
|
|
|
| -void HTMLMediaElement::mediaPlayerDidRemoveTrack(WebInbandTextTrack* webTrack)
|
| +void HTMLMediaElement::mediaPlayerDidRemoveTextTrack(WebInbandTextTrack* webTrack)
|
| {
|
| if (!RuntimeEnabledFeatures::videoTrackEnabled())
|
| return;
|
| @@ -2501,7 +2638,7 @@ void HTMLMediaElement::mediaPlayerDidRemoveTrack(WebInbandTextTrack* webTrack)
|
| if (!textTrack)
|
| return;
|
|
|
| - removeTrack(textTrack.get());
|
| + removeTextTrack(textTrack.get(), true);
|
| }
|
|
|
| void HTMLMediaElement::closeCaptionTracksChanged()
|
| @@ -2510,36 +2647,44 @@ void HTMLMediaElement::closeCaptionTracksChanged()
|
| mediaControls()->closedCaptionTracksChanged();
|
| }
|
|
|
| -void HTMLMediaElement::addTrack(TextTrack* track)
|
| +void HTMLMediaElement::addTextTrack(TextTrack* track)
|
| {
|
| textTracks()->append(track);
|
|
|
| closeCaptionTracksChanged();
|
| }
|
|
|
| -void HTMLMediaElement::removeTrack(TextTrack* track)
|
| +void HTMLMediaElement::removeTextTrack(TextTrack* track, bool fireRemoveTrackEvent)
|
| {
|
| TrackDisplayUpdateScope scope(this);
|
| TextTrackCueList* cues = track->cues();
|
| if (cues)
|
| textTrackRemoveCues(track, cues);
|
| - m_textTracks->remove(track);
|
| + m_textTracks->remove(track, fireRemoveTrackEvent);
|
|
|
| closeCaptionTracksChanged();
|
| }
|
|
|
| -void HTMLMediaElement::removeAllInbandTracks()
|
| +void HTMLMediaElement::forgetResourceSpecificTracks()
|
| {
|
| - if (!m_textTracks)
|
| - return;
|
| -
|
| - TrackDisplayUpdateScope scope(this);
|
| - for (int i = m_textTracks->length() - 1; i >= 0; --i) {
|
| - TextTrack* track = m_textTracks->item(i);
|
| + // Implements the "forget the media element's media-resource-specific tracks" algorithm.
|
| + // The order is explicitly specified as text, then audio, and finally video. Also
|
| + // 'removetrack' events should not be fired.
|
| + if (m_textTracks) {
|
| + TrackDisplayUpdateScope scope(this);
|
| + for (int i = m_textTracks->length() - 1; i >= 0; --i) {
|
| + TextTrack* track = m_textTracks->item(i);
|
|
|
| - if (track->trackType() == TextTrack::InBand)
|
| - removeTrack(track);
|
| + if (track->trackType() == TextTrack::InBand)
|
| + removeTextTrack(track, false);
|
| + }
|
| }
|
| +
|
| + if (m_audioTracks)
|
| + m_audioTracks->removeAll();
|
| +
|
| + if (m_videoTracks)
|
| + m_videoTracks->removeAll();
|
| }
|
|
|
| PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const AtomicString& kind, const AtomicString& label, const AtomicString& language, ExceptionState& exceptionState)
|
| @@ -2567,7 +2712,7 @@ PassRefPtr<TextTrack> HTMLMediaElement::addTextTrack(const AtomicString& kind, c
|
| // first append the track to the text track list.
|
|
|
| // 6. Add the new text track to the media element's list of text tracks.
|
| - addTrack(textTrack.get());
|
| + addTextTrack(textTrack.get());
|
|
|
| // ... its text track readiness state to the text track loaded state ...
|
| textTrack->setReadinessState(TextTrack::Loaded);
|
| @@ -2603,7 +2748,7 @@ void HTMLMediaElement::didAddTrack(HTMLTrackElement* trackElement)
|
| if (!textTrack)
|
| return;
|
|
|
| - addTrack(textTrack.get());
|
| + addTextTrack(textTrack.get());
|
|
|
| // Do not schedule the track loading until parsing finishes so we don't start before all tracks
|
| // in the markup have been added.
|
| @@ -2641,7 +2786,7 @@ void HTMLMediaElement::didRemoveTrack(HTMLTrackElement* trackElement)
|
| // When a track element's parent element changes and the old parent was a media element,
|
| // then the user agent must remove the track element's corresponding text track from the
|
| // media element's list of text tracks.
|
| - removeTrack(textTrack.get());
|
| + removeTextTrack(textTrack.get(), true);
|
|
|
| size_t index = m_textTracksWhenResourceSelectionBegan.find(textTrack.get());
|
| if (index != kNotFound)
|
| @@ -3394,7 +3539,7 @@ void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClient()
|
|
|
| void HTMLMediaElement::clearMediaPlayer(int flags)
|
| {
|
| - removeAllInbandTracks();
|
| + forgetResourceSpecificTracks();
|
|
|
| closeMediaSource();
|
|
|
| @@ -3861,6 +4006,8 @@ void HTMLMediaElement::prepareMediaFragmentURI()
|
| } else
|
| m_fragmentEndTime = MediaPlayer::invalidTime();
|
|
|
| + // FIXME: Add support for selecting tracks by ID with the Media Fragments track dimension.
|
| +
|
| if (m_fragmentStartTime != MediaPlayer::invalidTime() && m_readyState < HAVE_FUTURE_DATA)
|
| prepareToPlay();
|
| }
|
| @@ -3923,4 +4070,32 @@ bool HTMLMediaElement::isInteractiveContent() const
|
| return fastHasAttribute(controlsAttr);
|
| }
|
|
|
| +void HTMLMediaElement::createPlaceholderTracksIfNecessary()
|
| +{
|
| + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
|
| + return;
|
| +
|
| + // Create a placeholder audio track if |m_player| says it has audio but it didn't explicitly announce the tracks.
|
| + if (m_player->hasAudio() && !audioTracks()->length())
|
| + mediaPlayerDidAddAudioTrack("audio", AudioTrack::mainKeyword(), "Audio Track", "", true);
|
| +
|
| + // Create a placeholder video track if |m_player| says it has video but it didn't explicitly announce the tracks.
|
| + if (m_player->hasVideo() && !videoTracks()->length())
|
| + mediaPlayerDidAddVideoTrack("video", VideoTrack::mainKeyword(), "Video Track", "", true);
|
| +}
|
| +
|
| +void HTMLMediaElement::selectInitialTracksIfNecessary()
|
| +{
|
| + if (!RuntimeEnabledFeatures::audioVideoTracksEnabled())
|
| + return;
|
| +
|
| + // Enable the first audio track if an audio track hasn't been enabled yet.
|
| + if (audioTracks()->length() > 0 && !audioTracks()->hasEnabledTrack())
|
| + audioTracks()->anonymousIndexedGetter(0)->setEnabled(true);
|
| +
|
| + // Select the first video track if a video track hasn't been selected yet.
|
| + if (videoTracks()->length() > 0 && videoTracks()->selectedIndex() == -1)
|
| + videoTracks()->anonymousIndexedGetter(0)->setSelected(true);
|
| +}
|
| +
|
| }
|
|
|