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 +}