content/media/MediaRecorder.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

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 }

mercurial