TextTrack.cpp (12805B)
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/. */ 6 7 #include "mozilla/dom/TextTrack.h" 8 9 #include "mozilla/AsyncEventDispatcher.h" 10 #include "mozilla/BinarySearch.h" 11 #include "mozilla/dom/HTMLMediaElement.h" 12 #include "mozilla/dom/HTMLTrackElement.h" 13 #include "mozilla/dom/TextTrackBinding.h" 14 #include "mozilla/dom/TextTrackCue.h" 15 #include "mozilla/dom/TextTrackCueList.h" 16 #include "mozilla/dom/TextTrackList.h" 17 #include "mozilla/dom/TextTrackRegion.h" 18 #include "nsGlobalWindowInner.h" 19 20 extern mozilla::LazyLogModule gTextTrackLog; 21 22 #define WEBVTT_LOG(msg, ...) \ 23 MOZ_LOG(gTextTrackLog, LogLevel::Debug, \ 24 ("TextTrack=%p, " msg, this, ##__VA_ARGS__)) 25 #define WEBVTT_LOGV(msg, ...) \ 26 MOZ_LOG(gTextTrackLog, LogLevel::Verbose, \ 27 ("TextTrack=%p, " msg, this, ##__VA_ARGS__)) 28 29 namespace mozilla::dom { 30 31 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrack, DOMEventTargetHelper, mCueList, 32 mActiveCueList, mTextTrackList, 33 mTrackElement) 34 35 NS_IMPL_ADDREF_INHERITED(TextTrack, DOMEventTargetHelper) 36 NS_IMPL_RELEASE_INHERITED(TextTrack, DOMEventTargetHelper) 37 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrack) 38 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 39 40 TextTrack::TextTrack(nsPIDOMWindowInner* aOwnerWindow, TextTrackKind aKind, 41 const nsAString& aLabel, const nsAString& aLanguage, 42 TextTrackMode aMode, TextTrackReadyState aReadyState, 43 TextTrackSource aTextTrackSource) 44 : DOMEventTargetHelper(aOwnerWindow), 45 mKind(aKind), 46 mLabel(aLabel), 47 mLanguage(aLanguage), 48 mMode(aMode), 49 mReadyState(aReadyState), 50 mTextTrackSource(aTextTrackSource) { 51 SetDefaultSettings(); 52 } 53 54 TextTrack::TextTrack(nsPIDOMWindowInner* aOwnerWindow, 55 TextTrackList* aTextTrackList, TextTrackKind aKind, 56 const nsAString& aLabel, const nsAString& aLanguage, 57 TextTrackMode aMode, TextTrackReadyState aReadyState, 58 TextTrackSource aTextTrackSource) 59 : DOMEventTargetHelper(aOwnerWindow), 60 mTextTrackList(aTextTrackList), 61 mKind(aKind), 62 mLabel(aLabel), 63 mLanguage(aLanguage), 64 mMode(aMode), 65 mReadyState(aReadyState), 66 mTextTrackSource(aTextTrackSource) { 67 SetDefaultSettings(); 68 } 69 70 TextTrack::~TextTrack() = default; 71 72 void TextTrack::SetDefaultSettings() { 73 nsPIDOMWindowInner* ownerWindow = GetOwnerWindow(); 74 mCueList = new TextTrackCueList(ownerWindow); 75 mActiveCueList = new TextTrackCueList(ownerWindow); 76 mCuePos = 0; 77 mDirty = false; 78 } 79 80 JSObject* TextTrack::WrapObject(JSContext* aCx, 81 JS::Handle<JSObject*> aGivenProto) { 82 return TextTrack_Binding::Wrap(aCx, this, aGivenProto); 83 } 84 85 void TextTrack::SetMode(TextTrackMode aValue) { 86 if (mMode == aValue) { 87 return; 88 } 89 WEBVTT_LOG("Set mode=%s for track kind %s", GetEnumString(aValue).get(), 90 GetEnumString(mKind).get()); 91 mMode = aValue; 92 93 HTMLMediaElement* mediaElement = GetMediaElement(); 94 if (aValue == TextTrackMode::Disabled) { 95 for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) { 96 mediaElement->NotifyCueRemoved(*(*mCueList)[i]); 97 } 98 SetCuesInactive(); 99 } else { 100 for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) { 101 mediaElement->NotifyCueAdded(*(*mCueList)[i]); 102 } 103 } 104 if (mediaElement) { 105 mediaElement->NotifyTextTrackModeChanged(); 106 } 107 // https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks:start-the-track-processing-model 108 // Run the `start-the-track-processing-model` to track's corresponding track 109 // element whenever track's mode changes. 110 if (mTrackElement) { 111 mTrackElement->MaybeDispatchLoadResource(); 112 } 113 // Ensure the TimeMarchesOn is called in case that the mCueList 114 // is empty. 115 NotifyCueUpdated(nullptr); 116 } 117 118 void TextTrack::GetId(nsAString& aId) const { 119 // If the track has a track element then its id should be the same as the 120 // track element's id. 121 if (mTrackElement) { 122 mTrackElement->GetAttr(nsGkAtoms::id, aId); 123 } 124 } 125 126 void TextTrack::AddCue(TextTrackCue& aCue) { 127 WEBVTT_LOG("AddCue %p [%f:%f]", &aCue, aCue.StartTime(), aCue.EndTime()); 128 TextTrack* oldTextTrack = aCue.GetTrack(); 129 if (oldTextTrack) { 130 ErrorResult dummy; 131 oldTextTrack->RemoveCue(aCue, dummy); 132 } 133 mCueList->AddCue(aCue); 134 aCue.SetTrack(this); 135 HTMLMediaElement* mediaElement = GetMediaElement(); 136 if (mediaElement && (mMode != TextTrackMode::Disabled)) { 137 mediaElement->NotifyCueAdded(aCue); 138 } 139 } 140 141 void TextTrack::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) { 142 WEBVTT_LOG("RemoveCue %p", &aCue); 143 // Bug1304948, check the aCue belongs to the TextTrack. 144 mCueList->RemoveCue(aCue, aRv); 145 if (aRv.Failed()) { 146 return; 147 } 148 aCue.SetActive(false); 149 aCue.SetTrack(nullptr); 150 HTMLMediaElement* mediaElement = GetMediaElement(); 151 if (mediaElement) { 152 mediaElement->NotifyCueRemoved(aCue); 153 } 154 } 155 156 void TextTrack::ClearAllCues() { 157 WEBVTT_LOG("ClearAllCues"); 158 ErrorResult dummy; 159 while (!mCueList->IsEmpty()) { 160 RemoveCue(*(*mCueList)[0], dummy); 161 } 162 } 163 164 void TextTrack::SetCuesDirty() { 165 for (uint32_t i = 0; i < mCueList->Length(); i++) { 166 ((*mCueList)[i])->Reset(); 167 } 168 } 169 170 TextTrackCueList* TextTrack::GetActiveCues() { 171 if (mMode != TextTrackMode::Disabled) { 172 return mActiveCueList; 173 } 174 return nullptr; 175 } 176 177 void TextTrack::GetActiveCueArray(nsTArray<RefPtr<TextTrackCue>>& aCues) { 178 if (mMode != TextTrackMode::Disabled) { 179 mActiveCueList->GetArray(aCues); 180 } 181 } 182 183 TextTrackReadyState TextTrack::ReadyState() const { return mReadyState; } 184 185 void TextTrack::SetReadyState(TextTrackReadyState aState) { 186 WEBVTT_LOG("SetReadyState=%s", EnumValueToString(aState)); 187 mReadyState = aState; 188 HTMLMediaElement* mediaElement = GetMediaElement(); 189 if (mediaElement && (mReadyState == TextTrackReadyState::Loaded || 190 mReadyState == TextTrackReadyState::FailedToLoad)) { 191 mediaElement->RemoveTextTrack(this, true); 192 mediaElement->UpdateReadyState(); 193 } 194 } 195 196 TextTrackList* TextTrack::GetTextTrackList() { return mTextTrackList; } 197 198 void TextTrack::SetTextTrackList(TextTrackList* aTextTrackList) { 199 mTextTrackList = aTextTrackList; 200 } 201 202 HTMLTrackElement* TextTrack::GetTrackElement() { return mTrackElement; } 203 204 void TextTrack::SetTrackElement(HTMLTrackElement* aTrackElement) { 205 mTrackElement = aTrackElement; 206 } 207 208 void TextTrack::SetCuesInactive() { 209 WEBVTT_LOG("SetCuesInactive"); 210 mCueList->SetCuesInactive(); 211 } 212 213 void TextTrack::NotifyCueUpdated(TextTrackCue* aCue) { 214 WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue); 215 mCueList->NotifyCueUpdated(aCue); 216 HTMLMediaElement* mediaElement = GetMediaElement(); 217 if (mediaElement) { 218 mediaElement->NotifyCueUpdated(aCue); 219 } 220 } 221 222 void TextTrack::GetLabel(nsAString& aLabel) const { 223 if (mTrackElement) { 224 mTrackElement->GetLabel(aLabel); 225 } else { 226 aLabel = mLabel; 227 } 228 } 229 void TextTrack::GetLanguage(nsAString& aLanguage) const { 230 if (mTrackElement) { 231 mTrackElement->GetSrclang(aLanguage); 232 } else { 233 aLanguage = mLanguage; 234 } 235 } 236 237 void TextTrack::DispatchAsyncTrustedEvent(const nsString& aEventName) { 238 nsGlobalWindowInner* win = GetOwnerWindow(); 239 if (!win) { 240 return; 241 } 242 win->Dispatch( 243 NS_NewRunnableFunction("dom::TextTrack::DispatchAsyncTrustedEvent", 244 [self = RefPtr{this}, aEventName]() { 245 self->DispatchTrustedEvent(aEventName); 246 })); 247 } 248 249 bool TextTrack::IsLoaded() { 250 if (mMode == TextTrackMode::Disabled) { 251 return true; 252 } 253 // If the TrackElement's src is null, we can not block the 254 // MediaElement. 255 if (mTrackElement) { 256 nsAutoString src; 257 if (!(mTrackElement->GetAttr(nsGkAtoms::src, src))) { 258 return true; 259 } 260 } 261 return mReadyState >= TextTrackReadyState::Loaded; 262 } 263 264 void TextTrack::NotifyCueActiveStateChanged(TextTrackCue* aCue) { 265 MOZ_ASSERT(aCue); 266 if (aCue->GetActive()) { 267 MOZ_ASSERT(!mActiveCueList->IsCueExist(aCue)); 268 WEBVTT_LOG("NotifyCueActiveStateChanged, add cue %p to the active list", 269 aCue); 270 mActiveCueList->AddCue(*aCue); 271 } else { 272 MOZ_ASSERT(mActiveCueList->IsCueExist(aCue)); 273 WEBVTT_LOG( 274 "NotifyCueActiveStateChanged, remove cue %p from the active list", 275 aCue); 276 mActiveCueList->RemoveCue(*aCue); 277 } 278 } 279 280 void TextTrack::GetOverlappingCurrentOtherAndMissCues( 281 CueBuckets* aCurrentCues, CueBuckets* aOtherCues, CueBuckets* aMissCues, 282 const media::TimeInterval& aInterval, 283 const Maybe<double>& aLastTime) const { 284 const HTMLMediaElement* mediaElement = GetMediaElement(); 285 if (!mediaElement || Mode() == TextTrackMode::Disabled || 286 mCueList->IsEmpty()) { 287 return; 288 } 289 290 // According to `time marches on` step1, current cue list contains the cues 291 // whose start times are less than or equal to the current playback position 292 // and whose end times are greater than the current playback position. 293 // https://html.spec.whatwg.org/multipage/media.html#time-marches-on 294 MOZ_ASSERT(aCurrentCues && aOtherCues); 295 const double playbackTime = mediaElement->CurrentTime(); 296 const double intervalStart = aInterval.mStart.ToSeconds(); 297 const double intervalEnd = aInterval.mEnd.ToSeconds(); 298 299 if (intervalEnd < (*mCueList)[0]->StartTime()) { 300 WEBVTT_LOGV("Abort : interval ends before the first cue starts"); 301 return; 302 } 303 // Optimize the loop range by identifying the first cue that starts after the 304 // interval. 305 size_t lastIdx = 0; 306 struct LastIdxComparator { 307 const double mIntervalEnd; 308 explicit LastIdxComparator(double aIntervalEnd) 309 : mIntervalEnd(aIntervalEnd) {} 310 int operator()(const TextTrackCue* aCue) const { 311 return aCue->StartTime() > mIntervalEnd ? 0 : -1; 312 } 313 } compLast(intervalEnd); 314 if (!BinarySearchIf(mCueList->GetCuesArray(), 0, mCueList->Length(), compLast, 315 &lastIdx)) { 316 // Failed to find the match, set it to the last idx. 317 lastIdx = mCueList->Length() - 1; 318 } 319 320 // Search cues in the partial range. 321 for (size_t idx = 0; idx <= lastIdx; ++idx) { 322 TextTrackCue* cue = (*mCueList)[idx]; 323 double cueStart = cue->StartTime(); 324 double cueEnd = cue->EndTime(); 325 if (cueStart <= playbackTime && cueEnd > playbackTime) { 326 WEBVTT_LOG("Add cue %p [%f:%f] to current cue list", cue, cueStart, 327 cueEnd); 328 aCurrentCues->AddCue(cue); 329 } else { 330 // As the spec doesn't have a restriction for the negative duration, it 331 // does happen sometime if user sets it explicitly. It will be treated as 332 // a `missing cue` (a subset of the `other cues`) and it won't be 333 // displayed. 334 if (cueEnd < cueStart) { 335 // Add cue into `otherCue` only when its start time is contained by the 336 // current time interval. 337 if (intervalStart <= cueStart && cueStart < intervalEnd) { 338 WEBVTT_LOG( 339 "[Negative duration] Add cue %p [%f:%f] to other cues and " 340 "missing cues list", 341 cue, cueStart, cueEnd); 342 aOtherCues->AddCue(cue); 343 aMissCues->AddCue(cue); 344 } 345 continue; 346 } 347 // Cues are completely outside the time interval. 348 if (cueEnd < intervalStart || cueStart > intervalEnd) { 349 continue; 350 } 351 WEBVTT_LOG("Add cue %p [%f:%f] to other cue list", cue, cueStart, cueEnd); 352 aOtherCues->AddCue(cue); 353 if (aLastTime && cueStart >= *aLastTime && cueEnd <= playbackTime) { 354 WEBVTT_LOG("Add cue %p [%f:%f] to missing cues list", cue, cueStart, 355 cueEnd); 356 aMissCues->AddCue(cue); 357 } 358 } 359 } 360 } 361 362 HTMLMediaElement* TextTrack::GetMediaElement() const { 363 return mTextTrackList ? mTextTrackList->GetMediaElement() : nullptr; 364 } 365 366 void TextTrack::CueBuckets::AddCue(TextTrackCue* aCue) { 367 if (aCue->GetActive()) { 368 ActiveCues().AppendElement(aCue); 369 if (aCue->PauseOnExit()) { 370 mHasPauseOnExist[static_cast<uint8_t>(CueActivityState::Active)] = true; 371 } 372 } else { 373 InactiveCues().AppendElement(aCue); 374 if (aCue->PauseOnExit()) { 375 mHasPauseOnExist[static_cast<uint8_t>(CueActivityState::Inactive)] = true; 376 } 377 } 378 AllCues().AppendElement(aCue); 379 if (aCue->PauseOnExit()) { 380 mHasPauseOnExist[static_cast<uint8_t>(CueActivityState::All)] = true; 381 } 382 } 383 384 } // namespace mozilla::dom 385 386 #undef WEBVTT_LOG 387 #undef WEBVTT_LOGV