|
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/. */ |
|
6 |
|
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" |
|
21 |
|
22 #include "mozilla/dom/AudioStreamTrack.h" |
|
23 #include "mozilla/dom/VideoStreamTrack.h" |
|
24 |
|
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 |
|
31 |
|
32 namespace mozilla { |
|
33 |
|
34 namespace dom { |
|
35 |
|
36 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaRecorder, DOMEventTargetHelper, |
|
37 mStream) |
|
38 |
|
39 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder) |
|
40 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
|
41 |
|
42 NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper) |
|
43 NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper) |
|
44 |
|
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 |
|
79 |
|
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 { } |
|
88 |
|
89 NS_IMETHODIMP Run() |
|
90 { |
|
91 LOG(PR_LOG_DEBUG, ("Session.PushBlobRunnable s=(%p)", mSession.get())); |
|
92 MOZ_ASSERT(NS_IsMainThread()); |
|
93 |
|
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 } |
|
106 |
|
107 return NS_OK; |
|
108 } |
|
109 |
|
110 private: |
|
111 nsRefPtr<Session> mSession; |
|
112 }; |
|
113 |
|
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) {} |
|
121 |
|
122 NS_IMETHODIMP Run() |
|
123 { |
|
124 MOZ_ASSERT(NS_GetCurrentThread() == mSession->mReadThread); |
|
125 |
|
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 } |
|
138 |
|
139 private: |
|
140 nsRefPtr<Session> mSession; |
|
141 }; |
|
142 |
|
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) {} |
|
179 |
|
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 } |
|
202 |
|
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 } |
|
211 |
|
212 private: |
|
213 // Call mSession::Release automatically while DestroyRunnable be destroy. |
|
214 nsRefPtr<Session> mSession; |
|
215 }; |
|
216 |
|
217 friend class PushBlobRunnable; |
|
218 friend class ExtractRunnable; |
|
219 friend class DestroyRunnable; |
|
220 friend class TracksAvailableCallback; |
|
221 |
|
222 public: |
|
223 Session(MediaRecorder* aRecorder, int32_t aTimeSlice) |
|
224 : mRecorder(aRecorder), |
|
225 mTimeSlice(aTimeSlice), |
|
226 mStopIssued(false) |
|
227 { |
|
228 MOZ_ASSERT(NS_IsMainThread()); |
|
229 |
|
230 AddRef(); |
|
231 mEncodedBufferCache = new EncodedBufferCache(MAX_ALLOW_MEMORY_BUFFER); |
|
232 mLastBlobTimeStamp = TimeStamp::Now(); |
|
233 } |
|
234 |
|
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 } |
|
241 |
|
242 void Start() |
|
243 { |
|
244 LOG(PR_LOG_DEBUG, ("Session.Start %p", this)); |
|
245 MOZ_ASSERT(NS_IsMainThread()); |
|
246 |
|
247 SetupStreams(); |
|
248 } |
|
249 |
|
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 } |
|
258 |
|
259 nsresult Pause() |
|
260 { |
|
261 LOG(PR_LOG_DEBUG, ("Session.Pause")); |
|
262 MOZ_ASSERT(NS_IsMainThread()); |
|
263 |
|
264 NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE); |
|
265 mTrackUnionStream->ChangeExplicitBlockerCount(-1); |
|
266 |
|
267 return NS_OK; |
|
268 } |
|
269 |
|
270 nsresult Resume() |
|
271 { |
|
272 LOG(PR_LOG_DEBUG, ("Session.Resume")); |
|
273 MOZ_ASSERT(NS_IsMainThread()); |
|
274 |
|
275 NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE); |
|
276 mTrackUnionStream->ChangeExplicitBlockerCount(1); |
|
277 |
|
278 return NS_OK; |
|
279 } |
|
280 |
|
281 already_AddRefed<nsIDOMBlob> GetEncodedData() |
|
282 { |
|
283 MOZ_ASSERT(NS_IsMainThread()); |
|
284 return mEncodedBufferCache->ExtractBlob(mMimeType); |
|
285 } |
|
286 |
|
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: |
|
300 |
|
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; |
|
309 |
|
310 // Pull encoded media data from MediaEncoder |
|
311 nsTArray<nsTArray<uint8_t> > encodedBuf; |
|
312 mEncoder->GetEncodedData(&encodedBuf, mMimeType); |
|
313 |
|
314 // Append pulled data into cache buffer. |
|
315 for (uint32_t i = 0; i < encodedBuf.Length(); i++) { |
|
316 mEncodedBufferCache->AppendBuffer(encodedBuf[i]); |
|
317 } |
|
318 |
|
319 if (pushBlob) { |
|
320 if ((TimeStamp::Now() - mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice) { |
|
321 NS_DispatchToMainThread(new PushBlobRunnable(this)); |
|
322 mLastBlobTimeStamp = TimeStamp::Now(); |
|
323 } |
|
324 } |
|
325 } |
|
326 |
|
327 // Bind media source with MediaEncoder to receive raw media data. |
|
328 void SetupStreams() |
|
329 { |
|
330 MOZ_ASSERT(NS_IsMainThread()); |
|
331 |
|
332 // Create a Track Union Stream |
|
333 MediaStreamGraph* gm = mRecorder->mStream->GetStream()->Graph(); |
|
334 mTrackUnionStream = gm->CreateTrackUnionStream(nullptr); |
|
335 MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed"); |
|
336 |
|
337 mTrackUnionStream->SetAutofinish(true); |
|
338 |
|
339 // Bind this Track Union Stream with Source Media |
|
340 mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->mStream->GetStream(), MediaInputPort::FLAG_BLOCK_OUTPUT); |
|
341 |
|
342 // Allocate encoder and bind with the Track Union Stream. |
|
343 TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(mRecorder->mSessions.LastElement()); |
|
344 mRecorder->mStream->OnTracksAvailable(tracksAvailableCallback); |
|
345 } |
|
346 |
|
347 void AfterTracksAdded(uint8_t aTrackTypes) |
|
348 { |
|
349 LOG(PR_LOG_DEBUG, ("Session.AfterTracksAdded %p", this)); |
|
350 MOZ_ASSERT(NS_IsMainThread()); |
|
351 |
|
352 // Allocate encoder and bind with union stream. |
|
353 // At this stage, the API doesn't allow UA to choose the output mimeType format. |
|
354 |
|
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 } |
|
367 |
|
368 if (!mEncoder) { |
|
369 DoSessionEndTask(NS_ERROR_ABORT); |
|
370 return; |
|
371 } |
|
372 |
|
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 } |
|
389 |
|
390 // In case source media stream does not notify track end, recieve |
|
391 // shutdown notification and stop Read Thread. |
|
392 nsContentUtils::RegisterShutdownObserver(this); |
|
393 |
|
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 } |
|
403 |
|
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 } |
|
415 |
|
416 if (mTrackUnionStream.get()) { |
|
417 mTrackUnionStream->Destroy(); |
|
418 mTrackUnionStream = nullptr; |
|
419 } |
|
420 } |
|
421 |
|
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 } |
|
439 |
|
440 return NS_OK; |
|
441 } |
|
442 |
|
443 private: |
|
444 // Hold weak a reference to MediaRecoder and can be accessed ONLY on main thread. |
|
445 MediaRecorder* mRecorder; |
|
446 |
|
447 // Receive track data from source and dispatch to Encoder. |
|
448 // Pause/ Resume controller. |
|
449 nsRefPtr<ProcessedMediaStream> mTrackUnionStream; |
|
450 nsRefPtr<MediaInputPort> mInputPort; |
|
451 |
|
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 }; |
|
470 |
|
471 NS_IMPL_ISUPPORTS(MediaRecorder::Session, nsIObserver) |
|
472 |
|
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 } |
|
483 |
|
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 } |
|
498 |
|
499 void |
|
500 MediaRecorder::SetMimeType(const nsString &aMimeType) |
|
501 { |
|
502 MutexAutoLock lock(mMutex); |
|
503 mMimeType = aMimeType; |
|
504 } |
|
505 |
|
506 void |
|
507 MediaRecorder::GetMimeType(nsString &aMimeType) |
|
508 { |
|
509 MutexAutoLock lock(mMutex); |
|
510 aMimeType = mMimeType; |
|
511 } |
|
512 |
|
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 } |
|
521 |
|
522 if (mStream->GetStream()->IsFinished() || mStream->GetStream()->IsDestroyed()) { |
|
523 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
524 return; |
|
525 } |
|
526 |
|
527 if (!mStream->GetPrincipal()) { |
|
528 aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
529 return; |
|
530 } |
|
531 |
|
532 if (!CheckPrincipal()) { |
|
533 aResult.Throw(NS_ERROR_DOM_SECURITY_ERR); |
|
534 return; |
|
535 } |
|
536 |
|
537 int32_t timeSlice = 0; |
|
538 if (aTimeSlice.WasPassed()) { |
|
539 if (aTimeSlice.Value() < 0) { |
|
540 aResult.Throw(NS_ERROR_INVALID_ARG); |
|
541 return; |
|
542 } |
|
543 |
|
544 timeSlice = aTimeSlice.Value(); |
|
545 } |
|
546 |
|
547 mState = RecordingState::Recording; |
|
548 // Start a session |
|
549 |
|
550 mSessions.AppendElement(); |
|
551 mSessions.LastElement() = new Session(this, timeSlice); |
|
552 mSessions.LastElement()->Start(); |
|
553 } |
|
554 |
|
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 } |
|
568 |
|
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 } |
|
577 |
|
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 } |
|
586 |
|
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 } |
|
595 |
|
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 } |
|
604 |
|
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 { } |
|
613 |
|
614 NS_IMETHOD |
|
615 Run(); |
|
616 }; |
|
617 |
|
618 NS_IMETHODIMP |
|
619 CreateAndDispatchBlobEventRunnable::Run() |
|
620 { |
|
621 return mRecorder->CreateAndDispatchBlobEvent(mBlob.forget()); |
|
622 } |
|
623 |
|
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 } |
|
631 |
|
632 NS_DispatchToMainThread( |
|
633 new CreateAndDispatchBlobEventRunnable(mSessions.LastElement()->GetEncodedData(), |
|
634 this), |
|
635 NS_DISPATCH_NORMAL); |
|
636 } |
|
637 |
|
638 JSObject* |
|
639 MediaRecorder::WrapObject(JSContext* aCx) |
|
640 { |
|
641 return MediaRecorderBinding::Wrap(aCx, this); |
|
642 } |
|
643 |
|
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 } |
|
655 |
|
656 nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports()); |
|
657 if (!ownerWindow) { |
|
658 aRv.Throw(NS_ERROR_FAILURE); |
|
659 return nullptr; |
|
660 } |
|
661 |
|
662 nsRefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow); |
|
663 object->SetMimeType(aInitDict.mMimeType); |
|
664 return object.forget(); |
|
665 } |
|
666 |
|
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 } |
|
687 |
|
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 } |
|
696 |
|
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); |
|
704 |
|
705 if (NS_FAILED(rv)) { |
|
706 NS_WARNING("Failed to init the error event!!!"); |
|
707 return; |
|
708 } |
|
709 |
|
710 event->SetTrusted(true); |
|
711 |
|
712 rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr); |
|
713 if (NS_FAILED(rv)) { |
|
714 NS_ERROR("Failed to dispatch the event!!!"); |
|
715 return; |
|
716 } |
|
717 } |
|
718 |
|
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 } |
|
738 |
|
739 nsCOMPtr<nsIDOMEvent> event; |
|
740 rv = NS_NewDOMRecordErrorEvent(getter_AddRefs(event), this, nullptr, nullptr); |
|
741 |
|
742 nsCOMPtr<nsIDOMRecordErrorEvent> errorEvent = do_QueryInterface(event); |
|
743 rv = errorEvent->InitRecordErrorEvent(NS_LITERAL_STRING("error"), |
|
744 false, false, errorMsg); |
|
745 |
|
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 } |
|
754 |
|
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; |
|
767 |
|
768 bool subsumes; |
|
769 if (NS_FAILED(doc->NodePrincipal()->Subsumes(principal, &subsumes))) |
|
770 return false; |
|
771 |
|
772 return subsumes; |
|
773 } |
|
774 |
|
775 void |
|
776 MediaRecorder::RemoveSession(Session* aSession) |
|
777 { |
|
778 LOG(PR_LOG_DEBUG, ("MediaRecorder.RemoveSession (%p)", aSession)); |
|
779 mSessions.RemoveElement(aSession); |
|
780 } |
|
781 |
|
782 } |
|
783 } |