Line data Source code
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 : #include "AudioNodeEngine.h"
9 : #include "AudioNodeStream.h"
10 : #include "DOMMediaStream.h"
11 : #include "EncodedBufferCache.h"
12 : #include "MediaDecoder.h"
13 : #include "MediaEncoder.h"
14 : #include "mozilla/StaticPtr.h"
15 : #include "mozilla/DOMEventTargetHelper.h"
16 : #include "mozilla/Preferences.h"
17 : #include "mozilla/dom/AudioStreamTrack.h"
18 : #include "mozilla/dom/BlobEvent.h"
19 : #include "mozilla/dom/File.h"
20 : #include "mozilla/dom/RecordErrorEvent.h"
21 : #include "mozilla/dom/VideoStreamTrack.h"
22 : #include "nsAutoPtr.h"
23 : #include "nsContentUtils.h"
24 : #include "nsError.h"
25 : #include "nsIDocument.h"
26 : #include "nsIPermissionManager.h"
27 : #include "nsIPrincipal.h"
28 : #include "nsIScriptError.h"
29 : #include "nsMimeTypes.h"
30 : #include "nsProxyRelease.h"
31 : #include "nsTArray.h"
32 : #include "GeckoProfiler.h"
33 : #include "nsContentTypeParser.h"
34 : #include "nsCharSeparatedTokenizer.h"
35 :
36 : #ifdef LOG
37 : #undef LOG
38 : #endif
39 :
40 : mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
41 : #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
42 :
43 : namespace mozilla {
44 :
45 : namespace dom {
46 :
47 : /**
48 : + * MediaRecorderReporter measures memory being used by the Media Recorder.
49 : + *
50 : + * It is a singleton reporter and the single class object lives as long as at
51 : + * least one Recorder is registered. In MediaRecorder, the reporter is unregistered
52 : + * when it is destroyed.
53 : + */
54 : class MediaRecorderReporter final : public nsIMemoryReporter
55 : {
56 : public:
57 : NS_DECL_THREADSAFE_ISUPPORTS
58 0 : MediaRecorderReporter() {};
59 : static MediaRecorderReporter* UniqueInstance();
60 : void InitMemoryReporter();
61 :
62 0 : static void AddMediaRecorder(MediaRecorder *aRecorder)
63 : {
64 0 : GetRecorders().AppendElement(aRecorder);
65 0 : }
66 :
67 0 : static void RemoveMediaRecorder(MediaRecorder *aRecorder)
68 : {
69 0 : RecordersArray& recorders = GetRecorders();
70 0 : recorders.RemoveElement(aRecorder);
71 0 : if (recorders.IsEmpty()) {
72 0 : sUniqueInstance = nullptr;
73 : }
74 0 : }
75 :
76 : NS_IMETHOD
77 0 : CollectReports(nsIHandleReportCallback* aHandleReport,
78 : nsISupports* aData, bool aAnonymize) override
79 : {
80 0 : int64_t amount = 0;
81 0 : RecordersArray& recorders = GetRecorders();
82 0 : for (size_t i = 0; i < recorders.Length(); ++i) {
83 0 : amount += recorders[i]->SizeOfExcludingThis(MallocSizeOf);
84 : }
85 :
86 0 : MOZ_COLLECT_REPORT(
87 : "explicit/media/recorder", KIND_HEAP, UNITS_BYTES, amount,
88 0 : "Memory used by media recorder.");
89 :
90 0 : return NS_OK;
91 : }
92 :
93 : private:
94 0 : MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
95 : virtual ~MediaRecorderReporter();
96 : static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
97 : typedef nsTArray<MediaRecorder*> RecordersArray;
98 0 : static RecordersArray& GetRecorders()
99 : {
100 0 : return UniqueInstance()->mRecorders;
101 : }
102 : RecordersArray mRecorders;
103 : };
104 0 : NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
105 :
106 : NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
107 :
108 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
109 : DOMEventTargetHelper)
110 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStream)
111 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
112 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
113 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
114 :
115 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
116 : DOMEventTargetHelper)
117 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStream)
118 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
119 0 : tmp->UnRegisterActivityObserver();
120 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
121 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
122 :
123 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder)
124 0 : NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
125 0 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
126 :
127 0 : NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
128 0 : NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
129 :
130 : /**
131 : * Session is an object to represent a single recording event.
132 : * In original design, all recording context is stored in MediaRecorder, which causes
133 : * a problem if someone calls MediaRecoder::Stop and MedaiRecorder::Start quickly.
134 : * To prevent blocking main thread, media encoding is executed in a second thread,
135 : * named as Read Thread. For the same reason, we do not wait Read Thread shutdown in
136 : * MediaRecorder::Stop. If someone call MediaRecoder::Start before Read Thread shutdown,
137 : * the same recording context in MediaRecoder might be access by two Reading Threads,
138 : * which cause a problem.
139 : * In the new design, we put recording context into Session object, including Read
140 : * Thread. Each Session has its own recording context and Read Thread, problem is been
141 : * resolved.
142 : *
143 : * Life cycle of a Session object.
144 : * 1) Initialization Stage (in main thread)
145 : * Setup media streams in MSG, and bind MediaEncoder with Source Stream when mStream is available.
146 : * Resource allocation, such as encoded data cache buffer and MediaEncoder.
147 : * Create read thread.
148 : * Automatically switch to Extract stage in the end of this stage.
149 : * 2) Extract Stage (in Read Thread)
150 : * Pull encoded A/V frames from MediaEncoder, dispatch to OnDataAvailable handler.
151 : * Unless a client calls Session::Stop, Session object keeps stay in this stage.
152 : * 3) Destroy Stage (in main thread)
153 : * Switch from Extract stage to Destroy stage by calling Session::Stop.
154 : * Release session resource and remove associated streams from MSG.
155 : *
156 : * Lifetime of MediaRecorder and Session objects.
157 : * 1) MediaRecorder creates a Session in MediaRecorder::Start function and holds
158 : * a reference to Session. Then the Session registers itself to
159 : * ShutdownObserver and also holds a reference to MediaRecorder.
160 : * Therefore, the reference dependency in gecko is:
161 : * ShutdownObserver -> Session <-> MediaRecorder, note that there is a cycle
162 : * reference between Session and MediaRecorder.
163 : * 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
164 : * _and_ all encoded media data been passed to OnDataAvailable handler.
165 : * 3) MediaRecorder::Stop is called by user or the document is going to
166 : * inactive or invisible.
167 : */
168 : class MediaRecorder::Session: public nsIObserver,
169 : public PrincipalChangeObserver<MediaStreamTrack>,
170 : public DOMMediaStream::TrackListener
171 : {
172 : NS_DECL_THREADSAFE_ISUPPORTS
173 :
174 : // Main thread task.
175 : // Create a blob event and send back to client.
176 0 : class PushBlobRunnable : public Runnable
177 : {
178 : public:
179 0 : explicit PushBlobRunnable(Session* aSession)
180 0 : : Runnable("dom::MediaRecorder::Session::PushBlobRunnable")
181 0 : , mSession(aSession)
182 0 : { }
183 :
184 0 : NS_IMETHOD Run() override
185 : {
186 0 : LOG(LogLevel::Debug, ("Session.PushBlobRunnable s=(%p)", mSession.get()));
187 0 : MOZ_ASSERT(NS_IsMainThread());
188 :
189 0 : RefPtr<MediaRecorder> recorder = mSession->mRecorder;
190 0 : if (!recorder) {
191 0 : return NS_OK;
192 : }
193 :
194 0 : nsresult rv = recorder->CreateAndDispatchBlobEvent(mSession->GetEncodedData());
195 0 : if (NS_FAILED(rv)) {
196 0 : recorder->NotifyError(rv);
197 : }
198 :
199 0 : return NS_OK;
200 : }
201 :
202 : private:
203 : RefPtr<Session> mSession;
204 : };
205 :
206 : // Notify encoder error, run in main thread task. (Bug 1095381)
207 0 : class EncoderErrorNotifierRunnable : public Runnable
208 : {
209 : public:
210 0 : explicit EncoderErrorNotifierRunnable(Session* aSession)
211 0 : : Runnable("dom::MediaRecorder::Session::EncoderErrorNotifierRunnable")
212 0 : , mSession(aSession)
213 0 : { }
214 :
215 0 : NS_IMETHOD Run() override
216 : {
217 0 : LOG(LogLevel::Debug, ("Session.ErrorNotifyRunnable s=(%p)", mSession.get()));
218 0 : MOZ_ASSERT(NS_IsMainThread());
219 :
220 0 : RefPtr<MediaRecorder> recorder = mSession->mRecorder;
221 0 : if (!recorder) {
222 0 : return NS_OK;
223 : }
224 :
225 0 : if (mSession->IsEncoderError()) {
226 0 : recorder->NotifyError(NS_ERROR_UNEXPECTED);
227 : }
228 0 : return NS_OK;
229 : }
230 :
231 : private:
232 : RefPtr<Session> mSession;
233 : };
234 :
235 : // Fire start event and set mimeType, run in main thread task.
236 0 : class DispatchStartEventRunnable : public Runnable
237 : {
238 : public:
239 0 : DispatchStartEventRunnable(Session* aSession, const nsAString& aEventName)
240 0 : : Runnable("dom::MediaRecorder::Session::DispatchStartEventRunnable")
241 : , mSession(aSession)
242 0 : , mEventName(aEventName)
243 0 : { }
244 :
245 0 : NS_IMETHOD Run() override
246 : {
247 0 : LOG(LogLevel::Debug, ("Session.DispatchStartEventRunnable s=(%p)", mSession.get()));
248 0 : MOZ_ASSERT(NS_IsMainThread());
249 :
250 0 : NS_ENSURE_TRUE(mSession->mRecorder, NS_OK);
251 0 : RefPtr<MediaRecorder> recorder = mSession->mRecorder;
252 :
253 0 : recorder->SetMimeType(mSession->mMimeType);
254 0 : recorder->DispatchSimpleEvent(mEventName);
255 :
256 0 : return NS_OK;
257 : }
258 :
259 : private:
260 : RefPtr<Session> mSession;
261 : nsString mEventName;
262 : };
263 :
264 : // Record thread task and it run in Media Encoder thread.
265 : // Fetch encoded Audio/Video data from MediaEncoder.
266 : class ExtractRunnable : public Runnable
267 : {
268 : public:
269 0 : explicit ExtractRunnable(Session* aSession)
270 0 : : Runnable("dom::MediaRecorder::Session::ExtractRunnable")
271 0 : , mSession(aSession)
272 : {
273 0 : }
274 :
275 0 : ~ExtractRunnable()
276 0 : {}
277 :
278 0 : NS_IMETHOD Run() override
279 : {
280 0 : MOZ_ASSERT(mSession->mReadThread->EventTarget()->IsOnCurrentThread());
281 :
282 0 : LOG(LogLevel::Debug, ("Session.ExtractRunnable shutdown = %d", mSession->mEncoder->IsShutdown()));
283 0 : if (!mSession->mEncoder->IsShutdown()) {
284 0 : mSession->Extract(false);
285 0 : if (NS_FAILED(NS_DispatchToCurrentThread(this))) {
286 0 : NS_WARNING("Failed to dispatch ExtractRunnable to encoder thread");
287 : }
288 : } else {
289 : // Flush out remaining encoded data.
290 0 : mSession->Extract(true);
291 0 : if (NS_FAILED(NS_DispatchToMainThread(
292 : new DestroyRunnable(mSession.forget())))) {
293 0 : MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
294 : }
295 : }
296 0 : return NS_OK;
297 : }
298 :
299 : private:
300 : RefPtr<Session> mSession;
301 : };
302 :
303 : // For Ensure recorder has tracks to record.
304 0 : class TracksAvailableCallback : public OnTracksAvailableCallback
305 : {
306 : public:
307 0 : explicit TracksAvailableCallback(Session *aSession, TrackRate aTrackRate)
308 0 : : mSession(aSession)
309 0 : , mTrackRate(aTrackRate) {}
310 :
311 0 : virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
312 : {
313 0 : if (mSession->mStopIssued) {
314 0 : return;
315 : }
316 :
317 0 : MOZ_RELEASE_ASSERT(aStream);
318 0 : mSession->MediaStreamReady(*aStream);
319 :
320 0 : uint8_t trackTypes = 0;
321 0 : nsTArray<RefPtr<mozilla::dom::AudioStreamTrack>> audioTracks;
322 0 : aStream->GetAudioTracks(audioTracks);
323 0 : if (!audioTracks.IsEmpty()) {
324 0 : trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
325 0 : mSession->ConnectMediaStreamTrack(*audioTracks[0]);
326 : }
327 :
328 0 : nsTArray<RefPtr<mozilla::dom::VideoStreamTrack>> videoTracks;
329 0 : aStream->GetVideoTracks(videoTracks);
330 0 : if (!videoTracks.IsEmpty()) {
331 0 : trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
332 0 : mSession->ConnectMediaStreamTrack(*videoTracks[0]);
333 : }
334 :
335 0 : if (audioTracks.Length() > 1 ||
336 0 : videoTracks.Length() > 1) {
337 : // When MediaRecorder supports multiple tracks, we should set up a single
338 : // MediaInputPort from the input stream, and let main thread check
339 : // track principals async later.
340 0 : nsPIDOMWindowInner* window = mSession->mRecorder->GetParentObject();
341 0 : nsIDocument* document = window ? window->GetExtantDoc() : nullptr;
342 0 : nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
343 0 : NS_LITERAL_CSTRING("Media"),
344 : document,
345 : nsContentUtils::eDOM_PROPERTIES,
346 0 : "MediaRecorderMultiTracksNotSupported");
347 0 : mSession->DoSessionEndTask(NS_ERROR_ABORT);
348 0 : return;
349 : }
350 :
351 0 : NS_ASSERTION(trackTypes != 0, "TracksAvailableCallback without any tracks available");
352 :
353 : // Check that we may access the tracks' content.
354 0 : if (!mSession->MediaStreamTracksPrincipalSubsumes()) {
355 0 : LOG(LogLevel::Warning, ("Session.NotifyTracksAvailable MediaStreamTracks principal check failed"));
356 0 : mSession->DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
357 0 : return;
358 : }
359 :
360 0 : LOG(LogLevel::Debug, ("Session.NotifyTracksAvailable track type = (%d)", trackTypes));
361 0 : mSession->InitEncoder(trackTypes, mTrackRate);
362 : }
363 : private:
364 : RefPtr<Session> mSession;
365 : TrackRate mTrackRate;
366 : };
367 : // Main thread task.
368 : // To delete RecordingSession object.
369 0 : class DestroyRunnable : public Runnable
370 : {
371 : public:
372 0 : explicit DestroyRunnable(Session* aSession)
373 0 : : Runnable("dom::MediaRecorder::Session::DestroyRunnable")
374 0 : , mSession(aSession)
375 : {
376 0 : }
377 :
378 0 : explicit DestroyRunnable(already_AddRefed<Session> aSession)
379 0 : : Runnable("dom::MediaRecorder::Session::DestroyRunnable")
380 0 : , mSession(aSession)
381 : {
382 0 : }
383 :
384 0 : NS_IMETHOD Run() override
385 : {
386 0 : LOG(LogLevel::Debug, ("Session.DestroyRunnable session refcnt = (%d) stopIssued %d s=(%p)",
387 : (int)mSession->mRefCnt, mSession->mStopIssued, mSession.get()));
388 0 : MOZ_ASSERT(NS_IsMainThread() && mSession);
389 0 : RefPtr<MediaRecorder> recorder = mSession->mRecorder;
390 0 : if (!recorder) {
391 0 : return NS_OK;
392 : }
393 : // SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
394 : // Read Thread will be terminate soon.
395 : // We need to switch MediaRecorder to "Stop" state first to make sure
396 : // MediaRecorder is not associated with this Session anymore, then, it's
397 : // safe to delete this Session.
398 : // Also avoid to run if this session already call stop before
399 0 : if (!mSession->mStopIssued) {
400 0 : ErrorResult result;
401 0 : mSession->mStopIssued = true;
402 0 : recorder->Stop(result);
403 0 : if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(mSession.forget())))) {
404 0 : MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
405 : }
406 0 : return NS_OK;
407 : }
408 :
409 : // Dispatch stop event and clear MIME type.
410 0 : mSession->mMimeType = NS_LITERAL_STRING("");
411 0 : recorder->SetMimeType(mSession->mMimeType);
412 0 : recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
413 0 : mSession->BreakCycle();
414 0 : return NS_OK;
415 : }
416 :
417 : private:
418 : // Call mSession::Release automatically while DestroyRunnable be destroy.
419 : RefPtr<Session> mSession;
420 : };
421 :
422 : friend class EncoderErrorNotifierRunnable;
423 : friend class PushBlobRunnable;
424 : friend class ExtractRunnable;
425 : friend class DestroyRunnable;
426 : friend class TracksAvailableCallback;
427 :
428 : public:
429 0 : Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
430 0 : : mRecorder(aRecorder)
431 : , mTimeSlice(aTimeSlice)
432 : , mStopIssued(false)
433 : , mIsStartEventFired(false)
434 : , mNeedSessionEndTask(true)
435 0 : , mSelectedVideoTrackID(TRACK_NONE)
436 : {
437 0 : MOZ_ASSERT(NS_IsMainThread());
438 :
439 : uint32_t maxMem = Preferences::GetUint("media.recorder.max_memory",
440 0 : MAX_ALLOW_MEMORY_BUFFER);
441 0 : mEncodedBufferCache = new EncodedBufferCache(maxMem);
442 0 : mLastBlobTimeStamp = TimeStamp::Now();
443 0 : }
444 :
445 0 : void PrincipalChanged(MediaStreamTrack* aTrack) override
446 : {
447 0 : NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
448 : "Principal changed for unrecorded track");
449 0 : if (!MediaStreamTracksPrincipalSubsumes()) {
450 0 : DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
451 : }
452 0 : }
453 :
454 0 : void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override
455 : {
456 0 : LOG(LogLevel::Warning, ("Session.NotifyTrackAdded %p Raising error due to track set change", this));
457 0 : DoSessionEndTask(NS_ERROR_ABORT);
458 0 : }
459 :
460 0 : void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override
461 : {
462 : // Handle possible early removal of direct video listener
463 0 : if (mEncoder) {
464 0 : RefPtr<VideoStreamTrack> videoTrack = aTrack->AsVideoStreamTrack();
465 0 : if (videoTrack) {
466 0 : videoTrack->RemoveDirectListener(mEncoder->GetVideoSink());
467 : }
468 : }
469 :
470 0 : RefPtr<MediaInputPort> foundInputPort;
471 0 : for (RefPtr<MediaInputPort> inputPort : mInputPorts) {
472 0 : if (aTrack->IsForwardedThrough(inputPort)) {
473 0 : foundInputPort = inputPort;
474 0 : break;
475 : }
476 : }
477 :
478 0 : if (foundInputPort) {
479 : // A recorded track was removed or ended. End it in the recording.
480 : // Don't raise an error.
481 0 : foundInputPort->Destroy();
482 0 : DebugOnly<bool> removed = mInputPorts.RemoveElement(foundInputPort);
483 0 : MOZ_ASSERT(removed);
484 0 : return;
485 : }
486 :
487 0 : LOG(LogLevel::Warning, ("Session.NotifyTrackRemoved %p Raising error due to track set change", this));
488 0 : DoSessionEndTask(NS_ERROR_ABORT);
489 : }
490 :
491 0 : void Start()
492 : {
493 0 : LOG(LogLevel::Debug, ("Session.Start %p", this));
494 0 : MOZ_ASSERT(NS_IsMainThread());
495 :
496 : // Create a Track Union Stream
497 0 : MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
498 0 : TrackRate trackRate = gm->GraphRate();
499 0 : mTrackUnionStream = gm->CreateTrackUnionStream();
500 0 : MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
501 :
502 0 : mTrackUnionStream->SetAutofinish(true);
503 :
504 0 : DOMMediaStream* domStream = mRecorder->Stream();
505 0 : if (domStream) {
506 : // Get the available tracks from the DOMMediaStream.
507 : // The callback will report back tracks that we have to connect to
508 : // mTrackUnionStream and listen to principal changes on.
509 0 : TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(this, trackRate);
510 0 : domStream->OnTracksAvailable(tracksAvailableCallback);
511 : } else {
512 : // Check that we may access the audio node's content.
513 0 : if (!AudioNodePrincipalSubsumes()) {
514 0 : LOG(LogLevel::Warning, ("Session.Start AudioNode principal check failed"));
515 0 : DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
516 0 : return;
517 : }
518 : // Bind this Track Union Stream with Source Media.
519 : RefPtr<MediaInputPort> inputPort =
520 0 : mTrackUnionStream->AllocateInputPort(mRecorder->GetSourceMediaStream());
521 0 : mInputPorts.AppendElement(inputPort.forget());
522 0 : MOZ_ASSERT(mInputPorts[mInputPorts.Length()-1]);
523 :
524 : // Web Audio node has only audio.
525 0 : InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate);
526 : }
527 : }
528 :
529 0 : void Stop()
530 : {
531 0 : LOG(LogLevel::Debug, ("Session.Stop %p", this));
532 0 : MOZ_ASSERT(NS_IsMainThread());
533 0 : mStopIssued = true;
534 0 : CleanupStreams();
535 0 : if (mNeedSessionEndTask) {
536 0 : LOG(LogLevel::Debug, ("Session.Stop mNeedSessionEndTask %p", this));
537 : // End the Session directly if there is no ExtractRunnable.
538 0 : DoSessionEndTask(NS_OK);
539 : }
540 : // If we don't do this, the Session will be purged only when the navigator exit
541 : // by the ShutdownObserver and the memory and number of threads will quickly
542 : // grows with each couple stop/start.
543 0 : nsContentUtils::UnregisterShutdownObserver(this);
544 0 : }
545 :
546 0 : nsresult Pause()
547 : {
548 0 : LOG(LogLevel::Debug, ("Session.Pause"));
549 0 : MOZ_ASSERT(NS_IsMainThread());
550 :
551 0 : NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
552 0 : mTrackUnionStream->Suspend();
553 0 : if (mEncoder) {
554 0 : mEncoder->Suspend();
555 : }
556 :
557 0 : return NS_OK;
558 : }
559 :
560 0 : nsresult Resume()
561 : {
562 0 : LOG(LogLevel::Debug, ("Session.Resume"));
563 0 : MOZ_ASSERT(NS_IsMainThread());
564 :
565 0 : NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
566 0 : if (mEncoder) {
567 0 : mEncoder->Resume();
568 : }
569 0 : mTrackUnionStream->Resume();
570 :
571 0 : return NS_OK;
572 : }
573 :
574 0 : nsresult RequestData()
575 : {
576 0 : LOG(LogLevel::Debug, ("Session.RequestData"));
577 0 : MOZ_ASSERT(NS_IsMainThread());
578 :
579 0 : if (NS_FAILED(NS_DispatchToMainThread(new EncoderErrorNotifierRunnable(this))) ||
580 0 : NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
581 0 : MOZ_ASSERT(false, "RequestData NS_DispatchToMainThread failed");
582 : return NS_ERROR_FAILURE;
583 : }
584 :
585 0 : return NS_OK;
586 : }
587 :
588 0 : already_AddRefed<nsIDOMBlob> GetEncodedData()
589 : {
590 0 : MOZ_ASSERT(NS_IsMainThread());
591 0 : return mEncodedBufferCache->ExtractBlob(mRecorder->GetParentObject(),
592 0 : mMimeType);
593 : }
594 :
595 0 : bool IsEncoderError()
596 : {
597 0 : if (mEncoder && mEncoder->HasError()) {
598 0 : return true;
599 : }
600 0 : return false;
601 : }
602 :
603 : size_t
604 0 : SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
605 : {
606 0 : return (mEncoder ? mEncoder->SizeOfExcludingThis(aMallocSizeOf) : 0);
607 : }
608 :
609 :
610 : private:
611 : // Only DestroyRunnable is allowed to delete Session object.
612 0 : virtual ~Session()
613 0 : {
614 0 : LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
615 0 : CleanupStreams();
616 0 : if (mReadThread) {
617 0 : mReadThread->Shutdown();
618 0 : mReadThread = nullptr;
619 : // Inside the if() so that if we delete after xpcom-shutdown's Observe(), we
620 : // won't try to remove it after the observer service is shut down.
621 : // Unregistering for safety in case Stop() was never called
622 0 : nsContentUtils::UnregisterShutdownObserver(this);
623 : }
624 0 : }
625 : // Pull encoded media data from MediaEncoder and put into EncodedBufferCache.
626 : // Destroy this session object in the end of this function.
627 : // If the bool aForceFlush is true, we will force to dispatch a
628 : // PushBlobRunnable to main thread.
629 0 : void Extract(bool aForceFlush)
630 : {
631 0 : MOZ_ASSERT(mReadThread->EventTarget()->IsOnCurrentThread());
632 0 : LOG(LogLevel::Debug, ("Session.Extract %p", this));
633 :
634 0 : AUTO_PROFILER_LABEL("MediaRecorder::Session::Extract", OTHER);
635 :
636 : // Pull encoded media data from MediaEncoder
637 0 : nsTArray<nsTArray<uint8_t> > encodedBuf;
638 0 : mEncoder->GetEncodedData(&encodedBuf, mMimeType);
639 :
640 : // Append pulled data into cache buffer.
641 0 : for (uint32_t i = 0; i < encodedBuf.Length(); i++) {
642 0 : if (!encodedBuf[i].IsEmpty()) {
643 0 : mEncodedBufferCache->AppendBuffer(encodedBuf[i]);
644 : // Fire the start event when encoded data is available.
645 0 : if (!mIsStartEventFired) {
646 0 : NS_DispatchToMainThread(
647 0 : new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
648 0 : mIsStartEventFired = true;
649 : }
650 : }
651 : }
652 :
653 : // Whether push encoded data back to onDataAvailable automatically or we
654 : // need a flush.
655 0 : bool pushBlob = false;
656 0 : if ((mTimeSlice > 0) &&
657 0 : ((TimeStamp::Now()-mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice)) {
658 0 : pushBlob = true;
659 : }
660 0 : if (pushBlob || aForceFlush) {
661 : // Fire the start event before the blob.
662 0 : if (!mIsStartEventFired) {
663 0 : NS_DispatchToMainThread(
664 0 : new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
665 0 : mIsStartEventFired = true;
666 : }
667 0 : if (NS_FAILED(NS_DispatchToMainThread(new EncoderErrorNotifierRunnable(this)))) {
668 0 : MOZ_ASSERT(false, "NS_DispatchToMainThread EncoderErrorNotifierRunnable failed");
669 : }
670 0 : if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
671 0 : MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
672 : } else {
673 0 : mLastBlobTimeStamp = TimeStamp::Now();
674 : }
675 : }
676 0 : }
677 :
678 0 : void MediaStreamReady(DOMMediaStream& aStream) {
679 0 : mMediaStream = &aStream;
680 0 : aStream.RegisterTrackListener(this);
681 0 : }
682 :
683 0 : void ConnectMediaStreamTrack(MediaStreamTrack& aTrack)
684 : {
685 0 : mMediaStreamTracks.AppendElement(&aTrack);
686 0 : aTrack.AddPrincipalChangeObserver(this);
687 : RefPtr<MediaInputPort> inputPort =
688 0 : aTrack.ForwardTrackContentsTo(mTrackUnionStream);
689 0 : MOZ_ASSERT(inputPort);
690 0 : mInputPorts.AppendElement(inputPort.forget());
691 0 : MOZ_ASSERT(mInputPorts[mInputPorts.Length()-1]);
692 0 : }
693 :
694 0 : bool PrincipalSubsumes(nsIPrincipal* aPrincipal)
695 : {
696 0 : if (!mRecorder->GetOwner())
697 0 : return false;
698 0 : nsCOMPtr<nsIDocument> doc = mRecorder->GetOwner()->GetExtantDoc();
699 0 : if (!doc) {
700 0 : return false;
701 : }
702 0 : if (!aPrincipal) {
703 0 : return false;
704 : }
705 : bool subsumes;
706 0 : if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
707 0 : return false;
708 : }
709 0 : return subsumes;
710 : }
711 :
712 0 : bool MediaStreamTracksPrincipalSubsumes()
713 : {
714 0 : MOZ_ASSERT(mRecorder->mDOMStream);
715 0 : nsCOMPtr<nsIPrincipal> principal = nullptr;
716 0 : for (RefPtr<MediaStreamTrack>& track : mMediaStreamTracks) {
717 0 : nsContentUtils::CombineResourcePrincipals(&principal, track->GetPrincipal());
718 : }
719 0 : return PrincipalSubsumes(principal);
720 : }
721 :
722 0 : bool AudioNodePrincipalSubsumes()
723 : {
724 0 : MOZ_ASSERT(mRecorder->mAudioNode != nullptr);
725 0 : nsIDocument* doc = mRecorder->mAudioNode->GetOwner()
726 0 : ? mRecorder->mAudioNode->GetOwner()->GetExtantDoc()
727 0 : : nullptr;
728 0 : nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
729 0 : return PrincipalSubsumes(principal);
730 : }
731 :
732 0 : void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate)
733 : {
734 0 : LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
735 0 : MOZ_ASSERT(NS_IsMainThread());
736 :
737 0 : if (!mRecorder) {
738 0 : LOG(LogLevel::Debug, ("Session.InitEncoder failure, mRecorder is null %p", this));
739 0 : return;
740 : }
741 : // Allocate encoder and bind with union stream.
742 : // At this stage, the API doesn't allow UA to choose the output mimeType format.
743 :
744 0 : mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""),
745 : mRecorder->GetAudioBitrate(),
746 : mRecorder->GetVideoBitrate(),
747 : mRecorder->GetBitrate(),
748 0 : aTrackTypes, aTrackRate);
749 :
750 0 : if (!mEncoder) {
751 0 : LOG(LogLevel::Debug, ("Session.InitEncoder !mEncoder %p", this));
752 0 : DoSessionEndTask(NS_ERROR_ABORT);
753 0 : return;
754 : }
755 :
756 : // Media stream is ready but UA issues a stop method follow by start method.
757 : // The Session::stop would clean the mTrackUnionStream. If the AfterTracksAdded
758 : // comes after stop command, this function would crash.
759 0 : if (!mTrackUnionStream) {
760 0 : LOG(LogLevel::Debug, ("Session.InitEncoder !mTrackUnionStream %p", this));
761 0 : DoSessionEndTask(NS_OK);
762 0 : return;
763 : }
764 0 : mTrackUnionStream->AddListener(mEncoder.get());
765 :
766 0 : nsTArray<RefPtr<mozilla::dom::VideoStreamTrack>> videoTracks;
767 0 : DOMMediaStream* domStream = mRecorder->Stream();
768 0 : if (domStream) {
769 0 : domStream->GetVideoTracks(videoTracks);
770 0 : if (!videoTracks.IsEmpty()) {
771 : // Right now, the MediaRecorder hasn't dealt with multiple video track
772 : // issues. So we just bind with the first video track. Bug 1276928 is
773 : // the following.
774 0 : videoTracks[0]->AddDirectListener(mEncoder->GetVideoSink());
775 : }
776 : }
777 :
778 : // Try to use direct listeners if possible
779 0 : if (domStream && domStream->GetInputStream()) {
780 0 : mInputStream = domStream->GetInputStream()->AsSourceStream();
781 0 : if (mInputStream) {
782 0 : mInputStream->AddDirectListener(mEncoder.get());
783 0 : mEncoder->SetDirectConnect(true);
784 : }
785 : }
786 :
787 : // Create a thread to read encode media data from MediaEncoder.
788 0 : if (!mReadThread) {
789 0 : nsresult rv = NS_NewNamedThread("Media_Encoder", getter_AddRefs(mReadThread));
790 0 : if (NS_FAILED(rv)) {
791 0 : LOG(LogLevel::Debug, ("Session.InitEncoder !mReadThread %p", this));
792 0 : DoSessionEndTask(rv);
793 0 : return;
794 : }
795 : }
796 :
797 : // In case source media stream does not notify track end, receive
798 : // shutdown notification and stop Read Thread.
799 0 : nsContentUtils::RegisterShutdownObserver(this);
800 :
801 0 : nsCOMPtr<nsIRunnable> event = new ExtractRunnable(this);
802 0 : if (NS_FAILED(mReadThread->EventTarget()->Dispatch(event.forget(), NS_DISPATCH_NORMAL))) {
803 0 : NS_WARNING("Failed to dispatch ExtractRunnable at beginning");
804 0 : LOG(LogLevel::Debug, ("Session.InitEncoder !ReadThread->Dispatch %p", this));
805 0 : DoSessionEndTask(NS_ERROR_ABORT);
806 : }
807 : // Set mNeedSessionEndTask to false because the
808 : // ExtractRunnable/DestroyRunnable will take the response to
809 : // end the session.
810 0 : mNeedSessionEndTask = false;
811 : }
812 : // application should get blob and onstop event
813 0 : void DoSessionEndTask(nsresult rv)
814 : {
815 0 : MOZ_ASSERT(NS_IsMainThread());
816 0 : CleanupStreams();
817 0 : NS_DispatchToMainThread(
818 0 : new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
819 :
820 0 : if (NS_FAILED(rv)) {
821 0 : NS_DispatchToMainThread(
822 0 : NewRunnableMethod<nsresult>("dom::MediaRecorder::NotifyError",
823 : mRecorder,
824 : &MediaRecorder::NotifyError,
825 0 : rv));
826 : }
827 0 : if (NS_FAILED(NS_DispatchToMainThread(new EncoderErrorNotifierRunnable(this)))) {
828 0 : MOZ_ASSERT(false, "NS_DispatchToMainThread EncoderErrorNotifierRunnable failed");
829 : }
830 0 : if (rv != NS_ERROR_DOM_SECURITY_ERR) {
831 : // Don't push a blob if there was a security error.
832 0 : if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
833 0 : MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
834 : }
835 : }
836 0 : if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(this)))) {
837 0 : MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
838 : }
839 0 : mNeedSessionEndTask = false;
840 0 : }
841 0 : void CleanupStreams()
842 : {
843 0 : if (mInputStream) {
844 0 : if (mEncoder) {
845 0 : mInputStream->RemoveDirectListener(mEncoder.get());
846 : }
847 0 : mInputStream = nullptr;
848 : }
849 :
850 0 : if (mTrackUnionStream) {
851 0 : if (mEncoder) {
852 0 : nsTArray<RefPtr<mozilla::dom::VideoStreamTrack>> videoTracks;
853 0 : DOMMediaStream* domStream = mRecorder->Stream();
854 0 : if (domStream) {
855 0 : domStream->GetVideoTracks(videoTracks);
856 0 : if (!videoTracks.IsEmpty()) {
857 0 : videoTracks[0]->RemoveDirectListener(mEncoder->GetVideoSink());
858 : }
859 : }
860 : }
861 :
862 : // Sometimes the MediaEncoder might be initialized fail and go to
863 : // |CleanupStreams|. So the mEncoder might be a nullptr in this case.
864 0 : if (mEncoder && mSelectedVideoTrackID != TRACK_NONE) {
865 0 : mTrackUnionStream->RemoveVideoOutput(mEncoder->GetVideoSink(), mSelectedVideoTrackID);
866 : }
867 0 : if (mEncoder) {
868 0 : mTrackUnionStream->RemoveListener(mEncoder.get());
869 : }
870 0 : mTrackUnionStream->Destroy();
871 0 : mTrackUnionStream = nullptr;
872 : }
873 :
874 0 : for (RefPtr<MediaInputPort>& inputPort : mInputPorts) {
875 0 : MOZ_ASSERT(inputPort);
876 0 : inputPort->Destroy();
877 : }
878 0 : mInputPorts.Clear();
879 :
880 0 : if (mMediaStream) {
881 0 : mMediaStream->UnregisterTrackListener(this);
882 0 : mMediaStream = nullptr;
883 : }
884 :
885 0 : for (RefPtr<MediaStreamTrack>& track : mMediaStreamTracks) {
886 0 : track->RemovePrincipalChangeObserver(this);
887 : }
888 0 : mMediaStreamTracks.Clear();
889 0 : }
890 :
891 0 : NS_IMETHOD Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
892 : {
893 0 : MOZ_ASSERT(NS_IsMainThread());
894 0 : LOG(LogLevel::Debug, ("Session.Observe XPCOM_SHUTDOWN %p", this));
895 0 : if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
896 : // Force stop Session to terminate Read Thread.
897 0 : mEncoder->Cancel();
898 0 : if (mReadThread) {
899 0 : mReadThread->Shutdown();
900 0 : mReadThread = nullptr;
901 : }
902 0 : nsContentUtils::UnregisterShutdownObserver(this);
903 0 : BreakCycle();
904 0 : Stop();
905 : }
906 :
907 0 : return NS_OK;
908 : }
909 :
910 : // Break the cycle reference between Session and MediaRecorder.
911 0 : void BreakCycle()
912 : {
913 0 : MOZ_ASSERT(NS_IsMainThread());
914 0 : if (mRecorder) {
915 0 : mRecorder->RemoveSession(this);
916 0 : mRecorder = nullptr;
917 : }
918 0 : }
919 :
920 : private:
921 : // Hold reference to MediaRecoder that ensure MediaRecorder is alive
922 : // if there is an active session. Access ONLY on main thread.
923 : RefPtr<MediaRecorder> mRecorder;
924 :
925 : // Receive track data from source and dispatch to Encoder.
926 : // Pause/ Resume controller.
927 : RefPtr<ProcessedMediaStream> mTrackUnionStream;
928 : RefPtr<SourceMediaStream> mInputStream;
929 : nsTArray<RefPtr<MediaInputPort>> mInputPorts;
930 :
931 : // Stream currently recorded.
932 : RefPtr<DOMMediaStream> mMediaStream;
933 :
934 : // Tracks currently recorded. This should be a subset of mMediaStream's track
935 : // set.
936 : nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
937 :
938 : // Runnable thread for read data from MediaEncode.
939 : nsCOMPtr<nsIThread> mReadThread;
940 : // MediaEncoder pipeline.
941 : RefPtr<MediaEncoder> mEncoder;
942 : // A buffer to cache encoded meda data.
943 : nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
944 : // Current session mimeType
945 : nsString mMimeType;
946 : // Timestamp of the last fired dataavailable event.
947 : TimeStamp mLastBlobTimeStamp;
948 : // The interval of passing encoded data from EncodedBufferCache to onDataAvailable
949 : // handler. "mTimeSlice < 0" means Session object does not push encoded data to
950 : // onDataAvailable, instead, it passive wait the client side pull encoded data
951 : // by calling requestData API.
952 : const int32_t mTimeSlice;
953 : // Indicate this session's stop has been called.
954 : bool mStopIssued;
955 : // Indicate the session had fire start event. Encoding thread only.
956 : bool mIsStartEventFired;
957 : // False if the InitEncoder called successfully, ensure the
958 : // ExtractRunnable/DestroyRunnable will end the session.
959 : // Main thread only.
960 : bool mNeedSessionEndTask;
961 : TrackID mSelectedVideoTrackID;
962 : };
963 :
964 0 : NS_IMPL_ISUPPORTS(MediaRecorder::Session, nsIObserver)
965 :
966 0 : MediaRecorder::~MediaRecorder()
967 : {
968 0 : if (mPipeStream != nullptr) {
969 0 : mInputPort->Destroy();
970 0 : mPipeStream->Destroy();
971 : }
972 0 : LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
973 0 : UnRegisterActivityObserver();
974 0 : }
975 :
976 0 : MediaRecorder::MediaRecorder(DOMMediaStream& aSourceMediaStream,
977 0 : nsPIDOMWindowInner* aOwnerWindow)
978 : : DOMEventTargetHelper(aOwnerWindow)
979 0 : , mState(RecordingState::Inactive)
980 : {
981 0 : MOZ_ASSERT(aOwnerWindow);
982 0 : MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
983 0 : mDOMStream = &aSourceMediaStream;
984 :
985 0 : RegisterActivityObserver();
986 0 : }
987 :
988 0 : MediaRecorder::MediaRecorder(AudioNode& aSrcAudioNode,
989 : uint32_t aSrcOutput,
990 0 : nsPIDOMWindowInner* aOwnerWindow)
991 : : DOMEventTargetHelper(aOwnerWindow)
992 0 : , mState(RecordingState::Inactive)
993 : {
994 0 : MOZ_ASSERT(aOwnerWindow);
995 0 : MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
996 :
997 : // Only AudioNodeStream of kind EXTERNAL_STREAM stores output audio data in
998 : // the track (see AudioNodeStream::AdvanceOutputSegment()). That means track
999 : // union stream in recorder session won't be able to copy data from the
1000 : // stream of non-destination node. Create a pipe stream in this case.
1001 0 : if (aSrcAudioNode.NumberOfOutputs() > 0) {
1002 0 : AudioContext* ctx = aSrcAudioNode.Context();
1003 0 : AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
1004 : AudioNodeStream::Flags flags =
1005 : AudioNodeStream::EXTERNAL_OUTPUT |
1006 0 : AudioNodeStream::NEED_MAIN_THREAD_FINISHED;
1007 0 : mPipeStream = AudioNodeStream::Create(ctx, engine, flags, ctx->Graph());
1008 0 : AudioNodeStream* ns = aSrcAudioNode.GetStream();
1009 0 : if (ns) {
1010 : mInputPort =
1011 0 : mPipeStream->AllocateInputPort(aSrcAudioNode.GetStream(),
1012 : TRACK_ANY, TRACK_ANY,
1013 0 : 0, aSrcOutput);
1014 : }
1015 : }
1016 0 : mAudioNode = &aSrcAudioNode;
1017 :
1018 0 : RegisterActivityObserver();
1019 0 : }
1020 :
1021 : void
1022 0 : MediaRecorder::RegisterActivityObserver()
1023 : {
1024 0 : if (nsPIDOMWindowInner* window = GetOwner()) {
1025 0 : mDocument = window->GetExtantDoc();
1026 0 : if (mDocument) {
1027 0 : mDocument->RegisterActivityObserver(
1028 0 : NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1029 : }
1030 : }
1031 0 : }
1032 :
1033 : void
1034 0 : MediaRecorder::UnRegisterActivityObserver()
1035 : {
1036 0 : if (mDocument) {
1037 0 : mDocument->UnregisterActivityObserver(
1038 0 : NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1039 : }
1040 0 : }
1041 :
1042 : void
1043 0 : MediaRecorder::SetMimeType(const nsString &aMimeType)
1044 : {
1045 0 : mMimeType = aMimeType;
1046 0 : }
1047 :
1048 : void
1049 0 : MediaRecorder::GetMimeType(nsString &aMimeType)
1050 : {
1051 0 : aMimeType = mMimeType;
1052 0 : }
1053 :
1054 : void
1055 0 : MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
1056 : {
1057 0 : LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
1058 0 : if (mState != RecordingState::Inactive) {
1059 0 : aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1060 0 : return;
1061 : }
1062 :
1063 0 : if (GetSourceMediaStream()->IsFinished() || GetSourceMediaStream()->IsDestroyed()) {
1064 0 : aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1065 0 : return;
1066 : }
1067 :
1068 0 : nsTArray<RefPtr<MediaStreamTrack>> tracks;
1069 0 : if (mDOMStream) {
1070 0 : mDOMStream->GetTracks(tracks);
1071 : }
1072 0 : if (!tracks.IsEmpty()) {
1073 : // If there are tracks already available that we're not allowed
1074 : // to record, we should throw a security error.
1075 0 : bool subsumes = false;
1076 : nsPIDOMWindowInner* window;
1077 : nsIDocument* doc;
1078 0 : if (!(window = GetOwner()) ||
1079 0 : !(doc = window->GetExtantDoc()) ||
1080 0 : NS_FAILED(doc->NodePrincipal()->Subsumes(
1081 0 : mDOMStream->GetPrincipal(), &subsumes)) ||
1082 0 : !subsumes) {
1083 0 : aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
1084 0 : return;
1085 : }
1086 : }
1087 :
1088 0 : int32_t timeSlice = 0;
1089 0 : if (aTimeSlice.WasPassed()) {
1090 0 : if (aTimeSlice.Value() < 0) {
1091 0 : aResult.Throw(NS_ERROR_INVALID_ARG);
1092 0 : return;
1093 : }
1094 :
1095 0 : timeSlice = aTimeSlice.Value();
1096 : }
1097 0 : MediaRecorderReporter::AddMediaRecorder(this);
1098 0 : mState = RecordingState::Recording;
1099 : // Start a session.
1100 0 : mSessions.AppendElement();
1101 0 : mSessions.LastElement() = new Session(this, timeSlice);
1102 0 : mSessions.LastElement()->Start();
1103 : }
1104 :
1105 : void
1106 0 : MediaRecorder::Stop(ErrorResult& aResult)
1107 : {
1108 0 : LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
1109 0 : MediaRecorderReporter::RemoveMediaRecorder(this);
1110 0 : if (mState == RecordingState::Inactive) {
1111 0 : aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1112 0 : return;
1113 : }
1114 0 : mState = RecordingState::Inactive;
1115 0 : MOZ_ASSERT(mSessions.Length() > 0);
1116 0 : mSessions.LastElement()->Stop();
1117 : }
1118 :
1119 : void
1120 0 : MediaRecorder::Pause(ErrorResult& aResult)
1121 : {
1122 0 : LOG(LogLevel::Debug, ("MediaRecorder.Pause"));
1123 0 : if (mState != RecordingState::Recording) {
1124 0 : aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1125 0 : return;
1126 : }
1127 :
1128 0 : MOZ_ASSERT(mSessions.Length() > 0);
1129 0 : nsresult rv = mSessions.LastElement()->Pause();
1130 0 : if (NS_FAILED(rv)) {
1131 0 : NotifyError(rv);
1132 0 : return;
1133 : }
1134 0 : mState = RecordingState::Paused;
1135 : }
1136 :
1137 : void
1138 0 : MediaRecorder::Resume(ErrorResult& aResult)
1139 : {
1140 0 : LOG(LogLevel::Debug, ("MediaRecorder.Resume"));
1141 0 : if (mState != RecordingState::Paused) {
1142 0 : aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1143 0 : return;
1144 : }
1145 :
1146 0 : MOZ_ASSERT(mSessions.Length() > 0);
1147 0 : nsresult rv = mSessions.LastElement()->Resume();
1148 0 : if (NS_FAILED(rv)) {
1149 0 : NotifyError(rv);
1150 0 : return;
1151 : }
1152 0 : mState = RecordingState::Recording;
1153 : }
1154 :
1155 : void
1156 0 : MediaRecorder::RequestData(ErrorResult& aResult)
1157 : {
1158 0 : if (mState == RecordingState::Inactive) {
1159 0 : aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1160 0 : return;
1161 : }
1162 0 : MOZ_ASSERT(mSessions.Length() > 0);
1163 0 : nsresult rv = mSessions.LastElement()->RequestData();
1164 0 : if (NS_FAILED(rv)) {
1165 0 : NotifyError(rv);
1166 : }
1167 : }
1168 :
1169 : JSObject*
1170 0 : MediaRecorder::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
1171 : {
1172 0 : return MediaRecorderBinding::Wrap(aCx, this, aGivenProto);
1173 : }
1174 :
1175 : /* static */ already_AddRefed<MediaRecorder>
1176 0 : MediaRecorder::Constructor(const GlobalObject& aGlobal,
1177 : DOMMediaStream& aStream,
1178 : const MediaRecorderOptions& aInitDict,
1179 : ErrorResult& aRv)
1180 : {
1181 0 : nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
1182 0 : if (!ownerWindow) {
1183 0 : aRv.Throw(NS_ERROR_FAILURE);
1184 0 : return nullptr;
1185 : }
1186 :
1187 0 : if (!IsTypeSupported(aInitDict.mMimeType)) {
1188 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
1189 0 : return nullptr;
1190 : }
1191 :
1192 0 : RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
1193 0 : object->SetOptions(aInitDict);
1194 0 : return object.forget();
1195 : }
1196 :
1197 : /* static */ already_AddRefed<MediaRecorder>
1198 0 : MediaRecorder::Constructor(const GlobalObject& aGlobal,
1199 : AudioNode& aSrcAudioNode,
1200 : uint32_t aSrcOutput,
1201 : const MediaRecorderOptions& aInitDict,
1202 : ErrorResult& aRv)
1203 : {
1204 : // Allow recording from audio node only when pref is on.
1205 0 : if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
1206 : // Pretending that this constructor is not defined.
1207 0 : NS_NAMED_LITERAL_STRING(argStr, "Argument 1 of MediaRecorder.constructor");
1208 0 : NS_NAMED_LITERAL_STRING(typeStr, "MediaStream");
1209 0 : aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>(argStr, typeStr);
1210 0 : return nullptr;
1211 : }
1212 :
1213 0 : nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
1214 0 : if (!ownerWindow) {
1215 0 : aRv.Throw(NS_ERROR_FAILURE);
1216 0 : return nullptr;
1217 : }
1218 :
1219 : // aSrcOutput doesn't matter to destination node because it has no output.
1220 0 : if (aSrcAudioNode.NumberOfOutputs() > 0 &&
1221 0 : aSrcOutput >= aSrcAudioNode.NumberOfOutputs()) {
1222 0 : aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
1223 0 : return nullptr;
1224 : }
1225 :
1226 0 : if (!IsTypeSupported(aInitDict.mMimeType)) {
1227 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
1228 0 : return nullptr;
1229 : }
1230 :
1231 : RefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
1232 : aSrcOutput,
1233 0 : ownerWindow);
1234 0 : object->SetOptions(aInitDict);
1235 0 : return object.forget();
1236 : }
1237 :
1238 : void
1239 0 : MediaRecorder::SetOptions(const MediaRecorderOptions& aInitDict)
1240 : {
1241 0 : SetMimeType(aInitDict.mMimeType);
1242 0 : mAudioBitsPerSecond = aInitDict.mAudioBitsPerSecond.WasPassed() ?
1243 0 : aInitDict.mAudioBitsPerSecond.Value() : 0;
1244 0 : mVideoBitsPerSecond = aInitDict.mVideoBitsPerSecond.WasPassed() ?
1245 0 : aInitDict.mVideoBitsPerSecond.Value() : 0;
1246 0 : mBitsPerSecond = aInitDict.mBitsPerSecond.WasPassed() ?
1247 0 : aInitDict.mBitsPerSecond.Value() : 0;
1248 : // We're not handling dynamic changes yet. Eventually we'll handle
1249 : // setting audio, video and/or total -- and anything that isn't set,
1250 : // we'll derive. Calculated versions require querying bitrates after
1251 : // the encoder is Init()ed. This happens only after data is
1252 : // available and thus requires dynamic changes.
1253 : //
1254 : // Until dynamic changes are supported, I prefer to be safe and err
1255 : // slightly high
1256 0 : if (aInitDict.mBitsPerSecond.WasPassed() && !aInitDict.mVideoBitsPerSecond.WasPassed()) {
1257 0 : mVideoBitsPerSecond = mBitsPerSecond;
1258 : }
1259 0 : }
1260 :
1261 : static char const *const gWebMAudioEncoderCodecs[2] = {
1262 : "opus",
1263 : // no VP9 yet
1264 : nullptr,
1265 : };
1266 : static char const *const gWebMVideoEncoderCodecs[4] = {
1267 : "opus",
1268 : "vp8",
1269 : "vp8.0",
1270 : // no VP9 yet
1271 : nullptr,
1272 : };
1273 : static char const *const gOggAudioEncoderCodecs[2] = {
1274 : "opus",
1275 : // we could support vorbis here too, but don't
1276 : nullptr,
1277 : };
1278 :
1279 : template <class String>
1280 : static bool
1281 0 : CodecListContains(char const *const * aCodecs, const String& aCodec)
1282 : {
1283 0 : for (int32_t i = 0; aCodecs[i]; ++i) {
1284 0 : if (aCodec.EqualsASCII(aCodecs[i]))
1285 0 : return true;
1286 : }
1287 0 : return false;
1288 : }
1289 :
1290 : /* static */
1291 : bool
1292 0 : MediaRecorder::IsTypeSupported(GlobalObject& aGlobal, const nsAString& aMIMEType)
1293 : {
1294 0 : return IsTypeSupported(aMIMEType);
1295 : }
1296 :
1297 : /* static */
1298 : bool
1299 0 : MediaRecorder::IsTypeSupported(const nsAString& aMIMEType)
1300 : {
1301 0 : char const* const* codeclist = nullptr;
1302 :
1303 0 : if (aMIMEType.IsEmpty()) {
1304 0 : return true;
1305 : }
1306 :
1307 0 : nsContentTypeParser parser(aMIMEType);
1308 0 : nsAutoString mimeType;
1309 0 : nsresult rv = parser.GetType(mimeType);
1310 0 : if (NS_FAILED(rv)) {
1311 0 : return false;
1312 : }
1313 :
1314 : // effectively a 'switch (mimeType) {'
1315 0 : if (mimeType.EqualsLiteral(AUDIO_OGG)) {
1316 0 : if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled()) {
1317 0 : codeclist = gOggAudioEncoderCodecs;
1318 : }
1319 : }
1320 : #ifdef MOZ_WEBM_ENCODER
1321 0 : else if (mimeType.EqualsLiteral(VIDEO_WEBM) &&
1322 0 : MediaEncoder::IsWebMEncoderEnabled()) {
1323 0 : codeclist = gWebMVideoEncoderCodecs;
1324 : }
1325 : #endif
1326 :
1327 : // codecs don't matter if we don't support the container
1328 0 : if (!codeclist) {
1329 0 : return false;
1330 : }
1331 : // now filter on codecs, and if needed rescind support
1332 0 : nsAutoString codecstring;
1333 0 : rv = parser.GetParameter("codecs", codecstring);
1334 :
1335 0 : nsTArray<nsString> codecs;
1336 0 : if (!ParseCodecsString(codecstring, codecs)) {
1337 0 : return false;
1338 : }
1339 0 : for (const nsString& codec : codecs) {
1340 0 : if (!CodecListContains(codeclist, codec)) {
1341 : // Totally unsupported codec
1342 0 : return false;
1343 : }
1344 : }
1345 :
1346 0 : return true;
1347 : }
1348 :
1349 : nsresult
1350 0 : MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
1351 : {
1352 0 : MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1353 :
1354 0 : BlobEventInit init;
1355 0 : init.mBubbles = false;
1356 0 : init.mCancelable = false;
1357 :
1358 0 : nsCOMPtr<nsIDOMBlob> blob = aBlob;
1359 0 : init.mData = static_cast<Blob*>(blob.get());
1360 :
1361 : RefPtr<BlobEvent> event =
1362 0 : BlobEvent::Constructor(this,
1363 0 : NS_LITERAL_STRING("dataavailable"),
1364 0 : init);
1365 0 : event->SetTrusted(true);
1366 0 : return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
1367 : }
1368 :
1369 : void
1370 0 : MediaRecorder::DispatchSimpleEvent(const nsAString & aStr)
1371 : {
1372 0 : MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1373 0 : nsresult rv = CheckInnerWindowCorrectness();
1374 0 : if (NS_FAILED(rv)) {
1375 0 : return;
1376 : }
1377 :
1378 0 : RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1379 0 : event->InitEvent(aStr, false, false);
1380 0 : event->SetTrusted(true);
1381 :
1382 0 : rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
1383 0 : if (NS_FAILED(rv)) {
1384 0 : NS_ERROR("Failed to dispatch the event!!!");
1385 0 : return;
1386 : }
1387 : }
1388 :
1389 : void
1390 0 : MediaRecorder::NotifyError(nsresult aRv)
1391 : {
1392 0 : MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1393 0 : nsresult rv = CheckInnerWindowCorrectness();
1394 0 : if (NS_FAILED(rv)) {
1395 0 : return;
1396 : }
1397 0 : nsString errorMsg;
1398 0 : switch (aRv) {
1399 : case NS_ERROR_DOM_SECURITY_ERR:
1400 0 : errorMsg = NS_LITERAL_STRING("SecurityError");
1401 0 : break;
1402 : case NS_ERROR_OUT_OF_MEMORY:
1403 0 : errorMsg = NS_LITERAL_STRING("OutOfMemoryError");
1404 0 : break;
1405 : default:
1406 0 : errorMsg = NS_LITERAL_STRING("GenericError");
1407 : }
1408 :
1409 0 : RecordErrorEventInit init;
1410 0 : init.mBubbles = false;
1411 0 : init.mCancelable = false;
1412 0 : init.mName = errorMsg;
1413 :
1414 : RefPtr<RecordErrorEvent> event =
1415 0 : RecordErrorEvent::Constructor(this, NS_LITERAL_STRING("error"), init);
1416 0 : event->SetTrusted(true);
1417 :
1418 0 : rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
1419 0 : if (NS_FAILED(rv)) {
1420 0 : NS_ERROR("Failed to dispatch the error event!!!");
1421 0 : return;
1422 : }
1423 0 : return;
1424 : }
1425 :
1426 : void
1427 0 : MediaRecorder::RemoveSession(Session* aSession)
1428 : {
1429 0 : LOG(LogLevel::Debug, ("MediaRecorder.RemoveSession (%p)", aSession));
1430 0 : mSessions.RemoveElement(aSession);
1431 0 : }
1432 :
1433 : void
1434 0 : MediaRecorder::NotifyOwnerDocumentActivityChanged()
1435 : {
1436 0 : nsPIDOMWindowInner* window = GetOwner();
1437 0 : NS_ENSURE_TRUE_VOID(window);
1438 0 : nsIDocument* doc = window->GetExtantDoc();
1439 0 : NS_ENSURE_TRUE_VOID(doc);
1440 :
1441 0 : LOG(LogLevel::Debug, ("MediaRecorder %p document IsActive %d isVisible %d\n",
1442 : this, doc->IsActive(), doc->IsVisible()));
1443 0 : if (!doc->IsActive() || !doc->IsVisible()) {
1444 : // Stop the session.
1445 0 : ErrorResult result;
1446 0 : Stop(result);
1447 0 : result.SuppressException();
1448 : }
1449 : }
1450 :
1451 : MediaStream*
1452 0 : MediaRecorder::GetSourceMediaStream()
1453 : {
1454 0 : if (mDOMStream != nullptr) {
1455 0 : return mDOMStream->GetPlaybackStream();
1456 : }
1457 0 : MOZ_ASSERT(mAudioNode != nullptr);
1458 0 : return mPipeStream ? mPipeStream.get() : mAudioNode->GetStream();
1459 : }
1460 :
1461 : size_t
1462 0 : MediaRecorder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
1463 : {
1464 0 : size_t amount = 42;
1465 0 : for (size_t i = 0; i < mSessions.Length(); ++i) {
1466 0 : amount += mSessions[i]->SizeOfExcludingThis(aMallocSizeOf);
1467 : }
1468 0 : return amount;
1469 : }
1470 :
1471 3 : StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
1472 :
1473 0 : MediaRecorderReporter* MediaRecorderReporter::UniqueInstance()
1474 : {
1475 0 : if (!sUniqueInstance) {
1476 0 : sUniqueInstance = new MediaRecorderReporter();
1477 0 : sUniqueInstance->InitMemoryReporter();
1478 : }
1479 0 : return sUniqueInstance;
1480 : }
1481 :
1482 0 : void MediaRecorderReporter::InitMemoryReporter()
1483 : {
1484 0 : RegisterWeakMemoryReporter(this);
1485 0 : }
1486 :
1487 0 : MediaRecorderReporter::~MediaRecorderReporter()
1488 : {
1489 0 : UnregisterWeakMemoryReporter(this);
1490 0 : }
1491 :
1492 : } // namespace dom
1493 : } // namespace mozilla
|