Bug 1572460 - Refactor `selection` out of the `InspectorFront`. r=yulia
[gecko.git] / dom / media / TextTrack.cpp
blobd8a1cb92ebf10d2f6ac4190d38aa4c8c4746e4be
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 et tw=78: */
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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/AsyncEventDispatcher.h"
8 #include "mozilla/dom/TextTrack.h"
9 #include "mozilla/dom/TextTrackBinding.h"
10 #include "mozilla/dom/TextTrackList.h"
11 #include "mozilla/dom/TextTrackCue.h"
12 #include "mozilla/dom/TextTrackCueList.h"
13 #include "mozilla/dom/TextTrackRegion.h"
14 #include "mozilla/dom/HTMLMediaElement.h"
15 #include "mozilla/dom/HTMLTrackElement.h"
16 #include "nsGlobalWindow.h"
18 extern mozilla::LazyLogModule gTextTrackLog;
20 #define WEBVTT_LOG(msg, ...) \
21 MOZ_LOG(gTextTrackLog, LogLevel::Debug, \
22 ("TextTrack=%p, " msg, this, ##__VA_ARGS__))
24 namespace mozilla {
25 namespace dom {
27 static const char* ToStateStr(const TextTrackMode aMode) {
28 switch (aMode) {
29 case TextTrackMode::Disabled:
30 return "DISABLED";
31 case TextTrackMode::Hidden:
32 return "HIDDEN";
33 case TextTrackMode::Showing:
34 return "SHOWING";
35 default:
36 MOZ_ASSERT_UNREACHABLE("Invalid state.");
38 return "Unknown";
41 static const char* ToReadyStateStr(const TextTrackReadyState aState) {
42 switch (aState) {
43 case TextTrackReadyState::NotLoaded:
44 return "NotLoaded";
45 case TextTrackReadyState::Loading:
46 return "Loading";
47 case TextTrackReadyState::Loaded:
48 return "Loaded";
49 case TextTrackReadyState::FailedToLoad:
50 return "FailedToLoad";
51 default:
52 MOZ_ASSERT_UNREACHABLE("Invalid state.");
54 return "Unknown";
57 static const char* ToTextTrackKindStr(const TextTrackKind aKind) {
58 switch (aKind) {
59 case TextTrackKind::Subtitles:
60 return "Subtitles";
61 case TextTrackKind::Captions:
62 return "Captions";
63 case TextTrackKind::Descriptions:
64 return "Descriptions";
65 case TextTrackKind::Chapters:
66 return "Chapters";
67 case TextTrackKind::Metadata:
68 return "Metadata";
69 default:
70 MOZ_ASSERT_UNREACHABLE("Invalid kind.");
72 return "Unknown";
75 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrack, DOMEventTargetHelper, mCueList,
76 mActiveCueList, mTextTrackList,
77 mTrackElement)
79 NS_IMPL_ADDREF_INHERITED(TextTrack, DOMEventTargetHelper)
80 NS_IMPL_RELEASE_INHERITED(TextTrack, DOMEventTargetHelper)
81 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrack)
82 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
84 TextTrack::TextTrack(nsPIDOMWindowInner* aOwnerWindow, TextTrackKind aKind,
85 const nsAString& aLabel, const nsAString& aLanguage,
86 TextTrackMode aMode, TextTrackReadyState aReadyState,
87 TextTrackSource aTextTrackSource)
88 : DOMEventTargetHelper(aOwnerWindow),
89 mKind(aKind),
90 mLabel(aLabel),
91 mLanguage(aLanguage),
92 mMode(aMode),
93 mReadyState(aReadyState),
94 mTextTrackSource(aTextTrackSource) {
95 SetDefaultSettings();
98 TextTrack::TextTrack(nsPIDOMWindowInner* aOwnerWindow,
99 TextTrackList* aTextTrackList, TextTrackKind aKind,
100 const nsAString& aLabel, const nsAString& aLanguage,
101 TextTrackMode aMode, TextTrackReadyState aReadyState,
102 TextTrackSource aTextTrackSource)
103 : DOMEventTargetHelper(aOwnerWindow),
104 mTextTrackList(aTextTrackList),
105 mKind(aKind),
106 mLabel(aLabel),
107 mLanguage(aLanguage),
108 mMode(aMode),
109 mReadyState(aReadyState),
110 mTextTrackSource(aTextTrackSource) {
111 SetDefaultSettings();
114 TextTrack::~TextTrack() {}
116 void TextTrack::SetDefaultSettings() {
117 nsPIDOMWindowInner* ownerWindow = GetOwner();
118 mCueList = new TextTrackCueList(ownerWindow);
119 mActiveCueList = new TextTrackCueList(ownerWindow);
120 mCuePos = 0;
121 mDirty = false;
124 JSObject* TextTrack::WrapObject(JSContext* aCx,
125 JS::Handle<JSObject*> aGivenProto) {
126 return TextTrack_Binding::Wrap(aCx, this, aGivenProto);
129 void TextTrack::SetMode(TextTrackMode aValue) {
130 if (mMode == aValue) {
131 return;
133 WEBVTT_LOG("Set mode=%s for track kind %s", ToStateStr(aValue),
134 ToTextTrackKindStr(mKind));
135 mMode = aValue;
137 HTMLMediaElement* mediaElement = GetMediaElement();
138 if (aValue == TextTrackMode::Disabled) {
139 for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) {
140 mediaElement->NotifyCueRemoved(*(*mCueList)[i]);
142 SetCuesInactive();
143 } else {
144 for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) {
145 mediaElement->NotifyCueAdded(*(*mCueList)[i]);
148 if (mediaElement) {
149 mediaElement->NotifyTextTrackModeChanged();
151 // https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks:start-the-track-processing-model
152 // Run the `start-the-track-processing-model` to track's corresponding track
153 // element whenever track's mode changes.
154 if (mTrackElement) {
155 mTrackElement->MaybeDispatchLoadResource();
157 // Ensure the TimeMarchesOn is called in case that the mCueList
158 // is empty.
159 NotifyCueUpdated(nullptr);
162 void TextTrack::GetId(nsAString& aId) const {
163 // If the track has a track element then its id should be the same as the
164 // track element's id.
165 if (mTrackElement) {
166 mTrackElement->GetAttribute(NS_LITERAL_STRING("id"), aId);
170 void TextTrack::AddCue(TextTrackCue& aCue) {
171 WEBVTT_LOG("AddCue %p [%f:%f]", &aCue, aCue.StartTime(), aCue.EndTime());
172 TextTrack* oldTextTrack = aCue.GetTrack();
173 if (oldTextTrack) {
174 ErrorResult dummy;
175 oldTextTrack->RemoveCue(aCue, dummy);
177 mCueList->AddCue(aCue);
178 aCue.SetTrack(this);
179 HTMLMediaElement* mediaElement = GetMediaElement();
180 if (mediaElement && (mMode != TextTrackMode::Disabled)) {
181 mediaElement->NotifyCueAdded(aCue);
185 void TextTrack::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) {
186 WEBVTT_LOG("RemoveCue %p", &aCue);
187 // Bug1304948, check the aCue belongs to the TextTrack.
188 mCueList->RemoveCue(aCue, aRv);
189 if (aRv.Failed()) {
190 return;
192 aCue.SetActive(false);
193 aCue.SetTrack(nullptr);
194 HTMLMediaElement* mediaElement = GetMediaElement();
195 if (mediaElement) {
196 mediaElement->NotifyCueRemoved(aCue);
200 void TextTrack::ClearAllCues() {
201 WEBVTT_LOG("ClearAllCues");
202 ErrorResult dummy;
203 while (!mCueList->IsEmpty()) {
204 RemoveCue(*(*mCueList)[0], dummy);
208 void TextTrack::SetCuesDirty() {
209 for (uint32_t i = 0; i < mCueList->Length(); i++) {
210 ((*mCueList)[i])->Reset();
214 TextTrackCueList* TextTrack::GetActiveCues() {
215 if (mMode != TextTrackMode::Disabled) {
216 return mActiveCueList;
218 return nullptr;
221 void TextTrack::GetActiveCueArray(nsTArray<RefPtr<TextTrackCue> >& aCues) {
222 if (mMode != TextTrackMode::Disabled) {
223 mActiveCueList->GetArray(aCues);
227 TextTrackReadyState TextTrack::ReadyState() const { return mReadyState; }
229 void TextTrack::SetReadyState(uint32_t aReadyState) {
230 if (aReadyState <= TextTrackReadyState::FailedToLoad) {
231 SetReadyState(static_cast<TextTrackReadyState>(aReadyState));
235 void TextTrack::SetReadyState(TextTrackReadyState aState) {
236 WEBVTT_LOG("SetReadyState=%s", ToReadyStateStr(aState));
237 mReadyState = aState;
238 HTMLMediaElement* mediaElement = GetMediaElement();
239 if (mediaElement && (mReadyState == TextTrackReadyState::Loaded ||
240 mReadyState == TextTrackReadyState::FailedToLoad)) {
241 mediaElement->RemoveTextTrack(this, true);
242 mediaElement->UpdateReadyState();
246 TextTrackList* TextTrack::GetTextTrackList() { return mTextTrackList; }
248 void TextTrack::SetTextTrackList(TextTrackList* aTextTrackList) {
249 mTextTrackList = aTextTrackList;
252 HTMLTrackElement* TextTrack::GetTrackElement() { return mTrackElement; }
254 void TextTrack::SetTrackElement(HTMLTrackElement* aTrackElement) {
255 mTrackElement = aTrackElement;
258 void TextTrack::SetCuesInactive() {
259 WEBVTT_LOG("SetCuesInactive");
260 mCueList->SetCuesInactive();
263 void TextTrack::NotifyCueUpdated(TextTrackCue* aCue) {
264 WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue);
265 mCueList->NotifyCueUpdated(aCue);
266 HTMLMediaElement* mediaElement = GetMediaElement();
267 if (mediaElement) {
268 mediaElement->NotifyCueUpdated(aCue);
272 void TextTrack::GetLabel(nsAString& aLabel) const {
273 if (mTrackElement) {
274 mTrackElement->GetLabel(aLabel);
275 } else {
276 aLabel = mLabel;
279 void TextTrack::GetLanguage(nsAString& aLanguage) const {
280 if (mTrackElement) {
281 mTrackElement->GetSrclang(aLanguage);
282 } else {
283 aLanguage = mLanguage;
287 void TextTrack::DispatchAsyncTrustedEvent(const nsString& aEventName) {
288 nsPIDOMWindowInner* win = GetOwner();
289 if (!win) {
290 return;
292 RefPtr<TextTrack> self = this;
293 nsGlobalWindowInner::Cast(win)->Dispatch(
294 TaskCategory::Other,
295 NS_NewRunnableFunction(
296 "dom::TextTrack::DispatchAsyncTrustedEvent",
297 [self, aEventName]() { self->DispatchTrustedEvent(aEventName); }));
300 bool TextTrack::IsLoaded() {
301 if (mMode == TextTrackMode::Disabled) {
302 return true;
304 // If the TrackElement's src is null, we can not block the
305 // MediaElement.
306 if (mTrackElement) {
307 nsAutoString src;
308 if (!(mTrackElement->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src))) {
309 return true;
312 return (mReadyState >= Loaded);
315 void TextTrack::NotifyCueActiveStateChanged(TextTrackCue* aCue) {
316 MOZ_ASSERT(aCue);
317 if (aCue->GetActive()) {
318 MOZ_ASSERT(!mActiveCueList->IsCueExist(aCue));
319 WEBVTT_LOG("NotifyCueActiveStateChanged, add cue %p to the active list",
320 aCue);
321 mActiveCueList->AddCue(*aCue);
322 } else {
323 MOZ_ASSERT(mActiveCueList->IsCueExist(aCue));
324 WEBVTT_LOG(
325 "NotifyCueActiveStateChanged, remove cue %p from the active list",
326 aCue);
327 mActiveCueList->RemoveCue(*aCue);
331 void TextTrack::GetCurrentCuesAndOtherCues(
332 RefPtr<TextTrackCueList>& aCurrentCues,
333 RefPtr<TextTrackCueList>& aOtherCues,
334 const media::TimeInterval& aInterval) const {
335 const HTMLMediaElement* mediaElement = GetMediaElement();
336 if (!mediaElement) {
337 return;
340 if (Mode() == TextTrackMode::Disabled) {
341 return;
344 // According to `time marches on` step1, current cue list contains the cues
345 // whose start times are less than or equal to the current playback position
346 // and whose end times are greater than the current playback position.
347 // https://html.spec.whatwg.org/multipage/media.html#time-marches-on
348 MOZ_ASSERT(aCurrentCues && aOtherCues);
349 const double playbackTime = mediaElement->CurrentTime();
350 for (uint32_t idx = 0; idx < mCueList->Length(); idx++) {
351 TextTrackCue* cue = (*mCueList)[idx];
352 WEBVTT_LOG("cue %p [%f:%f], playbackTime=%f", cue, cue->StartTime(),
353 cue->EndTime(), playbackTime);
354 if (cue->StartTime() <= playbackTime && cue->EndTime() > playbackTime) {
355 WEBVTT_LOG("Add cue %p [%f:%f] to current cue list", cue,
356 cue->StartTime(), cue->EndTime());
357 aCurrentCues->AddCue(*cue);
358 } else {
359 // As the spec didn't have a restriction for the negative duration, it
360 // does happen sometime if user sets it explictly. It would be treated as
361 // a `missing cue` later in the `TimeMarchesOn` but it won't be displayed.
362 if (cue->EndTime() < cue->StartTime()) {
363 // Add cue into `otherCue` only when its start time is contained by the
364 // current time interval.
365 if (aInterval.Contains(
366 media::TimeUnit::FromSeconds(cue->StartTime()))) {
367 WEBVTT_LOG("[Negative duration] Add cue %p [%f:%f] to other cue list",
368 cue, cue->StartTime(), cue->EndTime());
369 aOtherCues->AddCue(*cue);
371 continue;
373 media::TimeInterval cueInterval(
374 media::TimeUnit::FromSeconds(cue->StartTime()),
375 media::TimeUnit::FromSeconds(cue->EndTime()));
376 // cues are completely outside the time interval.
377 if (!aInterval.Touches(cueInterval)) {
378 continue;
380 // contains any cues which are overlapping within the time interval.
381 WEBVTT_LOG("Add cue %p [%f:%f] to other cue list", cue, cue->StartTime(),
382 cue->EndTime());
383 aOtherCues->AddCue(*cue);
388 HTMLMediaElement* TextTrack::GetMediaElement() const {
389 return mTextTrackList ? mTextTrackList->GetMediaElement() : nullptr;
392 } // namespace dom
393 } // namespace mozilla