content/media/MediaRecorder.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/MediaRecorder.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,783 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "MediaRecorder.h"
    1.11 +#include "GeneratedEvents.h"
    1.12 +#include "MediaEncoder.h"
    1.13 +#include "mozilla/DOMEventTargetHelper.h"
    1.14 +#include "nsError.h"
    1.15 +#include "nsIDocument.h"
    1.16 +#include "nsIDOMRecordErrorEvent.h"
    1.17 +#include "nsTArray.h"
    1.18 +#include "DOMMediaStream.h"
    1.19 +#include "EncodedBufferCache.h"
    1.20 +#include "nsIDOMFile.h"
    1.21 +#include "mozilla/dom/BlobEvent.h"
    1.22 +#include "nsIPrincipal.h"
    1.23 +#include "nsMimeTypes.h"
    1.24 +
    1.25 +#include "mozilla/dom/AudioStreamTrack.h"
    1.26 +#include "mozilla/dom/VideoStreamTrack.h"
    1.27 +
    1.28 +#ifdef PR_LOGGING
    1.29 +PRLogModuleInfo* gMediaRecorderLog;
    1.30 +#define LOG(type, msg) PR_LOG(gMediaRecorderLog, type, msg)
    1.31 +#else
    1.32 +#define LOG(type, msg)
    1.33 +#endif
    1.34 +
    1.35 +namespace mozilla {
    1.36 +
    1.37 +namespace dom {
    1.38 +
    1.39 +NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaRecorder, DOMEventTargetHelper,
    1.40 +                                   mStream)
    1.41 +
    1.42 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder)
    1.43 +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
    1.44 +
    1.45 +NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
    1.46 +NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
    1.47 +
    1.48 +/**
    1.49 + * Session is an object to represent a single recording event.
    1.50 + * In original design, all recording context is stored in MediaRecorder, which causes
    1.51 + * a problem if someone calls MediaRecoder::Stop and MedaiRecorder::Start quickly.
    1.52 + * To prevent blocking main thread, media encoding is executed in a second thread,
    1.53 + * named as Read Thread. For the same reason, we do not wait Read Thread shutdown in
    1.54 + * MediaRecorder::Stop. If someone call MediaRecoder::Start before Read Thread shutdown,
    1.55 + * the same recording context in MediaRecoder might be access by two Reading Threads,
    1.56 + * which cause a  problem.
    1.57 + * In the new design, we put recording context into Session object, including Read
    1.58 + * Thread.  Each Session has its own recording context and Read Thread, problem is been
    1.59 + * resolved.
    1.60 + *
    1.61 + * Life cycle of a Session object.
    1.62 + * 1) Initialization Stage (in main thread)
    1.63 + *    Setup media streams in MSG, and bind MediaEncoder with Source Stream when mStream is available.
    1.64 + *    Resource allocation, such as encoded data cache buffer and MediaEncoder.
    1.65 + *    Create read thread.
    1.66 + *    Automatically switch to Extract stage in the end of this stage.
    1.67 + * 2) Extract Stage (in Read Thread)
    1.68 + *    Pull encoded A/V frames from MediaEncoder, dispatch to OnDataAvailable handler.
    1.69 + *    Unless a client calls Session::Stop, Session object keeps stay in this stage.
    1.70 + * 3) Destroy Stage (in main thread)
    1.71 + *    Switch from Extract stage to Destroy stage by calling Session::Stop.
    1.72 + *    Release session resource and remove associated streams from MSG.
    1.73 + *
    1.74 + * Lifetime of a Session object.
    1.75 + * 1) MediaRecorder creates a Session in MediaRecorder::Start function.
    1.76 + * 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
    1.77 + *    _and_ all encoded media data been passed to OnDataAvailable handler.
    1.78 + */
    1.79 +class MediaRecorder::Session: public nsIObserver
    1.80 +{
    1.81 +  NS_DECL_THREADSAFE_ISUPPORTS
    1.82 +
    1.83 +  // Main thread task.
    1.84 +  // Create a blob event and send back to client.
    1.85 +  class PushBlobRunnable : public nsRunnable
    1.86 +  {
    1.87 +  public:
    1.88 +    PushBlobRunnable(Session* aSession)
    1.89 +      : mSession(aSession)
    1.90 +    { }
    1.91 +
    1.92 +    NS_IMETHODIMP Run()
    1.93 +    {
    1.94 +      LOG(PR_LOG_DEBUG, ("Session.PushBlobRunnable s=(%p)", mSession.get()));
    1.95 +      MOZ_ASSERT(NS_IsMainThread());
    1.96 +
    1.97 +      nsRefPtr<MediaRecorder> recorder = mSession->mRecorder;
    1.98 +      if (!recorder) {
    1.99 +	 return NS_OK;
   1.100 +      }
   1.101 +      recorder->SetMimeType(mSession->mMimeType);
   1.102 +      if (mSession->IsEncoderError()) {
   1.103 +        recorder->NotifyError(NS_ERROR_UNEXPECTED);
   1.104 +      }
   1.105 +      nsresult rv = recorder->CreateAndDispatchBlobEvent(mSession->GetEncodedData());
   1.106 +      if (NS_FAILED(rv)) {
   1.107 +        recorder->NotifyError(rv);
   1.108 +      }
   1.109 +
   1.110 +      return NS_OK;
   1.111 +    }
   1.112 +
   1.113 +  private:
   1.114 +    nsRefPtr<Session> mSession;
   1.115 +  };
   1.116 +
   1.117 +  // Record thread task and it run in Media Encoder thread.
   1.118 +  // Fetch encoded Audio/Video data from MediaEncoder.
   1.119 +  class ExtractRunnable : public nsRunnable
   1.120 +  {
   1.121 +  public:
   1.122 +    ExtractRunnable(Session *aSession)
   1.123 +      : mSession(aSession) {}
   1.124 +
   1.125 +    NS_IMETHODIMP Run()
   1.126 +    {
   1.127 +      MOZ_ASSERT(NS_GetCurrentThread() == mSession->mReadThread);
   1.128 +
   1.129 +      mSession->Extract();
   1.130 +      LOG(PR_LOG_DEBUG, ("Session.ExtractRunnable shutdown = %d", mSession->mEncoder->IsShutdown()));
   1.131 +      if (!mSession->mEncoder->IsShutdown()) {
   1.132 +        NS_DispatchToCurrentThread(new ExtractRunnable(mSession));
   1.133 +      } else {
   1.134 +        // Flush out remainding encoded data.
   1.135 +        NS_DispatchToMainThread(new PushBlobRunnable(mSession));
   1.136 +        // Destroy this Session object in main thread.
   1.137 +        NS_DispatchToMainThread(new DestroyRunnable(already_AddRefed<Session>(mSession)));
   1.138 +      }
   1.139 +      return NS_OK;
   1.140 +    }
   1.141 +
   1.142 +  private:
   1.143 +    nsRefPtr<Session> mSession;
   1.144 +  };
   1.145 +
   1.146 +  // For Ensure recorder has tracks to record.
   1.147 +  class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
   1.148 +  {
   1.149 +  public:
   1.150 +    TracksAvailableCallback(Session *aSession)
   1.151 +     : mSession(aSession) {}
   1.152 +    virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
   1.153 +    {
   1.154 +      uint8_t trackType = aStream->GetHintContents();
   1.155 +      // ToDo: GetHintContents return 0 when recording media tags.
   1.156 +      if (trackType == 0) {
   1.157 +        nsTArray<nsRefPtr<mozilla::dom::AudioStreamTrack> > audioTracks;
   1.158 +        aStream->GetAudioTracks(audioTracks);
   1.159 +        nsTArray<nsRefPtr<mozilla::dom::VideoStreamTrack> > videoTracks;
   1.160 +        aStream->GetVideoTracks(videoTracks);
   1.161 +        // What is inside the track
   1.162 +        if (videoTracks.Length() > 0) {
   1.163 +          trackType |= DOMMediaStream::HINT_CONTENTS_VIDEO;
   1.164 +        }
   1.165 +        if (audioTracks.Length() > 0) {
   1.166 +          trackType |= DOMMediaStream::HINT_CONTENTS_AUDIO;
   1.167 +        }
   1.168 +      }
   1.169 +      LOG(PR_LOG_DEBUG, ("Session.NotifyTracksAvailable track type = (%d)", trackType));
   1.170 +      mSession->AfterTracksAdded(trackType);
   1.171 +    }
   1.172 +  private:
   1.173 +    nsRefPtr<Session> mSession;
   1.174 +  };
   1.175 +  // Main thread task.
   1.176 +  // To delete RecordingSession object.
   1.177 +  class DestroyRunnable : public nsRunnable
   1.178 +  {
   1.179 +  public:
   1.180 +    DestroyRunnable(already_AddRefed<Session>&& aSession)
   1.181 +      : mSession(aSession) {}
   1.182 +
   1.183 +    NS_IMETHODIMP Run()
   1.184 +    {
   1.185 +      LOG(PR_LOG_DEBUG, ("Session.DestroyRunnable session refcnt = (%d) stopIssued %d s=(%p)",
   1.186 +                         (int)mSession->mRefCnt, mSession->mStopIssued, mSession.get()));
   1.187 +      MOZ_ASSERT(NS_IsMainThread() && mSession.get());
   1.188 +      nsRefPtr<MediaRecorder> recorder = mSession->mRecorder;
   1.189 +      if (!recorder) {
   1.190 +        return NS_OK;
   1.191 +      }
   1.192 +      // SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
   1.193 +      // Read Thread will be terminate soon.
   1.194 +      // We need to switch MediaRecorder to "Stop" state first to make sure
   1.195 +      // MediaRecorder is not associated with this Session anymore, then, it's
   1.196 +      // safe to delete this Session.
   1.197 +      // Also avoid to run if this session already call stop before
   1.198 +      if (!mSession->mStopIssued) {
   1.199 +        ErrorResult result;
   1.200 +        mSession->mStopIssued = true;
   1.201 +        recorder->Stop(result);
   1.202 +        NS_DispatchToMainThread(new DestroyRunnable(mSession.forget()));
   1.203 +        return NS_OK;
   1.204 +      }
   1.205 +
   1.206 +      // Dispatch stop event and clear MIME type.
   1.207 +      mSession->mMimeType = NS_LITERAL_STRING("");
   1.208 +      recorder->SetMimeType(mSession->mMimeType);
   1.209 +      recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
   1.210 +      recorder->RemoveSession(mSession);
   1.211 +      mSession->mRecorder = nullptr;
   1.212 +      return NS_OK;
   1.213 +    }
   1.214 +
   1.215 +  private:
   1.216 +    // Call mSession::Release automatically while DestroyRunnable be destroy.
   1.217 +    nsRefPtr<Session> mSession;
   1.218 +  };
   1.219 +
   1.220 +  friend class PushBlobRunnable;
   1.221 +  friend class ExtractRunnable;
   1.222 +  friend class DestroyRunnable;
   1.223 +  friend class TracksAvailableCallback;
   1.224 +
   1.225 +public:
   1.226 +  Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
   1.227 +    : mRecorder(aRecorder),
   1.228 +      mTimeSlice(aTimeSlice),
   1.229 +      mStopIssued(false)
   1.230 +  {
   1.231 +    MOZ_ASSERT(NS_IsMainThread());
   1.232 +
   1.233 +    AddRef();
   1.234 +    mEncodedBufferCache = new EncodedBufferCache(MAX_ALLOW_MEMORY_BUFFER);
   1.235 +    mLastBlobTimeStamp = TimeStamp::Now();
   1.236 +  }
   1.237 +
   1.238 +  // Only DestroyRunnable is allowed to delete Session object.
   1.239 +  virtual ~Session()
   1.240 +  {
   1.241 +    LOG(PR_LOG_DEBUG, ("Session.~Session (%p)", this));
   1.242 +    CleanupStreams();
   1.243 +  }
   1.244 +
   1.245 +  void Start()
   1.246 +  {
   1.247 +    LOG(PR_LOG_DEBUG, ("Session.Start %p", this));
   1.248 +    MOZ_ASSERT(NS_IsMainThread());
   1.249 +
   1.250 +    SetupStreams();
   1.251 +  }
   1.252 +
   1.253 +  void Stop()
   1.254 +  {
   1.255 +    LOG(PR_LOG_DEBUG, ("Session.Stop %p", this));
   1.256 +    MOZ_ASSERT(NS_IsMainThread());
   1.257 +    mStopIssued = true;
   1.258 +    CleanupStreams();
   1.259 +    nsContentUtils::UnregisterShutdownObserver(this);
   1.260 +  }
   1.261 +
   1.262 +  nsresult Pause()
   1.263 +  {
   1.264 +    LOG(PR_LOG_DEBUG, ("Session.Pause"));
   1.265 +    MOZ_ASSERT(NS_IsMainThread());
   1.266 +
   1.267 +    NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
   1.268 +    mTrackUnionStream->ChangeExplicitBlockerCount(-1);
   1.269 +
   1.270 +    return NS_OK;
   1.271 +  }
   1.272 +
   1.273 +  nsresult Resume()
   1.274 +  {
   1.275 +    LOG(PR_LOG_DEBUG, ("Session.Resume"));
   1.276 +    MOZ_ASSERT(NS_IsMainThread());
   1.277 +
   1.278 +    NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
   1.279 +    mTrackUnionStream->ChangeExplicitBlockerCount(1);
   1.280 +
   1.281 +    return NS_OK;
   1.282 +  }
   1.283 +
   1.284 +  already_AddRefed<nsIDOMBlob> GetEncodedData()
   1.285 +  {
   1.286 +    MOZ_ASSERT(NS_IsMainThread());
   1.287 +    return mEncodedBufferCache->ExtractBlob(mMimeType);
   1.288 +  }
   1.289 +
   1.290 +  bool IsEncoderError()
   1.291 +  {
   1.292 +    if (mEncoder && mEncoder->HasError()) {
   1.293 +      return true;
   1.294 +    }
   1.295 +    return false;
   1.296 +  }
   1.297 +  void ForgetMediaRecorder()
   1.298 +  {
   1.299 +    LOG(PR_LOG_DEBUG, ("Session.ForgetMediaRecorder (%p)", mRecorder));
   1.300 +    mRecorder = nullptr;
   1.301 +  }
   1.302 +private:
   1.303 +
   1.304 +  // Pull encoded meida data from MediaEncoder and put into EncodedBufferCache.
   1.305 +  // Destroy this session object in the end of this function.
   1.306 +  void Extract()
   1.307 +  {
   1.308 +    MOZ_ASSERT(NS_GetCurrentThread() == mReadThread);
   1.309 +    LOG(PR_LOG_DEBUG, ("Session.Extract %p", this));
   1.310 +    // Whether push encoded data back to onDataAvailable automatically.
   1.311 +    const bool pushBlob = (mTimeSlice > 0) ? true : false;
   1.312 +
   1.313 +    // Pull encoded media data from MediaEncoder
   1.314 +    nsTArray<nsTArray<uint8_t> > encodedBuf;
   1.315 +    mEncoder->GetEncodedData(&encodedBuf, mMimeType);
   1.316 +
   1.317 +    // Append pulled data into cache buffer.
   1.318 +    for (uint32_t i = 0; i < encodedBuf.Length(); i++) {
   1.319 +      mEncodedBufferCache->AppendBuffer(encodedBuf[i]);
   1.320 +    }
   1.321 +
   1.322 +    if (pushBlob) {
   1.323 +      if ((TimeStamp::Now() - mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice) {
   1.324 +        NS_DispatchToMainThread(new PushBlobRunnable(this));
   1.325 +        mLastBlobTimeStamp = TimeStamp::Now();
   1.326 +      }
   1.327 +    }
   1.328 +  }
   1.329 +
   1.330 +  // Bind media source with MediaEncoder to receive raw media data.
   1.331 +  void SetupStreams()
   1.332 +  {
   1.333 +    MOZ_ASSERT(NS_IsMainThread());
   1.334 +
   1.335 +    // Create a Track Union Stream
   1.336 +    MediaStreamGraph* gm = mRecorder->mStream->GetStream()->Graph();
   1.337 +    mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
   1.338 +    MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
   1.339 +
   1.340 +    mTrackUnionStream->SetAutofinish(true);
   1.341 +
   1.342 +    // Bind this Track Union Stream with Source Media
   1.343 +    mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->mStream->GetStream(), MediaInputPort::FLAG_BLOCK_OUTPUT);
   1.344 +
   1.345 +    // Allocate encoder and bind with the Track Union Stream.
   1.346 +    TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(mRecorder->mSessions.LastElement());
   1.347 +    mRecorder->mStream->OnTracksAvailable(tracksAvailableCallback);
   1.348 +  }
   1.349 +
   1.350 +  void AfterTracksAdded(uint8_t aTrackTypes)
   1.351 +  {
   1.352 +    LOG(PR_LOG_DEBUG, ("Session.AfterTracksAdded %p", this));
   1.353 +    MOZ_ASSERT(NS_IsMainThread());
   1.354 +
   1.355 +    // Allocate encoder and bind with union stream.
   1.356 +    // At this stage, the API doesn't allow UA to choose the output mimeType format.
   1.357 +
   1.358 +    nsCOMPtr<nsIDocument> doc = mRecorder->GetOwner()->GetExtantDoc();
   1.359 +    uint16_t appStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
   1.360 +    if (doc) {
   1.361 +      doc->NodePrincipal()->GetAppStatus(&appStatus);
   1.362 +    }
   1.363 +    // Only allow certificated application can assign AUDIO_3GPP
   1.364 +    if (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED &&
   1.365 +         mRecorder->mMimeType.EqualsLiteral(AUDIO_3GPP)) {
   1.366 +      mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(AUDIO_3GPP), aTrackTypes);
   1.367 +    } else {
   1.368 +      mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""), aTrackTypes);
   1.369 +    }
   1.370 +
   1.371 +    if (!mEncoder) {
   1.372 +      DoSessionEndTask(NS_ERROR_ABORT);
   1.373 +      return;
   1.374 +    }
   1.375 +
   1.376 +    // Media stream is ready but UA issues a stop method follow by start method.
   1.377 +    // The Session::stop would clean the mTrackUnionStream. If the AfterTracksAdded
   1.378 +    // comes after stop command, this function would crash.
   1.379 +    if (!mTrackUnionStream) {
   1.380 +      DoSessionEndTask(NS_OK);
   1.381 +      return;
   1.382 +    }
   1.383 +    mTrackUnionStream->AddListener(mEncoder);
   1.384 +    // Create a thread to read encode media data from MediaEncoder.
   1.385 +    if (!mReadThread) {
   1.386 +      nsresult rv = NS_NewNamedThread("Media Encoder", getter_AddRefs(mReadThread));
   1.387 +      if (NS_FAILED(rv)) {
   1.388 +        DoSessionEndTask(rv);
   1.389 +        return;
   1.390 +      }
   1.391 +    }
   1.392 +
   1.393 +    // In case source media stream does not notify track end, recieve
   1.394 +    // shutdown notification and stop Read Thread.
   1.395 +    nsContentUtils::RegisterShutdownObserver(this);
   1.396 +
   1.397 +    mReadThread->Dispatch(new ExtractRunnable(this), NS_DISPATCH_NORMAL);
   1.398 +  }
   1.399 +  // application should get blob and onstop event
   1.400 +  void DoSessionEndTask(nsresult rv)
   1.401 +  {
   1.402 +    MOZ_ASSERT(NS_IsMainThread());
   1.403 +    if (NS_FAILED(rv)) {
   1.404 +      mRecorder->NotifyError(rv);
   1.405 +    }
   1.406 +
   1.407 +    CleanupStreams();
   1.408 +    // Destroy this session object in main thread.
   1.409 +    NS_DispatchToMainThread(new PushBlobRunnable(this));
   1.410 +    NS_DispatchToMainThread(new DestroyRunnable(already_AddRefed<Session>(this)));
   1.411 +  }
   1.412 +  void CleanupStreams()
   1.413 +  {
   1.414 +    if (mInputPort.get()) {
   1.415 +      mInputPort->Destroy();
   1.416 +      mInputPort = nullptr;
   1.417 +    }
   1.418 +
   1.419 +    if (mTrackUnionStream.get()) {
   1.420 +      mTrackUnionStream->Destroy();
   1.421 +      mTrackUnionStream = nullptr;
   1.422 +    }
   1.423 +  }
   1.424 +
   1.425 +  NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
   1.426 +  {
   1.427 +    MOZ_ASSERT(NS_IsMainThread());
   1.428 +    LOG(PR_LOG_DEBUG, ("Session.Observe XPCOM_SHUTDOWN %p", this));
   1.429 +    if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
   1.430 +      // Force stop Session to terminate Read Thread.
   1.431 +      mEncoder->Cancel();
   1.432 +      if (mReadThread) {
   1.433 +        mReadThread->Shutdown();
   1.434 +        mReadThread = nullptr;
   1.435 +      }
   1.436 +      if (mRecorder) {
   1.437 +        mRecorder->RemoveSession(this);
   1.438 +        mRecorder = nullptr;
   1.439 +      }
   1.440 +      Stop();
   1.441 +    }
   1.442 +
   1.443 +    return NS_OK;
   1.444 +  }
   1.445 +
   1.446 +private:
   1.447 +  // Hold weak a reference to MediaRecoder and can be accessed ONLY on main thread.
   1.448 +  MediaRecorder* mRecorder;
   1.449 +
   1.450 +  // Receive track data from source and dispatch to Encoder.
   1.451 +  // Pause/ Resume controller.
   1.452 +  nsRefPtr<ProcessedMediaStream> mTrackUnionStream;
   1.453 +  nsRefPtr<MediaInputPort> mInputPort;
   1.454 +
   1.455 +  // Runnable thread for read data from MediaEncode.
   1.456 +  nsCOMPtr<nsIThread> mReadThread;
   1.457 +  // MediaEncoder pipeline.
   1.458 +  nsRefPtr<MediaEncoder> mEncoder;
   1.459 +  // A buffer to cache encoded meda data.
   1.460 +  nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
   1.461 +  // Current session mimeType
   1.462 +  nsString mMimeType;
   1.463 +  // Timestamp of the last fired dataavailable event.
   1.464 +  TimeStamp mLastBlobTimeStamp;
   1.465 +  // The interval of passing encoded data from EncodedBufferCache to onDataAvailable
   1.466 +  // handler. "mTimeSlice < 0" means Session object does not push encoded data to
   1.467 +  // onDataAvailable, instead, it passive wait the client side pull encoded data
   1.468 +  // by calling requestData API.
   1.469 +  const int32_t mTimeSlice;
   1.470 +  // Indicate this session's stop has been called.
   1.471 +  bool mStopIssued;
   1.472 +};
   1.473 +
   1.474 +NS_IMPL_ISUPPORTS(MediaRecorder::Session, nsIObserver)
   1.475 +
   1.476 +MediaRecorder::~MediaRecorder()
   1.477 +{
   1.478 +  LOG(PR_LOG_DEBUG, ("~MediaRecorder (%p)", this));
   1.479 +  for (uint32_t i = 0; i < mSessions.Length(); i ++) {
   1.480 +    if (mSessions[i]) {
   1.481 +      mSessions[i]->ForgetMediaRecorder();
   1.482 +      mSessions[i]->Stop();
   1.483 +    }
   1.484 +  }
   1.485 +}
   1.486 +
   1.487 +MediaRecorder::MediaRecorder(DOMMediaStream& aStream, nsPIDOMWindow* aOwnerWindow)
   1.488 +  : DOMEventTargetHelper(aOwnerWindow),
   1.489 +    mState(RecordingState::Inactive),
   1.490 +    mMutex("Session.Data.Mutex")
   1.491 +{
   1.492 +  MOZ_ASSERT(aOwnerWindow);
   1.493 +  MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
   1.494 +  mStream = &aStream;
   1.495 +#ifdef PR_LOGGING
   1.496 +  if (!gMediaRecorderLog) {
   1.497 +    gMediaRecorderLog = PR_NewLogModule("MediaRecorder");
   1.498 +  }
   1.499 +#endif
   1.500 +}
   1.501 +
   1.502 +void
   1.503 +MediaRecorder::SetMimeType(const nsString &aMimeType)
   1.504 +{
   1.505 +  MutexAutoLock lock(mMutex);
   1.506 +  mMimeType = aMimeType;
   1.507 +}
   1.508 +
   1.509 +void
   1.510 +MediaRecorder::GetMimeType(nsString &aMimeType)
   1.511 +{
   1.512 +  MutexAutoLock lock(mMutex);
   1.513 +  aMimeType = mMimeType;
   1.514 +}
   1.515 +
   1.516 +void
   1.517 +MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
   1.518 +{
   1.519 +  LOG(PR_LOG_DEBUG, ("MediaRecorder.Start %p", this));
   1.520 +  if (mState != RecordingState::Inactive) {
   1.521 +    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1.522 +    return;
   1.523 +  }
   1.524 +
   1.525 +  if (mStream->GetStream()->IsFinished() || mStream->GetStream()->IsDestroyed()) {
   1.526 +    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1.527 +    return;
   1.528 +  }
   1.529 +
   1.530 +  if (!mStream->GetPrincipal()) {
   1.531 +    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1.532 +    return;
   1.533 +  }
   1.534 +
   1.535 +  if (!CheckPrincipal()) {
   1.536 +    aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
   1.537 +    return;
   1.538 +  }
   1.539 +
   1.540 +  int32_t timeSlice = 0;
   1.541 +  if (aTimeSlice.WasPassed()) {
   1.542 +    if (aTimeSlice.Value() < 0) {
   1.543 +      aResult.Throw(NS_ERROR_INVALID_ARG);
   1.544 +      return;
   1.545 +    }
   1.546 +
   1.547 +    timeSlice = aTimeSlice.Value();
   1.548 +  }
   1.549 +
   1.550 +  mState = RecordingState::Recording;
   1.551 +  // Start a session
   1.552 +
   1.553 +  mSessions.AppendElement();
   1.554 +  mSessions.LastElement() = new Session(this, timeSlice);
   1.555 +  mSessions.LastElement()->Start();
   1.556 +}
   1.557 +
   1.558 +void
   1.559 +MediaRecorder::Stop(ErrorResult& aResult)
   1.560 +{
   1.561 +  LOG(PR_LOG_DEBUG, ("MediaRecorder.Stop %p", this));
   1.562 +  if (mState == RecordingState::Inactive) {
   1.563 +    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1.564 +    return;
   1.565 +  }
   1.566 +  mState = RecordingState::Inactive;
   1.567 +  if (mSessions.Length() > 0) {
   1.568 +    mSessions.LastElement()->Stop();
   1.569 +  }
   1.570 +}
   1.571 +
   1.572 +void
   1.573 +MediaRecorder::Pause(ErrorResult& aResult)
   1.574 +{
   1.575 +  LOG(PR_LOG_DEBUG, ("MediaRecorder.Pause"));
   1.576 +  if (mState != RecordingState::Recording) {
   1.577 +    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1.578 +    return;
   1.579 +  }
   1.580 +
   1.581 +  MOZ_ASSERT(mSessions.Length() > 0);
   1.582 +  nsresult rv = mSessions.LastElement()->Pause();
   1.583 +  if (NS_FAILED(rv)) {
   1.584 +    NotifyError(rv);
   1.585 +    return;
   1.586 +  }
   1.587 +  mState = RecordingState::Paused;
   1.588 +}
   1.589 +
   1.590 +void
   1.591 +MediaRecorder::Resume(ErrorResult& aResult)
   1.592 +{
   1.593 +  LOG(PR_LOG_DEBUG, ("MediaRecorder.Resume"));
   1.594 +  if (mState != RecordingState::Paused) {
   1.595 +    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1.596 +    return;
   1.597 +  }
   1.598 +
   1.599 +  MOZ_ASSERT(mSessions.Length() > 0);
   1.600 +  nsresult rv = mSessions.LastElement()->Resume();
   1.601 +  if (NS_FAILED(rv)) {
   1.602 +    NotifyError(rv);
   1.603 +    return;
   1.604 +  }
   1.605 +  mState = RecordingState::Recording;
   1.606 +}
   1.607 +
   1.608 +class CreateAndDispatchBlobEventRunnable : public nsRunnable {
   1.609 +  nsCOMPtr<nsIDOMBlob> mBlob;
   1.610 +  nsRefPtr<MediaRecorder> mRecorder;
   1.611 +public:
   1.612 +  CreateAndDispatchBlobEventRunnable(already_AddRefed<nsIDOMBlob>&& aBlob,
   1.613 +                                     MediaRecorder* aRecorder)
   1.614 +    : mBlob(aBlob), mRecorder(aRecorder)
   1.615 +  { }
   1.616 +
   1.617 +  NS_IMETHOD
   1.618 +  Run();
   1.619 +};
   1.620 +
   1.621 +NS_IMETHODIMP
   1.622 +CreateAndDispatchBlobEventRunnable::Run()
   1.623 +{
   1.624 +  return mRecorder->CreateAndDispatchBlobEvent(mBlob.forget());
   1.625 +}
   1.626 +
   1.627 +void
   1.628 +MediaRecorder::RequestData(ErrorResult& aResult)
   1.629 +{
   1.630 +  if (mState != RecordingState::Recording) {
   1.631 +    aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   1.632 +    return;
   1.633 +  }
   1.634 +
   1.635 +  NS_DispatchToMainThread(
   1.636 +    new CreateAndDispatchBlobEventRunnable(mSessions.LastElement()->GetEncodedData(),
   1.637 +                                           this),
   1.638 +                                           NS_DISPATCH_NORMAL);
   1.639 +}
   1.640 +
   1.641 +JSObject*
   1.642 +MediaRecorder::WrapObject(JSContext* aCx)
   1.643 +{
   1.644 +  return MediaRecorderBinding::Wrap(aCx, this);
   1.645 +}
   1.646 +
   1.647 +/* static */ already_AddRefed<MediaRecorder>
   1.648 +MediaRecorder::Constructor(const GlobalObject& aGlobal,
   1.649 +                           DOMMediaStream& aStream,
   1.650 +                           const MediaRecorderOptions& aInitDict,
   1.651 +                           ErrorResult& aRv)
   1.652 +{
   1.653 +  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports());
   1.654 +  if (!sgo) {
   1.655 +    aRv.Throw(NS_ERROR_FAILURE);
   1.656 +    return nullptr;
   1.657 +  }
   1.658 +
   1.659 +  nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
   1.660 +  if (!ownerWindow) {
   1.661 +    aRv.Throw(NS_ERROR_FAILURE);
   1.662 +    return nullptr;
   1.663 +  }
   1.664 +
   1.665 +  nsRefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
   1.666 +  object->SetMimeType(aInitDict.mMimeType);
   1.667 +  return object.forget();
   1.668 +}
   1.669 +
   1.670 +nsresult
   1.671 +MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
   1.672 +{
   1.673 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.674 +  if (!CheckPrincipal()) {
   1.675 +    // Media is not same-origin, don't allow the data out.
   1.676 +    nsRefPtr<nsIDOMBlob> blob = aBlob;
   1.677 +    return NS_ERROR_DOM_SECURITY_ERR;
   1.678 +  }
   1.679 +  BlobEventInit init;
   1.680 +  init.mBubbles = false;
   1.681 +  init.mCancelable = false;
   1.682 +  init.mData = aBlob;
   1.683 +  nsRefPtr<BlobEvent> event =
   1.684 +    BlobEvent::Constructor(this,
   1.685 +                           NS_LITERAL_STRING("dataavailable"),
   1.686 +                           init);
   1.687 +  event->SetTrusted(true);
   1.688 +  return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   1.689 +}
   1.690 +
   1.691 +void
   1.692 +MediaRecorder::DispatchSimpleEvent(const nsAString & aStr)
   1.693 +{
   1.694 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.695 +  nsresult rv = CheckInnerWindowCorrectness();
   1.696 +  if (NS_FAILED(rv)) {
   1.697 +    return;
   1.698 +  }
   1.699 +
   1.700 +  nsCOMPtr<nsIDOMEvent> event;
   1.701 +  rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
   1.702 +  if (NS_FAILED(rv)) {
   1.703 +    NS_WARNING("Failed to create the error event!!!");
   1.704 +    return;
   1.705 +  }
   1.706 +  rv = event->InitEvent(aStr, false, false);
   1.707 +
   1.708 +  if (NS_FAILED(rv)) {
   1.709 +    NS_WARNING("Failed to init the error event!!!");
   1.710 +    return;
   1.711 +  }
   1.712 +
   1.713 +  event->SetTrusted(true);
   1.714 +
   1.715 +  rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   1.716 +  if (NS_FAILED(rv)) {
   1.717 +    NS_ERROR("Failed to dispatch the event!!!");
   1.718 +    return;
   1.719 +  }
   1.720 +}
   1.721 +
   1.722 +void
   1.723 +MediaRecorder::NotifyError(nsresult aRv)
   1.724 +{
   1.725 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.726 +  nsresult rv = CheckInnerWindowCorrectness();
   1.727 +  if (NS_FAILED(rv)) {
   1.728 +    return;
   1.729 +  }
   1.730 +  nsString errorMsg;
   1.731 +  switch (aRv) {
   1.732 +  case NS_ERROR_DOM_SECURITY_ERR:
   1.733 +    errorMsg = NS_LITERAL_STRING("SecurityError");
   1.734 +    break;
   1.735 +  case NS_ERROR_OUT_OF_MEMORY:
   1.736 +    errorMsg = NS_LITERAL_STRING("OutOfMemoryError");
   1.737 +    break;
   1.738 +  default:
   1.739 +    errorMsg = NS_LITERAL_STRING("GenericError");
   1.740 +  }
   1.741 +
   1.742 +  nsCOMPtr<nsIDOMEvent> event;
   1.743 +  rv = NS_NewDOMRecordErrorEvent(getter_AddRefs(event), this, nullptr, nullptr);
   1.744 +
   1.745 +  nsCOMPtr<nsIDOMRecordErrorEvent> errorEvent = do_QueryInterface(event);
   1.746 +  rv = errorEvent->InitRecordErrorEvent(NS_LITERAL_STRING("error"),
   1.747 +                                        false, false, errorMsg);
   1.748 +
   1.749 +  event->SetTrusted(true);
   1.750 +  rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   1.751 +  if (NS_FAILED(rv)) {
   1.752 +    NS_ERROR("Failed to dispatch the error event!!!");
   1.753 +    return;
   1.754 +  }
   1.755 +  return;
   1.756 +}
   1.757 +
   1.758 +bool MediaRecorder::CheckPrincipal()
   1.759 +{
   1.760 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.761 +  if (!mStream) {
   1.762 +    return false;
   1.763 +  }
   1.764 +  nsCOMPtr<nsIPrincipal> principal = mStream->GetPrincipal();
   1.765 +  if (!GetOwner())
   1.766 +    return false;
   1.767 +  nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
   1.768 +  if (!doc || !principal)
   1.769 +    return false;
   1.770 +
   1.771 +  bool subsumes;
   1.772 +  if (NS_FAILED(doc->NodePrincipal()->Subsumes(principal, &subsumes)))
   1.773 +    return false;
   1.774 +
   1.775 +  return subsumes;
   1.776 +}
   1.777 +
   1.778 +void
   1.779 +MediaRecorder::RemoveSession(Session* aSession)
   1.780 +{
   1.781 +  LOG(PR_LOG_DEBUG, ("MediaRecorder.RemoveSession (%p)", aSession));
   1.782 +  mSessions.RemoveElement(aSession);
   1.783 +}
   1.784 +
   1.785 +}
   1.786 +}

mercurial