Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "config.h" | |
| 6 #include "core/html/track/CueTimeline.h" | |
| 7 | |
| 8 #include "core/events/Event.h" | |
| 9 #include "core/html/HTMLMediaElement.h" | |
| 10 #include "core/html/HTMLTrackElement.h" | |
| 11 #include "core/html/track/LoadableTextTrack.h" | |
| 12 #include "core/html/track/TextTrack.h" | |
| 13 #include "core/html/track/TextTrackCue.h" | |
| 14 #include "core/html/track/TextTrackCueList.h" | |
| 15 #include "wtf/NonCopyingSort.h" | |
| 16 | |
| 17 namespace blink { | |
| 18 | |
| 19 CueTimeline::CueTimeline(HTMLMediaElement& mediaElement) | |
| 20 : m_mediaElement(&mediaElement) | |
| 21 , m_lastUpdateTime(-1) | |
| 22 , m_ignoreUpdate(0) | |
| 23 { | |
| 24 } | |
| 25 | |
| 26 void CueTimeline::addCues(TextTrack* track, const TextTrackCueList* cues) | |
| 27 { | |
| 28 ASSERT(track->mode() != TextTrack::disabledKeyword()); | |
| 29 | |
| 30 TrackDisplayUpdateScope scope(*this); | |
| 31 for (size_t i = 0; i < cues->length(); ++i) | |
| 32 addCue(cues->item(i)->track(), cues->item(i)); | |
| 33 } | |
| 34 | |
| 35 void CueTimeline::addCue(TextTrack* track, PassRefPtrWillBeRawPtr<TextTrackCue> cue) | |
| 36 { | |
| 37 ASSERT(track->mode() != TextTrack::disabledKeyword()); | |
| 38 | |
| 39 // Negative duration cues need be treated in the interval tree as | |
| 40 // zero-length cues. | |
| 41 double endTime = std::max(cue->startTime(), cue->endTime()); | |
| 42 | |
| 43 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, c ue.get()); | |
| 44 if (!m_cueTree.contains(interval)) | |
| 45 m_cueTree.add(interval); | |
| 46 updateActiveCues(mediaElement().currentTime()); | |
| 47 } | |
| 48 | |
| 49 void CueTimeline::removeCues(TextTrack*, const TextTrackCueList* cues) | |
| 50 { | |
| 51 TrackDisplayUpdateScope scope(*this); | |
| 52 for (size_t i = 0; i < cues->length(); ++i) | |
| 53 removeCue(cues->item(i)->track(), cues->item(i)); | |
| 54 } | |
| 55 | |
| 56 void CueTimeline::removeCue(TextTrack*, PassRefPtrWillBeRawPtr<TextTrackCue> cue ) | |
| 57 { | |
| 58 // Negative duration cues need to be treated in the interval tree as | |
| 59 // zero-length cues. | |
| 60 double endTime = std::max(cue->startTime(), cue->endTime()); | |
| 61 | |
| 62 CueInterval interval = m_cueTree.createInterval(cue->startTime(), endTime, c ue.get()); | |
| 63 m_cueTree.remove(interval); | |
| 64 | |
| 65 // Since the cue will be removed from the media element and likely the | |
| 66 // TextTrack might also be destructed, notifying the region of the cue | |
| 67 // removal shouldn't be done. | |
| 68 cue->notifyRegionWhenRemovingDisplayTree(false); | |
| 69 | |
| 70 size_t index = m_currentlyActiveCues.find(interval); | |
| 71 if (index != kNotFound) { | |
| 72 m_currentlyActiveCues.remove(index); | |
| 73 cue->setIsActive(false); | |
| 74 } | |
| 75 cue->removeDisplayTree(); | |
| 76 updateActiveCues(mediaElement().currentTime()); | |
| 77 | |
| 78 cue->notifyRegionWhenRemovingDisplayTree(true); | |
| 79 } | |
| 80 | |
| 81 static bool trackIndexCompare(TextTrack* a, TextTrack* b) | |
| 82 { | |
| 83 return a->trackIndex() - b->trackIndex() < 0; | |
| 84 } | |
| 85 | |
| 86 static bool eventTimeCueCompare(const std::pair<double, TextTrackCue*>& a, const std::pair<double, TextTrackCue*>& b) | |
| 87 { | |
| 88 // 12 - Sort the tasks in events in ascending time order (tasks with earlier | |
| 89 // times first). | |
| 90 if (a.first != b.first) | |
| 91 return a.first - b.first < 0; | |
| 92 | |
| 93 // If the cues belong to different text tracks, it doesn't make sense to | |
| 94 // compare the two tracks by the relative cue order, so return the relative | |
| 95 // track order. | |
| 96 if (a.second->track() != b.second->track()) | |
| 97 return trackIndexCompare(a.second->track(), b.second->track()); | |
| 98 | |
| 99 // 12 - Further sort tasks in events that have the same time by the | |
| 100 // relative text track cue order of the text track cues associated | |
| 101 // with these tasks. | |
| 102 return a.second->cueIndex() - b.second->cueIndex() < 0; | |
| 103 } | |
| 104 | |
| 105 void CueTimeline::updateActiveCues(double movieTime) | |
| 106 { | |
| 107 // 4.8.10.8 Playing the media resource | |
| 108 | |
| 109 // If the current playback position changes while the steps are running, | |
| 110 // then the user agent must wait for the steps to complete, and then must | |
| 111 // immediately rerun the steps. | |
| 112 if (ignoreUpdateRequests()) | |
| 113 return; | |
| 114 | |
| 115 HTMLMediaElement& mediaElement = this->mediaElement(); | |
| 116 | |
| 117 // 1 - Let current cues be a list of cues, initialized to contain all the | |
| 118 // cues of all the hidden, showing, or showing by default text tracks of the | |
| 119 // media element (not the disabled ones) whose start times are less than or | |
| 120 // equal to the current playback position and whose end times are greater | |
| 121 // than the current playback position. | |
| 122 CueList currentCues; | |
| 123 | |
| 124 // The user agent must synchronously unset [the text track cue active] flag | |
| 125 // whenever ... the media element's readyState is changed back to HAVE_NOTHI NG. | |
| 126 if (mediaElement.readyState() != HTMLMediaElement::HAVE_NOTHING && mediaElem ent.webMediaPlayer()) | |
| 127 currentCues = m_cueTree.allOverlaps(m_cueTree.createInterval(movieTime, movieTime)); | |
| 128 | |
| 129 CueList previousCues; | |
| 130 CueList missedCues; | |
| 131 | |
| 132 // 2 - Let other cues be a list of cues, initialized to contain all the cues | |
| 133 // of hidden, showing, and showing by default text tracks of the media | |
| 134 // element that are not present in current cues. | |
| 135 previousCues = m_currentlyActiveCues; | |
| 136 | |
| 137 // 3 - Let last time be the current playback position at the time this | |
| 138 // algorithm was last run for this media element, if this is not the first | |
| 139 // time it has run. | |
| 140 double lastTime = m_lastUpdateTime; | |
| 141 double lastSeekTime = mediaElement.lastSeekTime(); | |
| 142 | |
| 143 // 4 - If the current playback position has, since the last time this | |
| 144 // algorithm was run, only changed through its usual monotonic increase | |
| 145 // during normal playback, then let missed cues be the list of cues in other | |
| 146 // cues whose start times are greater than or equal to last time and whose | |
| 147 // end times are less than or equal to the current playback position. | |
| 148 // Otherwise, let missed cues be an empty list. | |
| 149 if (lastTime >= 0 && lastSeekTime < movieTime) { | |
| 150 CueList potentiallySkippedCues = | |
| 151 m_cueTree.allOverlaps(m_cueTree.createInterval(lastTime, movieTime)) ; | |
| 152 | |
| 153 for (CueInterval cue : potentiallySkippedCues) { | |
| 154 // Consider cues that may have been missed since the last seek time. | |
| 155 if (cue.low() > std::max(lastSeekTime, lastTime) && cue.high() < mov ieTime) | |
| 156 missedCues.append(cue); | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 m_lastUpdateTime = movieTime; | |
| 161 | |
| 162 // 5 - If the time was reached through the usual monotonic increase of the | |
| 163 // current playback position during normal playback, and if the user agent | |
| 164 // has not fired a timeupdate event at the element in the past 15 to 250ms | |
| 165 // and is not still running event handlers for such an event, then the user | |
| 166 // agent must queue a task to fire a simple event named timeupdate at the | |
| 167 // element. (In the other cases, such as explicit seeks, relevant events get | |
| 168 // fired as part of the overall process of changing the current playback | |
| 169 // position.) | |
| 170 if (!mediaElement.seeking() && lastSeekTime < lastTime) | |
| 171 mediaElement.scheduleTimeupdateEvent(true); | |
| 172 | |
| 173 // Explicitly cache vector sizes, as their content is constant from here. | |
| 174 size_t currentCuesSize = currentCues.size(); | |
| 175 size_t missedCuesSize = missedCues.size(); | |
| 176 size_t previousCuesSize = previousCues.size(); | |
| 177 | |
| 178 // 6 - If all of the cues in current cues have their text track cue active | |
| 179 // flag set, none of the cues in other cues have their text track cue active | |
| 180 // flag set, and missed cues is empty, then abort these steps. | |
| 181 bool activeSetChanged = missedCuesSize; | |
| 182 | |
| 183 for (size_t i = 0; !activeSetChanged && i < previousCuesSize; ++i) { | |
| 184 if (!currentCues.contains(previousCues[i]) && previousCues[i].data()->is Active()) | |
| 185 activeSetChanged = true; | |
| 186 } | |
| 187 | |
| 188 for (CueInterval currentCue : currentCues) { | |
| 189 currentCue.data()->updateDisplayTree(movieTime); | |
| 190 | |
| 191 if (!currentCue.data()->isActive()) | |
| 192 activeSetChanged = true; | |
| 193 } | |
| 194 | |
| 195 if (!activeSetChanged) | |
| 196 return; | |
| 197 | |
| 198 // 7 - If the time was reached through the usual monotonic increase of the | |
| 199 // current playback position during normal playback, and there are cues in | |
| 200 // other cues that have their text track cue pause-on-exi flag set and that | |
| 201 // either have their text track cue active flag set or are also in missed | |
| 202 // cues, then immediately pause the media element. | |
| 203 for (size_t i = 0; !mediaElement.paused() && i < previousCuesSize; ++i) { | |
| 204 if (previousCues[i].data()->pauseOnExit() | |
| 205 && previousCues[i].data()->isActive() | |
| 206 && !currentCues.contains(previousCues[i])) | |
| 207 mediaElement.pause(); | |
| 208 } | |
| 209 | |
| 210 for (size_t i = 0; !mediaElement.paused() && i < missedCuesSize; ++i) { | |
| 211 if (missedCues[i].data()->pauseOnExit()) | |
| 212 mediaElement.pause(); | |
| 213 } | |
| 214 | |
| 215 // 8 - Let events be a list of tasks, initially empty. Each task in this | |
| 216 // list will be associated with a text track, a text track cue, and a time, | |
| 217 // which are used to sort the list before the tasks are queued. | |
| 218 WillBeHeapVector<std::pair<double, RawPtrWillBeMember<TextTrackCue>>> eventT asks; | |
| 219 | |
| 220 // 8 - Let affected tracks be a list of text tracks, initially empty. | |
| 221 WillBeHeapVector<RawPtrWillBeMember<TextTrack>> affectedTracks; | |
| 222 | |
| 223 for (size_t i = 0; i < missedCuesSize; ++i) { | |
| 224 // 9 - For each text track cue in missed cues, prepare an event named en ter | |
| 225 // for the TextTrackCue object with the text track cue start time. | |
| 226 eventTasks.append(std::make_pair(missedCues[i].data()->startTime(), miss edCues[i].data())); | |
| 227 | |
| 228 // 10 - For each text track [...] in missed cues, prepare an event | |
| 229 // named exit for the TextTrackCue object with the with the later of | |
| 230 // the text track cue end time and the text track cue start time. | |
| 231 | |
| 232 // Note: An explicit task is added only if the cue is NOT a zero or | |
| 233 // negative length cue. Otherwise, the need for an exit event is | |
| 234 // checked when these tasks are actually queued below. This doesn't | |
| 235 // affect sorting events before dispatch either, because the exit | |
| 236 // event has the same time as the enter event. | |
| 237 if (missedCues[i].data()->startTime() < missedCues[i].data()->endTime()) | |
| 238 eventTasks.append(std::make_pair(missedCues[i].data()->endTime(), mi ssedCues[i].data())); | |
| 239 } | |
| 240 | |
| 241 for (size_t i = 0; i < previousCuesSize; ++i) { | |
| 242 // 10 - For each text track cue in other cues that has its text | |
| 243 // track cue active flag set prepare an event named exit for the | |
| 244 // TextTrackCue object with the text track cue end time. | |
| 245 if (!currentCues.contains(previousCues[i])) | |
| 246 eventTasks.append(std::make_pair(previousCues[i].data()->endTime(), previousCues[i].data())); | |
| 247 } | |
| 248 | |
| 249 for (size_t i = 0; i < currentCuesSize; ++i) { | |
| 250 // 11 - For each text track cue in current cues that does not have its | |
| 251 // text track cue active flag set, prepare an event named enter for the | |
| 252 // TextTrackCue object with the text track cue start time. | |
| 253 if (!previousCues.contains(currentCues[i])) | |
| 254 eventTasks.append(std::make_pair(currentCues[i].data()->startTime(), currentCues[i].data())); | |
| 255 } | |
| 256 | |
| 257 // 12 - Sort the tasks in events in ascending time order (tasks with earlier | |
| 258 // times first). | |
| 259 nonCopyingSort(eventTasks.begin(), eventTasks.end(), eventTimeCueCompare); | |
| 260 | |
| 261 for (size_t i = 0; i < eventTasks.size(); ++i) { | |
| 262 if (!affectedTracks.contains(eventTasks[i].second->track())) | |
| 263 affectedTracks.append(eventTasks[i].second->track()); | |
| 264 | |
| 265 // 13 - Queue each task in events, in list order. | |
| 266 RefPtrWillBeRawPtr<Event> event = nullptr; | |
| 267 | |
| 268 // Each event in eventTasks may be either an enterEvent or an exitEvent, | |
| 269 // depending on the time that is associated with the event. This | |
| 270 // correctly identifies the type of the event, if the startTime is | |
| 271 // less than the endTime in the cue. | |
| 272 if (eventTasks[i].second->startTime() >= eventTasks[i].second->endTime() ) { | |
| 273 event = Event::create(EventTypeNames::enter); | |
| 274 event->setTarget(eventTasks[i].second); | |
| 275 mediaElement.scheduleEvent(event.release()); | |
| 276 | |
| 277 event = Event::create(EventTypeNames::exit); | |
| 278 event->setTarget(eventTasks[i].second); | |
| 279 mediaElement.scheduleEvent(event.release()); | |
| 280 } else { | |
| 281 if (eventTasks[i].first == eventTasks[i].second->startTime()) | |
| 282 event = Event::create(EventTypeNames::enter); | |
| 283 else | |
| 284 event = Event::create(EventTypeNames::exit); | |
| 285 | |
| 286 event->setTarget(eventTasks[i].second); | |
| 287 mediaElement.scheduleEvent(event.release()); | |
| 288 } | |
| 289 } | |
| 290 | |
| 291 // 14 - Sort affected tracks in the same order as the text tracks appear in | |
| 292 // the media element's list of text tracks, and remove duplicates. | |
| 293 nonCopyingSort(affectedTracks.begin(), affectedTracks.end(), trackIndexCompa re); | |
| 294 | |
| 295 // 15 - For each text track in affected tracks, in the list order, queue a | |
| 296 // task to fire a simple event named cuechange at the TextTrack object, and, ... | |
| 297 for (size_t i = 0; i < affectedTracks.size(); ++i) { | |
| 298 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuechang e); | |
| 299 event->setTarget(affectedTracks[i]); | |
| 300 | |
| 301 mediaElement.scheduleEvent(event.release()); | |
| 302 | |
| 303 // ... if the text track has a corresponding track element, to then fire a | |
| 304 // simple event named cuechange at the track element as well. | |
| 305 if (affectedTracks[i]->trackType() == TextTrack::TrackElement) { | |
| 306 RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::cuec hange); | |
|
brucedawson
2015/02/18 21:06:40
The declaration of 'event' shadows the declaration
| |
| 307 HTMLTrackElement* trackElement = static_cast<LoadableTextTrack*>(aff ectedTracks[i].get())->trackElement(); | |
| 308 ASSERT(trackElement); | |
| 309 event->setTarget(trackElement); | |
| 310 | |
| 311 mediaElement.scheduleEvent(event.release()); | |
| 312 } | |
| 313 } | |
| 314 | |
| 315 // 16 - Set the text track cue active flag of all the cues in the current | |
| 316 // cues, and unset the text track cue active flag of all the cues in the | |
| 317 // other cues. | |
| 318 for (size_t i = 0; i < currentCuesSize; ++i) | |
| 319 currentCues[i].data()->setIsActive(true); | |
| 320 | |
| 321 for (size_t i = 0; i < previousCuesSize; ++i) { | |
| 322 if (!currentCues.contains(previousCues[i])) | |
| 323 previousCues[i].data()->setIsActive(false); | |
| 324 } | |
| 325 | |
| 326 // Update the current active cues. | |
| 327 m_currentlyActiveCues = currentCues; | |
| 328 | |
| 329 if (activeSetChanged) | |
| 330 mediaElement.updateTextTrackDisplay(); | |
| 331 } | |
| 332 | |
| 333 void CueTimeline::beginIgnoringUpdateRequests() | |
| 334 { | |
| 335 ++m_ignoreUpdate; | |
| 336 } | |
| 337 | |
| 338 void CueTimeline::endIgnoringUpdateRequests() | |
| 339 { | |
| 340 ASSERT(m_ignoreUpdate); | |
| 341 --m_ignoreUpdate; | |
| 342 if (!m_ignoreUpdate && mediaElement().inActiveDocument()) | |
| 343 updateActiveCues(mediaElement().currentTime()); | |
| 344 } | |
| 345 | |
| 346 DEFINE_TRACE(CueTimeline) | |
| 347 { | |
| 348 visitor->trace(m_mediaElement); | |
| 349 } | |
| 350 | |
| 351 } // namespace blink | |
| OLD | NEW |