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 (!streamPrincipal
) {
1246 // This is more or less part of the step 7, see below.
1247 aResult
.ThrowNotSupportedError("The MediaStream is inactive");
1251 if (!PrincipalSubsumes(this, streamPrincipal
)) {
1252 aResult
.ThrowSecurityError(
1253 "The MediaStream's isolation properties disallow access from "
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 "
1267 // 7. If stream is inactive, throw a NotSupportedError DOMException and abort
1269 if (mStream
&& !mStream
->Active()) {
1270 aResult
.ThrowNotSupportedError("The MediaStream is inactive");
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(
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
) {
1293 aResult
.ThrowNotSupportedError(nsPrintfCString(
1294 "%s track cannot be recorded: %s",
1295 track
->AsAudioStreamTrack() ? "An audio" : "A video",
1296 TypeSupportToCString(support
, mConstrainedMimeType
).get()));
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()));
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
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
) {
1322 } else if (t
->AsAudioStreamTrack() && numAudioTracks
< UINT8_MAX
) {
1327 MOZ_DIAGNOSTIC_ASSERT(!mStream
);
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
);
1364 if (numAudioTracks
> 1) {
1365 aResult
.ThrowNotSupportedError(
1366 "MediaRecorder does not support recording more than one audio track"_ns
);
1370 // 14. Set recorder’s state to recording
1371 mState
= RecordingState::Recording
;
1373 MediaRecorderReporter::AddMediaRecorder(this);
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
1388 // 1. Let recorder be the MediaRecorder object on which the method was
1391 // 2. If recorder’s state attribute is inactive, abort these steps.
1392 if (mState
== RecordingState::Inactive
) {
1396 // 3. Inactivate the recorder with recorder.
1399 // 4. Queue a task, using the DOM manipulation task source, that runs the
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
1419 if (mState
== RecordingState::Inactive
) {
1420 aResult
.ThrowInvalidStateError("The MediaRecorder is inactive");
1424 // 2. If state is paused, abort these steps.
1425 if (mState
== RecordingState::Paused
) {
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
);
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
1458 if (mState
== RecordingState::Inactive
) {
1459 aResult
.ThrowInvalidStateError("The MediaRecorder is inactive");
1463 // 2. If state is recording, abort these steps.
1464 if (mState
== RecordingState::Recording
) {
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
);
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");
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
);
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());
1524 aRv
.Throw(NS_ERROR_FAILURE
);
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
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
));
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())
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();
1602 already_AddRefed
<MediaRecorder
> MediaRecorder::Constructor(
1603 const GlobalObject
& aGlobal
, AudioNode
& aAudioNode
,
1604 uint32_t aAudioNodeOutput
, const MediaRecorderOptions
& aOptions
,
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",
1614 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
=
1615 do_QueryInterface(aGlobal
.GetAsSupports());
1617 aRv
.Throw(NS_ERROR_FAILURE
);
1621 // aAudioNodeOutput doesn't matter to destination node because it has no
1623 if (aAudioNode
.NumberOfOutputs() > 0 &&
1624 aAudioNodeOutput
>= aAudioNode
.NumberOfOutputs()) {
1625 aRv
.ThrowIndexSizeError("Invalid AudioNode output index");
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
1638 // 3. If invoking is type supported with options’ mimeType member as its
1639 // argument returns false, throw a NotSupportedError DOMException and abort
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
));
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())
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();
1707 bool MediaRecorder::IsTypeSupported(GlobalObject
& aGlobal
,
1708 const nsAString
& aMIMEType
) {
1709 return MediaRecorder::IsTypeSupported(aMIMEType
);
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
;
1731 init
.mBubbles
= false;
1732 init
.mCancelable
= false;
1735 RefPtr
<BlobEvent
> event
=
1736 BlobEvent::Constructor(this, u
"dataavailable"_ns
, init
);
1737 event
->SetTrusted(true);
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
)) {
1750 rv
= DOMEventTargetHelper::DispatchTrustedEvent(aStr
);
1751 if (NS_FAILED(rv
)) {
1752 LOG(LogLevel::Error
,
1753 ("MediaRecorder.DispatchSimpleEvent: DispatchTrustedEvent failed %p",
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
)) {
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.
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
);
1781 if (mOtherDomException
&& aRv
== mOtherDomException
->GetResult()) {
1782 LOG(LogLevel::Debug
, ("MediaRecorder.NotifyError: "
1783 "mOtherDomException being fired for aRv: %X",
1785 init
.mError
= std::move(mOtherDomException
);
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",
1796 init
.mError
= std::move(mUnknownDomException
);
1800 RefPtr
<MediaRecorderErrorEvent
> event
=
1801 MediaRecorderErrorEvent::Constructor(this, u
"error"_ns
, init
);
1802 event
->SetTrusted(true);
1804 IgnoredErrorResult res
;
1805 DispatchEvent(*event
, res
);
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 "
1825 this, doc
->IsActive(), doc
->IsVisible()));
1826 if (!doc
->IsActive() || !doc
->IsVisible()) {
1827 // Stop the session.
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
)
1877 GetCurrentSerialEventTarget(), __func__
,
1878 [holder
](const nsTArray
<size_t>& sizes
) {
1880 for (const size_t& size
: sizes
) {
1883 holder
->Resolve(total
, __func__
);
1885 []() { MOZ_CRASH("Unexpected reject"); });
1890 StaticRefPtr
<MediaRecorderReporter
> MediaRecorderReporter::sUniqueInstance
;
1892 } // namespace mozilla::dom