content/media/MediaRecorder.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial