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"
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"
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
;
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
{
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
) {
77 sUniqueInstance
->mRecorders
.RemoveElement(aRecorder
);
78 if (sUniqueInstance
->mRecorders
.IsEmpty()) {
79 UnregisterWeakMemoryReporter(sUniqueInstance
);
80 sUniqueInstance
= nullptr;
84 NS_DECL_THREADSAFE_ISUPPORTS
86 MediaRecorderReporter() = default;
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
)
100 GetCurrentSerialEventTarget(), __func__
,
101 [handleReport
, data
](const nsTArray
<size_t>& sizes
) {
102 nsCOMPtr
<nsIMemoryReporterManager
> manager
=
103 do_GetService("@mozilla.org/memory-reporter-manager;1");
109 for (const size_t& size
: sizes
) {
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"); });
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
)
168 bool PrincipalSubsumes(MediaRecorder
* aRecorder
, nsIPrincipal
* aPrincipal
) {
169 if (!aRecorder
->GetOwner()) {
172 nsCOMPtr
<Document
> doc
= aRecorder
->GetOwner()->GetExtantDoc();
180 if (NS_FAILED(doc
->NodePrincipal()->Subsumes(aPrincipal
, &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
);
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
{
211 NoVideoWithAudioType
,
214 ContainerUnsupported
,
220 nsCString
TypeSupportToCString(TypeSupport aSupport
,
221 const nsAString
& aMimeType
) {
222 nsAutoCString mime
= NS_ConvertUTF16toUTF8(aMimeType
);
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",
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",
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",
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
;
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
;
308 for (const auto& codec
: aMimeType
->ExtendedType().Codecs().Range()) {
309 // Ignore video codecs.
310 if (codec
.EqualsLiteral("vp8")) {
313 if (codec
.EqualsLiteral("vp8.0")) {
316 if (codec
.EqualsLiteral("opus")) {
317 // All containers support opus
325 // Unsupported codec.
326 return TypeSupport::CodecUnsupported
;
330 // Codecs specified but not opus. Unsupported for audio.
331 return TypeSupport::CodecUnsupported
;
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
;
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
;
380 for (const auto& codec
: aMimeType
->ExtendedType().Codecs().Range()) {
381 if (codec
.EqualsLiteral("opus")) {
382 // Ignore audio codecs.
385 if (codec
.EqualsLiteral("vp8")) {
389 if (codec
.EqualsLiteral("vp8.0")) {
397 // Unsupported codec.
398 return TypeSupport::CodecUnsupported
;
402 // Codecs specified but not vp8. Unsupported for video.
403 return TypeSupport::CodecUnsupported
;
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
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());
456 if (constrainedType
&& constrainedType
->ExtendedType().HaveCodecs()) {
457 // The constrained mime type is fully defined (it has codecs!). No need to
459 CopyUTF8toUTF16(constrainedType
->OriginalString(), result
);
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());
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
);
479 majorType
= nsLiteralCString(AUDIO_OGG
);
485 if (aHasVideo
&& aHasAudio
) {
486 codecs
= "\"vp8, opus\""_ns
;
487 } else if (aHasVideo
) {
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
);
506 void SelectBitrates(uint32_t aBitsPerSecond
, uint8_t aNumVideoTracks
,
507 uint32_t* aOutVideoBps
, uint8_t aNumAudioTracks
,
508 uint32_t* aOutAudioBps
) {
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
));
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
;
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
{
579 static bool Equals(const RefPtr
<MediaStreamTrack
>& aTrack
, Type aType
) {
580 return (aType
== AUDIO
&& aTrack
->AsAudioStreamTrack()) ||
581 (aType
== VIDEO
&& aTrack
->AsVideoStreamTrack());
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",
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.
634 LOG(LogLevel::Warning
,
635 ("Session.NotifyTrackRemoved %p Raising error due to track set change",
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
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
;
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(),
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
);
687 MOZ_ASSERT(false, "Unknown source");
691 LOG(LogLevel::Debug
, ("Session.Stop %p", this));
692 MOZ_ASSERT(NS_IsMainThread());
695 mEncoder
->DisconnectTracks();
698 // Remove main thread state added in Start().
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
721 mStartedListener
.DisconnectIfExists();
722 NS_DispatchToMainThread(NewRunnableMethod(
723 "MediaRecorder::Session::Stop", this, &Session::OnStarted
));
725 mRunningState
= RunningState::Stopping
;
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
) {
739 MOZ_ASSERT(mEncoder
);
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
) {
753 MOZ_ASSERT(mEncoder
);
758 LOG(LogLevel::Debug
, ("Session.RequestData"));
759 MOZ_ASSERT(NS_IsMainThread());
760 MOZ_ASSERT(mEncoder
);
762 InvokeAsync(mEncoderThread
, mEncoder
.get(), __func__
,
763 &MediaEncoder::RequestData
)
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());
775 mRecorder
->CreateAndDispatchBlobEvent(aRrv
.ResolveValue());
777 DoSessionEndTask(NS_OK
);
783 RefPtr
<SizeOfPromise
> SizeOfExcludingThis(
784 mozilla::MallocSizeOf aMallocSizeOf
) {
785 MOZ_ASSERT(NS_IsMainThread());
787 return SizeOfPromise::CreateAndResolve(0, __func__
);
790 return mEncoder
->SizeOfExcludingThis(aMallocSizeOf
);
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");
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
);
817 LOG(LogLevel::Debug
, ("Session.InitEncoder %p Failed to create "
818 "MediaRecorderReadThread thread pool",
820 DoSessionEndTask(NS_ERROR_FAILURE
);
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
;
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
);
842 nsCOMPtr
<nsIAsyncShutdownClient
> barrier
= GetShutdownBarrier();
845 ("Session.InitEncoder %p Failed to get shutdown barrier", this));
846 DoSessionEndTask(NS_ERROR_FAILURE
);
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
);
866 LOG(LogLevel::Error
, ("Session.InitEncoder !mEncoder %p", this));
867 DoSessionEndTask(NS_ERROR_ABORT
);
871 mStartedListener
= mEncoder
->StartedEvent().Connect(mMainThread
, this,
872 &Session::OnStarted
);
873 mDataAvailableListener
= mEncoder
->DataAvailableEvent().Connect(
874 mMainThread
, this, &Session::OnDataAvailable
);
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.
909 if (mRunningState
.isOk() &&
910 mRunningState
.inspect() == RunningState::Stopped
) {
911 // We have already ended gracefully.
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
925 mRunningState
= RunningState::Stopped
;
927 mRunningState
= Err(rv
);
930 RefPtr
<MediaEncoder::BlobPromise
> blobPromise
;
932 blobPromise
= MediaEncoder::BlobPromise::CreateAndReject(NS_OK
, __func__
);
935 (rv
== NS_ERROR_ABORT
|| rv
== NS_ERROR_DOM_SECURITY_ERR
938 ->Then(mEncoderThread
, __func__
,
939 [encoder
= mEncoder
](
940 const GenericNonExclusivePromise::ResolveOrRejectValue
&
942 MOZ_DIAGNOSTIC_ASSERT(aValue
.IsResolve());
943 return encoder
->RequestData();
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
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
973 // In case we failed to gather blob data, we create an empty
974 // memory blob instead.
975 blobImpl
= new EmptyBlobImpl(mMimeType
);
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
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
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
) {
1001 MustGetShutdownBarrier()->RemoveBlocker(mShutdownBlocker
);
1002 mShutdownBlocker
= nullptr;
1007 MOZ_ASSERT(NS_IsMainThread());
1008 if (mRunningState
.isErr()) {
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
) {
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
);
1039 MOZ_ASSERT(NS_IsMainThread());
1040 DoSessionEndTask(NS_ERROR_FAILURE
);
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__
);
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.
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__
);
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();
1115 MOZ_ASSERT_UNREACHABLE("Unexpected reject");
1116 return ShutdownPromise::CreateAndReject(false, __func__
);
1120 return mShutdownPromise
;
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
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
;
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();
1189 mDocument
->RegisterActivityObserver(
1190 NS_ISUPPORTS_CAST(nsIDocumentActivity
*, this));
1195 void MediaRecorder::UnRegisterActivityObserver() {
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
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
;
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");
1241 // 6. If the isolation properties of stream disallow access from recorder,
1242 // throw a SecurityError DOMException and abort these steps.
1244 RefPtr
<nsIPrincipal
> streamPrincipal
= mStream
->GetPrincipal();
1245 if (!PrincipalSubsumes(this, streamPrincipal
)) {
1246 aResult
.ThrowSecurityError(
1247 "The MediaStream's isolation properties disallow access from "
1252 if (mAudioNode
&& !AudioNodePrincipalSubsumes(this, mAudioNode
)) {
1253 LOG(LogLevel::Warning
,
1254 ("MediaRecorder %p Start AudioNode principal check failed", this));
1255 aResult
.ThrowSecurityError(
1256 "The AudioNode's isolation properties disallow access from "
1261 // 7. If stream is inactive, throw a NotSupportedError DOMException and abort
1263 if (mStream
&& !mStream
->Active()) {
1264 aResult
.ThrowNotSupportedError("The MediaStream is inactive");
1268 // 8. If the [[ConstrainedMimeType]] slot specifies a media type, container,
1269 // or codec, then run the following sub steps:
1270 // 1. Constrain the configuration of recorder to the media type, container,
1271 // and codec specified in the [[ConstrainedMimeType]] slot.
1272 // 2. For each track in tracks, if the User Agent cannot record the track
1273 // using the current configuration, then throw a NotSupportedError
1274 // DOMException and abort all steps.
1275 Maybe
<MediaContainerType
> mime
;
1276 if (mConstrainedMimeType
.Length() > 0) {
1277 mime
= MakeMediaContainerType(mConstrainedMimeType
);
1278 MOZ_DIAGNOSTIC_ASSERT(
1280 "Invalid media MIME type should have been caught by IsTypeSupported");
1282 for (const auto& track
: tracks
) {
1283 TypeSupport support
= CanRecordWith(track
, mime
, mConstrainedMimeType
);
1284 if (support
!= TypeSupport::Supported
) {
1287 aResult
.ThrowNotSupportedError(nsPrintfCString(
1288 "%s track cannot be recorded: %s",
1289 track
->AsAudioStreamTrack() ? "An audio" : "A video",
1290 TypeSupportToCString(support
, mConstrainedMimeType
).get()));
1295 TypeSupport support
= CanRecordAudioTrackWith(mime
, mConstrainedMimeType
);
1296 if (support
!= TypeSupport::Supported
) {
1297 aResult
.ThrowNotSupportedError(nsPrintfCString(
1298 "An AudioNode cannot be recorded: %s",
1299 TypeSupportToCString(support
, mConstrainedMimeType
).get()));
1304 // 9. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1305 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1306 // values the User Agent deems reasonable for the respective media types,
1307 // for recording all tracks in tracks, such that the sum of
1308 // videoBitsPerSecond and audioBitsPerSecond is close to the value of
1310 // [[ConstrainedBitsPerSecond]] slot.
1311 uint8_t numVideoTracks
= 0;
1312 uint8_t numAudioTracks
= 0;
1313 for (const auto& t
: tracks
) {
1314 if (t
->AsVideoStreamTrack() && numVideoTracks
< UINT8_MAX
) {
1316 } else if (t
->AsAudioStreamTrack() && numAudioTracks
< UINT8_MAX
) {
1321 MOZ_DIAGNOSTIC_ASSERT(!mStream
);
1324 if (mConstrainedBitsPerSecond
) {
1325 SelectBitrates(*mConstrainedBitsPerSecond
, numVideoTracks
,
1326 &mVideoBitsPerSecond
, numAudioTracks
, &mAudioBitsPerSecond
);
1329 // 10. Let videoBitrate be the value of recorder’s videoBitsPerSecond
1330 // attribute, and constrain the configuration of recorder to target an
1331 // aggregate bitrate of videoBitrate bits per second for all video tracks
1332 // recorder will be recording. videoBitrate is a hint for the encoder and
1333 // the value might be surpassed, not achieved, or only be achieved over a
1334 // long period of time.
1335 const uint32_t videoBitrate
= mVideoBitsPerSecond
;
1337 // 11. Let audioBitrate be the value of recorder’s audioBitsPerSecond
1338 // attribute, and constrain the configuration of recorder to target an
1339 // aggregate bitrate of audioBitrate bits per second for all audio tracks
1340 // recorder will be recording. audioBitrate is a hint for the encoder and
1341 // the value might be surpassed, not achieved, or only be achieved over a
1342 // long period of time.
1343 const uint32_t audioBitrate
= mAudioBitsPerSecond
;
1345 // 12. Constrain the configuration of recorder to encode using the BitrateMode
1346 // specified by the value of recorder’s audioBitrateMode attribute for all
1347 // audio tracks recorder will be recording.
1348 // -- NOT IMPLEMENTED
1350 // 13. For each track in tracks, if the User Agent cannot record the track
1351 // using the current configuration, then throw a NotSupportedError
1352 // DOMException and abort these steps.
1353 if (numVideoTracks
> 1) {
1354 aResult
.ThrowNotSupportedError(
1355 "MediaRecorder does not support recording more than one video track"_ns
);
1358 if (numAudioTracks
> 1) {
1359 aResult
.ThrowNotSupportedError(
1360 "MediaRecorder does not support recording more than one audio track"_ns
);
1364 // 14. Set recorder’s state to recording
1365 mState
= RecordingState::Recording
;
1367 MediaRecorderReporter::AddMediaRecorder(this);
1369 mSessions
.AppendElement();
1370 mSessions
.LastElement() =
1371 new Session(this, std::move(tracks
), videoBitrate
, audioBitrate
);
1372 mSessions
.LastElement()->Start(timeslice
);
1375 void MediaRecorder::Stop(ErrorResult
& aResult
) {
1376 LOG(LogLevel::Debug
, ("MediaRecorder.Stop %p", this));
1377 MediaRecorderReporter::RemoveMediaRecorder(this);
1379 // When a MediaRecorder object’s stop() method is invoked, the UA MUST run the
1382 // 1. Let recorder be the MediaRecorder object on which the method was
1385 // 2. If recorder’s state attribute is inactive, abort these steps.
1386 if (mState
== RecordingState::Inactive
) {
1390 // 3. Inactivate the recorder with recorder.
1393 // 4. Queue a task, using the DOM manipulation task source, that runs the
1395 // 1. Stop gathering data.
1396 // 2. Let blob be the Blob of collected data so far, then fire a blob event
1397 // named dataavailable at recorder with blob.
1398 // 3. Fire an event named stop at recorder.
1399 MOZ_ASSERT(mSessions
.Length() > 0);
1400 mSessions
.LastElement()->Stop();
1402 // 5. return undefined.
1405 void MediaRecorder::Pause(ErrorResult
& aResult
) {
1406 LOG(LogLevel::Debug
, ("MediaRecorder.Pause %p", this));
1408 // When a MediaRecorder object’s pause() method is invoked, the UA MUST run
1409 // the following steps:
1411 // 1. If state is inactive, throw an InvalidStateError DOMException and abort
1413 if (mState
== RecordingState::Inactive
) {
1414 aResult
.ThrowInvalidStateError("The MediaRecorder is inactive");
1418 // 2. If state is paused, abort these steps.
1419 if (mState
== RecordingState::Paused
) {
1423 // 3. Set state to paused, and queue a task, using the DOM manipulation task
1424 // source, that runs the following steps:
1425 mState
= RecordingState::Paused
;
1427 // XXX - We pause synchronously pending spec issue
1428 // https://github.com/w3c/mediacapture-record/issues/131
1429 // 1. Stop gathering data into blob (but keep it available so that
1430 // recording can be resumed in the future).
1431 MOZ_ASSERT(!mSessions
.IsEmpty());
1432 mSessions
.LastElement()->Pause();
1434 NS_DispatchToMainThread(NS_NewRunnableFunction(
1435 "MediaRecorder::Pause", [recorder
= RefPtr
<MediaRecorder
>(this)] {
1436 // 2. Let target be the MediaRecorder context object. Fire an event
1437 // named pause at target.
1438 recorder
->DispatchSimpleEvent(u
"pause"_ns
);
1441 // 4. return undefined.
1444 void MediaRecorder::Resume(ErrorResult
& aResult
) {
1445 LOG(LogLevel::Debug
, ("MediaRecorder.Resume %p", this));
1447 // When a MediaRecorder object’s resume() method is invoked, the UA MUST run
1448 // the following steps:
1450 // 1. If state is inactive, throw an InvalidStateError DOMException and abort
1452 if (mState
== RecordingState::Inactive
) {
1453 aResult
.ThrowInvalidStateError("The MediaRecorder is inactive");
1457 // 2. If state is recording, abort these steps.
1458 if (mState
== RecordingState::Recording
) {
1462 // 3. Set state to recording, and queue a task, using the DOM manipulation
1463 // task source, that runs the following steps:
1464 mState
= RecordingState::Recording
;
1466 // XXX - We resume synchronously pending spec issue
1467 // https://github.com/w3c/mediacapture-record/issues/131
1468 // 1. Resume (or continue) gathering data into the current blob.
1469 MOZ_ASSERT(!mSessions
.IsEmpty());
1470 mSessions
.LastElement()->Resume();
1472 NS_DispatchToMainThread(NS_NewRunnableFunction(
1473 "MediaRecorder::Resume", [recorder
= RefPtr
<MediaRecorder
>(this)] {
1474 // 2. Let target be the MediaRecorder context object. Fire an event
1475 // named resume at target.
1476 recorder
->DispatchSimpleEvent(u
"resume"_ns
);
1479 // 4. return undefined.
1482 void MediaRecorder::RequestData(ErrorResult
& aResult
) {
1483 LOG(LogLevel::Debug
, ("MediaRecorder.RequestData %p", this));
1485 // When a MediaRecorder object’s requestData() method is invoked, the UA MUST
1486 // run the following steps:
1488 // 1. If state is inactive throw an InvalidStateError DOMException and
1489 // terminate these steps. Otherwise the UA MUST queue a task, using the DOM
1490 // manipulation task source, that runs the following steps:
1491 // 1. Let blob be the Blob of collected data so far and let target be the
1492 // MediaRecorder context object, then fire a blob event named
1493 // dataavailable at target with blob. (Note that blob will be empty if no
1494 // data has been gathered yet.)
1495 // 2. Create a new Blob and gather subsequent data into it.
1496 if (mState
== RecordingState::Inactive
) {
1497 aResult
.ThrowInvalidStateError("The MediaRecorder is inactive");
1500 MOZ_ASSERT(mSessions
.Length() > 0);
1501 mSessions
.LastElement()->RequestData();
1503 // 2. return undefined.
1506 JSObject
* MediaRecorder::WrapObject(JSContext
* aCx
,
1507 JS::Handle
<JSObject
*> aGivenProto
) {
1508 return MediaRecorder_Binding::Wrap(aCx
, this, aGivenProto
);
1512 already_AddRefed
<MediaRecorder
> MediaRecorder::Constructor(
1513 const GlobalObject
& aGlobal
, DOMMediaStream
& aStream
,
1514 const MediaRecorderOptions
& aOptions
, ErrorResult
& aRv
) {
1515 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
=
1516 do_QueryInterface(aGlobal
.GetAsSupports());
1518 aRv
.Throw(NS_ERROR_FAILURE
);
1522 // When the MediaRecorder() constructor is invoked, the User Agent MUST run
1523 // the following steps:
1525 // 1. Let stream be the constructor’s first argument.
1527 // 2. Let options be the constructor’s second argument.
1529 // 3. If invoking is type supported with options’ mimeType member as its
1530 // argument returns false, throw a NotSupportedError DOMException and abort
1532 TypeSupport support
= IsTypeSupportedImpl(aOptions
.mMimeType
);
1533 if (support
!= TypeSupport::Supported
) {
1534 // This catches also the empty string mimeType when support for any encoders
1535 // has been disabled.
1536 aRv
.ThrowNotSupportedError(
1537 TypeSupportToCString(support
, aOptions
.mMimeType
));
1541 // 4. Let recorder be a newly constructed MediaRecorder object.
1542 RefPtr
<MediaRecorder
> recorder
= new MediaRecorder(ownerWindow
);
1544 // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
1545 // to the value of options' mimeType member.
1546 recorder
->mConstrainedMimeType
= aOptions
.mMimeType
;
1548 // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
1549 // initialized to the value of options’ bitsPerSecond member, if it is
1550 // present, otherwise undefined.
1551 recorder
->mConstrainedBitsPerSecond
=
1552 aOptions
.mBitsPerSecond
.WasPassed()
1553 ? Some(aOptions
.mBitsPerSecond
.Value())
1556 // 7. Initialize recorder’s stream attribute to stream.
1557 recorder
->mStream
= &aStream
;
1559 // 8. Initialize recorder’s mimeType attribute to the value of recorder’s
1560 // [[ConstrainedMimeType]] slot.
1561 recorder
->mMimeType
= recorder
->mConstrainedMimeType
;
1563 // 9. Initialize recorder’s state attribute to inactive.
1564 recorder
->mState
= RecordingState::Inactive
;
1566 // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
1567 // options’ videoBitsPerSecond member, if it is present. Otherwise, choose
1568 // a target value the User Agent deems reasonable for video.
1569 recorder
->mVideoBitsPerSecond
= aOptions
.mVideoBitsPerSecond
.WasPassed()
1570 ? aOptions
.mVideoBitsPerSecond
.Value()
1571 : DEFAULT_VIDEO_BITRATE_BPS
;
1573 // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
1574 // options’ audioBitsPerSecond member, if it is present. Otherwise, choose
1575 // a target value the User Agent deems reasonable for audio.
1576 recorder
->mAudioBitsPerSecond
= aOptions
.mAudioBitsPerSecond
.WasPassed()
1577 ? aOptions
.mAudioBitsPerSecond
.Value()
1578 : DEFAULT_AUDIO_BITRATE_BPS
;
1580 // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1581 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1582 // values the User Agent deems reasonable for the respective media types,
1583 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
1584 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
1585 if (recorder
->mConstrainedBitsPerSecond
) {
1586 SelectBitrates(*recorder
->mConstrainedBitsPerSecond
, 1,
1587 &recorder
->mVideoBitsPerSecond
, 1,
1588 &recorder
->mAudioBitsPerSecond
);
1591 // 13. Return recorder.
1592 return recorder
.forget();
1596 already_AddRefed
<MediaRecorder
> MediaRecorder::Constructor(
1597 const GlobalObject
& aGlobal
, AudioNode
& aAudioNode
,
1598 uint32_t aAudioNodeOutput
, const MediaRecorderOptions
& aOptions
,
1600 // Allow recording from audio node only when pref is on.
1601 if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
1602 // Pretending that this constructor is not defined.
1603 aRv
.ThrowTypeError
<MSG_DOES_NOT_IMPLEMENT_INTERFACE
>("Argument 1",
1608 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
=
1609 do_QueryInterface(aGlobal
.GetAsSupports());
1611 aRv
.Throw(NS_ERROR_FAILURE
);
1615 // aAudioNodeOutput doesn't matter to destination node because it has no
1617 if (aAudioNode
.NumberOfOutputs() > 0 &&
1618 aAudioNodeOutput
>= aAudioNode
.NumberOfOutputs()) {
1619 aRv
.ThrowIndexSizeError("Invalid AudioNode output index");
1623 // When the MediaRecorder() constructor is invoked, the User Agent MUST run
1624 // the following steps:
1626 // 1. Let stream be the constructor’s first argument. (we'll let audioNode be
1627 // the first arg, and audioNodeOutput the second)
1629 // 2. Let options be the constructor’s second argument. (we'll let options be
1632 // 3. If invoking is type supported with options’ mimeType member as its
1633 // argument returns false, throw a NotSupportedError DOMException and abort
1635 TypeSupport support
= IsTypeSupportedImpl(aOptions
.mMimeType
);
1636 if (support
!= TypeSupport::Supported
) {
1637 // This catches also the empty string mimeType when support for any encoders
1638 // has been disabled.
1639 aRv
.ThrowNotSupportedError(
1640 TypeSupportToCString(support
, aOptions
.mMimeType
));
1644 // 4. Let recorder be a newly constructed MediaRecorder object.
1645 RefPtr
<MediaRecorder
> recorder
= new MediaRecorder(ownerWindow
);
1647 // 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
1648 // to the value of options' mimeType member.
1649 recorder
->mConstrainedMimeType
= aOptions
.mMimeType
;
1651 // 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
1652 // initialized to the value of options’ bitsPerSecond member, if it is
1653 // present, otherwise undefined.
1654 recorder
->mConstrainedBitsPerSecond
=
1655 aOptions
.mBitsPerSecond
.WasPassed()
1656 ? Some(aOptions
.mBitsPerSecond
.Value())
1659 // 7. Initialize recorder’s stream attribute to stream. (make that the
1660 // audioNode and audioNodeOutput equivalents)
1661 recorder
->mAudioNode
= &aAudioNode
;
1662 recorder
->mAudioNodeOutput
= aAudioNodeOutput
;
1664 // 8. Initialize recorder’s mimeType attribute to the value of recorder’s
1665 // [[ConstrainedMimeType]] slot.
1666 recorder
->mMimeType
= recorder
->mConstrainedMimeType
;
1668 // 9. Initialize recorder’s state attribute to inactive.
1669 recorder
->mState
= RecordingState::Inactive
;
1671 // 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
1672 // options’ videoBitsPerSecond member, if it is present. Otherwise, choose
1673 // a target value the User Agent deems reasonable for video.
1674 recorder
->mVideoBitsPerSecond
= aOptions
.mVideoBitsPerSecond
.WasPassed()
1675 ? aOptions
.mVideoBitsPerSecond
.Value()
1676 : DEFAULT_VIDEO_BITRATE_BPS
;
1678 // 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
1679 // options’ audioBitsPerSecond member, if it is present. Otherwise, choose
1680 // a target value the User Agent deems reasonable for audio.
1681 recorder
->mAudioBitsPerSecond
= aOptions
.mAudioBitsPerSecond
.WasPassed()
1682 ? aOptions
.mAudioBitsPerSecond
.Value()
1683 : DEFAULT_AUDIO_BITRATE_BPS
;
1685 // 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1686 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1687 // values the User Agent deems reasonable for the respective media types,
1688 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
1689 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
1690 if (recorder
->mConstrainedBitsPerSecond
) {
1691 SelectBitrates(*recorder
->mConstrainedBitsPerSecond
, 1,
1692 &recorder
->mVideoBitsPerSecond
, 1,
1693 &recorder
->mAudioBitsPerSecond
);
1696 // 13. Return recorder.
1697 return recorder
.forget();
1701 bool MediaRecorder::IsTypeSupported(GlobalObject
& aGlobal
,
1702 const nsAString
& aMIMEType
) {
1703 return MediaRecorder::IsTypeSupported(aMIMEType
);
1707 bool MediaRecorder::IsTypeSupported(const nsAString
& aMIMEType
) {
1708 return IsTypeSupportedImpl(aMIMEType
) == TypeSupport::Supported
;
1711 nsresult
MediaRecorder::CreateAndDispatchBlobEvent(BlobImpl
* aBlobImpl
) {
1712 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1714 if (!GetOwnerGlobal()) {
1715 // This MediaRecorder has been disconnected in the meantime.
1716 return NS_ERROR_FAILURE
;
1719 RefPtr
<Blob
> blob
= Blob::Create(GetOwnerGlobal(), aBlobImpl
);
1720 if (NS_WARN_IF(!blob
)) {
1721 return NS_ERROR_FAILURE
;
1725 init
.mBubbles
= false;
1726 init
.mCancelable
= false;
1729 RefPtr
<BlobEvent
> event
=
1730 BlobEvent::Constructor(this, u
"dataavailable"_ns
, init
);
1731 event
->SetTrusted(true);
1733 DispatchEvent(*event
, rv
);
1734 return rv
.StealNSResult();
1737 void MediaRecorder::DispatchSimpleEvent(const nsAString
& aStr
) {
1738 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1739 nsresult rv
= CheckCurrentGlobalCorrectness();
1740 if (NS_FAILED(rv
)) {
1744 rv
= DOMEventTargetHelper::DispatchTrustedEvent(aStr
);
1745 if (NS_FAILED(rv
)) {
1746 LOG(LogLevel::Error
,
1747 ("MediaRecorder.DispatchSimpleEvent: DispatchTrustedEvent failed %p",
1749 NS_ERROR("Failed to dispatch the event!!!");
1753 void MediaRecorder::NotifyError(nsresult aRv
) {
1754 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1755 nsresult rv
= CheckCurrentGlobalCorrectness();
1756 if (NS_FAILED(rv
)) {
1759 MediaRecorderErrorEventInit init
;
1760 init
.mBubbles
= false;
1761 init
.mCancelable
= false;
1762 // These DOMExceptions have been created earlier so they can contain stack
1763 // traces. We attach the appropriate one here to be fired. We should have
1764 // exceptions here, but defensively check.
1766 case NS_ERROR_DOM_SECURITY_ERR
:
1767 if (!mSecurityDomException
) {
1768 LOG(LogLevel::Debug
, ("MediaRecorder.NotifyError: "
1769 "mSecurityDomException was not initialized"));
1770 mSecurityDomException
= DOMException::Create(NS_ERROR_DOM_SECURITY_ERR
);
1772 init
.mError
= std::move(mSecurityDomException
);
1775 if (mOtherDomException
&& aRv
== mOtherDomException
->GetResult()) {
1776 LOG(LogLevel::Debug
, ("MediaRecorder.NotifyError: "
1777 "mOtherDomException being fired for aRv: %X",
1779 init
.mError
= std::move(mOtherDomException
);
1782 if (!mUnknownDomException
) {
1783 LOG(LogLevel::Debug
, ("MediaRecorder.NotifyError: "
1784 "mUnknownDomException was not initialized"));
1785 mUnknownDomException
= DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR
);
1787 LOG(LogLevel::Debug
, ("MediaRecorder.NotifyError: "
1788 "mUnknownDomException being fired for aRv: %X",
1790 init
.mError
= std::move(mUnknownDomException
);
1794 RefPtr
<MediaRecorderErrorEvent
> event
=
1795 MediaRecorderErrorEvent::Constructor(this, u
"error"_ns
, init
);
1796 event
->SetTrusted(true);
1798 IgnoredErrorResult res
;
1799 DispatchEvent(*event
, res
);
1801 NS_ERROR("Failed to dispatch the error event!!!");
1805 void MediaRecorder::RemoveSession(Session
* aSession
) {
1806 LOG(LogLevel::Debug
, ("MediaRecorder.RemoveSession (%p)", aSession
));
1807 mSessions
.RemoveElement(aSession
);
1810 void MediaRecorder::NotifyOwnerDocumentActivityChanged() {
1811 nsPIDOMWindowInner
* window
= GetOwner();
1812 NS_ENSURE_TRUE_VOID(window
);
1813 Document
* doc
= window
->GetExtantDoc();
1814 NS_ENSURE_TRUE_VOID(doc
);
1816 LOG(LogLevel::Debug
, ("MediaRecorder %p NotifyOwnerDocumentActivityChanged "
1819 this, doc
->IsActive(), doc
->IsVisible()));
1820 if (!doc
->IsActive() || !doc
->IsVisible()) {
1821 // Stop the session.
1824 result
.SuppressException();
1828 void MediaRecorder::Inactivate() {
1829 LOG(LogLevel::Debug
, ("MediaRecorder.Inactivate %p", this));
1830 // The Inactivate the recorder algorithm given a recorder, is as follows:
1832 // 1. Set recorder’s mimeType attribute to the value of the
1833 // [[ConstrainedMimeType]] slot.
1834 mMimeType
= mConstrainedMimeType
;
1836 // 2. Set recorder’s state attribute to inactive.
1837 mState
= RecordingState::Inactive
;
1839 // 3. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1840 // recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1841 // values the User Agent deems reasonable for the respective media types,
1842 // such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
1843 // to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
1844 if (mConstrainedBitsPerSecond
) {
1845 SelectBitrates(*mConstrainedBitsPerSecond
, 1, &mVideoBitsPerSecond
, 1,
1846 &mAudioBitsPerSecond
);
1850 void MediaRecorder::InitializeDomExceptions() {
1851 mSecurityDomException
= DOMException::Create(NS_ERROR_DOM_SECURITY_ERR
);
1852 mUnknownDomException
= DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR
);
1855 RefPtr
<MediaRecorder::SizeOfPromise
> MediaRecorder::SizeOfExcludingThis(
1856 mozilla::MallocSizeOf aMallocSizeOf
) {
1857 MOZ_ASSERT(NS_IsMainThread());
1859 // The return type of a chained MozPromise cannot be changed, so we create a
1860 // holder for our desired return type and resolve that from All()->Then().
1861 auto holder
= MakeRefPtr
<Refcountable
<MozPromiseHolder
<SizeOfPromise
>>>();
1862 RefPtr
<SizeOfPromise
> promise
= holder
->Ensure(__func__
);
1864 nsTArray
<RefPtr
<SizeOfPromise
>> promises(mSessions
.Length());
1865 for (const RefPtr
<Session
>& session
: mSessions
) {
1866 promises
.AppendElement(session
->SizeOfExcludingThis(aMallocSizeOf
));
1869 SizeOfPromise::All(GetCurrentSerialEventTarget(), promises
)
1871 GetCurrentSerialEventTarget(), __func__
,
1872 [holder
](const nsTArray
<size_t>& sizes
) {
1874 for (const size_t& size
: sizes
) {
1877 holder
->Resolve(total
, __func__
);
1879 []() { MOZ_CRASH("Unexpected reject"); });
1884 StaticRefPtr
<MediaRecorderReporter
> MediaRecorderReporter::sUniqueInstance
;
1886 } // namespace mozilla::dom