MediaCapabilities.cpp (33038B)
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 "MediaCapabilities.h" 8 9 #include <inttypes.h> 10 11 #include <utility> 12 13 #include "AllocationPolicy.h" 14 #include "DecoderTraits.h" 15 #include "MP4Decoder.h" 16 #include "MediaCapabilitiesValidation.h" 17 #include "MediaInfo.h" 18 #include "MediaRecorder.h" 19 #include "PDMFactory.h" 20 #include "VPXDecoder.h" 21 #include "WindowRenderer.h" 22 #include "mozilla/ClearOnShutdown.h" 23 #include "mozilla/EMEUtils.h" 24 #include "mozilla/SchedulerGroup.h" 25 #include "mozilla/StaticPrefs_media.h" 26 #include "mozilla/TaskQueue.h" 27 #include "mozilla/dom/DOMMozPromiseRequestHolder.h" 28 #include "mozilla/dom/Document.h" 29 #include "mozilla/dom/MediaCapabilitiesBinding.h" 30 #include "mozilla/dom/MediaKeySystemAccess.h" 31 #include "mozilla/dom/MediaSource.h" 32 #include "mozilla/dom/Navigator.h" 33 #include "mozilla/dom/Promise.h" 34 #include "mozilla/dom/WorkerCommon.h" 35 #include "mozilla/dom/WorkerPrivate.h" 36 #include "mozilla/dom/WorkerRef.h" 37 #include "mozilla/layers/KnowsCompositor.h" 38 #include "nsContentUtils.h" 39 40 mozilla::LazyLogModule sMediaCapabilitiesLog("MediaCapabilities"); 41 42 #define LOG(msg, ...) \ 43 DDMOZ_LOG(sMediaCapabilitiesLog, LogLevel::Debug, msg, ##__VA_ARGS__) 44 45 namespace mozilla::dom { 46 using mediacaps::IsValidMediaDecodingConfiguration; 47 using mediacaps::IsValidMediaEncodingConfiguration; 48 49 static bool 50 MediaCapabilitiesKeySystemConfigurationToMediaKeySystemConfiguration( 51 const MediaDecodingConfiguration& aInConfig, 52 MediaKeySystemConfiguration& aOutConfig) { 53 if (!aInConfig.mKeySystemConfiguration.WasPassed()) { 54 return false; 55 } 56 57 const auto& keySystemConfig = aInConfig.mKeySystemConfiguration.Value(); 58 if (!keySystemConfig.mInitDataType.IsEmpty()) { 59 if (NS_WARN_IF(!aOutConfig.mInitDataTypes.AppendElement( 60 keySystemConfig.mInitDataType, fallible))) { 61 return false; 62 } 63 } 64 if (keySystemConfig.mSessionTypes.WasPassed() && 65 !keySystemConfig.mSessionTypes.Value().IsEmpty()) { 66 aOutConfig.mSessionTypes.Construct(); 67 for (const auto& type : keySystemConfig.mSessionTypes.Value()) { 68 if (NS_WARN_IF(!aOutConfig.mSessionTypes.Value().AppendElement( 69 type, fallible))) { 70 return false; 71 } 72 } 73 } 74 aOutConfig.mDistinctiveIdentifier = keySystemConfig.mDistinctiveIdentifier; 75 aOutConfig.mPersistentState = keySystemConfig.mPersistentState; 76 77 if (aInConfig.mAudio.WasPassed()) { 78 auto* capabilitiy = aOutConfig.mAudioCapabilities.AppendElement(fallible); 79 if (NS_WARN_IF(!capabilitiy)) { 80 return false; 81 } 82 capabilitiy->mContentType = aInConfig.mAudio.Value().mContentType; 83 if (keySystemConfig.mAudio.WasPassed()) { 84 const auto& config = keySystemConfig.mAudio.Value(); 85 capabilitiy->mRobustness = config.mRobustness; 86 capabilitiy->mEncryptionScheme = config.mEncryptionScheme; 87 } 88 } 89 if (aInConfig.mVideo.WasPassed()) { 90 auto* capabilitiy = aOutConfig.mVideoCapabilities.AppendElement(fallible); 91 if (NS_WARN_IF(!capabilitiy)) { 92 return false; 93 } 94 capabilitiy->mContentType = aInConfig.mVideo.Value().mContentType; 95 if (keySystemConfig.mVideo.WasPassed()) { 96 const auto& config = keySystemConfig.mVideo.Value(); 97 capabilitiy->mRobustness = config.mRobustness; 98 capabilitiy->mEncryptionScheme = config.mEncryptionScheme; 99 } 100 } 101 return true; 102 } 103 104 static nsCString VideoConfigurationToStr(const VideoConfiguration* aConfig) { 105 if (!aConfig) { 106 return nsCString(); 107 } 108 109 auto str = nsPrintfCString( 110 "[contentType:%s width:%d height:%d bitrate:%" PRIu64 111 " framerate:%lf hasAlphaChannel:%s hdrMetadataType:%s colorGamut:%s " 112 "transferFunction:%s scalabilityMode:%s]", 113 NS_ConvertUTF16toUTF8(aConfig->mContentType).get(), aConfig->mWidth, 114 aConfig->mHeight, aConfig->mBitrate, aConfig->mFramerate, 115 aConfig->mHasAlphaChannel.WasPassed() 116 ? aConfig->mHasAlphaChannel.Value() ? "true" : "false" 117 : "?", 118 aConfig->mHdrMetadataType.WasPassed() 119 ? GetEnumString(aConfig->mHdrMetadataType.Value()).get() 120 : "?", 121 aConfig->mColorGamut.WasPassed() 122 ? GetEnumString(aConfig->mColorGamut.Value()).get() 123 : "?", 124 aConfig->mTransferFunction.WasPassed() 125 ? GetEnumString(aConfig->mTransferFunction.Value()).get() 126 : "?", 127 aConfig->mScalabilityMode.WasPassed() 128 ? NS_ConvertUTF16toUTF8(aConfig->mScalabilityMode.Value()).get() 129 : "?"); 130 return std::move(str); 131 } 132 133 static nsCString AudioConfigurationToStr(const AudioConfiguration* aConfig) { 134 if (!aConfig) { 135 return nsCString(); 136 } 137 auto str = nsPrintfCString( 138 "[contentType:%s channels:%s bitrate:%" PRIu64 " samplerate:%d]", 139 NS_ConvertUTF16toUTF8(aConfig->mContentType).get(), 140 aConfig->mChannels.WasPassed() 141 ? NS_ConvertUTF16toUTF8(aConfig->mChannels.Value()).get() 142 : "?", 143 aConfig->mBitrate.WasPassed() ? aConfig->mBitrate.Value() : 0, 144 aConfig->mSamplerate.WasPassed() ? aConfig->mSamplerate.Value() : 0); 145 return std::move(str); 146 } 147 148 static nsCString MediaCapabilitiesInfoToStr( 149 const MediaCapabilitiesInfo& aInfo) { 150 auto str = nsPrintfCString("[supported:%s smooth:%s powerEfficient:%s]", 151 aInfo.mSupported ? "true" : "false", 152 aInfo.mSmooth ? "true" : "false", 153 aInfo.mPowerEfficient ? "true" : "false"); 154 return std::move(str); 155 } 156 157 static nsCString MediaDecodingConfigurationToStr( 158 const MediaDecodingConfiguration& aConfig) { 159 nsCString str; 160 str += "["_ns; 161 if (aConfig.mVideo.WasPassed()) { 162 str += "video:"_ns + VideoConfigurationToStr(&aConfig.mVideo.Value()); 163 if (aConfig.mAudio.WasPassed()) { 164 str += " "_ns; 165 } 166 } 167 if (aConfig.mAudio.WasPassed()) { 168 str += "audio:"_ns + AudioConfigurationToStr(&aConfig.mAudio.Value()); 169 } 170 if (aConfig.mKeySystemConfiguration.WasPassed()) { 171 str += "[keySystem:"_ns + 172 NS_ConvertUTF16toUTF8( 173 aConfig.mKeySystemConfiguration.Value().mKeySystem) + 174 ", "_ns; 175 MediaKeySystemConfiguration emeConfig; 176 if (MediaCapabilitiesKeySystemConfigurationToMediaKeySystemConfiguration( 177 aConfig, emeConfig)) { 178 str += MediaKeySystemAccess::ToCString(emeConfig); 179 } 180 str += "]"_ns; 181 } 182 str += "]"_ns; 183 return str; 184 } 185 186 MediaCapabilities::MediaCapabilities(nsIGlobalObject* aParent) 187 : mParent(aParent) {} 188 189 // https://w3c.github.io/media-capabilities/#dom-mediacapabilities-decodinginfo 190 // Section 2.5.2 DecodingInfo() Method 191 already_AddRefed<Promise> MediaCapabilities::DecodingInfo( 192 const MediaDecodingConfiguration& aConfiguration, ErrorResult& aRv) { 193 RefPtr<Promise> promise = Promise::Create(mParent, aRv); 194 if (aRv.Failed()) { 195 return nullptr; 196 } 197 198 // Step 1: If configuration is not a valid MediaDecodingConfiguration, 199 // return a Promise rejected with a newly created TypeError. 200 if (auto configCheck = IsValidMediaDecodingConfiguration(aConfiguration); 201 configCheck.isErr()) { 202 RejectWithValidationResult(promise, configCheck.unwrapErr()); 203 return promise.forget(); 204 } 205 206 // Step 2: If configuration.keySystemConfiguration exists, 207 // run the following substeps: 208 if (aConfiguration.mKeySystemConfiguration.WasPassed()) { 209 // Step 2.1: If the global object is of type WorkerGlobalScope, 210 // return a Promise rejected with a newly created DOMException 211 // whose name is InvalidStateError. 212 if (IsWorkerGlobal(mParent->GetGlobalJSObject())) { 213 promise->MaybeRejectWithInvalidStateError( 214 "key system configuration is not allowed in the worker scope"); 215 return promise.forget(); 216 } 217 // Step 2.2 If the global object’s relevant settings object is a 218 // non-secure context, return a Promise rejected with a newly 219 // created DOMException whose name is SecurityError. 220 if (auto* window = mParent->GetAsInnerWindow(); 221 window && !window->IsSecureContext()) { 222 promise->MaybeRejectWithSecurityError( 223 "key system configuration is not allowed in a non-secure context"); 224 return promise.forget(); 225 } 226 } 227 228 // Step 3: Let p be a new Promise (already have it!) 229 // Step 4: In parallel, run the Create a MediaCapabilitiesDecodingInfo 230 // algorithm with configuration and resolve p with its result. 231 CreateMediaCapabilitiesDecodingInfo(aConfiguration, aRv, promise); 232 return promise.forget(); 233 } 234 235 // https://w3c.github.io/media-capabilities/#create-media-capabilities-decoding-info 236 void MediaCapabilities::CreateMediaCapabilitiesDecodingInfo( 237 const MediaDecodingConfiguration& aConfiguration, ErrorResult& aRv, 238 Promise* aPromise) { 239 LOG("Processing %s", MediaDecodingConfigurationToStr(aConfiguration).get()); 240 241 bool supported = true; 242 Maybe<MediaContainerType> videoContainer; 243 Maybe<MediaContainerType> audioContainer; 244 245 // If configuration.video is present and is not a valid video configuration, 246 // return a Promise rejected with a TypeError. 247 if (aConfiguration.mVideo.WasPassed()) { 248 videoContainer = CheckVideoConfiguration(aConfiguration.mVideo.Value()); 249 if (!videoContainer) { 250 aPromise->MaybeRejectWithTypeError("Invalid VideoConfiguration"); 251 return; 252 } 253 254 // We have a video configuration and it is valid. Check if it is supported. 255 supported &= 256 aConfiguration.mType == MediaDecodingType::File 257 ? CheckTypeForFile(aConfiguration.mVideo.Value().mContentType) 258 : CheckTypeForMediaSource( 259 aConfiguration.mVideo.Value().mContentType); 260 } 261 if (aConfiguration.mAudio.WasPassed()) { 262 audioContainer = CheckAudioConfiguration(aConfiguration.mAudio.Value()); 263 if (!audioContainer) { 264 aPromise->MaybeRejectWithTypeError("Invalid AudioConfiguration"); 265 return; 266 } 267 // We have an audio configuration and it is valid. Check if it is supported. 268 supported &= 269 aConfiguration.mType == MediaDecodingType::File 270 ? CheckTypeForFile(aConfiguration.mAudio.Value().mContentType) 271 : CheckTypeForMediaSource( 272 aConfiguration.mAudio.Value().mContentType); 273 } 274 275 if (!supported) { 276 MediaCapabilitiesDecodingInfo info; 277 info.mSupported = false; 278 info.mSmooth = false; 279 info.mPowerEfficient = false; 280 LOG("%s -> %s", MediaDecodingConfigurationToStr(aConfiguration).get(), 281 MediaCapabilitiesInfoToStr(info).get()); 282 aPromise->MaybeResolve(std::move(info)); 283 return; 284 } 285 286 nsTArray<UniquePtr<TrackInfo>> tracks; 287 if (aConfiguration.mVideo.WasPassed()) { 288 MOZ_ASSERT(videoContainer.isSome(), "configuration is valid and supported"); 289 auto videoTracks = DecoderTraits::GetTracksInfo(*videoContainer); 290 // If the MIME type does not imply a codec, the string MUST 291 // also have one and only one parameter that is named codecs with a value 292 // describing a single media codec. Otherwise, it MUST contain no 293 // parameters. 294 if (videoTracks.Length() != 1) { 295 aPromise->MaybeRejectWithTypeError(nsPrintfCString( 296 "The provided type '%s' does not have a 'codecs' parameter.", 297 videoContainer->OriginalString().get())); 298 return; 299 } 300 MOZ_DIAGNOSTIC_ASSERT(videoTracks.ElementAt(0), 301 "must contain a valid trackinfo"); 302 // If the type refers to an audio codec, reject now. 303 if (videoTracks[0]->GetType() != TrackInfo::kVideoTrack) { 304 aPromise->MaybeRejectWithTypeError("Invalid VideoConfiguration"); 305 return; 306 } 307 tracks.AppendElements(std::move(videoTracks)); 308 } 309 if (aConfiguration.mAudio.WasPassed()) { 310 MOZ_ASSERT(audioContainer.isSome(), "configuration is valid and supported"); 311 auto audioTracks = DecoderTraits::GetTracksInfo(*audioContainer); 312 // If the MIME type does not imply a codec, the string MUST 313 // also have one and only one parameter that is named codecs with a value 314 // describing a single media codec. Otherwise, it MUST contain no 315 // parameters. 316 if (audioTracks.Length() != 1) { 317 aPromise->MaybeRejectWithTypeError(nsPrintfCString( 318 "The provided type '%s' does not have a 'codecs' parameter.", 319 audioContainer->OriginalString().get())); 320 return; 321 } 322 MOZ_DIAGNOSTIC_ASSERT(audioTracks.ElementAt(0), 323 "must contain a valid trackinfo"); 324 // If the type refers to a video codec, reject now. 325 if (audioTracks[0]->GetType() != TrackInfo::kAudioTrack) { 326 aPromise->MaybeRejectWithTypeError("Invalid AudioConfiguration"); 327 return; 328 } 329 tracks.AppendElements(std::move(audioTracks)); 330 } 331 332 // If configuration.keySystemConfiguration exists: 333 if (aConfiguration.mKeySystemConfiguration.WasPassed()) { 334 MOZ_ASSERT( 335 NS_IsMainThread(), 336 "Key system configuration qurey can not run on the worker thread!"); 337 338 auto* mainThread = GetMainThreadSerialEventTarget(); 339 if (!mainThread) { 340 aPromise->MaybeRejectWithInvalidStateError( 341 "The main thread is shutted down"); 342 return; 343 } 344 345 // This check isn't defined in the spec but exists in web platform tests, so 346 // we perform the check as well in order to reduce the web compatibility 347 // issues. https://github.com/w3c/media-capabilities/issues/220 348 const auto& keySystemConfig = 349 aConfiguration.mKeySystemConfiguration.Value(); 350 if ((keySystemConfig.mVideo.WasPassed() && 351 !aConfiguration.mVideo.WasPassed()) || 352 (keySystemConfig.mAudio.WasPassed() && 353 !aConfiguration.mAudio.WasPassed())) { 354 aPromise->MaybeRejectWithTypeError( 355 "The type of decoding config doesn't match the type of key system " 356 "config"); 357 return; 358 } 359 CheckEncryptedDecodingSupport(aConfiguration) 360 ->Then(mainThread, __func__, 361 [promise = RefPtr<Promise>{aPromise}, 362 self = RefPtr<MediaCapabilities>{this}, aConfiguration, 363 this](MediaKeySystemAccessManager::MediaKeySystemAccessPromise:: 364 ResolveOrRejectValue&& aValue) { 365 if (aValue.IsReject()) { 366 MediaCapabilitiesDecodingInfo info; 367 info.mSupported = false; 368 info.mSmooth = false; 369 info.mPowerEfficient = false; 370 LOG("%s -> %s", 371 MediaDecodingConfigurationToStr(aConfiguration).get(), 372 MediaCapabilitiesInfoToStr(info).get()); 373 promise->MaybeResolve(std::move(info)); 374 return; 375 } 376 MediaCapabilitiesDecodingInfo info; 377 info.mSupported = true; 378 info.mSmooth = true; 379 info.mKeySystemAccess = aValue.ResolveValue(); 380 MOZ_ASSERT(info.mKeySystemAccess); 381 MediaKeySystemConfiguration config; 382 info.mKeySystemAccess->GetConfiguration(config); 383 info.mPowerEfficient = IsHardwareDecryptionSupported(config); 384 LOG("%s -> %s", 385 MediaDecodingConfigurationToStr(aConfiguration).get(), 386 MediaCapabilitiesInfoToStr(info).get()); 387 promise->MaybeResolve(std::move(info)); 388 }); 389 return; 390 } 391 392 // Otherwise, run the following steps: 393 using CapabilitiesPromise = MozPromise<MediaCapabilitiesInfo, MediaResult, 394 /* IsExclusive = */ true>; 395 nsTArray<RefPtr<CapabilitiesPromise>> promises; 396 397 RefPtr<TaskQueue> taskQueue = 398 TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), 399 "MediaCapabilities::TaskQueue"); 400 for (auto&& config : tracks) { 401 TrackInfo::TrackType type = 402 config->IsVideo() ? TrackInfo::kVideoTrack : TrackInfo::kAudioTrack; 403 404 MOZ_ASSERT(type == TrackInfo::kAudioTrack || 405 videoContainer->ExtendedType().GetFramerate().isSome(), 406 "framerate is a required member of VideoConfiguration"); 407 408 if (type == TrackInfo::kAudioTrack) { 409 // There's no need to create an audio decoder has we only want to know if 410 // such codec is supported. We do need to call the PDMFactory::Supports 411 // API outside the main thread to get accurate results. 412 promises.AppendElement( 413 InvokeAsync(taskQueue, __func__, [config = std::move(config)]() { 414 RefPtr<PDMFactory> pdm = new PDMFactory(); 415 SupportDecoderParams params{*config}; 416 if (pdm->Supports(params, nullptr /* decoder doctor */).isEmpty()) { 417 return CapabilitiesPromise::CreateAndReject(NS_ERROR_FAILURE, 418 __func__); 419 } 420 MediaCapabilitiesDecodingInfo info; 421 info.mSupported = true; 422 info.mSmooth = true; 423 info.mPowerEfficient = true; 424 return CapabilitiesPromise::CreateAndResolve(std::move(info), 425 __func__); 426 })); 427 continue; 428 } 429 430 // On Windows, the MediaDataDecoder expects to be created on a thread 431 // supporting MTA, which the main thread doesn't. So we use our task queue 432 // to create such decoder and perform initialization. 433 434 RefPtr<layers::KnowsCompositor> compositor = GetCompositor(); 435 float frameRate = 436 static_cast<float>(videoContainer->ExtendedType().GetFramerate().ref()); 437 const bool shouldResistFingerprinting = 438 mParent->ShouldResistFingerprinting(RFPTarget::MediaCapabilities); 439 440 // clang-format off 441 promises.AppendElement(InvokeAsync( 442 taskQueue, __func__, 443 [taskQueue, frameRate, shouldResistFingerprinting, compositor, 444 config = std::move(config)]() mutable -> RefPtr<CapabilitiesPromise> { 445 // MediaDataDecoder keeps a reference to the config object, so we must 446 // keep it alive until the decoder has been shutdown. 447 static Atomic<uint32_t> sTrackingIdCounter(0); 448 TrackingId trackingId(TrackingId::Source::MediaCapabilities, 449 sTrackingIdCounter++, 450 TrackingId::TrackAcrossProcesses::Yes); 451 CreateDecoderParams params{ 452 *config, compositor, 453 CreateDecoderParams::VideoFrameRate(frameRate), 454 TrackInfo::kVideoTrack, Some(std::move(trackingId))}; 455 // We want to ensure that all decoder's queries are occurring only 456 // once at a time as it can quickly exhaust the system resources 457 // otherwise. 458 static RefPtr<AllocPolicy> sVideoAllocPolicy = [&taskQueue]() { 459 SchedulerGroup::Dispatch( 460 NS_NewRunnableFunction( 461 "MediaCapabilities::AllocPolicy:Video", []() { 462 ClearOnShutdown(&sVideoAllocPolicy, 463 ShutdownPhase::XPCOMShutdownThreads); 464 })); 465 return new SingleAllocPolicy(TrackInfo::TrackType::kVideoTrack, 466 taskQueue); 467 }(); 468 return AllocationWrapper::CreateDecoder(params, sVideoAllocPolicy) 469 ->Then( 470 taskQueue, __func__, 471 [taskQueue, shouldResistFingerprinting, 472 config = std::move(config)]( 473 AllocationWrapper::AllocateDecoderPromise:: 474 ResolveOrRejectValue&& aValue) mutable { 475 if (aValue.IsReject()) { 476 return CapabilitiesPromise::CreateAndReject( 477 std::move(aValue.RejectValue()), __func__); 478 } 479 RefPtr<MediaDataDecoder> decoder = 480 std::move(aValue.ResolveValue()); 481 // We now query the decoder to determine if it's power 482 // efficient. 483 RefPtr<CapabilitiesPromise> p = decoder->Init()->Then( 484 taskQueue, __func__, 485 [taskQueue, decoder, 486 shouldResistFingerprinting, 487 config = std::move(config)]( 488 MediaDataDecoder::InitPromise:: 489 ResolveOrRejectValue&& aValue) mutable { 490 RefPtr<CapabilitiesPromise> p; 491 if (aValue.IsReject()) { 492 p = CapabilitiesPromise::CreateAndReject( 493 std::move(aValue.RejectValue()), __func__); 494 } else if (shouldResistFingerprinting) { 495 MediaCapabilitiesDecodingInfo info; 496 info.mSupported = true; 497 info.mSmooth = true; 498 info.mPowerEfficient = false; 499 p = CapabilitiesPromise::CreateAndResolve(std::move(info), __func__); 500 } else { 501 MOZ_ASSERT(config->IsVideo()); 502 if (config->GetAsVideoInfo()->mImage.height < 480) { 503 // Assume that we can do stuff at 480p or less in 504 // a power efficient manner and smoothly. If 505 // greater than 480p we assume that if the video 506 // decoding is hardware accelerated it will be 507 // smooth and power efficient, otherwise we use 508 // the benchmark to estimate 509 MediaCapabilitiesDecodingInfo info; 510 info.mSupported = true; 511 info.mSmooth = true; 512 info.mPowerEfficient = true; 513 p = CapabilitiesPromise::CreateAndResolve(std::move(info), __func__); 514 } else { 515 nsAutoCString reason; 516 bool smooth = true; 517 bool powerEfficient = 518 decoder->IsHardwareAccelerated(reason); 519 MediaCapabilitiesDecodingInfo info; 520 info.mSupported = true; 521 info.mSmooth = smooth; 522 info.mPowerEfficient = powerEfficient; 523 p = CapabilitiesPromise::CreateAndResolve(std::move(info), __func__); 524 } 525 } 526 MOZ_ASSERT(p.get(), "the promise has been created"); 527 // Let's keep alive the decoder and the config object 528 // until the decoder has shutdown. 529 decoder->Shutdown()->Then( 530 taskQueue, __func__, 531 [taskQueue, decoder, config = std::move(config)]( 532 const ShutdownPromise::ResolveOrRejectValue& 533 aValue) {}); 534 return p; 535 }); 536 return p; 537 }); 538 })); 539 // clang-format on 540 } 541 542 auto holder = MakeRefPtr< 543 DOMMozPromiseRequestHolder<CapabilitiesPromise::AllPromiseType>>(mParent); 544 RefPtr<nsISerialEventTarget> targetThread; 545 RefPtr<StrongWorkerRef> workerRef; 546 547 if (NS_IsMainThread()) { 548 targetThread = GetMainThreadSerialEventTarget(); 549 } else { 550 WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); 551 MOZ_ASSERT(wp, "Must be called from a worker thread"); 552 targetThread = wp->HybridEventTarget(); 553 workerRef = StrongWorkerRef::Create( 554 wp, "MediaCapabilities", [holder, targetThread]() { 555 MOZ_ASSERT(targetThread->IsOnCurrentThread()); 556 holder->DisconnectIfExists(); 557 }); 558 if (NS_WARN_IF(!workerRef)) { 559 aPromise->MaybeRejectWithInvalidStateError("The worker is shutting down"); 560 return; 561 } 562 } 563 564 MOZ_ASSERT(targetThread); 565 566 // this is only captured for use with the LOG macro. 567 RefPtr<MediaCapabilities> self = this; 568 569 CapabilitiesPromise::All(targetThread, promises) 570 ->Then(targetThread, __func__, 571 [promise = RefPtr<Promise>{aPromise}, tracks = std::move(tracks), 572 workerRef, holder, aConfiguration, self, 573 this](CapabilitiesPromise::AllPromiseType::ResolveOrRejectValue&& 574 aValue) { 575 holder->Complete(); 576 if (aValue.IsReject()) { 577 MediaCapabilitiesDecodingInfo info; 578 info.mSupported = false; 579 info.mSmooth = false; 580 info.mPowerEfficient = false; 581 LOG("%s -> %s", 582 MediaDecodingConfigurationToStr(aConfiguration).get(), 583 MediaCapabilitiesInfoToStr(info).get()); 584 promise->MaybeResolve(std::move(info)); 585 return; 586 } 587 bool powerEfficient = true; 588 bool smooth = true; 589 for (auto&& capability : aValue.ResolveValue()) { 590 smooth &= capability.mSmooth; 591 powerEfficient &= capability.mPowerEfficient; 592 } 593 MediaCapabilitiesDecodingInfo info; 594 info.mSupported = true; 595 info.mSmooth = smooth; 596 info.mPowerEfficient = powerEfficient; 597 LOG("%s -> %s", 598 MediaDecodingConfigurationToStr(aConfiguration).get(), 599 MediaCapabilitiesInfoToStr(info).get()); 600 promise->MaybeResolve(std::move(info)); 601 }) 602 ->Track(*holder); 603 } 604 605 // https://www.w3.org/TR/media-capabilities/#is-encrypted-decode-supported 606 RefPtr<MediaKeySystemAccessManager::MediaKeySystemAccessPromise> 607 MediaCapabilities::CheckEncryptedDecodingSupport( 608 const MediaDecodingConfiguration& aConfiguration) { 609 using MediaKeySystemAccessPromise = 610 MediaKeySystemAccessManager::MediaKeySystemAccessPromise; 611 auto* window = mParent->GetAsInnerWindow(); 612 if (NS_WARN_IF(!window)) { 613 return MediaKeySystemAccessPromise::CreateAndReject(NS_ERROR_FAILURE, 614 __func__); 615 } 616 617 auto* manager = window->Navigator()->GetOrCreateMediaKeySystemAccessManager(); 618 if (NS_WARN_IF(!manager)) { 619 return MediaKeySystemAccessPromise::CreateAndReject(NS_ERROR_FAILURE, 620 __func__); 621 } 622 623 // Let emeConfiguration be a new MediaKeySystemConfiguration, and initialize 624 // it as follows 625 Sequence<MediaKeySystemConfiguration> configs; 626 auto* emeConfig = configs.AppendElement(fallible); 627 if (NS_WARN_IF(!emeConfig)) { 628 return MediaKeySystemAccessPromise::CreateAndReject(NS_ERROR_FAILURE, 629 __func__); 630 } 631 632 if (!MediaCapabilitiesKeySystemConfigurationToMediaKeySystemConfiguration( 633 aConfiguration, *emeConfig)) { 634 return MediaKeySystemAccessPromise::CreateAndReject(NS_ERROR_FAILURE, 635 __func__); 636 } 637 return manager->Request( 638 aConfiguration.mKeySystemConfiguration.Value().mKeySystem, configs); 639 } 640 641 already_AddRefed<Promise> MediaCapabilities::EncodingInfo( 642 const MediaEncodingConfiguration& aConfiguration, ErrorResult& aRv) { 643 RefPtr<Promise> promise = Promise::Create(mParent, aRv); 644 if (aRv.Failed()) { 645 return nullptr; 646 } 647 648 // If configuration is not a valid MediaConfiguration, return a Promise 649 // rejected with a TypeError. 650 if (auto configCheck = IsValidMediaEncodingConfiguration(aConfiguration); 651 configCheck.isErr()) { 652 ThrowWithValidationResult(aRv, configCheck.unwrapErr()); 653 return nullptr; 654 } 655 656 bool supported = true; 657 658 // If configuration.video is present and is not a valid video configuration, 659 // return a Promise rejected with a TypeError. 660 if (aConfiguration.mVideo.WasPassed()) { 661 if (!CheckVideoConfiguration(aConfiguration.mVideo.Value())) { 662 aRv.ThrowTypeError<MSG_INVALID_MEDIA_VIDEO_CONFIGURATION>(); 663 return nullptr; 664 } 665 // We have a video configuration and it is valid. Check if it is supported. 666 supported &= 667 CheckTypeForEncoder(aConfiguration.mVideo.Value().mContentType); 668 } 669 if (aConfiguration.mAudio.WasPassed()) { 670 if (!CheckAudioConfiguration(aConfiguration.mAudio.Value())) { 671 aRv.ThrowTypeError<MSG_INVALID_MEDIA_AUDIO_CONFIGURATION>(); 672 return nullptr; 673 } 674 // We have an audio configuration and it is valid. Check if it is supported. 675 supported &= 676 CheckTypeForEncoder(aConfiguration.mAudio.Value().mContentType); 677 } 678 679 MediaCapabilitiesInfo info; 680 info.mSupported = supported; 681 info.mSmooth = supported; 682 info.mPowerEfficient = false; 683 promise->MaybeResolve(std::move(info)); 684 685 return promise.forget(); 686 } 687 688 Maybe<MediaContainerType> MediaCapabilities::CheckVideoConfiguration( 689 const VideoConfiguration& aConfig) const { 690 Maybe<MediaExtendedMIMEType> container = MakeMediaExtendedMIMEType(aConfig); 691 if (!container) { 692 return Nothing(); 693 } 694 // A valid video MIME type is a string that is a valid media MIME type and for 695 // which the type per [RFC7231] is either video or application. 696 if (!container->Type().HasVideoMajorType() && 697 !container->Type().HasApplicationMajorType()) { 698 return Nothing(); 699 } 700 701 // If the MIME type does not imply a codec, the string MUST also have one and 702 // only one parameter that is named codecs with a value describing a single 703 // media codec. Otherwise, it MUST contain no parameters. 704 // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of 705 // parameters) 706 707 return Some(MediaContainerType(std::move(*container))); 708 } 709 710 Maybe<MediaContainerType> MediaCapabilities::CheckAudioConfiguration( 711 const AudioConfiguration& aConfig) const { 712 Maybe<MediaExtendedMIMEType> container = MakeMediaExtendedMIMEType(aConfig); 713 if (!container) { 714 return Nothing(); 715 } 716 // A valid audio MIME type is a string that is valid media MIME type and for 717 // which the type per [RFC7231] is either audio or application. 718 if (!container->Type().HasAudioMajorType() && 719 !container->Type().HasApplicationMajorType()) { 720 return Nothing(); 721 } 722 723 // If the MIME type does not imply a codec, the string MUST also have one and 724 // only one parameter that is named codecs with a value describing a single 725 // media codec. Otherwise, it MUST contain no parameters. 726 // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of 727 // parameters) 728 729 return Some(MediaContainerType(std::move(*container))); 730 } 731 732 bool MediaCapabilities::CheckTypeForMediaSource(const nsAString& aType) { 733 IgnoredErrorResult rv; 734 MediaSource::IsTypeSupported( 735 aType, nullptr /* DecoderDoctorDiagnostics */, rv, 736 Some(mParent->ShouldResistFingerprinting(RFPTarget::MediaCapabilities))); 737 738 return !rv.Failed(); 739 } 740 741 bool MediaCapabilities::CheckTypeForFile(const nsAString& aType) { 742 Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType); 743 if (!containerType) { 744 return false; 745 } 746 747 return DecoderTraits::CanHandleContainerType( 748 *containerType, nullptr /* DecoderDoctorDiagnostics */) != 749 CANPLAY_NO; 750 } 751 752 bool MediaCapabilities::CheckTypeForEncoder(const nsAString& aType) { 753 return MediaRecorder::IsTypeSupported(aType); 754 } 755 756 already_AddRefed<layers::KnowsCompositor> MediaCapabilities::GetCompositor() { 757 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetParentObject()); 758 if (NS_WARN_IF(!window)) { 759 return nullptr; 760 } 761 762 nsCOMPtr<Document> doc = window->GetExtantDoc(); 763 if (NS_WARN_IF(!doc)) { 764 return nullptr; 765 } 766 WindowRenderer* renderer = nsContentUtils::WindowRendererForDocument(doc); 767 if (NS_WARN_IF(!renderer)) { 768 return nullptr; 769 } 770 RefPtr<layers::KnowsCompositor> knows = renderer->AsKnowsCompositor(); 771 if (NS_WARN_IF(!knows)) { 772 return nullptr; 773 } 774 return knows->GetForMedia().forget(); 775 } 776 777 JSObject* MediaCapabilities::WrapObject(JSContext* aCx, 778 JS::Handle<JSObject*> aGivenProto) { 779 return MediaCapabilities_Binding::Wrap(aCx, this, aGivenProto); 780 } 781 782 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaCapabilities) 783 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 784 NS_INTERFACE_MAP_ENTRY(nsISupports) 785 NS_INTERFACE_MAP_END 786 787 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaCapabilities) 788 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaCapabilities) 789 790 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaCapabilities, mParent) 791 792 } // namespace mozilla::dom 793 #undef LOG