Coverage Report

Created: 2018-09-25 14:53

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