MediaSource.cpp (26405B)
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 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "MediaSource.h" 8 9 #include "AsyncEventRunner.h" 10 #include "DecoderDoctorDiagnostics.h" 11 #include "DecoderTraits.h" 12 #include "MP4Decoder.h" 13 #include "MediaContainerType.h" 14 #include "MediaResult.h" 15 #include "MediaSourceDemuxer.h" 16 #include "SourceBuffer.h" 17 #include "SourceBufferList.h" 18 #include "mozilla/ErrorResult.h" 19 #include "mozilla/FloatingPoint.h" 20 #include "mozilla/Logging.h" 21 #include "mozilla/StaticPrefs_media.h" 22 #include "mozilla/dom/BindingDeclarations.h" 23 #include "mozilla/dom/Document.h" 24 #include "mozilla/dom/HTMLMediaElement.h" 25 #include "mozilla/gfx/gfxVars.h" 26 #include "mozilla/glean/DomMediaMetrics.h" 27 #include "mozilla/mozalloc.h" 28 #include "nsDebug.h" 29 #include "nsError.h" 30 #include "nsGlobalWindowInner.h" 31 #include "nsIRunnable.h" 32 #include "nsIScriptObjectPrincipal.h" 33 #include "nsMimeTypes.h" 34 #include "nsPIDOMWindow.h" 35 #include "nsString.h" 36 #include "nsThreadUtils.h" 37 38 struct JSContext; 39 class JSObject; 40 41 mozilla::LogModule* GetMediaSourceLog() { 42 static mozilla::LazyLogModule sLogModule("MediaSource"); 43 return sLogModule; 44 } 45 46 mozilla::LogModule* GetMediaSourceAPILog() { 47 static mozilla::LazyLogModule sLogModule("MediaSource"); 48 return sLogModule; 49 } 50 51 #define MSE_DEBUG(arg, ...) \ 52 DDMOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, "::%s: " arg, \ 53 __func__, ##__VA_ARGS__) 54 #define MSE_API(arg, ...) \ 55 DDMOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, "::%s: " arg, \ 56 __func__, ##__VA_ARGS__) 57 58 // Arbitrary limit. 59 static const unsigned int MAX_SOURCE_BUFFERS = 16; 60 61 namespace mozilla { 62 63 // Returns true if we should enable MSE webm regardless of preferences. 64 // 1. If MP4/H264 isn't supported: 65 // * N/KN editions (Europe and Korea) of Windows 10/11 without the optional 66 // "Windows Media Feature Pack" 67 // * Some Linux Desktop. 68 // 2. If H264 hardware acceleration is not available. 69 static bool IsVP9Forced(DecoderDoctorDiagnostics* aDiagnostics) { 70 bool mp4supported = MP4Decoder::IsSupportedType( 71 MediaContainerType(MEDIAMIMETYPE(VIDEO_MP4)), aDiagnostics); 72 bool hwsupported = gfx::gfxVars::CanUseHardwareVideoDecoding(); 73 #ifdef MOZ_WIDGET_ANDROID 74 return !mp4supported || !hwsupported || gfx::gfxVars::UseVP9HwDecode(); 75 #else 76 return !mp4supported || !hwsupported; 77 #endif 78 } 79 80 namespace dom { 81 82 static void RecordTypeForTelemetry(const nsAString& aType, 83 nsPIDOMWindowInner* aWindow) { 84 Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType); 85 if (!containerType) { 86 return; 87 } 88 89 const MediaMIMEType& mimeType = containerType->Type(); 90 if (mimeType == MEDIAMIMETYPE(VIDEO_WEBM)) { 91 mozilla::glean::media::mse_source_buffer_type 92 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eVideowebm) 93 .Add(); 94 } else if (mimeType == MEDIAMIMETYPE(AUDIO_WEBM)) { 95 mozilla::glean::media::mse_source_buffer_type 96 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eAudiowebm) 97 .Add(); 98 } else if (mimeType == MEDIAMIMETYPE(VIDEO_MP4)) { 99 mozilla::glean::media::mse_source_buffer_type 100 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eVideomp4) 101 .Add(); 102 const auto& codecString = containerType->ExtendedType().Codecs().AsString(); 103 if (StringBeginsWith(codecString, u"hev1"_ns) || 104 StringBeginsWith(codecString, u"hvc1"_ns)) { 105 mozilla::glean::media::mse_source_buffer_type 106 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eVideohevc) 107 .Add(); 108 } 109 } else if (mimeType == MEDIAMIMETYPE(AUDIO_MP4)) { 110 mozilla::glean::media::mse_source_buffer_type 111 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eAudiomp4) 112 .Add(); 113 } else if (mimeType == MEDIAMIMETYPE(VIDEO_MPEG_TS)) { 114 mozilla::glean::media::mse_source_buffer_type 115 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eVideomp2t) 116 .Add(); 117 } else if (mimeType == MEDIAMIMETYPE(AUDIO_MPEG_TS)) { 118 mozilla::glean::media::mse_source_buffer_type 119 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eAudiomp2t) 120 .Add(); 121 } else if (mimeType == MEDIAMIMETYPE(AUDIO_MP3)) { 122 mozilla::glean::media::mse_source_buffer_type 123 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eAudiompeg) 124 .Add(); 125 } else if (mimeType == MEDIAMIMETYPE(AUDIO_AAC)) { 126 mozilla::glean::media::mse_source_buffer_type 127 .EnumGet(mozilla::glean::media::MseSourceBufferTypeLabel::eAudioaac) 128 .Add(); 129 } 130 } 131 132 /* static */ 133 void MediaSource::IsTypeSupported(const nsAString& aType, 134 DecoderDoctorDiagnostics* aDiagnostics, 135 ErrorResult& aRv, 136 Maybe<bool> aShouldResistFingerprinting) { 137 if (aType.IsEmpty()) { 138 return aRv.ThrowTypeError("Empty type"); 139 } 140 141 Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType); 142 if (!containerType) { 143 return aRv.ThrowNotSupportedError("Unknown type"); 144 } 145 146 if (DecoderTraits::CanHandleContainerType(*containerType, aDiagnostics) == 147 CANPLAY_NO) { 148 return aRv.ThrowNotSupportedError("Can't play type"); 149 } 150 151 bool hasVP9 = false; 152 const MediaCodecs& codecs = containerType->ExtendedType().Codecs(); 153 for (const auto& codec : codecs.Range()) { 154 if (IsVP9CodecString(codec)) { 155 hasVP9 = true; 156 break; 157 } 158 } 159 160 // Now we know that this media type could be played. 161 // MediaSource imposes extra restrictions, and some prefs. 162 // Avoid leaking information about the fact that it's pref-disabled, 163 // or that HW acceleration is available (only applicable to VP9 on Android). 164 bool shouldResistFingerprinting = 165 aShouldResistFingerprinting.isSome() 166 ? aShouldResistFingerprinting.value() 167 : nsContentUtils::ShouldResistFingerprinting( 168 "Couldn't drill down ShouldResistFingerprinting", 169 RFPTarget::MediaCapabilities); 170 const MediaMIMEType& mimeType = containerType->Type(); 171 if (mimeType == MEDIAMIMETYPE("video/mp4") || 172 mimeType == MEDIAMIMETYPE("audio/mp4")) { 173 if (!StaticPrefs::media_mediasource_vp9_enabled() && hasVP9 && 174 !IsVP9Forced(aDiagnostics) && !shouldResistFingerprinting) { 175 // Don't leak information about the fact that it's pref-disabled; just act 176 // like we can't play it. Or should this throw "Unknown type"? 177 return aRv.ThrowNotSupportedError("Can't play type"); 178 } 179 180 return; 181 } 182 if (mimeType == MEDIAMIMETYPE("video/webm")) { 183 if (!StaticPrefs::media_mediasource_webm_enabled() && 184 !shouldResistFingerprinting) { 185 // Don't leak information about the fact that it's pref-disabled; just act 186 // like we can't play it. Or should this throw "Unknown type"? 187 return aRv.ThrowNotSupportedError("Can't play type"); 188 } 189 if (!StaticPrefs::media_mediasource_vp9_enabled() && hasVP9 && 190 !IsVP9Forced(aDiagnostics) && !shouldResistFingerprinting) { 191 // Don't leak information about the fact that it's pref-disabled; just act 192 // like we can't play it. Or should this throw "Unknown type"? 193 return aRv.ThrowNotSupportedError("Can't play type"); 194 } 195 return; 196 } 197 if (mimeType == MEDIAMIMETYPE("audio/webm")) { 198 if (!StaticPrefs::media_mediasource_webm_enabled() && 199 !shouldResistFingerprinting) { 200 // Don't leak information about the fact that it's pref-disabled; just act 201 // like we can't play it. Or should this throw "Unknown type"? 202 return aRv.ThrowNotSupportedError("Can't play type"); 203 } 204 return; 205 } 206 207 return aRv.ThrowNotSupportedError("Type not supported in MediaSource"); 208 } 209 210 /* static */ 211 already_AddRefed<MediaSource> MediaSource::Constructor( 212 const GlobalObject& aGlobal, ErrorResult& aRv) { 213 nsCOMPtr<nsPIDOMWindowInner> window = 214 do_QueryInterface(aGlobal.GetAsSupports()); 215 if (!window) { 216 aRv.Throw(NS_ERROR_UNEXPECTED); 217 return nullptr; 218 } 219 220 RefPtr<MediaSource> mediaSource = new MediaSource(window); 221 return mediaSource.forget(); 222 } 223 224 MediaSource::~MediaSource() { 225 MOZ_ASSERT(NS_IsMainThread()); 226 MSE_API(""); 227 if (mDecoder) { 228 mDecoder->DetachMediaSource(); 229 } 230 } 231 232 SourceBufferList* MediaSource::SourceBuffers() { 233 MOZ_ASSERT(NS_IsMainThread()); 234 MOZ_ASSERT_IF(mReadyState == MediaSourceReadyState::Closed, 235 mSourceBuffers->IsEmpty()); 236 return mSourceBuffers; 237 } 238 239 SourceBufferList* MediaSource::ActiveSourceBuffers() { 240 MOZ_ASSERT(NS_IsMainThread()); 241 MOZ_ASSERT_IF(mReadyState == MediaSourceReadyState::Closed, 242 mActiveSourceBuffers->IsEmpty()); 243 return mActiveSourceBuffers; 244 } 245 246 MediaSourceReadyState MediaSource::ReadyState() { 247 MOZ_ASSERT(NS_IsMainThread()); 248 return mReadyState; 249 } 250 251 double MediaSource::Duration() { 252 MOZ_ASSERT(NS_IsMainThread()); 253 if (mReadyState == MediaSourceReadyState::Closed) { 254 return UnspecifiedNaN<double>(); 255 } 256 MOZ_ASSERT(mDecoder); 257 return mDecoder->GetDuration(); 258 } 259 260 void MediaSource::SetDuration(double aDuration, ErrorResult& aRv) { 261 MOZ_ASSERT(NS_IsMainThread()); 262 if (aDuration < 0 || std::isnan(aDuration)) { 263 nsPrintfCString error("Invalid duration value %f", aDuration); 264 MSE_API("SetDuration(aDuration=%f, invalid value)", aDuration); 265 aRv.ThrowTypeError(error); 266 return; 267 } 268 if (mReadyState != MediaSourceReadyState::Open || 269 mSourceBuffers->AnyUpdating()) { 270 MSE_API("SetDuration(aDuration=%f, invalid state)", aDuration); 271 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 272 return; 273 } 274 DurationChange(aDuration, aRv); 275 MSE_API("SetDuration(aDuration=%f, errorCode=%d)", aDuration, 276 aRv.ErrorCodeAsInt()); 277 } 278 279 void MediaSource::SetDuration(const media::TimeUnit& aDuration) { 280 MOZ_ASSERT(NS_IsMainThread()); 281 MSE_API("SetDuration(aDuration=%f)", aDuration.ToSeconds()); 282 mDecoder->SetMediaSourceDuration(aDuration); 283 } 284 285 already_AddRefed<SourceBuffer> MediaSource::AddSourceBuffer( 286 const nsAString& aType, ErrorResult& aRv) { 287 MOZ_ASSERT(NS_IsMainThread()); 288 nsCOMPtr<nsPIDOMWindowInner> window = GetOwnerWindow(); 289 Document* doc = window ? window->GetExtantDoc() : nullptr; 290 DecoderDoctorDiagnostics diagnostics; 291 IsTypeSupported( 292 aType, &diagnostics, aRv, 293 doc ? Some(doc->ShouldResistFingerprinting(RFPTarget::MediaCapabilities)) 294 : Nothing()); 295 RecordTypeForTelemetry(aType, window); 296 bool supported = !aRv.Failed(); 297 diagnostics.StoreFormatDiagnostics(doc, aType, supported, __func__); 298 MSE_API("AddSourceBuffer(aType=%s)%s", NS_ConvertUTF16toUTF8(aType).get(), 299 supported ? "" : " [not supported]"); 300 if (!supported) { 301 return nullptr; 302 } 303 if (mSourceBuffers->Length() >= MAX_SOURCE_BUFFERS) { 304 aRv.Throw(NS_ERROR_DOM_MEDIA_SOURCE_MAX_BUFFER_QUOTA_EXCEEDED_ERR); 305 return nullptr; 306 } 307 if (mReadyState != MediaSourceReadyState::Open) { 308 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 309 return nullptr; 310 } 311 Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType); 312 if (!containerType) { 313 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 314 return nullptr; 315 } 316 RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(this, *containerType); 317 mSourceBuffers->Append(sourceBuffer); 318 DDLINKCHILD("sourcebuffer[]", sourceBuffer.get()); 319 MSE_DEBUG("sourceBuffer=%p", sourceBuffer.get()); 320 return sourceBuffer.forget(); 321 } 322 323 RefPtr<MediaSource::ActiveCompletionPromise> MediaSource::SourceBufferIsActive( 324 SourceBuffer* aSourceBuffer) { 325 MOZ_ASSERT(NS_IsMainThread()); 326 mActiveSourceBuffers->ClearSimple(); 327 bool initMissing = false; 328 bool found = false; 329 for (uint32_t i = 0; i < mSourceBuffers->Length(); i++) { 330 SourceBuffer* sourceBuffer = mSourceBuffers->IndexedGetter(i, found); 331 MOZ_ALWAYS_TRUE(found); 332 if (sourceBuffer == aSourceBuffer) { 333 mActiveSourceBuffers->Append(aSourceBuffer); 334 } else if (sourceBuffer->IsActive()) { 335 mActiveSourceBuffers->AppendSimple(sourceBuffer); 336 } else { 337 // Some source buffers haven't yet received an init segment. 338 // There's nothing more we can do at this stage. 339 initMissing = true; 340 } 341 } 342 if (initMissing || !mDecoder) { 343 return ActiveCompletionPromise::CreateAndResolve(true, __func__); 344 } 345 346 mDecoder->NotifyInitDataArrived(); 347 348 // Add our promise to the queue. 349 // It will be resolved once the HTMLMediaElement modifies its readyState. 350 MozPromiseHolder<ActiveCompletionPromise> holder; 351 RefPtr<ActiveCompletionPromise> promise = holder.Ensure(__func__); 352 mCompletionPromises.AppendElement(std::move(holder)); 353 return promise; 354 } 355 356 void MediaSource::CompletePendingTransactions() { 357 MOZ_ASSERT(NS_IsMainThread()); 358 MSE_DEBUG("Resolving %u promises", unsigned(mCompletionPromises.Length())); 359 for (auto& promise : mCompletionPromises) { 360 promise.Resolve(true, __func__); 361 } 362 mCompletionPromises.Clear(); 363 } 364 365 void MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, 366 ErrorResult& aRv) { 367 MOZ_ASSERT(NS_IsMainThread()); 368 SourceBuffer* sourceBuffer = &aSourceBuffer; 369 MSE_API("RemoveSourceBuffer(aSourceBuffer=%p)", sourceBuffer); 370 if (!mSourceBuffers->Contains(sourceBuffer)) { 371 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); 372 return; 373 } 374 375 sourceBuffer->AbortBufferAppend(); 376 // TODO: 377 // abort stream append loop (if running) 378 379 // TODO: 380 // For all sourceBuffer audioTracks, videoTracks, textTracks: 381 // set sourceBuffer to null 382 // remove sourceBuffer video, audio, text Tracks from MediaElement tracks 383 // remove sourceBuffer video, audio, text Tracks and fire "removetrack" at 384 // affected lists fire "removetrack" at modified MediaElement track lists 385 // If removed enabled/selected, fire "change" at affected MediaElement list. 386 if (mActiveSourceBuffers->Contains(sourceBuffer)) { 387 mActiveSourceBuffers->Remove(sourceBuffer); 388 } 389 mSourceBuffers->Remove(sourceBuffer); 390 DDUNLINKCHILD(sourceBuffer); 391 // TODO: Free all resources associated with sourceBuffer 392 } 393 394 void MediaSource::EndOfStream( 395 const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv) { 396 MOZ_ASSERT(NS_IsMainThread()); 397 MSE_API("EndOfStream(aError=%d)", 398 aError.WasPassed() ? uint32_t(aError.Value()) : 0); 399 if (mReadyState != MediaSourceReadyState::Open || 400 mSourceBuffers->AnyUpdating()) { 401 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 402 return; 403 } 404 405 SetReadyState(MediaSourceReadyState::Ended); 406 mSourceBuffers->SetEnded(aError); 407 if (!aError.WasPassed()) { 408 DurationChangeOnEndOfStream(); 409 // Notify reader that all data is now available. 410 mDecoder->Ended(true); 411 return; 412 } 413 switch (aError.Value()) { 414 case MediaSourceEndOfStreamError::Network: 415 mDecoder->NetworkError(MediaResult(NS_ERROR_FAILURE, "MSE network")); 416 break; 417 case MediaSourceEndOfStreamError::Decode: 418 mDecoder->DecodeError(NS_ERROR_DOM_MEDIA_FATAL_ERR); 419 break; 420 default: 421 MOZ_ASSERT_UNREACHABLE( 422 "Someone added a MediaSourceReadyState value and didn't handle it " 423 "here"); 424 break; 425 } 426 } 427 428 void MediaSource::EndOfStream(const MediaResult& aError) { 429 MOZ_ASSERT(NS_IsMainThread()); 430 MSE_API("EndOfStream(aError=%s)", aError.ErrorName().get()); 431 432 SetReadyState(MediaSourceReadyState::Ended); 433 mSourceBuffers->SetEnded(Optional(MediaSourceEndOfStreamError::Decode)); 434 mDecoder->DecodeError(aError); 435 } 436 437 /* static */ 438 bool MediaSource::IsTypeSupported(const GlobalObject& aOwner, 439 const nsAString& aType) { 440 MOZ_ASSERT(NS_IsMainThread()); 441 DecoderDoctorDiagnostics diagnostics; 442 IgnoredErrorResult rv; 443 nsCOMPtr<nsPIDOMWindowInner> window = 444 do_QueryInterface(aOwner.GetAsSupports()); 445 Document* doc = window ? window->GetExtantDoc() : nullptr; 446 IsTypeSupported( 447 aType, &diagnostics, rv, 448 doc ? Some(doc->ShouldResistFingerprinting(RFPTarget::MediaCapabilities)) 449 : Nothing()); 450 bool supported = !rv.Failed(); 451 RecordTypeForTelemetry(aType, window); 452 diagnostics.StoreFormatDiagnostics(doc, aType, supported, __func__); 453 MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, 454 ("MediaSource::%s: IsTypeSupported(aType=%s) %s", __func__, 455 NS_ConvertUTF16toUTF8(aType).get(), 456 supported ? "OK" : "[not supported]")); 457 return supported; 458 } 459 460 void MediaSource::SetLiveSeekableRange(double aStart, double aEnd, 461 ErrorResult& aRv) { 462 MOZ_ASSERT(NS_IsMainThread()); 463 464 // 1. If the readyState attribute is not "open" then throw an 465 // InvalidStateError exception and abort these steps. 466 if (mReadyState != MediaSourceReadyState::Open) { 467 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 468 return; 469 } 470 471 // 2. If start is negative or greater than end, then throw a TypeError 472 // exception and abort these steps. 473 if (aStart < 0 || aStart > aEnd) { 474 aRv.ThrowTypeError("Invalid start value"); 475 return; 476 } 477 478 // 3. Set live seekable range to be a new normalized TimeRanges object 479 // containing a single range whose start position is start and end position is 480 // end. 481 mLiveSeekableRange = Some(media::TimeRanges(media::TimeRange(aStart, aEnd))); 482 } 483 484 void MediaSource::ClearLiveSeekableRange(ErrorResult& aRv) { 485 MOZ_ASSERT(NS_IsMainThread()); 486 487 // 1. If the readyState attribute is not "open" then throw an 488 // InvalidStateError exception and abort these steps. 489 if (mReadyState != MediaSourceReadyState::Open) { 490 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 491 return; 492 } 493 494 // 2. If live seekable range contains a range, then set live seekable range to 495 // be a new empty TimeRanges object. 496 mLiveSeekableRange.reset(); 497 } 498 499 bool MediaSource::Attach(MediaSourceDecoder* aDecoder) { 500 MOZ_ASSERT(NS_IsMainThread()); 501 MSE_DEBUG("Attach(aDecoder=%p) owner=%p", aDecoder, aDecoder->GetOwner()); 502 MOZ_ASSERT(aDecoder); 503 MOZ_ASSERT(aDecoder->GetOwner()); 504 if (mReadyState != MediaSourceReadyState::Closed) { 505 return false; 506 } 507 MOZ_ASSERT(!mMediaElement); 508 mMediaElement = aDecoder->GetOwner()->GetMediaElement(); 509 MOZ_ASSERT(!mDecoder); 510 mDecoder = aDecoder; 511 mDecoder->AttachMediaSource(this); 512 SetReadyState(MediaSourceReadyState::Open); 513 return true; 514 } 515 516 void MediaSource::Detach() { 517 MOZ_ASSERT(NS_IsMainThread()); 518 MOZ_RELEASE_ASSERT(mCompletionPromises.IsEmpty()); 519 MSE_DEBUG("mDecoder=%p owner=%p", mDecoder.get(), 520 mDecoder ? mDecoder->GetOwner() : nullptr); 521 if (!mDecoder) { 522 MOZ_ASSERT(mReadyState == MediaSourceReadyState::Closed); 523 MOZ_ASSERT(mActiveSourceBuffers->IsEmpty() && mSourceBuffers->IsEmpty()); 524 return; 525 } 526 mMediaElement = nullptr; 527 SetReadyState(MediaSourceReadyState::Closed); 528 if (mActiveSourceBuffers) { 529 mActiveSourceBuffers->Clear(); 530 } 531 if (mSourceBuffers) { 532 mSourceBuffers->Clear(); 533 } 534 mDecoder->DetachMediaSource(); 535 mDecoder = nullptr; 536 } 537 538 MediaSource::MediaSource(nsPIDOMWindowInner* aWindow) 539 : DOMEventTargetHelper(aWindow), 540 mDecoder(nullptr), 541 mPrincipal(nullptr), 542 mAbstractMainThread(AbstractThread::MainThread()), 543 mReadyState(MediaSourceReadyState::Closed) { 544 MOZ_ASSERT(NS_IsMainThread()); 545 mSourceBuffers = new SourceBufferList(this); 546 mActiveSourceBuffers = new SourceBufferList(this); 547 548 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow); 549 if (sop) { 550 mPrincipal = sop->GetPrincipal(); 551 } 552 553 MSE_API("MediaSource(aWindow=%p) mSourceBuffers=%p mActiveSourceBuffers=%p", 554 aWindow, mSourceBuffers.get(), mActiveSourceBuffers.get()); 555 } 556 557 void MediaSource::SetReadyState(MediaSourceReadyState aState) { 558 MOZ_ASSERT(NS_IsMainThread()); 559 MOZ_ASSERT(aState != mReadyState); 560 MSE_DEBUG("SetReadyState(aState=%" PRIu32 ") mReadyState=%" PRIu32, 561 static_cast<uint32_t>(aState), static_cast<uint32_t>(mReadyState)); 562 563 MediaSourceReadyState oldState = mReadyState; 564 mReadyState = aState; 565 566 if (mReadyState == MediaSourceReadyState::Open && 567 (oldState == MediaSourceReadyState::Closed || 568 oldState == MediaSourceReadyState::Ended)) { 569 QueueAsyncSimpleEvent("sourceopen"); 570 if (oldState == MediaSourceReadyState::Ended) { 571 // Notify reader that more data may come. 572 mDecoder->Ended(false); 573 } 574 return; 575 } 576 577 if (mReadyState == MediaSourceReadyState::Ended && 578 oldState == MediaSourceReadyState::Open) { 579 QueueAsyncSimpleEvent("sourceended"); 580 return; 581 } 582 583 if (mReadyState == MediaSourceReadyState::Closed && 584 (oldState == MediaSourceReadyState::Open || 585 oldState == MediaSourceReadyState::Ended)) { 586 QueueAsyncSimpleEvent("sourceclose"); 587 return; 588 } 589 590 NS_WARNING("Invalid MediaSource readyState transition"); 591 } 592 593 void MediaSource::DispatchSimpleEvent(const char* aName) { 594 MOZ_ASSERT(NS_IsMainThread()); 595 MSE_API("Dispatch event '%s'", aName); 596 DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName)); 597 } 598 599 void MediaSource::QueueAsyncSimpleEvent(const char* aName) { 600 MSE_DEBUG("Queuing event '%s'", aName); 601 nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<MediaSource>(this, aName); 602 mAbstractMainThread->Dispatch(event.forget()); 603 } 604 605 void MediaSource::DurationChangeOnEndOfStream() { 606 MOZ_ASSERT(NS_IsMainThread()); 607 MOZ_ASSERT(mReadyState == MediaSourceReadyState::Ended); 608 // https://w3c.github.io/media-source/#dfn-end-of-stream 609 // 3.1 Run the duration change algorithm with new duration set to the 610 // largest track buffer ranges end time across all the track buffers across 611 // all SourceBuffer objects in sourceBuffers. 612 613 // https://w3c.github.io/media-source/#duration-change-algorithm 614 // 1. If the current value of duration is equal to new duration, then return. 615 // 2. If new duration is less than the highest starting presentation timestamp 616 // of any buffered coded frames for all SourceBuffer objects in sourceBuffers, 617 // then throw an InvalidStateError exception and abort these steps. 618 // 3. Let highest end time be the largest track buffer ranges end time across 619 // all the track buffers across all SourceBuffer objects in sourceBuffers. 620 // 4. If new duration is less than highest end time, then 621 // 4.1 Update new duration to equal highest end time. 622 media::TimeUnit highestEndTime = mSourceBuffers->HighestEndTime(); 623 // Highest track buffer ranges end time == highest end time when Ended, so 624 // new duration == highest end time and steps 2 and 4 are no-ops. 625 MOZ_ASSERT(highestEndTime == mSourceBuffers->GetHighestBufferedEndTime()); 626 MOZ_ASSERT(highestEndTime >= mSourceBuffers->HighestStartTime()); 627 // Truncate to microsecond resolution for consistency with the 628 // SourceBuffer.buffered getter. Do this before comparison because 629 // mDecoder->GetDuration() may have been similarly truncated. 630 media::TimeUnit newDuration = highestEndTime.ToBase(USECS_PER_S); 631 MSE_DEBUG("DurationChangeOnEndOfStream(newDuration=%s)", 632 newDuration.ToString().get()); 633 if (mDecoder->GetDuration() == newDuration.ToSeconds()) { 634 return; 635 } 636 637 // 5. Update the media duration to new duration and run the HTMLMediaElement 638 // duration change algorithm. 639 mDecoder->SetMediaSourceDuration(newDuration); 640 } 641 642 void MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv) { 643 MOZ_ASSERT(NS_IsMainThread()); 644 MSE_DEBUG("DurationChange(aNewDuration=%f)", aNewDuration); 645 646 // 1. If the current value of duration is equal to new duration, then return. 647 if (mDecoder->GetDuration() == aNewDuration) { 648 return; 649 } 650 651 // 2. If new duration is less than the highest starting presentation timestamp 652 // of any buffered coded frames for all SourceBuffer objects in sourceBuffers, 653 // then throw an InvalidStateError exception and abort these steps. 654 if (aNewDuration < mSourceBuffers->HighestStartTime().ToSeconds()) { 655 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 656 return; 657 } 658 659 // 3. Let highest end time be the largest track buffer ranges end time across 660 // all the track buffers across all SourceBuffer objects in sourceBuffers. 661 double highestEndTime = mSourceBuffers->HighestEndTime().ToSeconds(); 662 // 4. If new duration is less than highest end time, then 663 // 4.1 Update new duration to equal highest end time. 664 double newDuration = std::max(aNewDuration, highestEndTime); 665 666 // 5. Update the media duration to new duration and run the HTMLMediaElement 667 // duration change algorithm. 668 mDecoder->SetMediaSourceDuration(newDuration); 669 } 670 671 already_AddRefed<Promise> MediaSource::MozDebugReaderData(ErrorResult& aRv) { 672 // Creating a JS promise 673 nsGlobalWindowInner* win = GetOwnerWindow(); 674 if (!win) { 675 aRv.Throw(NS_ERROR_UNEXPECTED); 676 return nullptr; 677 } 678 RefPtr<Promise> domPromise = Promise::Create(win, aRv); 679 if (NS_WARN_IF(aRv.Failed())) { 680 return nullptr; 681 } 682 MOZ_ASSERT(domPromise); 683 UniquePtr<MediaSourceDecoderDebugInfo> info = 684 MakeUnique<MediaSourceDecoderDebugInfo>(); 685 mDecoder->RequestDebugInfo(*info)->Then( 686 mAbstractMainThread, __func__, 687 [domPromise, infoPtr = std::move(info)] { 688 domPromise->MaybeResolve(infoPtr.get()); 689 }, 690 [] { 691 MOZ_ASSERT_UNREACHABLE("Unexpected rejection while getting debug data"); 692 }); 693 694 return domPromise.forget(); 695 } 696 697 nsPIDOMWindowInner* MediaSource::GetParentObject() const { 698 return GetOwnerWindow(); 699 } 700 701 JSObject* MediaSource::WrapObject(JSContext* aCx, 702 JS::Handle<JSObject*> aGivenProto) { 703 return MediaSource_Binding::Wrap(aCx, this, aGivenProto); 704 } 705 706 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaSource, DOMEventTargetHelper, 707 mMediaElement, mSourceBuffers, 708 mActiveSourceBuffers) 709 710 NS_IMPL_ADDREF_INHERITED(MediaSource, DOMEventTargetHelper) 711 NS_IMPL_RELEASE_INHERITED(MediaSource, DOMEventTargetHelper) 712 713 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaSource) 714 NS_INTERFACE_MAP_ENTRY_CONCRETE(mozilla::dom::MediaSource) 715 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 716 717 #undef MSE_DEBUG 718 #undef MSE_API 719 720 } // namespace dom 721 722 } // namespace mozilla