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 |