Fri, 16 Jan 2015 04:50:19 +0100
Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MediaRecorder.h"
8 #include "GeneratedEvents.h"
9 #include "MediaEncoder.h"
10 #include "mozilla/DOMEventTargetHelper.h"
11 #include "nsError.h"
12 #include "nsIDocument.h"
13 #include "nsIDOMRecordErrorEvent.h"
14 #include "nsTArray.h"
15 #include "DOMMediaStream.h"
16 #include "EncodedBufferCache.h"
17 #include "nsIDOMFile.h"
18 #include "mozilla/dom/BlobEvent.h"
19 #include "nsIPrincipal.h"
20 #include "nsMimeTypes.h"
22 #include "mozilla/dom/AudioStreamTrack.h"
23 #include "mozilla/dom/VideoStreamTrack.h"
25 #ifdef PR_LOGGING
26 PRLogModuleInfo* gMediaRecorderLog;
27 #define LOG(type, msg) PR_LOG(gMediaRecorderLog, type, msg)
28 #else
29 #define LOG(type, msg)
30 #endif
32 namespace mozilla {
34 namespace dom {
36 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaRecorder, DOMEventTargetHelper,
37 mStream)
39 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder)
40 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
42 NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
43 NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
45 /**
46 * Session is an object to represent a single recording event.
47 * In original design, all recording context is stored in MediaRecorder, which causes
48 * a problem if someone calls MediaRecoder::Stop and MedaiRecorder::Start quickly.
49 * To prevent blocking main thread, media encoding is executed in a second thread,
50 * named as Read Thread. For the same reason, we do not wait Read Thread shutdown in
51 * MediaRecorder::Stop. If someone call MediaRecoder::Start before Read Thread shutdown,
52 * the same recording context in MediaRecoder might be access by two Reading Threads,
53 * which cause a problem.
54 * In the new design, we put recording context into Session object, including Read
55 * Thread. Each Session has its own recording context and Read Thread, problem is been
56 * resolved.
57 *
58 * Life cycle of a Session object.
59 * 1) Initialization Stage (in main thread)
60 * Setup media streams in MSG, and bind MediaEncoder with Source Stream when mStream is available.
61 * Resource allocation, such as encoded data cache buffer and MediaEncoder.
62 * Create read thread.
63 * Automatically switch to Extract stage in the end of this stage.
64 * 2) Extract Stage (in Read Thread)
65 * Pull encoded A/V frames from MediaEncoder, dispatch to OnDataAvailable handler.
66 * Unless a client calls Session::Stop, Session object keeps stay in this stage.
67 * 3) Destroy Stage (in main thread)
68 * Switch from Extract stage to Destroy stage by calling Session::Stop.
69 * Release session resource and remove associated streams from MSG.
70 *
71 * Lifetime of a Session object.
72 * 1) MediaRecorder creates a Session in MediaRecorder::Start function.
73 * 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
74 * _and_ all encoded media data been passed to OnDataAvailable handler.
75 */
76 class MediaRecorder::Session: public nsIObserver
77 {
78 NS_DECL_THREADSAFE_ISUPPORTS
80 // Main thread task.
81 // Create a blob event and send back to client.
82 class PushBlobRunnable : public nsRunnable
83 {
84 public:
85 PushBlobRunnable(Session* aSession)
86 : mSession(aSession)
87 { }
89 NS_IMETHODIMP Run()
90 {
91 LOG(PR_LOG_DEBUG, ("Session.PushBlobRunnable s=(%p)", mSession.get()));
92 MOZ_ASSERT(NS_IsMainThread());
94 nsRefPtr<MediaRecorder> recorder = mSession->mRecorder;
95 if (!recorder) {
96 return NS_OK;
97 }
98 recorder->SetMimeType(mSession->mMimeType);
99 if (mSession->IsEncoderError()) {
100 recorder->NotifyError(NS_ERROR_UNEXPECTED);
101 }
102 nsresult rv = recorder->CreateAndDispatchBlobEvent(mSession->GetEncodedData());
103 if (NS_FAILED(rv)) {
104 recorder->NotifyError(rv);
105 }
107 return NS_OK;
108 }
110 private:
111 nsRefPtr<Session> mSession;
112 };
114 // Record thread task and it run in Media Encoder thread.
115 // Fetch encoded Audio/Video data from MediaEncoder.
116 class ExtractRunnable : public nsRunnable
117 {
118 public:
119 ExtractRunnable(Session *aSession)
120 : mSession(aSession) {}
122 NS_IMETHODIMP Run()
123 {
124 MOZ_ASSERT(NS_GetCurrentThread() == mSession->mReadThread);
126 mSession->Extract();
127 LOG(PR_LOG_DEBUG, ("Session.ExtractRunnable shutdown = %d", mSession->mEncoder->IsShutdown()));
128 if (!mSession->mEncoder->IsShutdown()) {
129 NS_DispatchToCurrentThread(new ExtractRunnable(mSession));
130 } else {
131 // Flush out remainding encoded data.
132 NS_DispatchToMainThread(new PushBlobRunnable(mSession));
133 // Destroy this Session object in main thread.
134 NS_DispatchToMainThread(new DestroyRunnable(already_AddRefed<Session>(mSession)));
135 }
136 return NS_OK;
137 }
139 private:
140 nsRefPtr<Session> mSession;
141 };
143 // For Ensure recorder has tracks to record.
144 class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
145 {
146 public:
147 TracksAvailableCallback(Session *aSession)
148 : mSession(aSession) {}
149 virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
150 {
151 uint8_t trackType = aStream->GetHintContents();
152 // ToDo: GetHintContents return 0 when recording media tags.
153 if (trackType == 0) {
154 nsTArray<nsRefPtr<mozilla::dom::AudioStreamTrack> > audioTracks;
155 aStream->GetAudioTracks(audioTracks);
156 nsTArray<nsRefPtr<mozilla::dom::VideoStreamTrack> > videoTracks;
157 aStream->GetVideoTracks(videoTracks);
158 // What is inside the track
159 if (videoTracks.Length() > 0) {
160 trackType |= DOMMediaStream::HINT_CONTENTS_VIDEO;
161 }
162 if (audioTracks.Length() > 0) {
163 trackType |= DOMMediaStream::HINT_CONTENTS_AUDIO;
164 }
165 }
166 LOG(PR_LOG_DEBUG, ("Session.NotifyTracksAvailable track type = (%d)", trackType));
167 mSession->AfterTracksAdded(trackType);
168 }
169 private:
170 nsRefPtr<Session> mSession;
171 };
172 // Main thread task.
173 // To delete RecordingSession object.
174 class DestroyRunnable : public nsRunnable
175 {
176 public:
177 DestroyRunnable(already_AddRefed<Session>&& aSession)
178 : mSession(aSession) {}
180 NS_IMETHODIMP Run()
181 {
182 LOG(PR_LOG_DEBUG, ("Session.DestroyRunnable session refcnt = (%d) stopIssued %d s=(%p)",
183 (int)mSession->mRefCnt, mSession->mStopIssued, mSession.get()));
184 MOZ_ASSERT(NS_IsMainThread() && mSession.get());
185 nsRefPtr<MediaRecorder> recorder = mSession->mRecorder;
186 if (!recorder) {
187 return NS_OK;
188 }
189 // SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
190 // Read Thread will be terminate soon.
191 // We need to switch MediaRecorder to "Stop" state first to make sure
192 // MediaRecorder is not associated with this Session anymore, then, it's
193 // safe to delete this Session.
194 // Also avoid to run if this session already call stop before
195 if (!mSession->mStopIssued) {
196 ErrorResult result;
197 mSession->mStopIssued = true;
198 recorder->Stop(result);
199 NS_DispatchToMainThread(new DestroyRunnable(mSession.forget()));
200 return NS_OK;
201 }
203 // Dispatch stop event and clear MIME type.
204 mSession->mMimeType = NS_LITERAL_STRING("");
205 recorder->SetMimeType(mSession->mMimeType);
206 recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
207 recorder->RemoveSession(mSession);
208 mSession->mRecorder = nullptr;
209 return NS_OK;
210 }
212 private:
213 // Call mSession::Release automatically while DestroyRunnable be destroy.
214 nsRefPtr<Session> mSession;
215 };
217 friend class PushBlobRunnable;
218 friend class ExtractRunnable;
219 friend class DestroyRunnable;
220 friend class TracksAvailableCallback;
222 public:
223 Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
224 : mRecorder(aRecorder),
225 mTimeSlice(aTimeSlice),
226 mStopIssued(false)
227 {
228 MOZ_ASSERT(NS_IsMainThread());
230 AddRef();
231 mEncodedBufferCache = new EncodedBufferCache(MAX_ALLOW_MEMORY_BUFFER);
232 mLastBlobTimeStamp = TimeStamp::Now();
233 }
235 // Only DestroyRunnable is allowed to delete Session object.
236 virtual ~Session()
237 {
238 LOG(PR_LOG_DEBUG, ("Session.~Session (%p)", this));
239 CleanupStreams();
240 }
242 void Start()
243 {
244 LOG(PR_LOG_DEBUG, ("Session.Start %p", this));
245 MOZ_ASSERT(NS_IsMainThread());
247 SetupStreams();
248 }
250 void Stop()
251 {
252 LOG(PR_LOG_DEBUG, ("Session.Stop %p", this));
253 MOZ_ASSERT(NS_IsMainThread());
254 mStopIssued = true;
255 CleanupStreams();
256 nsContentUtils::UnregisterShutdownObserver(this);
257 }
259 nsresult Pause()
260 {
261 LOG(PR_LOG_DEBUG, ("Session.Pause"));
262 MOZ_ASSERT(NS_IsMainThread());
264 NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
265 mTrackUnionStream->ChangeExplicitBlockerCount(-1);
267 return NS_OK;
268 }
270 nsresult Resume()
271 {
272 LOG(PR_LOG_DEBUG, ("Session.Resume"));
273 MOZ_ASSERT(NS_IsMainThread());
275 NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
276 mTrackUnionStream->ChangeExplicitBlockerCount(1);
278 return NS_OK;
279 }
281 already_AddRefed<nsIDOMBlob> GetEncodedData()
282 {
283 MOZ_ASSERT(NS_IsMainThread());
284 return mEncodedBufferCache->ExtractBlob(mMimeType);
285 }
287 bool IsEncoderError()
288 {
289 if (mEncoder && mEncoder->HasError()) {
290 return true;
291 }
292 return false;
293 }
294 void ForgetMediaRecorder()
295 {
296 LOG(PR_LOG_DEBUG, ("Session.ForgetMediaRecorder (%p)", mRecorder));
297 mRecorder = nullptr;
298 }
299 private:
301 // Pull encoded meida data from MediaEncoder and put into EncodedBufferCache.
302 // Destroy this session object in the end of this function.
303 void Extract()
304 {
305 MOZ_ASSERT(NS_GetCurrentThread() == mReadThread);
306 LOG(PR_LOG_DEBUG, ("Session.Extract %p", this));
307 // Whether push encoded data back to onDataAvailable automatically.
308 const bool pushBlob = (mTimeSlice > 0) ? true : false;
310 // Pull encoded media data from MediaEncoder
311 nsTArray<nsTArray<uint8_t> > encodedBuf;
312 mEncoder->GetEncodedData(&encodedBuf, mMimeType);
314 // Append pulled data into cache buffer.
315 for (uint32_t i = 0; i < encodedBuf.Length(); i++) {
316 mEncodedBufferCache->AppendBuffer(encodedBuf[i]);
317 }
319 if (pushBlob) {
320 if ((TimeStamp::Now() - mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice) {
321 NS_DispatchToMainThread(new PushBlobRunnable(this));
322 mLastBlobTimeStamp = TimeStamp::Now();
323 }
324 }
325 }
327 // Bind media source with MediaEncoder to receive raw media data.
328 void SetupStreams()
329 {
330 MOZ_ASSERT(NS_IsMainThread());
332 // Create a Track Union Stream
333 MediaStreamGraph* gm = mRecorder->mStream->GetStream()->Graph();
334 mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
335 MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
337 mTrackUnionStream->SetAutofinish(true);
339 // Bind this Track Union Stream with Source Media
340 mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->mStream->GetStream(), MediaInputPort::FLAG_BLOCK_OUTPUT);
342 // Allocate encoder and bind with the Track Union Stream.
343 TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(mRecorder->mSessions.LastElement());
344 mRecorder->mStream->OnTracksAvailable(tracksAvailableCallback);
345 }
347 void AfterTracksAdded(uint8_t aTrackTypes)
348 {
349 LOG(PR_LOG_DEBUG, ("Session.AfterTracksAdded %p", this));
350 MOZ_ASSERT(NS_IsMainThread());
352 // Allocate encoder and bind with union stream.
353 // At this stage, the API doesn't allow UA to choose the output mimeType format.
355 nsCOMPtr<nsIDocument> doc = mRecorder->GetOwner()->GetExtantDoc();
356 uint16_t appStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
357 if (doc) {
358 doc->NodePrincipal()->GetAppStatus(&appStatus);
359 }
360 // Only allow certificated application can assign AUDIO_3GPP
361 if (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED &&
362 mRecorder->mMimeType.EqualsLiteral(AUDIO_3GPP)) {
363 mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(AUDIO_3GPP), aTrackTypes);
364 } else {
365 mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""), aTrackTypes);
366 }
368 if (!mEncoder) {
369 DoSessionEndTask(NS_ERROR_ABORT);
370 return;
371 }
373 // Media stream is ready but UA issues a stop method follow by start method.
374 // The Session::stop would clean the mTrackUnionStream. If the AfterTracksAdded
375 // comes after stop command, this function would crash.
376 if (!mTrackUnionStream) {
377 DoSessionEndTask(NS_OK);
378 return;
379 }
380 mTrackUnionStream->AddListener(mEncoder);
381 // Create a thread to read encode media data from MediaEncoder.
382 if (!mReadThread) {
383 nsresult rv = NS_NewNamedThread("Media Encoder", getter_AddRefs(mReadThread));
384 if (NS_FAILED(rv)) {
385 DoSessionEndTask(rv);
386 return;
387 }
388 }
390 // In case source media stream does not notify track end, recieve
391 // shutdown notification and stop Read Thread.
392 nsContentUtils::RegisterShutdownObserver(this);
394 mReadThread->Dispatch(new ExtractRunnable(this), NS_DISPATCH_NORMAL);
395 }
396 // application should get blob and onstop event
397 void DoSessionEndTask(nsresult rv)
398 {
399 MOZ_ASSERT(NS_IsMainThread());
400 if (NS_FAILED(rv)) {
401 mRecorder->NotifyError(rv);
402 }
404 CleanupStreams();
405 // Destroy this session object in main thread.
406 NS_DispatchToMainThread(new PushBlobRunnable(this));
407 NS_DispatchToMainThread(new DestroyRunnable(already_AddRefed<Session>(this)));
408 }
409 void CleanupStreams()
410 {
411 if (mInputPort.get()) {
412 mInputPort->Destroy();
413 mInputPort = nullptr;
414 }
416 if (mTrackUnionStream.get()) {
417 mTrackUnionStream->Destroy();
418 mTrackUnionStream = nullptr;
419 }
420 }
422 NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
423 {
424 MOZ_ASSERT(NS_IsMainThread());
425 LOG(PR_LOG_DEBUG, ("Session.Observe XPCOM_SHUTDOWN %p", this));
426 if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
427 // Force stop Session to terminate Read Thread.
428 mEncoder->Cancel();
429 if (mReadThread) {
430 mReadThread->Shutdown();
431 mReadThread = nullptr;
432 }
433 if (mRecorder) {
434 mRecorder->RemoveSession(this);
435 mRecorder = nullptr;
436 }
437 Stop();
438 }
440 return NS_OK;
441 }
443 private:
444 // Hold weak a reference to MediaRecoder and can be accessed ONLY on main thread.
445 MediaRecorder* mRecorder;
447 // Receive track data from source and dispatch to Encoder.
448 // Pause/ Resume controller.
449 nsRefPtr<ProcessedMediaStream> mTrackUnionStream;
450 nsRefPtr<MediaInputPort> mInputPort;
452 // Runnable thread for read data from MediaEncode.
453 nsCOMPtr<nsIThread> mReadThread;
454 // MediaEncoder pipeline.
455 nsRefPtr<MediaEncoder> mEncoder;
456 // A buffer to cache encoded meda data.
457 nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
458 // Current session mimeType
459 nsString mMimeType;
460 // Timestamp of the last fired dataavailable event.
461 TimeStamp mLastBlobTimeStamp;
462 // The interval of passing encoded data from EncodedBufferCache to onDataAvailable
463 // handler. "mTimeSlice < 0" means Session object does not push encoded data to
464 // onDataAvailable, instead, it passive wait the client side pull encoded data
465 // by calling requestData API.
466 const int32_t mTimeSlice;
467 // Indicate this session's stop has been called.
468 bool mStopIssued;
469 };
471 NS_IMPL_ISUPPORTS(MediaRecorder::Session, nsIObserver)
473 MediaRecorder::~MediaRecorder()
474 {
475 LOG(PR_LOG_DEBUG, ("~MediaRecorder (%p)", this));
476 for (uint32_t i = 0; i < mSessions.Length(); i ++) {
477 if (mSessions[i]) {
478 mSessions[i]->ForgetMediaRecorder();
479 mSessions[i]->Stop();
480 }
481 }
482 }
484 MediaRecorder::MediaRecorder(DOMMediaStream& aStream, nsPIDOMWindow* aOwnerWindow)
485 : DOMEventTargetHelper(aOwnerWindow),
486 mState(RecordingState::Inactive),
487 mMutex("Session.Data.Mutex")
488 {
489 MOZ_ASSERT(aOwnerWindow);
490 MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
491 mStream = &aStream;
492 #ifdef PR_LOGGING
493 if (!gMediaRecorderLog) {
494 gMediaRecorderLog = PR_NewLogModule("MediaRecorder");
495 }
496 #endif
497 }
499 void
500 MediaRecorder::SetMimeType(const nsString &aMimeType)
501 {
502 MutexAutoLock lock(mMutex);
503 mMimeType = aMimeType;
504 }
506 void
507 MediaRecorder::GetMimeType(nsString &aMimeType)
508 {
509 MutexAutoLock lock(mMutex);
510 aMimeType = mMimeType;
511 }
513 void
514 MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
515 {
516 LOG(PR_LOG_DEBUG, ("MediaRecorder.Start %p", this));
517 if (mState != RecordingState::Inactive) {
518 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
519 return;
520 }
522 if (mStream->GetStream()->IsFinished() || mStream->GetStream()->IsDestroyed()) {
523 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
524 return;
525 }
527 if (!mStream->GetPrincipal()) {
528 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
529 return;
530 }
532 if (!CheckPrincipal()) {
533 aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
534 return;
535 }
537 int32_t timeSlice = 0;
538 if (aTimeSlice.WasPassed()) {
539 if (aTimeSlice.Value() < 0) {
540 aResult.Throw(NS_ERROR_INVALID_ARG);
541 return;
542 }
544 timeSlice = aTimeSlice.Value();
545 }
547 mState = RecordingState::Recording;
548 // Start a session
550 mSessions.AppendElement();
551 mSessions.LastElement() = new Session(this, timeSlice);
552 mSessions.LastElement()->Start();
553 }
555 void
556 MediaRecorder::Stop(ErrorResult& aResult)
557 {
558 LOG(PR_LOG_DEBUG, ("MediaRecorder.Stop %p", this));
559 if (mState == RecordingState::Inactive) {
560 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
561 return;
562 }
563 mState = RecordingState::Inactive;
564 if (mSessions.Length() > 0) {
565 mSessions.LastElement()->Stop();
566 }
567 }
569 void
570 MediaRecorder::Pause(ErrorResult& aResult)
571 {
572 LOG(PR_LOG_DEBUG, ("MediaRecorder.Pause"));
573 if (mState != RecordingState::Recording) {
574 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
575 return;
576 }
578 MOZ_ASSERT(mSessions.Length() > 0);
579 nsresult rv = mSessions.LastElement()->Pause();
580 if (NS_FAILED(rv)) {
581 NotifyError(rv);
582 return;
583 }
584 mState = RecordingState::Paused;
585 }
587 void
588 MediaRecorder::Resume(ErrorResult& aResult)
589 {
590 LOG(PR_LOG_DEBUG, ("MediaRecorder.Resume"));
591 if (mState != RecordingState::Paused) {
592 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
593 return;
594 }
596 MOZ_ASSERT(mSessions.Length() > 0);
597 nsresult rv = mSessions.LastElement()->Resume();
598 if (NS_FAILED(rv)) {
599 NotifyError(rv);
600 return;
601 }
602 mState = RecordingState::Recording;
603 }
605 class CreateAndDispatchBlobEventRunnable : public nsRunnable {
606 nsCOMPtr<nsIDOMBlob> mBlob;
607 nsRefPtr<MediaRecorder> mRecorder;
608 public:
609 CreateAndDispatchBlobEventRunnable(already_AddRefed<nsIDOMBlob>&& aBlob,
610 MediaRecorder* aRecorder)
611 : mBlob(aBlob), mRecorder(aRecorder)
612 { }
614 NS_IMETHOD
615 Run();
616 };
618 NS_IMETHODIMP
619 CreateAndDispatchBlobEventRunnable::Run()
620 {
621 return mRecorder->CreateAndDispatchBlobEvent(mBlob.forget());
622 }
624 void
625 MediaRecorder::RequestData(ErrorResult& aResult)
626 {
627 if (mState != RecordingState::Recording) {
628 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
629 return;
630 }
632 NS_DispatchToMainThread(
633 new CreateAndDispatchBlobEventRunnable(mSessions.LastElement()->GetEncodedData(),
634 this),
635 NS_DISPATCH_NORMAL);
636 }
638 JSObject*
639 MediaRecorder::WrapObject(JSContext* aCx)
640 {
641 return MediaRecorderBinding::Wrap(aCx, this);
642 }
644 /* static */ already_AddRefed<MediaRecorder>
645 MediaRecorder::Constructor(const GlobalObject& aGlobal,
646 DOMMediaStream& aStream,
647 const MediaRecorderOptions& aInitDict,
648 ErrorResult& aRv)
649 {
650 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports());
651 if (!sgo) {
652 aRv.Throw(NS_ERROR_FAILURE);
653 return nullptr;
654 }
656 nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
657 if (!ownerWindow) {
658 aRv.Throw(NS_ERROR_FAILURE);
659 return nullptr;
660 }
662 nsRefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
663 object->SetMimeType(aInitDict.mMimeType);
664 return object.forget();
665 }
667 nsresult
668 MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
669 {
670 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
671 if (!CheckPrincipal()) {
672 // Media is not same-origin, don't allow the data out.
673 nsRefPtr<nsIDOMBlob> blob = aBlob;
674 return NS_ERROR_DOM_SECURITY_ERR;
675 }
676 BlobEventInit init;
677 init.mBubbles = false;
678 init.mCancelable = false;
679 init.mData = aBlob;
680 nsRefPtr<BlobEvent> event =
681 BlobEvent::Constructor(this,
682 NS_LITERAL_STRING("dataavailable"),
683 init);
684 event->SetTrusted(true);
685 return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
686 }
688 void
689 MediaRecorder::DispatchSimpleEvent(const nsAString & aStr)
690 {
691 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
692 nsresult rv = CheckInnerWindowCorrectness();
693 if (NS_FAILED(rv)) {
694 return;
695 }
697 nsCOMPtr<nsIDOMEvent> event;
698 rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
699 if (NS_FAILED(rv)) {
700 NS_WARNING("Failed to create the error event!!!");
701 return;
702 }
703 rv = event->InitEvent(aStr, false, false);
705 if (NS_FAILED(rv)) {
706 NS_WARNING("Failed to init the error event!!!");
707 return;
708 }
710 event->SetTrusted(true);
712 rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
713 if (NS_FAILED(rv)) {
714 NS_ERROR("Failed to dispatch the event!!!");
715 return;
716 }
717 }
719 void
720 MediaRecorder::NotifyError(nsresult aRv)
721 {
722 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
723 nsresult rv = CheckInnerWindowCorrectness();
724 if (NS_FAILED(rv)) {
725 return;
726 }
727 nsString errorMsg;
728 switch (aRv) {
729 case NS_ERROR_DOM_SECURITY_ERR:
730 errorMsg = NS_LITERAL_STRING("SecurityError");
731 break;
732 case NS_ERROR_OUT_OF_MEMORY:
733 errorMsg = NS_LITERAL_STRING("OutOfMemoryError");
734 break;
735 default:
736 errorMsg = NS_LITERAL_STRING("GenericError");
737 }
739 nsCOMPtr<nsIDOMEvent> event;
740 rv = NS_NewDOMRecordErrorEvent(getter_AddRefs(event), this, nullptr, nullptr);
742 nsCOMPtr<nsIDOMRecordErrorEvent> errorEvent = do_QueryInterface(event);
743 rv = errorEvent->InitRecordErrorEvent(NS_LITERAL_STRING("error"),
744 false, false, errorMsg);
746 event->SetTrusted(true);
747 rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
748 if (NS_FAILED(rv)) {
749 NS_ERROR("Failed to dispatch the error event!!!");
750 return;
751 }
752 return;
753 }
755 bool MediaRecorder::CheckPrincipal()
756 {
757 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
758 if (!mStream) {
759 return false;
760 }
761 nsCOMPtr<nsIPrincipal> principal = mStream->GetPrincipal();
762 if (!GetOwner())
763 return false;
764 nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
765 if (!doc || !principal)
766 return false;
768 bool subsumes;
769 if (NS_FAILED(doc->NodePrincipal()->Subsumes(principal, &subsumes)))
770 return false;
772 return subsumes;
773 }
775 void
776 MediaRecorder::RemoveSession(Session* aSession)
777 {
778 LOG(PR_LOG_DEBUG, ("MediaRecorder.RemoveSession (%p)", aSession));
779 mSessions.RemoveElement(aSession);
780 }
782 }
783 }