Backed out 15 changesets (bug 1852806) for causing mda failures on test_video_low_pow...
[gecko.git] / dom / media / MediaRecorder.cpp
blob9f8c2ad4256e3a6c0183a4113d1fb5cc6fe7b957
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/. */
7 #include "MediaRecorder.h"
9 #include "AudioNodeEngine.h"
10 #include "AudioNodeTrack.h"
11 #include "DOMMediaStream.h"
12 #include "MediaDecoder.h"
13 #include "MediaEncoder.h"
14 #include "MediaTrackGraphImpl.h"
15 #include "VideoUtils.h"
16 #include "mozilla/DOMEventTargetHelper.h"
17 #include "mozilla/dom/AudioStreamTrack.h"
18 #include "mozilla/dom/BlobEvent.h"
19 #include "mozilla/dom/EmptyBlobImpl.h"
20 #include "mozilla/dom/File.h"
21 #include "mozilla/dom/MediaRecorderErrorEvent.h"
22 #include "mozilla/dom/VideoStreamTrack.h"
23 #include "mozilla/media/MediaUtils.h"
24 #include "mozilla/MemoryReporting.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/StaticPtr.h"
27 #include "mozilla/TaskQueue.h"
28 #include "nsContentTypeParser.h"
29 #include "nsContentUtils.h"
30 #include "nsDocShell.h"
31 #include "nsError.h"
32 #include "mozilla/dom/Document.h"
33 #include "nsIPrincipal.h"
34 #include "nsIScriptError.h"
35 #include "nsMimeTypes.h"
36 #include "nsProxyRelease.h"
37 #include "nsServiceManagerUtils.h"
38 #include "nsTArray.h"
40 mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
41 #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
43 constexpr int MIN_VIDEO_BITRATE_BPS = 10e3; // 10kbps
44 constexpr int DEFAULT_VIDEO_BITRATE_BPS = 2500e3; // 2.5Mbps
45 constexpr int MAX_VIDEO_BITRATE_BPS = 100e6; // 100Mbps
47 constexpr int MIN_AUDIO_BITRATE_BPS = 500; // 500bps
48 constexpr int DEFAULT_AUDIO_BITRATE_BPS = 128e3; // 128kbps
49 constexpr int MAX_AUDIO_BITRATE_BPS = 512e3; // 512kbps
51 namespace mozilla::dom {
53 using namespace mozilla::media;
55 /**
56 * MediaRecorderReporter measures memory being used by the Media Recorder.
58 * It is a singleton reporter and the single class object lives as long as at
59 * least one Recorder is registered. In MediaRecorder, the reporter is
60 * unregistered when it is destroyed.
62 class MediaRecorderReporter final : public nsIMemoryReporter {
63 public:
64 static void AddMediaRecorder(MediaRecorder* aRecorder) {
65 if (!sUniqueInstance) {
66 sUniqueInstance = MakeAndAddRef<MediaRecorderReporter>();
67 RegisterWeakAsyncMemoryReporter(sUniqueInstance);
69 sUniqueInstance->mRecorders.AppendElement(aRecorder);
72 static void RemoveMediaRecorder(MediaRecorder* aRecorder) {
73 if (!sUniqueInstance) {
74 return;
77 sUniqueInstance->mRecorders.RemoveElement(aRecorder);
78 if (sUniqueInstance->mRecorders.IsEmpty()) {
79 UnregisterWeakMemoryReporter(sUniqueInstance);
80 sUniqueInstance = nullptr;
84 NS_DECL_THREADSAFE_ISUPPORTS
86 MediaRecorderReporter() = default;
88 NS_IMETHOD
89 CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
90 bool aAnonymize) override {
91 nsTArray<RefPtr<MediaRecorder::SizeOfPromise>> promises;
92 for (const RefPtr<MediaRecorder>& recorder : mRecorders) {
93 promises.AppendElement(recorder->SizeOfExcludingThis(MallocSizeOf));
96 nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
97 nsCOMPtr<nsISupports> data = aData;
98 MediaRecorder::SizeOfPromise::All(GetCurrentSerialEventTarget(), promises)
99 ->Then(
100 GetCurrentSerialEventTarget(), __func__,
101 [handleReport, data](const nsTArray<size_t>& sizes) {
102 nsCOMPtr<nsIMemoryReporterManager> manager =
103 do_GetService("@mozilla.org/memory-reporter-manager;1");
104 if (!manager) {
105 return;
108 size_t sum = 0;
109 for (const size_t& size : sizes) {
110 sum += size;
113 handleReport->Callback(""_ns, "explicit/media/recorder"_ns,
114 KIND_HEAP, UNITS_BYTES, sum,
115 "Memory used by media recorder."_ns, data);
117 manager->EndReport();
119 [](size_t) { MOZ_CRASH("Unexpected reject"); });
121 return NS_OK;
124 private:
125 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
127 virtual ~MediaRecorderReporter() {
128 MOZ_ASSERT(mRecorders.IsEmpty(), "All recorders must have been removed");
131 static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
133 nsTArray<RefPtr<MediaRecorder>> mRecorders;
135 NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
137 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
139 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
140 DOMEventTargetHelper)
141 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStream)
142 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
143 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOtherDomException)
144 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityDomException)
145 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnknownDomException)
146 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
147 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
149 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
150 DOMEventTargetHelper)
151 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStream)
152 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
153 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOtherDomException)
154 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityDomException)
155 NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnknownDomException)
156 tmp->UnRegisterActivityObserver();
157 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
158 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
160 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder)
161 NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
162 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
164 NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
165 NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
167 namespace {
168 bool PrincipalSubsumes(MediaRecorder* aRecorder, nsIPrincipal* aPrincipal) {
169 if (!aRecorder->GetOwner()) {
170 return false;
172 nsCOMPtr<Document> doc = aRecorder->GetOwner()->GetExtantDoc();
173 if (!doc) {
174 return false;
176 if (!aPrincipal) {
177 return false;
179 bool subsumes;
180 if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
181 return false;
183 return subsumes;
186 bool MediaStreamTracksPrincipalSubsumes(
187 MediaRecorder* aRecorder,
188 const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) {
189 nsCOMPtr<nsIPrincipal> principal = nullptr;
190 for (const auto& track : aTracks) {
191 nsContentUtils::CombineResourcePrincipals(&principal,
192 track->GetPrincipal());
194 return PrincipalSubsumes(aRecorder, principal);
197 bool AudioNodePrincipalSubsumes(MediaRecorder* aRecorder,
198 AudioNode* aAudioNode) {
199 MOZ_ASSERT(aAudioNode);
200 Document* doc =
201 aAudioNode->GetOwner() ? aAudioNode->GetOwner()->GetExtantDoc() : nullptr;
202 nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
203 return PrincipalSubsumes(aRecorder, principal);
206 // This list is sorted so that lesser failures are later, so that
207 // IsTypeSupportedImpl() can report the error from audio or video types that
208 // is closer to being supported.
209 enum class TypeSupport {
210 MediaTypeInvalid,
211 NoVideoWithAudioType,
212 ContainersDisabled,
213 CodecsDisabled,
214 ContainerUnsupported,
215 CodecUnsupported,
216 CodecDuplicated,
217 Supported,
220 nsCString TypeSupportToCString(TypeSupport aSupport,
221 const nsAString& aMimeType) {
222 nsAutoCString mime = NS_ConvertUTF16toUTF8(aMimeType);
223 switch (aSupport) {
224 case TypeSupport::Supported:
225 return nsPrintfCString("%s is supported", mime.get());
226 case TypeSupport::MediaTypeInvalid:
227 return nsPrintfCString("%s is not a valid media type", mime.get());
228 case TypeSupport::NoVideoWithAudioType:
229 return nsPrintfCString(
230 "Video cannot be recorded with %s as it is an audio type",
231 mime.get());
232 case TypeSupport::ContainersDisabled:
233 return "All containers are disabled"_ns;
234 case TypeSupport::CodecsDisabled:
235 return "All codecs are disabled"_ns;
236 case TypeSupport::ContainerUnsupported:
237 return nsPrintfCString("%s indicates an unsupported container",
238 mime.get());
239 case TypeSupport::CodecUnsupported:
240 return nsPrintfCString("%s indicates an unsupported codec", mime.get());
241 case TypeSupport::CodecDuplicated:
242 return nsPrintfCString("%s contains the same codec multiple times",
243 mime.get());
244 default:
245 MOZ_ASSERT_UNREACHABLE("Unknown TypeSupport");
246 return "Unknown error"_ns;
250 TypeSupport CanRecordAudioTrackWith(const Maybe<MediaContainerType>& aMimeType,
251 const nsAString& aMimeTypeString) {
252 if (aMimeTypeString.IsEmpty()) {
253 // For the empty string we just need to check whether we have support for an
254 // audio container and an audio codec.
255 if (!MediaEncoder::IsWebMEncoderEnabled() &&
256 !MediaDecoder::IsOggEnabled()) {
257 // No container support for audio.
258 return TypeSupport::ContainersDisabled;
261 if (!MediaDecoder::IsOpusEnabled()) {
262 // No codec support for audio.
263 return TypeSupport::CodecsDisabled;
266 return TypeSupport::Supported;
269 if (!aMimeType) {
270 // A mime type string was set, but it couldn't be parsed to a valid
271 // MediaContainerType.
272 return TypeSupport::MediaTypeInvalid;
275 if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM) &&
276 aMimeType->Type() != MEDIAMIMETYPE(AUDIO_WEBM) &&
277 aMimeType->Type() != MEDIAMIMETYPE(AUDIO_OGG)) {
278 // Any currently supported container can record audio.
279 return TypeSupport::ContainerUnsupported;
282 if (aMimeType->Type() == MEDIAMIMETYPE(VIDEO_WEBM) &&
283 !MediaEncoder::IsWebMEncoderEnabled()) {
284 return TypeSupport::ContainerUnsupported;
287 if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM) &&
288 !MediaEncoder::IsWebMEncoderEnabled()) {
289 return TypeSupport::ContainerUnsupported;
292 if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_OGG) &&
293 !MediaDecoder::IsOggEnabled()) {
294 return TypeSupport::ContainerUnsupported;
297 if (!MediaDecoder::IsOpusEnabled()) {
298 return TypeSupport::CodecUnsupported;
301 if (!aMimeType->ExtendedType().HaveCodecs()) {
302 // No codecs constrained, we can pick opus.
303 return TypeSupport::Supported;
306 size_t opus = 0;
307 size_t unknown = 0;
308 for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
309 // Ignore video codecs.
310 if (codec.EqualsLiteral("vp8")) {
311 continue;
313 if (codec.EqualsLiteral("vp8.0")) {
314 continue;
316 if (codec.EqualsLiteral("opus")) {
317 // All containers support opus
318 opus++;
319 continue;
321 unknown++;
324 if (unknown > 0) {
325 // Unsupported codec.
326 return TypeSupport::CodecUnsupported;
329 if (opus == 0) {
330 // Codecs specified but not opus. Unsupported for audio.
331 return TypeSupport::CodecUnsupported;
334 if (opus > 1) {
335 // Opus specified more than once. Bad form.
336 return TypeSupport::CodecDuplicated;
339 return TypeSupport::Supported;
342 TypeSupport CanRecordVideoTrackWith(const Maybe<MediaContainerType>& aMimeType,
343 const nsAString& aMimeTypeString) {
344 if (aMimeTypeString.IsEmpty()) {
345 // For the empty string we just need to check whether we have support for a
346 // video container and a video codec. The VP8 encoder is always available.
347 if (!MediaEncoder::IsWebMEncoderEnabled()) {
348 // No container support for video.
349 return TypeSupport::ContainersDisabled;
352 return TypeSupport::Supported;
355 if (!aMimeType) {
356 // A mime type string was set, but it couldn't be parsed to a valid
357 // MediaContainerType.
358 return TypeSupport::MediaTypeInvalid;
361 if (!aMimeType->Type().HasVideoMajorType()) {
362 return TypeSupport::NoVideoWithAudioType;
365 if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM)) {
366 return TypeSupport::ContainerUnsupported;
369 if (!MediaEncoder::IsWebMEncoderEnabled()) {
370 return TypeSupport::ContainerUnsupported;
373 if (!aMimeType->ExtendedType().HaveCodecs()) {
374 // No codecs constrained, we can pick vp8.
375 return TypeSupport::Supported;
378 size_t vp8 = 0;
379 size_t unknown = 0;
380 for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
381 if (codec.EqualsLiteral("opus")) {
382 // Ignore audio codecs.
383 continue;
385 if (codec.EqualsLiteral("vp8")) {
386 vp8++;
387 continue;
389 if (codec.EqualsLiteral("vp8.0")) {
390 vp8++;
391 continue;
393 unknown++;
396 if (unknown > 0) {
397 // Unsupported codec.
398 return TypeSupport::CodecUnsupported;
401 if (vp8 == 0) {
402 // Codecs specified but not vp8. Unsupported for video.
403 return TypeSupport::CodecUnsupported;
406 if (vp8 > 1) {
407 // Vp8 specified more than once. Bad form.
408 return TypeSupport::CodecDuplicated;
411 return TypeSupport::Supported;
414 TypeSupport CanRecordWith(MediaStreamTrack* aTrack,
415 const Maybe<MediaContainerType>& aMimeType,
416 const nsAString& aMimeTypeString) {
417 if (aTrack->AsAudioStreamTrack()) {
418 return CanRecordAudioTrackWith(aMimeType, aMimeTypeString);
421 if (aTrack->AsVideoStreamTrack()) {
422 return CanRecordVideoTrackWith(aMimeType, aMimeTypeString);
425 MOZ_CRASH("Unexpected track type");
428 TypeSupport IsTypeSupportedImpl(const nsAString& aMIMEType) {
429 if (aMIMEType.IsEmpty()) {
430 // Lie and return true even if no container/codec support is enabled,
431 // because the spec mandates it.
432 return TypeSupport::Supported;
434 Maybe<MediaContainerType> mime = MakeMediaContainerType(aMIMEType);
435 TypeSupport audioSupport = CanRecordAudioTrackWith(mime, aMIMEType);
436 TypeSupport videoSupport = CanRecordVideoTrackWith(mime, aMIMEType);
437 return std::max(audioSupport, videoSupport);
440 nsString SelectMimeType(bool aHasVideo, bool aHasAudio,
441 const nsString& aConstrainedMimeType) {
442 MOZ_ASSERT(aHasVideo || aHasAudio);
444 Maybe<MediaContainerType> constrainedType =
445 MakeMediaContainerType(aConstrainedMimeType);
447 // If we are recording video, Start() should have rejected any non-video mime
448 // types.
449 MOZ_ASSERT_IF(constrainedType && aHasVideo,
450 constrainedType->Type().HasVideoMajorType());
451 // IsTypeSupported() rejects application mime types.
452 MOZ_ASSERT_IF(constrainedType,
453 !constrainedType->Type().HasApplicationMajorType());
455 nsString result;
456 if (constrainedType && constrainedType->ExtendedType().HaveCodecs()) {
457 // The constrained mime type is fully defined (it has codecs!). No need to
458 // select anything.
459 CopyUTF8toUTF16(constrainedType->OriginalString(), result);
460 } else {
461 // There is no constrained mime type, or there is and it is not fully
462 // defined but still valid. Select what's missing, so that we have major
463 // type, container and codecs.
465 // If there is a constrained mime type it should not have codecs defined,
466 // because then it is fully defined and used unchanged (covered earlier).
467 MOZ_ASSERT_IF(constrainedType,
468 !constrainedType->ExtendedType().HaveCodecs());
470 nsCString majorType;
472 if (constrainedType) {
473 // There is a constrained type. It has both major type and container in
474 // order to be valid. Use them as is.
475 majorType = constrainedType->Type().AsString();
476 } else if (aHasVideo) {
477 majorType = nsLiteralCString(VIDEO_WEBM);
478 } else {
479 majorType = nsLiteralCString(AUDIO_OGG);
483 nsCString codecs;
485 if (aHasVideo && aHasAudio) {
486 codecs = "\"vp8, opus\""_ns;
487 } else if (aHasVideo) {
488 codecs = "vp8"_ns;
489 } else {
490 codecs = "opus"_ns;
493 result = NS_ConvertUTF8toUTF16(
494 nsPrintfCString("%s; codecs=%s", majorType.get(), codecs.get()));
497 MOZ_ASSERT_IF(aHasAudio,
498 CanRecordAudioTrackWith(MakeMediaContainerType(result),
499 result) == TypeSupport::Supported);
500 MOZ_ASSERT_IF(aHasVideo,
501 CanRecordVideoTrackWith(MakeMediaContainerType(result),
502 result) == TypeSupport::Supported);
503 return result;
506 void SelectBitrates(uint32_t aBitsPerSecond, uint8_t aNumVideoTracks,
507 uint32_t* aOutVideoBps, uint8_t aNumAudioTracks,
508 uint32_t* aOutAudioBps) {
509 uint32_t vbps = 0;
510 uint32_t abps = 0;
512 const uint32_t minVideoBps = MIN_VIDEO_BITRATE_BPS * aNumVideoTracks;
513 const uint32_t maxVideoBps = MAX_VIDEO_BITRATE_BPS * aNumVideoTracks;
515 const uint32_t minAudioBps = MIN_AUDIO_BITRATE_BPS * aNumAudioTracks;
516 const uint32_t maxAudioBps = MAX_AUDIO_BITRATE_BPS * aNumAudioTracks;
518 if (aNumVideoTracks == 0) {
519 MOZ_DIAGNOSTIC_ASSERT(aNumAudioTracks > 0);
520 abps = std::min(maxAudioBps, std::max(minAudioBps, aBitsPerSecond));
521 } else if (aNumAudioTracks == 0) {
522 vbps = std::min(maxVideoBps, std::max(minVideoBps, aBitsPerSecond));
523 } else {
524 // Scale the bits so that video gets 20 times the bits of audio.
525 // Since we must account for varying number of tracks of each type we weight
526 // them by type; video = weight 20, audio = weight 1.
527 const uint32_t videoWeight = aNumVideoTracks * 20;
528 const uint32_t audioWeight = aNumAudioTracks;
529 const uint32_t totalWeights = audioWeight + videoWeight;
530 const uint32_t videoBitrate =
531 uint64_t(aBitsPerSecond) * videoWeight / totalWeights;
532 const uint32_t audioBitrate =
533 uint64_t(aBitsPerSecond) * audioWeight / totalWeights;
534 vbps = std::min(maxVideoBps, std::max(minVideoBps, videoBitrate));
535 abps = std::min(maxAudioBps, std::max(minAudioBps, audioBitrate));
538 *aOutVideoBps = vbps;
539 *aOutAudioBps = abps;
541 } // namespace
544 * Session is an object to represent a single recording event.
545 * In original design, all recording context is stored in MediaRecorder, which
546 * causes a problem if someone calls MediaRecorder::Stop and
547 * MediaRecorder::Start quickly. To prevent blocking main thread, media encoding
548 * is executed in a second thread, named encoder thread. For the same reason, we
549 * do not await encoder thread shutdown in MediaRecorder::Stop.
550 * If someone calls MediaRecorder::Start before encoder thread shutdown, the
551 * same recording context in MediaRecorder might be accessed by two distinct
552 * encoder threads, which would be racy. With the recording context, including
553 * the encoder thread, in a Session object the problem is solved.
555 * Lifetime of MediaRecorder and Session objects.
556 * 1) MediaRecorder creates a Session in MediaRecorder::Start() and holds
557 * a reference to it. Then the Session registers itself to a ShutdownBlocker
558 * and also holds a reference to MediaRecorder.
559 * Therefore, the reference dependency in gecko is:
560 * ShutdownBlocker -> Session <-> MediaRecorder, note that there is a cycle
561 * reference between Session and MediaRecorder.
562 * 2) A Session is destroyed after Session::DoSessionEndTask() has been called
563 * _and_ all encoded media data has been passed to OnDataAvailable handler.
564 * In some cases the encoded media can be discarded before being passed to
565 * the OnDataAvailable handler.
566 * 3) Session::DoSessionEndTask is called by an application through
567 * MediaRecorder::Stop(), from a MediaEncoder Shutdown notification, from the
568 * document going inactive or invisible, or from the ShutdownBlocker.
570 class MediaRecorder::Session : public PrincipalChangeObserver<MediaStreamTrack>,
571 public DOMMediaStream::TrackListener {
572 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Session)
574 struct TrackTypeComparator {
575 enum Type {
576 AUDIO,
577 VIDEO,
579 static bool Equals(const RefPtr<MediaStreamTrack>& aTrack, Type aType) {
580 return (aType == AUDIO && aTrack->AsAudioStreamTrack()) ||
581 (aType == VIDEO && aTrack->AsVideoStreamTrack());
585 public:
586 Session(MediaRecorder* aRecorder,
587 nsTArray<RefPtr<MediaStreamTrack>> aMediaStreamTracks,
588 uint32_t aVideoBitsPerSecond, uint32_t aAudioBitsPerSecond)
589 : mRecorder(aRecorder),
590 mMediaStreamTracks(std::move(aMediaStreamTracks)),
591 mMainThread(mRecorder->GetOwner()->EventTargetFor(TaskCategory::Other)),
592 mMimeType(SelectMimeType(
593 mMediaStreamTracks.Contains(TrackTypeComparator::VIDEO,
594 TrackTypeComparator()),
595 mRecorder->mAudioNode ||
596 mMediaStreamTracks.Contains(TrackTypeComparator::AUDIO,
597 TrackTypeComparator()),
598 mRecorder->mConstrainedMimeType)),
599 mVideoBitsPerSecond(aVideoBitsPerSecond),
600 mAudioBitsPerSecond(aAudioBitsPerSecond),
601 mStartTime(TimeStamp::Now()),
602 mRunningState(RunningState::Idling) {
603 MOZ_ASSERT(NS_IsMainThread());
604 Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1);
607 void PrincipalChanged(MediaStreamTrack* aTrack) override {
608 NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
609 "Principal changed for unrecorded track");
610 if (!MediaStreamTracksPrincipalSubsumes(mRecorder, mMediaStreamTracks)) {
611 DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
615 void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
616 LOG(LogLevel::Warning,
617 ("Session.NotifyTrackAdded %p Raising error due to track set change",
618 this));
619 // There's a chance we have a sensible JS stack here.
620 if (!mRecorder->mOtherDomException) {
621 mRecorder->mOtherDomException = DOMException::Create(
622 NS_ERROR_DOM_INVALID_MODIFICATION_ERR,
623 "An attempt was made to add a track to the recorded MediaStream "
624 "during the recording"_ns);
626 DoSessionEndTask(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
629 void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
630 if (aTrack->Ended()) {
631 // TrackEncoder will pickup tracks that end itself.
632 return;
634 LOG(LogLevel::Warning,
635 ("Session.NotifyTrackRemoved %p Raising error due to track set change",
636 this));
637 // There's a chance we have a sensible JS stack here.
638 if (!mRecorder->mOtherDomException) {
639 mRecorder->mOtherDomException = DOMException::Create(
640 NS_ERROR_DOM_INVALID_MODIFICATION_ERR,
641 "An attempt was made to remove a track from the recorded MediaStream "
642 "during the recording"_ns);
644 DoSessionEndTask(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
647 void Start(TimeDuration aTimeslice) {
648 LOG(LogLevel::Debug, ("Session.Start %p", this));
649 MOZ_ASSERT(NS_IsMainThread());
651 if (mRecorder->mStream) {
652 // The TrackListener reports back when tracks are added or removed from
653 // the MediaStream.
654 mMediaStream = mRecorder->mStream;
655 mMediaStream->RegisterTrackListener(this);
657 uint8_t trackTypes = 0;
658 for (const auto& track : mMediaStreamTracks) {
659 if (track->AsAudioStreamTrack()) {
660 trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
661 } else if (track->AsVideoStreamTrack()) {
662 trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
663 } else {
664 MOZ_CRASH("Unexpected track type");
668 for (const auto& t : mMediaStreamTracks) {
669 t->AddPrincipalChangeObserver(this);
672 LOG(LogLevel::Debug, ("Session.Start track types = (%d)", trackTypes));
673 InitEncoder(trackTypes, mMediaStreamTracks[0]->Graph()->GraphRate(),
674 aTimeslice);
675 return;
678 if (mRecorder->mAudioNode) {
679 TrackRate trackRate =
680 mRecorder->mAudioNode->Context()->Graph()->GraphRate();
682 // Web Audio node has only audio.
683 InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate, aTimeslice);
684 return;
687 MOZ_ASSERT(false, "Unknown source");
690 void Stop() {
691 LOG(LogLevel::Debug, ("Session.Stop %p", this));
692 MOZ_ASSERT(NS_IsMainThread());
694 if (mEncoder) {
695 mEncoder->DisconnectTracks();
698 // Remove main thread state added in Start().
699 if (mMediaStream) {
700 mMediaStream->UnregisterTrackListener(this);
701 mMediaStream = nullptr;
705 for (const auto& track : mMediaStreamTracks) {
706 track->RemovePrincipalChangeObserver(this);
710 if (mRunningState.isOk() &&
711 mRunningState.inspect() == RunningState::Idling) {
712 LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this));
713 // End the Session directly if there is no encoder.
714 DoSessionEndTask(NS_OK);
715 } else if (mRunningState.isOk() &&
716 (mRunningState.inspect() == RunningState::Starting ||
717 mRunningState.inspect() == RunningState::Running)) {
718 if (mRunningState.inspect() == RunningState::Starting) {
719 // The MediaEncoder might not report started, but by spec we must fire
720 // "start".
721 mStartedListener.DisconnectIfExists();
722 NS_DispatchToMainThread(NewRunnableMethod(
723 "MediaRecorder::Session::Stop", this, &Session::OnStarted));
725 mRunningState = RunningState::Stopping;
729 void Pause() {
730 LOG(LogLevel::Debug, ("Session.Pause"));
731 MOZ_ASSERT(NS_IsMainThread());
732 MOZ_ASSERT_IF(mRunningState.isOk(),
733 mRunningState.unwrap() != RunningState::Idling);
734 if (mRunningState.isErr() ||
735 mRunningState.unwrap() == RunningState::Stopping ||
736 mRunningState.unwrap() == RunningState::Stopped) {
737 return;
739 MOZ_ASSERT(mEncoder);
740 mEncoder->Suspend();
743 void Resume() {
744 LOG(LogLevel::Debug, ("Session.Resume"));
745 MOZ_ASSERT(NS_IsMainThread());
746 MOZ_ASSERT_IF(mRunningState.isOk(),
747 mRunningState.unwrap() != RunningState::Idling);
748 if (mRunningState.isErr() ||
749 mRunningState.unwrap() == RunningState::Stopping ||
750 mRunningState.unwrap() == RunningState::Stopped) {
751 return;
753 MOZ_ASSERT(mEncoder);
754 mEncoder->Resume();
757 void RequestData() {
758 LOG(LogLevel::Debug, ("Session.RequestData"));
759 MOZ_ASSERT(NS_IsMainThread());
760 MOZ_ASSERT(mEncoder);
762 InvokeAsync(mEncoderThread, mEncoder.get(), __func__,
763 &MediaEncoder::RequestData)
764 ->Then(
765 mMainThread, __func__,
766 [this, self = RefPtr<Session>(this)](
767 const MediaEncoder::BlobPromise::ResolveOrRejectValue& aRrv) {
768 if (aRrv.IsReject()) {
769 LOG(LogLevel::Warning, ("RequestData failed"));
770 DoSessionEndTask(aRrv.RejectValue());
771 return;
774 nsresult rv =
775 mRecorder->CreateAndDispatchBlobEvent(aRrv.ResolveValue());
776 if (NS_FAILED(rv)) {
777 DoSessionEndTask(NS_OK);
782 public:
783 RefPtr<SizeOfPromise> SizeOfExcludingThis(
784 mozilla::MallocSizeOf aMallocSizeOf) {
785 MOZ_ASSERT(NS_IsMainThread());
786 if (!mEncoder) {
787 return SizeOfPromise::CreateAndResolve(0, __func__);
790 return mEncoder->SizeOfExcludingThis(aMallocSizeOf);
793 private:
794 virtual ~Session() {
795 MOZ_ASSERT(NS_IsMainThread());
796 MOZ_ASSERT(mShutdownPromise);
797 MOZ_ASSERT(!mShutdownBlocker);
798 LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
801 void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate,
802 TimeDuration aTimeslice) {
803 LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
804 MOZ_ASSERT(NS_IsMainThread());
806 if (!mRunningState.isOk() ||
807 mRunningState.inspect() != RunningState::Idling) {
808 MOZ_ASSERT_UNREACHABLE("Double-init");
809 return;
812 // Create a TaskQueue to read encode media data from MediaEncoder.
813 MOZ_RELEASE_ASSERT(!mEncoderThread);
814 RefPtr<SharedThreadPool> pool =
815 GetMediaThreadPool(MediaThreadType::WEBRTC_WORKER);
816 if (!pool) {
817 LOG(LogLevel::Debug, ("Session.InitEncoder %p Failed to create "
818 "MediaRecorderReadThread thread pool",
819 this));
820 DoSessionEndTask(NS_ERROR_FAILURE);
821 return;
824 mEncoderThread =
825 TaskQueue::Create(pool.forget(), "MediaRecorderReadThread");
827 MOZ_DIAGNOSTIC_ASSERT(!mShutdownBlocker);
828 // Add a shutdown blocker so mEncoderThread can be shutdown async.
829 class Blocker : public ShutdownBlocker {
830 const RefPtr<Session> mSession;
832 public:
833 Blocker(RefPtr<Session> aSession, const nsString& aName)
834 : ShutdownBlocker(aName), mSession(std::move(aSession)) {}
836 NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override {
837 mSession->DoSessionEndTask(NS_ERROR_ABORT);
838 return NS_OK;
842 nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
843 if (!barrier) {
844 LOG(LogLevel::Error,
845 ("Session.InitEncoder %p Failed to get shutdown barrier", this));
846 DoSessionEndTask(NS_ERROR_FAILURE);
847 return;
850 nsString name;
851 name.AppendPrintf("MediaRecorder::Session %p shutdown", this);
852 mShutdownBlocker = MakeAndAddRef<Blocker>(this, name);
853 nsresult rv = barrier->AddBlocker(
854 mShutdownBlocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
855 u"MediaRecorder::Session: shutdown"_ns);
856 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
858 uint32_t maxMemory = Preferences::GetUint("media.recorder.max_memory",
859 MAX_ALLOW_MEMORY_BUFFER);
861 mEncoder = MediaEncoder::CreateEncoder(
862 mEncoderThread, mMimeType, mAudioBitsPerSecond, mVideoBitsPerSecond,
863 aTrackTypes, aTrackRate, maxMemory, aTimeslice);
865 if (!mEncoder) {
866 LOG(LogLevel::Error, ("Session.InitEncoder !mEncoder %p", this));
867 DoSessionEndTask(NS_ERROR_ABORT);
868 return;
871 mStartedListener = mEncoder->StartedEvent().Connect(mMainThread, this,
872 &Session::OnStarted);
873 mDataAvailableListener = mEncoder->DataAvailableEvent().Connect(
874 mMainThread, this, &Session::OnDataAvailable);
875 mErrorListener =
876 mEncoder->ErrorEvent().Connect(mMainThread, this, &Session::OnError);
877 mShutdownListener = mEncoder->ShutdownEvent().Connect(mMainThread, this,
878 &Session::OnShutdown);
880 if (mRecorder->mAudioNode) {
881 mEncoder->ConnectAudioNode(mRecorder->mAudioNode,
882 mRecorder->mAudioNodeOutput);
885 for (const auto& track : mMediaStreamTracks) {
886 mEncoder->ConnectMediaStreamTrack(track);
889 // Set mRunningState to Running so that DoSessionEndTask will
890 // take the responsibility to end the session.
891 mRunningState = RunningState::Starting;
894 // This is the task that will stop recording per spec:
895 // - If rv is NS_ERROR_ABORT or NS_ERROR_DOM_SECURITY_ERR, cancel the encoders
896 // - Otherwise, stop the encoders gracefully, this still encodes buffered data
897 // - Set state to "inactive"
898 // - Fire an error event, if NS_FAILED(rv)
899 // - Discard blob data if rv is NS_ERROR_DOM_SECURITY_ERR
900 // - Fire a Blob event
901 // - Fire an event named stop
902 void DoSessionEndTask(nsresult rv) {
903 MOZ_ASSERT(NS_IsMainThread());
904 if (mRunningState.isErr()) {
905 // We have already ended with an error.
906 return;
909 if (mRunningState.isOk() &&
910 mRunningState.inspect() == RunningState::Stopped) {
911 // We have already ended gracefully.
912 return;
915 bool needsStartEvent = false;
916 if (mRunningState.isOk() &&
917 (mRunningState.inspect() == RunningState::Idling ||
918 mRunningState.inspect() == RunningState::Starting)) {
919 needsStartEvent = true;
922 // Set a terminated running state. Future DoSessionEnd tasks will exit
923 // early.
924 if (rv == NS_OK) {
925 mRunningState = RunningState::Stopped;
926 } else {
927 mRunningState = Err(rv);
930 RefPtr<MediaEncoder::BlobPromise> blobPromise;
931 if (!mEncoder) {
932 blobPromise = MediaEncoder::BlobPromise::CreateAndReject(NS_OK, __func__);
933 } else {
934 blobPromise =
935 (rv == NS_ERROR_ABORT || rv == NS_ERROR_DOM_SECURITY_ERR
936 ? mEncoder->Cancel()
937 : mEncoder->Stop())
938 ->Then(mEncoderThread, __func__,
939 [encoder = mEncoder](
940 const GenericNonExclusivePromise::ResolveOrRejectValue&
941 aValue) {
942 MOZ_DIAGNOSTIC_ASSERT(aValue.IsResolve());
943 return encoder->RequestData();
947 blobPromise
948 ->Then(
949 mMainThread, __func__,
950 [this, self = RefPtr<Session>(this), rv, needsStartEvent](
951 const MediaEncoder::BlobPromise::ResolveOrRejectValue& aRv) {
952 if (mRecorder->mSessions.LastElement() == this) {
953 // Set state to inactive, but only if the recorder is not
954 // controlled by another session already.
955 mRecorder->Inactivate();
958 if (needsStartEvent) {
959 mRecorder->DispatchSimpleEvent(u"start"_ns);
962 // If there was an error, Fire the appropriate one
963 if (NS_FAILED(rv)) {
964 mRecorder->NotifyError(rv);
967 // Fire a blob event named dataavailable
968 RefPtr<BlobImpl> blobImpl;
969 if (rv == NS_ERROR_DOM_SECURITY_ERR || aRv.IsReject()) {
970 // In case of SecurityError, the blob data must be discarded.
971 // We create a new empty one and throw the blob with its data
972 // away.
973 // In case we failed to gather blob data, we create an empty
974 // memory blob instead.
975 blobImpl = new EmptyBlobImpl(mMimeType);
976 } else {
977 blobImpl = aRv.ResolveValue();
979 if (NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(blobImpl))) {
980 // Failed to dispatch blob event. That's unexpected. It's
981 // probably all right to fire an error event if we haven't
982 // already.
983 if (NS_SUCCEEDED(rv)) {
984 mRecorder->NotifyError(NS_ERROR_FAILURE);
988 // Fire an event named stop
989 mRecorder->DispatchSimpleEvent(u"stop"_ns);
991 // And finally, Shutdown and destroy the Session
992 return Shutdown();
994 ->Then(mMainThread, __func__, [this, self = RefPtr<Session>(this)] {
995 // Guard against the case where we fail to add a blocker due to being
996 // in XPCOM shutdown. If we're in this state we shouldn't try and get
997 // a shutdown barrier as we'll fail.
998 if (!mShutdownBlocker) {
999 return;
1001 MustGetShutdownBarrier()->RemoveBlocker(mShutdownBlocker);
1002 mShutdownBlocker = nullptr;
1006 void OnStarted() {
1007 MOZ_ASSERT(NS_IsMainThread());
1008 if (mRunningState.isErr()) {
1009 return;
1011 RunningState state = mRunningState.inspect();
1012 if (state == RunningState::Starting || state == RunningState::Stopping) {
1013 if (state == RunningState::Starting) {
1014 // We set it to Running in the runnable since we can only assign
1015 // mRunningState on main thread. We set it before running the start
1016 // event runnable since that dispatches synchronously (and may cause
1017 // js calls to methods depending on mRunningState).
1018 mRunningState = RunningState::Running;
1020 mRecorder->mMimeType = mEncoder->mMimeType;
1022 mRecorder->DispatchSimpleEvent(u"start"_ns);
1026 void OnDataAvailable(const RefPtr<BlobImpl>& aBlob) {
1027 if (mRunningState.isErr() &&
1028 mRunningState.unwrapErr() == NS_ERROR_DOM_SECURITY_ERR) {
1029 return;
1031 if (NS_WARN_IF(NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(aBlob)))) {
1032 LOG(LogLevel::Warning,
1033 ("MediaRecorder %p Creating or dispatching BlobEvent failed", this));
1034 DoSessionEndTask(NS_OK);
1038 void OnError() {
1039 MOZ_ASSERT(NS_IsMainThread());
1040 DoSessionEndTask(NS_ERROR_FAILURE);
1043 void OnShutdown() {
1044 MOZ_ASSERT(NS_IsMainThread());
1045 DoSessionEndTask(NS_OK);
1048 RefPtr<ShutdownPromise> Shutdown() {
1049 MOZ_ASSERT(NS_IsMainThread());
1050 LOG(LogLevel::Debug, ("Session Shutdown %p", this));
1052 if (mShutdownPromise) {
1053 return mShutdownPromise;
1056 // This is a coarse calculation and does not reflect the duration of the
1057 // final recording for reasons such as pauses. However it allows us an
1058 // idea of how long people are running their recorders for.
1059 TimeDuration timeDelta = TimeStamp::Now() - mStartTime;
1060 Telemetry::Accumulate(Telemetry::MEDIA_RECORDER_RECORDING_DURATION,
1061 timeDelta.ToSeconds());
1063 mShutdownPromise = ShutdownPromise::CreateAndResolve(true, __func__);
1065 if (mEncoder) {
1066 mShutdownPromise =
1067 mShutdownPromise
1068 ->Then(mMainThread, __func__,
1069 [this, self = RefPtr<Session>(this)] {
1070 mStartedListener.DisconnectIfExists();
1071 mDataAvailableListener.DisconnectIfExists();
1072 mErrorListener.DisconnectIfExists();
1073 mShutdownListener.DisconnectIfExists();
1074 return mEncoder->Cancel();
1076 ->Then(mEncoderThread, __func__, [] {
1077 // Meh, this is just to convert the promise type to match
1078 // mShutdownPromise.
1079 return ShutdownPromise::CreateAndResolve(true, __func__);
1083 // Remove main thread state. This could be needed if Stop() wasn't called.
1084 if (mMediaStream) {
1085 mMediaStream->UnregisterTrackListener(this);
1086 mMediaStream = nullptr;
1090 auto tracks(std::move(mMediaStreamTracks));
1091 for (RefPtr<MediaStreamTrack>& track : tracks) {
1092 track->RemovePrincipalChangeObserver(this);
1096 // Break the cycle reference between Session and MediaRecorder.
1097 mShutdownPromise = mShutdownPromise->Then(
1098 mMainThread, __func__,
1099 [self = RefPtr<Session>(this)]() {
1100 self->mRecorder->RemoveSession(self);
1101 return ShutdownPromise::CreateAndResolve(true, __func__);
1103 []() {
1104 MOZ_ASSERT_UNREACHABLE("Unexpected reject");
1105 return ShutdownPromise::CreateAndReject(false, __func__);
1108 if (mEncoderThread) {
1109 mShutdownPromise = mShutdownPromise->Then(
1110 mMainThread, __func__,
1111 [encoderThread = mEncoderThread]() {
1112 return encoderThread->BeginShutdown();
1114 []() {
1115 MOZ_ASSERT_UNREACHABLE("Unexpected reject");
1116 return ShutdownPromise::CreateAndReject(false, __func__);
1120 return mShutdownPromise;
1123 private:
1124 enum class RunningState {
1125 Idling, // Session has been created
1126 Starting, // MediaEncoder started, waiting for data
1127 Running, // MediaEncoder has received data
1128 Stopping, // Stop() has been called
1129 Stopped, // Session has stopped without any error
1132 // Our associated MediaRecorder.
1133 const RefPtr<MediaRecorder> mRecorder;
1135 // Stream currently recorded.
1136 RefPtr<DOMMediaStream> mMediaStream;
1138 // Tracks currently recorded. This should be a subset of mMediaStream's track
1139 // set.
1140 nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
1142 // Main thread used for MozPromise operations.
1143 const RefPtr<nsISerialEventTarget> mMainThread;
1144 // Runnable thread for reading data from MediaEncoder.
1145 RefPtr<TaskQueue> mEncoderThread;
1146 // MediaEncoder pipeline.
1147 RefPtr<MediaEncoder> mEncoder;
1148 // Listener connected to mMediaEncoder::StartedEvent().
1149 MediaEventListener mStartedListener;
1150 // Listener connected to mMediaEncoder::DataAvailableEvent().
1151 MediaEventListener mDataAvailableListener;
1152 // Listener connected to mMediaEncoder::ErrorEvent().
1153 MediaEventListener mErrorListener;
1154 // Listener connected to mMediaEncoder::ShutdownEvent().
1155 MediaEventListener mShutdownListener;
1156 // Set in Shutdown() and resolved when shutdown is complete.
1157 RefPtr<ShutdownPromise> mShutdownPromise;
1158 // Session mimeType
1159 const nsString mMimeType;
1160 // The video bitrate the recorder was configured with.
1161 const uint32_t mVideoBitsPerSecond;
1162 // The audio bitrate the recorder was configured with.
1163 const uint32_t mAudioBitsPerSecond;
1164 // The time this session started, for telemetry.
1165 const TimeStamp mStartTime;
1166 // The session's current main thread state. The error type gets set when
1167 // ending a recording with an error. An NS_OK error is invalid.
1168 // Main thread only.
1169 Result<RunningState, nsresult> mRunningState;
1170 // Shutdown blocker unique for this Session. Main thread only.
1171 RefPtr<ShutdownBlocker> mShutdownBlocker;
1174 MediaRecorder::~MediaRecorder() {
1175 LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
1176 UnRegisterActivityObserver();
1179 MediaRecorder::MediaRecorder(nsPIDOMWindowInner* aOwnerWindow)
1180 : DOMEventTargetHelper(aOwnerWindow) {
1181 MOZ_ASSERT(aOwnerWindow);
1182 RegisterActivityObserver();
1185 void MediaRecorder::RegisterActivityObserver() {
1186 if (nsPIDOMWindowInner* window = GetOwner()) {
1187 mDocument = window->GetExtantDoc();
1188 if (mDocument) {
1189 mDocument->RegisterActivityObserver(
1190 NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1195 void MediaRecorder::UnRegisterActivityObserver() {
1196 if (mDocument) {
1197 mDocument->UnregisterActivityObserver(
1198 NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1202 void MediaRecorder::GetMimeType(nsString& aMimeType) { aMimeType = mMimeType; }
1204 void MediaRecorder::Start(const Optional<uint32_t>& aTimeslice,
1205 ErrorResult& aResult) {
1206 LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
1208 InitializeDomExceptions();
1210 // When a MediaRecorder object’s start() method is invoked, the UA MUST run
1211 // the following steps:
1213 // 1. Let recorder be the MediaRecorder object on which the method was
1214 // invoked.
1216 // 2. Let timeslice be the method’s first argument, if provided, or undefined.
1217 TimeDuration timeslice =
1218 aTimeslice.WasPassed()
1219 ? TimeDuration::FromMilliseconds(aTimeslice.Value())
1220 : TimeDuration::Forever();
1222 // 3. Let stream be the value of recorder’s stream attribute.
1224 // 4. Let tracks be the set of live tracks in stream’s track set.
1225 nsTArray<RefPtr<MediaStreamTrack>> tracks;
1226 if (mStream) {
1227 mStream->GetTracks(tracks);
1229 tracks.RemoveLastElements(
1230 tracks.end() - std::remove_if(tracks.begin(), tracks.end(),
1231 [](const auto& t) { return t->Ended(); }));
1233 // 5. If the value of recorder’s state attribute is not inactive, throw an
1234 // InvalidStateError DOMException and abort these steps.
1235 if (mState != RecordingState::Inactive) {
1236 aResult.ThrowInvalidStateError(
1237 "The MediaRecorder has already been started");
1238 return;
1241 // 6. If the isolation properties of stream disallow access from recorder,
1242 // throw a SecurityError DOMException and abort these steps.
1243 if (mStream) {
1244 RefPtr<nsIPrincipal> streamPrincipal = mStream->GetPrincipal();
1245 if (!streamPrincipal) {
1246 // This is more or less part of the step 7, see below.
1247 aResult.ThrowNotSupportedError("The MediaStream is inactive");
1248 return;
1251 if (!PrincipalSubsumes(this, streamPrincipal)) {
1252 aResult.ThrowSecurityError(
1253 "The MediaStream's isolation properties disallow access from "
1254 "MediaRecorder");
1255 return;
1258 if (mAudioNode && !AudioNodePrincipalSubsumes(this, mAudioNode)) {
1259 LOG(LogLevel::Warning,
1260 ("MediaRecorder %p Start AudioNode principal check failed", this));
1261 aResult.ThrowSecurityError(
1262 "The AudioNode's isolation properties disallow access from "
1263 "MediaRecorder");
1264 return;
1267 // 7. If stream is inactive, throw a NotSupportedError DOMException and abort
1268 // these steps.
1269 if (mStream && !mStream->Active()) {
1270 aResult.ThrowNotSupportedError("The MediaStream is inactive");
1271 return;
1274 // 8. If the [[ConstrainedMimeType]] slot specifies a media type, container,
1275 // or codec, then run the following sub steps:
1276 // 1. Constrain the configuration of recorder to the media type, container,
1277 // and codec specified in the [[ConstrainedMimeType]] slot.
1278 // 2. For each track in tracks, if the User Agent cannot record the track
1279 // using the current configuration, then throw a NotSupportedError
1280 // DOMException and abort all steps.
1281 Maybe<MediaContainerType> mime;
1282 if (mConstrainedMimeType.Length() > 0) {
1283 mime = MakeMediaContainerType(mConstrainedMimeType);
1284 MOZ_DIAGNOSTIC_ASSERT(
1285 mime,
1286 "Invalid media MIME type should have been caught by IsTypeSupported");
1288 for (const auto& track : tracks) {
1289 TypeSupport support = CanRecordWith(track, mime, mConstrainedMimeType);
1290 if (support != TypeSupport::Supported) {
1291 nsString id;
1292 track->GetId(id);
1293 aResult.ThrowNotSupportedError(nsPrintfCString(
1294 "%s track cannot be recorded: %s",
1295 track->AsAudioStreamTrack() ? "An audio" : "A video",
1296 TypeSupportToCString(support, mConstrainedMimeType).get()));
1297 return;
1300 if (mAudioNode) {
1301 TypeSupport support = CanRecordAudioTrackWith(mime, mConstrainedMimeType);
1302 if (support != TypeSupport::Supported) {
1303 aResult.ThrowNotSupportedError(nsPrintfCString(
1304 "An AudioNode cannot be recorded: %s",
1305 TypeSupportToCString(support, mConstrainedMimeType).get()));
1306 return;
1310 // 9. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1311 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1312 // values the User Agent deems reasonable for the respective media types,
1313 // for recording all tracks in tracks, such that the sum of
1314 // videoBitsPerSecond and audioBitsPerSecond is close to the value of
1315 // recorder’s
1316 // [[ConstrainedBitsPerSecond]] slot.
1317 uint8_t numVideoTracks = 0;
1318 uint8_t numAudioTracks = 0;
1319 for (const auto& t : tracks) {
1320 if (t->AsVideoStreamTrack() && numVideoTracks < UINT8_MAX) {
1321 ++numVideoTracks;
1322 } else if (t->AsAudioStreamTrack() && numAudioTracks < UINT8_MAX) {
1323 ++numAudioTracks;
1326 if (mAudioNode) {
1327 MOZ_DIAGNOSTIC_ASSERT(!mStream);
1328 ++numAudioTracks;
1330 if (mConstrainedBitsPerSecond) {
1331 SelectBitrates(*mConstrainedBitsPerSecond, numVideoTracks,
1332 &mVideoBitsPerSecond, numAudioTracks, &mAudioBitsPerSecond);
1335 // 10. Let videoBitrate be the value of recorder’s videoBitsPerSecond
1336 // attribute, and constrain the configuration of recorder to target an
1337 // aggregate bitrate of videoBitrate bits per second for all video tracks
1338 // recorder will be recording. videoBitrate is a hint for the encoder and
1339 // the value might be surpassed, not achieved, or only be achieved over a
1340 // long period of time.
1341 const uint32_t videoBitrate = mVideoBitsPerSecond;
1343 // 11. Let audioBitrate be the value of recorder’s audioBitsPerSecond
1344 // attribute, and constrain the configuration of recorder to target an
1345 // aggregate bitrate of audioBitrate bits per second for all audio tracks
1346 // recorder will be recording. audioBitrate is a hint for the encoder and
1347 // the value might be surpassed, not achieved, or only be achieved over a
1348 // long period of time.
1349 const uint32_t audioBitrate = mAudioBitsPerSecond;
1351 // 12. Constrain the configuration of recorder to encode using the BitrateMode
1352 // specified by the value of recorder’s audioBitrateMode attribute for all
1353 // audio tracks recorder will be recording.
1354 // -- NOT IMPLEMENTED
1356 // 13. For each track in tracks, if the User Agent cannot record the track
1357 // using the current configuration, then throw a NotSupportedError
1358 // DOMException and abort these steps.
1359 if (numVideoTracks > 1) {
1360 aResult.ThrowNotSupportedError(
1361 "MediaRecorder does not support recording more than one video track"_ns);
1362 return;
1364 if (numAudioTracks > 1) {
1365 aResult.ThrowNotSupportedError(
1366 "MediaRecorder does not support recording more than one audio track"_ns);
1367 return;
1370 // 14. Set recorder’s state to recording
1371 mState = RecordingState::Recording;
1373 MediaRecorderReporter::AddMediaRecorder(this);
1374 // Start a session.
1375 mSessions.AppendElement();
1376 mSessions.LastElement() =
1377 new Session(this, std::move(tracks), videoBitrate, audioBitrate);
1378 mSessions.LastElement()->Start(timeslice);
1381 void MediaRecorder::Stop(ErrorResult& aResult) {
1382 LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
1383 MediaRecorderReporter::RemoveMediaRecorder(this);
1385 // When a MediaRecorder object’s stop() method is invoked, the UA MUST run the
1386 // following steps:
1388 // 1. Let recorder be the MediaRecorder object on which the method was
1389 // invoked.
1391 // 2. If recorder’s state attribute is inactive, abort these steps.
1392 if (mState == RecordingState::Inactive) {
1393 return;
1396 // 3. Inactivate the recorder with recorder.
1397 Inactivate();
1399 // 4. Queue a task, using the DOM manipulation task source, that runs the
1400 // following steps:
1401 // 1. Stop gathering data.
1402 // 2. Let blob be the Blob of collected data so far, then fire a blob event
1403 // named dataavailable at recorder with blob.
1404 // 3. Fire an event named stop at recorder.
1405 MOZ_ASSERT(mSessions.Length() > 0);
1406 mSessions.LastElement()->Stop();
1408 // 5. return undefined.
1411 void MediaRecorder::Pause(ErrorResult& aResult) {
1412 LOG(LogLevel::Debug, ("MediaRecorder.Pause %p", this));
1414 // When a MediaRecorder object’s pause() method is invoked, the UA MUST run
1415 // the following steps:
1417 // 1. If state is inactive, throw an InvalidStateError DOMException and abort
1418 // these steps.
1419 if (mState == RecordingState::Inactive) {
1420 aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
1421 return;
1424 // 2. If state is paused, abort these steps.
1425 if (mState == RecordingState::Paused) {
1426 return;
1429 // 3. Set state to paused, and queue a task, using the DOM manipulation task
1430 // source, that runs the following steps:
1431 mState = RecordingState::Paused;
1433 // XXX - We pause synchronously pending spec issue
1434 // https://github.com/w3c/mediacapture-record/issues/131
1435 // 1. Stop gathering data into blob (but keep it available so that
1436 // recording can be resumed in the future).
1437 MOZ_ASSERT(!mSessions.IsEmpty());
1438 mSessions.LastElement()->Pause();
1440 NS_DispatchToMainThread(NS_NewRunnableFunction(
1441 "MediaRecorder::Pause", [recorder = RefPtr<MediaRecorder>(this)] {
1442 // 2. Let target be the MediaRecorder context object. Fire an event
1443 // named pause at target.
1444 recorder->DispatchSimpleEvent(u"pause"_ns);
1445 }));
1447 // 4. return undefined.
1450 void MediaRecorder::Resume(ErrorResult& aResult) {
1451 LOG(LogLevel::Debug, ("MediaRecorder.Resume %p", this));
1453 // When a MediaRecorder object’s resume() method is invoked, the UA MUST run
1454 // the following steps:
1456 // 1. If state is inactive, throw an InvalidStateError DOMException and abort
1457 // these steps.
1458 if (mState == RecordingState::Inactive) {
1459 aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
1460 return;
1463 // 2. If state is recording, abort these steps.
1464 if (mState == RecordingState::Recording) {
1465 return;
1468 // 3. Set state to recording, and queue a task, using the DOM manipulation
1469 // task source, that runs the following steps:
1470 mState = RecordingState::Recording;
1472 // XXX - We resume synchronously pending spec issue
1473 // https://github.com/w3c/mediacapture-record/issues/131
1474 // 1. Resume (or continue) gathering data into the current blob.
1475 MOZ_ASSERT(!mSessions.IsEmpty());
1476 mSessions.LastElement()->Resume();
1478 NS_DispatchToMainThread(NS_NewRunnableFunction(
1479 "MediaRecorder::Resume", [recorder = RefPtr<MediaRecorder>(this)] {
1480 // 2. Let target be the MediaRecorder context object. Fire an event
1481 // named resume at target.
1482 recorder->DispatchSimpleEvent(u"resume"_ns);
1483 }));
1485 // 4. return undefined.
1488 void MediaRecorder::RequestData(ErrorResult& aResult) {
1489 LOG(LogLevel::Debug, ("MediaRecorder.RequestData %p", this));
1491 // When a MediaRecorder object’s requestData() method is invoked, the UA MUST
1492 // run the following steps:
1494 // 1. If state is inactive throw an InvalidStateError DOMException and
1495 // terminate these steps. Otherwise the UA MUST queue a task, using the DOM
1496 // manipulation task source, that runs the following steps:
1497 // 1. Let blob be the Blob of collected data so far and let target be the
1498 // MediaRecorder context object, then fire a blob event named
1499 // dataavailable at target with blob. (Note that blob will be empty if no
1500 // data has been gathered yet.)
1501 // 2. Create a new Blob and gather subsequent data into it.
1502 if (mState == RecordingState::Inactive) {
1503 aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
1504 return;
1506 MOZ_ASSERT(mSessions.Length() > 0);
1507 mSessions.LastElement()->RequestData();
1509 // 2. return undefined.
1512 JSObject* MediaRecorder::WrapObject(JSContext* aCx,
1513 JS::Handle<JSObject*> aGivenProto) {
1514 return MediaRecorder_Binding::Wrap(aCx, this, aGivenProto);
1517 /* static */
1518 already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
1519 const GlobalObject& aGlobal, DOMMediaStream& aStream,
1520 const MediaRecorderOptions& aOptions, ErrorResult& aRv) {
1521 nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
1522 do_QueryInterface(aGlobal.GetAsSupports());
1523 if (!ownerWindow) {
1524 aRv.Throw(NS_ERROR_FAILURE);
1525 return nullptr;
1528 // When the MediaRecorder() constructor is invoked, the User Agent MUST run
1529 // the following steps:
1531 // 1. Let stream be the constructor’s first argument.
1533 // 2. Let options be the constructor’s second argument.
1535 // 3. If invoking is type supported with options’ mimeType member as its
1536 // argument returns false, throw a NotSupportedError DOMException and abort
1537 // these steps.
1538 TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
1539 if (support != TypeSupport::Supported) {
1540 // This catches also the empty string mimeType when support for any encoders
1541 // has been disabled.
1542 aRv.ThrowNotSupportedError(
1543 TypeSupportToCString(support, aOptions.mMimeType));
1544 return nullptr;
1547 // 4. Let recorder be a newly constructed MediaRecorder object.
1548 RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
1550 // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
1551 // to the value of options' mimeType member.
1552 recorder->mConstrainedMimeType = aOptions.mMimeType;
1554 // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
1555 // initialized to the value of options’ bitsPerSecond member, if it is
1556 // present, otherwise undefined.
1557 recorder->mConstrainedBitsPerSecond =
1558 aOptions.mBitsPerSecond.WasPassed()
1559 ? Some(aOptions.mBitsPerSecond.Value())
1560 : Nothing();
1562 // 7. Initialize recorder’s stream attribute to stream.
1563 recorder->mStream = &aStream;
1565 // 8. Initialize recorder’s mimeType attribute to the value of recorder’s
1566 // [[ConstrainedMimeType]] slot.
1567 recorder->mMimeType = recorder->mConstrainedMimeType;
1569 // 9. Initialize recorder’s state attribute to inactive.
1570 recorder->mState = RecordingState::Inactive;
1572 // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
1573 // options’ videoBitsPerSecond member, if it is present. Otherwise, choose
1574 // a target value the User Agent deems reasonable for video.
1575 recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
1576 ? aOptions.mVideoBitsPerSecond.Value()
1577 : DEFAULT_VIDEO_BITRATE_BPS;
1579 // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
1580 // options’ audioBitsPerSecond member, if it is present. Otherwise, choose
1581 // a target value the User Agent deems reasonable for audio.
1582 recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
1583 ? aOptions.mAudioBitsPerSecond.Value()
1584 : DEFAULT_AUDIO_BITRATE_BPS;
1586 // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1587 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1588 // values the User Agent deems reasonable for the respective media types,
1589 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
1590 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
1591 if (recorder->mConstrainedBitsPerSecond) {
1592 SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
1593 &recorder->mVideoBitsPerSecond, 1,
1594 &recorder->mAudioBitsPerSecond);
1597 // 13. Return recorder.
1598 return recorder.forget();
1601 /* static */
1602 already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
1603 const GlobalObject& aGlobal, AudioNode& aAudioNode,
1604 uint32_t aAudioNodeOutput, const MediaRecorderOptions& aOptions,
1605 ErrorResult& aRv) {
1606 // Allow recording from audio node only when pref is on.
1607 if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
1608 // Pretending that this constructor is not defined.
1609 aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("Argument 1",
1610 "MediaStream");
1611 return nullptr;
1614 nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
1615 do_QueryInterface(aGlobal.GetAsSupports());
1616 if (!ownerWindow) {
1617 aRv.Throw(NS_ERROR_FAILURE);
1618 return nullptr;
1621 // aAudioNodeOutput doesn't matter to destination node because it has no
1622 // output.
1623 if (aAudioNode.NumberOfOutputs() > 0 &&
1624 aAudioNodeOutput >= aAudioNode.NumberOfOutputs()) {
1625 aRv.ThrowIndexSizeError("Invalid AudioNode output index");
1626 return nullptr;
1629 // When the MediaRecorder() constructor is invoked, the User Agent MUST run
1630 // the following steps:
1632 // 1. Let stream be the constructor’s first argument. (we'll let audioNode be
1633 // the first arg, and audioNodeOutput the second)
1635 // 2. Let options be the constructor’s second argument. (we'll let options be
1636 // the third arg)
1638 // 3. If invoking is type supported with options’ mimeType member as its
1639 // argument returns false, throw a NotSupportedError DOMException and abort
1640 // these steps.
1641 TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
1642 if (support != TypeSupport::Supported) {
1643 // This catches also the empty string mimeType when support for any encoders
1644 // has been disabled.
1645 aRv.ThrowNotSupportedError(
1646 TypeSupportToCString(support, aOptions.mMimeType));
1647 return nullptr;
1650 // 4. Let recorder be a newly constructed MediaRecorder object.
1651 RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
1653 // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
1654 // to the value of options' mimeType member.
1655 recorder->mConstrainedMimeType = aOptions.mMimeType;
1657 // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
1658 // initialized to the value of options’ bitsPerSecond member, if it is
1659 // present, otherwise undefined.
1660 recorder->mConstrainedBitsPerSecond =
1661 aOptions.mBitsPerSecond.WasPassed()
1662 ? Some(aOptions.mBitsPerSecond.Value())
1663 : Nothing();
1665 // 7. Initialize recorder’s stream attribute to stream. (make that the
1666 // audioNode and audioNodeOutput equivalents)
1667 recorder->mAudioNode = &aAudioNode;
1668 recorder->mAudioNodeOutput = aAudioNodeOutput;
1670 // 8. Initialize recorder’s mimeType attribute to the value of recorder’s
1671 // [[ConstrainedMimeType]] slot.
1672 recorder->mMimeType = recorder->mConstrainedMimeType;
1674 // 9. Initialize recorder’s state attribute to inactive.
1675 recorder->mState = RecordingState::Inactive;
1677 // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
1678 // options’ videoBitsPerSecond member, if it is present. Otherwise, choose
1679 // a target value the User Agent deems reasonable for video.
1680 recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
1681 ? aOptions.mVideoBitsPerSecond.Value()
1682 : DEFAULT_VIDEO_BITRATE_BPS;
1684 // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
1685 // options’ audioBitsPerSecond member, if it is present. Otherwise, choose
1686 // a target value the User Agent deems reasonable for audio.
1687 recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
1688 ? aOptions.mAudioBitsPerSecond.Value()
1689 : DEFAULT_AUDIO_BITRATE_BPS;
1691 // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1692 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1693 // values the User Agent deems reasonable for the respective media types,
1694 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
1695 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
1696 if (recorder->mConstrainedBitsPerSecond) {
1697 SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
1698 &recorder->mVideoBitsPerSecond, 1,
1699 &recorder->mAudioBitsPerSecond);
1702 // 13. Return recorder.
1703 return recorder.forget();
1706 /* static */
1707 bool MediaRecorder::IsTypeSupported(GlobalObject& aGlobal,
1708 const nsAString& aMIMEType) {
1709 return MediaRecorder::IsTypeSupported(aMIMEType);
1712 /* static */
1713 bool MediaRecorder::IsTypeSupported(const nsAString& aMIMEType) {
1714 return IsTypeSupportedImpl(aMIMEType) == TypeSupport::Supported;
1717 nsresult MediaRecorder::CreateAndDispatchBlobEvent(BlobImpl* aBlobImpl) {
1718 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1720 if (!GetOwnerGlobal()) {
1721 // This MediaRecorder has been disconnected in the meantime.
1722 return NS_ERROR_FAILURE;
1725 RefPtr<Blob> blob = Blob::Create(GetOwnerGlobal(), aBlobImpl);
1726 if (NS_WARN_IF(!blob)) {
1727 return NS_ERROR_FAILURE;
1730 BlobEventInit init;
1731 init.mBubbles = false;
1732 init.mCancelable = false;
1733 init.mData = blob;
1735 RefPtr<BlobEvent> event =
1736 BlobEvent::Constructor(this, u"dataavailable"_ns, init);
1737 event->SetTrusted(true);
1738 ErrorResult rv;
1739 DispatchEvent(*event, rv);
1740 return rv.StealNSResult();
1743 void MediaRecorder::DispatchSimpleEvent(const nsAString& aStr) {
1744 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1745 nsresult rv = CheckCurrentGlobalCorrectness();
1746 if (NS_FAILED(rv)) {
1747 return;
1750 rv = DOMEventTargetHelper::DispatchTrustedEvent(aStr);
1751 if (NS_FAILED(rv)) {
1752 LOG(LogLevel::Error,
1753 ("MediaRecorder.DispatchSimpleEvent: DispatchTrustedEvent failed %p",
1754 this));
1755 NS_ERROR("Failed to dispatch the event!!!");
1759 void MediaRecorder::NotifyError(nsresult aRv) {
1760 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1761 nsresult rv = CheckCurrentGlobalCorrectness();
1762 if (NS_FAILED(rv)) {
1763 return;
1765 MediaRecorderErrorEventInit init;
1766 init.mBubbles = false;
1767 init.mCancelable = false;
1768 // These DOMExceptions have been created earlier so they can contain stack
1769 // traces. We attach the appropriate one here to be fired. We should have
1770 // exceptions here, but defensively check.
1771 switch (aRv) {
1772 case NS_ERROR_DOM_SECURITY_ERR:
1773 if (!mSecurityDomException) {
1774 LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
1775 "mSecurityDomException was not initialized"));
1776 mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
1778 init.mError = std::move(mSecurityDomException);
1779 break;
1780 default:
1781 if (mOtherDomException && aRv == mOtherDomException->GetResult()) {
1782 LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
1783 "mOtherDomException being fired for aRv: %X",
1784 uint32_t(aRv)));
1785 init.mError = std::move(mOtherDomException);
1786 break;
1788 if (!mUnknownDomException) {
1789 LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
1790 "mUnknownDomException was not initialized"));
1791 mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
1793 LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
1794 "mUnknownDomException being fired for aRv: %X",
1795 uint32_t(aRv)));
1796 init.mError = std::move(mUnknownDomException);
1797 break;
1800 RefPtr<MediaRecorderErrorEvent> event =
1801 MediaRecorderErrorEvent::Constructor(this, u"error"_ns, init);
1802 event->SetTrusted(true);
1804 IgnoredErrorResult res;
1805 DispatchEvent(*event, res);
1806 if (res.Failed()) {
1807 NS_ERROR("Failed to dispatch the error event!!!");
1811 void MediaRecorder::RemoveSession(Session* aSession) {
1812 LOG(LogLevel::Debug, ("MediaRecorder.RemoveSession (%p)", aSession));
1813 mSessions.RemoveElement(aSession);
1816 void MediaRecorder::NotifyOwnerDocumentActivityChanged() {
1817 nsPIDOMWindowInner* window = GetOwner();
1818 NS_ENSURE_TRUE_VOID(window);
1819 Document* doc = window->GetExtantDoc();
1820 NS_ENSURE_TRUE_VOID(doc);
1822 LOG(LogLevel::Debug, ("MediaRecorder %p NotifyOwnerDocumentActivityChanged "
1823 "IsActive=%d, "
1824 "IsVisible=%d, ",
1825 this, doc->IsActive(), doc->IsVisible()));
1826 if (!doc->IsActive() || !doc->IsVisible()) {
1827 // Stop the session.
1828 ErrorResult result;
1829 Stop(result);
1830 result.SuppressException();
1834 void MediaRecorder::Inactivate() {
1835 LOG(LogLevel::Debug, ("MediaRecorder.Inactivate %p", this));
1836 // The Inactivate the recorder algorithm given a recorder, is as follows:
1838 // 1. Set recorder’s mimeType attribute to the value of the
1839 // [[ConstrainedMimeType]] slot.
1840 mMimeType = mConstrainedMimeType;
1842 // 2. Set recorder’s state attribute to inactive.
1843 mState = RecordingState::Inactive;
1845 // 3. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1846 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1847 // values the User Agent deems reasonable for the respective media types,
1848 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
1849 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
1850 if (mConstrainedBitsPerSecond) {
1851 SelectBitrates(*mConstrainedBitsPerSecond, 1, &mVideoBitsPerSecond, 1,
1852 &mAudioBitsPerSecond);
1856 void MediaRecorder::InitializeDomExceptions() {
1857 mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
1858 mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
1861 RefPtr<MediaRecorder::SizeOfPromise> MediaRecorder::SizeOfExcludingThis(
1862 mozilla::MallocSizeOf aMallocSizeOf) {
1863 MOZ_ASSERT(NS_IsMainThread());
1865 // The return type of a chained MozPromise cannot be changed, so we create a
1866 // holder for our desired return type and resolve that from All()->Then().
1867 auto holder = MakeRefPtr<Refcountable<MozPromiseHolder<SizeOfPromise>>>();
1868 RefPtr<SizeOfPromise> promise = holder->Ensure(__func__);
1870 nsTArray<RefPtr<SizeOfPromise>> promises(mSessions.Length());
1871 for (const RefPtr<Session>& session : mSessions) {
1872 promises.AppendElement(session->SizeOfExcludingThis(aMallocSizeOf));
1875 SizeOfPromise::All(GetCurrentSerialEventTarget(), promises)
1876 ->Then(
1877 GetCurrentSerialEventTarget(), __func__,
1878 [holder](const nsTArray<size_t>& sizes) {
1879 size_t total = 0;
1880 for (const size_t& size : sizes) {
1881 total += size;
1883 holder->Resolve(total, __func__);
1885 []() { MOZ_CRASH("Unexpected reject"); });
1887 return promise;
1890 StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
1892 } // namespace mozilla::dom
1894 #undef LOG