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); |
+} |
+ |
} |