Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim:set ts=2 sw=2 sts=2 et cindent: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "MediaRecorder.h"
8
9
#include "AudioNodeEngine.h"
10
#include "AudioNodeTrack.h"
11
#include "DOMMediaStream.h"
12
#include "GeckoProfiler.h"
13
#include "MediaDecoder.h"
14
#include "MediaEncoder.h"
15
#include "MediaTrackGraphImpl.h"
16
#include "VideoUtils.h"
17
#include "mozilla/DOMEventTargetHelper.h"
18
#include "mozilla/dom/AudioStreamTrack.h"
19
#include "mozilla/dom/BlobEvent.h"
20
#include "mozilla/dom/EmptyBlobImpl.h"
21
#include "mozilla/dom/File.h"
22
#include "mozilla/dom/MediaRecorderErrorEvent.h"
23
#include "mozilla/dom/MutableBlobStorage.h"
24
#include "mozilla/dom/VideoStreamTrack.h"
25
#include "mozilla/media/MediaUtils.h"
26
#include "mozilla/MemoryReporting.h"
27
#include "mozilla/Preferences.h"
28
#include "mozilla/StaticPtr.h"
29
#include "mozilla/TaskQueue.h"
30
#include "nsAutoPtr.h"
31
#include "nsCharSeparatedTokenizer.h"
32
#include "nsContentTypeParser.h"
33
#include "nsContentUtils.h"
34
#include "nsDocShell.h"
35
#include "nsError.h"
36
#include "mozilla/dom/Document.h"
37
#include "nsIPermissionManager.h"
38
#include "nsIPrincipal.h"
39
#include "nsIScriptError.h"
40
#include "nsMimeTypes.h"
41
#include "nsProxyRelease.h"
42
#include "nsTArray.h"
43
44
mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
45
#define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
46
47
#define MIN_VIDEO_BITRATE_BPS 10e3 // 10kbps
48
#define DEFAULT_VIDEO_BITRATE_BPS 2500e3 // 2.5Mbps
49
#define MAX_VIDEO_BITRATE_BPS 100e6 // 100Mbps
50
51
#define MIN_AUDIO_BITRATE_BPS 500 // 500bps
52
#define DEFAULT_AUDIO_BITRATE_BPS 128e3 // 128kbps
53
#define MAX_AUDIO_BITRATE_BPS 512e3 // 512kbps
54
55
namespace mozilla {
56
57
namespace dom {
58
59
using namespace mozilla::media;
60
61
/**
62
* MediaRecorderReporter measures memory being used by the Media Recorder.
63
*
64
* It is a singleton reporter and the single class object lives as long as at
65
* least one Recorder is registered. In MediaRecorder, the reporter is
66
* unregistered when it is destroyed.
67
*/
68
class MediaRecorderReporter final : public nsIMemoryReporter {
69
public:
70
static void AddMediaRecorder(MediaRecorder* aRecorder) {
71
if (!sUniqueInstance) {
72
sUniqueInstance = MakeAndAddRef<MediaRecorderReporter>();
73
RegisterWeakAsyncMemoryReporter(sUniqueInstance);
74
}
75
sUniqueInstance->mRecorders.AppendElement(aRecorder);
76
}
77
78
static void RemoveMediaRecorder(MediaRecorder* aRecorder) {
79
if (!sUniqueInstance) {
80
return;
81
}
82
83
sUniqueInstance->mRecorders.RemoveElement(aRecorder);
84
if (sUniqueInstance->mRecorders.IsEmpty()) {
85
UnregisterWeakMemoryReporter(sUniqueInstance);
86
sUniqueInstance = nullptr;
87
}
88
}
89
90
NS_DECL_THREADSAFE_ISUPPORTS
91
92
MediaRecorderReporter() = default;
93
94
NS_IMETHOD
95
CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
96
bool aAnonymize) override {
97
nsTArray<RefPtr<MediaRecorder::SizeOfPromise>> promises;
98
for (const RefPtr<MediaRecorder>& recorder : mRecorders) {
99
promises.AppendElement(recorder->SizeOfExcludingThis(MallocSizeOf));
100
}
101
102
nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
103
nsCOMPtr<nsISupports> data = aData;
104
MediaRecorder::SizeOfPromise::All(GetCurrentThreadSerialEventTarget(),
105
promises)
106
->Then(
107
GetCurrentThreadSerialEventTarget(), __func__,
108
[handleReport, data](const nsTArray<size_t>& sizes) {
109
nsCOMPtr<nsIMemoryReporterManager> manager =
110
do_GetService("@mozilla.org/memory-reporter-manager;1");
111
if (!manager) {
112
return;
113
}
114
115
size_t sum = 0;
116
for (const size_t& size : sizes) {
117
sum += size;
118
}
119
120
handleReport->Callback(
121
EmptyCString(), NS_LITERAL_CSTRING("explicit/media/recorder"),
122
KIND_HEAP, UNITS_BYTES, sum,
123
NS_LITERAL_CSTRING("Memory used by media recorder."), data);
124
125
manager->EndReport();
126
},
127
[](size_t) { MOZ_CRASH("Unexpected reject"); });
128
129
return NS_OK;
130
}
131
132
private:
133
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
134
135
virtual ~MediaRecorderReporter() {
136
MOZ_ASSERT(mRecorders.IsEmpty(), "All recorders must have been removed");
137
}
138
139
static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
140
141
nsTArray<RefPtr<MediaRecorder>> mRecorders;
142
};
143
NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
144
145
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
146
147
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
148
DOMEventTargetHelper)
149
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStream)
150
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
151
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityDomException)
152
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnknownDomException)
153
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
154
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
155
156
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
157
DOMEventTargetHelper)
158
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStream)
159
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
160
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityDomException)
161
NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnknownDomException)
162
tmp->UnRegisterActivityObserver();
163
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
164
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
165
166
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder)
167
NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
168
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
169
170
NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
171
NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
172
173
namespace {
174
bool PrincipalSubsumes(MediaRecorder* aRecorder, nsIPrincipal* aPrincipal) {
175
if (!aRecorder->GetOwner()) {
176
return false;
177
}
178
nsCOMPtr<Document> doc = aRecorder->GetOwner()->GetExtantDoc();
179
if (!doc) {
180
return false;
181
}
182
if (!aPrincipal) {
183
return false;
184
}
185
bool subsumes;
186
if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
187
return false;
188
}
189
return subsumes;
190
}
191
192
bool MediaStreamTracksPrincipalSubsumes(
193
MediaRecorder* aRecorder,
194
const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) {
195
nsCOMPtr<nsIPrincipal> principal = nullptr;
196
for (const auto& track : aTracks) {
197
nsContentUtils::CombineResourcePrincipals(&principal,
198
track->GetPrincipal());
199
}
200
return PrincipalSubsumes(aRecorder, principal);
201
}
202
203
bool AudioNodePrincipalSubsumes(MediaRecorder* aRecorder,
204
AudioNode* aAudioNode) {
205
MOZ_ASSERT(aAudioNode);
206
Document* doc =
207
aAudioNode->GetOwner() ? aAudioNode->GetOwner()->GetExtantDoc() : nullptr;
208
nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
209
return PrincipalSubsumes(aRecorder, principal);
210
}
211
212
enum class TypeSupport {
213
Supported,
214
MediaTypeInvalid,
215
NoVideoWithAudioType,
216
ContainersDisabled,
217
CodecsDisabled,
218
ContainerUnsupported,
219
CodecUnsupported,
220
CodecDuplicated,
221
};
222
223
nsCString TypeSupportToCString(TypeSupport aSupport,
224
const nsAString& aMimeType) {
225
nsAutoCString mime = NS_ConvertUTF16toUTF8(aMimeType);
226
switch (aSupport) {
227
case TypeSupport::Supported:
228
return nsPrintfCString("%s is supported", mime.get());
229
case TypeSupport::MediaTypeInvalid:
230
return nsPrintfCString("%s is not a valid media type", mime.get());
231
case TypeSupport::NoVideoWithAudioType:
232
return nsPrintfCString(
233
"Video cannot be recorded with %s as it is an audio type",
234
mime.get());
235
case TypeSupport::ContainersDisabled:
236
return NS_LITERAL_CSTRING("All containers are disabled");
237
case TypeSupport::CodecsDisabled:
238
return NS_LITERAL_CSTRING("All codecs are disabled");
239
case TypeSupport::ContainerUnsupported:
240
return nsPrintfCString("%s indicates an unsupported container",
241
mime.get());
242
case TypeSupport::CodecUnsupported:
243
return nsPrintfCString("%s indicates an unsupported codec", mime.get());
244
case TypeSupport::CodecDuplicated:
245
return nsPrintfCString("%s contains the same codec multiple times",
246
mime.get());
247
default:
248
MOZ_ASSERT_UNREACHABLE("Unknown TypeSupport");
249
return NS_LITERAL_CSTRING("Unknown error");
250
}
251
}
252
253
TypeSupport CanRecordAudioTrackWith(const Maybe<MediaContainerType>& aMimeType,
254
const nsAString& aMimeTypeString) {
255
if (aMimeTypeString.IsEmpty()) {
256
// For the empty string we just need to check whether we have support for an
257
// audio container and an audio codec.
258
if (!MediaEncoder::IsWebMEncoderEnabled() &&
259
!MediaDecoder::IsOggEnabled()) {
260
// No container support for audio.
261
return TypeSupport::ContainersDisabled;
262
}
263
264
if (!MediaDecoder::IsOpusEnabled()) {
265
// No codec support for audio.
266
return TypeSupport::CodecsDisabled;
267
}
268
269
return TypeSupport::Supported;
270
}
271
272
if (!aMimeType) {
273
// A mime type string was set, but it couldn't be parsed to a valid
274
// MediaContainerType.
275
return TypeSupport::MediaTypeInvalid;
276
}
277
278
if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM) &&
279
aMimeType->Type() != MEDIAMIMETYPE(AUDIO_WEBM) &&
280
aMimeType->Type() != MEDIAMIMETYPE(AUDIO_OGG)) {
281
// Any currently supported container can record audio.
282
return TypeSupport::ContainerUnsupported;
283
}
284
285
if (aMimeType->Type() == MEDIAMIMETYPE(VIDEO_WEBM) &&
286
!MediaEncoder::IsWebMEncoderEnabled()) {
287
return TypeSupport::ContainerUnsupported;
288
}
289
290
if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM) &&
291
!MediaEncoder::IsWebMEncoderEnabled()) {
292
return TypeSupport::ContainerUnsupported;
293
}
294
295
if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_OGG) &&
296
!MediaDecoder::IsOggEnabled()) {
297
return TypeSupport::ContainerUnsupported;
298
}
299
300
if (!MediaDecoder::IsOpusEnabled()) {
301
return TypeSupport::CodecUnsupported;
302
}
303
304
if (!aMimeType->ExtendedType().HaveCodecs()) {
305
// No codecs constrained, we can pick opus.
306
return TypeSupport::Supported;
307
}
308
309
size_t opus = 0;
310
size_t unknown = 0;
311
for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
312
// Ignore video codecs.
313
if (codec.EqualsLiteral("vp8")) {
314
continue;
315
}
316
if (codec.EqualsLiteral("vp8.0")) {
317
continue;
318
}
319
if (codec.EqualsLiteral("opus")) {
320
// All containers support opus
321
opus++;
322
continue;
323
}
324
unknown++;
325
}
326
327
if (unknown > 0) {
328
// Unsupported codec.
329
return TypeSupport::CodecUnsupported;
330
}
331
332
if (opus == 0) {
333
// Codecs specified but not opus. Unsupported for audio.
334
return TypeSupport::CodecUnsupported;
335
}
336
337
if (opus > 1) {
338
// Opus specified more than once. Bad form.
339
return TypeSupport::CodecDuplicated;
340
}
341
342
return TypeSupport::Supported;
343
}
344
345
TypeSupport CanRecordVideoTrackWith(const Maybe<MediaContainerType>& aMimeType,
346
const nsAString& aMimeTypeString) {
347
if (aMimeTypeString.IsEmpty()) {
348
// For the empty string we just need to check whether we have support for a
349
// video container and a video codec. The VP8 encoder is always available.
350
if (!MediaEncoder::IsWebMEncoderEnabled()) {
351
// No container support for video.
352
return TypeSupport::ContainersDisabled;
353
}
354
355
return TypeSupport::Supported;
356
}
357
358
if (!aMimeType) {
359
// A mime type string was set, but it couldn't be parsed to a valid
360
// MediaContainerType.
361
return TypeSupport::MediaTypeInvalid;
362
}
363
364
if (!aMimeType->Type().HasVideoMajorType()) {
365
return TypeSupport::NoVideoWithAudioType;
366
}
367
368
if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM)) {
369
return TypeSupport::ContainerUnsupported;
370
}
371
372
if (!MediaEncoder::IsWebMEncoderEnabled()) {
373
return TypeSupport::ContainerUnsupported;
374
}
375
376
if (!aMimeType->ExtendedType().HaveCodecs()) {
377
// No codecs constrained, we can pick vp8.
378
return TypeSupport::Supported;
379
}
380
381
size_t vp8 = 0;
382
size_t unknown = 0;
383
for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
384
if (codec.EqualsLiteral("opus")) {
385
// Ignore audio codecs.
386
continue;
387
}
388
if (codec.EqualsLiteral("vp8")) {
389
vp8++;
390
continue;
391
}
392
if (codec.EqualsLiteral("vp8.0")) {
393
vp8++;
394
continue;
395
}
396
unknown++;
397
}
398
399
if (unknown > 0) {
400
// Unsupported codec.
401
return TypeSupport::CodecUnsupported;
402
}
403
404
if (vp8 == 0) {
405
// Codecs specified but not vp8. Unsupported for video.
406
return TypeSupport::CodecUnsupported;
407
}
408
409
if (vp8 > 1) {
410
// Vp8 specified more than once. Bad form.
411
return TypeSupport::CodecDuplicated;
412
}
413
414
return TypeSupport::Supported;
415
}
416
417
TypeSupport CanRecordWith(MediaStreamTrack* aTrack,
418
const Maybe<MediaContainerType>& aMimeType,
419
const nsAString& aMimeTypeString) {
420
if (aTrack->AsAudioStreamTrack()) {
421
return CanRecordAudioTrackWith(aMimeType, aMimeTypeString);
422
}
423
424
if (aTrack->AsVideoStreamTrack()) {
425
return CanRecordVideoTrackWith(aMimeType, aMimeTypeString);
426
}
427
428
MOZ_CRASH("Unexpected track type");
429
}
430
431
TypeSupport IsTypeSupportedImpl(const nsAString& aMIMEType) {
432
if (aMIMEType.IsEmpty()) {
433
// Lie and return true even if no container/codec support is enabled,
434
// because the spec mandates it.
435
return TypeSupport::Supported;
436
}
437
Maybe<MediaContainerType> mime = MakeMediaContainerType(aMIMEType);
438
TypeSupport rv = CanRecordAudioTrackWith(mime, aMIMEType);
439
if (rv == TypeSupport::Supported) {
440
return rv;
441
}
442
return CanRecordVideoTrackWith(mime, aMIMEType);
443
}
444
445
nsString SelectMimeType(bool aHasVideo, bool aHasAudio,
446
const nsString& aConstrainedMimeType) {
447
MOZ_ASSERT(aHasVideo || aHasAudio);
448
449
Maybe<MediaContainerType> constrainedType =
450
MakeMediaContainerType(aConstrainedMimeType);
451
452
nsCString majorType;
453
{
454
// Select major type and container.
455
if (constrainedType) {
456
MOZ_ASSERT_IF(aHasVideo, constrainedType->Type().HasVideoMajorType());
457
MOZ_ASSERT(!constrainedType->Type().HasApplicationMajorType());
458
majorType = constrainedType->Type().AsString();
459
} else if (aHasVideo) {
460
majorType = NS_LITERAL_CSTRING(VIDEO_WEBM);
461
} else {
462
majorType = NS_LITERAL_CSTRING(AUDIO_OGG);
463
}
464
}
465
466
nsString codecs;
467
{
468
if (constrainedType && constrainedType->ExtendedType().HaveCodecs()) {
469
codecs = constrainedType->ExtendedType().Codecs().AsString();
470
} else {
471
if (aHasVideo && aHasAudio) {
472
codecs = NS_LITERAL_STRING("\"vp8, opus\"");
473
} else if (aHasVideo) {
474
codecs = NS_LITERAL_STRING("vp8");
475
} else {
476
codecs = NS_LITERAL_STRING("opus");
477
}
478
}
479
}
480
481
nsString result = NS_ConvertUTF8toUTF16(nsPrintfCString(
482
"%s; codecs=%s", majorType.get(), NS_ConvertUTF16toUTF8(codecs).get()));
483
484
MOZ_ASSERT_IF(aHasAudio,
485
CanRecordAudioTrackWith(MakeMediaContainerType(result),
486
result) == TypeSupport::Supported);
487
MOZ_ASSERT_IF(aHasVideo,
488
CanRecordVideoTrackWith(MakeMediaContainerType(result),
489
result) == TypeSupport::Supported);
490
return result;
491
}
492
493
void SelectBitrates(uint32_t aBitsPerSecond, uint8_t aNumVideoTracks,
494
uint32_t* aOutVideoBps, uint8_t aNumAudioTracks,
495
uint32_t* aOutAudioBps) {
496
uint32_t vbps = 0;
497
uint32_t abps = 0;
498
499
const uint32_t minVideoBps = MIN_VIDEO_BITRATE_BPS * aNumVideoTracks;
500
const uint32_t maxVideoBps = MAX_VIDEO_BITRATE_BPS * aNumVideoTracks;
501
502
const uint32_t minAudioBps = MIN_AUDIO_BITRATE_BPS * aNumAudioTracks;
503
const uint32_t maxAudioBps = MAX_AUDIO_BITRATE_BPS * aNumAudioTracks;
504
505
if (aNumVideoTracks == 0) {
506
MOZ_DIAGNOSTIC_ASSERT(aNumAudioTracks > 0);
507
abps = std::min(maxAudioBps, std::max(minAudioBps, aBitsPerSecond));
508
} else if (aNumAudioTracks == 0) {
509
vbps = std::min(maxVideoBps, std::max(minVideoBps, aBitsPerSecond));
510
} else {
511
// Scale the bits so that video gets 20 times the bits of audio.
512
// Since we must account for varying number of tracks of each type we weight
513
// them by type; video = weight 20, audio = weight 1.
514
const uint32_t videoWeight = aNumVideoTracks * 20;
515
const uint32_t audioWeight = aNumAudioTracks;
516
const uint32_t totalWeights = audioWeight + videoWeight;
517
const uint32_t videoBitrate =
518
uint64_t(aBitsPerSecond) * videoWeight / totalWeights;
519
const uint32_t audioBitrate =
520
uint64_t(aBitsPerSecond) * audioWeight / totalWeights;
521
vbps = std::min(maxVideoBps, std::max(minVideoBps, videoBitrate));
522
abps = std::min(maxAudioBps, std::max(minAudioBps, audioBitrate));
523
}
524
525
*aOutVideoBps = vbps;
526
*aOutAudioBps = abps;
527
}
528
} // namespace
529
530
/**
531
* Session is an object to represent a single recording event.
532
* In original design, all recording context is stored in MediaRecorder, which
533
* causes a problem if someone calls MediaRecorder::Stop and
534
* MediaRecorder::Start quickly. To prevent blocking main thread, media encoding
535
* is executed in a second thread, named as Read Thread. For the same reason, we
536
* do not wait Read Thread shutdown in MediaRecorder::Stop. If someone call
537
* MediaRecorder::Start before Read Thread shutdown, the same recording context
538
* in MediaRecorder might be access by two Reading Threads, which cause a
539
* problem. In the new design, we put recording context into Session object,
540
* including Read Thread. Each Session has its own recording context and Read
541
* Thread, problem is been resolved.
542
*
543
* Life cycle of a Session object.
544
* 1) Initialization Stage (in main thread)
545
* Setup media tracks in MTG, and bind MediaEncoder with Source Stream when
546
* mStream is available. Resource allocation, such as encoded data cache buffer
547
* and MediaEncoder. Create read thread. Automatically switch to Extract stage
548
* in the end of this stage. 2) Extract Stage (in Read Thread) Pull encoded A/V
549
* frames from MediaEncoder, dispatch to OnDataAvailable handler. Unless a
550
* client calls Session::Stop, Session object keeps stay in this stage. 3)
551
* Destroy Stage (in main thread) Switch from Extract stage to Destroy stage by
552
* calling Session::Stop. Release session resource and remove associated tracks
553
* from MTG.
554
*
555
* Lifetime of MediaRecorder and Session objects.
556
* 1) MediaRecorder creates a Session in MediaRecorder::Start function and holds
557
* a reference to Session. Then the Session registers itself to a
558
* ShutdownBlocker 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 MediaRecorder::Stop has been called _and_ all
563
* encoded media data has been passed to OnDataAvailable handler. 3)
564
* MediaRecorder::Stop is called by user or the document is going to inactive or
565
* invisible.
566
*/
567
class MediaRecorder::Session : public PrincipalChangeObserver<MediaStreamTrack>,
568
public DOMMediaStream::TrackListener {
569
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Session)
570
571
class StoreEncodedBufferRunnable final : public Runnable {
572
RefPtr<Session> mSession;
573
nsTArray<nsTArray<uint8_t>> mBuffer;
574
575
public:
576
StoreEncodedBufferRunnable(Session* aSession,
577
nsTArray<nsTArray<uint8_t>>&& aBuffer)
578
: Runnable("StoreEncodedBufferRunnable"),
579
mSession(aSession),
580
mBuffer(std::move(aBuffer)) {}
581
582
NS_IMETHOD
583
Run() override {
584
MOZ_ASSERT(NS_IsMainThread());
585
mSession->MaybeCreateMutableBlobStorage();
586
for (const auto& part : mBuffer) {
587
if (part.IsEmpty()) {
588
continue;
589
}
590
591
nsresult rv = mSession->mMutableBlobStorage->Append(part.Elements(),
592
part.Length());
593
if (NS_WARN_IF(NS_FAILED(rv))) {
594
mSession->DoSessionEndTask(rv);
595
break;
596
}
597
}
598
599
return NS_OK;
600
}
601
};
602
603
class EncoderListener : public MediaEncoderListener {
604
public:
605
EncoderListener(TaskQueue* aEncoderThread, Session* aSession)
606
: mEncoderThread(aEncoderThread), mSession(aSession) {}
607
608
void Forget() {
609
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
610
mSession = nullptr;
611
}
612
613
void Initialized() override {
614
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
615
if (mSession) {
616
mSession->MediaEncoderInitialized();
617
}
618
}
619
620
void DataAvailable() override {
621
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
622
if (mSession) {
623
mSession->MediaEncoderDataAvailable();
624
}
625
}
626
627
void Error() override {
628
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
629
if (mSession) {
630
mSession->MediaEncoderError();
631
}
632
}
633
634
void Shutdown() override {
635
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
636
if (mSession) {
637
mSession->MediaEncoderShutdown();
638
}
639
}
640
641
protected:
642
RefPtr<TaskQueue> mEncoderThread;
643
RefPtr<Session> mSession;
644
};
645
646
struct TrackTypeComparator {
647
enum Type {
648
AUDIO,
649
VIDEO,
650
};
651
static bool Equals(const RefPtr<MediaStreamTrack>& aTrack, Type aType) {
652
return (aType == AUDIO && aTrack->AsAudioStreamTrack()) ||
653
(aType == VIDEO && aTrack->AsVideoStreamTrack());
654
}
655
};
656
657
public:
658
Session(MediaRecorder* aRecorder,
659
nsTArray<RefPtr<MediaStreamTrack>> aMediaStreamTracks,
660
TimeDuration aTimeslice, uint32_t aVideoBitsPerSecond,
661
uint32_t aAudioBitsPerSecond)
662
: mRecorder(aRecorder),
663
mMediaStreamTracks(std::move(aMediaStreamTracks)),
664
mMainThread(mRecorder->GetOwner()->EventTargetFor(TaskCategory::Other)),
665
mMimeType(SelectMimeType(
666
mMediaStreamTracks.Contains(TrackTypeComparator::VIDEO,
667
TrackTypeComparator()),
668
mRecorder->mAudioNode ||
669
mMediaStreamTracks.Contains(TrackTypeComparator::AUDIO,
670
TrackTypeComparator()),
671
mRecorder->mConstrainedMimeType)),
672
mTimeslice(aTimeslice),
673
mVideoBitsPerSecond(aVideoBitsPerSecond),
674
mAudioBitsPerSecond(aAudioBitsPerSecond),
675
mStartTime(TimeStamp::Now()),
676
mRunningState(RunningState::Idling) {
677
MOZ_ASSERT(NS_IsMainThread());
678
679
mMaxMemory = Preferences::GetUint("media.recorder.max_memory",
680
MAX_ALLOW_MEMORY_BUFFER);
681
Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1);
682
}
683
684
void PrincipalChanged(MediaStreamTrack* aTrack) override {
685
NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
686
"Principal changed for unrecorded track");
687
if (!MediaStreamTracksPrincipalSubsumes(mRecorder, mMediaStreamTracks)) {
688
DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
689
}
690
}
691
692
void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
693
LOG(LogLevel::Warning,
694
("Session.NotifyTrackAdded %p Raising error due to track set change",
695
this));
696
DoSessionEndTask(NS_ERROR_ABORT);
697
}
698
699
void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
700
if (aTrack->Ended()) {
701
// TrackEncoder will pickup tracks that end itself.
702
return;
703
}
704
LOG(LogLevel::Warning,
705
("Session.NotifyTrackRemoved %p Raising error due to track set change",
706
this));
707
DoSessionEndTask(NS_ERROR_ABORT);
708
}
709
710
void Start() {
711
LOG(LogLevel::Debug, ("Session.Start %p", this));
712
MOZ_ASSERT(NS_IsMainThread());
713
714
if (mRecorder->mStream) {
715
// The TrackListener reports back when tracks are added or removed from
716
// the MediaStream.
717
mMediaStream = mRecorder->mStream;
718
mMediaStream->RegisterTrackListener(this);
719
720
uint8_t trackTypes = 0;
721
int32_t audioTracks = 0;
722
int32_t videoTracks = 0;
723
for (const auto& track : mMediaStreamTracks) {
724
if (track->AsAudioStreamTrack()) {
725
++audioTracks;
726
trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
727
} else if (track->AsVideoStreamTrack()) {
728
++videoTracks;
729
trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
730
} else {
731
MOZ_CRASH("Unexpected track type");
732
}
733
}
734
735
if (audioTracks > 1 || videoTracks > 1) {
736
// When MediaRecorder supports multiple tracks, we should set up a
737
// single MediaInputPort from the input stream, and let main thread
738
// check track principals async later.
739
nsPIDOMWindowInner* window = mRecorder->GetOwner();
740
Document* document = window ? window->GetExtantDoc() : nullptr;
741
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
742
NS_LITERAL_CSTRING("Media"), document,
743
nsContentUtils::eDOM_PROPERTIES,
744
"MediaRecorderMultiTracksNotSupported");
745
DoSessionEndTask(NS_ERROR_ABORT);
746
return;
747
}
748
749
for (const auto& t : mMediaStreamTracks) {
750
t->AddPrincipalChangeObserver(this);
751
}
752
753
LOG(LogLevel::Debug, ("Session.Start track types = (%d)", trackTypes));
754
InitEncoder(trackTypes, mMediaStreamTracks[0]->Graph()->GraphRate());
755
return;
756
}
757
758
if (mRecorder->mAudioNode) {
759
TrackRate trackRate =
760
mRecorder->mAudioNode->Context()->Graph()->GraphRate();
761
762
// Web Audio node has only audio.
763
InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate);
764
return;
765
}
766
767
MOZ_ASSERT(false, "Unknown source");
768
}
769
770
void Stop() {
771
LOG(LogLevel::Debug, ("Session.Stop %p", this));
772
MOZ_ASSERT(NS_IsMainThread());
773
774
if (mEncoder) {
775
mEncoder->Stop();
776
}
777
778
// Remove main thread state added in Start().
779
if (mMediaStream) {
780
mMediaStream->UnregisterTrackListener(this);
781
mMediaStream = nullptr;
782
}
783
784
{
785
for (const auto& track : mMediaStreamTracks) {
786
track->RemovePrincipalChangeObserver(this);
787
}
788
}
789
790
if (mRunningState.isOk() &&
791
mRunningState.inspect() == RunningState::Idling) {
792
LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this));
793
// End the Session directly if there is no encoder.
794
DoSessionEndTask(NS_OK);
795
} else if (mRunningState.isOk() &&
796
(mRunningState.inspect() == RunningState::Starting ||
797
mRunningState.inspect() == RunningState::Running)) {
798
mRunningState = RunningState::Stopping;
799
}
800
}
801
802
void Pause() {
803
LOG(LogLevel::Debug, ("Session.Pause"));
804
MOZ_ASSERT(NS_IsMainThread());
805
MOZ_ASSERT_IF(mRunningState.isOk(),
806
mRunningState.unwrap() != RunningState::Idling);
807
if (mRunningState.isErr() ||
808
mRunningState.unwrap() == RunningState::Stopping ||
809
mRunningState.unwrap() == RunningState::Stopped) {
810
return;
811
}
812
MOZ_ASSERT(mEncoder);
813
mEncoder->Suspend();
814
}
815
816
void Resume() {
817
LOG(LogLevel::Debug, ("Session.Resume"));
818
MOZ_ASSERT(NS_IsMainThread());
819
MOZ_ASSERT_IF(mRunningState.isOk(),
820
mRunningState.unwrap() != RunningState::Idling);
821
if (mRunningState.isErr() ||
822
mRunningState.unwrap() == RunningState::Stopping ||
823
mRunningState.unwrap() == RunningState::Stopped) {
824
return;
825
}
826
MOZ_ASSERT(mEncoder);
827
mEncoder->Resume();
828
}
829
830
void RequestData() {
831
LOG(LogLevel::Debug, ("Session.RequestData"));
832
MOZ_ASSERT(NS_IsMainThread());
833
834
GatherBlob()->Then(
835
mMainThread, __func__,
836
[this, self = RefPtr<Session>(this)](
837
const BlobPromise::ResolveOrRejectValue& aResult) {
838
if (aResult.IsReject()) {
839
LOG(LogLevel::Warning, ("GatherBlob failed for RequestData()"));
840
DoSessionEndTask(aResult.RejectValue());
841
return;
842
}
843
844
nsresult rv =
845
mRecorder->CreateAndDispatchBlobEvent(aResult.ResolveValue());
846
if (NS_FAILED(rv)) {
847
DoSessionEndTask(NS_OK);
848
}
849
});
850
}
851
852
void MaybeCreateMutableBlobStorage() {
853
if (!mMutableBlobStorage) {
854
mMutableBlobStorage = new MutableBlobStorage(
855
MutableBlobStorage::eCouldBeInTemporaryFile, nullptr, mMaxMemory);
856
}
857
}
858
859
static const bool IsExclusive = false;
860
using BlobPromise = MozPromise<RefPtr<BlobImpl>, nsresult, IsExclusive>;
861
class BlobStorer : public MutableBlobStorageCallback {
862
MozPromiseHolder<BlobPromise> mHolder;
863
864
virtual ~BlobStorer() = default;
865
866
public:
867
BlobStorer() = default;
868
869
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlobStorer, override)
870
871
void BlobStoreCompleted(MutableBlobStorage*, BlobImpl* aBlobImpl,
872
nsresult aRv) override {
873
MOZ_ASSERT(NS_IsMainThread());
874
if (NS_FAILED(aRv)) {
875
mHolder.Reject(aRv, __func__);
876
return;
877
}
878
879
mHolder.Resolve(aBlobImpl, __func__);
880
}
881
882
RefPtr<BlobPromise> Promise() { return mHolder.Ensure(__func__); }
883
};
884
885
protected:
886
RefPtr<BlobPromise> GatherBlobImpl() {
887
RefPtr<BlobStorer> storer = MakeAndAddRef<BlobStorer>();
888
MaybeCreateMutableBlobStorage();
889
mMutableBlobStorage->GetBlobImplWhenReady(NS_ConvertUTF16toUTF8(mMimeType),
890
storer);
891
mMutableBlobStorage = nullptr;
892
893
storer->Promise()->Then(
894
mMainThread, __func__,
895
[self = RefPtr<Session>(this), p = storer->Promise()] {
896
if (self->mBlobPromise == p) {
897
// Reset BlobPromise.
898
self->mBlobPromise = nullptr;
899
}
900
});
901
902
return storer->Promise();
903
}
904
905
public:
906
// Stops gathering data into the current blob and resolves when the current
907
// blob is available. Future data will be stored in a new blob.
908
// Should a previous async GatherBlob() operation still be in progress, we'll
909
// wait for it to finish before starting this one.
910
RefPtr<BlobPromise> GatherBlob() {
911
MOZ_ASSERT(NS_IsMainThread());
912
if (!mBlobPromise) {
913
return mBlobPromise = GatherBlobImpl();
914
}
915
return mBlobPromise = mBlobPromise->Then(mMainThread, __func__,
916
[self = RefPtr<Session>(this)] {
917
return self->GatherBlobImpl();
918
});
919
}
920
921
RefPtr<SizeOfPromise> SizeOfExcludingThis(
922
mozilla::MallocSizeOf aMallocSizeOf) {
923
MOZ_ASSERT(NS_IsMainThread());
924
size_t encodedBufferSize =
925
mMutableBlobStorage ? mMutableBlobStorage->SizeOfCurrentMemoryBuffer()
926
: 0;
927
928
if (!mEncoder) {
929
return SizeOfPromise::CreateAndResolve(encodedBufferSize, __func__);
930
}
931
932
auto& encoder = mEncoder;
933
return InvokeAsync(
934
mEncoderThread, __func__,
935
[encoder, encodedBufferSize, aMallocSizeOf]() {
936
return SizeOfPromise::CreateAndResolve(
937
encodedBufferSize + encoder->SizeOfExcludingThis(aMallocSizeOf),
938
__func__);
939
});
940
}
941
942
private:
943
virtual ~Session() {
944
MOZ_ASSERT(NS_IsMainThread());
945
MOZ_ASSERT(mShutdownPromise);
946
MOZ_ASSERT(!mShutdownBlocker);
947
LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
948
}
949
950
// Pull encoded media data from MediaEncoder and put into MutableBlobStorage.
951
// If the bool aForceFlush is true, we will force a dispatch of a blob to
952
// main thread.
953
void Extract(TimeStamp aNow, bool aForceFlush) {
954
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
955
956
LOG(LogLevel::Debug, ("Session.Extract %p", this));
957
958
AUTO_PROFILER_LABEL("MediaRecorder::Session::Extract", OTHER);
959
960
// Pull encoded media data from MediaEncoder
961
nsTArray<nsTArray<uint8_t>> encodedBuf;
962
nsresult rv = mEncoder->GetEncodedData(&encodedBuf);
963
if (NS_FAILED(rv)) {
964
MOZ_RELEASE_ASSERT(encodedBuf.IsEmpty());
965
// Even if we failed to encode more data, it might be time to push a blob
966
// with already encoded data.
967
}
968
969
// Append pulled data into cache buffer.
970
NS_DispatchToMainThread(
971
new StoreEncodedBufferRunnable(this, std::move(encodedBuf)));
972
973
// Whether push encoded data back to onDataAvailable automatically or we
974
// need a flush.
975
bool pushBlob = aForceFlush;
976
if (!pushBlob && !mLastBlobTimeStamp.IsNull() &&
977
(aNow - mLastBlobTimeStamp) > mTimeslice) {
978
pushBlob = true;
979
}
980
if (pushBlob) {
981
MOZ_ASSERT(!mLastBlobTimeStamp.IsNull(),
982
"The encoder must have been initialized if there's data");
983
mLastBlobTimeStamp = aNow;
984
InvokeAsync(mMainThread, this, __func__, &Session::GatherBlob)
985
->Then(mMainThread, __func__,
986
[this, self = RefPtr<Session>(this)](
987
const BlobPromise::ResolveOrRejectValue& aResult) {
988
// Assert that we've seen the start event
989
MOZ_ASSERT_IF(
990
mRunningState.isOk(),
991
mRunningState.inspect() != RunningState::Starting);
992
if (aResult.IsReject()) {
993
LOG(LogLevel::Warning,
994
("GatherBlob failed for pushing blob"));
995
DoSessionEndTask(aResult.RejectValue());
996
return;
997
}
998
999
nsresult rv = mRecorder->CreateAndDispatchBlobEvent(
1000
aResult.ResolveValue());
1001
if (NS_FAILED(rv)) {
1002
DoSessionEndTask(NS_OK);
1003
}
1004
});
1005
}
1006
}
1007
1008
void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate) {
1009
LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
1010
MOZ_ASSERT(NS_IsMainThread());
1011
1012
if (!mRunningState.isOk() ||
1013
mRunningState.inspect() != RunningState::Idling) {
1014
MOZ_ASSERT_UNREACHABLE("Double-init");
1015
return;
1016
}
1017
1018
// Create a TaskQueue to read encode media data from MediaEncoder.
1019
MOZ_RELEASE_ASSERT(!mEncoderThread);
1020
RefPtr<SharedThreadPool> pool =
1021
GetMediaThreadPool(MediaThreadType::WEBRTC_DECODER);
1022
if (!pool) {
1023
LOG(LogLevel::Debug, ("Session.InitEncoder %p Failed to create "
1024
"MediaRecorderReadThread thread pool",
1025
this));
1026
DoSessionEndTask(NS_ERROR_FAILURE);
1027
return;
1028
}
1029
1030
mEncoderThread =
1031
MakeAndAddRef<TaskQueue>(pool.forget(), "MediaRecorderReadThread");
1032
1033
MOZ_DIAGNOSTIC_ASSERT(!mShutdownBlocker);
1034
// Add a shutdown blocker so mEncoderThread can be shutdown async.
1035
class Blocker : public ShutdownBlocker {
1036
const RefPtr<Session> mSession;
1037
1038
public:
1039
Blocker(RefPtr<Session> aSession, const nsString& aName)
1040
: ShutdownBlocker(aName), mSession(std::move(aSession)) {}
1041
1042
NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override {
1043
Unused << mSession->Shutdown();
1044
return NS_OK;
1045
}
1046
};
1047
1048
nsString name;
1049
name.AppendPrintf("MediaRecorder::Session %p shutdown", this);
1050
mShutdownBlocker = MakeAndAddRef<Blocker>(this, name);
1051
nsresult rv = GetShutdownBarrier()->AddBlocker(
1052
mShutdownBlocker, NS_LITERAL_STRING(__FILE__), __LINE__,
1053
NS_LITERAL_STRING("MediaRecorder::Session: shutdown"));
1054
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
1055
1056
mEncoder = MediaEncoder::CreateEncoder(
1057
mEncoderThread, mMimeType, mAudioBitsPerSecond, mVideoBitsPerSecond,
1058
aTrackTypes, aTrackRate);
1059
1060
if (!mEncoder) {
1061
LOG(LogLevel::Error, ("Session.InitEncoder !mEncoder %p", this));
1062
DoSessionEndTask(NS_ERROR_ABORT);
1063
return;
1064
}
1065
1066
mEncoderListener = MakeAndAddRef<EncoderListener>(mEncoderThread, this);
1067
rv = mEncoderThread->Dispatch(NewRunnableMethod<RefPtr<EncoderListener>>(
1068
"mozilla::MediaEncoder::RegisterListener", mEncoder,
1069
&MediaEncoder::RegisterListener, mEncoderListener));
1070
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
1071
Unused << rv;
1072
1073
if (mRecorder->mAudioNode) {
1074
mEncoder->ConnectAudioNode(mRecorder->mAudioNode,
1075
mRecorder->mAudioNodeOutput);
1076
}
1077
1078
for (const auto& track : mMediaStreamTracks) {
1079
mEncoder->ConnectMediaStreamTrack(track);
1080
}
1081
1082
// If a timeslice is defined we set an appropriate video keyframe interval.
1083
// This allows users to get blobs regularly when the timeslice interval is
1084
// shorter than the default key frame interval, as we'd normally wait for a
1085
// key frame before sending data to the blob.
1086
mEncoder->SetVideoKeyFrameInterval(
1087
std::max(TimeDuration::FromSeconds(1), mTimeslice).ToMilliseconds());
1088
1089
// Set mRunningState to Running so that DoSessionEndTask will
1090
// take the responsibility to end the session.
1091
mRunningState = RunningState::Starting;
1092
}
1093
1094
// This is the task that will stop recording per spec:
1095
// - Stop gathering data (this is inherently async)
1096
// - Set state to "inactive"
1097
// - Fire an error event, if NS_FAILED(rv)
1098
// - Discard blob data if rv is NS_ERROR_DOM_SECURITY_ERR
1099
// - Fire a Blob event
1100
// - Fire an event named stop
1101
void DoSessionEndTask(nsresult rv) {
1102
MOZ_ASSERT(NS_IsMainThread());
1103
if (mRunningState.isErr()) {
1104
// We have already ended with an error.
1105
return;
1106
}
1107
1108
if (mRunningState.isOk() &&
1109
mRunningState.inspect() == RunningState::Stopped) {
1110
// We have already ended gracefully.
1111
return;
1112
}
1113
1114
bool needsStartEvent = false;
1115
if (mRunningState.isOk() &&
1116
(mRunningState.inspect() == RunningState::Idling ||
1117
mRunningState.inspect() == RunningState::Starting)) {
1118
needsStartEvent = true;
1119
}
1120
1121
if (rv == NS_OK) {
1122
mRunningState = RunningState::Stopped;
1123
} else {
1124
mRunningState = Err(rv);
1125
}
1126
1127
GatherBlob()
1128
->Then(
1129
mMainThread, __func__,
1130
[this, self = RefPtr<Session>(this), rv, needsStartEvent](
1131
const BlobPromise::ResolveOrRejectValue& aResult) {
1132
if (mRecorder->mSessions.LastElement() == this) {
1133
// Set state to inactive, but only if the recorder is not
1134
// controlled by another session already.
1135
mRecorder->Inactivate();
1136
}
1137
1138
if (needsStartEvent) {
1139
mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("start"));
1140
}
1141
1142
// If there was an error, Fire the appropriate one
1143
if (NS_FAILED(rv)) {
1144
mRecorder->NotifyError(rv);
1145
}
1146
1147
// Fire a blob event named dataavailable
1148
RefPtr<BlobImpl> blobImpl;
1149
if (rv == NS_ERROR_DOM_SECURITY_ERR || aResult.IsReject()) {
1150
// In case of SecurityError, the blob data must be discarded.
1151
// We create a new empty one and throw the blob with its data
1152
// away.
1153
// In case we failed to gather blob data, we create an empty
1154
// memory blob instead.
1155
blobImpl = new EmptyBlobImpl(mMimeType);
1156
} else {
1157
blobImpl = aResult.ResolveValue();
1158
}
1159
if (NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(blobImpl))) {
1160
// Failed to dispatch blob event. That's unexpected. It's
1161
// probably all right to fire an error event if we haven't
1162
// already.
1163
if (NS_SUCCEEDED(rv)) {
1164
mRecorder->NotifyError(NS_ERROR_FAILURE);
1165
}
1166
}
1167
1168
// Fire an event named stop
1169
mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
1170
1171
// And finally, Shutdown and destroy the Session
1172
return Shutdown();
1173
})
1174
->Then(mMainThread, __func__, [this, self = RefPtr<Session>(this)] {
1175
GetShutdownBarrier()->RemoveBlocker(mShutdownBlocker);
1176
mShutdownBlocker = nullptr;
1177
});
1178
}
1179
1180
void MediaEncoderInitialized() {
1181
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
1182
1183
// Start issuing timeslice-based blobs.
1184
MOZ_ASSERT(mLastBlobTimeStamp.IsNull());
1185
mLastBlobTimeStamp = TimeStamp::Now();
1186
1187
Extract(mLastBlobTimeStamp, false);
1188
1189
NS_DispatchToMainThread(NewRunnableFrom([self = RefPtr<Session>(this), this,
1190
mime = mEncoder->MimeType()]() {
1191
if (mRunningState.isErr()) {
1192
return NS_OK;
1193
}
1194
RunningState state = mRunningState.inspect();
1195
if (state == RunningState::Starting || state == RunningState::Stopping) {
1196
if (state == RunningState::Starting) {
1197
// We set it to Running in the runnable since we can only assign
1198
// mRunningState on main thread. We set it before running the start
1199
// event runnable since that dispatches synchronously (and may cause
1200
// js calls to methods depending on mRunningState).
1201
mRunningState = RunningState::Running;
1202
1203
mRecorder->mMimeType = mMimeType;
1204
}
1205
mRecorder->DispatchSimpleEvent(NS_LITERAL_STRING("start"));
1206
}
1207
return NS_OK;
1208
}));
1209
}
1210
1211
void MediaEncoderDataAvailable() {
1212
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
1213
1214
Extract(TimeStamp::Now(), false);
1215
}
1216
1217
void MediaEncoderError() {
1218
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
1219
NS_DispatchToMainThread(NewRunnableMethod<nsresult>(
1220
"dom::MediaRecorder::Session::DoSessionEndTask", this,
1221
&Session::DoSessionEndTask, NS_ERROR_FAILURE));
1222
}
1223
1224
void MediaEncoderShutdown() {
1225
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
1226
MOZ_ASSERT(mEncoder->IsShutdown());
1227
1228
mMainThread->Dispatch(NewRunnableMethod<nsresult>(
1229
"MediaRecorder::Session::MediaEncoderShutdown->DoSessionEndTask", this,
1230
&Session::DoSessionEndTask, NS_OK));
1231
1232
// Clean up.
1233
mEncoderListener->Forget();
1234
DebugOnly<bool> unregistered =
1235
mEncoder->UnregisterListener(mEncoderListener);
1236
MOZ_ASSERT(unregistered);
1237
}
1238
1239
RefPtr<ShutdownPromise> Shutdown() {
1240
MOZ_ASSERT(NS_IsMainThread());
1241
LOG(LogLevel::Debug, ("Session Shutdown %p", this));
1242
1243
if (mShutdownPromise) {
1244
return mShutdownPromise;
1245
}
1246
1247
// This is a coarse calculation and does not reflect the duration of the
1248
// final recording for reasons such as pauses. However it allows us an
1249
// idea of how long people are running their recorders for.
1250
TimeDuration timeDelta = TimeStamp::Now() - mStartTime;
1251
Telemetry::Accumulate(Telemetry::MEDIA_RECORDER_RECORDING_DURATION,
1252
timeDelta.ToSeconds());
1253
1254
mShutdownPromise = ShutdownPromise::CreateAndResolve(true, __func__);
1255
1256
if (mEncoder) {
1257
MOZ_RELEASE_ASSERT(mEncoderListener);
1258
mShutdownPromise =
1259
mShutdownPromise
1260
->Then(mEncoderThread, __func__,
1261
[encoder = mEncoder, encoderListener = mEncoderListener] {
1262
// Unregister the listener before canceling so that we
1263
// don't get the Shutdown notification from Cancel().
1264
encoder->UnregisterListener(encoderListener);
1265
encoderListener->Forget();
1266
return ShutdownPromise::CreateAndResolve(true, __func__);
1267
})
1268
->Then(mMainThread, __func__,
1269
[encoder = mEncoder] { return encoder->Cancel(); })
1270
->Then(mEncoderThread, __func__, [] {
1271
// Meh, this is just to convert the promise type to match
1272
// mShutdownPromise.
1273
return ShutdownPromise::CreateAndResolve(true, __func__);
1274
});
1275
}
1276
1277
// Remove main thread state. This could be needed if Stop() wasn't called.
1278
if (mMediaStream) {
1279
mMediaStream->UnregisterTrackListener(this);
1280
mMediaStream = nullptr;
1281
}
1282
1283
{
1284
auto tracks(std::move(mMediaStreamTracks));
1285
for (RefPtr<MediaStreamTrack>& track : tracks) {
1286
track->RemovePrincipalChangeObserver(this);
1287
}
1288
}
1289
1290
// Break the cycle reference between Session and MediaRecorder.
1291
mShutdownPromise = mShutdownPromise->Then(
1292
mMainThread, __func__,
1293
[self = RefPtr<Session>(this)]() {
1294
self->mRecorder->RemoveSession(self);
1295
return ShutdownPromise::CreateAndResolve(true, __func__);
1296
},
1297
[]() {
1298
MOZ_ASSERT_UNREACHABLE("Unexpected reject");
1299
return ShutdownPromise::CreateAndReject(false, __func__);
1300
});
1301
1302
if (mEncoderThread) {
1303
mShutdownPromise = mShutdownPromise->Then(
1304
mMainThread, __func__,
1305
[encoderThread = mEncoderThread]() {
1306
return encoderThread->BeginShutdown();
1307
},
1308
[]() {
1309
MOZ_ASSERT_UNREACHABLE("Unexpected reject");
1310
return ShutdownPromise::CreateAndReject(false, __func__);
1311
});
1312
}
1313
1314
return mShutdownPromise;
1315
}
1316
1317
private:
1318
enum class RunningState {
1319
Idling, // Session has been created
1320
Starting, // MediaEncoder started, waiting for data
1321
Running, // MediaEncoder has produced data
1322
Stopping, // Stop() has been called
1323
Stopped, // Session has stopped without any error
1324
};
1325
1326
// Our associated MediaRecorder.
1327
const RefPtr<MediaRecorder> mRecorder;
1328
1329
// Stream currently recorded.
1330
RefPtr<DOMMediaStream> mMediaStream;
1331
1332
// Tracks currently recorded. This should be a subset of mMediaStream's track
1333
// set.
1334
nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
1335
1336
// Main thread used for MozPromise operations.
1337
const RefPtr<nsISerialEventTarget> mMainThread;
1338
// Runnable thread for reading data from MediaEncoder.
1339
RefPtr<TaskQueue> mEncoderThread;
1340
// MediaEncoder pipeline.
1341
RefPtr<MediaEncoder> mEncoder;
1342
// Listener through which MediaEncoder signals us.
1343
RefPtr<EncoderListener> mEncoderListener;
1344
// Set in Shutdown() and resolved when shutdown is complete.
1345
RefPtr<ShutdownPromise> mShutdownPromise;
1346
// A buffer to cache encoded media data.
1347
RefPtr<MutableBlobStorage> mMutableBlobStorage;
1348
// Max memory to use for the MutableBlobStorage.
1349
uint64_t mMaxMemory;
1350
// If set, is a promise for the latest GatherBlob() operation. Allows
1351
// GatherBlob() operations to be serialized in order to avoid races.
1352
RefPtr<BlobPromise> mBlobPromise;
1353
// Session mimeType
1354
const nsString mMimeType;
1355
// Timestamp of the last fired dataavailable event.
1356
TimeStamp mLastBlobTimeStamp;
1357
// The interval of passing encoded data from MutableBlobStorage to
1358
// onDataAvailable handler.
1359
const TimeDuration mTimeslice;
1360
// The video bitrate the recorder was configured with.
1361
const uint32_t mVideoBitsPerSecond;
1362
// The audio bitrate the recorder was configured with.
1363
const uint32_t mAudioBitsPerSecond;
1364
// The time this session started, for telemetry.
1365
const TimeStamp mStartTime;
1366
// The session's current main thread state. The error type gets set when
1367
// ending a recording with an error. An NS_OK error is invalid.
1368
// Main thread only.
1369
Result<RunningState, nsresult> mRunningState;
1370
// Shutdown blocker unique for this Session. Main thread only.
1371
RefPtr<ShutdownBlocker> mShutdownBlocker;
1372
};
1373
1374
MediaRecorder::~MediaRecorder() {
1375
LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
1376
UnRegisterActivityObserver();
1377
}
1378
1379
MediaRecorder::MediaRecorder(nsPIDOMWindowInner* aOwnerWindow)
1380
: DOMEventTargetHelper(aOwnerWindow) {
1381
MOZ_ASSERT(aOwnerWindow);
1382
RegisterActivityObserver();
1383
}
1384
1385
void MediaRecorder::RegisterActivityObserver() {
1386
if (nsPIDOMWindowInner* window = GetOwner()) {
1387
mDocument = window->GetExtantDoc();
1388
if (mDocument) {
1389
mDocument->RegisterActivityObserver(
1390
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1391
}
1392
}
1393
}
1394
1395
void MediaRecorder::UnRegisterActivityObserver() {
1396
if (mDocument) {
1397
mDocument->UnregisterActivityObserver(
1398
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1399
}
1400
}
1401
1402
void MediaRecorder::GetMimeType(nsString& aMimeType) { aMimeType = mMimeType; }
1403
1404
void MediaRecorder::Start(const Optional<uint32_t>& aTimeslice,
1405
ErrorResult& aResult) {
1406
LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
1407
1408
InitializeDomExceptions();
1409
1410
// When a MediaRecorder object’s start() method is invoked, the UA MUST run
1411
// the following steps:
1412
1413
// 1. Let recorder be the MediaRecorder object on which the method was
1414
// invoked.
1415
1416
// 2. Let timeslice be the method’s first argument, if provided, or undefined.
1417
TimeDuration timeslice =
1418
aTimeslice.WasPassed()
1419
? TimeDuration::FromMilliseconds(aTimeslice.Value())
1420
: TimeDuration::Forever();
1421
1422
// 3. Let stream be the value of recorder’s stream attribute.
1423
1424
// 4. Let tracks be the set of live tracks in stream’s track set.
1425
nsTArray<RefPtr<MediaStreamTrack>> tracks;
1426
if (mStream) {
1427
mStream->GetTracks(tracks);
1428
}
1429
for (const auto& t : nsTArray<RefPtr<MediaStreamTrack>>(tracks)) {
1430
if (t->Ended()) {
1431
tracks.RemoveElement(t);
1432
}
1433
}
1434
1435
// 5. If the value of recorder’s state attribute is not inactive, throw an
1436
// InvalidStateError DOMException and abort these steps.
1437
if (mState != RecordingState::Inactive) {
1438
aResult.ThrowDOMException(
1439
NS_ERROR_DOM_INVALID_STATE_ERR,
1440
NS_LITERAL_CSTRING("The MediaRecorder has already been started"));
1441
return;
1442
}
1443
1444
// 6. If the isolation properties of stream disallow access from recorder,
1445
// throw a SecurityError DOMException and abort these steps.
1446
if (mStream) {
1447
RefPtr<nsIPrincipal> streamPrincipal = mStream->GetPrincipal();
1448
if (!PrincipalSubsumes(this, streamPrincipal)) {
1449
aResult.ThrowDOMException(
1450
NS_ERROR_DOM_SECURITY_ERR,
1451
NS_LITERAL_CSTRING("The MediaStream's isolation properties disallow "
1452
"access from MediaRecorder"));
1453
return;
1454
}
1455
}
1456
if (mAudioNode && !AudioNodePrincipalSubsumes(this, mAudioNode)) {
1457
LOG(LogLevel::Warning,
1458
("MediaRecorder %p Start AudioNode principal check failed", this));
1459
aResult.ThrowDOMException(
1460
NS_ERROR_DOM_SECURITY_ERR,
1461
NS_LITERAL_CSTRING("The AudioNode's isolation properties disallow "
1462
"access from MediaRecorder"));
1463
return;
1464
}
1465
1466
// 7. If stream is inactive, throw a NotSupportedError DOMException and abort
1467
// these steps.
1468
if (mStream && !mStream->Active()) {
1469
aResult.ThrowDOMException(
1470
NS_ERROR_DOM_NOT_SUPPORTED_ERR,
1471
NS_LITERAL_CSTRING("The MediaStream is inactive"));
1472
return;
1473
}
1474
1475
// 8. If the [[ConstrainedMimeType]] slot specifies a media type, container,
1476
// or codec, then run the following sub steps:
1477
// 1. Constrain the configuration of recorder to the media type, container,
1478
// and codec specified in the [[ConstrainedMimeType]] slot.
1479
// 2. For each track in tracks, if the User Agent cannot record the track
1480
// using the current configuration, then throw a NotSupportedError
1481
// DOMException and abort all steps.
1482
Maybe<MediaContainerType> mime;
1483
if (mConstrainedMimeType.Length() > 0) {
1484
mime = MakeMediaContainerType(mConstrainedMimeType);
1485
MOZ_DIAGNOSTIC_ASSERT(
1486
mime,
1487
"Invalid media MIME type should have been caught by IsTypeSupported");
1488
}
1489
for (const auto& track : tracks) {
1490
TypeSupport support = CanRecordWith(track, mime, mConstrainedMimeType);
1491
if (support != TypeSupport::Supported) {
1492
nsString id;
1493
track->GetId(id);
1494
aResult.ThrowDOMException(
1495
NS_ERROR_DOM_NOT_SUPPORTED_ERR,
1496
nsPrintfCString(
1497
"%s track cannot be recorded: %s",
1498
track->AsAudioStreamTrack() ? "An audio" : "A video",
1499
TypeSupportToCString(support, mConstrainedMimeType).get()));
1500
return;
1501
}
1502
}
1503
if (mAudioNode) {
1504
TypeSupport support = CanRecordAudioTrackWith(mime, mConstrainedMimeType);
1505
if (support != TypeSupport::Supported) {
1506
aResult.ThrowDOMException(
1507
NS_ERROR_DOM_NOT_SUPPORTED_ERR,
1508
nsPrintfCString(
1509
"An AudioNode cannot be recorded: %s",
1510
TypeSupportToCString(support, mConstrainedMimeType).get()));
1511
return;
1512
}
1513
}
1514
1515
// 9. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1516
// recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1517
// values the User Agent deems reasonable for the respective media types,
1518
// for recording all tracks in tracks, such that the sum of
1519
// videoBitsPerSecond and audioBitsPerSecond is close to the value of
1520
// recorder’s
1521
// [[ConstrainedBitsPerSecond]] slot.
1522
if (mConstrainedBitsPerSecond) {
1523
uint8_t numVideoTracks = 0;
1524
uint8_t numAudioTracks = 0;
1525
for (const auto& t : tracks) {
1526
if (t->AsVideoStreamTrack() && numVideoTracks < UINT8_MAX) {
1527
++numVideoTracks;
1528
} else if (t->AsAudioStreamTrack() && numAudioTracks < UINT8_MAX) {
1529
++numAudioTracks;
1530
}
1531
}
1532
if (mAudioNode) {
1533
MOZ_DIAGNOSTIC_ASSERT(!mStream);
1534
++numAudioTracks;
1535
}
1536
SelectBitrates(*mConstrainedBitsPerSecond, numVideoTracks,
1537
&mVideoBitsPerSecond, numAudioTracks, &mAudioBitsPerSecond);
1538
}
1539
1540
// 10. Let videoBitrate be the value of recorder’s videoBitsPerSecond
1541
// attribute, and constrain the configuration of recorder to target an
1542
// aggregate bitrate of videoBitrate bits per second for all video tracks
1543
// recorder will be recording. videoBitrate is a hint for the encoder and
1544
// the value might be surpassed, not achieved, or only be achieved over a
1545
// long period of time.
1546
const uint32_t videoBitrate = mVideoBitsPerSecond;
1547
1548
// 11. Let audioBitrate be the value of recorder’s audioBitsPerSecond
1549
// attribute, and constrain the configuration of recorder to target an
1550
// aggregate bitrate of audioBitrate bits per second for all audio tracks
1551
// recorder will be recording. audioBitrate is a hint for the encoder and
1552
// the value might be surpassed, not achieved, or only be achieved over a
1553
// long period of time.
1554
const uint32_t audioBitrate = mAudioBitsPerSecond;
1555
1556
// 12. Set recorder’s state to recording
1557
mState = RecordingState::Recording;
1558
1559
MediaRecorderReporter::AddMediaRecorder(this);
1560
// Start a session.
1561
mSessions.AppendElement();
1562
mSessions.LastElement() = new Session(this, std::move(tracks), timeslice,
1563
videoBitrate, audioBitrate);
1564
mSessions.LastElement()->Start();
1565
}
1566
1567
void MediaRecorder::Stop(ErrorResult& aResult) {
1568
LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
1569
MediaRecorderReporter::RemoveMediaRecorder(this);
1570
1571
// When a MediaRecorder object’s stop() method is invoked, the UA MUST run the
1572
// following steps:
1573
1574
// 1. Let recorder be the MediaRecorder object on which the method was
1575
// invoked.
1576
1577
// 2. If recorder’s state attribute is inactive, abort these steps.
1578
if (mState == RecordingState::Inactive) {
1579
return;
1580
}
1581
1582
// 3. Inactivate the recorder with recorder.
1583
Inactivate();
1584
1585
// 4. Queue a task, using the DOM manipulation task source, that runs the
1586
// following steps:
1587
// 1. Stop gathering data.
1588
// 2. Let blob be the Blob of collected data so far, then fire a blob event
1589
// named dataavailable at recorder with blob.
1590
// 3. Fire an event named stop at recorder.
1591
MOZ_ASSERT(mSessions.Length() > 0);
1592
mSessions.LastElement()->Stop();
1593
1594
// 5. return undefined.
1595
}
1596
1597
void MediaRecorder::Pause(ErrorResult& aResult) {
1598
LOG(LogLevel::Debug, ("MediaRecorder.Pause %p", this));
1599
1600
// When a MediaRecorder object’s pause() method is invoked, the UA MUST run
1601
// the following steps:
1602
1603
// 1. If state is inactive, throw an InvalidStateError DOMException and abort
1604
// these steps.
1605
if (mState == RecordingState::Inactive) {
1606
aResult.ThrowDOMException(
1607
NS_ERROR_DOM_INVALID_STATE_ERR,
1608
NS_LITERAL_CSTRING("The MediaRecorder is inactive"));
1609
return;
1610
}
1611
1612
// 2. If state is paused, abort these steps.
1613
if (mState == RecordingState::Paused) {
1614
return;
1615
}
1616
1617
// 3. Set state to paused, and queue a task, using the DOM manipulation task
1618
// source, that runs the following steps:
1619
mState = RecordingState::Paused;
1620
1621
// XXX - We pause synchronously pending spec issue
1623
// 1. Stop gathering data into blob (but keep it available so that
1624
// recording can be resumed in the future).
1625
MOZ_ASSERT(!mSessions.IsEmpty());
1626
mSessions.LastElement()->Pause();
1627
1628
NS_DispatchToMainThread(NS_NewRunnableFunction(
1629
"MediaRecorder::Pause", [recorder = RefPtr<MediaRecorder>(this)] {
1630
// 2. Let target be the MediaRecorder context object. Fire an event
1631
// named pause at target.
1632
recorder->DispatchSimpleEvent(NS_LITERAL_STRING("pause"));
1633
}));
1634
1635
// 4. return undefined.
1636
}
1637
1638
void MediaRecorder::Resume(ErrorResult& aResult) {
1639
LOG(LogLevel::Debug, ("MediaRecorder.Resume %p", this));
1640
1641
// When a MediaRecorder object’s resume() method is invoked, the UA MUST run
1642
// the following steps:
1643
1644
// 1. If state is inactive, throw an InvalidStateError DOMException and abort
1645
// these steps.
1646
if (mState == RecordingState::Inactive) {
1647
aResult.ThrowDOMException(
1648
NS_ERROR_DOM_INVALID_STATE_ERR,
1649
NS_LITERAL_CSTRING("The MediaRecorder is inactive"));
1650
return;
1651
}
1652
1653
// 2. If state is recording, abort these steps.
1654
if (mState == RecordingState::Recording) {
1655
return;
1656
}
1657
1658
// 3. Set state to recording, and queue a task, using the DOM manipulation
1659
// task source, that runs the following steps:
1660
mState = RecordingState::Recording;
1661
1662
// XXX - We resume synchronously pending spec issue
1664
// 1. Resume (or continue) gathering data into the current blob.
1665
MOZ_ASSERT(!mSessions.IsEmpty());
1666
mSessions.LastElement()->Resume();
1667
1668
NS_DispatchToMainThread(NS_NewRunnableFunction(
1669
"MediaRecorder::Resume", [recorder = RefPtr<MediaRecorder>(this)] {
1670
// 2. Let target be the MediaRecorder context object. Fire an event
1671
// named resume at target.
1672
recorder->DispatchSimpleEvent(NS_LITERAL_STRING("resume"));
1673
}));
1674
1675
// 4. return undefined.
1676
}
1677
1678
void MediaRecorder::RequestData(ErrorResult& aResult) {
1679
LOG(LogLevel::Debug, ("MediaRecorder.RequestData %p", this));
1680
1681
// When a MediaRecorder object’s requestData() method is invoked, the UA MUST
1682
// run the following steps:
1683
1684
// 1. If state is inactive throw an InvalidStateError DOMException and
1685
// terminate these steps. Otherwise the UA MUST queue a task, using the DOM
1686
// manipulation task source, that runs the following steps:
1687
// 1. Let blob be the Blob of collected data so far and let target be the
1688
// MediaRecorder context object, then fire a blob event named
1689
// dataavailable at target with blob. (Note that blob will be empty if no
1690
// data has been gathered yet.)
1691
// 2. Create a new Blob and gather subsequent data into it.
1692
if (mState == RecordingState::Inactive) {
1693
aResult.ThrowDOMException(
1694
NS_ERROR_DOM_INVALID_STATE_ERR,
1695
NS_LITERAL_CSTRING("The MediaRecorder is inactive"));
1696
return;
1697
}
1698
MOZ_ASSERT(mSessions.Length() > 0);
1699
mSessions.LastElement()->RequestData();
1700
1701
// 2. return undefined.
1702
}
1703
1704
JSObject* MediaRecorder::WrapObject(JSContext* aCx,
1705
JS::Handle<JSObject*> aGivenProto) {
1706
return MediaRecorder_Binding::Wrap(aCx, this, aGivenProto);
1707
}
1708
1709
/* static */
1710
already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
1711
const GlobalObject& aGlobal, DOMMediaStream& aStream,
1712
const MediaRecorderOptions& aOptions, ErrorResult& aRv) {
1713
nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
1714
do_QueryInterface(aGlobal.GetAsSupports());
1715
if (!ownerWindow) {
1716
aRv.Throw(NS_ERROR_FAILURE);
1717
return nullptr;
1718
}
1719
1720
// When the MediaRecorder() constructor is invoked, the User Agent MUST run
1721
// the following steps:
1722
1723
// 1. Let stream be the constructor’s first argument.
1724
1725
// 2. Let options be the constructor’s second argument.
1726
1727
// 3. If invoking is type supported with options’ mimeType member as its
1728
// argument returns false, throw a NotSupportedError DOMException and abort
1729
// these steps.
1730
TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
1731
if (support != TypeSupport::Supported) {
1732
// This catches also the empty string mimeType when support for any encoders
1733
// has been disabled.
1734
aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
1735
TypeSupportToCString(support, aOptions.mMimeType));
1736
return nullptr;
1737
}
1738
1739
// 4. Let recorder be a newly constructed MediaRecorder object.
1740
RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
1741
1742
// 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
1743
// to the value of options' mimeType member.
1744
recorder->mConstrainedMimeType = aOptions.mMimeType;
1745
1746
// 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
1747
// initialized to the value of options’ bitsPerSecond member, if it is
1748
// present, otherwise undefined.
1749
recorder->mConstrainedBitsPerSecond =
1750
aOptions.mBitsPerSecond.WasPassed()
1751
? Some(aOptions.mBitsPerSecond.Value())
1752
: Nothing();
1753
1754
// 7. Initialize recorder’s stream attribute to stream.
1755
recorder->mStream = &aStream;
1756
1757
// 8. Initialize recorder’s mimeType attribute to the value of recorder’s
1758
// [[ConstrainedMimeType]] slot.
1759
recorder->mMimeType = recorder->mConstrainedMimeType;
1760
1761
// 9. Initialize recorder’s state attribute to inactive.
1762
recorder->mState = RecordingState::Inactive;
1763
1764
// 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
1765
// options’ videoBitsPerSecond member, if it is present. Otherwise, choose
1766
// a target value the User Agent deems reasonable for video.
1767
recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
1768
? aOptions.mVideoBitsPerSecond.Value()
1769
: DEFAULT_VIDEO_BITRATE_BPS;
1770
1771
// 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
1772
// options’ audioBitsPerSecond member, if it is present. Otherwise, choose
1773
// a target value the User Agent deems reasonable for audio.
1774
recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
1775
? aOptions.mAudioBitsPerSecond.Value()
1776
: DEFAULT_AUDIO_BITRATE_BPS;
1777
1778
// 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
1779
// recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
1780
// values the User Agent deems reasonable for the respective media types,
1781
// such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
1782
// to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
1783
if (recorder->mConstrainedBitsPerSecond) {
1784
SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
1785
&recorder->mVideoBitsPerSecond, 1,
1786
&recorder->mAudioBitsPerSecond);
1787
}
1788
1789
// 13. Return recorder.
1790
return recorder.forget();
1791
}
1792
1793
/* static */
1794
already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
1795
const GlobalObject& aGlobal, AudioNode& aAudioNode,
1796
uint32_t aAudioNodeOutput, const MediaRecorderOptions& aOptions,
1797
ErrorResult& aRv) {
1798
// Allow recording from audio node only when pref is on.
1799
if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
1800
// Pretending that this constructor is not defined.
1801
NS_NAMED_LITERAL_STRING(argStr, "Argument 1 of MediaRecorder.constructor");
1802
NS_NAMED_LITERAL_STRING(typeStr, "MediaStream");
1803
aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>(argStr, typeStr);
1804
return nullptr;
1805
}
1806
1807
nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
1808
do_QueryInterface(aGlobal.GetAsSupports());
1809
if (!ownerWindow) {
1810
aRv.Throw(NS_ERROR_FAILURE);
1811
return nullptr;
1812
}
1813
1814
// aAudioNodeOutput doesn't matter to destination node because it has no
1815
// output.
1816
if (aAudioNode.NumberOfOutputs() > 0 &&
1817
aAudioNodeOutput >= aAudioNode.NumberOfOutputs()) {
1818
aRv.ThrowDOMException(NS_ERROR_DOM_INDEX_SIZE_ERR,
1819
NS_LITERAL_CSTRING("Invalid AudioNode output index"));
1820
return nullptr;
1821
}
1822
1823
// When the MediaRecorder() constructor is invoked, the User Agent MUST run
1824
// the following steps:
1825
1826
// 1. Let stream be the constructor’s first argument. (we'll let audioNode be
1827
// the first arg, and audioNodeOutput the second)
1828
1829
// 2. Let options be the constructor’s second argument. (we'll let options be
1830
// the third arg)
1831
1832
// 3. If invoking is type supported with options’ mimeType member as its
1833
// argument returns false, throw a NotSupportedError DOMException and abort
1834
// these steps.
1835
TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
1836
if (support != TypeSupport::Supported) {
1837
// This catches also the empty string mimeType when support for any encoders
1838
// has been disabled.
1839
aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
1840
TypeSupportToCString(support, aOptions.mMimeType));
1841
return nullptr;
1842
}
1843
1844
// 4. Let recorder be a newly constructed MediaRecorder object.
1845
RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
1846
1847
// 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
1848
// to the value of options' mimeType member.
1849
recorder->mConstrainedMimeType = aOptions.mMimeType;
1850
1851
// 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
1852
// initialized to the value of options’ bitsPerSecond member, if it is
1853
// present, otherwise undefined.
1854
recorder->mConstrainedBitsPerSecond =
1855
aOptions.mBitsPerSecond.WasPassed()
1856
? Some(aOptions.mBitsPerSecond.Value())
1857
: Nothing();
1858
1859
// 7. Initialize recorder’s stream attribute to stream. (make that the
1860
// audioNode and audioNodeOutput equivalents)
1861
recorder->mAudioNode = &aAudioNode;
1862
recorder->mAudioNodeOutput = aAudioNodeOutput;
1863
1864
// 8. Initialize recorder’s mimeType attribute to the value of recorder’s
1865
// [[ConstrainedMimeType]] slot.