Bug 882718 - Implement "TimeMarchesOn". r=rillian draft
authorAndrew Quartey <andrew.quartey@gmail.com>
Wed, 01 Jun 2016 13:35:56 +0800
changeset 373767 de92f37386b5adddc995f529e9a5db58379354a1
parent 373766 ae68a45a10921bcb1556dc9d0ddbba8cda4ae361
child 373768 426a405c60c614139e0d29e659f169b8b2859ee9
push id19838
push userbechen@mozilla.com
push dateWed, 01 Jun 2016 07:10:27 +0000
reviewersrillian
bugs882718
milestone49.0a1
Bug 882718 - Implement "TimeMarchesOn". r=rillian MozReview-Commit-ID: 1RqUmgz056N * * * [mq]: hotfix MozReview-Commit-ID: CPByIPsUag4
dom/html/TextTrackManager.cpp
dom/html/TextTrackManager.h
dom/media/TextTrackCueList.cpp
dom/media/TextTrackCueList.h
--- 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();