MediaRecorder.cpp (76898B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 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 "MediaRecorder.h" 8 9 #include "AudioNodeEngine.h" 10 #include "AudioNodeTrack.h" 11 #include "DOMMediaStream.h" 12 #include "MediaDecoder.h" 13 #include "MediaEncoder.h" 14 #include "MediaTrackGraph.h" 15 #include "VideoUtils.h" 16 #include "mozilla/DOMEventTargetHelper.h" 17 #include "mozilla/DefineEnum.h" 18 #include "mozilla/MemoryReporting.h" 19 #include "mozilla/Preferences.h" 20 #include "mozilla/StaticPtr.h" 21 #include "mozilla/TaskQueue.h" 22 #include "mozilla/ToString.h" 23 #include "mozilla/dom/AudioStreamTrack.h" 24 #include "mozilla/dom/BlobEvent.h" 25 #include "mozilla/dom/Document.h" 26 #include "mozilla/dom/EmptyBlobImpl.h" 27 #include "mozilla/dom/File.h" 28 #include "mozilla/dom/MediaRecorderErrorEvent.h" 29 #include "mozilla/dom/VideoStreamTrack.h" 30 #include "mozilla/glean/DomMediaMetrics.h" 31 #include "mozilla/media/MediaUtils.h" 32 #include "nsContentTypeParser.h" 33 #include "nsContentUtils.h" 34 #include "nsDocShell.h" 35 #include "nsError.h" 36 #include "nsGlobalWindowInner.h" 37 #include "nsIPrincipal.h" 38 #include "nsIScriptError.h" 39 #include "nsMimeTypes.h" 40 #include "nsProxyRelease.h" 41 #include "nsServiceManagerUtils.h" 42 #include "nsTArray.h" 43 44 mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder"); 45 #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg) 46 47 constexpr int MIN_VIDEO_BITRATE_BPS = 10e3; // 10kbps 48 constexpr int DEFAULT_VIDEO_BITRATE_BPS = 2500e3; // 2.5Mbps 49 constexpr int MAX_VIDEO_BITRATE_BPS = 100e6; // 100Mbps 50 51 constexpr int MIN_AUDIO_BITRATE_BPS = 500; // 500bps 52 constexpr int DEFAULT_AUDIO_BITRATE_BPS = 128e3; // 128kbps 53 constexpr int MAX_AUDIO_BITRATE_BPS = 512e3; // 512kbps 54 55 namespace mozilla::dom { 56 57 using namespace mozilla::media; 58 59 /** 60 * MediaRecorderReporter measures memory being used by the Media Recorder. 61 * 62 * It is a singleton reporter and the single class object lives as long as at 63 * least one Recorder is registered. In MediaRecorder, the reporter is 64 * unregistered when it is destroyed. 65 */ 66 class MediaRecorderReporter final : public nsIMemoryReporter { 67 public: 68 static void AddMediaRecorder(MediaRecorder* aRecorder) { 69 if (!sUniqueInstance) { 70 sUniqueInstance = MakeAndAddRef<MediaRecorderReporter>(); 71 RegisterWeakAsyncMemoryReporter(sUniqueInstance); 72 } 73 sUniqueInstance->mRecorders.AppendElement(aRecorder); 74 } 75 76 static void RemoveMediaRecorder(MediaRecorder* aRecorder) { 77 if (!sUniqueInstance) { 78 return; 79 } 80 81 sUniqueInstance->mRecorders.RemoveElement(aRecorder); 82 if (sUniqueInstance->mRecorders.IsEmpty()) { 83 UnregisterWeakMemoryReporter(sUniqueInstance); 84 sUniqueInstance = nullptr; 85 } 86 } 87 88 NS_DECL_THREADSAFE_ISUPPORTS 89 90 MediaRecorderReporter() = default; 91 92 NS_IMETHOD 93 CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, 94 bool aAnonymize) override { 95 nsTArray<RefPtr<MediaRecorder::SizeOfPromise>> promises; 96 for (const RefPtr<MediaRecorder>& recorder : mRecorders) { 97 promises.AppendElement(recorder->SizeOfExcludingThis(MallocSizeOf)); 98 } 99 100 nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport; 101 nsCOMPtr<nsISupports> data = aData; 102 MediaRecorder::SizeOfPromise::All(GetCurrentSerialEventTarget(), promises) 103 ->Then( 104 GetCurrentSerialEventTarget(), __func__, 105 [handleReport, data](const nsTArray<size_t>& sizes) { 106 nsCOMPtr<nsIMemoryReporterManager> manager = 107 do_GetService("@mozilla.org/memory-reporter-manager;1"); 108 if (!manager) { 109 return; 110 } 111 112 size_t sum = 0; 113 for (const size_t& size : sizes) { 114 sum += size; 115 } 116 117 handleReport->Callback(""_ns, "explicit/media/recorder"_ns, 118 KIND_HEAP, UNITS_BYTES, sum, 119 "Memory used by media recorder."_ns, data); 120 121 manager->EndReport(); 122 }, 123 [](size_t) { MOZ_CRASH("Unexpected reject"); }); 124 125 return NS_OK; 126 } 127 128 private: 129 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) 130 131 virtual ~MediaRecorderReporter() { 132 MOZ_ASSERT(mRecorders.IsEmpty(), "All recorders must have been removed"); 133 } 134 135 static StaticRefPtr<MediaRecorderReporter> sUniqueInstance; 136 137 nsTArray<RefPtr<MediaRecorder>> mRecorders; 138 }; 139 NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter); 140 141 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder) 142 143 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder, 144 DOMEventTargetHelper) 145 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStream) 146 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode) 147 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOtherDomException) 148 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityDomException) 149 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnknownDomException) 150 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) 151 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 152 153 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder, 154 DOMEventTargetHelper) 155 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStream) 156 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode) 157 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOtherDomException) 158 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityDomException) 159 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnknownDomException) 160 tmp->UnRegisterActivityObserver(); 161 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) 162 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 163 164 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder) 165 NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity) 166 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 167 168 NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper) 169 NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper) 170 171 namespace { 172 bool PrincipalSubsumes(MediaRecorder* aRecorder, nsIPrincipal* aPrincipal) { 173 if (!aRecorder->GetOwnerWindow()) { 174 return false; 175 } 176 nsCOMPtr<Document> doc = aRecorder->GetOwnerWindow()->GetExtantDoc(); 177 if (!doc) { 178 return false; 179 } 180 if (!aPrincipal) { 181 return false; 182 } 183 bool subsumes; 184 if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) { 185 return false; 186 } 187 return subsumes; 188 } 189 190 bool MediaStreamTracksPrincipalSubsumes( 191 MediaRecorder* aRecorder, 192 const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) { 193 nsCOMPtr<nsIPrincipal> principal = nullptr; 194 for (const auto& track : aTracks) { 195 nsContentUtils::CombineResourcePrincipals(&principal, 196 track->GetPrincipal()); 197 } 198 return PrincipalSubsumes(aRecorder, principal); 199 } 200 201 bool AudioNodePrincipalSubsumes(MediaRecorder* aRecorder, 202 AudioNode* aAudioNode) { 203 MOZ_ASSERT(aAudioNode); 204 Document* doc = aAudioNode->GetOwnerWindow() 205 ? aAudioNode->GetOwnerWindow()->GetExtantDoc() 206 : nullptr; 207 nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr; 208 return PrincipalSubsumes(aRecorder, principal); 209 } 210 211 // This list is sorted so that lesser failures are later, so that 212 // IsTypeSupportedImpl() can report the error from audio or video types that 213 // is closer to being supported. 214 enum class TypeSupport { 215 MediaTypeInvalid, 216 NoVideoWithAudioType, 217 ContainersDisabled, 218 CodecsDisabled, 219 ContainerUnsupported, 220 CodecUnsupported, 221 CodecDuplicated, 222 Supported, 223 }; 224 225 nsCString TypeSupportToCString(TypeSupport aSupport, 226 const nsAString& aMimeType) { 227 nsAutoCString mime = NS_ConvertUTF16toUTF8(aMimeType); 228 switch (aSupport) { 229 case TypeSupport::Supported: 230 return nsPrintfCString("%s is supported", mime.get()); 231 case TypeSupport::MediaTypeInvalid: 232 return nsPrintfCString("%s is not a valid media type", mime.get()); 233 case TypeSupport::NoVideoWithAudioType: 234 return nsPrintfCString( 235 "Video cannot be recorded with %s as it is an audio type", 236 mime.get()); 237 case TypeSupport::ContainersDisabled: 238 return "All containers are disabled"_ns; 239 case TypeSupport::CodecsDisabled: 240 return "All codecs are disabled"_ns; 241 case TypeSupport::ContainerUnsupported: 242 return nsPrintfCString("%s indicates an unsupported container", 243 mime.get()); 244 case TypeSupport::CodecUnsupported: 245 return nsPrintfCString("%s indicates an unsupported codec", mime.get()); 246 case TypeSupport::CodecDuplicated: 247 return nsPrintfCString("%s contains the same codec multiple times", 248 mime.get()); 249 default: 250 MOZ_ASSERT_UNREACHABLE("Unknown TypeSupport"); 251 return "Unknown error"_ns; 252 } 253 } 254 255 TypeSupport CanRecordAudioTrackWith(const Maybe<MediaContainerType>& aMimeType, 256 const nsAString& aMimeTypeString) { 257 if (aMimeTypeString.IsEmpty()) { 258 // For the empty string we just need to check whether we have support for an 259 // audio container and an audio codec. 260 if (!MediaEncoder::IsWebMEncoderEnabled() && 261 !MediaDecoder::IsOggEnabled()) { 262 // No container support for audio. 263 return TypeSupport::ContainersDisabled; 264 } 265 266 if (!MediaDecoder::IsOpusEnabled()) { 267 // No codec support for audio. 268 return TypeSupport::CodecsDisabled; 269 } 270 271 return TypeSupport::Supported; 272 } 273 274 if (!aMimeType) { 275 // A mime type string was set, but it couldn't be parsed to a valid 276 // MediaContainerType. 277 return TypeSupport::MediaTypeInvalid; 278 } 279 280 if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM) && 281 aMimeType->Type() != MEDIAMIMETYPE(AUDIO_WEBM) && 282 aMimeType->Type() != MEDIAMIMETYPE(AUDIO_OGG)) { 283 // Any currently supported container can record audio. 284 return TypeSupport::ContainerUnsupported; 285 } 286 287 if (aMimeType->Type() == MEDIAMIMETYPE(VIDEO_WEBM) && 288 !MediaEncoder::IsWebMEncoderEnabled()) { 289 return TypeSupport::ContainerUnsupported; 290 } 291 292 if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM) && 293 !MediaEncoder::IsWebMEncoderEnabled()) { 294 return TypeSupport::ContainerUnsupported; 295 } 296 297 if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_OGG) && 298 !MediaDecoder::IsOggEnabled()) { 299 return TypeSupport::ContainerUnsupported; 300 } 301 302 if (!MediaDecoder::IsOpusEnabled()) { 303 return TypeSupport::CodecUnsupported; 304 } 305 306 if (!aMimeType->ExtendedType().HaveCodecs()) { 307 // No codecs constrained, we can pick opus. 308 return TypeSupport::Supported; 309 } 310 311 size_t opus = 0; 312 size_t unknown = 0; 313 for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) { 314 // Ignore video codecs. 315 if (codec.EqualsLiteral("vp8")) { 316 continue; 317 } 318 if (codec.EqualsLiteral("vp8.0")) { 319 continue; 320 } 321 if (codec.EqualsLiteral("opus")) { 322 // All containers support opus 323 opus++; 324 continue; 325 } 326 unknown++; 327 } 328 329 if (unknown > 0) { 330 // Unsupported codec. 331 return TypeSupport::CodecUnsupported; 332 } 333 334 if (opus == 0) { 335 // Codecs specified but not opus. Unsupported for audio. 336 return TypeSupport::CodecUnsupported; 337 } 338 339 if (opus > 1) { 340 // Opus specified more than once. Bad form. 341 return TypeSupport::CodecDuplicated; 342 } 343 344 return TypeSupport::Supported; 345 } 346 347 TypeSupport CanRecordVideoTrackWith(const Maybe<MediaContainerType>& aMimeType, 348 const nsAString& aMimeTypeString) { 349 if (aMimeTypeString.IsEmpty()) { 350 // For the empty string we just need to check whether we have support for a 351 // video container and a video codec. The VP8 encoder is always available. 352 if (!MediaEncoder::IsWebMEncoderEnabled()) { 353 // No container support for video. 354 return TypeSupport::ContainersDisabled; 355 } 356 357 return TypeSupport::Supported; 358 } 359 360 if (!aMimeType) { 361 // A mime type string was set, but it couldn't be parsed to a valid 362 // MediaContainerType. 363 return TypeSupport::MediaTypeInvalid; 364 } 365 366 if (!aMimeType->Type().HasVideoMajorType()) { 367 return TypeSupport::NoVideoWithAudioType; 368 } 369 370 if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM)) { 371 return TypeSupport::ContainerUnsupported; 372 } 373 374 if (!MediaEncoder::IsWebMEncoderEnabled()) { 375 return TypeSupport::ContainerUnsupported; 376 } 377 378 if (!aMimeType->ExtendedType().HaveCodecs()) { 379 // No codecs constrained, we can pick vp8. 380 return TypeSupport::Supported; 381 } 382 383 size_t vp8 = 0; 384 size_t unknown = 0; 385 for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) { 386 if (codec.EqualsLiteral("opus")) { 387 // Ignore audio codecs. 388 continue; 389 } 390 if (codec.EqualsLiteral("vp8")) { 391 vp8++; 392 continue; 393 } 394 if (codec.EqualsLiteral("vp8.0")) { 395 vp8++; 396 continue; 397 } 398 unknown++; 399 } 400 401 if (unknown > 0) { 402 // Unsupported codec. 403 return TypeSupport::CodecUnsupported; 404 } 405 406 if (vp8 == 0) { 407 // Codecs specified but not vp8. Unsupported for video. 408 return TypeSupport::CodecUnsupported; 409 } 410 411 if (vp8 > 1) { 412 // Vp8 specified more than once. Bad form. 413 return TypeSupport::CodecDuplicated; 414 } 415 416 return TypeSupport::Supported; 417 } 418 419 TypeSupport CanRecordWith(MediaStreamTrack* aTrack, 420 const Maybe<MediaContainerType>& aMimeType, 421 const nsAString& aMimeTypeString) { 422 if (aTrack->AsAudioStreamTrack()) { 423 return CanRecordAudioTrackWith(aMimeType, aMimeTypeString); 424 } 425 426 if (aTrack->AsVideoStreamTrack()) { 427 return CanRecordVideoTrackWith(aMimeType, aMimeTypeString); 428 } 429 430 MOZ_CRASH("Unexpected track type"); 431 } 432 433 struct ParsedMIMEType { 434 MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING_AT_CLASS_SCOPE(MediaType, 435 (Audio, Video, Unknown)); 436 MediaType mMediaType = MediaType::Unknown; 437 MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING_AT_CLASS_SCOPE(Container, (MP4, MKV, WebM, 438 Ogg, Unknown)); 439 Container mContainer = Container::Unknown; 440 nsTArray<CodecType> mCodecs; 441 }; 442 443 constexpr std::array<std::array<CodecType, 5>, 5> kValidAudioCodecs = {{ 444 // MP4 445 {{CodecType::AAC, CodecType::Flac, CodecType::Opus}}, 446 // MKV 447 {{CodecType::AAC, CodecType::Flac, CodecType::Opus, CodecType::PCM, 448 CodecType::Vorbis}}, 449 // WebM 450 {{CodecType::Opus, CodecType::Vorbis}}, 451 // Ogg 452 {{CodecType::Flac, CodecType::Opus, CodecType::Vorbis}}, 453 // Unknown 454 {{}}, 455 }}; 456 457 constexpr std::array<std::array<CodecType, 5>, 5> kValidVideoOnlyCodecs = {{ 458 // MP4 459 {{CodecType::AV1, CodecType::H264, CodecType::H265, CodecType::VP9}}, 460 // MKV 461 {{CodecType::AV1, CodecType::H264, CodecType::H265, CodecType::VP8, 462 CodecType::VP9}}, 463 // WebM 464 {{CodecType::AV1, CodecType::VP8, CodecType::VP9}}, 465 // Ogg 466 {{CodecType::VP8, CodecType::VP9}}, 467 // Unknown 468 {{}}, 469 }}; 470 471 constexpr auto kValidContainerCodecPairs = []() constexpr { 472 std::array< 473 std::array<std::array<CodecType, UnderlyingValue(kHighestCodecType) + 1>, 474 UnderlyingValue(ParsedMIMEType::sHighestContainer) + 1>, 475 UnderlyingValue(ParsedMIMEType::sHighestMediaType) + 1> 476 result{}; 477 478 // Generate valid audio container-codec pairs 479 for (size_t c = 0; c < kValidAudioCodecs.size(); ++c) { 480 for (size_t i = 0; 481 i < kValidAudioCodecs[c].size() && IsAudio(kValidAudioCodecs[c][i]); 482 ++i) { 483 result[UnderlyingValue(ParsedMIMEType::MediaType::Audio)][c][i] = 484 kValidAudioCodecs[c][i]; 485 } 486 } 487 488 // Generate valid video container-codec pairs 489 for (size_t c = 0; c < kValidVideoOnlyCodecs.size(); ++c) { 490 size_t k = 0; 491 // Add video-only codecs 492 for (size_t i = 0; i < kValidVideoOnlyCodecs[c].size() && 493 IsVideo(kValidVideoOnlyCodecs[c][i]); 494 ++i) { 495 result[UnderlyingValue(ParsedMIMEType::MediaType::Video)][c][k++] = 496 kValidVideoOnlyCodecs[c][i]; 497 } 498 // Add audio-only codecs 499 for (size_t i = 0; 500 i < kValidAudioCodecs[c].size() && IsAudio(kValidAudioCodecs[c][i]); 501 ++i) { 502 result[UnderlyingValue(ParsedMIMEType::MediaType::Video)][c][k++] = 503 kValidAudioCodecs[c][i]; 504 } 505 } 506 507 return result; 508 }(); 509 510 static ParsedMIMEType::Container GetContainerFromMimeType( 511 const MediaMIMEType& aType) { 512 if (aType == MEDIAMIMETYPE(VIDEO_MP4) || aType == MEDIAMIMETYPE(AUDIO_MP4)) { 513 return ParsedMIMEType::Container::MP4; 514 } 515 if (aType == MEDIAMIMETYPE(VIDEO_MATROSKA) || 516 aType == MEDIAMIMETYPE(VIDEO_MATROSKA_LEGACY) || 517 aType == MEDIAMIMETYPE(AUDIO_MATROSKA) || 518 aType == MEDIAMIMETYPE(AUDIO_MATROSKA_LEGACY)) { 519 return ParsedMIMEType::Container::MKV; 520 } 521 if (aType == MEDIAMIMETYPE(VIDEO_WEBM) || 522 aType == MEDIAMIMETYPE(AUDIO_WEBM)) { 523 return ParsedMIMEType::Container::WebM; 524 } 525 if (aType == MEDIAMIMETYPE(VIDEO_OGG) || aType == MEDIAMIMETYPE(AUDIO_OGG)) { 526 return ParsedMIMEType::Container::Ogg; 527 } 528 return ParsedMIMEType::Container::Unknown; 529 } 530 531 static CodecType GetCodecTypeFromString(const nsAString& aCodec) { 532 if (IsVP8CodecString(aCodec)) { 533 return CodecType::VP8; 534 } 535 if (IsVP9CodecString(aCodec)) { 536 return CodecType::VP9; 537 } 538 if (IsAV1CodecString(aCodec)) { 539 return CodecType::AV1; 540 } 541 if (IsH264CodecString(aCodec)) { 542 return CodecType::H264; 543 } 544 if (IsH265CodecString(aCodec)) { 545 return CodecType::H265; 546 } 547 if (IsAACCodecString(aCodec)) { 548 return CodecType::AAC; 549 } 550 if (aCodec.EqualsLiteral("flac")) { 551 return CodecType::Flac; 552 } 553 if (aCodec.EqualsLiteral("pcm")) { 554 return CodecType::PCM; 555 } 556 if (aCodec.EqualsLiteral("opus")) { 557 return CodecType::Opus; 558 } 559 if (aCodec.EqualsLiteral("vorbis")) { 560 return CodecType::Vorbis; 561 } 562 return CodecType::Unknown; 563 } 564 565 static ParsedMIMEType ParseMimeType(const Maybe<MediaContainerType>& aType) { 566 ParsedMIMEType result; 567 568 if (!aType) { 569 return result; 570 } 571 572 result.mMediaType = [&] { 573 if (aType->Type().HasAudioMajorType()) { 574 return ParsedMIMEType::MediaType::Audio; 575 } 576 if (aType->Type().HasVideoMajorType()) { 577 return ParsedMIMEType::MediaType::Video; 578 } 579 return ParsedMIMEType::MediaType::Unknown; 580 }(); 581 result.mContainer = GetContainerFromMimeType(aType->Type()); 582 for (const auto& codec : aType->ExtendedType().Codecs().Range()) { 583 result.mCodecs.AppendElement(GetCodecTypeFromString(codec)); 584 } 585 return result; 586 } 587 588 static bool IsValidContainerCodecPair(ParsedMIMEType::MediaType aMediaType, 589 ParsedMIMEType::Container aContainer, 590 CodecType aCodec) { 591 const auto& validCodecs = 592 kValidContainerCodecPairs[UnderlyingValue(aMediaType)] 593 [UnderlyingValue(aContainer)]; 594 return std::find(validCodecs.begin(), validCodecs.end(), aCodec) != 595 validCodecs.end(); 596 } 597 598 static nsTArray<nsCString> GetMIMELabelStrings(const ParsedMIMEType& aType) { 599 nsTArray<nsCString> labels; 600 if (aType.mContainer == ParsedMIMEType::Container::Unknown || 601 aType.mMediaType == ParsedMIMEType::MediaType::Unknown) { 602 labels.AppendElement("others"_ns); 603 return labels; 604 } 605 nsCString baseLabel(ParsedMIMEType::EnumValueToString(aType.mContainer)); 606 ToLowerCase(baseLabel); 607 if (aType.mCodecs.IsEmpty()) { 608 nsCString label = baseLabel; 609 label.AppendLiteral("_unspecified"); 610 labels.AppendElement(label); 611 return labels; 612 } 613 for (const auto& codec : aType.mCodecs) { 614 nsCString label = baseLabel; 615 if (IsValidContainerCodecPair(aType.mMediaType, aType.mContainer, codec)) { 616 label.AppendLiteral("_"); 617 label.Append(EnumValueToString(codec)); 618 ToLowerCase(label); 619 } else { 620 label.AppendLiteral("_others"); 621 } 622 LOG(LogLevel::Verbose, 623 ("GetMIMELabelStrings: type: %s, container: %s, codec: %s => label: %s", 624 ToString(aType.mMediaType).c_str(), ToString(aType.mContainer).c_str(), 625 ToString(codec).c_str(), label.get())); 626 labels.AppendElement(label); 627 } 628 return labels; 629 } 630 631 // The primary goal is to measure how frequently the MP4 container is requested, 632 // while also collecting data on other container/codec combinations as a 633 // secondary benefit. 634 static void RecordQueriedMIMEType(const Maybe<MediaContainerType>& aMimeType, 635 const nsAString& aMimeTypeString) { 636 LOG(LogLevel::Verbose, ("RecordQueriedMIMEType: %s", 637 NS_ConvertUTF16toUTF8(aMimeTypeString).get())); 638 if (aMimeTypeString.IsEmpty()) { 639 LOG(LogLevel::Verbose, ("MIME queried is empty")); 640 glean::media_recorder::mime_type_query.Get("empty"_ns).Add(1); 641 return; 642 } 643 ParsedMIMEType aType = ParseMimeType(aMimeType); 644 nsTArray<nsCString> labels = GetMIMELabelStrings(aType); 645 for (const auto& label : labels) { 646 LOG(LogLevel::Verbose, ("MIME queried: %s", label.get())); 647 glean::media_recorder::mime_type_query.Get(label).Add(1); 648 } 649 } 650 651 TypeSupport IsTypeSupportedImpl(const nsAString& aMIMEType) { 652 if (aMIMEType.IsEmpty()) { 653 RecordQueriedMIMEType(Nothing(), aMIMEType); 654 // Lie and return true even if no container/codec support is enabled, 655 // because the spec mandates it. 656 return TypeSupport::Supported; 657 } 658 Maybe<MediaContainerType> mime = MakeMediaContainerType(aMIMEType); 659 RecordQueriedMIMEType(mime, aMIMEType); 660 TypeSupport audioSupport = CanRecordAudioTrackWith(mime, aMIMEType); 661 TypeSupport videoSupport = CanRecordVideoTrackWith(mime, aMIMEType); 662 return std::max(audioSupport, videoSupport); 663 } 664 665 nsString SelectMimeType(bool aHasVideo, bool aHasAudio, 666 const nsString& aConstrainedMimeType) { 667 MOZ_ASSERT(aHasVideo || aHasAudio); 668 669 Maybe<MediaContainerType> constrainedType = 670 MakeMediaContainerType(aConstrainedMimeType); 671 672 // If we are recording video, Start() should have rejected any non-video mime 673 // types. 674 MOZ_ASSERT_IF(constrainedType && aHasVideo, 675 constrainedType->Type().HasVideoMajorType()); 676 // IsTypeSupported() rejects application mime types. 677 MOZ_ASSERT_IF(constrainedType, 678 !constrainedType->Type().HasApplicationMajorType()); 679 680 nsString result; 681 if (constrainedType && constrainedType->ExtendedType().HaveCodecs()) { 682 // The constrained mime type is fully defined (it has codecs!). No need to 683 // select anything. 684 CopyUTF8toUTF16(constrainedType->OriginalString(), result); 685 } else { 686 // There is no constrained mime type, or there is and it is not fully 687 // defined but still valid. Select what's missing, so that we have major 688 // type, container and codecs. 689 690 // If there is a constrained mime type it should not have codecs defined, 691 // because then it is fully defined and used unchanged (covered earlier). 692 MOZ_ASSERT_IF(constrainedType, 693 !constrainedType->ExtendedType().HaveCodecs()); 694 695 nsCString majorType; 696 { 697 if (constrainedType) { 698 // There is a constrained type. It has both major type and container in 699 // order to be valid. Use them as is. 700 majorType = constrainedType->Type().AsString(); 701 } else if (aHasVideo) { 702 majorType = nsLiteralCString(VIDEO_WEBM); 703 } else { 704 majorType = nsLiteralCString(AUDIO_OGG); 705 } 706 } 707 708 nsCString codecs; 709 { 710 if (aHasVideo && aHasAudio) { 711 codecs = "\"vp8, opus\""_ns; 712 } else if (aHasVideo) { 713 codecs = "vp8"_ns; 714 } else { 715 codecs = "opus"_ns; 716 } 717 } 718 result = NS_ConvertUTF8toUTF16( 719 nsPrintfCString("%s; codecs=%s", majorType.get(), codecs.get())); 720 } 721 722 MOZ_ASSERT_IF(aHasAudio, 723 CanRecordAudioTrackWith(MakeMediaContainerType(result), 724 result) == TypeSupport::Supported); 725 MOZ_ASSERT_IF(aHasVideo, 726 CanRecordVideoTrackWith(MakeMediaContainerType(result), 727 result) == TypeSupport::Supported); 728 return result; 729 } 730 731 void SelectBitrates(uint32_t aBitsPerSecond, uint8_t aNumVideoTracks, 732 uint32_t* aOutVideoBps, uint8_t aNumAudioTracks, 733 uint32_t* aOutAudioBps) { 734 uint32_t vbps = 0; 735 uint32_t abps = 0; 736 737 const uint32_t minVideoBps = MIN_VIDEO_BITRATE_BPS * aNumVideoTracks; 738 const uint32_t maxVideoBps = MAX_VIDEO_BITRATE_BPS * aNumVideoTracks; 739 740 const uint32_t minAudioBps = MIN_AUDIO_BITRATE_BPS * aNumAudioTracks; 741 const uint32_t maxAudioBps = MAX_AUDIO_BITRATE_BPS * aNumAudioTracks; 742 743 if (aNumVideoTracks == 0) { 744 MOZ_DIAGNOSTIC_ASSERT(aNumAudioTracks > 0); 745 abps = std::min(maxAudioBps, std::max(minAudioBps, aBitsPerSecond)); 746 } else if (aNumAudioTracks == 0) { 747 vbps = std::min(maxVideoBps, std::max(minVideoBps, aBitsPerSecond)); 748 } else { 749 // Scale the bits so that video gets 20 times the bits of audio. 750 // Since we must account for varying number of tracks of each type we weight 751 // them by type; video = weight 20, audio = weight 1. 752 const uint32_t videoWeight = aNumVideoTracks * 20; 753 const uint32_t audioWeight = aNumAudioTracks; 754 const uint32_t totalWeights = audioWeight + videoWeight; 755 const uint32_t videoBitrate = 756 uint64_t(aBitsPerSecond) * videoWeight / totalWeights; 757 const uint32_t audioBitrate = 758 uint64_t(aBitsPerSecond) * audioWeight / totalWeights; 759 vbps = std::min(maxVideoBps, std::max(minVideoBps, videoBitrate)); 760 abps = std::min(maxAudioBps, std::max(minAudioBps, audioBitrate)); 761 } 762 763 *aOutVideoBps = vbps; 764 *aOutAudioBps = abps; 765 } 766 } // namespace 767 768 /** 769 * Session is an object to represent a single recording event. 770 * In original design, all recording context is stored in MediaRecorder, which 771 * causes a problem if someone calls MediaRecorder::Stop and 772 * MediaRecorder::Start quickly. To prevent blocking main thread, media encoding 773 * is executed in a second thread, named encoder thread. For the same reason, we 774 * do not await encoder thread shutdown in MediaRecorder::Stop. 775 * If someone calls MediaRecorder::Start before encoder thread shutdown, the 776 * same recording context in MediaRecorder might be accessed by two distinct 777 * encoder threads, which would be racy. With the recording context, including 778 * the encoder thread, in a Session object the problem is solved. 779 * 780 * Lifetime of MediaRecorder and Session objects. 781 * 1) MediaRecorder creates a Session in MediaRecorder::Start() and holds 782 * a reference to it. Then the Session registers itself to a ShutdownBlocker 783 * and also holds a reference to MediaRecorder. 784 * Therefore, the reference dependency in gecko is: 785 * ShutdownBlocker -> Session <-> MediaRecorder, note that there is a cycle 786 * reference between Session and MediaRecorder. 787 * 2) A Session is destroyed after Session::DoSessionEndTask() has been called 788 * _and_ all encoded media data has been passed to OnDataAvailable handler. 789 * In some cases the encoded media can be discarded before being passed to 790 * the OnDataAvailable handler. 791 * 3) Session::DoSessionEndTask is called by an application through 792 * MediaRecorder::Stop(), from a MediaEncoder Shutdown notification, from the 793 * document going inactive or invisible, or from the ShutdownBlocker. 794 */ 795 class MediaRecorder::Session : public PrincipalChangeObserver<MediaStreamTrack>, 796 public DOMMediaStream::TrackListener { 797 NS_DECL_ISUPPORTS_INHERITED 798 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Session, 799 DOMMediaStream::TrackListener) 800 801 struct TrackTypeComparator { 802 enum Type { 803 AUDIO, 804 VIDEO, 805 }; 806 static bool Equals(const RefPtr<MediaStreamTrack>& aTrack, Type aType) { 807 return (aType == AUDIO && aTrack->AsAudioStreamTrack()) || 808 (aType == VIDEO && aTrack->AsVideoStreamTrack()); 809 } 810 }; 811 812 public: 813 Session(MediaRecorder* aRecorder, 814 nsTArray<RefPtr<MediaStreamTrack>> aMediaStreamTracks, 815 uint32_t aVideoBitsPerSecond, uint32_t aAudioBitsPerSecond) 816 : mRecorder(aRecorder), 817 mMediaStreamTracks(std::move(aMediaStreamTracks)), 818 mMimeType(SelectMimeType( 819 mMediaStreamTracks.Contains(TrackTypeComparator::VIDEO, 820 TrackTypeComparator()), 821 mRecorder->mAudioNode || 822 mMediaStreamTracks.Contains(TrackTypeComparator::AUDIO, 823 TrackTypeComparator()), 824 mRecorder->mConstrainedMimeType)), 825 mVideoBitsPerSecond(aVideoBitsPerSecond), 826 mAudioBitsPerSecond(aAudioBitsPerSecond), 827 mRunningState(RunningState::Idling) { 828 MOZ_ASSERT(NS_IsMainThread()); 829 } 830 831 void PrincipalChanged(MediaStreamTrack* aTrack) override { 832 NS_ASSERTION(mMediaStreamTracks.Contains(aTrack), 833 "Principal changed for unrecorded track"); 834 if (!MediaStreamTracksPrincipalSubsumes(mRecorder, mMediaStreamTracks)) { 835 DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR); 836 } 837 } 838 839 void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override { 840 LOG(LogLevel::Warning, 841 ("Session.NotifyTrackAdded %p Raising error due to track set change", 842 this)); 843 // There's a chance we have a sensible JS stack here. 844 if (!mRecorder->mOtherDomException) { 845 mRecorder->mOtherDomException = DOMException::Create( 846 NS_ERROR_DOM_INVALID_MODIFICATION_ERR, 847 "An attempt was made to add a track to the recorded MediaStream " 848 "during the recording"_ns); 849 } 850 DoSessionEndTask(NS_ERROR_DOM_INVALID_MODIFICATION_ERR); 851 } 852 853 void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override { 854 if (aTrack->Ended()) { 855 // TrackEncoder will pickup tracks that end itself. 856 return; 857 } 858 LOG(LogLevel::Warning, 859 ("Session.NotifyTrackRemoved %p Raising error due to track set change", 860 this)); 861 // There's a chance we have a sensible JS stack here. 862 if (!mRecorder->mOtherDomException) { 863 mRecorder->mOtherDomException = DOMException::Create( 864 NS_ERROR_DOM_INVALID_MODIFICATION_ERR, 865 "An attempt was made to remove a track from the recorded MediaStream " 866 "during the recording"_ns); 867 } 868 DoSessionEndTask(NS_ERROR_DOM_INVALID_MODIFICATION_ERR); 869 } 870 871 void Start(TimeDuration aTimeslice) { 872 LOG(LogLevel::Debug, ("Session.Start %p", this)); 873 MOZ_ASSERT(NS_IsMainThread()); 874 875 if (mRecorder->mStream) { 876 // The TrackListener reports back when tracks are added or removed from 877 // the MediaStream. 878 mMediaStream = mRecorder->mStream; 879 mMediaStream->RegisterTrackListener(this); 880 881 uint8_t trackTypes = 0; 882 for (const auto& track : mMediaStreamTracks) { 883 if (track->AsAudioStreamTrack()) { 884 trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK; 885 } else if (track->AsVideoStreamTrack()) { 886 trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK; 887 } else { 888 MOZ_CRASH("Unexpected track type"); 889 } 890 } 891 892 for (const auto& t : mMediaStreamTracks) { 893 t->AddPrincipalChangeObserver(this); 894 } 895 896 LOG(LogLevel::Debug, ("Session.Start track types = (%d)", trackTypes)); 897 InitEncoder(trackTypes, mMediaStreamTracks[0]->Graph()->GraphRate(), 898 aTimeslice); 899 return; 900 } 901 902 if (mRecorder->mAudioNode) { 903 TrackRate trackRate = 904 mRecorder->mAudioNode->Context()->Graph()->GraphRate(); 905 906 // Web Audio node has only audio. 907 InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate, aTimeslice); 908 return; 909 } 910 911 MOZ_ASSERT(false, "Unknown source"); 912 } 913 914 void Stop() { 915 LOG(LogLevel::Debug, ("Session.Stop %p", this)); 916 MOZ_ASSERT(NS_IsMainThread()); 917 918 if (mEncoder) { 919 mEncoder->DisconnectTracks(); 920 } 921 922 // Remove main thread state added in Start(). 923 if (mMediaStream) { 924 mMediaStream->UnregisterTrackListener(this); 925 mMediaStream = nullptr; 926 } 927 928 { 929 for (const auto& track : mMediaStreamTracks) { 930 track->RemovePrincipalChangeObserver(this); 931 } 932 } 933 934 if (mRunningState.isOk() && 935 mRunningState.inspect() == RunningState::Idling) { 936 LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this)); 937 // End the Session directly if there is no encoder. 938 DoSessionEndTask(NS_OK); 939 } else if (mRunningState.isOk() && 940 (mRunningState.inspect() == RunningState::Starting || 941 mRunningState.inspect() == RunningState::Running)) { 942 if (mRunningState.inspect() == RunningState::Starting) { 943 // The MediaEncoder might not report started, but by spec we must fire 944 // "start". 945 mStartedListener.DisconnectIfExists(); 946 NS_DispatchToMainThread(NewRunnableMethod( 947 "MediaRecorder::Session::Stop", this, &Session::OnStarted)); 948 } 949 mRunningState = RunningState::Stopping; 950 } 951 } 952 953 void Pause() { 954 LOG(LogLevel::Debug, ("Session.Pause")); 955 MOZ_ASSERT(NS_IsMainThread()); 956 MOZ_ASSERT_IF(mRunningState.isOk(), 957 mRunningState.unwrap() != RunningState::Idling); 958 if (mRunningState.isErr() || 959 mRunningState.unwrap() == RunningState::Stopping || 960 mRunningState.unwrap() == RunningState::Stopped) { 961 return; 962 } 963 MOZ_ASSERT(mEncoder); 964 mEncoder->Suspend(); 965 } 966 967 void Resume() { 968 LOG(LogLevel::Debug, ("Session.Resume")); 969 MOZ_ASSERT(NS_IsMainThread()); 970 MOZ_ASSERT_IF(mRunningState.isOk(), 971 mRunningState.unwrap() != RunningState::Idling); 972 if (mRunningState.isErr() || 973 mRunningState.unwrap() == RunningState::Stopping || 974 mRunningState.unwrap() == RunningState::Stopped) { 975 return; 976 } 977 MOZ_ASSERT(mEncoder); 978 mEncoder->Resume(); 979 } 980 981 void RequestData() { 982 LOG(LogLevel::Debug, ("Session.RequestData")); 983 MOZ_ASSERT(NS_IsMainThread()); 984 MOZ_ASSERT(mEncoder); 985 986 InvokeAsync(mEncoderThread, mEncoder.get(), __func__, 987 &MediaEncoder::RequestData) 988 ->Then( 989 GetMainThreadSerialEventTarget(), __func__, 990 [this, self = RefPtr<Session>(this)]( 991 const MediaEncoder::BlobPromise::ResolveOrRejectValue& aRrv) { 992 if (aRrv.IsReject()) { 993 LOG(LogLevel::Warning, ("RequestData failed")); 994 DoSessionEndTask(aRrv.RejectValue()); 995 return; 996 } 997 998 nsresult rv = 999 mRecorder->CreateAndDispatchBlobEvent(aRrv.ResolveValue()); 1000 if (NS_FAILED(rv)) { 1001 DoSessionEndTask(NS_OK); 1002 } 1003 }); 1004 } 1005 1006 public: 1007 RefPtr<SizeOfPromise> SizeOfExcludingThis( 1008 mozilla::MallocSizeOf aMallocSizeOf) { 1009 MOZ_ASSERT(NS_IsMainThread()); 1010 if (!mEncoder) { 1011 return SizeOfPromise::CreateAndResolve(0, __func__); 1012 } 1013 1014 return mEncoder->SizeOfExcludingThis(aMallocSizeOf); 1015 } 1016 1017 private: 1018 virtual ~Session() { 1019 MOZ_ASSERT(NS_IsMainThread()); 1020 MOZ_ASSERT(mShutdownPromise); 1021 MOZ_ASSERT(!mShutdownBlocker); 1022 LOG(LogLevel::Debug, ("Session.~Session (%p)", this)); 1023 } 1024 1025 void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate, 1026 TimeDuration aTimeslice) { 1027 LOG(LogLevel::Debug, ("Session.InitEncoder %p", this)); 1028 MOZ_ASSERT(NS_IsMainThread()); 1029 1030 if (!mRunningState.isOk() || 1031 mRunningState.inspect() != RunningState::Idling) { 1032 MOZ_ASSERT_UNREACHABLE("Double-init"); 1033 return; 1034 } 1035 1036 // Create a TaskQueue to read encode media data from MediaEncoder. 1037 MOZ_RELEASE_ASSERT(!mEncoderThread); 1038 RefPtr<SharedThreadPool> pool = 1039 GetMediaThreadPool(MediaThreadType::WEBRTC_WORKER); 1040 if (!pool) { 1041 LOG(LogLevel::Debug, ("Session.InitEncoder %p Failed to create " 1042 "MediaRecorderReadThread thread pool", 1043 this)); 1044 DoSessionEndTask(NS_ERROR_FAILURE); 1045 return; 1046 } 1047 1048 mEncoderThread = 1049 TaskQueue::Create(pool.forget(), "MediaRecorderReadThread"); 1050 1051 MOZ_DIAGNOSTIC_ASSERT(!mShutdownBlocker); 1052 // Add a shutdown blocker so mEncoderThread can be shutdown async. 1053 class Blocker : public ShutdownBlocker { 1054 const RefPtr<Session> mSession; 1055 1056 public: 1057 Blocker(RefPtr<Session> aSession, const nsString& aName) 1058 : ShutdownBlocker(aName), mSession(std::move(aSession)) {} 1059 1060 NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override { 1061 mSession->DoSessionEndTask(NS_ERROR_ABORT); 1062 return NS_OK; 1063 } 1064 }; 1065 1066 nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier(); 1067 if (!barrier) { 1068 LOG(LogLevel::Error, 1069 ("Session.InitEncoder %p Failed to get shutdown barrier", this)); 1070 DoSessionEndTask(NS_ERROR_FAILURE); 1071 return; 1072 } 1073 1074 nsString name; 1075 name.AppendPrintf("MediaRecorder::Session %p shutdown", this); 1076 mShutdownBlocker = MakeAndAddRef<Blocker>(this, name); 1077 nsresult rv = barrier->AddBlocker( 1078 mShutdownBlocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, 1079 u"MediaRecorder::Session: shutdown"_ns); 1080 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 1081 1082 uint32_t maxMemory = Preferences::GetUint("media.recorder.max_memory", 1083 MAX_ALLOW_MEMORY_BUFFER); 1084 1085 mEncoder = MediaEncoder::CreateEncoder( 1086 mEncoderThread, mMimeType, mAudioBitsPerSecond, mVideoBitsPerSecond, 1087 aTrackTypes, aTrackRate, maxMemory, aTimeslice); 1088 1089 if (!mEncoder) { 1090 LOG(LogLevel::Error, ("Session.InitEncoder !mEncoder %p", this)); 1091 DoSessionEndTask(NS_ERROR_ABORT); 1092 return; 1093 } 1094 1095 nsISerialEventTarget* mainThread = GetMainThreadSerialEventTarget(); 1096 mStartedListener = 1097 mEncoder->StartedEvent().Connect(mainThread, this, &Session::OnStarted); 1098 mDataAvailableListener = mEncoder->DataAvailableEvent().Connect( 1099 mainThread, this, &Session::OnDataAvailable); 1100 mErrorListener = 1101 mEncoder->ErrorEvent().Connect(mainThread, this, &Session::OnError); 1102 mShutdownListener = mEncoder->ShutdownEvent().Connect(mainThread, this, 1103 &Session::OnShutdown); 1104 1105 if (mRecorder->mAudioNode) { 1106 mEncoder->ConnectAudioNode(mRecorder->mAudioNode, 1107 mRecorder->mAudioNodeOutput); 1108 } 1109 1110 for (const auto& track : mMediaStreamTracks) { 1111 mEncoder->ConnectMediaStreamTrack(track); 1112 } 1113 1114 // Set mRunningState to Running so that DoSessionEndTask will 1115 // take the responsibility to end the session. 1116 mRunningState = RunningState::Starting; 1117 } 1118 1119 // This is the task that will stop recording per spec: 1120 // - If rv is NS_ERROR_ABORT or NS_ERROR_DOM_SECURITY_ERR, cancel the encoders 1121 // - Otherwise, stop the encoders gracefully, this still encodes buffered data 1122 // - Set state to "inactive" 1123 // - Fire an error event, if NS_FAILED(rv) 1124 // - Discard blob data if rv is NS_ERROR_DOM_SECURITY_ERR 1125 // - Fire a Blob event 1126 // - Fire an event named stop 1127 void DoSessionEndTask(nsresult rv) { 1128 MOZ_ASSERT(NS_IsMainThread()); 1129 if (mRunningState.isErr()) { 1130 // We have already ended with an error. 1131 return; 1132 } 1133 1134 if (mRunningState.isOk() && 1135 mRunningState.inspect() == RunningState::Stopped) { 1136 // We have already ended gracefully. 1137 return; 1138 } 1139 1140 bool needsStartEvent = false; 1141 if (mRunningState.isOk() && 1142 (mRunningState.inspect() == RunningState::Idling || 1143 mRunningState.inspect() == RunningState::Starting)) { 1144 needsStartEvent = true; 1145 } 1146 1147 // Set a terminated running state. Future DoSessionEnd tasks will exit 1148 // early. 1149 if (rv == NS_OK) { 1150 mRunningState = RunningState::Stopped; 1151 } else { 1152 mRunningState = Err(rv); 1153 } 1154 1155 RefPtr<MediaEncoder::BlobPromise> blobPromise; 1156 if (!mEncoder) { 1157 blobPromise = MediaEncoder::BlobPromise::CreateAndReject(NS_OK, __func__); 1158 } else { 1159 blobPromise = 1160 (rv == NS_ERROR_ABORT || rv == NS_ERROR_DOM_SECURITY_ERR 1161 ? mEncoder->Cancel() 1162 : mEncoder->Stop()) 1163 ->Then(mEncoderThread, __func__, 1164 [encoder = mEncoder]( 1165 const GenericNonExclusivePromise::ResolveOrRejectValue& 1166 aValue) { 1167 MOZ_DIAGNOSTIC_ASSERT(aValue.IsResolve()); 1168 return encoder->RequestData(); 1169 }); 1170 } 1171 1172 blobPromise 1173 ->Then( 1174 GetMainThreadSerialEventTarget(), __func__, 1175 [this, self = RefPtr<Session>(this), rv, needsStartEvent]( 1176 const MediaEncoder::BlobPromise::ResolveOrRejectValue& aRv) { 1177 if (mRecorder->mSessions.LastElement() == this) { 1178 // Set state to inactive, but only if the recorder is not 1179 // controlled by another session already. 1180 mRecorder->Inactivate(); 1181 } 1182 1183 if (needsStartEvent) { 1184 mRecorder->DispatchSimpleEvent(u"start"_ns); 1185 } 1186 1187 // If there was an error, Fire the appropriate one 1188 if (NS_FAILED(rv)) { 1189 mRecorder->NotifyError(rv); 1190 } 1191 1192 // Fire a blob event named dataavailable 1193 RefPtr<BlobImpl> blobImpl; 1194 if (rv == NS_ERROR_DOM_SECURITY_ERR || aRv.IsReject()) { 1195 // In case of SecurityError, the blob data must be discarded. 1196 // We create a new empty one and throw the blob with its data 1197 // away. 1198 // In case we failed to gather blob data, we create an empty 1199 // memory blob instead. 1200 blobImpl = new EmptyBlobImpl(mMimeType); 1201 } else { 1202 blobImpl = aRv.ResolveValue(); 1203 } 1204 if (NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(blobImpl))) { 1205 // Failed to dispatch blob event. That's unexpected. It's 1206 // probably all right to fire an error event if we haven't 1207 // already. 1208 if (NS_SUCCEEDED(rv)) { 1209 mRecorder->NotifyError(NS_ERROR_FAILURE); 1210 } 1211 } 1212 1213 // Fire an event named stop 1214 mRecorder->DispatchSimpleEvent(u"stop"_ns); 1215 1216 // And finally, Shutdown and destroy the Session 1217 return Shutdown(); 1218 }) 1219 ->Then(GetMainThreadSerialEventTarget(), __func__, 1220 [this, self = RefPtr<Session>(this)] { 1221 // Guard against the case where we fail to add a blocker due to 1222 // being in XPCOM shutdown. If we're in this state we shouldn't 1223 // try and get a shutdown barrier as we'll fail. 1224 if (!mShutdownBlocker) { 1225 return; 1226 } 1227 MustGetShutdownBarrier()->RemoveBlocker(mShutdownBlocker); 1228 mShutdownBlocker = nullptr; 1229 }); 1230 } 1231 1232 void OnStarted() { 1233 MOZ_ASSERT(NS_IsMainThread()); 1234 if (mRunningState.isErr()) { 1235 return; 1236 } 1237 RunningState state = mRunningState.inspect(); 1238 if (state == RunningState::Starting || state == RunningState::Stopping) { 1239 if (state == RunningState::Starting) { 1240 // We set it to Running in the runnable since we can only assign 1241 // mRunningState on main thread. We set it before running the start 1242 // event runnable since that dispatches synchronously (and may cause 1243 // js calls to methods depending on mRunningState). 1244 mRunningState = RunningState::Running; 1245 1246 mRecorder->mMimeType = mEncoder->mMimeType; 1247 } 1248 mRecorder->DispatchSimpleEvent(u"start"_ns); 1249 } 1250 } 1251 1252 void OnDataAvailable(const RefPtr<BlobImpl>& aBlob) { 1253 if (mRunningState.isErr() && 1254 mRunningState.unwrapErr() == NS_ERROR_DOM_SECURITY_ERR) { 1255 return; 1256 } 1257 if (NS_WARN_IF(NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(aBlob)))) { 1258 LOG(LogLevel::Warning, 1259 ("MediaRecorder %p Creating or dispatching BlobEvent failed", this)); 1260 DoSessionEndTask(NS_OK); 1261 } 1262 } 1263 1264 void OnError() { 1265 MOZ_ASSERT(NS_IsMainThread()); 1266 DoSessionEndTask(NS_ERROR_FAILURE); 1267 } 1268 1269 void OnShutdown() { 1270 MOZ_ASSERT(NS_IsMainThread()); 1271 DoSessionEndTask(NS_OK); 1272 } 1273 1274 RefPtr<ShutdownPromise> Shutdown() { 1275 MOZ_ASSERT(NS_IsMainThread()); 1276 LOG(LogLevel::Debug, ("Session Shutdown %p", this)); 1277 1278 if (mShutdownPromise) { 1279 return mShutdownPromise; 1280 } 1281 1282 mShutdownPromise = ShutdownPromise::CreateAndResolve(true, __func__); 1283 1284 if (mEncoder) { 1285 mShutdownPromise = 1286 mShutdownPromise 1287 ->Then(GetMainThreadSerialEventTarget(), __func__, 1288 [this, self = RefPtr<Session>(this)] { 1289 mStartedListener.DisconnectIfExists(); 1290 mDataAvailableListener.DisconnectIfExists(); 1291 mErrorListener.DisconnectIfExists(); 1292 mShutdownListener.DisconnectIfExists(); 1293 return mEncoder->Cancel(); 1294 }) 1295 ->Then(mEncoderThread, __func__, [] { 1296 // Meh, this is just to convert the promise type to match 1297 // mShutdownPromise. 1298 return ShutdownPromise::CreateAndResolve(true, __func__); 1299 }); 1300 } 1301 1302 // Remove main thread state. This could be needed if Stop() wasn't called. 1303 if (mMediaStream) { 1304 mMediaStream->UnregisterTrackListener(this); 1305 mMediaStream = nullptr; 1306 } 1307 1308 { 1309 auto tracks(std::move(mMediaStreamTracks)); 1310 for (RefPtr<MediaStreamTrack>& track : tracks) { 1311 track->RemovePrincipalChangeObserver(this); 1312 } 1313 } 1314 1315 // Break the cycle reference between Session and MediaRecorder. 1316 mShutdownPromise = mShutdownPromise->Then( 1317 GetMainThreadSerialEventTarget(), __func__, 1318 [self = RefPtr<Session>(this)]() { 1319 self->mRecorder->RemoveSession(self); 1320 return ShutdownPromise::CreateAndResolve(true, __func__); 1321 }, 1322 []() { 1323 MOZ_ASSERT_UNREACHABLE("Unexpected reject"); 1324 return ShutdownPromise::CreateAndReject(false, __func__); 1325 }); 1326 1327 if (mEncoderThread) { 1328 mShutdownPromise = mShutdownPromise->Then( 1329 GetMainThreadSerialEventTarget(), __func__, 1330 [encoderThread = mEncoderThread]() { 1331 return encoderThread->BeginShutdown(); 1332 }, 1333 []() { 1334 MOZ_ASSERT_UNREACHABLE("Unexpected reject"); 1335 return ShutdownPromise::CreateAndReject(false, __func__); 1336 }); 1337 } 1338 1339 return mShutdownPromise; 1340 } 1341 1342 private: 1343 enum class RunningState { 1344 Idling, // Session has been created 1345 Starting, // MediaEncoder started, waiting for data 1346 Running, // MediaEncoder has received data 1347 Stopping, // Stop() has been called 1348 Stopped, // Session has stopped without any error 1349 }; 1350 1351 // Our associated MediaRecorder. 1352 const RefPtr<MediaRecorder> mRecorder; 1353 1354 // Stream currently recorded. 1355 RefPtr<DOMMediaStream> mMediaStream; 1356 1357 // Tracks currently recorded. This should be a subset of mMediaStream's track 1358 // set. 1359 nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks; 1360 1361 // Runnable thread for reading data from MediaEncoder. 1362 RefPtr<TaskQueue> mEncoderThread; 1363 // MediaEncoder pipeline. 1364 RefPtr<MediaEncoder> mEncoder; 1365 // Listener connected to mMediaEncoder::StartedEvent(). 1366 MediaEventListener mStartedListener; 1367 // Listener connected to mMediaEncoder::DataAvailableEvent(). 1368 MediaEventListener mDataAvailableListener; 1369 // Listener connected to mMediaEncoder::ErrorEvent(). 1370 MediaEventListener mErrorListener; 1371 // Listener connected to mMediaEncoder::ShutdownEvent(). 1372 MediaEventListener mShutdownListener; 1373 // Set in Shutdown() and resolved when shutdown is complete. 1374 RefPtr<ShutdownPromise> mShutdownPromise; 1375 // Session mimeType 1376 const nsString mMimeType; 1377 // The video bitrate the recorder was configured with. 1378 const uint32_t mVideoBitsPerSecond; 1379 // The audio bitrate the recorder was configured with. 1380 const uint32_t mAudioBitsPerSecond; 1381 // The session's current main thread state. The error type gets set when 1382 // ending a recording with an error. An NS_OK error is invalid. 1383 // Main thread only. 1384 Result<RunningState, nsresult> mRunningState; 1385 // Shutdown blocker unique for this Session. Main thread only. 1386 RefPtr<ShutdownBlocker> mShutdownBlocker; 1387 }; 1388 1389 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaRecorder::Session, 1390 DOMMediaStream::TrackListener, mMediaStream, 1391 mMediaStreamTracks) 1392 NS_IMPL_ADDREF_INHERITED(MediaRecorder::Session, DOMMediaStream::TrackListener) 1393 NS_IMPL_RELEASE_INHERITED(MediaRecorder::Session, DOMMediaStream::TrackListener) 1394 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder::Session) 1395 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream::TrackListener) 1396 1397 MediaRecorder::~MediaRecorder() { 1398 LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this)); 1399 UnRegisterActivityObserver(); 1400 } 1401 1402 MediaRecorder::MediaRecorder(nsPIDOMWindowInner* aOwnerWindow) 1403 : DOMEventTargetHelper(aOwnerWindow) { 1404 MOZ_ASSERT(aOwnerWindow); 1405 RegisterActivityObserver(); 1406 } 1407 1408 void MediaRecorder::RegisterActivityObserver() { 1409 if (nsPIDOMWindowInner* window = GetOwnerWindow()) { 1410 mDocument = window->GetExtantDoc(); 1411 if (mDocument) { 1412 mDocument->RegisterActivityObserver( 1413 NS_ISUPPORTS_CAST(nsIDocumentActivity*, this)); 1414 } 1415 } 1416 } 1417 1418 void MediaRecorder::UnRegisterActivityObserver() { 1419 if (mDocument) { 1420 mDocument->UnregisterActivityObserver( 1421 NS_ISUPPORTS_CAST(nsIDocumentActivity*, this)); 1422 } 1423 } 1424 1425 void MediaRecorder::GetMimeType(nsString& aMimeType) { aMimeType = mMimeType; } 1426 1427 void MediaRecorder::Start(const Optional<uint32_t>& aTimeslice, 1428 ErrorResult& aResult) { 1429 LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this)); 1430 1431 InitializeDomExceptions(); 1432 1433 // When a MediaRecorder object’s start() method is invoked, the UA MUST run 1434 // the following steps: 1435 1436 // 1. Let recorder be the MediaRecorder object on which the method was 1437 // invoked. 1438 1439 // 2. Let timeslice be the method’s first argument, if provided, or undefined. 1440 TimeDuration timeslice = 1441 aTimeslice.WasPassed() 1442 ? TimeDuration::FromMilliseconds(aTimeslice.Value()) 1443 : TimeDuration::Forever(); 1444 1445 // 3. Let stream be the value of recorder’s stream attribute. 1446 1447 // 4. Let tracks be the set of live tracks in stream’s track set. 1448 nsTArray<RefPtr<MediaStreamTrack>> tracks; 1449 if (mStream) { 1450 mStream->GetTracks(tracks); 1451 } 1452 tracks.RemoveLastElements( 1453 tracks.end() - std::remove_if(tracks.begin(), tracks.end(), 1454 [](const auto& t) { return t->Ended(); })); 1455 1456 // 5. If the value of recorder’s state attribute is not inactive, throw an 1457 // InvalidStateError DOMException and abort these steps. 1458 if (mState != RecordingState::Inactive) { 1459 aResult.ThrowInvalidStateError( 1460 "The MediaRecorder has already been started"); 1461 return; 1462 } 1463 1464 // 6. If the isolation properties of stream disallow access from recorder, 1465 // throw a SecurityError DOMException and abort these steps. 1466 if (mStream) { 1467 RefPtr<nsIPrincipal> streamPrincipal = mStream->GetPrincipal(); 1468 if (!streamPrincipal) { 1469 // This is more or less part of the step 7, see below. 1470 aResult.ThrowNotSupportedError("The MediaStream is inactive"); 1471 return; 1472 } 1473 1474 if (!PrincipalSubsumes(this, streamPrincipal)) { 1475 aResult.ThrowSecurityError( 1476 "The MediaStream's isolation properties disallow access from " 1477 "MediaRecorder"); 1478 return; 1479 } 1480 } 1481 if (mAudioNode && !AudioNodePrincipalSubsumes(this, mAudioNode)) { 1482 LOG(LogLevel::Warning, 1483 ("MediaRecorder %p Start AudioNode principal check failed", this)); 1484 aResult.ThrowSecurityError( 1485 "The AudioNode's isolation properties disallow access from " 1486 "MediaRecorder"); 1487 return; 1488 } 1489 1490 // 7. If stream is inactive, throw a NotSupportedError DOMException and abort 1491 // these steps. 1492 if (mStream && !mStream->Active()) { 1493 aResult.ThrowNotSupportedError("The MediaStream is inactive"); 1494 return; 1495 } 1496 1497 // 8. If the [[ConstrainedMimeType]] slot specifies a media type, container, 1498 // or codec, then run the following sub steps: 1499 // 1. Constrain the configuration of recorder to the media type, container, 1500 // and codec specified in the [[ConstrainedMimeType]] slot. 1501 // 2. For each track in tracks, if the User Agent cannot record the track 1502 // using the current configuration, then throw a NotSupportedError 1503 // DOMException and abort all steps. 1504 Maybe<MediaContainerType> mime; 1505 if (mConstrainedMimeType.Length() > 0) { 1506 mime = MakeMediaContainerType(mConstrainedMimeType); 1507 MOZ_DIAGNOSTIC_ASSERT( 1508 mime, 1509 "Invalid media MIME type should have been caught by IsTypeSupported"); 1510 } 1511 for (const auto& track : tracks) { 1512 TypeSupport support = CanRecordWith(track, mime, mConstrainedMimeType); 1513 if (support != TypeSupport::Supported) { 1514 nsString id; 1515 track->GetId(id); 1516 aResult.ThrowNotSupportedError(nsPrintfCString( 1517 "%s track cannot be recorded: %s", 1518 track->AsAudioStreamTrack() ? "An audio" : "A video", 1519 TypeSupportToCString(support, mConstrainedMimeType).get())); 1520 return; 1521 } 1522 } 1523 if (mAudioNode) { 1524 TypeSupport support = CanRecordAudioTrackWith(mime, mConstrainedMimeType); 1525 if (support != TypeSupport::Supported) { 1526 aResult.ThrowNotSupportedError(nsPrintfCString( 1527 "An AudioNode cannot be recorded: %s", 1528 TypeSupportToCString(support, mConstrainedMimeType).get())); 1529 return; 1530 } 1531 } 1532 1533 // 9. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set 1534 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to 1535 // values the User Agent deems reasonable for the respective media types, 1536 // for recording all tracks in tracks, such that the sum of 1537 // videoBitsPerSecond and audioBitsPerSecond is close to the value of 1538 // recorder’s 1539 // [[ConstrainedBitsPerSecond]] slot. 1540 uint8_t numVideoTracks = 0; 1541 uint8_t numAudioTracks = 0; 1542 for (const auto& t : tracks) { 1543 if (t->AsVideoStreamTrack() && numVideoTracks < UINT8_MAX) { 1544 ++numVideoTracks; 1545 } else if (t->AsAudioStreamTrack() && numAudioTracks < UINT8_MAX) { 1546 ++numAudioTracks; 1547 } 1548 } 1549 if (mAudioNode) { 1550 MOZ_DIAGNOSTIC_ASSERT(!mStream); 1551 ++numAudioTracks; 1552 } 1553 if (mConstrainedBitsPerSecond) { 1554 SelectBitrates(*mConstrainedBitsPerSecond, numVideoTracks, 1555 &mVideoBitsPerSecond, numAudioTracks, &mAudioBitsPerSecond); 1556 } 1557 1558 // 10. Let videoBitrate be the value of recorder’s videoBitsPerSecond 1559 // attribute, and constrain the configuration of recorder to target an 1560 // aggregate bitrate of videoBitrate bits per second for all video tracks 1561 // recorder will be recording. videoBitrate is a hint for the encoder and 1562 // the value might be surpassed, not achieved, or only be achieved over a 1563 // long period of time. 1564 const uint32_t videoBitrate = mVideoBitsPerSecond; 1565 1566 // 11. Let audioBitrate be the value of recorder’s audioBitsPerSecond 1567 // attribute, and constrain the configuration of recorder to target an 1568 // aggregate bitrate of audioBitrate bits per second for all audio tracks 1569 // recorder will be recording. audioBitrate is a hint for the encoder and 1570 // the value might be surpassed, not achieved, or only be achieved over a 1571 // long period of time. 1572 const uint32_t audioBitrate = mAudioBitsPerSecond; 1573 1574 // 12. Constrain the configuration of recorder to encode using the BitrateMode 1575 // specified by the value of recorder’s audioBitrateMode attribute for all 1576 // audio tracks recorder will be recording. 1577 // -- NOT IMPLEMENTED 1578 1579 // 13. For each track in tracks, if the User Agent cannot record the track 1580 // using the current configuration, then throw a NotSupportedError 1581 // DOMException and abort these steps. 1582 if (numVideoTracks > 1) { 1583 aResult.ThrowNotSupportedError( 1584 "MediaRecorder does not support recording more than one video track"_ns); 1585 return; 1586 } 1587 if (numAudioTracks > 1) { 1588 aResult.ThrowNotSupportedError( 1589 "MediaRecorder does not support recording more than one audio track"_ns); 1590 return; 1591 } 1592 1593 // 14. Set recorder’s state to recording 1594 mState = RecordingState::Recording; 1595 1596 MediaRecorderReporter::AddMediaRecorder(this); 1597 // Start a session. 1598 mSessions.AppendElement(); 1599 mSessions.LastElement() = 1600 new Session(this, std::move(tracks), videoBitrate, audioBitrate); 1601 mSessions.LastElement()->Start(timeslice); 1602 } 1603 1604 void MediaRecorder::Stop(ErrorResult& aResult) { 1605 LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this)); 1606 MediaRecorderReporter::RemoveMediaRecorder(this); 1607 1608 // When a MediaRecorder object’s stop() method is invoked, the UA MUST run the 1609 // following steps: 1610 1611 // 1. Let recorder be the MediaRecorder object on which the method was 1612 // invoked. 1613 1614 // 2. If recorder’s state attribute is inactive, abort these steps. 1615 if (mState == RecordingState::Inactive) { 1616 return; 1617 } 1618 1619 // 3. Inactivate the recorder with recorder. 1620 Inactivate(); 1621 1622 // 4. Queue a task, using the DOM manipulation task source, that runs the 1623 // following steps: 1624 // 1. Stop gathering data. 1625 // 2. Let blob be the Blob of collected data so far, then fire a blob event 1626 // named dataavailable at recorder with blob. 1627 // 3. Fire an event named stop at recorder. 1628 MOZ_ASSERT(mSessions.Length() > 0); 1629 mSessions.LastElement()->Stop(); 1630 1631 // 5. return undefined. 1632 } 1633 1634 void MediaRecorder::Pause(ErrorResult& aResult) { 1635 LOG(LogLevel::Debug, ("MediaRecorder.Pause %p", this)); 1636 1637 // When a MediaRecorder object’s pause() method is invoked, the UA MUST run 1638 // the following steps: 1639 1640 // 1. If state is inactive, throw an InvalidStateError DOMException and abort 1641 // these steps. 1642 if (mState == RecordingState::Inactive) { 1643 aResult.ThrowInvalidStateError("The MediaRecorder is inactive"); 1644 return; 1645 } 1646 1647 // 2. If state is paused, abort these steps. 1648 if (mState == RecordingState::Paused) { 1649 return; 1650 } 1651 1652 // 3. Set state to paused, and queue a task, using the DOM manipulation task 1653 // source, that runs the following steps: 1654 mState = RecordingState::Paused; 1655 1656 // XXX - We pause synchronously pending spec issue 1657 // https://github.com/w3c/mediacapture-record/issues/131 1658 // 1. Stop gathering data into blob (but keep it available so that 1659 // recording can be resumed in the future). 1660 MOZ_ASSERT(!mSessions.IsEmpty()); 1661 mSessions.LastElement()->Pause(); 1662 1663 NS_DispatchToMainThread(NS_NewRunnableFunction( 1664 "MediaRecorder::Pause", [recorder = RefPtr<MediaRecorder>(this)] { 1665 // 2. Let target be the MediaRecorder context object. Fire an event 1666 // named pause at target. 1667 recorder->DispatchSimpleEvent(u"pause"_ns); 1668 })); 1669 1670 // 4. return undefined. 1671 } 1672 1673 void MediaRecorder::Resume(ErrorResult& aResult) { 1674 LOG(LogLevel::Debug, ("MediaRecorder.Resume %p", this)); 1675 1676 // When a MediaRecorder object’s resume() method is invoked, the UA MUST run 1677 // the following steps: 1678 1679 // 1. If state is inactive, throw an InvalidStateError DOMException and abort 1680 // these steps. 1681 if (mState == RecordingState::Inactive) { 1682 aResult.ThrowInvalidStateError("The MediaRecorder is inactive"); 1683 return; 1684 } 1685 1686 // 2. If state is recording, abort these steps. 1687 if (mState == RecordingState::Recording) { 1688 return; 1689 } 1690 1691 // 3. Set state to recording, and queue a task, using the DOM manipulation 1692 // task source, that runs the following steps: 1693 mState = RecordingState::Recording; 1694 1695 // XXX - We resume synchronously pending spec issue 1696 // https://github.com/w3c/mediacapture-record/issues/131 1697 // 1. Resume (or continue) gathering data into the current blob. 1698 MOZ_ASSERT(!mSessions.IsEmpty()); 1699 mSessions.LastElement()->Resume(); 1700 1701 NS_DispatchToMainThread(NS_NewRunnableFunction( 1702 "MediaRecorder::Resume", [recorder = RefPtr<MediaRecorder>(this)] { 1703 // 2. Let target be the MediaRecorder context object. Fire an event 1704 // named resume at target. 1705 recorder->DispatchSimpleEvent(u"resume"_ns); 1706 })); 1707 1708 // 4. return undefined. 1709 } 1710 1711 void MediaRecorder::RequestData(ErrorResult& aResult) { 1712 LOG(LogLevel::Debug, ("MediaRecorder.RequestData %p", this)); 1713 1714 // When a MediaRecorder object’s requestData() method is invoked, the UA MUST 1715 // run the following steps: 1716 1717 // 1. If state is inactive throw an InvalidStateError DOMException and 1718 // terminate these steps. Otherwise the UA MUST queue a task, using the DOM 1719 // manipulation task source, that runs the following steps: 1720 // 1. Let blob be the Blob of collected data so far and let target be the 1721 // MediaRecorder context object, then fire a blob event named 1722 // dataavailable at target with blob. (Note that blob will be empty if no 1723 // data has been gathered yet.) 1724 // 2. Create a new Blob and gather subsequent data into it. 1725 if (mState == RecordingState::Inactive) { 1726 aResult.ThrowInvalidStateError("The MediaRecorder is inactive"); 1727 return; 1728 } 1729 MOZ_ASSERT(mSessions.Length() > 0); 1730 mSessions.LastElement()->RequestData(); 1731 1732 // 2. return undefined. 1733 } 1734 1735 JSObject* MediaRecorder::WrapObject(JSContext* aCx, 1736 JS::Handle<JSObject*> aGivenProto) { 1737 return MediaRecorder_Binding::Wrap(aCx, this, aGivenProto); 1738 } 1739 1740 /* static */ 1741 already_AddRefed<MediaRecorder> MediaRecorder::Constructor( 1742 const GlobalObject& aGlobal, DOMMediaStream& aStream, 1743 const MediaRecorderOptions& aOptions, ErrorResult& aRv) { 1744 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = 1745 do_QueryInterface(aGlobal.GetAsSupports()); 1746 if (!ownerWindow) { 1747 aRv.Throw(NS_ERROR_FAILURE); 1748 return nullptr; 1749 } 1750 1751 // When the MediaRecorder() constructor is invoked, the User Agent MUST run 1752 // the following steps: 1753 1754 // 1. Let stream be the constructor’s first argument. 1755 1756 // 2. Let options be the constructor’s second argument. 1757 1758 // 3. If invoking is type supported with options’ mimeType member as its 1759 // argument returns false, throw a NotSupportedError DOMException and abort 1760 // these steps. 1761 TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType); 1762 if (support != TypeSupport::Supported) { 1763 // This catches also the empty string mimeType when support for any encoders 1764 // has been disabled. 1765 aRv.ThrowNotSupportedError( 1766 TypeSupportToCString(support, aOptions.mMimeType)); 1767 return nullptr; 1768 } 1769 1770 // 4. Let recorder be a newly constructed MediaRecorder object. 1771 RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow); 1772 1773 // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized 1774 // to the value of options' mimeType member. 1775 recorder->mConstrainedMimeType = aOptions.mMimeType; 1776 1777 // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot, 1778 // initialized to the value of options’ bitsPerSecond member, if it is 1779 // present, otherwise undefined. 1780 recorder->mConstrainedBitsPerSecond = 1781 aOptions.mBitsPerSecond.WasPassed() 1782 ? Some(aOptions.mBitsPerSecond.Value()) 1783 : Nothing(); 1784 1785 // 7. Initialize recorder’s stream attribute to stream. 1786 recorder->mStream = &aStream; 1787 1788 // 8. Initialize recorder’s mimeType attribute to the value of recorder’s 1789 // [[ConstrainedMimeType]] slot. 1790 recorder->mMimeType = recorder->mConstrainedMimeType; 1791 1792 // 9. Initialize recorder’s state attribute to inactive. 1793 recorder->mState = RecordingState::Inactive; 1794 1795 // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of 1796 // options’ videoBitsPerSecond member, if it is present. Otherwise, choose 1797 // a target value the User Agent deems reasonable for video. 1798 recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed() 1799 ? aOptions.mVideoBitsPerSecond.Value() 1800 : DEFAULT_VIDEO_BITRATE_BPS; 1801 1802 // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of 1803 // options’ audioBitsPerSecond member, if it is present. Otherwise, choose 1804 // a target value the User Agent deems reasonable for audio. 1805 recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed() 1806 ? aOptions.mAudioBitsPerSecond.Value() 1807 : DEFAULT_AUDIO_BITRATE_BPS; 1808 1809 // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set 1810 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to 1811 // values the User Agent deems reasonable for the respective media types, 1812 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close 1813 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot. 1814 if (recorder->mConstrainedBitsPerSecond) { 1815 SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1, 1816 &recorder->mVideoBitsPerSecond, 1, 1817 &recorder->mAudioBitsPerSecond); 1818 } 1819 1820 // 13. Return recorder. 1821 return recorder.forget(); 1822 } 1823 1824 /* static */ 1825 already_AddRefed<MediaRecorder> MediaRecorder::Constructor( 1826 const GlobalObject& aGlobal, AudioNode& aAudioNode, 1827 uint32_t aAudioNodeOutput, const MediaRecorderOptions& aOptions, 1828 ErrorResult& aRv) { 1829 // Allow recording from audio node only when pref is on. 1830 if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) { 1831 // Pretending that this constructor is not defined. 1832 aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("Argument 1", 1833 "MediaStream"); 1834 return nullptr; 1835 } 1836 1837 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = 1838 do_QueryInterface(aGlobal.GetAsSupports()); 1839 if (!ownerWindow) { 1840 aRv.Throw(NS_ERROR_FAILURE); 1841 return nullptr; 1842 } 1843 1844 // aAudioNodeOutput doesn't matter to destination node because it has no 1845 // output. 1846 if (aAudioNode.NumberOfOutputs() > 0 && 1847 aAudioNodeOutput >= aAudioNode.NumberOfOutputs()) { 1848 aRv.ThrowIndexSizeError("Invalid AudioNode output index"); 1849 return nullptr; 1850 } 1851 1852 // When the MediaRecorder() constructor is invoked, the User Agent MUST run 1853 // the following steps: 1854 1855 // 1. Let stream be the constructor’s first argument. (we'll let audioNode be 1856 // the first arg, and audioNodeOutput the second) 1857 1858 // 2. Let options be the constructor’s second argument. (we'll let options be 1859 // the third arg) 1860 1861 // 3. If invoking is type supported with options’ mimeType member as its 1862 // argument returns false, throw a NotSupportedError DOMException and abort 1863 // these steps. 1864 TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType); 1865 if (support != TypeSupport::Supported) { 1866 // This catches also the empty string mimeType when support for any encoders 1867 // has been disabled. 1868 aRv.ThrowNotSupportedError( 1869 TypeSupportToCString(support, aOptions.mMimeType)); 1870 return nullptr; 1871 } 1872 1873 // 4. Let recorder be a newly constructed MediaRecorder object. 1874 RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow); 1875 1876 // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized 1877 // to the value of options' mimeType member. 1878 recorder->mConstrainedMimeType = aOptions.mMimeType; 1879 1880 // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot, 1881 // initialized to the value of options’ bitsPerSecond member, if it is 1882 // present, otherwise undefined. 1883 recorder->mConstrainedBitsPerSecond = 1884 aOptions.mBitsPerSecond.WasPassed() 1885 ? Some(aOptions.mBitsPerSecond.Value()) 1886 : Nothing(); 1887 1888 // 7. Initialize recorder’s stream attribute to stream. (make that the 1889 // audioNode and audioNodeOutput equivalents) 1890 recorder->mAudioNode = &aAudioNode; 1891 recorder->mAudioNodeOutput = aAudioNodeOutput; 1892 1893 // 8. Initialize recorder’s mimeType attribute to the value of recorder’s 1894 // [[ConstrainedMimeType]] slot. 1895 recorder->mMimeType = recorder->mConstrainedMimeType; 1896 1897 // 9. Initialize recorder’s state attribute to inactive. 1898 recorder->mState = RecordingState::Inactive; 1899 1900 // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of 1901 // options’ videoBitsPerSecond member, if it is present. Otherwise, choose 1902 // a target value the User Agent deems reasonable for video. 1903 recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed() 1904 ? aOptions.mVideoBitsPerSecond.Value() 1905 : DEFAULT_VIDEO_BITRATE_BPS; 1906 1907 // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of 1908 // options’ audioBitsPerSecond member, if it is present. Otherwise, choose 1909 // a target value the User Agent deems reasonable for audio. 1910 recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed() 1911 ? aOptions.mAudioBitsPerSecond.Value() 1912 : DEFAULT_AUDIO_BITRATE_BPS; 1913 1914 // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set 1915 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to 1916 // values the User Agent deems reasonable for the respective media types, 1917 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close 1918 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot. 1919 if (recorder->mConstrainedBitsPerSecond) { 1920 SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1, 1921 &recorder->mVideoBitsPerSecond, 1, 1922 &recorder->mAudioBitsPerSecond); 1923 } 1924 1925 // 13. Return recorder. 1926 return recorder.forget(); 1927 } 1928 1929 /* static */ 1930 bool MediaRecorder::IsTypeSupported(GlobalObject& aGlobal, 1931 const nsAString& aMIMEType) { 1932 return MediaRecorder::IsTypeSupported(aMIMEType); 1933 } 1934 1935 /* static */ 1936 bool MediaRecorder::IsTypeSupported(const nsAString& aMIMEType) { 1937 return IsTypeSupportedImpl(aMIMEType) == TypeSupport::Supported; 1938 } 1939 1940 nsresult MediaRecorder::CreateAndDispatchBlobEvent(BlobImpl* aBlobImpl) { 1941 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread"); 1942 1943 if (!GetOwnerGlobal()) { 1944 // This MediaRecorder has been disconnected in the meantime. 1945 return NS_ERROR_FAILURE; 1946 } 1947 1948 RefPtr<Blob> blob = Blob::Create(GetOwnerGlobal(), aBlobImpl); 1949 if (NS_WARN_IF(!blob)) { 1950 return NS_ERROR_FAILURE; 1951 } 1952 1953 BlobEventInit init; 1954 init.mBubbles = false; 1955 init.mCancelable = false; 1956 init.mData = blob; 1957 1958 RefPtr<BlobEvent> event = 1959 BlobEvent::Constructor(this, u"dataavailable"_ns, init); 1960 event->SetTrusted(true); 1961 ErrorResult rv; 1962 DispatchEvent(*event, rv); 1963 return rv.StealNSResult(); 1964 } 1965 1966 void MediaRecorder::DispatchSimpleEvent(const nsAString& aStr) { 1967 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread"); 1968 nsresult rv = CheckCurrentGlobalCorrectness(); 1969 if (NS_FAILED(rv)) { 1970 return; 1971 } 1972 1973 rv = DOMEventTargetHelper::DispatchTrustedEvent(aStr); 1974 if (NS_FAILED(rv)) { 1975 LOG(LogLevel::Error, 1976 ("MediaRecorder.DispatchSimpleEvent: DispatchTrustedEvent failed %p", 1977 this)); 1978 NS_ERROR("Failed to dispatch the event!!!"); 1979 } 1980 } 1981 1982 void MediaRecorder::NotifyError(nsresult aRv) { 1983 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread"); 1984 nsresult rv = CheckCurrentGlobalCorrectness(); 1985 if (NS_FAILED(rv)) { 1986 return; 1987 } 1988 MediaRecorderErrorEventInit init; 1989 init.mBubbles = false; 1990 init.mCancelable = false; 1991 // These DOMExceptions have been created earlier so they can contain stack 1992 // traces. We attach the appropriate one here to be fired. We should have 1993 // exceptions here, but defensively check. 1994 switch (aRv) { 1995 case NS_ERROR_DOM_SECURITY_ERR: 1996 if (!mSecurityDomException) { 1997 LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: " 1998 "mSecurityDomException was not initialized")); 1999 mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR); 2000 } 2001 init.mError = std::move(mSecurityDomException); 2002 break; 2003 default: 2004 if (mOtherDomException && aRv == mOtherDomException->GetResult()) { 2005 LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: " 2006 "mOtherDomException being fired for aRv: %X", 2007 uint32_t(aRv))); 2008 init.mError = std::move(mOtherDomException); 2009 break; 2010 } 2011 if (!mUnknownDomException) { 2012 LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: " 2013 "mUnknownDomException was not initialized")); 2014 mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR); 2015 } 2016 LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: " 2017 "mUnknownDomException being fired for aRv: %X", 2018 uint32_t(aRv))); 2019 init.mError = std::move(mUnknownDomException); 2020 break; 2021 } 2022 2023 RefPtr<MediaRecorderErrorEvent> event = 2024 MediaRecorderErrorEvent::Constructor(this, u"error"_ns, init); 2025 event->SetTrusted(true); 2026 2027 IgnoredErrorResult res; 2028 DispatchEvent(*event, res); 2029 if (res.Failed()) { 2030 NS_ERROR("Failed to dispatch the error event!!!"); 2031 } 2032 } 2033 2034 void MediaRecorder::RemoveSession(Session* aSession) { 2035 LOG(LogLevel::Debug, ("MediaRecorder.RemoveSession (%p)", aSession)); 2036 mSessions.RemoveElement(aSession); 2037 } 2038 2039 void MediaRecorder::NotifyOwnerDocumentActivityChanged() { 2040 nsPIDOMWindowInner* window = GetOwnerWindow(); 2041 NS_ENSURE_TRUE_VOID(window); 2042 Document* doc = window->GetExtantDoc(); 2043 NS_ENSURE_TRUE_VOID(doc); 2044 2045 LOG(LogLevel::Debug, ("MediaRecorder %p NotifyOwnerDocumentActivityChanged " 2046 "IsActive=%d, " 2047 "IsVisible=%d, ", 2048 this, doc->IsActive(), doc->IsVisible())); 2049 if (!doc->IsActive() || !doc->IsVisible()) { 2050 // Stop the session. 2051 ErrorResult result; 2052 Stop(result); 2053 result.SuppressException(); 2054 } 2055 } 2056 2057 void MediaRecorder::Inactivate() { 2058 LOG(LogLevel::Debug, ("MediaRecorder.Inactivate %p", this)); 2059 // The Inactivate the recorder algorithm given a recorder, is as follows: 2060 2061 // 1. Set recorder’s mimeType attribute to the value of the 2062 // [[ConstrainedMimeType]] slot. 2063 mMimeType = mConstrainedMimeType; 2064 2065 // 2. Set recorder’s state attribute to inactive. 2066 mState = RecordingState::Inactive; 2067 2068 // 3. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set 2069 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to 2070 // values the User Agent deems reasonable for the respective media types, 2071 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close 2072 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot. 2073 if (mConstrainedBitsPerSecond) { 2074 SelectBitrates(*mConstrainedBitsPerSecond, 1, &mVideoBitsPerSecond, 1, 2075 &mAudioBitsPerSecond); 2076 } 2077 } 2078 2079 void MediaRecorder::InitializeDomExceptions() { 2080 mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR); 2081 mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR); 2082 } 2083 2084 RefPtr<MediaRecorder::SizeOfPromise> MediaRecorder::SizeOfExcludingThis( 2085 mozilla::MallocSizeOf aMallocSizeOf) { 2086 MOZ_ASSERT(NS_IsMainThread()); 2087 2088 // The return type of a chained MozPromise cannot be changed, so we create a 2089 // holder for our desired return type and resolve that from All()->Then(). 2090 auto holder = MakeRefPtr<Refcountable<MozPromiseHolder<SizeOfPromise>>>(); 2091 RefPtr<SizeOfPromise> promise = holder->Ensure(__func__); 2092 2093 nsTArray<RefPtr<SizeOfPromise>> promises(mSessions.Length()); 2094 for (const RefPtr<Session>& session : mSessions) { 2095 promises.AppendElement(session->SizeOfExcludingThis(aMallocSizeOf)); 2096 } 2097 2098 SizeOfPromise::All(GetCurrentSerialEventTarget(), promises) 2099 ->Then( 2100 GetCurrentSerialEventTarget(), __func__, 2101 [holder](const nsTArray<size_t>& sizes) { 2102 size_t total = 0; 2103 for (const size_t& size : sizes) { 2104 total += size; 2105 } 2106 holder->Resolve(total, __func__); 2107 }, 2108 []() { MOZ_CRASH("Unexpected reject"); }); 2109 2110 return promise; 2111 } 2112 2113 StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance; 2114 2115 } // namespace mozilla::dom 2116 2117 #undef LOG