Bug 882718 - Implement "TimeMarchesOn". r=rillian
MozReview-Commit-ID: 1RqUmgz056N
* * *
[mq]: hotfix
MozReview-Commit-ID: CPByIPsUag4
--- a/dom/html/TextTrackManager.cpp
+++ b/dom/html/TextTrackManager.cpp
@@ -71,38 +71,43 @@ CompareTextTracks::LessThan(TextTrack* a
case MediaResourceSpecific:
// No rules for Media Resource Specific tracks yet.
break;
}
return true;
}
NS_IMPL_CYCLE_COLLECTION(TextTrackManager, mMediaElement, mTextTracks,
- mPendingTextTracks, mNewCues)
+ mPendingTextTracks, mNewCues,
+ mLastActiveCues)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrackManager)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(TextTrackManager)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TextTrackManager)
StaticRefPtr<nsIWebVTTParserWrapper> TextTrackManager::sParserWrapper;
TextTrackManager::TextTrackManager(HTMLMediaElement *aMediaElement)
: mMediaElement(aMediaElement)
+ , mHasSeeked(false)
+ , mLastTimeMarchesOnCalled(0.0)
+ , mTimeMarchesOnDispatched(false)
, performedTrackSelection(false)
{
nsISupports* parentObject =
mMediaElement->OwnerDoc()->GetParentObject();
NS_ENSURE_TRUE_VOID(parentObject);
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
mNewCues = new TextTrackCueList(window);
+ mLastActiveCues = new TextTrackCueList(window);
mTextTracks = new TextTrackList(window, this);
mPendingTextTracks = new TextTrackList(window, this);
if (!sParserWrapper) {
nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper =
do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID);
sParserWrapper = parserWrapper;
ClearOnShutdown(&sParserWrapper);
@@ -186,16 +191,17 @@ TextTrackManager::RemoveTextTrack(TextTr
}
void
TextTrackManager::DidSeek()
{
if (mTextTracks) {
mTextTracks->DidSeek();
}
+ mHasSeeked = true;
}
void
TextTrackManager::UpdateCueDisplay()
{
if (!mMediaElement || !mTextTracks) {
return;
}
@@ -375,10 +381,297 @@ TextTrackManager::HandleEvent(nsIDOMEven
if (type.EqualsLiteral("resizevideocontrols")) {
for (uint32_t i = 0; i< mTextTracks->Length(); i++) {
((*mTextTracks)[i])->SetCuesDirty();
}
}
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<TextTrackCue> 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<RefPtr<TextTrack>> 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<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
+
+ // Step 3.
+ double currentPlaybackTime = mMediaElement->CurrentTime();
+ bool hasNormalPlayback = !mHasSeeked;
+ mHasSeeked = false;
+
+ // 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) {
+ 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<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];
+ ErrorResult dummy;
+ otherCues->RemoveCue(*cue, dummy);
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ // 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<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->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
--- a/dom/html/TextTrackManager.h
+++ b/dom/html/TextTrackManager.h
@@ -17,19 +17,19 @@ class nsIWebVTTParserWrapper;
namespace mozilla {
namespace dom {
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;
};
class TextTrack;
class TextTrackCue;
class TextTrackManager final : public nsIDOMEventListener
@@ -89,23 +89,40 @@ public:
void UpdateCueDisplay();
void PopulatePendingList();
void AddListeners();
// The HTMLMediaElement that this TextTrackManager manages the TextTracks of.
RefPtr<HTMLMediaElement> mMediaElement;
+
+ void DispatchTimeMarchesOn();
+
private:
+ void TimeMarchesOn();
+
// List of the TextTrackManager's owning HTMLMediaElement's TextTracks.
RefPtr<TextTrackList> mTextTracks;
// List of text track objects awaiting loading.
RefPtr<TextTrackList> mPendingTextTracks;
// List of newly introduced Text Track cues.
+
+ // Contain all cues for a MediaElement.
RefPtr<TextTrackCueList> mNewCues;
+ // The active cues for the last TimeMarchesOn iteration.
+ RefPtr<TextTrackCueList> 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<nsIWebVTTParserWrapper> sParserWrapper;
bool performedTrackSelection;
// Runs the algorithm for performing automatic track selection.
void HonorUserPreferencesForTrackSelection();
// Performs track selection for a single TextTrackKind.
--- a/dom/media/TextTrackCueList.cpp
+++ b/dom/media/TextTrackCueList.cpp
@@ -57,16 +57,23 @@ TextTrackCueList::IndexedGetter(uint32_t
}
TextTrackCue*
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)
{
if (aId.IsEmpty()) {
return nullptr;
}
for (uint32_t i = 0; i < mList.Length(); i++) {
--- a/dom/media/TextTrackCueList.h
+++ b/dom/media/TextTrackCueList.h
@@ -39,17 +39,17 @@ public:
uint32_t Length() const
{
return mList.Length();
}
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
// sort step after all cues are loaded.
void AddCue(TextTrackCue& aCue);
void RemoveCue(TextTrackCue& aCue, ErrorResult& aRv);
void RemoveCueAt(uint32_t aIndex);
void RemoveAll();