From c70c946e3ee3baa8c6caa711ed2cf6c5c0b95c4f Mon Sep 17 00:00:00 2001 From: Andrew Quartey Date: Wed, 1 Jun 2016 13:35:56 +0800 Subject: [PATCH] Bug 882718 - Implement "TimeMarchesOn". r=rillian MozReview-Commit-ID: 1RqUmgz056N * * * [mq]: hotfix MozReview-Commit-ID: CPByIPsUag4 --HG-- extra : rebase_source : 4b1fea4f04553ea5b3a0c3c4ddfdb60b485b803e --- dom/html/TextTrackManager.cpp | 295 ++++++++++++++++++++++++++++++++- dom/html/TextTrackManager.h | 19 ++- dom/media/TextTrackCueList.cpp | 7 + dom/media/TextTrackCueList.h | 2 +- 4 files changed, 320 insertions(+), 3 deletions(-) diff --git a/dom/html/TextTrackManager.cpp b/dom/html/TextTrackManager.cpp index e86490287be7..fed128d30a04 100644 --- a/dom/html/TextTrackManager.cpp +++ b/dom/html/TextTrackManager.cpp @@ -76,7 +76,8 @@ CompareTextTracks::LessThan(TextTrack* aOne, TextTrack* aTwo) const } NS_IMPL_CYCLE_COLLECTION(TextTrackManager, mMediaElement, mTextTracks, - mPendingTextTracks, mNewCues) + mPendingTextTracks, mNewCues, + mLastActiveCues) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrackManager) NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) @@ -89,6 +90,9 @@ StaticRefPtr TextTrackManager::sParserWrapper; TextTrackManager::TextTrackManager(HTMLMediaElement *aMediaElement) : mMediaElement(aMediaElement) + , mHasSeeked(false) + , mLastTimeMarchesOnCalled(0.0) + , mTimeMarchesOnDispatched(false) , performedTrackSelection(false) { nsISupports* parentObject = @@ -98,6 +102,7 @@ TextTrackManager::TextTrackManager(HTMLMediaElement *aMediaElement) nsCOMPtr window = do_QueryInterface(parentObject); mNewCues = new TextTrackCueList(window); + mLastActiveCues = new TextTrackCueList(window); mTextTracks = new TextTrackList(window, this); mPendingTextTracks = new TextTrackList(window, this); @@ -191,6 +196,7 @@ TextTrackManager::DidSeek() if (mTextTracks) { mTextTracks->DidSeek(); } + mHasSeeked = true; } void @@ -380,5 +386,292 @@ TextTrackManager::HandleEvent(nsIDOMEvent* aEvent) return NS_OK; } + +class SimpleTextTrackEvent : public Runnable +{ +public: + friend class CompareSimpleTextTrackEvents; + SimpleTextTrackEvent(const nsAString& aEventName, double aTime, + TextTrack* aTrack, TextTrackCue* aCue) + : mName(aEventName), + mTime(aTime), + mTrack(aTrack), + mCue(aCue) + {} + + NS_IMETHOD Run() { + mCue->DispatchTrustedEvent(mName); + return NS_OK; + } + +private: + nsString mName; + double mTime; + TextTrack* mTrack; + RefPtr mCue; +}; + +class CompareSimpleTextTrackEvents { +private: + int32_t TrackChildPosition(SimpleTextTrackEvent* aEvent) const + { + HTMLTrackElement* trackElement = aEvent->mTrack->GetTrackElement();; + if (!trackElement) { + return -1; + } + return mMediaElement->IndexOf(trackElement); + } + HTMLMediaElement* mMediaElement; +public: + explicit CompareSimpleTextTrackEvents(HTMLMediaElement* aMediaElement) + { + mMediaElement = aMediaElement; + } + + bool Equals(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const + { + return false; + } + + bool LessThan(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const + { + if (aOne->mTime < aTwo->mTime) { + return true; + } else if (aOne->mTime > aTwo->mTime) { + return false; + } + + int32_t positionOne = TrackChildPosition(aOne); + int32_t positionTwo = TrackChildPosition(aTwo); + if (positionOne < positionTwo) { + return true; + } else if (positionOne > positionTwo) { + return false; + } + + if (aOne->mName.EqualsLiteral("enter") || + aTwo->mName.EqualsLiteral("exit")) { + return true; + } + return false; + } +}; + +class TextTrackListInternal +{ +public: + void AddTextTrack(TextTrack* aTextTrack, + const CompareTextTracks& aCompareTT) + { + if (!mTextTracks.Contains(aTextTrack)) { + mTextTracks.InsertElementSorted(aTextTrack, aCompareTT); + } + } + uint32_t Length() const + { + return mTextTracks.Length(); + } + TextTrack* operator[](uint32_t aIndex) + { + return mTextTracks.SafeElementAt(aIndex, nullptr); + } +private: + nsTArray> mTextTracks; +}; + +void +TextTrackManager::DispatchTimeMarchesOn() +{ + // Run the algorithm if no previous instance is still running, otherwise + // enqueue the current playback position and whether only that changed + // through its usual monotonic increase during normal playback; current + // executing call upon completion will check queue for further 'work'. + if (!mTimeMarchesOnDispatched) { + NS_DispatchToMainThread(NewRunnableMethod(this, &TextTrackManager::TimeMarchesOn)); + mTimeMarchesOnDispatched = true; + } +} + +// https://html.spec.whatwg.org/multipage/embedded-content.html#time-marches-on +void +TextTrackManager::TimeMarchesOn() +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + mTimeMarchesOnDispatched = false; + + nsISupports* parentObject = + mMediaElement->OwnerDoc()->GetParentObject(); + if (NS_WARN_IF(!parentObject)) { + return; + } + nsCOMPtr window = do_QueryInterface(parentObject); + + // Step 3. + double currentPlaybackTime = mMediaElement->CurrentTime(); + bool hasNormalPlayback = !mHasSeeked; + mHasSeeked = false; + + // Step 1, 2. + RefPtr currentCues = + new TextTrackCueList(window); + RefPtr otherCues = + new TextTrackCueList(window); + bool dummy; + for (uint32_t index = 0; index < mTextTracks->Length(); ++index) { + TextTrack* ttrack = mTextTracks->IndexedGetter(index, dummy); + if (ttrack && dummy) { + TextTrackCueList* activeCueList = ttrack->GetActiveCues(); + if (activeCueList) { + for (uint32_t i = 0; i < activeCueList->Length(); ++i) { + currentCues->AddCue(*((*activeCueList)[i])); + } + } + } + } + // Populate otherCues with 'non-active" cues. + if (hasNormalPlayback) { + media::Interval 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]; + ErrorResult dummy; + otherCues->RemoveCue(*cue, dummy); + } + + // Step 4. + RefPtr 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); + } + } + } + + // 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; + 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()) { + mMediaElement->Pause(); + break; + } + } + for (uint32_t i = 0; i < missedCues->Length(); ++i) { + TextTrackCue* cue = (*missedCues)[i]; + if (cue && cue->PauseOnExit()) { + 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> 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->GetCueById(cue->Id()) != nullptr) { + 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->DispatchTrustedEvent(NS_LITERAL_STRING("cuechange")); + } + } + + mLastTimeMarchesOnCalled = currentPlaybackTime; + mLastActiveCues = currentCues; +} + } // namespace dom } // namespace mozilla diff --git a/dom/html/TextTrackManager.h b/dom/html/TextTrackManager.h index 40e585fb7b2d..e6e8d94289c6 100644 --- a/dom/html/TextTrackManager.h +++ b/dom/html/TextTrackManager.h @@ -22,9 +22,9 @@ class HTMLMediaElement; class CompareTextTracks { private: HTMLMediaElement* mMediaElement; + int32_t TrackChildPosition(TextTrack* aTrack) const; public: explicit CompareTextTracks(HTMLMediaElement* aMediaElement); - int32_t TrackChildPosition(TextTrack* aTrack) const; bool Equals(TextTrack* aOne, TextTrack* aTwo) const; bool LessThan(TextTrack* aOne, TextTrack* aTwo) const; }; @@ -94,13 +94,30 @@ public: // The HTMLMediaElement that this TextTrackManager manages the TextTracks of. RefPtr mMediaElement; + + void DispatchTimeMarchesOn(); + private: + void TimeMarchesOn(); + // List of the TextTrackManager's owning HTMLMediaElement's TextTracks. RefPtr mTextTracks; // List of text track objects awaiting loading. RefPtr mPendingTextTracks; // List of newly introduced Text Track cues. + + // Contain all cues for a MediaElement. RefPtr mNewCues; + // The active cues for the last TimeMarchesOn iteration. + RefPtr mLastActiveCues; + + // True if the media player playback changed due to seeking prior to and + // during running the "Time Marches On" algorithm. + bool mHasSeeked; + // Playback position at the time of last "Time Marches On" call + double mLastTimeMarchesOnCalled; + + bool mTimeMarchesOnDispatched; static StaticRefPtr sParserWrapper; diff --git a/dom/media/TextTrackCueList.cpp b/dom/media/TextTrackCueList.cpp index 655ebf7065e7..e3f98d38d7c1 100644 --- a/dom/media/TextTrackCueList.cpp +++ b/dom/media/TextTrackCueList.cpp @@ -62,6 +62,13 @@ TextTrackCueList::operator[](uint32_t aIndex) return mList.SafeElementAt(aIndex, nullptr); } +TextTrackCueList& +TextTrackCueList::operator=(const TextTrackCueList& aOther) +{ + mList = aOther.mList; + return *this; +} + TextTrackCue* TextTrackCueList::GetCueById(const nsAString& aId) { diff --git a/dom/media/TextTrackCueList.h b/dom/media/TextTrackCueList.h index 6ae019c8daac..aa88f3c062cb 100644 --- a/dom/media/TextTrackCueList.h +++ b/dom/media/TextTrackCueList.h @@ -44,7 +44,7 @@ public: TextTrackCue* IndexedGetter(uint32_t aIndex, bool& aFound); TextTrackCue* operator[](uint32_t aIndex); TextTrackCue* GetCueById(const nsAString& aId); - + TextTrackCueList& operator=(const TextTrackCueList& aOther); // Adds a cue to mList by performing an insertion sort on mList. // We expect most files to already be sorted, so an insertion sort starting // from the end of the current array should be more efficient than a general