/src/mozilla-central/dom/html/TextTrackManager.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
5 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/dom/TextTrackManager.h" |
8 | | #include "mozilla/dom/HTMLMediaElement.h" |
9 | | #include "mozilla/dom/HTMLTrackElement.h" |
10 | | #include "mozilla/dom/HTMLVideoElement.h" |
11 | | #include "mozilla/dom/TextTrack.h" |
12 | | #include "mozilla/dom/TextTrackCue.h" |
13 | | #include "mozilla/dom/Event.h" |
14 | | #include "mozilla/ClearOnShutdown.h" |
15 | | #include "mozilla/Telemetry.h" |
16 | | #include "nsComponentManagerUtils.h" |
17 | | #include "nsGlobalWindow.h" |
18 | | #include "nsVariant.h" |
19 | | #include "nsVideoFrame.h" |
20 | | #include "nsIFrame.h" |
21 | | #include "nsTArrayHelpers.h" |
22 | | #include "nsIWebVTTParserWrapper.h" |
23 | | |
24 | | |
25 | | static mozilla::LazyLogModule gTextTrackLog("TextTrackManager"); |
26 | 0 | #define WEBVTT_LOG(...) MOZ_LOG(gTextTrackLog, LogLevel::Debug, (__VA_ARGS__)) |
27 | 0 | #define WEBVTT_LOGV(...) MOZ_LOG(gTextTrackLog, LogLevel::Verbose, (__VA_ARGS__)) |
28 | | |
29 | | namespace mozilla { |
30 | | namespace dom { |
31 | | |
32 | | NS_IMPL_ISUPPORTS(TextTrackManager::ShutdownObserverProxy, nsIObserver); |
33 | | |
34 | | CompareTextTracks::CompareTextTracks(HTMLMediaElement* aMediaElement) |
35 | 0 | { |
36 | 0 | mMediaElement = aMediaElement; |
37 | 0 | } |
38 | | |
39 | | int32_t |
40 | 0 | CompareTextTracks::TrackChildPosition(TextTrack* aTextTrack) const { |
41 | 0 | MOZ_DIAGNOSTIC_ASSERT(aTextTrack); |
42 | 0 | HTMLTrackElement* trackElement = aTextTrack->GetTrackElement(); |
43 | 0 | if (!trackElement) { |
44 | 0 | return -1; |
45 | 0 | } |
46 | 0 | return mMediaElement->ComputeIndexOf(trackElement); |
47 | 0 | } |
48 | | |
49 | | bool |
50 | 0 | CompareTextTracks::Equals(TextTrack* aOne, TextTrack* aTwo) const { |
51 | 0 | // Two tracks can never be equal. If they have corresponding TrackElements |
52 | 0 | // they would need to occupy the same tree position (impossible) and in the |
53 | 0 | // case of tracks coming from AddTextTrack source we put the newest at the |
54 | 0 | // last position, so they won't be equal as well. |
55 | 0 | return false; |
56 | 0 | } |
57 | | |
58 | | bool |
59 | | CompareTextTracks::LessThan(TextTrack* aOne, TextTrack* aTwo) const |
60 | 0 | { |
61 | 0 | // Protect against nullptr TextTrack objects; treat them as |
62 | 0 | // sorting toward the end. |
63 | 0 | if (!aOne) { |
64 | 0 | return false; |
65 | 0 | } |
66 | 0 | if (!aTwo) { |
67 | 0 | return true; |
68 | 0 | } |
69 | 0 | TextTrackSource sourceOne = aOne->GetTextTrackSource(); |
70 | 0 | TextTrackSource sourceTwo = aTwo->GetTextTrackSource(); |
71 | 0 | if (sourceOne != sourceTwo) { |
72 | 0 | return sourceOne == TextTrackSource::Track || |
73 | 0 | (sourceOne == AddTextTrack && sourceTwo == MediaResourceSpecific); |
74 | 0 | } |
75 | 0 | switch (sourceOne) { |
76 | 0 | case Track: { |
77 | 0 | int32_t positionOne = TrackChildPosition(aOne); |
78 | 0 | int32_t positionTwo = TrackChildPosition(aTwo); |
79 | 0 | // If either position one or positiontwo are -1 then something has gone |
80 | 0 | // wrong. In this case we should just put them at the back of the list. |
81 | 0 | return positionOne != -1 && positionTwo != -1 && |
82 | 0 | positionOne < positionTwo; |
83 | 0 | } |
84 | 0 | case AddTextTrack: |
85 | 0 | // For AddTextTrack sources the tracks will already be in the correct relative |
86 | 0 | // order in the source array. Assume we're called in iteration order and can |
87 | 0 | // therefore always report aOne < aTwo to maintain the original temporal ordering. |
88 | 0 | return true; |
89 | 0 | case MediaResourceSpecific: |
90 | 0 | // No rules for Media Resource Specific tracks yet. |
91 | 0 | break; |
92 | 0 | } |
93 | 0 | return true; |
94 | 0 | } |
95 | | |
96 | | NS_IMPL_CYCLE_COLLECTION(TextTrackManager, mMediaElement, mTextTracks, |
97 | | mPendingTextTracks, mNewCues, |
98 | | mLastActiveCues) |
99 | | |
100 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrackManager) |
101 | 0 | NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) |
102 | 0 | NS_INTERFACE_MAP_END |
103 | | |
104 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(TextTrackManager) |
105 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(TextTrackManager) |
106 | | |
107 | | StaticRefPtr<nsIWebVTTParserWrapper> TextTrackManager::sParserWrapper; |
108 | | |
109 | | TextTrackManager::TextTrackManager(HTMLMediaElement *aMediaElement) |
110 | | : mMediaElement(aMediaElement) |
111 | | , mHasSeeked(false) |
112 | | , mLastTimeMarchesOnCalled(0.0) |
113 | | , mTimeMarchesOnDispatched(false) |
114 | | , mUpdateCueDisplayDispatched(false) |
115 | | , performedTrackSelection(false) |
116 | | , mCueTelemetryReported(false) |
117 | | , mShutdown(false) |
118 | 0 | { |
119 | 0 | nsISupports* parentObject = |
120 | 0 | mMediaElement->OwnerDoc()->GetParentObject(); |
121 | 0 |
|
122 | 0 | NS_ENSURE_TRUE_VOID(parentObject); |
123 | 0 | WEBVTT_LOG("%p Create TextTrackManager",this); |
124 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject); |
125 | 0 | mNewCues = new TextTrackCueList(window); |
126 | 0 | mLastActiveCues = new TextTrackCueList(window); |
127 | 0 | mTextTracks = new TextTrackList(window, this); |
128 | 0 | mPendingTextTracks = new TextTrackList(window, this); |
129 | 0 |
|
130 | 0 | if (!sParserWrapper) { |
131 | 0 | nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper = |
132 | 0 | do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID); |
133 | 0 | MOZ_ASSERT(parserWrapper, "Can't create nsIWebVTTParserWrapper"); |
134 | 0 | sParserWrapper = parserWrapper; |
135 | 0 | ClearOnShutdown(&sParserWrapper); |
136 | 0 | } |
137 | 0 | mShutdownProxy = new ShutdownObserverProxy(this); |
138 | 0 | } |
139 | | |
140 | | TextTrackManager::~TextTrackManager() |
141 | 0 | { |
142 | 0 | WEBVTT_LOG("%p ~TextTrackManager",this); |
143 | 0 | nsContentUtils::UnregisterShutdownObserver(mShutdownProxy); |
144 | 0 | } |
145 | | |
146 | | TextTrackList* |
147 | | TextTrackManager::GetTextTracks() const |
148 | 0 | { |
149 | 0 | return mTextTracks; |
150 | 0 | } |
151 | | |
152 | | already_AddRefed<TextTrack> |
153 | | TextTrackManager::AddTextTrack(TextTrackKind aKind, const nsAString& aLabel, |
154 | | const nsAString& aLanguage, |
155 | | TextTrackMode aMode, |
156 | | TextTrackReadyState aReadyState, |
157 | | TextTrackSource aTextTrackSource) |
158 | 0 | { |
159 | 0 | if (!mMediaElement || !mTextTracks) { |
160 | 0 | return nullptr; |
161 | 0 | } |
162 | 0 | WEBVTT_LOG("%p AddTextTrack",this); |
163 | 0 | WEBVTT_LOGV("AddTextTrack kind %" PRIu32 " Label %s Language %s", |
164 | 0 | static_cast<uint32_t>(aKind), |
165 | 0 | NS_ConvertUTF16toUTF8(aLabel).get(), NS_ConvertUTF16toUTF8(aLanguage).get()); |
166 | 0 | RefPtr<TextTrack> track = |
167 | 0 | mTextTracks->AddTextTrack(aKind, aLabel, aLanguage, aMode, aReadyState, |
168 | 0 | aTextTrackSource, CompareTextTracks(mMediaElement)); |
169 | 0 | AddCues(track); |
170 | 0 | ReportTelemetryForTrack(track); |
171 | 0 |
|
172 | 0 | if (aTextTrackSource == TextTrackSource::Track) { |
173 | 0 | RefPtr<nsIRunnable> task = NewRunnableMethod( |
174 | 0 | "dom::TextTrackManager::HonorUserPreferencesForTrackSelection", |
175 | 0 | this, |
176 | 0 | &TextTrackManager::HonorUserPreferencesForTrackSelection); |
177 | 0 | nsContentUtils::RunInStableState(task.forget()); |
178 | 0 | } |
179 | 0 |
|
180 | 0 | return track.forget(); |
181 | 0 | } |
182 | | |
183 | | void |
184 | | TextTrackManager::AddTextTrack(TextTrack* aTextTrack) |
185 | 0 | { |
186 | 0 | if (!mMediaElement || !mTextTracks) { |
187 | 0 | return; |
188 | 0 | } |
189 | 0 | WEBVTT_LOG("%p AddTextTrack TextTrack %p",this, aTextTrack); |
190 | 0 | mTextTracks->AddTextTrack(aTextTrack, CompareTextTracks(mMediaElement)); |
191 | 0 | AddCues(aTextTrack); |
192 | 0 | ReportTelemetryForTrack(aTextTrack); |
193 | 0 |
|
194 | 0 | if (aTextTrack->GetTextTrackSource() == TextTrackSource::Track) { |
195 | 0 | RefPtr<nsIRunnable> task = NewRunnableMethod( |
196 | 0 | "dom::TextTrackManager::HonorUserPreferencesForTrackSelection", |
197 | 0 | this, |
198 | 0 | &TextTrackManager::HonorUserPreferencesForTrackSelection); |
199 | 0 | nsContentUtils::RunInStableState(task.forget()); |
200 | 0 | } |
201 | 0 | } |
202 | | |
203 | | void |
204 | | TextTrackManager::AddCues(TextTrack* aTextTrack) |
205 | 0 | { |
206 | 0 | if (!mNewCues) { |
207 | 0 | WEBVTT_LOG("AddCues mNewCues is null"); |
208 | 0 | return; |
209 | 0 | } |
210 | 0 |
|
211 | 0 | TextTrackCueList* cueList = aTextTrack->GetCues(); |
212 | 0 | if (cueList) { |
213 | 0 | bool dummy; |
214 | 0 | WEBVTT_LOGV("AddCues cueList->Length() %d",cueList->Length()); |
215 | 0 | for (uint32_t i = 0; i < cueList->Length(); ++i) { |
216 | 0 | mNewCues->AddCue(*cueList->IndexedGetter(i, dummy)); |
217 | 0 | } |
218 | 0 | DispatchTimeMarchesOn(); |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | | void |
223 | | TextTrackManager::RemoveTextTrack(TextTrack* aTextTrack, bool aPendingListOnly) |
224 | 0 | { |
225 | 0 | if (!mPendingTextTracks || !mTextTracks) { |
226 | 0 | return; |
227 | 0 | } |
228 | 0 | |
229 | 0 | WEBVTT_LOG("%p RemoveTextTrack TextTrack %p", this, aTextTrack); |
230 | 0 | mPendingTextTracks->RemoveTextTrack(aTextTrack); |
231 | 0 | if (aPendingListOnly) { |
232 | 0 | return; |
233 | 0 | } |
234 | 0 | |
235 | 0 | mTextTracks->RemoveTextTrack(aTextTrack); |
236 | 0 | // Remove the cues in mNewCues belong to aTextTrack. |
237 | 0 | TextTrackCueList* removeCueList = aTextTrack->GetCues(); |
238 | 0 | if (removeCueList) { |
239 | 0 | WEBVTT_LOGV("RemoveTextTrack removeCueList->Length() %d", removeCueList->Length()); |
240 | 0 | for (uint32_t i = 0; i < removeCueList->Length(); ++i) { |
241 | 0 | mNewCues->RemoveCue(*((*removeCueList)[i])); |
242 | 0 | } |
243 | 0 | DispatchTimeMarchesOn(); |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | | void |
248 | | TextTrackManager::DidSeek() |
249 | 0 | { |
250 | 0 | WEBVTT_LOG("%p DidSeek",this); |
251 | 0 | if (mTextTracks) { |
252 | 0 | mTextTracks->DidSeek(); |
253 | 0 | } |
254 | 0 | if (mMediaElement) { |
255 | 0 | mLastTimeMarchesOnCalled = mMediaElement->CurrentTime(); |
256 | 0 | WEBVTT_LOGV("DidSeek set mLastTimeMarchesOnCalled %lf",mLastTimeMarchesOnCalled); |
257 | 0 | } |
258 | 0 | mHasSeeked = true; |
259 | 0 | } |
260 | | |
261 | | void |
262 | | TextTrackManager::UpdateCueDisplay() |
263 | 0 | { |
264 | 0 | WEBVTT_LOG("UpdateCueDisplay"); |
265 | 0 | mUpdateCueDisplayDispatched = false; |
266 | 0 |
|
267 | 0 | if (!mMediaElement || !mTextTracks || IsShutdown()) { |
268 | 0 | return; |
269 | 0 | } |
270 | 0 | |
271 | 0 | nsIFrame* frame = mMediaElement->GetPrimaryFrame(); |
272 | 0 | nsVideoFrame* videoFrame = do_QueryFrame(frame); |
273 | 0 | if (!videoFrame) { |
274 | 0 | return; |
275 | 0 | } |
276 | 0 | |
277 | 0 | nsCOMPtr<nsIContent> overlay = videoFrame->GetCaptionOverlay(); |
278 | 0 | nsCOMPtr<nsIContent> controls = videoFrame->GetVideoControls(); |
279 | 0 | if (!overlay) { |
280 | 0 | return; |
281 | 0 | } |
282 | 0 | |
283 | 0 | nsTArray<RefPtr<TextTrackCue> > showingCues; |
284 | 0 | mTextTracks->GetShowingCues(showingCues); |
285 | 0 |
|
286 | 0 | if (showingCues.Length() > 0) { |
287 | 0 | WEBVTT_LOG("UpdateCueDisplay ProcessCues"); |
288 | 0 | WEBVTT_LOGV("UpdateCueDisplay showingCues.Length() %zu", showingCues.Length()); |
289 | 0 | RefPtr<nsVariantCC> jsCues = new nsVariantCC(); |
290 | 0 |
|
291 | 0 | jsCues->SetAsArray(nsIDataType::VTYPE_INTERFACE, |
292 | 0 | &NS_GET_IID(EventTarget), |
293 | 0 | showingCues.Length(), |
294 | 0 | static_cast<void*>(showingCues.Elements())); |
295 | 0 | nsPIDOMWindowInner* window = mMediaElement->OwnerDoc()->GetInnerWindow(); |
296 | 0 | if (window) { |
297 | 0 | sParserWrapper->ProcessCues(window, jsCues, overlay, controls); |
298 | 0 | } |
299 | 0 | } else if (overlay->Length() > 0) { |
300 | 0 | WEBVTT_LOG("UpdateCueDisplay EmptyString"); |
301 | 0 | nsContentUtils::SetNodeTextContent(overlay, EmptyString(), true); |
302 | 0 | } |
303 | 0 | } |
304 | | |
305 | | void |
306 | | TextTrackManager::NotifyCueAdded(TextTrackCue& aCue) |
307 | 0 | { |
308 | 0 | WEBVTT_LOG("NotifyCueAdded"); |
309 | 0 | if (mNewCues) { |
310 | 0 | mNewCues->AddCue(aCue); |
311 | 0 | } |
312 | 0 | DispatchTimeMarchesOn(); |
313 | 0 | ReportTelemetryForCue(); |
314 | 0 | } |
315 | | |
316 | | void |
317 | | TextTrackManager::NotifyCueRemoved(TextTrackCue& aCue) |
318 | 0 | { |
319 | 0 | WEBVTT_LOG("NotifyCueRemoved"); |
320 | 0 | if (mNewCues) { |
321 | 0 | mNewCues->RemoveCue(aCue); |
322 | 0 | } |
323 | 0 | DispatchTimeMarchesOn(); |
324 | 0 | if (aCue.GetActive()) { |
325 | 0 | // We remove an active cue, need to update the display. |
326 | 0 | DispatchUpdateCueDisplay(); |
327 | 0 | } |
328 | 0 | } |
329 | | |
330 | | void |
331 | | TextTrackManager::PopulatePendingList() |
332 | 0 | { |
333 | 0 | if (!mTextTracks || !mPendingTextTracks || !mMediaElement) { |
334 | 0 | return; |
335 | 0 | } |
336 | 0 | uint32_t len = mTextTracks->Length(); |
337 | 0 | bool dummy; |
338 | 0 | for (uint32_t index = 0; index < len; ++index) { |
339 | 0 | TextTrack* ttrack = mTextTracks->IndexedGetter(index, dummy); |
340 | 0 | if (ttrack && ttrack->Mode() != TextTrackMode::Disabled && |
341 | 0 | ttrack->ReadyState() == TextTrackReadyState::Loading) { |
342 | 0 | mPendingTextTracks->AddTextTrack(ttrack, |
343 | 0 | CompareTextTracks(mMediaElement)); |
344 | 0 | } |
345 | 0 | } |
346 | 0 | } |
347 | | |
348 | | void |
349 | | TextTrackManager::AddListeners() |
350 | 0 | { |
351 | 0 | if (mMediaElement) { |
352 | 0 | mMediaElement->AddEventListener(NS_LITERAL_STRING("resizevideocontrols"), |
353 | 0 | this, false, false); |
354 | 0 | mMediaElement->AddEventListener(NS_LITERAL_STRING("seeked"), |
355 | 0 | this, false, false); |
356 | 0 | mMediaElement->AddEventListener(NS_LITERAL_STRING("controlbarchange"), |
357 | 0 | this, false, true); |
358 | 0 | } |
359 | 0 | } |
360 | | |
361 | | void |
362 | | TextTrackManager::HonorUserPreferencesForTrackSelection() |
363 | 0 | { |
364 | 0 | if (performedTrackSelection || !mTextTracks) { |
365 | 0 | return; |
366 | 0 | } |
367 | 0 | WEBVTT_LOG("HonorUserPreferencesForTrackSelection"); |
368 | 0 | TextTrackKind ttKinds[] = { TextTrackKind::Captions, |
369 | 0 | TextTrackKind::Subtitles }; |
370 | 0 |
|
371 | 0 | // Steps 1 - 3: Perform automatic track selection for different TextTrack |
372 | 0 | // Kinds. |
373 | 0 | PerformTrackSelection(ttKinds, ArrayLength(ttKinds)); |
374 | 0 | PerformTrackSelection(TextTrackKind::Descriptions); |
375 | 0 | PerformTrackSelection(TextTrackKind::Chapters); |
376 | 0 |
|
377 | 0 | // Step 4: Set all TextTracks with a kind of metadata that are disabled |
378 | 0 | // to hidden. |
379 | 0 | for (uint32_t i = 0; i < mTextTracks->Length(); i++) { |
380 | 0 | TextTrack* track = (*mTextTracks)[i]; |
381 | 0 | if (track->Kind() == TextTrackKind::Metadata && TrackIsDefault(track) && |
382 | 0 | track->Mode() == TextTrackMode::Disabled) { |
383 | 0 | track->SetMode(TextTrackMode::Hidden); |
384 | 0 | } |
385 | 0 | } |
386 | 0 |
|
387 | 0 | performedTrackSelection = true; |
388 | 0 | } |
389 | | |
390 | | bool |
391 | | TextTrackManager::TrackIsDefault(TextTrack* aTextTrack) |
392 | 0 | { |
393 | 0 | HTMLTrackElement* trackElement = aTextTrack->GetTrackElement(); |
394 | 0 | if (!trackElement) { |
395 | 0 | return false; |
396 | 0 | } |
397 | 0 | return trackElement->Default(); |
398 | 0 | } |
399 | | |
400 | | void |
401 | | TextTrackManager::PerformTrackSelection(TextTrackKind aTextTrackKind) |
402 | 0 | { |
403 | 0 | TextTrackKind ttKinds[] = { aTextTrackKind }; |
404 | 0 | PerformTrackSelection(ttKinds, ArrayLength(ttKinds)); |
405 | 0 | } |
406 | | |
407 | | void |
408 | | TextTrackManager::PerformTrackSelection(TextTrackKind aTextTrackKinds[], |
409 | | uint32_t size) |
410 | 0 | { |
411 | 0 | nsTArray<TextTrack*> candidates; |
412 | 0 | GetTextTracksOfKinds(aTextTrackKinds, size, candidates); |
413 | 0 |
|
414 | 0 | // Step 3: If any TextTracks in candidates are showing then abort these steps. |
415 | 0 | for (uint32_t i = 0; i < candidates.Length(); i++) { |
416 | 0 | if (candidates[i]->Mode() == TextTrackMode::Showing) { |
417 | 0 | WEBVTT_LOGV("PerformTrackSelection Showing return kind %d", |
418 | 0 | static_cast<int>(candidates[i]->Kind())); |
419 | 0 | return; |
420 | 0 | } |
421 | 0 | } |
422 | 0 |
|
423 | 0 | // Step 4: Honor user preferences for track selection, otherwise, set the |
424 | 0 | // first TextTrack in candidates with a default attribute to showing. |
425 | 0 | // TODO: Bug 981691 - Honor user preferences for text track selection. |
426 | 0 | for (uint32_t i = 0; i < candidates.Length(); i++) { |
427 | 0 | if (TrackIsDefault(candidates[i]) && |
428 | 0 | candidates[i]->Mode() == TextTrackMode::Disabled) { |
429 | 0 | candidates[i]->SetMode(TextTrackMode::Showing); |
430 | 0 | WEBVTT_LOGV("PerformTrackSelection set Showing kind %d", |
431 | 0 | static_cast<int>(candidates[i]->Kind())); |
432 | 0 | return; |
433 | 0 | } |
434 | 0 | } |
435 | 0 | } |
436 | | |
437 | | void |
438 | | TextTrackManager::GetTextTracksOfKinds(TextTrackKind aTextTrackKinds[], |
439 | | uint32_t size, |
440 | | nsTArray<TextTrack*>& aTextTracks) |
441 | 0 | { |
442 | 0 | for (uint32_t i = 0; i < size; i++) { |
443 | 0 | GetTextTracksOfKind(aTextTrackKinds[i], aTextTracks); |
444 | 0 | } |
445 | 0 | } |
446 | | |
447 | | void |
448 | | TextTrackManager::GetTextTracksOfKind(TextTrackKind aTextTrackKind, |
449 | | nsTArray<TextTrack*>& aTextTracks) |
450 | 0 | { |
451 | 0 | if (!mTextTracks) { |
452 | 0 | return; |
453 | 0 | } |
454 | 0 | for (uint32_t i = 0; i < mTextTracks->Length(); i++) { |
455 | 0 | TextTrack* textTrack = (*mTextTracks)[i]; |
456 | 0 | if (textTrack->Kind() == aTextTrackKind) { |
457 | 0 | aTextTracks.AppendElement(textTrack); |
458 | 0 | } |
459 | 0 | } |
460 | 0 | } |
461 | | |
462 | | NS_IMETHODIMP |
463 | | TextTrackManager::HandleEvent(Event* aEvent) |
464 | 0 | { |
465 | 0 | if (!mTextTracks) { |
466 | 0 | return NS_OK; |
467 | 0 | } |
468 | 0 | |
469 | 0 | nsAutoString type; |
470 | 0 | aEvent->GetType(type); |
471 | 0 | if (type.EqualsLiteral("resizevideocontrols") || |
472 | 0 | type.EqualsLiteral("seeked")) { |
473 | 0 | for (uint32_t i = 0; i< mTextTracks->Length(); i++) { |
474 | 0 | ((*mTextTracks)[i])->SetCuesDirty(); |
475 | 0 | } |
476 | 0 | } |
477 | 0 |
|
478 | 0 | if (type.EqualsLiteral("controlbarchange")) { |
479 | 0 | UpdateCueDisplay(); |
480 | 0 | } |
481 | 0 |
|
482 | 0 | return NS_OK; |
483 | 0 | } |
484 | | |
485 | | |
486 | | class SimpleTextTrackEvent : public Runnable |
487 | | { |
488 | | public: |
489 | | friend class CompareSimpleTextTrackEvents; |
490 | | SimpleTextTrackEvent(const nsAString& aEventName, |
491 | | double aTime, |
492 | | TextTrack* aTrack, |
493 | | TextTrackCue* aCue) |
494 | | : Runnable("dom::SimpleTextTrackEvent") |
495 | | , mName(aEventName) |
496 | | , mTime(aTime) |
497 | | , mTrack(aTrack) |
498 | | , mCue(aCue) |
499 | 0 | {} |
500 | | |
501 | 0 | NS_IMETHOD Run() override { |
502 | 0 | WEBVTT_LOGV("SimpleTextTrackEvent cue %p mName %s mTime %lf", |
503 | 0 | mCue.get(), NS_ConvertUTF16toUTF8(mName).get(), mTime); |
504 | 0 | mCue->DispatchTrustedEvent(mName); |
505 | 0 | return NS_OK; |
506 | 0 | } |
507 | | |
508 | 0 | void Dispatch() { |
509 | 0 | if (nsCOMPtr<nsIGlobalObject> global = mCue->GetOwnerGlobal()) { |
510 | 0 | global->Dispatch(TaskCategory::Other, do_AddRef(this)); |
511 | 0 | } else { |
512 | 0 | NS_DispatchToMainThread(do_AddRef(this)); |
513 | 0 | } |
514 | 0 | } |
515 | | |
516 | | private: |
517 | | nsString mName; |
518 | | double mTime; |
519 | | TextTrack* mTrack; |
520 | | RefPtr<TextTrackCue> mCue; |
521 | | }; |
522 | | |
523 | | class CompareSimpleTextTrackEvents { |
524 | | private: |
525 | | int32_t TrackChildPosition(SimpleTextTrackEvent* aEvent) const |
526 | 0 | { |
527 | 0 | if (aEvent->mTrack) { |
528 | 0 | HTMLTrackElement* trackElement = aEvent->mTrack->GetTrackElement(); |
529 | 0 | if (trackElement) { |
530 | 0 | return mMediaElement->ComputeIndexOf(trackElement); |
531 | 0 | } |
532 | 0 | } |
533 | 0 | return -1; |
534 | 0 | } |
535 | | HTMLMediaElement* mMediaElement; |
536 | | public: |
537 | | explicit CompareSimpleTextTrackEvents(HTMLMediaElement* aMediaElement) |
538 | 0 | { |
539 | 0 | mMediaElement = aMediaElement; |
540 | 0 | } |
541 | | |
542 | | bool Equals(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const |
543 | 0 | { |
544 | 0 | return false; |
545 | 0 | } |
546 | | |
547 | | bool LessThan(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const |
548 | 0 | { |
549 | 0 | // TimeMarchesOn step 13.1. |
550 | 0 | if (aOne->mTime < aTwo->mTime) { |
551 | 0 | return true; |
552 | 0 | } else if (aOne->mTime > aTwo->mTime) { |
553 | 0 | return false; |
554 | 0 | } |
555 | 0 | |
556 | 0 | // TimeMarchesOn step 13.2 text track cue order. |
557 | 0 | // TextTrack position in TextTrackList |
558 | 0 | TextTrack* t1 = aOne->mTrack; |
559 | 0 | TextTrack* t2 = aTwo->mTrack; |
560 | 0 | MOZ_ASSERT(t1, "CompareSimpleTextTrackEvents t1 is null"); |
561 | 0 | MOZ_ASSERT(t2, "CompareSimpleTextTrackEvents t2 is null"); |
562 | 0 | if (t1 != t2) { |
563 | 0 | TextTrackList* tList= t1->GetTextTrackList(); |
564 | 0 | MOZ_ASSERT(tList, "CompareSimpleTextTrackEvents tList is null"); |
565 | 0 | nsTArray<RefPtr<TextTrack>>& textTracks = tList->GetTextTrackArray(); |
566 | 0 | auto index1 = textTracks.IndexOf(t1); |
567 | 0 | auto index2 = textTracks.IndexOf(t2); |
568 | 0 | if (index1 < index2) { |
569 | 0 | return true; |
570 | 0 | } else if (index1 > index2) { |
571 | 0 | return false; |
572 | 0 | } |
573 | 0 | } |
574 | 0 | |
575 | 0 | MOZ_ASSERT(t1 == t2, "CompareSimpleTextTrackEvents t1 != t2"); |
576 | 0 | // c1 and c2 are both belongs to t1. |
577 | 0 | TextTrackCue* c1 = aOne->mCue; |
578 | 0 | TextTrackCue* c2 = aTwo->mCue; |
579 | 0 | if (c1 != c2) { |
580 | 0 | if (c1->StartTime() < c2->StartTime()) { |
581 | 0 | return true; |
582 | 0 | } else if (c1->StartTime() > c2->StartTime()) { |
583 | 0 | return false; |
584 | 0 | } |
585 | 0 | if (c1->EndTime() < c2->EndTime()) { |
586 | 0 | return true; |
587 | 0 | } else if (c1->EndTime() > c2->EndTime()) { |
588 | 0 | return false; |
589 | 0 | } |
590 | 0 | |
591 | 0 | TextTrackCueList* cueList = t1->GetCues(); |
592 | 0 | nsTArray<RefPtr<TextTrackCue>>& cues = cueList->GetCuesArray(); |
593 | 0 | auto index1 = cues.IndexOf(c1); |
594 | 0 | auto index2 = cues.IndexOf(c2); |
595 | 0 | if (index1 < index2) { |
596 | 0 | return true; |
597 | 0 | } else if (index1 > index2) { |
598 | 0 | return false; |
599 | 0 | } |
600 | 0 | } |
601 | 0 | |
602 | 0 | // TimeMarchesOn step 13.3. |
603 | 0 | if (aOne->mName.EqualsLiteral("enter") || |
604 | 0 | aTwo->mName.EqualsLiteral("exit")) { |
605 | 0 | return true; |
606 | 0 | } |
607 | 0 | return false; |
608 | 0 | } |
609 | | }; |
610 | | |
611 | | class TextTrackListInternal |
612 | | { |
613 | | public: |
614 | | void AddTextTrack(TextTrack* aTextTrack, |
615 | | const CompareTextTracks& aCompareTT) |
616 | 0 | { |
617 | 0 | if (!mTextTracks.Contains(aTextTrack)) { |
618 | 0 | mTextTracks.InsertElementSorted(aTextTrack, aCompareTT); |
619 | 0 | } |
620 | 0 | } |
621 | | uint32_t Length() const |
622 | 0 | { |
623 | 0 | return mTextTracks.Length(); |
624 | 0 | } |
625 | | TextTrack* operator[](uint32_t aIndex) |
626 | 0 | { |
627 | 0 | return mTextTracks.SafeElementAt(aIndex, nullptr); |
628 | 0 | } |
629 | | private: |
630 | | nsTArray<RefPtr<TextTrack>> mTextTracks; |
631 | | }; |
632 | | |
633 | | void |
634 | | TextTrackManager::DispatchUpdateCueDisplay() |
635 | 0 | { |
636 | 0 | if (!mUpdateCueDisplayDispatched && !IsShutdown() && |
637 | 0 | mMediaElement->IsCurrentlyPlaying()) { |
638 | 0 | WEBVTT_LOG("DispatchUpdateCueDisplay"); |
639 | 0 | nsPIDOMWindowInner* win = mMediaElement->OwnerDoc()->GetInnerWindow(); |
640 | 0 | if (win) { |
641 | 0 | nsGlobalWindowInner::Cast(win)->Dispatch( |
642 | 0 | TaskCategory::Other, |
643 | 0 | NewRunnableMethod("dom::TextTrackManager::UpdateCueDisplay", |
644 | 0 | this, |
645 | 0 | &TextTrackManager::UpdateCueDisplay)); |
646 | 0 | mUpdateCueDisplayDispatched = true; |
647 | 0 | } |
648 | 0 | } |
649 | 0 | } |
650 | | |
651 | | void |
652 | | TextTrackManager::DispatchTimeMarchesOn() |
653 | 0 | { |
654 | 0 | // Run the algorithm if no previous instance is still running, otherwise |
655 | 0 | // enqueue the current playback position and whether only that changed |
656 | 0 | // through its usual monotonic increase during normal playback; current |
657 | 0 | // executing call upon completion will check queue for further 'work'. |
658 | 0 | if (!mTimeMarchesOnDispatched && !IsShutdown() && |
659 | 0 | mMediaElement->IsCurrentlyPlaying()) { |
660 | 0 | WEBVTT_LOG("DispatchTimeMarchesOn"); |
661 | 0 | nsPIDOMWindowInner* win = mMediaElement->OwnerDoc()->GetInnerWindow(); |
662 | 0 | if (win) { |
663 | 0 | nsGlobalWindowInner::Cast(win)->Dispatch( |
664 | 0 | TaskCategory::Other, |
665 | 0 | NewRunnableMethod("dom::TextTrackManager::TimeMarchesOn", |
666 | 0 | this, |
667 | 0 | &TextTrackManager::TimeMarchesOn)); |
668 | 0 | mTimeMarchesOnDispatched = true; |
669 | 0 | } |
670 | 0 | } |
671 | 0 | } |
672 | | |
673 | | // https://html.spec.whatwg.org/multipage/embedded-content.html#time-marches-on |
674 | | void |
675 | | TextTrackManager::TimeMarchesOn() |
676 | 0 | { |
677 | 0 | NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
678 | 0 | WEBVTT_LOG("TimeMarchesOn"); |
679 | 0 | mTimeMarchesOnDispatched = false; |
680 | 0 |
|
681 | 0 | // Early return if we don't have any TextTracks or shutting down. |
682 | 0 | if (!mTextTracks || mTextTracks->Length() == 0 || IsShutdown()) { |
683 | 0 | return; |
684 | 0 | } |
685 | 0 | |
686 | 0 | nsISupports* parentObject = |
687 | 0 | mMediaElement->OwnerDoc()->GetParentObject(); |
688 | 0 | if (NS_WARN_IF(!parentObject)) { |
689 | 0 | return; |
690 | 0 | } |
691 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject); |
692 | 0 |
|
693 | 0 | if (mMediaElement && |
694 | 0 | (!(mMediaElement->GetPlayedOrSeeked()) || mMediaElement->Seeking())) { |
695 | 0 | WEBVTT_LOG("TimeMarchesOn seeking or post return"); |
696 | 0 | return; |
697 | 0 | } |
698 | 0 |
|
699 | 0 | // Step 3. |
700 | 0 | double currentPlaybackTime = mMediaElement->CurrentTime(); |
701 | 0 | bool hasNormalPlayback = !mHasSeeked; |
702 | 0 | mHasSeeked = false; |
703 | 0 | WEBVTT_LOG("TimeMarchesOn mLastTimeMarchesOnCalled %lf currentPlaybackTime %lf hasNormalPlayback %d" |
704 | 0 | , mLastTimeMarchesOnCalled, currentPlaybackTime, hasNormalPlayback); |
705 | 0 |
|
706 | 0 | // Step 1, 2. |
707 | 0 | RefPtr<TextTrackCueList> currentCues = |
708 | 0 | new TextTrackCueList(window); |
709 | 0 | RefPtr<TextTrackCueList> otherCues = |
710 | 0 | new TextTrackCueList(window); |
711 | 0 | bool dummy; |
712 | 0 | for (uint32_t index = 0; index < mTextTracks->Length(); ++index) { |
713 | 0 | TextTrack* ttrack = mTextTracks->IndexedGetter(index, dummy); |
714 | 0 | if (ttrack && dummy) { |
715 | 0 | // TODO: call GetCueListByTimeInterval on mNewCues? |
716 | 0 | ttrack->UpdateActiveCueList(); |
717 | 0 | TextTrackCueList* activeCueList = ttrack->GetActiveCues(); |
718 | 0 | if (activeCueList) { |
719 | 0 | for (uint32_t i = 0; i < activeCueList->Length(); ++i) { |
720 | 0 | currentCues->AddCue(*((*activeCueList)[i])); |
721 | 0 | } |
722 | 0 | } |
723 | 0 | } |
724 | 0 | } |
725 | 0 | WEBVTT_LOGV("TimeMarchesOn currentCues %d", currentCues->Length()); |
726 | 0 | // Populate otherCues with 'non-active" cues. |
727 | 0 | if (hasNormalPlayback) { |
728 | 0 | if (currentPlaybackTime < mLastTimeMarchesOnCalled) { |
729 | 0 | // TODO: Add log and find the root cause why the |
730 | 0 | // playback position goes backward. |
731 | 0 | mLastTimeMarchesOnCalled = currentPlaybackTime; |
732 | 0 | } |
733 | 0 | media::Interval<double> interval(mLastTimeMarchesOnCalled, |
734 | 0 | currentPlaybackTime); |
735 | 0 | otherCues = mNewCues->GetCueListByTimeInterval(interval);; |
736 | 0 | } else { |
737 | 0 | // Seek case. Put the mLastActiveCues into otherCues. |
738 | 0 | otherCues = mLastActiveCues; |
739 | 0 | } |
740 | 0 | for (uint32_t i = 0; i < currentCues->Length(); ++i) { |
741 | 0 | TextTrackCue* cue = (*currentCues)[i]; |
742 | 0 | otherCues->RemoveCue(*cue); |
743 | 0 | } |
744 | 0 | WEBVTT_LOGV("TimeMarchesOn otherCues %d", otherCues->Length()); |
745 | 0 | // Step 4. |
746 | 0 | RefPtr<TextTrackCueList> missedCues = new TextTrackCueList(window); |
747 | 0 | if (hasNormalPlayback) { |
748 | 0 | for (uint32_t i = 0; i < otherCues->Length(); ++i) { |
749 | 0 | TextTrackCue* cue = (*otherCues)[i]; |
750 | 0 | if (cue->StartTime() >= mLastTimeMarchesOnCalled && |
751 | 0 | cue->EndTime() <= currentPlaybackTime) { |
752 | 0 | missedCues->AddCue(*cue); |
753 | 0 | } |
754 | 0 | } |
755 | 0 | } |
756 | 0 | WEBVTT_LOGV("TimeMarchesOn missedCues %d", missedCues->Length()); |
757 | 0 | // Step 5. Empty now. |
758 | 0 | // TODO: Step 6: fire timeupdate? |
759 | 0 |
|
760 | 0 | // Step 7. Abort steps if condition 1, 2, 3 are satisfied. |
761 | 0 | // 1. All of the cues in current cues have their active flag set. |
762 | 0 | // 2. None of the cues in other cues have their active flag set. |
763 | 0 | // 3. Missed cues is empty. |
764 | 0 | bool c1 = true; |
765 | 0 | for (uint32_t i = 0; i < currentCues->Length(); ++i) { |
766 | 0 | if (!(*currentCues)[i]->GetActive()) { |
767 | 0 | c1 = false; |
768 | 0 | break; |
769 | 0 | } |
770 | 0 | } |
771 | 0 | bool c2 = true; |
772 | 0 | for (uint32_t i = 0; i < otherCues->Length(); ++i) { |
773 | 0 | if ((*otherCues)[i]->GetActive()) { |
774 | 0 | c2 = false; |
775 | 0 | break; |
776 | 0 | } |
777 | 0 | } |
778 | 0 | bool c3 = (missedCues->Length() == 0); |
779 | 0 | if (c1 && c2 && c3) { |
780 | 0 | mLastTimeMarchesOnCalled = currentPlaybackTime; |
781 | 0 | WEBVTT_LOG("TimeMarchesOn step 7 return, mLastTimeMarchesOnCalled %lf", mLastTimeMarchesOnCalled); |
782 | 0 | return; |
783 | 0 | } |
784 | 0 |
|
785 | 0 | // Step 8. Respect PauseOnExit flag if not seek. |
786 | 0 | if (hasNormalPlayback) { |
787 | 0 | for (uint32_t i = 0; i < otherCues->Length(); ++i) { |
788 | 0 | TextTrackCue* cue = (*otherCues)[i]; |
789 | 0 | if (cue && cue->PauseOnExit() && cue->GetActive()) { |
790 | 0 | WEBVTT_LOG("TimeMarchesOn pause the MediaElement"); |
791 | 0 | mMediaElement->Pause(); |
792 | 0 | break; |
793 | 0 | } |
794 | 0 | } |
795 | 0 | for (uint32_t i = 0; i < missedCues->Length(); ++i) { |
796 | 0 | TextTrackCue* cue = (*missedCues)[i]; |
797 | 0 | if (cue && cue->PauseOnExit()) { |
798 | 0 | WEBVTT_LOG("TimeMarchesOn pause the MediaElement"); |
799 | 0 | mMediaElement->Pause(); |
800 | 0 | break; |
801 | 0 | } |
802 | 0 | } |
803 | 0 | } |
804 | 0 |
|
805 | 0 | // Step 15. |
806 | 0 | // Sort text tracks in the same order as the text tracks appear |
807 | 0 | // in the media element's list of text tracks, and remove |
808 | 0 | // duplicates. |
809 | 0 | TextTrackListInternal affectedTracks; |
810 | 0 | // Step 13, 14. |
811 | 0 | nsTArray<RefPtr<SimpleTextTrackEvent>> eventList; |
812 | 0 | // Step 9, 10. |
813 | 0 | // For each text track cue in missed cues, prepare an event named |
814 | 0 | // enter for the TextTrackCue object with the cue start time. |
815 | 0 | for (uint32_t i = 0; i < missedCues->Length(); ++i) { |
816 | 0 | TextTrackCue* cue = (*missedCues)[i]; |
817 | 0 | if (cue) { |
818 | 0 | SimpleTextTrackEvent* event = |
819 | 0 | new SimpleTextTrackEvent(NS_LITERAL_STRING("enter"), |
820 | 0 | cue->StartTime(), cue->GetTrack(), |
821 | 0 | cue); |
822 | 0 | eventList.InsertElementSorted(event, |
823 | 0 | CompareSimpleTextTrackEvents(mMediaElement)); |
824 | 0 | affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement)); |
825 | 0 | } |
826 | 0 | } |
827 | 0 |
|
828 | 0 | // Step 11, 17. |
829 | 0 | for (uint32_t i = 0; i < otherCues->Length(); ++i) { |
830 | 0 | TextTrackCue* cue = (*otherCues)[i]; |
831 | 0 | if (cue->GetActive() || missedCues->IsCueExist(cue)) { |
832 | 0 | double time = cue->StartTime() > cue->EndTime() ? cue->StartTime() |
833 | 0 | : cue->EndTime(); |
834 | 0 | SimpleTextTrackEvent* event = |
835 | 0 | new SimpleTextTrackEvent(NS_LITERAL_STRING("exit"), time, |
836 | 0 | cue->GetTrack(), cue); |
837 | 0 | eventList.InsertElementSorted(event, |
838 | 0 | CompareSimpleTextTrackEvents(mMediaElement)); |
839 | 0 | affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement)); |
840 | 0 | } |
841 | 0 | cue->SetActive(false); |
842 | 0 | } |
843 | 0 |
|
844 | 0 | // Step 12, 17. |
845 | 0 | for (uint32_t i = 0; i < currentCues->Length(); ++i) { |
846 | 0 | TextTrackCue* cue = (*currentCues)[i]; |
847 | 0 | if (!cue->GetActive()) { |
848 | 0 | SimpleTextTrackEvent* event = |
849 | 0 | new SimpleTextTrackEvent(NS_LITERAL_STRING("enter"), |
850 | 0 | cue->StartTime(), cue->GetTrack(), |
851 | 0 | cue); |
852 | 0 | eventList.InsertElementSorted(event, |
853 | 0 | CompareSimpleTextTrackEvents(mMediaElement)); |
854 | 0 | affectedTracks.AddTextTrack(cue->GetTrack(), CompareTextTracks(mMediaElement)); |
855 | 0 | } |
856 | 0 | cue->SetActive(true); |
857 | 0 | } |
858 | 0 |
|
859 | 0 | // Fire the eventList |
860 | 0 | for (uint32_t i = 0; i < eventList.Length(); ++i) { |
861 | 0 | eventList[i]->Dispatch(); |
862 | 0 | } |
863 | 0 |
|
864 | 0 | // Step 16. |
865 | 0 | for (uint32_t i = 0; i < affectedTracks.Length(); ++i) { |
866 | 0 | TextTrack* ttrack = affectedTracks[i]; |
867 | 0 | if (ttrack) { |
868 | 0 | ttrack->DispatchAsyncTrustedEvent(NS_LITERAL_STRING("cuechange")); |
869 | 0 | HTMLTrackElement* trackElement = ttrack->GetTrackElement(); |
870 | 0 | if (trackElement) { |
871 | 0 | trackElement->DispatchTrackRunnable(NS_LITERAL_STRING("cuechange")); |
872 | 0 | } |
873 | 0 | } |
874 | 0 | } |
875 | 0 |
|
876 | 0 | mLastTimeMarchesOnCalled = currentPlaybackTime; |
877 | 0 | mLastActiveCues = currentCues; |
878 | 0 |
|
879 | 0 | // Step 18. |
880 | 0 | UpdateCueDisplay(); |
881 | 0 | } |
882 | | |
883 | | void |
884 | | TextTrackManager::NotifyCueUpdated(TextTrackCue *aCue) |
885 | 0 | { |
886 | 0 | // TODO: Add/Reorder the cue to mNewCues if we have some optimization? |
887 | 0 | WEBVTT_LOG("NotifyCueUpdated"); |
888 | 0 | DispatchTimeMarchesOn(); |
889 | 0 | // For the case "Texttrack.mode = hidden/showing", if the mode |
890 | 0 | // changing between showing and hidden, TimeMarchesOn |
891 | 0 | // doesn't render the cue. Call DispatchUpdateCueDisplay() explicitly. |
892 | 0 | DispatchUpdateCueDisplay(); |
893 | 0 | } |
894 | | |
895 | | void |
896 | | TextTrackManager::NotifyReset() |
897 | 0 | { |
898 | 0 | WEBVTT_LOG("NotifyReset"); |
899 | 0 | mLastTimeMarchesOnCalled = 0.0; |
900 | 0 | } |
901 | | |
902 | | void |
903 | | TextTrackManager::ReportTelemetryForTrack(TextTrack* aTextTrack) const |
904 | 0 | { |
905 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
906 | 0 | MOZ_ASSERT(aTextTrack); |
907 | 0 | MOZ_ASSERT(mTextTracks->Length() > 0); |
908 | 0 |
|
909 | 0 | TextTrackKind kind = aTextTrack->Kind(); |
910 | 0 | Telemetry::Accumulate(Telemetry::WEBVTT_TRACK_KINDS, uint32_t(kind)); |
911 | 0 | } |
912 | | |
913 | | void |
914 | | TextTrackManager::ReportTelemetryForCue() |
915 | 0 | { |
916 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
917 | 0 | MOZ_ASSERT(!mNewCues->IsEmpty() || !mLastActiveCues->IsEmpty()); |
918 | 0 |
|
919 | 0 | if (!mCueTelemetryReported) { |
920 | 0 | Telemetry::Accumulate(Telemetry::WEBVTT_USED_VTT_CUES, 1); |
921 | 0 | mCueTelemetryReported = true; |
922 | 0 | } |
923 | 0 | } |
924 | | |
925 | | bool |
926 | | TextTrackManager::IsLoaded() |
927 | 0 | { |
928 | 0 | return mTextTracks ? mTextTracks->AreTextTracksLoaded() : true; |
929 | 0 | } |
930 | | |
931 | | bool |
932 | | TextTrackManager::IsShutdown() const |
933 | 0 | { |
934 | 0 | return (mShutdown || !sParserWrapper); |
935 | 0 | } |
936 | | |
937 | | } // namespace dom |
938 | | } // namespace mozilla |