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__))
27 static const char* ToStateStr(const TextTrackMode aMode
) {
29 case TextTrackMode::Disabled
:
31 case TextTrackMode::Hidden
:
33 case TextTrackMode::Showing
:
36 MOZ_ASSERT_UNREACHABLE("Invalid state.");
41 static const char* ToReadyStateStr(const TextTrackReadyState aState
) {
43 case TextTrackReadyState::NotLoaded
:
45 case TextTrackReadyState::Loading
:
47 case TextTrackReadyState::Loaded
:
49 case TextTrackReadyState::FailedToLoad
:
50 return "FailedToLoad";
52 MOZ_ASSERT_UNREACHABLE("Invalid state.");
57 static const char* ToTextTrackKindStr(const TextTrackKind aKind
) {
59 case TextTrackKind::Subtitles
:
61 case TextTrackKind::Captions
:
63 case TextTrackKind::Descriptions
:
64 return "Descriptions";
65 case TextTrackKind::Chapters
:
67 case TextTrackKind::Metadata
:
70 MOZ_ASSERT_UNREACHABLE("Invalid kind.");
75 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrack
, DOMEventTargetHelper
, mCueList
,
76 mActiveCueList
, mTextTrackList
,
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
),
93 mReadyState(aReadyState
),
94 mTextTrackSource(aTextTrackSource
) {
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
),
107 mLanguage(aLanguage
),
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
);
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
) {
133 WEBVTT_LOG("Set mode=%s for track kind %s", ToStateStr(aValue
),
134 ToTextTrackKindStr(mKind
));
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
]);
144 for (size_t i
= 0; i
< mCueList
->Length() && mediaElement
; ++i
) {
145 mediaElement
->NotifyCueAdded(*(*mCueList
)[i
]);
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.
155 mTrackElement
->MaybeDispatchLoadResource();
157 // Ensure the TimeMarchesOn is called in case that the mCueList
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.
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();
175 oldTextTrack
->RemoveCue(aCue
, dummy
);
177 mCueList
->AddCue(aCue
);
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
);
192 aCue
.SetActive(false);
193 aCue
.SetTrack(nullptr);
194 HTMLMediaElement
* mediaElement
= GetMediaElement();
196 mediaElement
->NotifyCueRemoved(aCue
);
200 void TextTrack::ClearAllCues() {
201 WEBVTT_LOG("ClearAllCues");
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
;
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();
268 mediaElement
->NotifyCueUpdated(aCue
);
272 void TextTrack::GetLabel(nsAString
& aLabel
) const {
274 mTrackElement
->GetLabel(aLabel
);
279 void TextTrack::GetLanguage(nsAString
& aLanguage
) const {
281 mTrackElement
->GetSrclang(aLanguage
);
283 aLanguage
= mLanguage
;
287 void TextTrack::DispatchAsyncTrustedEvent(const nsString
& aEventName
) {
288 nsPIDOMWindowInner
* win
= GetOwner();
292 RefPtr
<TextTrack
> self
= this;
293 nsGlobalWindowInner::Cast(win
)->Dispatch(
295 NS_NewRunnableFunction(
296 "dom::TextTrack::DispatchAsyncTrustedEvent",
297 [self
, aEventName
]() { self
->DispatchTrustedEvent(aEventName
); }));
300 bool TextTrack::IsLoaded() {
301 if (mMode
== TextTrackMode::Disabled
) {
304 // If the TrackElement's src is null, we can not block the
308 if (!(mTrackElement
->GetAttr(kNameSpaceID_None
, nsGkAtoms::src
, src
))) {
312 return (mReadyState
>= Loaded
);
315 void TextTrack::NotifyCueActiveStateChanged(TextTrackCue
* aCue
) {
317 if (aCue
->GetActive()) {
318 MOZ_ASSERT(!mActiveCueList
->IsCueExist(aCue
));
319 WEBVTT_LOG("NotifyCueActiveStateChanged, add cue %p to the active list",
321 mActiveCueList
->AddCue(*aCue
);
323 MOZ_ASSERT(mActiveCueList
->IsCueExist(aCue
));
325 "NotifyCueActiveStateChanged, remove cue %p from the active list",
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();
340 if (Mode() == TextTrackMode::Disabled
) {
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
);
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
);
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
)) {
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(),
383 aOtherCues
->AddCue(*cue
);
388 HTMLMediaElement
* TextTrack::GetMediaElement() const {
389 return mTextTrackList
? mTextTrackList
->GetMediaElement() : nullptr;
393 } // namespace mozilla