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/dom/TextTrack.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/dom/HTMLMediaElement.h"
11 #include "mozilla/dom/HTMLTrackElement.h"
12 #include "mozilla/dom/TextTrackBinding.h"
13 #include "mozilla/dom/TextTrackCue.h"
14 #include "mozilla/dom/TextTrackCueList.h"
15 #include "mozilla/dom/TextTrackList.h"
16 #include "mozilla/dom/TextTrackRegion.h"
17 #include "nsGlobalWindowInner.h"
19 extern mozilla::LazyLogModule gTextTrackLog
;
21 #define WEBVTT_LOG(msg, ...) \
22 MOZ_LOG(gTextTrackLog, LogLevel::Debug, \
23 ("TextTrack=%p, " msg, this, ##__VA_ARGS__))
25 namespace mozilla::dom
{
27 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrack
, DOMEventTargetHelper
, mCueList
,
28 mActiveCueList
, mTextTrackList
,
31 NS_IMPL_ADDREF_INHERITED(TextTrack
, DOMEventTargetHelper
)
32 NS_IMPL_RELEASE_INHERITED(TextTrack
, DOMEventTargetHelper
)
33 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrack
)
34 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
36 TextTrack::TextTrack(nsPIDOMWindowInner
* aOwnerWindow
, TextTrackKind aKind
,
37 const nsAString
& aLabel
, const nsAString
& aLanguage
,
38 TextTrackMode aMode
, TextTrackReadyState aReadyState
,
39 TextTrackSource aTextTrackSource
)
40 : DOMEventTargetHelper(aOwnerWindow
),
45 mReadyState(aReadyState
),
46 mTextTrackSource(aTextTrackSource
) {
50 TextTrack::TextTrack(nsPIDOMWindowInner
* aOwnerWindow
,
51 TextTrackList
* aTextTrackList
, TextTrackKind aKind
,
52 const nsAString
& aLabel
, const nsAString
& aLanguage
,
53 TextTrackMode aMode
, TextTrackReadyState aReadyState
,
54 TextTrackSource aTextTrackSource
)
55 : DOMEventTargetHelper(aOwnerWindow
),
56 mTextTrackList(aTextTrackList
),
61 mReadyState(aReadyState
),
62 mTextTrackSource(aTextTrackSource
) {
66 TextTrack::~TextTrack() = default;
68 void TextTrack::SetDefaultSettings() {
69 nsPIDOMWindowInner
* ownerWindow
= GetOwnerWindow();
70 mCueList
= new TextTrackCueList(ownerWindow
);
71 mActiveCueList
= new TextTrackCueList(ownerWindow
);
76 JSObject
* TextTrack::WrapObject(JSContext
* aCx
,
77 JS::Handle
<JSObject
*> aGivenProto
) {
78 return TextTrack_Binding::Wrap(aCx
, this, aGivenProto
);
81 void TextTrack::SetMode(TextTrackMode aValue
) {
82 if (mMode
== aValue
) {
85 WEBVTT_LOG("Set mode=%s for track kind %s", GetEnumString(aValue
).get(),
86 GetEnumString(mKind
).get());
89 HTMLMediaElement
* mediaElement
= GetMediaElement();
90 if (aValue
== TextTrackMode::Disabled
) {
91 for (size_t i
= 0; i
< mCueList
->Length() && mediaElement
; ++i
) {
92 mediaElement
->NotifyCueRemoved(*(*mCueList
)[i
]);
96 for (size_t i
= 0; i
< mCueList
->Length() && mediaElement
; ++i
) {
97 mediaElement
->NotifyCueAdded(*(*mCueList
)[i
]);
101 mediaElement
->NotifyTextTrackModeChanged();
103 // https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks:start-the-track-processing-model
104 // Run the `start-the-track-processing-model` to track's corresponding track
105 // element whenever track's mode changes.
107 mTrackElement
->MaybeDispatchLoadResource();
109 // Ensure the TimeMarchesOn is called in case that the mCueList
111 NotifyCueUpdated(nullptr);
114 void TextTrack::GetId(nsAString
& aId
) const {
115 // If the track has a track element then its id should be the same as the
116 // track element's id.
118 mTrackElement
->GetAttr(nsGkAtoms::id
, aId
);
122 void TextTrack::AddCue(TextTrackCue
& aCue
) {
123 WEBVTT_LOG("AddCue %p [%f:%f]", &aCue
, aCue
.StartTime(), aCue
.EndTime());
124 TextTrack
* oldTextTrack
= aCue
.GetTrack();
127 oldTextTrack
->RemoveCue(aCue
, dummy
);
129 mCueList
->AddCue(aCue
);
131 HTMLMediaElement
* mediaElement
= GetMediaElement();
132 if (mediaElement
&& (mMode
!= TextTrackMode::Disabled
)) {
133 mediaElement
->NotifyCueAdded(aCue
);
137 void TextTrack::RemoveCue(TextTrackCue
& aCue
, ErrorResult
& aRv
) {
138 WEBVTT_LOG("RemoveCue %p", &aCue
);
139 // Bug1304948, check the aCue belongs to the TextTrack.
140 mCueList
->RemoveCue(aCue
, aRv
);
144 aCue
.SetActive(false);
145 aCue
.SetTrack(nullptr);
146 HTMLMediaElement
* mediaElement
= GetMediaElement();
148 mediaElement
->NotifyCueRemoved(aCue
);
152 void TextTrack::ClearAllCues() {
153 WEBVTT_LOG("ClearAllCues");
155 while (!mCueList
->IsEmpty()) {
156 RemoveCue(*(*mCueList
)[0], dummy
);
160 void TextTrack::SetCuesDirty() {
161 for (uint32_t i
= 0; i
< mCueList
->Length(); i
++) {
162 ((*mCueList
)[i
])->Reset();
166 TextTrackCueList
* TextTrack::GetActiveCues() {
167 if (mMode
!= TextTrackMode::Disabled
) {
168 return mActiveCueList
;
173 void TextTrack::GetActiveCueArray(nsTArray
<RefPtr
<TextTrackCue
> >& aCues
) {
174 if (mMode
!= TextTrackMode::Disabled
) {
175 mActiveCueList
->GetArray(aCues
);
179 TextTrackReadyState
TextTrack::ReadyState() const { return mReadyState
; }
181 void TextTrack::SetReadyState(TextTrackReadyState aState
) {
182 WEBVTT_LOG("SetReadyState=%s", EnumValueToString(aState
));
183 mReadyState
= aState
;
184 HTMLMediaElement
* mediaElement
= GetMediaElement();
185 if (mediaElement
&& (mReadyState
== TextTrackReadyState::Loaded
||
186 mReadyState
== TextTrackReadyState::FailedToLoad
)) {
187 mediaElement
->RemoveTextTrack(this, true);
188 mediaElement
->UpdateReadyState();
192 TextTrackList
* TextTrack::GetTextTrackList() { return mTextTrackList
; }
194 void TextTrack::SetTextTrackList(TextTrackList
* aTextTrackList
) {
195 mTextTrackList
= aTextTrackList
;
198 HTMLTrackElement
* TextTrack::GetTrackElement() { return mTrackElement
; }
200 void TextTrack::SetTrackElement(HTMLTrackElement
* aTrackElement
) {
201 mTrackElement
= aTrackElement
;
204 void TextTrack::SetCuesInactive() {
205 WEBVTT_LOG("SetCuesInactive");
206 mCueList
->SetCuesInactive();
209 void TextTrack::NotifyCueUpdated(TextTrackCue
* aCue
) {
210 WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue
);
211 mCueList
->NotifyCueUpdated(aCue
);
212 HTMLMediaElement
* mediaElement
= GetMediaElement();
214 mediaElement
->NotifyCueUpdated(aCue
);
218 void TextTrack::GetLabel(nsAString
& aLabel
) const {
220 mTrackElement
->GetLabel(aLabel
);
225 void TextTrack::GetLanguage(nsAString
& aLanguage
) const {
227 mTrackElement
->GetSrclang(aLanguage
);
229 aLanguage
= mLanguage
;
233 void TextTrack::DispatchAsyncTrustedEvent(const nsString
& aEventName
) {
234 nsGlobalWindowInner
* win
= GetOwnerWindow();
239 NS_NewRunnableFunction("dom::TextTrack::DispatchAsyncTrustedEvent",
240 [self
= RefPtr
{this}, aEventName
]() {
241 self
->DispatchTrustedEvent(aEventName
);
245 bool TextTrack::IsLoaded() {
246 if (mMode
== TextTrackMode::Disabled
) {
249 // If the TrackElement's src is null, we can not block the
253 if (!(mTrackElement
->GetAttr(nsGkAtoms::src
, src
))) {
257 return mReadyState
>= TextTrackReadyState::Loaded
;
260 void TextTrack::NotifyCueActiveStateChanged(TextTrackCue
* aCue
) {
262 if (aCue
->GetActive()) {
263 MOZ_ASSERT(!mActiveCueList
->IsCueExist(aCue
));
264 WEBVTT_LOG("NotifyCueActiveStateChanged, add cue %p to the active list",
266 mActiveCueList
->AddCue(*aCue
);
268 MOZ_ASSERT(mActiveCueList
->IsCueExist(aCue
));
270 "NotifyCueActiveStateChanged, remove cue %p from the active list",
272 mActiveCueList
->RemoveCue(*aCue
);
276 void TextTrack::GetCurrentCuesAndOtherCues(
277 RefPtr
<TextTrackCueList
>& aCurrentCues
,
278 RefPtr
<TextTrackCueList
>& aOtherCues
,
279 const media::TimeInterval
& aInterval
) const {
280 const HTMLMediaElement
* mediaElement
= GetMediaElement();
285 if (Mode() == TextTrackMode::Disabled
) {
289 // According to `time marches on` step1, current cue list contains the cues
290 // whose start times are less than or equal to the current playback position
291 // and whose end times are greater than the current playback position.
292 // https://html.spec.whatwg.org/multipage/media.html#time-marches-on
293 MOZ_ASSERT(aCurrentCues
&& aOtherCues
);
294 const double playbackTime
= mediaElement
->CurrentTime();
295 for (uint32_t idx
= 0; idx
< mCueList
->Length(); idx
++) {
296 TextTrackCue
* cue
= (*mCueList
)[idx
];
297 WEBVTT_LOG("cue %p [%f:%f], playbackTime=%f", cue
, cue
->StartTime(),
298 cue
->EndTime(), playbackTime
);
299 if (cue
->StartTime() <= playbackTime
&& cue
->EndTime() > playbackTime
) {
300 WEBVTT_LOG("Add cue %p [%f:%f] to current cue list", cue
,
301 cue
->StartTime(), cue
->EndTime());
302 aCurrentCues
->AddCue(*cue
);
304 // As the spec didn't have a restriction for the negative duration, it
305 // does happen sometime if user sets it explicitly. It would be treated as
306 // a `missing cue` later in the `TimeMarchesOn` but it won't be displayed.
307 if (cue
->EndTime() < cue
->StartTime()) {
308 // Add cue into `otherCue` only when its start time is contained by the
309 // current time interval.
310 if (aInterval
.Contains(
311 media::TimeUnit::FromSeconds(cue
->StartTime()))) {
312 WEBVTT_LOG("[Negative duration] Add cue %p [%f:%f] to other cue list",
313 cue
, cue
->StartTime(), cue
->EndTime());
314 aOtherCues
->AddCue(*cue
);
318 media::TimeInterval
cueInterval(
319 media::TimeUnit::FromSeconds(cue
->StartTime()),
320 media::TimeUnit::FromSeconds(cue
->EndTime()));
321 // cues are completely outside the time interval.
322 if (!aInterval
.Touches(cueInterval
)) {
325 // contains any cues which are overlapping within the time interval.
326 WEBVTT_LOG("Add cue %p [%f:%f] to other cue list", cue
, cue
->StartTime(),
328 aOtherCues
->AddCue(*cue
);
333 HTMLMediaElement
* TextTrack::GetMediaElement() const {
334 return mTextTrackList
? mTextTrackList
->GetMediaElement() : nullptr;
337 } // namespace mozilla::dom