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 "AudioNodeStream.h"
11
#include "DOMMediaStream.h"
12
#include "GeckoProfiler.h"
13
#include "MediaDecoder.h"
14
#include "MediaEncoder.h"
15
#include "MediaStreamGraphImpl.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/File.h"
21
#include "mozilla/dom/MediaRecorderErrorEvent.h"
22
#include "mozilla/dom/MutableBlobStorage.h"
23
#include "mozilla/dom/VideoStreamTrack.h"
24
#include "mozilla/media/MediaUtils.h"
25
#include "mozilla/MemoryReporting.h"
26
#include "mozilla/Preferences.h"
27
#include "mozilla/StaticPtr.h"
28
#include "mozilla/TaskQueue.h"
29
#include "nsAutoPtr.h"
30
#include "nsCharSeparatedTokenizer.h"
31
#include "nsContentTypeParser.h"
32
#include "nsContentUtils.h"
33
#include "nsDocShell.h"
34
#include "nsError.h"
35
#include "mozilla/dom/Document.h"
36
#include "nsIPermissionManager.h"
37
#include "nsIPrincipal.h"
38
#include "nsIScriptError.h"
39
#include "nsMimeTypes.h"
40
#include "nsProxyRelease.h"
41
#include "nsTArray.h"
42
43
#ifdef LOG
44
# undef LOG
45
#endif
46
47
mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
48
#define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
49
50
namespace mozilla {
51
52
namespace dom {
53
54
using namespace mozilla::media;
55
56
/* static */ StaticRefPtr<nsIAsyncShutdownBlocker>
57
gMediaRecorderShutdownBlocker;
58
static nsTHashtable<nsRefPtrHashKey<MediaRecorder::Session>> gSessions;
59
60
/**
61
* MediaRecorderReporter measures memory being used by the Media Recorder.
62
*
63
* It is a singleton reporter and the single class object lives as long as at
64
* least one Recorder is registered. In MediaRecorder, the reporter is
65
* unregistered when it is destroyed.
66
*/
67
class MediaRecorderReporter final : public nsIMemoryReporter {
68
public:
69
static void AddMediaRecorder(MediaRecorder* aRecorder) {
70
if (!sUniqueInstance) {
71
sUniqueInstance = MakeAndAddRef<MediaRecorderReporter>();
72
RegisterWeakAsyncMemoryReporter(sUniqueInstance);
73
}
74
sUniqueInstance->mRecorders.AppendElement(aRecorder);
75
}
76
77
static void RemoveMediaRecorder(MediaRecorder* aRecorder) {
78
if (!sUniqueInstance) {
79
return;
80
}
81
82
sUniqueInstance->mRecorders.RemoveElement(aRecorder);
83
if (sUniqueInstance->mRecorders.IsEmpty()) {
84
UnregisterWeakMemoryReporter(sUniqueInstance);
85
sUniqueInstance = nullptr;
86
}
87
}
88
89
NS_DECL_THREADSAFE_ISUPPORTS
90
91
MediaRecorderReporter() = default;
92
93
NS_IMETHOD
94
CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
95
bool aAnonymize) override {
96
nsTArray<RefPtr<MediaRecorder::SizeOfPromise>> promises;
97
for (const RefPtr<MediaRecorder>& recorder : mRecorders) {
98
promises.AppendElement(recorder->SizeOfExcludingThis(MallocSizeOf));
99
}
100
101
nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
102
nsCOMPtr<nsISupports> data = aData;
103
MediaRecorder::SizeOfPromise::All(GetCurrentThreadSerialEventTarget(),
104
promises)
105
->Then(
106
GetCurrentThreadSerialEventTarget(), __func__,
107
[handleReport, data](const nsTArray<size_t>& sizes) {
108
nsCOMPtr<nsIMemoryReporterManager> manager =
109
do_GetService("@mozilla.org/memory-reporter-manager;1");
110
if (!manager) {
111
return;
112
}
113
114
size_t sum = 0;
115
for (const size_t& size : sizes) {
116
sum += size;
117
}
118
119
handleReport->Callback(
120
EmptyCString(), NS_LITERAL_CSTRING("explicit/media/recorder"),
121
KIND_HEAP, UNITS_BYTES, sum,
122
NS_LITERAL_CSTRING("Memory used by media recorder."), data);
123
124
manager->EndReport();
125
},
126
[](size_t) { MOZ_CRASH("Unexpected reject"); });
127
128
return NS_OK;
129
}
130
131
private:
132
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
133
134
virtual ~MediaRecorderReporter() {
135
MOZ_ASSERT(mRecorders.IsEmpty(), "All recorders must have been removed");
136
}
137
138
static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
139
140
nsTArray<RefPtr<MediaRecorder>> mRecorders;
141
};
142
NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
143
144
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
145
146
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
147
DOMEventTargetHelper)
148
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStream)
149
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
150
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityDomException)
151
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnknownDomException)
152
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
153
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
154
155
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
156
DOMEventTargetHelper)
157
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStream)
158
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
159
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityDomException)
160
NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnknownDomException)
161
tmp->UnRegisterActivityObserver();
162
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
163
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
164
165
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder)
166
NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
167
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
168
169
NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
170
NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
171
172
/**
173
* Session is an object to represent a single recording event.
174
* In original design, all recording context is stored in MediaRecorder, which
175
* causes a problem if someone calls MediaRecorder::Stop and
176
* MediaRecorder::Start quickly. To prevent blocking main thread, media encoding
177
* is executed in a second thread, named as Read Thread. For the same reason, we
178
* do not wait Read Thread shutdown in MediaRecorder::Stop. If someone call
179
* MediaRecorder::Start before Read Thread shutdown, the same recording context
180
* in MediaRecorder might be access by two Reading Threads, which cause a
181
* problem. In the new design, we put recording context into Session object,
182
* including Read Thread. Each Session has its own recording context and Read
183
* Thread, problem is been resolved.
184
*
185
* Life cycle of a Session object.
186
* 1) Initialization Stage (in main thread)
187
* Setup media streams in MSG, and bind MediaEncoder with Source Stream when
188
* mStream is available. Resource allocation, such as encoded data cache buffer
189
* and MediaEncoder. Create read thread. Automatically switch to Extract stage
190
* in the end of this stage. 2) Extract Stage (in Read Thread) Pull encoded A/V
191
* frames from MediaEncoder, dispatch to OnDataAvailable handler. Unless a
192
* client calls Session::Stop, Session object keeps stay in this stage. 3)
193
* Destroy Stage (in main thread) Switch from Extract stage to Destroy stage by
194
* calling Session::Stop. Release session resource and remove associated streams
195
* from MSG.
196
*
197
* Lifetime of MediaRecorder and Session objects.
198
* 1) MediaRecorder creates a Session in MediaRecorder::Start function and holds
199
* a reference to Session. Then the Session registers itself to a
200
* ShutdownBlocker and also holds a reference to MediaRecorder.
201
* Therefore, the reference dependency in gecko is:
202
* ShutdownBlocker -> Session <-> MediaRecorder, note that there is a cycle
203
* reference between Session and MediaRecorder.
204
* 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being
205
* called _and_ all encoded media data been passed to OnDataAvailable handler.
206
* 3) MediaRecorder::Stop is called by user or the document is going to
207
* inactive or invisible.
208
*/
209
class MediaRecorder::Session : public PrincipalChangeObserver<MediaStreamTrack>,
210
public DOMMediaStream::TrackListener {
211
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Session)
212
213
// Main thread task.
214
// Create a blob event and send back to client.
215
class PushBlobRunnable : public Runnable, public MutableBlobStorageCallback {
216
public:
217
// We need to always declare refcounting because
218
// MutableBlobStorageCallback has pure-virtual refcounting.
219
NS_DECL_ISUPPORTS_INHERITED
220
221
// aDestroyRunnable can be null. If it's not, it will be dispatched after
222
// the PushBlobRunnable::Run().
223
PushBlobRunnable(Session* aSession, Runnable* aDestroyRunnable)
224
: Runnable("dom::MediaRecorder::Session::PushBlobRunnable"),
225
mSession(aSession),
226
mDestroyRunnable(aDestroyRunnable) {}
227
228
NS_IMETHOD Run() override {
229
LOG(LogLevel::Debug, ("Session.PushBlobRunnable s=(%p)", mSession.get()));
230
MOZ_ASSERT(NS_IsMainThread());
231
232
mSession->GetBlobWhenReady(this);
233
return NS_OK;
234
}
235
236
void BlobStoreCompleted(MutableBlobStorage* aBlobStorage, Blob* aBlob,
237
nsresult aRv) override {
238
RefPtr<MediaRecorder> recorder = mSession->mRecorder;
239
if (!recorder) {
240
return;
241
}
242
243
if (NS_FAILED(aRv)) {
244
mSession->DoSessionEndTask(aRv);
245
return;
246
}
247
248
nsresult rv = recorder->CreateAndDispatchBlobEvent(aBlob);
249
if (NS_FAILED(rv)) {
250
mSession->DoSessionEndTask(aRv);
251
}
252
253
if (mDestroyRunnable &&
254
NS_FAILED(NS_DispatchToMainThread(mDestroyRunnable.forget()))) {
255
MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
256
}
257
}
258
259
private:
260
~PushBlobRunnable() = default;
261
262
RefPtr<Session> mSession;
263
264
// The generation of the blob is async. In order to avoid dispatching the
265
// DestroyRunnable before pushing the blob event, we store the runnable
266
// here.
267
RefPtr<Runnable> mDestroyRunnable;
268
};
269
270
class StoreEncodedBufferRunnable final : public Runnable {
271
RefPtr<Session> mSession;
272
nsTArray<nsTArray<uint8_t>> mBuffer;
273
274
public:
275
StoreEncodedBufferRunnable(Session* aSession,
276
nsTArray<nsTArray<uint8_t>>&& aBuffer)
277
: Runnable("StoreEncodedBufferRunnable"),
278
mSession(aSession),
279
mBuffer(std::move(aBuffer)) {}
280
281
NS_IMETHOD
282
Run() override {
283
MOZ_ASSERT(NS_IsMainThread());
284
mSession->MaybeCreateMutableBlobStorage();
285
for (uint32_t i = 0; i < mBuffer.Length(); i++) {
286
if (mBuffer[i].IsEmpty()) {
287
continue;
288
}
289
290
nsresult rv = mSession->mMutableBlobStorage->Append(
291
mBuffer[i].Elements(), mBuffer[i].Length());
292
if (NS_WARN_IF(NS_FAILED(rv))) {
293
mSession->DoSessionEndTask(rv);
294
break;
295
}
296
}
297
298
return NS_OK;
299
}
300
};
301
302
// Notify encoder error, run in main thread task. (Bug 1095381)
303
class EncoderErrorNotifierRunnable : public Runnable {
304
public:
305
explicit EncoderErrorNotifierRunnable(Session* aSession)
306
: Runnable("dom::MediaRecorder::Session::EncoderErrorNotifierRunnable"),
307
mSession(aSession) {}
308
309
NS_IMETHOD Run() override {
310
LOG(LogLevel::Debug,
311
("Session.ErrorNotifyRunnable s=(%p)", mSession.get()));
312
MOZ_ASSERT(NS_IsMainThread());
313
314
RefPtr<MediaRecorder> recorder = mSession->mRecorder;
315
if (!recorder) {
316
return NS_OK;
317
}
318
319
recorder->NotifyError(NS_ERROR_UNEXPECTED);
320
return NS_OK;
321
}
322
323
private:
324
RefPtr<Session> mSession;
325
};
326
327
// Fire a named event, run in main thread task.
328
class DispatchEventRunnable : public Runnable {
329
public:
330
explicit DispatchEventRunnable(Session* aSession,
331
const nsAString& aEventName)
332
: Runnable("dom::MediaRecorder::Session::DispatchEventRunnable"),
333
mSession(aSession),
334
mEventName(aEventName) {}
335
336
NS_IMETHOD Run() override {
337
LOG(LogLevel::Debug,
338
("Session.DispatchEventRunnable s=(%p) e=(%s)", mSession.get(),
339
NS_ConvertUTF16toUTF8(mEventName).get()));
340
MOZ_ASSERT(NS_IsMainThread());
341
342
NS_ENSURE_TRUE(mSession->mRecorder, NS_OK);
343
mSession->mRecorder->DispatchSimpleEvent(mEventName);
344
345
return NS_OK;
346
}
347
348
private:
349
RefPtr<Session> mSession;
350
nsString mEventName;
351
};
352
353
// Main thread task.
354
// To delete RecordingSession object.
355
class DestroyRunnable : public Runnable {
356
public:
357
explicit DestroyRunnable(Session* aSession)
358
: Runnable("dom::MediaRecorder::Session::DestroyRunnable"),
359
mSession(aSession) {}
360
361
explicit DestroyRunnable(already_AddRefed<Session> aSession)
362
: Runnable("dom::MediaRecorder::Session::DestroyRunnable"),
363
mSession(aSession) {}
364
365
NS_IMETHOD Run() override {
366
LOG(LogLevel::Debug,
367
("Session.DestroyRunnable session refcnt = (%d) s=(%p)",
368
static_cast<int>(mSession->mRefCnt), mSession.get()));
369
MOZ_ASSERT(NS_IsMainThread() && mSession);
370
RefPtr<MediaRecorder> recorder = mSession->mRecorder;
371
if (!recorder) {
372
return NS_OK;
373
}
374
// SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
375
// Read Thread will be terminate soon.
376
// We need to switch MediaRecorder to "Stop" state first to make sure
377
// MediaRecorder is not associated with this Session anymore, then, it's
378
// safe to delete this Session.
379
// Also avoid to run if this session already call stop before
380
if (mSession->mRunningState.isOk() &&
381
mSession->mRunningState.unwrap() != RunningState::Stopping &&
382
mSession->mRunningState.unwrap() != RunningState::Stopped) {
383
recorder->StopForSessionDestruction();
384
if (NS_FAILED(NS_DispatchToMainThread(
385
new DestroyRunnable(mSession.forget())))) {
386
MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
387
}
388
return NS_OK;
389
}
390
391
if (mSession->mRunningState.isOk()) {
392
mSession->mRunningState = RunningState::Stopped;
393
}
394
395
// Dispatch stop event and clear MIME type.
396
mSession->mMimeType = NS_LITERAL_STRING("");
397
recorder->SetMimeType(mSession->mMimeType);
398
recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
399
400
RefPtr<Session> session = mSession.forget();
401
session->Shutdown()->Then(
402
GetCurrentThreadSerialEventTarget(), __func__,
403
[session]() {
404
gSessions.RemoveEntry(session);
405
if (gSessions.Count() == 0 && gMediaRecorderShutdownBlocker) {
406
// All sessions finished before shutdown, no need to keep the
407
// blocker.
408
RefPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
409
barrier->RemoveBlocker(gMediaRecorderShutdownBlocker);
410
gMediaRecorderShutdownBlocker = nullptr;
411
}
412
},
413
[]() { MOZ_CRASH("Not reached"); });
414
return NS_OK;
415
}
416
417
private:
418
// Call mSession::Release automatically while DestroyRunnable be destroy.
419
RefPtr<Session> mSession;
420
};
421
422
class EncoderListener : public MediaEncoderListener {
423
public:
424
EncoderListener(TaskQueue* aEncoderThread, Session* aSession)
425
: mEncoderThread(aEncoderThread), mSession(aSession) {}
426
427
void Forget() {
428
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
429
mSession = nullptr;
430
}
431
432
void Initialized() override {
433
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
434
if (mSession) {
435
mSession->MediaEncoderInitialized();
436
}
437
}
438
439
void DataAvailable() override {
440
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
441
if (mSession) {
442
mSession->MediaEncoderDataAvailable();
443
}
444
}
445
446
void Error() override {
447
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
448
if (mSession) {
449
mSession->MediaEncoderError();
450
}
451
}
452
453
void Shutdown() override {
454
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
455
if (mSession) {
456
mSession->MediaEncoderShutdown();
457
}
458
}
459
460
protected:
461
RefPtr<TaskQueue> mEncoderThread;
462
RefPtr<Session> mSession;
463
};
464
465
friend class EncoderErrorNotifierRunnable;
466
friend class PushBlobRunnable;
467
friend class DestroyRunnable;
468
469
public:
470
Session(MediaRecorder* aRecorder, uint32_t aTimeSlice)
471
: mRecorder(aRecorder),
472
mMediaStreamReady(false),
473
mTimeSlice(aTimeSlice),
474
mRunningState(RunningState::Idling) {
475
MOZ_ASSERT(NS_IsMainThread());
476
477
aRecorder->GetMimeType(mMimeType);
478
mMaxMemory = Preferences::GetUint("media.recorder.max_memory",
479
MAX_ALLOW_MEMORY_BUFFER);
480
mLastBlobTimeStamp = TimeStamp::Now();
481
}
482
483
void PrincipalChanged(MediaStreamTrack* aTrack) override {
484
NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
485
"Principal changed for unrecorded track");
486
if (!MediaStreamTracksPrincipalSubsumes()) {
487
DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
488
}
489
}
490
491
void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
492
LOG(LogLevel::Warning,
493
("Session.NotifyTrackAdded %p Raising error due to track set change",
494
this));
495
if (mMediaStreamReady) {
496
DoSessionEndTask(NS_ERROR_ABORT);
497
}
498
499
NS_DispatchToMainThread(
500
NewRunnableMethod("MediaRecorder::Session::MediaStreamReady", this,
501
&Session::MediaStreamReady));
502
return;
503
}
504
505
void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
506
if (!mMediaStreamReady) {
507
// We haven't chosen the track set to record yet.
508
return;
509
}
510
511
if (aTrack->Ended()) {
512
// TrackEncoder will pickup tracks that end itself.
513
return;
514
}
515
516
MOZ_ASSERT(mEncoder);
517
if (mEncoder) {
518
mEncoder->RemoveMediaStreamTrack(aTrack);
519
}
520
521
LOG(LogLevel::Warning,
522
("Session.NotifyTrackRemoved %p Raising error due to track set change",
523
this));
524
DoSessionEndTask(NS_ERROR_ABORT);
525
}
526
527
void Start() {
528
LOG(LogLevel::Debug, ("Session.Start %p", this));
529
MOZ_ASSERT(NS_IsMainThread());
530
531
DOMMediaStream* domStream = mRecorder->Stream();
532
if (domStream) {
533
// The callback reports back when tracks are available and can be
534
// attached to MediaEncoder. This allows `recorder.start()` before any
535
// tracks are available. We have supported this historically and have
536
// mochitests assuming this behavior.
537
mMediaStream = domStream;
538
mMediaStream->RegisterTrackListener(this);
539
nsTArray<RefPtr<MediaStreamTrack>> tracks(2);
540
mMediaStream->GetTracks(tracks);
541
for (const auto& track : tracks) {
542
// Notify of existing tracks, as the stream doesn't do this by itself.
543
NotifyTrackAdded(track);
544
}
545
return;
546
}
547
548
if (mRecorder->mAudioNode) {
549
// Check that we may access the audio node's content.
550
if (!AudioNodePrincipalSubsumes()) {
551
LOG(LogLevel::Warning,
552
("Session.Start AudioNode principal check failed"));
553
DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
554
return;
555
}
556
557
TrackRate trackRate =
558
mRecorder->mAudioNode->Context()->Graph()->GraphRate();
559
560
// Web Audio node has only audio.
561
InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate);
562
return;
563
}
564
565
MOZ_ASSERT(false, "Unknown source");
566
}
567
568
void Stop() {
569
LOG(LogLevel::Debug, ("Session.Stop %p", this));
570
MOZ_ASSERT(NS_IsMainThread());
571
572
if (mEncoder) {
573
mEncoder->Stop();
574
}
575
576
// Remove main thread state added in Start().
577
if (mMediaStream) {
578
mMediaStream->UnregisterTrackListener(this);
579
mMediaStream = nullptr;
580
}
581
582
{
583
auto tracks(std::move(mMediaStreamTracks));
584
for (RefPtr<MediaStreamTrack>& track : tracks) {
585
track->RemovePrincipalChangeObserver(this);
586
}
587
}
588
589
if (mRunningState.isOk() &&
590
mRunningState.unwrap() == RunningState::Idling) {
591
LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this));
592
// End the Session directly if there is no ExtractRunnable.
593
DoSessionEndTask(NS_OK);
594
} else if (mRunningState.isOk() &&
595
(mRunningState.unwrap() == RunningState::Starting ||
596
mRunningState.unwrap() == RunningState::Running)) {
597
mRunningState = RunningState::Stopping;
598
}
599
}
600
601
nsresult Pause() {
602
LOG(LogLevel::Debug, ("Session.Pause"));
603
MOZ_ASSERT(NS_IsMainThread());
604
605
if (!mEncoder) {
606
return NS_ERROR_FAILURE;
607
}
608
609
mEncoder->Suspend();
610
NS_DispatchToMainThread(
611
new DispatchEventRunnable(this, NS_LITERAL_STRING("pause")));
612
return NS_OK;
613
}
614
615
nsresult Resume() {
616
LOG(LogLevel::Debug, ("Session.Resume"));
617
MOZ_ASSERT(NS_IsMainThread());
618
619
if (!mEncoder) {
620
return NS_ERROR_FAILURE;
621
}
622
623
mEncoder->Resume();
624
NS_DispatchToMainThread(
625
new DispatchEventRunnable(this, NS_LITERAL_STRING("resume")));
626
return NS_OK;
627
}
628
629
nsresult RequestData() {
630
LOG(LogLevel::Debug, ("Session.RequestData"));
631
MOZ_ASSERT(NS_IsMainThread());
632
633
if (NS_FAILED(
634
NS_DispatchToMainThread(new PushBlobRunnable(this, nullptr)))) {
635
MOZ_ASSERT(false, "RequestData NS_DispatchToMainThread failed");
636
return NS_ERROR_FAILURE;
637
}
638
639
return NS_OK;
640
}
641
642
void MaybeCreateMutableBlobStorage() {
643
if (!mMutableBlobStorage) {
644
mMutableBlobStorage = new MutableBlobStorage(
645
MutableBlobStorage::eCouldBeInTemporaryFile, nullptr, mMaxMemory);
646
}
647
}
648
649
void GetBlobWhenReady(MutableBlobStorageCallback* aCallback) {
650
MOZ_ASSERT(NS_IsMainThread());
651
652
MaybeCreateMutableBlobStorage();
653
mMutableBlobStorage->GetBlobWhenReady(mRecorder->GetParentObject(),
654
NS_ConvertUTF16toUTF8(mMimeType),
655
aCallback);
656
mMutableBlobStorage = nullptr;
657
}
658
659
RefPtr<SizeOfPromise> SizeOfExcludingThis(
660
mozilla::MallocSizeOf aMallocSizeOf) {
661
MOZ_ASSERT(NS_IsMainThread());
662
size_t encodedBufferSize =
663
mMutableBlobStorage ? mMutableBlobStorage->SizeOfCurrentMemoryBuffer()
664
: 0;
665
666
if (!mEncoder) {
667
return SizeOfPromise::CreateAndResolve(encodedBufferSize, __func__);
668
}
669
670
auto& encoder = mEncoder;
671
return InvokeAsync(
672
mEncoderThread, __func__,
673
[encoder, encodedBufferSize, aMallocSizeOf]() {
674
return SizeOfPromise::CreateAndResolve(
675
encodedBufferSize + encoder->SizeOfExcludingThis(aMallocSizeOf),
676
__func__);
677
});
678
}
679
680
private:
681
// Only DestroyRunnable is allowed to delete Session object on main thread.
682
virtual ~Session() {
683
MOZ_ASSERT(NS_IsMainThread());
684
MOZ_ASSERT(mShutdownPromise);
685
LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
686
}
687
// Pull encoded media data from MediaEncoder and put into MutableBlobStorage.
688
// Destroy this session object in the end of this function.
689
// If the bool aForceFlush is true, we will force to dispatch a
690
// PushBlobRunnable to main thread.
691
void Extract(bool aForceFlush, Runnable* aDestroyRunnable) {
692
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
693
694
LOG(LogLevel::Debug, ("Session.Extract %p", this));
695
696
AUTO_PROFILER_LABEL("MediaRecorder::Session::Extract", OTHER);
697
698
// Pull encoded media data from MediaEncoder
699
nsTArray<nsTArray<uint8_t>> encodedBuf;
700
nsresult rv = mEncoder->GetEncodedData(&encodedBuf);
701
if (NS_FAILED(rv)) {
702
MOZ_RELEASE_ASSERT(encodedBuf.IsEmpty());
703
// Even if we failed to encode more data, it might be time to push a blob
704
// with already encoded data.
705
}
706
707
// Append pulled data into cache buffer.
708
NS_DispatchToMainThread(
709
new StoreEncodedBufferRunnable(this, std::move(encodedBuf)));
710
711
// Whether push encoded data back to onDataAvailable automatically or we
712
// need a flush.
713
bool pushBlob = aForceFlush;
714
if (!pushBlob && mTimeSlice > 0 &&
715
(TimeStamp::Now() - mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice) {
716
pushBlob = true;
717
}
718
if (pushBlob) {
719
if (NS_FAILED(NS_DispatchToMainThread(
720
new PushBlobRunnable(this, aDestroyRunnable)))) {
721
MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
722
} else {
723
mLastBlobTimeStamp = TimeStamp::Now();
724
}
725
} else if (aDestroyRunnable) {
726
if (NS_FAILED(NS_DispatchToMainThread(aDestroyRunnable))) {
727
MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
728
}
729
}
730
}
731
732
void MediaStreamReady() {
733
if (!mMediaStream) {
734
// Already shut down. This can happen because MediaStreamReady is async.
735
return;
736
}
737
738
if (mMediaStreamReady) {
739
return;
740
}
741
742
if (!mRunningState.isOk() ||
743
mRunningState.unwrap() != RunningState::Idling) {
744
return;
745
}
746
747
nsTArray<RefPtr<mozilla::dom::MediaStreamTrack>> tracks;
748
mMediaStream->GetTracks(tracks);
749
uint8_t trackTypes = 0;
750
int32_t audioTracks = 0;
751
int32_t videoTracks = 0;
752
for (auto& track : tracks) {
753
if (track->Ended()) {
754
continue;
755
}
756
757
ConnectMediaStreamTrack(*track);
758
759
if (track->AsAudioStreamTrack()) {
760
++audioTracks;
761
trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
762
} else if (track->AsVideoStreamTrack()) {
763
++videoTracks;
764
trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
765
} else {
766
MOZ_CRASH("Unexpected track type");
767
}
768
}
769
770
if (trackTypes == 0) {
771
MOZ_ASSERT(audioTracks == 0);
772
MOZ_ASSERT(videoTracks == 0);
773
return;
774
}
775
776
mMediaStreamReady = true;
777
778
if (audioTracks > 1 || videoTracks > 1) {
779
// When MediaRecorder supports multiple tracks, we should set up a single
780
// MediaInputPort from the input stream, and let main thread check
781
// track principals async later.
782
nsPIDOMWindowInner* window = mRecorder->GetParentObject();
783
Document* document = window ? window->GetExtantDoc() : nullptr;
784
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
785
NS_LITERAL_CSTRING("Media"), document,
786
nsContentUtils::eDOM_PROPERTIES,
787
"MediaRecorderMultiTracksNotSupported");
788
DoSessionEndTask(NS_ERROR_ABORT);
789
return;
790
}
791
792
// Check that we may access the tracks' content.
793
if (!MediaStreamTracksPrincipalSubsumes()) {
794
LOG(LogLevel::Warning, ("Session.MediaTracksReady MediaStreamTracks "
795
"principal check failed"));
796
DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
797
return;
798
}
799
800
LOG(LogLevel::Debug,
801
("Session.MediaTracksReady track type = (%d)", trackTypes));
802
InitEncoder(trackTypes, mMediaStream->GraphRate());
803
}
804
805
void ConnectMediaStreamTrack(MediaStreamTrack& aTrack) {
806
for (auto& track : mMediaStreamTracks) {
807
if (track->AsAudioStreamTrack() && aTrack.AsAudioStreamTrack()) {
808
// We only allow one audio track. See bug 1276928.
809
return;
810
}
811
if (track->AsVideoStreamTrack() && aTrack.AsVideoStreamTrack()) {
812
// We only allow one video track. See bug 1276928.
813
return;
814
}
815
}
816
mMediaStreamTracks.AppendElement(&aTrack);
817
aTrack.AddPrincipalChangeObserver(this);
818
}
819
820
bool PrincipalSubsumes(nsIPrincipal* aPrincipal) {
821
if (!mRecorder->GetOwner()) return false;
822
nsCOMPtr<Document> doc = mRecorder->GetOwner()->GetExtantDoc();
823
if (!doc) {
824
return false;
825
}
826
if (!aPrincipal) {
827
return false;
828
}
829
bool subsumes;
830
if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
831
return false;
832
}
833
return subsumes;
834
}
835
836
bool MediaStreamTracksPrincipalSubsumes() {
837
MOZ_ASSERT(mRecorder->mDOMStream);
838
nsCOMPtr<nsIPrincipal> principal = nullptr;
839
for (RefPtr<MediaStreamTrack>& track : mMediaStreamTracks) {
840
nsContentUtils::CombineResourcePrincipals(&principal,
841
track->GetPrincipal());
842
}
843
return PrincipalSubsumes(principal);
844
}
845
846
bool AudioNodePrincipalSubsumes() {
847
MOZ_ASSERT(mRecorder->mAudioNode);
848
Document* doc = mRecorder->mAudioNode->GetOwner()
849
? mRecorder->mAudioNode->GetOwner()->GetExtantDoc()
850
: nullptr;
851
nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
852
return PrincipalSubsumes(principal);
853
}
854
855
void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate) {
856
LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
857
MOZ_ASSERT(NS_IsMainThread());
858
859
if (!mRunningState.isOk() ||
860
mRunningState.unwrap() != RunningState::Idling) {
861
MOZ_ASSERT_UNREACHABLE("Double-init");
862
return;
863
}
864
865
// Create a TaskQueue to read encode media data from MediaEncoder.
866
MOZ_RELEASE_ASSERT(!mEncoderThread);
867
RefPtr<SharedThreadPool> pool =
868
GetMediaThreadPool(MediaThreadType::WEBRTC_DECODER);
869
if (!pool) {
870
LOG(LogLevel::Debug, ("Session.InitEncoder %p Failed to create "
871
"MediaRecorderReadThread thread pool",
872
this));
873
DoSessionEndTask(NS_ERROR_FAILURE);
874
return;
875
}
876
877
mEncoderThread =
878
MakeAndAddRef<TaskQueue>(pool.forget(), "MediaRecorderReadThread");
879
880
if (!gMediaRecorderShutdownBlocker) {
881
// Add a shutdown blocker so mEncoderThread can be shutdown async.
882
class Blocker : public ShutdownBlocker {
883
public:
884
Blocker()
885
: ShutdownBlocker(
886
NS_LITERAL_STRING("MediaRecorder::Session: shutdown")) {}
887
888
NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override {
889
// Distribute the global async shutdown blocker in a ticket. If there
890
// are zero graphs then shutdown is unblocked when we go out of scope.
891
RefPtr<ShutdownTicket> ticket =
892
MakeAndAddRef<ShutdownTicket>(gMediaRecorderShutdownBlocker);
893
gMediaRecorderShutdownBlocker = nullptr;
894
895
nsTArray<RefPtr<ShutdownPromise>> promises(gSessions.Count());
896
for (auto iter = gSessions.Iter(); !iter.Done(); iter.Next()) {
897
promises.AppendElement(iter.Get()->GetKey()->Shutdown());
898
}
899
gSessions.Clear();
900
ShutdownPromise::All(GetCurrentThreadSerialEventTarget(), promises)
901
->Then(
902
GetCurrentThreadSerialEventTarget(), __func__,
903
[ticket]() mutable {
904
MOZ_ASSERT(gSessions.Count() == 0);
905
// Unblock shutdown
906
ticket = nullptr;
907
},
908
[]() { MOZ_CRASH("Not reached"); });
909
return NS_OK;
910
}
911
};
912
913
gMediaRecorderShutdownBlocker = MakeAndAddRef<Blocker>();
914
RefPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier();
915
nsresult rv = barrier->AddBlocker(
916
gMediaRecorderShutdownBlocker, NS_LITERAL_STRING(__FILE__), __LINE__,
917
NS_LITERAL_STRING("MediaRecorder::Session: shutdown"));
918
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
919
}
920
921
gSessions.PutEntry(this);
922
923
uint32_t audioBitrate = mRecorder->GetAudioBitrate();
924
uint32_t videoBitrate = mRecorder->GetVideoBitrate();
925
uint32_t bitrate = mRecorder->GetBitrate();
926
if (bitrate > 0) {
927
// There's a total cap set. We have to make sure the type-specific limits
928
// are within range.
929
if ((aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) &&
930
(aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK) &&
931
audioBitrate + videoBitrate > bitrate) {
932
LOG(LogLevel::Info, ("Session.InitEncoder Bitrates higher than total "
933
"cap. Recalculating."));
934
double factor =
935
bitrate / static_cast<double>(audioBitrate + videoBitrate);
936
audioBitrate = static_cast<uint32_t>(audioBitrate * factor);
937
videoBitrate = static_cast<uint32_t>(videoBitrate * factor);
938
} else if ((aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) &&
939
!(aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK)) {
940
audioBitrate = std::min(audioBitrate, bitrate);
941
videoBitrate = 0;
942
} else if (!(aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) &&
943
(aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK)) {
944
audioBitrate = 0;
945
videoBitrate = std::min(videoBitrate, bitrate);
946
}
947
MOZ_ASSERT(audioBitrate + videoBitrate <= bitrate);
948
}
949
950
// Allocate encoder and bind with union stream.
951
// At this stage, the API doesn't allow UA to choose the output mimeType
952
// format.
953
954
mEncoder =
955
MediaEncoder::CreateEncoder(mEncoderThread, mMimeType, audioBitrate,
956
videoBitrate, aTrackTypes, aTrackRate);
957
958
if (!mEncoder) {
959
LOG(LogLevel::Error, ("Session.InitEncoder !mEncoder %p", this));
960
DoSessionEndTask(NS_ERROR_ABORT);
961
return;
962
}
963
964
mEncoderListener = MakeAndAddRef<EncoderListener>(mEncoderThread, this);
965
nsresult rv =
966
mEncoderThread->Dispatch(NewRunnableMethod<RefPtr<EncoderListener>>(
967
"mozilla::MediaEncoder::RegisterListener", mEncoder,
968
&MediaEncoder::RegisterListener, mEncoderListener));
969
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
970
Unused << rv;
971
972
if (mRecorder->mAudioNode) {
973
mEncoder->ConnectAudioNode(mRecorder->mAudioNode,
974
mRecorder->mAudioNodeOutput);
975
}
976
977
for (auto& track : mMediaStreamTracks) {
978
mEncoder->ConnectMediaStreamTrack(track);
979
}
980
981
// If user defines timeslice interval for video blobs we have to set
982
// appropriate video keyframe interval defined in milliseconds.
983
mEncoder->SetVideoKeyFrameInterval(mTimeSlice);
984
985
// Set mRunningState to Running so that ExtractRunnable/DestroyRunnable will
986
// take the responsibility to end the session.
987
mRunningState = RunningState::Starting;
988
}
989
990
// application should get blob and onstop event
991
void DoSessionEndTask(nsresult rv) {
992
MOZ_ASSERT(NS_IsMainThread());
993
if (mRunningState.isErr()) {
994
// We have already ended with an error.
995
return;
996
}
997
998
if (mRunningState.isOk() &&
999
mRunningState.unwrap() == RunningState::Stopped) {
1000
// We have already ended gracefully.
1001
return;
1002
}
1003
1004
if (mRunningState.isOk() &&
1005
(mRunningState.unwrap() == RunningState::Idling ||
1006
mRunningState.unwrap() == RunningState::Starting)) {
1007
NS_DispatchToMainThread(
1008
new DispatchEventRunnable(this, NS_LITERAL_STRING("start")));
1009
}
1010
1011
if (rv == NS_OK) {
1012
mRunningState = RunningState::Stopped;
1013
} else {
1014
mRunningState = Err(rv);
1015
}
1016
1017
if (NS_FAILED(rv)) {
1018
mRecorder->ForceInactive();
1019
NS_DispatchToMainThread(NewRunnableMethod<nsresult>(
1020
"dom::MediaRecorder::NotifyError", mRecorder,
1021
&MediaRecorder::NotifyError, rv));
1022
}
1023
1024
RefPtr<Runnable> destroyRunnable = new DestroyRunnable(this);
1025
1026
if (rv != NS_ERROR_DOM_SECURITY_ERR) {
1027
// Don't push a blob if there was a security error.
1028
if (NS_FAILED(NS_DispatchToMainThread(
1029
new PushBlobRunnable(this, destroyRunnable)))) {
1030
MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
1031
}
1032
} else {
1033
if (NS_FAILED(NS_DispatchToMainThread(destroyRunnable))) {
1034
MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
1035
}
1036
}
1037
}
1038
1039
void MediaEncoderInitialized() {
1040
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
1041
1042
// Pull encoded metadata from MediaEncoder
1043
nsTArray<nsTArray<uint8_t>> encodedBuf;
1044
nsString mime;
1045
nsresult rv = mEncoder->GetEncodedMetadata(&encodedBuf, mime);
1046
1047
if (NS_FAILED(rv)) {
1048
MOZ_ASSERT(false);
1049
return;
1050
}
1051
1052
// Append pulled data into cache buffer.
1053
NS_DispatchToMainThread(
1054
new StoreEncodedBufferRunnable(this, std::move(encodedBuf)));
1055
1056
RefPtr<Session> self = this;
1057
NS_DispatchToMainThread(NewRunnableFrom([self, mime]() {
1058
if (!self->mRecorder) {
1059
MOZ_ASSERT_UNREACHABLE("Recorder should be live");
1060
return NS_OK;
1061
}
1062
if (self->mRunningState.isOk()) {
1063
auto state = self->mRunningState.unwrap();
1064
if (state == RunningState::Starting ||
1065
state == RunningState::Stopping) {
1066
if (state == RunningState::Starting) {
1067
// We set it to Running in the runnable since we can only assign
1068
// mRunningState on main thread. We set it before running the start
1069
// event runnable since that dispatches synchronously (and may cause
1070
// js calls to methods depending on mRunningState).
1071
self->mRunningState = RunningState::Running;
1072
}
1073
self->mMimeType = mime;
1074
self->mRecorder->SetMimeType(self->mMimeType);
1075
auto startEvent = MakeRefPtr<DispatchEventRunnable>(
1076
self, NS_LITERAL_STRING("start"));
1077
startEvent->Run();
1078
}
1079
}
1080
return NS_OK;
1081
}));
1082
}
1083
1084
void MediaEncoderDataAvailable() {
1085
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
1086
1087
Extract(false, nullptr);
1088
}
1089
1090
void MediaEncoderError() {
1091
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
1092
NS_DispatchToMainThread(NewRunnableMethod<nsresult>(
1093
"dom::MediaRecorder::Session::DoSessionEndTask", this,
1094
&Session::DoSessionEndTask, NS_ERROR_FAILURE));
1095
}
1096
1097
void MediaEncoderShutdown() {
1098
MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn());
1099
MOZ_ASSERT(mEncoder->IsShutdown());
1100
1101
// For the stop event. Let's the creation of the blob to dispatch this
1102
// runnable.
1103
RefPtr<Runnable> destroyRunnable = new DestroyRunnable(this);
1104
1105
// Forces the last blob even if it's not time for it yet.
1106
Extract(true, destroyRunnable);
1107
1108
// Clean up.
1109
mEncoderListener->Forget();
1110
DebugOnly<bool> unregistered =
1111
mEncoder->UnregisterListener(mEncoderListener);
1112
MOZ_ASSERT(unregistered);
1113
}
1114
1115
RefPtr<ShutdownPromise> Shutdown() {
1116
MOZ_ASSERT(NS_IsMainThread());
1117
LOG(LogLevel::Debug, ("Session Shutdown %p", this));
1118
1119
if (mShutdownPromise) {
1120
return mShutdownPromise;
1121
}
1122
1123
mShutdownPromise = ShutdownPromise::CreateAndResolve(true, __func__);
1124
RefPtr<Session> self = this;
1125
1126
if (mEncoder) {
1127
auto& encoder = mEncoder;
1128
encoder->Cancel();
1129
1130
MOZ_RELEASE_ASSERT(mEncoderListener);
1131
auto& encoderListener = mEncoderListener;
1132
mShutdownPromise = mShutdownPromise->Then(
1133
mEncoderThread, __func__,
1134
[encoder, encoderListener]() {
1135
encoder->UnregisterListener(encoderListener);
1136
encoderListener->Forget();
1137
return ShutdownPromise::CreateAndResolve(true, __func__);
1138
},
1139
[]() {
1140
MOZ_ASSERT_UNREACHABLE("Unexpected reject");
1141
return ShutdownPromise::CreateAndReject(false, __func__);
1142
});
1143
}
1144
1145
// Remove main thread state. This could be needed if Stop() wasn't called.
1146
if (mMediaStream) {
1147
mMediaStream->UnregisterTrackListener(this);
1148
mMediaStream = nullptr;
1149
}
1150
1151
{
1152
auto tracks(std::move(mMediaStreamTracks));
1153
for (RefPtr<MediaStreamTrack>& track : tracks) {
1154
track->RemovePrincipalChangeObserver(this);
1155
}
1156
}
1157
1158
// Break the cycle reference between Session and MediaRecorder.
1159
if (mRecorder) {
1160
mShutdownPromise = mShutdownPromise->Then(
1161
GetCurrentThreadSerialEventTarget(), __func__,
1162
[self]() {
1163
self->mRecorder->RemoveSession(self);
1164
self->mRecorder = nullptr;
1165
return ShutdownPromise::CreateAndResolve(true, __func__);
1166
},
1167
[]() {
1168
MOZ_ASSERT_UNREACHABLE("Unexpected reject");
1169
return ShutdownPromise::CreateAndReject(false, __func__);
1170
});
1171
}
1172
1173
if (mEncoderThread) {
1174
RefPtr<TaskQueue>& encoderThread = mEncoderThread;
1175
mShutdownPromise = mShutdownPromise->Then(
1176
GetCurrentThreadSerialEventTarget(), __func__,
1177
[encoderThread]() { return encoderThread->BeginShutdown(); },
1178
[]() {
1179
MOZ_ASSERT_UNREACHABLE("Unexpected reject");
1180
return ShutdownPromise::CreateAndReject(false, __func__);
1181
});
1182
}
1183
1184
return mShutdownPromise;
1185
}
1186
1187
private:
1188
enum class RunningState {
1189
Idling, // Session has been created
1190
Starting, // MediaEncoder started, waiting for data
1191
Running, // MediaEncoder has produced data
1192
Stopping, // Stop() has been called
1193
Stopped, // Session has stopped without any error
1194
};
1195
1196
// Hold reference to MediaRecorder that ensure MediaRecorder is alive
1197
// if there is an active session. Access ONLY on main thread.
1198
RefPtr<MediaRecorder> mRecorder;
1199
1200
// Stream currently recorded.
1201
RefPtr<DOMMediaStream> mMediaStream;
1202
1203
// True after we have decided on the track set to use for the recording.
1204
bool mMediaStreamReady;
1205
1206
// Tracks currently recorded. This should be a subset of mMediaStream's track
1207
// set.
1208
nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
1209
1210
// Runnable thread for reading data from MediaEncoder.
1211
RefPtr<TaskQueue> mEncoderThread;
1212
// MediaEncoder pipeline.
1213
RefPtr<MediaEncoder> mEncoder;
1214
// Listener through which MediaEncoder signals us.
1215
RefPtr<EncoderListener> mEncoderListener;
1216
// Set in Shutdown() and resolved when shutdown is complete.
1217
RefPtr<ShutdownPromise> mShutdownPromise;
1218
// A buffer to cache encoded media data.
1219
RefPtr<MutableBlobStorage> mMutableBlobStorage;
1220
// Max memory to use for the MutableBlobStorage.
1221
uint64_t mMaxMemory;
1222
// Current session mimeType
1223
nsString mMimeType;
1224
// Timestamp of the last fired dataavailable event.
1225
TimeStamp mLastBlobTimeStamp;
1226
// The interval of passing encoded data from MutableBlobStorage to
1227
// onDataAvailable handler.
1228
const uint32_t mTimeSlice;
1229
// The session's current main thread state. The error type gets setwhen ending
1230
// a recording with an error. An NS_OK error is invalid.
1231
// Main thread only.
1232
Result<RunningState, nsresult> mRunningState;
1233
};
1234
1235
NS_IMPL_ISUPPORTS_INHERITED0(MediaRecorder::Session::PushBlobRunnable, Runnable)
1236
1237
MediaRecorder::~MediaRecorder() {
1238
LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
1239
UnRegisterActivityObserver();
1240
}
1241
1242
MediaRecorder::MediaRecorder(DOMMediaStream& aSourceMediaStream,
1243
nsPIDOMWindowInner* aOwnerWindow)
1244
: DOMEventTargetHelper(aOwnerWindow),
1245
mAudioNodeOutput(0),
1246
mState(RecordingState::Inactive),
1247
mAudioBitsPerSecond(0),
1248
mVideoBitsPerSecond(0),
1249
mBitsPerSecond(0) {
1250
MOZ_ASSERT(aOwnerWindow);
1251
mDOMStream = &aSourceMediaStream;
1252
1253
RegisterActivityObserver();
1254
}
1255
1256
MediaRecorder::MediaRecorder(AudioNode& aSrcAudioNode, uint32_t aSrcOutput,
1257
nsPIDOMWindowInner* aOwnerWindow)
1258
: DOMEventTargetHelper(aOwnerWindow),
1259
mAudioNodeOutput(aSrcOutput),
1260
mState(RecordingState::Inactive),
1261
mAudioBitsPerSecond(0),
1262
mVideoBitsPerSecond(0),
1263
mBitsPerSecond(0) {
1264
MOZ_ASSERT(aOwnerWindow);
1265
1266
mAudioNode = &aSrcAudioNode;
1267
1268
RegisterActivityObserver();
1269
}
1270
1271
void MediaRecorder::RegisterActivityObserver() {
1272
if (nsPIDOMWindowInner* window = GetOwner()) {
1273
mDocument = window->GetExtantDoc();
1274
if (mDocument) {
1275
mDocument->RegisterActivityObserver(
1276
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1277
}
1278
}
1279
}
1280
1281
void MediaRecorder::UnRegisterActivityObserver() {
1282
if (mDocument) {
1283
mDocument->UnregisterActivityObserver(
1284
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1285
}
1286
}
1287
1288
void MediaRecorder::SetMimeType(const nsString& aMimeType) {
1289
mMimeType = aMimeType;
1290
}
1291
1292
void MediaRecorder::GetMimeType(nsString& aMimeType) { aMimeType = mMimeType; }
1293
1294
void MediaRecorder::Start(const Optional<uint32_t>& aTimeSlice,
1295
ErrorResult& aResult) {
1296
LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
1297
1298
InitializeDomExceptions();
1299
1300
if (mState != RecordingState::Inactive) {
1301
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1302
return;
1303
}
1304
1305
nsTArray<RefPtr<MediaStreamTrack>> tracks;
1306
if (mDOMStream) {
1307
mDOMStream->GetTracks(tracks);
1308
}
1309
if (!tracks.IsEmpty()) {
1310
// If there are tracks already available that we're not allowed
1311
// to record, we should throw a security error.
1312
bool subsumes = false;
1313
nsPIDOMWindowInner* window;
1314
Document* doc;
1315
if (!(window = GetOwner()) || !(doc = window->GetExtantDoc()) ||
1316
NS_FAILED(doc->NodePrincipal()->Subsumes(mDOMStream->GetPrincipal(),
1317
&subsumes)) ||
1318
!subsumes) {
1319
aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
1320
return;
1321
}
1322
}
1323
1324
uint32_t timeSlice = aTimeSlice.WasPassed() ? aTimeSlice.Value() : 0;
1325
MediaRecorderReporter::AddMediaRecorder(this);
1326
mState = RecordingState::Recording;
1327
// Start a session.
1328
mSessions.AppendElement();
1329
mSessions.LastElement() = new Session(this, timeSlice);
1330
mSessions.LastElement()->Start();
1331
mStartTime = TimeStamp::Now();
1332
Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1);
1333
}
1334
1335
void MediaRecorder::Stop(ErrorResult& aResult) {
1336
LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
1337
MediaRecorderReporter::RemoveMediaRecorder(this);
1338
if (mState == RecordingState::Inactive) {
1339
return;
1340
}
1341
mState = RecordingState::Inactive;
1342
MOZ_ASSERT(mSessions.Length() > 0);
1343
mSessions.LastElement()->Stop();
1344
}
1345
1346
void MediaRecorder::Pause(ErrorResult& aResult) {
1347
LOG(LogLevel::Debug, ("MediaRecorder.Pause %p", this));
1348
if (mState == RecordingState::Inactive) {
1349
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1350
return;
1351
}
1352
1353
if (mState == RecordingState::Paused) {
1354
return;
1355
}
1356
1357
MOZ_ASSERT(mSessions.Length() > 0);
1358
nsresult rv = mSessions.LastElement()->Pause();
1359
if (NS_FAILED(rv)) {
1360
NotifyError(rv);
1361
return;
1362
}
1363
1364
mState = RecordingState::Paused;
1365
}
1366
1367
void MediaRecorder::Resume(ErrorResult& aResult) {
1368
LOG(LogLevel::Debug, ("MediaRecorder.Resume %p", this));
1369
if (mState == RecordingState::Inactive) {
1370
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1371
return;
1372
}
1373
1374
if (mState == RecordingState::Recording) {
1375
return;
1376
}
1377
1378
MOZ_ASSERT(mSessions.Length() > 0);
1379
nsresult rv = mSessions.LastElement()->Resume();
1380
if (NS_FAILED(rv)) {
1381
NotifyError(rv);
1382
return;
1383
}
1384
1385
mState = RecordingState::Recording;
1386
}
1387
1388
void MediaRecorder::RequestData(ErrorResult& aResult) {
1389
if (mState == RecordingState::Inactive) {
1390
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1391
return;
1392
}
1393
MOZ_ASSERT(mSessions.Length() > 0);
1394
nsresult rv = mSessions.LastElement()->RequestData();
1395
if (NS_FAILED(rv)) {
1396
NotifyError(rv);
1397
}
1398
}
1399
1400
JSObject* MediaRecorder::WrapObject(JSContext* aCx,
1401
JS::Handle<JSObject*> aGivenProto) {
1402
return MediaRecorder_Binding::Wrap(aCx, this, aGivenProto);
1403
}
1404
1405
/* static */
1406
already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
1407
const GlobalObject& aGlobal, DOMMediaStream& aStream,
1408
const MediaRecorderOptions& aInitDict, ErrorResult& aRv) {
1409
nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
1410
do_QueryInterface(aGlobal.GetAsSupports());
1411
if (!ownerWindow) {
1412
aRv.Throw(NS_ERROR_FAILURE);
1413
return nullptr;
1414
}
1415
1416
if (!IsTypeSupported(aInitDict.mMimeType)) {
1417
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
1418
return nullptr;
1419
}
1420
1421
RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
1422
object->SetOptions(aInitDict);
1423
return object.forget();
1424
}
1425
1426
/* static */
1427
already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
1428
const GlobalObject& aGlobal, AudioNode& aSrcAudioNode, uint32_t aSrcOutput,
1429
const MediaRecorderOptions& aInitDict, ErrorResult& aRv) {
1430
// Allow recording from audio node only when pref is on.
1431
if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
1432
// Pretending that this constructor is not defined.
1433
NS_NAMED_LITERAL_STRING(argStr, "Argument 1 of MediaRecorder.constructor");
1434
NS_NAMED_LITERAL_STRING(typeStr, "MediaStream");
1435
aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>(argStr, typeStr);
1436
return nullptr;
1437
}
1438
1439
nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
1440
do_QueryInterface(aGlobal.GetAsSupports());
1441
if (!ownerWindow) {
1442
aRv.Throw(NS_ERROR_FAILURE);
1443
return nullptr;
1444
}
1445
1446
// aSrcOutput doesn't matter to destination node because it has no output.
1447
if (aSrcAudioNode.NumberOfOutputs() > 0 &&
1448
aSrcOutput >= aSrcAudioNode.NumberOfOutputs()) {
1449
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
1450
return nullptr;
1451
}
1452
1453
if (!IsTypeSupported(aInitDict.mMimeType)) {
1454
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
1455
return nullptr;
1456
}
1457
1458
RefPtr<MediaRecorder> object =
1459
new MediaRecorder(aSrcAudioNode, aSrcOutput, ownerWindow);
1460
object->SetOptions(aInitDict);
1461
return object.forget();
1462
}
1463
1464
void MediaRecorder::SetOptions(const MediaRecorderOptions& aInitDict) {
1465
SetMimeType(aInitDict.mMimeType);
1466
mAudioBitsPerSecond = aInitDict.mAudioBitsPerSecond.WasPassed()
1467
? aInitDict.mAudioBitsPerSecond.Value()
1468
: 0;
1469
mVideoBitsPerSecond = aInitDict.mVideoBitsPerSecond.WasPassed()
1470
? aInitDict.mVideoBitsPerSecond.Value()
1471
: 0;
1472
mBitsPerSecond = aInitDict.mBitsPerSecond.WasPassed()
1473
? aInitDict.mBitsPerSecond.Value()
1474
: 0;
1475
// We're not handling dynamic changes yet. Eventually we'll handle
1476
// setting audio, video and/or total -- and anything that isn't set,
1477
// we'll derive. Calculated versions require querying bitrates after
1478
// the encoder is Init()ed. This happens only after data is
1479
// available and thus requires dynamic changes.
1480
//
1481
// Until dynamic changes are supported, I prefer to be safe and err
1482
// slightly high
1483
if (aInitDict.mBitsPerSecond.WasPassed() &&
1484
!aInitDict.mVideoBitsPerSecond.WasPassed()) {
1485
mVideoBitsPerSecond = mBitsPerSecond;
1486
}
1487
}
1488
1489
static char const* const gWebMVideoEncoderCodecs[4] = {
1490
"opus",
1491
"vp8",
1492
"vp8.0",
1493
// no VP9 yet
1494
nullptr,
1495
};
1496
static char const* const gWebMAudioEncoderCodecs[4] = {
1497
"opus",
1498
nullptr,
1499
};
1500
static char const* const gOggAudioEncoderCodecs[2] = {
1501
"opus",
1502
// we could support vorbis here too, but don't
1503
nullptr,
1504
};
1505
1506
template <class String>
1507
static bool CodecListContains(char const* const* aCodecs,
1508
const String& aCodec) {
1509
for (int32_t i = 0; aCodecs[i]; ++i) {
1510
if (aCodec.EqualsASCII(aCodecs[i])) return true;
1511
}
1512
return false;
1513
}
1514
1515
/* static */
1516
bool MediaRecorder::IsTypeSupported(GlobalObject& aGlobal,
1517
const nsAString& aMIMEType) {
1518
return IsTypeSupported(aMIMEType);
1519
}
1520
1521
/* static */
1522
bool MediaRecorder::IsTypeSupported(const nsAString& aMIMEType) {
1523
char const* const* codeclist = nullptr;
1524
1525
if (aMIMEType.IsEmpty()) {
1526
return true;
1527
}
1528
1529
nsContentTypeParser parser(aMIMEType);
1530
nsAutoString mimeType;
1531
nsresult rv = parser.GetType(mimeType);
1532
if (NS_FAILED(rv)) {
1533
return false;
1534
}
1535
1536
// effectively a 'switch (mimeType) {'
1537
if (mimeType.EqualsLiteral(AUDIO_OGG)) {
1538
if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled()) {
1539
codeclist = gOggAudioEncoderCodecs;
1540
}
1541
}
1542
#ifdef MOZ_WEBM_ENCODER
1543
else if ((mimeType.EqualsLiteral(VIDEO_WEBM) ||
1544
mimeType.EqualsLiteral(AUDIO_WEBM)) &&
1545
MediaEncoder::IsWebMEncoderEnabled()) {
1546
if (mimeType.EqualsLiteral(AUDIO_WEBM)) {
1547
codeclist = gWebMAudioEncoderCodecs;
1548
} else {
1549
codeclist = gWebMVideoEncoderCodecs;
1550
}
1551
}
1552
#endif
1553
1554
// codecs don't matter if we don't support the container
1555
if (!codeclist) {
1556
return false;
1557
}
1558
// now filter on codecs, and if needed rescind support
1559
nsAutoString codecstring;
1560
rv = parser.GetParameter("codecs", codecstring);
1561
1562
nsTArray<nsString> codecs;
1563
if (!ParseCodecsString(codecstring, codecs)) {
1564
return false;
1565
}
1566
for (const nsString& codec : codecs) {
1567
if (!CodecListContains(codeclist, codec)) {
1568
// Totally unsupported codec
1569
return false;
1570
}
1571
}
1572
1573
return true;
1574
}
1575
1576
nsresult MediaRecorder::CreateAndDispatchBlobEvent(Blob* aBlob) {
1577
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1578
1579
BlobEventInit init;
1580
init.mBubbles = false;
1581
init.mCancelable = false;
1582
init.mData = aBlob;
1583
1584
RefPtr<BlobEvent> event =
1585
BlobEvent::Constructor(this, NS_LITERAL_STRING("dataavailable"), init);
1586
event->SetTrusted(true);
1587
ErrorResult rv;
1588
DispatchEvent(*event, rv);
1589
return rv.StealNSResult();
1590
}
1591
1592
void MediaRecorder::DispatchSimpleEvent(const nsAString& aStr) {
1593
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1594
nsresult rv = CheckCurrentGlobalCorrectness();
1595
if (NS_FAILED(rv)) {
1596
return;
1597
}
1598
1599
rv = DOMEventTargetHelper::DispatchTrustedEvent(aStr);
1600
if (NS_FAILED(rv)) {
1601
LOG(LogLevel::Error,
1602
("MediaRecorder.DispatchSimpleEvent: DispatchTrustedEvent failed %p",
1603
this));
1604
NS_ERROR("Failed to dispatch the event!!!");
1605
}
1606
}
1607
1608
void MediaRecorder::NotifyError(nsresult aRv) {
1609
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1610
nsresult rv = CheckCurrentGlobalCorrectness();
1611
if (NS_FAILED(rv)) {
1612
return;
1613
}
1614
MediaRecorderErrorEventInit init;
1615
init.mBubbles = false;
1616
init.mCancelable = false;
1617
// These DOMExceptions have been created earlier so they can contain stack
1618
// traces. We attach the appropriate one here to be fired. We should have
1619
// exceptions here, but defensively check.
1620
switch (aRv) {
1621
case NS_ERROR_DOM_SECURITY_ERR:
1622
if (!mSecurityDomException) {
1623
LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
1624
"mSecurityDomException was not initialized"));
1625
mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
1626
}
1627
init.mError = mSecurityDomException.forget();
1628
break;
1629
default:
1630
if (!mUnknownDomException) {
1631
LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
1632
"mUnknownDomException was not initialized"));
1633
mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
1634
}
1635
LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
1636
"mUnknownDomException being fired for aRv: %X",
1637
uint32_t(aRv)));
1638
init.mError = mUnknownDomException.forget();
1639
}
1640
1641
RefPtr<MediaRecorderErrorEvent> event = MediaRecorderErrorEvent::Constructor(
1642
this, NS_LITERAL_STRING("error"), init);
1643
event->SetTrusted(true);
1644
1645
IgnoredErrorResult res;
1646
DispatchEvent(*event, res);
1647
if (res.Failed()) {
1648
NS_ERROR("Failed to dispatch the error event!!!");
1649
}
1650
}
1651
1652
void MediaRecorder::RemoveSession(Session* aSession) {
1653
LOG(LogLevel::Debug, ("MediaRecorder.RemoveSession (%p)", aSession));
1654
mSessions.RemoveElement(aSession);
1655
}
1656
1657
void MediaRecorder::NotifyOwnerDocumentActivityChanged() {
1658
nsPIDOMWindowInner* window = GetOwner();
1659
NS_ENSURE_TRUE_VOID(window);
1660
Document* doc = window->GetExtantDoc();
1661
NS_ENSURE_TRUE_VOID(doc);
1662
1663
bool inFrameSwap = false;
1664
if (nsDocShell* docShell = static_cast<nsDocShell*>(doc->GetDocShell())) {
1665
inFrameSwap = docShell->InFrameSwap();
1666
}
1667
1668
LOG(LogLevel::Debug, ("MediaRecorder %p NotifyOwnerDocumentActivityChanged "
1669
"IsActive=%d, "
1670
"IsVisible=%d, "
1671
"InFrameSwap=%d",
1672
this, doc->IsActive(), doc->IsVisible(), inFrameSwap));
1673
if (!doc->IsActive() || !(inFrameSwap || doc->IsVisible())) {
1674
// Stop the session.
1675
ErrorResult result;
1676
Stop(result);
1677
result.SuppressException();
1678
}
1679
}
1680
1681
void MediaRecorder::ForceInactive() {
1682
LOG(LogLevel::Debug, ("MediaRecorder.ForceInactive %p", this));
1683
mState = RecordingState::Inactive;
1684
}
1685
1686
void MediaRecorder::StopForSessionDestruction() {
1687
LOG(LogLevel::Debug, ("MediaRecorder.StopForSessionDestruction %p", this));
1688
MediaRecorderReporter::RemoveMediaRecorder(this);
1689
// We do not perform a mState != RecordingState::Recording) check here as
1690
// we may already be inactive due to ForceInactive().
1691
mState = RecordingState::Inactive;
1692
MOZ_ASSERT(mSessions.Length() > 0);
1693
mSessions.LastElement()->Stop();
1694
// This is a coarse calculation and does not reflect the duration of the
1695
// final recording for reasons such as pauses. However it allows us an idea
1696
// of how long people are running their recorders for.
1697
TimeDuration timeDelta = TimeStamp::Now() - mStartTime;
1698
Telemetry::Accumulate(Telemetry::MEDIA_RECORDER_RECORDING_DURATION,
1699
timeDelta.ToSeconds());
1700
}
1701
1702
void MediaRecorder::InitializeDomExceptions() {
1703
mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
1704
mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
1705
}
1706
1707
RefPtr<MediaRecorder::SizeOfPromise> MediaRecorder::SizeOfExcludingThis(
1708
mozilla::MallocSizeOf aMallocSizeOf) {
1709
MOZ_ASSERT(NS_IsMainThread());
1710
1711
// The return type of a chained MozPromise cannot be changed, so we create a
1712
// holder for our desired return type and resolve that from All()->Then().
1713
auto holder = MakeRefPtr<Refcountable<MozPromiseHolder<SizeOfPromise>>>();
1714
RefPtr<SizeOfPromise> promise = holder->Ensure(__func__);
1715
1716
nsTArray<RefPtr<SizeOfPromise>> promises(mSessions.Length());
1717
for (const RefPtr<Session>& session : mSessions) {
1718
promises.AppendElement(session->SizeOfExcludingThis(aMallocSizeOf));
1719
}
1720
1721
SizeOfPromise::All(GetCurrentThreadSerialEventTarget(), promises)
1722
->Then(
1723
GetCurrentThreadSerialEventTarget(), __func__,
1724
[holder](const nsTArray<size_t>& sizes) {
1725
size_t total = 0;
1726
for (const size_t& size : sizes) {
1727
total += size;
1728
}
1729
holder->Resolve(total, __func__);
1730
},
1731
[]() { MOZ_CRASH("Unexpected reject"); });
1732
1733
return promise;
1734
}
1735
1736
StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
1737
1738
} // namespace dom
1739
} // namespace mozilla