2013-07-04 18:50:25 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
#include "MediaRecorder.h"
|
2014-08-27 14:40:00 -07:00
|
|
|
#include "AudioNodeEngine.h"
|
|
|
|
#include "AudioNodeStream.h"
|
|
|
|
#include "DOMMediaStream.h"
|
|
|
|
#include "EncodedBufferCache.h"
|
2013-07-04 18:50:25 -07:00
|
|
|
#include "MediaEncoder.h"
|
2014-03-31 23:13:50 -07:00
|
|
|
#include "mozilla/DOMEventTargetHelper.h"
|
2014-08-27 14:40:00 -07:00
|
|
|
#include "mozilla/Preferences.h"
|
|
|
|
#include "mozilla/dom/AudioStreamTrack.h"
|
|
|
|
#include "mozilla/dom/BlobEvent.h"
|
|
|
|
#include "mozilla/dom/RecordErrorEvent.h"
|
|
|
|
#include "mozilla/dom/VideoStreamTrack.h"
|
2013-07-04 18:50:25 -07:00
|
|
|
#include "nsError.h"
|
|
|
|
#include "nsIDocument.h"
|
2013-09-05 13:25:17 -07:00
|
|
|
#include "nsIDOMFile.h"
|
2014-03-18 23:52:45 -07:00
|
|
|
#include "nsIPrincipal.h"
|
|
|
|
#include "nsMimeTypes.h"
|
2014-06-26 02:22:05 -07:00
|
|
|
#include "nsProxyRelease.h"
|
2014-08-27 14:40:00 -07:00
|
|
|
#include "nsTArray.h"
|
2013-12-18 02:39:45 -08:00
|
|
|
|
2014-03-25 10:11:58 -07:00
|
|
|
#ifdef PR_LOGGING
|
|
|
|
PRLogModuleInfo* gMediaRecorderLog;
|
|
|
|
#define LOG(type, msg) PR_LOG(gMediaRecorderLog, type, msg)
|
|
|
|
#else
|
|
|
|
#define LOG(type, msg)
|
|
|
|
#endif
|
|
|
|
|
2013-07-04 18:50:25 -07:00
|
|
|
namespace mozilla {
|
|
|
|
|
|
|
|
namespace dom {
|
|
|
|
|
2014-04-25 09:49:00 -07:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaRecorder, DOMEventTargetHelper,
|
2014-08-27 14:40:00 -07:00
|
|
|
mDOMStream, mAudioNode)
|
2013-07-04 18:50:25 -07:00
|
|
|
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder)
|
2014-06-18 19:11:34 -07:00
|
|
|
NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
|
2014-03-31 23:13:50 -07:00
|
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
2013-07-04 18:50:25 -07:00
|
|
|
|
2014-03-31 23:13:50 -07:00
|
|
|
NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
|
|
|
|
NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
|
2013-07-04 18:50:25 -07:00
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
/**
|
|
|
|
* Session is an object to represent a single recording event.
|
|
|
|
* In original design, all recording context is stored in MediaRecorder, which causes
|
|
|
|
* a problem if someone calls MediaRecoder::Stop and MedaiRecorder::Start quickly.
|
|
|
|
* To prevent blocking main thread, media encoding is executed in a second thread,
|
|
|
|
* named as Read Thread. For the same reason, we do not wait Read Thread shutdown in
|
|
|
|
* MediaRecorder::Stop. If someone call MediaRecoder::Start before Read Thread shutdown,
|
|
|
|
* the same recording context in MediaRecoder might be access by two Reading Threads,
|
|
|
|
* which cause a problem.
|
|
|
|
* In the new design, we put recording context into Session object, including Read
|
|
|
|
* Thread. Each Session has its own recording context and Read Thread, problem is been
|
|
|
|
* resolved.
|
|
|
|
*
|
|
|
|
* Life cycle of a Session object.
|
|
|
|
* 1) Initialization Stage (in main thread)
|
2013-12-18 02:39:45 -08:00
|
|
|
* Setup media streams in MSG, and bind MediaEncoder with Source Stream when mStream is available.
|
2013-09-26 06:40:40 -07:00
|
|
|
* Resource allocation, such as encoded data cache buffer and MediaEncoder.
|
|
|
|
* Create read thread.
|
|
|
|
* Automatically switch to Extract stage in the end of this stage.
|
|
|
|
* 2) Extract Stage (in Read Thread)
|
|
|
|
* Pull encoded A/V frames from MediaEncoder, dispatch to OnDataAvailable handler.
|
|
|
|
* Unless a client calls Session::Stop, Session object keeps stay in this stage.
|
|
|
|
* 3) Destroy Stage (in main thread)
|
|
|
|
* Switch from Extract stage to Destroy stage by calling Session::Stop.
|
|
|
|
* Release session resource and remove associated streams from MSG.
|
|
|
|
*
|
2014-06-18 19:11:34 -07:00
|
|
|
* Lifetime of MediaRecorder and Session objects.
|
2014-08-17 20:42:49 -07:00
|
|
|
* 1) MediaRecorder creates a Session in MediaRecorder::Start function and holds
|
|
|
|
* a reference to Session. Then the Session registers itself to
|
|
|
|
* ShutdownObserver and also holds a reference to MediaRecorder.
|
|
|
|
* Therefore, the reference dependency in gecko is:
|
|
|
|
* ShutdownObserver -> Session <-> MediaRecorder, note that there is a cycle
|
|
|
|
* reference between Session and MediaRecorder.
|
2013-09-26 06:40:40 -07:00
|
|
|
* 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
|
|
|
|
* _and_ all encoded media data been passed to OnDataAvailable handler.
|
2014-06-18 19:11:34 -07:00
|
|
|
* 3) MediaRecorder::Stop is called by user or the document is going to
|
|
|
|
* inactive or invisible.
|
2013-09-26 06:40:40 -07:00
|
|
|
*/
|
2013-11-04 14:27:39 -08:00
|
|
|
class MediaRecorder::Session: public nsIObserver
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
2013-11-04 14:27:39 -08:00
|
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
// Main thread task.
|
|
|
|
// Create a blob event and send back to client.
|
|
|
|
class PushBlobRunnable : public nsRunnable
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
2013-09-26 06:40:40 -07:00
|
|
|
public:
|
|
|
|
PushBlobRunnable(Session* aSession)
|
|
|
|
: mSession(aSession)
|
|
|
|
{ }
|
|
|
|
|
|
|
|
NS_IMETHODIMP Run()
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.PushBlobRunnable s=(%p)", mSession.get()));
|
2013-09-26 06:40:40 -07:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
2014-03-25 10:11:58 -07:00
|
|
|
nsRefPtr<MediaRecorder> recorder = mSession->mRecorder;
|
|
|
|
if (!recorder) {
|
2014-06-18 19:11:34 -07:00
|
|
|
return NS_OK;
|
2014-03-25 10:11:58 -07:00
|
|
|
}
|
2014-06-26 20:07:45 -07:00
|
|
|
|
2013-11-24 15:57:52 -08:00
|
|
|
if (mSession->IsEncoderError()) {
|
|
|
|
recorder->NotifyError(NS_ERROR_UNEXPECTED);
|
|
|
|
}
|
2014-01-13 00:11:22 -08:00
|
|
|
nsresult rv = recorder->CreateAndDispatchBlobEvent(mSession->GetEncodedData());
|
2013-09-26 06:40:40 -07:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
recorder->NotifyError(rv);
|
|
|
|
}
|
2014-03-25 10:11:58 -07:00
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
return NS_OK;
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
private:
|
2013-12-18 02:39:45 -08:00
|
|
|
nsRefPtr<Session> mSession;
|
2013-09-26 06:40:40 -07:00
|
|
|
};
|
2013-07-04 18:50:25 -07:00
|
|
|
|
2014-06-26 20:07:45 -07:00
|
|
|
// Fire start event and set mimeType, run in main thread task.
|
|
|
|
class DispatchStartEventRunnable : public nsRunnable
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
DispatchStartEventRunnable(Session* aSession, const nsAString & aEventName)
|
|
|
|
: mSession(aSession)
|
|
|
|
, mEventName(aEventName)
|
|
|
|
{ }
|
|
|
|
|
|
|
|
NS_IMETHODIMP Run()
|
|
|
|
{
|
|
|
|
LOG(PR_LOG_DEBUG, ("Session.DispatchStartEventRunnable s=(%p)", mSession.get()));
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
NS_ENSURE_TRUE(mSession->mRecorder, NS_OK);
|
|
|
|
nsRefPtr<MediaRecorder> recorder = mSession->mRecorder;
|
|
|
|
|
|
|
|
recorder->SetMimeType(mSession->mMimeType);
|
|
|
|
recorder->DispatchSimpleEvent(mEventName);
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
nsRefPtr<Session> mSession;
|
|
|
|
nsString mEventName;
|
|
|
|
};
|
|
|
|
|
2014-01-02 23:24:57 -08:00
|
|
|
// Record thread task and it run in Media Encoder thread.
|
2013-09-26 06:40:40 -07:00
|
|
|
// Fetch encoded Audio/Video data from MediaEncoder.
|
|
|
|
class ExtractRunnable : public nsRunnable
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
2013-09-26 06:40:40 -07:00
|
|
|
public:
|
2014-08-17 20:42:49 -07:00
|
|
|
ExtractRunnable(Session* aSession)
|
2013-09-26 06:40:40 -07:00
|
|
|
: mSession(aSession) {}
|
2013-07-04 18:50:25 -07:00
|
|
|
|
2014-06-26 02:22:05 -07:00
|
|
|
~ExtractRunnable()
|
2014-08-17 20:42:49 -07:00
|
|
|
{}
|
2014-06-26 02:22:05 -07:00
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
NS_IMETHODIMP Run()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_GetCurrentThread() == mSession->mReadThread);
|
2013-07-04 18:50:25 -07:00
|
|
|
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.ExtractRunnable shutdown = %d", mSession->mEncoder->IsShutdown()));
|
2014-01-04 09:20:21 -08:00
|
|
|
if (!mSession->mEncoder->IsShutdown()) {
|
2014-06-23 23:05:49 -07:00
|
|
|
mSession->Extract(false);
|
2014-08-17 20:42:49 -07:00
|
|
|
nsRefPtr<nsIRunnable> event = new ExtractRunnable(mSession);
|
2014-06-26 02:22:05 -07:00
|
|
|
if (NS_FAILED(NS_DispatchToCurrentThread(event))) {
|
|
|
|
NS_WARNING("Failed to dispatch ExtractRunnable to encoder thread");
|
|
|
|
}
|
2014-01-04 09:20:21 -08:00
|
|
|
} else {
|
2014-06-23 23:05:49 -07:00
|
|
|
// Flush out remaining encoded data.
|
|
|
|
mSession->Extract(true);
|
2014-06-26 02:22:05 -07:00
|
|
|
if (NS_FAILED(NS_DispatchToMainThread(
|
2014-08-17 20:42:49 -07:00
|
|
|
new DestroyRunnable(mSession)))) {
|
2014-06-26 02:22:05 -07:00
|
|
|
MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
|
|
|
|
}
|
2014-01-04 09:20:21 -08:00
|
|
|
}
|
2013-09-26 06:40:40 -07:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2014-03-25 10:11:58 -07:00
|
|
|
nsRefPtr<Session> mSession;
|
2013-12-18 02:39:45 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// For Ensure recorder has tracks to record.
|
|
|
|
class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
TracksAvailableCallback(Session *aSession)
|
|
|
|
: mSession(aSession) {}
|
|
|
|
virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
|
|
|
|
{
|
|
|
|
uint8_t trackType = aStream->GetHintContents();
|
|
|
|
// ToDo: GetHintContents return 0 when recording media tags.
|
|
|
|
if (trackType == 0) {
|
|
|
|
nsTArray<nsRefPtr<mozilla::dom::AudioStreamTrack> > audioTracks;
|
|
|
|
aStream->GetAudioTracks(audioTracks);
|
|
|
|
nsTArray<nsRefPtr<mozilla::dom::VideoStreamTrack> > videoTracks;
|
|
|
|
aStream->GetVideoTracks(videoTracks);
|
|
|
|
// What is inside the track
|
|
|
|
if (videoTracks.Length() > 0) {
|
|
|
|
trackType |= DOMMediaStream::HINT_CONTENTS_VIDEO;
|
|
|
|
}
|
|
|
|
if (audioTracks.Length() > 0) {
|
|
|
|
trackType |= DOMMediaStream::HINT_CONTENTS_AUDIO;
|
|
|
|
}
|
|
|
|
}
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.NotifyTracksAvailable track type = (%d)", trackType));
|
2014-08-27 14:40:00 -07:00
|
|
|
mSession->InitEncoder(trackType);
|
2013-12-18 02:39:45 -08:00
|
|
|
}
|
|
|
|
private:
|
|
|
|
nsRefPtr<Session> mSession;
|
2013-09-26 06:40:40 -07:00
|
|
|
};
|
|
|
|
// Main thread task.
|
|
|
|
// To delete RecordingSession object.
|
|
|
|
class DestroyRunnable : public nsRunnable
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
|
|
|
public:
|
2014-08-17 20:42:49 -07:00
|
|
|
DestroyRunnable(Session* aSession)
|
2013-09-26 06:40:40 -07:00
|
|
|
: mSession(aSession) {}
|
2013-07-04 18:50:25 -07:00
|
|
|
|
|
|
|
NS_IMETHODIMP Run()
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.DestroyRunnable session refcnt = (%d) stopIssued %d s=(%p)",
|
|
|
|
(int)mSession->mRefCnt, mSession->mStopIssued, mSession.get()));
|
2013-09-26 06:40:40 -07:00
|
|
|
MOZ_ASSERT(NS_IsMainThread() && mSession.get());
|
2014-03-25 10:11:58 -07:00
|
|
|
nsRefPtr<MediaRecorder> recorder = mSession->mRecorder;
|
|
|
|
if (!recorder) {
|
|
|
|
return NS_OK;
|
|
|
|
}
|
2013-11-04 14:27:39 -08:00
|
|
|
// SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
|
|
|
|
// Read Thread will be terminate soon.
|
|
|
|
// We need to switch MediaRecorder to "Stop" state first to make sure
|
|
|
|
// MediaRecorder is not associated with this Session anymore, then, it's
|
|
|
|
// safe to delete this Session.
|
2014-01-29 07:09:43 -08:00
|
|
|
// Also avoid to run if this session already call stop before
|
|
|
|
if (!mSession->mStopIssued) {
|
2013-09-26 06:40:40 -07:00
|
|
|
ErrorResult result;
|
2014-03-25 10:11:58 -07:00
|
|
|
mSession->mStopIssued = true;
|
2013-09-26 06:40:40 -07:00
|
|
|
recorder->Stop(result);
|
2014-08-17 20:42:49 -07:00
|
|
|
if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(mSession)))) {
|
2014-06-26 02:22:05 -07:00
|
|
|
MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
|
|
|
|
}
|
2013-09-26 06:40:40 -07:00
|
|
|
return NS_OK;
|
|
|
|
}
|
2013-08-29 04:59:08 -07:00
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
// Dispatch stop event and clear MIME type.
|
2014-01-29 07:09:43 -08:00
|
|
|
mSession->mMimeType = NS_LITERAL_STRING("");
|
|
|
|
recorder->SetMimeType(mSession->mMimeType);
|
2014-03-25 10:11:58 -07:00
|
|
|
recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
|
2014-08-17 20:42:49 -07:00
|
|
|
mSession->BreakCycle();
|
2013-07-04 18:50:25 -07:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2013-11-04 14:27:39 -08:00
|
|
|
// Call mSession::Release automatically while DestroyRunnable be destroy.
|
|
|
|
nsRefPtr<Session> mSession;
|
2013-07-04 18:50:25 -07:00
|
|
|
};
|
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
friend class PushBlobRunnable;
|
|
|
|
friend class ExtractRunnable;
|
|
|
|
friend class DestroyRunnable;
|
2013-12-18 02:39:45 -08:00
|
|
|
friend class TracksAvailableCallback;
|
2013-09-26 06:40:40 -07:00
|
|
|
|
|
|
|
public:
|
|
|
|
Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
|
|
|
|
: mRecorder(aRecorder),
|
2014-01-29 07:09:43 -08:00
|
|
|
mTimeSlice(aTimeSlice),
|
2014-06-26 20:07:45 -07:00
|
|
|
mStopIssued(false),
|
|
|
|
mCanRetrieveData(false)
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
2013-09-26 06:40:40 -07:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
mEncodedBufferCache = new EncodedBufferCache(MAX_ALLOW_MEMORY_BUFFER);
|
2014-01-04 09:20:21 -08:00
|
|
|
mLastBlobTimeStamp = TimeStamp::Now();
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.Start %p", this));
|
2013-09-26 06:40:40 -07:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
SetupStreams();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Stop()
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.Stop %p", this));
|
2013-09-26 06:40:40 -07:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2014-01-29 07:09:43 -08:00
|
|
|
mStopIssued = true;
|
2013-11-04 14:27:39 -08:00
|
|
|
CleanupStreams();
|
|
|
|
nsContentUtils::UnregisterShutdownObserver(this);
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
|
|
|
|
2014-01-26 18:33:00 -08:00
|
|
|
nsresult Pause()
|
2013-09-26 06:40:40 -07:00
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.Pause"));
|
2014-02-18 20:15:53 -08:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
|
2014-06-27 09:16:27 -07:00
|
|
|
mTrackUnionStream->ChangeExplicitBlockerCount(1);
|
2014-01-26 18:33:00 -08:00
|
|
|
|
|
|
|
return NS_OK;
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
|
|
|
|
2014-01-26 18:33:00 -08:00
|
|
|
nsresult Resume()
|
2013-09-26 06:40:40 -07:00
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.Resume"));
|
2014-02-18 20:15:53 -08:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
|
2014-06-27 09:16:27 -07:00
|
|
|
mTrackUnionStream->ChangeExplicitBlockerCount(-1);
|
2014-01-26 18:33:00 -08:00
|
|
|
|
|
|
|
return NS_OK;
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
already_AddRefed<nsIDOMBlob> GetEncodedData()
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2014-01-29 07:09:43 -08:00
|
|
|
return mEncodedBufferCache->ExtractBlob(mMimeType);
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
2013-11-24 15:57:52 -08:00
|
|
|
bool IsEncoderError()
|
|
|
|
{
|
|
|
|
if (mEncoder && mEncoder->HasError()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2014-06-18 19:11:34 -07:00
|
|
|
|
2013-07-04 18:50:25 -07:00
|
|
|
private:
|
2014-06-24 09:36:43 -07:00
|
|
|
// Only DestroyRunnable is allowed to delete Session object.
|
|
|
|
virtual ~Session()
|
|
|
|
{
|
|
|
|
LOG(PR_LOG_DEBUG, ("Session.~Session (%p)", this));
|
|
|
|
CleanupStreams();
|
|
|
|
}
|
2014-06-23 23:05:49 -07:00
|
|
|
// Pull encoded media data from MediaEncoder and put into EncodedBufferCache.
|
2013-09-26 06:40:40 -07:00
|
|
|
// Destroy this session object in the end of this function.
|
2014-06-23 23:05:49 -07:00
|
|
|
// If the bool aForceFlush is true, we will force to dispatch a
|
|
|
|
// PushBlobRunnable to main thread.
|
|
|
|
void Extract(bool aForceFlush)
|
2013-09-26 06:40:40 -07:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_GetCurrentThread() == mReadThread);
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.Extract %p", this));
|
2013-09-26 06:40:40 -07:00
|
|
|
|
2014-01-04 09:20:21 -08:00
|
|
|
// Pull encoded media data from MediaEncoder
|
|
|
|
nsTArray<nsTArray<uint8_t> > encodedBuf;
|
2014-01-29 07:09:43 -08:00
|
|
|
mEncoder->GetEncodedData(&encodedBuf, mMimeType);
|
2013-09-26 06:40:40 -07:00
|
|
|
|
2014-01-04 09:20:21 -08:00
|
|
|
// Append pulled data into cache buffer.
|
|
|
|
for (uint32_t i = 0; i < encodedBuf.Length(); i++) {
|
2014-06-26 20:07:45 -07:00
|
|
|
if (!encodedBuf[i].IsEmpty()) {
|
|
|
|
mEncodedBufferCache->AppendBuffer(encodedBuf[i]);
|
|
|
|
// Fire the start event when encoded data is available.
|
|
|
|
if (!mCanRetrieveData) {
|
|
|
|
NS_DispatchToMainThread(
|
|
|
|
new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
|
|
|
|
mCanRetrieveData = true;
|
|
|
|
}
|
|
|
|
}
|
2014-01-04 09:20:21 -08:00
|
|
|
}
|
2013-09-26 06:40:40 -07:00
|
|
|
|
2014-06-23 23:05:49 -07:00
|
|
|
// Whether push encoded data back to onDataAvailable automatically or we
|
|
|
|
// need a flush.
|
|
|
|
bool pushBlob = false;
|
|
|
|
if ((mTimeSlice > 0) &&
|
|
|
|
((TimeStamp::Now()-mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice)) {
|
|
|
|
pushBlob = true;
|
|
|
|
}
|
|
|
|
if (pushBlob || aForceFlush) {
|
2014-06-26 02:22:05 -07:00
|
|
|
if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
|
|
|
|
MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
|
|
|
|
} else {
|
|
|
|
mLastBlobTimeStamp = TimeStamp::Now();
|
|
|
|
}
|
2014-01-04 09:20:21 -08:00
|
|
|
}
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Bind media source with MediaEncoder to receive raw media data.
|
|
|
|
void SetupStreams()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
2013-11-04 14:27:39 -08:00
|
|
|
// Create a Track Union Stream
|
2014-08-27 14:40:00 -07:00
|
|
|
MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
|
2013-09-26 06:40:40 -07:00
|
|
|
mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
|
|
|
|
MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
|
|
|
|
|
|
|
|
mTrackUnionStream->SetAutofinish(true);
|
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
// Bind this Track Union Stream with Source Media.
|
|
|
|
mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->GetSourceMediaStream(),
|
|
|
|
MediaInputPort::FLAG_BLOCK_OUTPUT);
|
2013-09-26 06:40:40 -07:00
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
DOMMediaStream* domStream = mRecorder->Stream();
|
|
|
|
if (domStream) {
|
|
|
|
// Get the track type hint from DOM media stream.
|
|
|
|
TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(this);
|
|
|
|
domStream->OnTracksAvailable(tracksAvailableCallback);
|
|
|
|
} else {
|
|
|
|
// Web Audio node has only audio.
|
|
|
|
InitEncoder(DOMMediaStream::HINT_CONTENTS_AUDIO);
|
|
|
|
}
|
2013-12-18 02:39:45 -08:00
|
|
|
}
|
2013-09-26 06:40:40 -07:00
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
void InitEncoder(uint8_t aTrackTypes)
|
2013-12-18 02:39:45 -08:00
|
|
|
{
|
2014-08-27 14:40:00 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.InitEncoder %p", this));
|
2013-12-18 02:39:45 -08:00
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
// Allocate encoder and bind with union stream.
|
|
|
|
// At this stage, the API doesn't allow UA to choose the output mimeType format.
|
2014-03-18 23:52:45 -07:00
|
|
|
|
|
|
|
nsCOMPtr<nsIDocument> doc = mRecorder->GetOwner()->GetExtantDoc();
|
|
|
|
uint16_t appStatus = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
|
|
|
|
if (doc) {
|
|
|
|
doc->NodePrincipal()->GetAppStatus(&appStatus);
|
|
|
|
}
|
|
|
|
// Only allow certificated application can assign AUDIO_3GPP
|
|
|
|
if (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED &&
|
|
|
|
mRecorder->mMimeType.EqualsLiteral(AUDIO_3GPP)) {
|
|
|
|
mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(AUDIO_3GPP), aTrackTypes);
|
|
|
|
} else {
|
|
|
|
mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""), aTrackTypes);
|
|
|
|
}
|
2013-12-18 02:39:45 -08:00
|
|
|
|
|
|
|
if (!mEncoder) {
|
|
|
|
DoSessionEndTask(NS_ERROR_ABORT);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-01-29 07:09:43 -08:00
|
|
|
// Media stream is ready but UA issues a stop method follow by start method.
|
|
|
|
// The Session::stop would clean the mTrackUnionStream. If the AfterTracksAdded
|
|
|
|
// comes after stop command, this function would crash.
|
|
|
|
if (!mTrackUnionStream) {
|
2013-12-18 02:39:45 -08:00
|
|
|
DoSessionEndTask(NS_OK);
|
|
|
|
return;
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
2013-12-18 02:39:45 -08:00
|
|
|
mTrackUnionStream->AddListener(mEncoder);
|
|
|
|
// Create a thread to read encode media data from MediaEncoder.
|
|
|
|
if (!mReadThread) {
|
|
|
|
nsresult rv = NS_NewNamedThread("Media Encoder", getter_AddRefs(mReadThread));
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
DoSessionEndTask(rv);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// In case source media stream does not notify track end, recieve
|
|
|
|
// shutdown notification and stop Read Thread.
|
|
|
|
nsContentUtils::RegisterShutdownObserver(this);
|
|
|
|
|
2014-08-17 20:42:49 -07:00
|
|
|
nsRefPtr<nsIRunnable> event = new ExtractRunnable(this);
|
2014-06-26 02:22:05 -07:00
|
|
|
if (NS_FAILED(mReadThread->Dispatch(event, NS_DISPATCH_NORMAL))) {
|
|
|
|
NS_WARNING("Failed to dispatch ExtractRunnable at beginning");
|
|
|
|
}
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
2013-12-18 02:39:45 -08:00
|
|
|
// application should get blob and onstop event
|
|
|
|
void DoSessionEndTask(nsresult rv)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
mRecorder->NotifyError(rv);
|
|
|
|
}
|
2014-03-25 10:11:58 -07:00
|
|
|
|
2013-12-18 02:39:45 -08:00
|
|
|
CleanupStreams();
|
2014-06-26 02:22:05 -07:00
|
|
|
if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
|
|
|
|
MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
|
|
|
|
}
|
2014-08-17 20:42:49 -07:00
|
|
|
if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(this)))) {
|
2014-06-26 02:22:05 -07:00
|
|
|
MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
|
|
|
|
}
|
2013-12-18 02:39:45 -08:00
|
|
|
}
|
2013-11-04 14:27:39 -08:00
|
|
|
void CleanupStreams()
|
|
|
|
{
|
|
|
|
if (mInputPort.get()) {
|
|
|
|
mInputPort->Destroy();
|
|
|
|
mInputPort = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mTrackUnionStream.get()) {
|
|
|
|
mTrackUnionStream->Destroy();
|
|
|
|
mTrackUnionStream = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-04 07:02:17 -08:00
|
|
|
NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
|
2013-11-04 14:27:39 -08:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("Session.Observe XPCOM_SHUTDOWN %p", this));
|
2013-11-04 14:27:39 -08:00
|
|
|
if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
|
|
|
|
// Force stop Session to terminate Read Thread.
|
2014-03-25 10:11:58 -07:00
|
|
|
mEncoder->Cancel();
|
2014-03-31 16:33:48 -07:00
|
|
|
if (mReadThread) {
|
|
|
|
mReadThread->Shutdown();
|
|
|
|
mReadThread = nullptr;
|
|
|
|
}
|
2014-08-17 20:42:49 -07:00
|
|
|
BreakCycle();
|
2013-11-04 14:27:39 -08:00
|
|
|
Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2014-08-17 20:42:49 -07:00
|
|
|
// Break the cycle reference between Session and MediaRecorder.
|
|
|
|
void BreakCycle()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mRecorder) {
|
|
|
|
mRecorder->RemoveSession(this);
|
|
|
|
mRecorder = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
private:
|
2014-06-18 19:11:34 -07:00
|
|
|
// Hold reference to MediaRecoder that ensure MediaRecorder is alive
|
|
|
|
// if there is an active session. Access ONLY on main thread.
|
|
|
|
nsRefPtr<MediaRecorder> mRecorder;
|
2013-09-26 06:40:40 -07:00
|
|
|
|
2013-11-04 14:27:39 -08:00
|
|
|
// Receive track data from source and dispatch to Encoder.
|
2013-09-26 06:40:40 -07:00
|
|
|
// Pause/ Resume controller.
|
|
|
|
nsRefPtr<ProcessedMediaStream> mTrackUnionStream;
|
|
|
|
nsRefPtr<MediaInputPort> mInputPort;
|
|
|
|
|
|
|
|
// Runnable thread for read data from MediaEncode.
|
|
|
|
nsCOMPtr<nsIThread> mReadThread;
|
|
|
|
// MediaEncoder pipeline.
|
|
|
|
nsRefPtr<MediaEncoder> mEncoder;
|
|
|
|
// A buffer to cache encoded meda data.
|
|
|
|
nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
|
2014-01-29 07:09:43 -08:00
|
|
|
// Current session mimeType
|
|
|
|
nsString mMimeType;
|
2014-01-04 09:20:21 -08:00
|
|
|
// Timestamp of the last fired dataavailable event.
|
|
|
|
TimeStamp mLastBlobTimeStamp;
|
2013-09-26 06:40:40 -07:00
|
|
|
// The interval of passing encoded data from EncodedBufferCache to onDataAvailable
|
|
|
|
// handler. "mTimeSlice < 0" means Session object does not push encoded data to
|
|
|
|
// onDataAvailable, instead, it passive wait the client side pull encoded data
|
|
|
|
// by calling requestData API.
|
|
|
|
const int32_t mTimeSlice;
|
2014-01-29 07:09:43 -08:00
|
|
|
// Indicate this session's stop has been called.
|
|
|
|
bool mStopIssued;
|
2014-06-26 20:07:45 -07:00
|
|
|
// Indicate session has encoded data. This can be changed in recording thread.
|
|
|
|
bool mCanRetrieveData;
|
2013-07-04 18:50:25 -07:00
|
|
|
};
|
|
|
|
|
2014-04-27 00:06:00 -07:00
|
|
|
NS_IMPL_ISUPPORTS(MediaRecorder::Session, nsIObserver)
|
2013-11-04 14:27:39 -08:00
|
|
|
|
2013-07-04 18:50:25 -07:00
|
|
|
MediaRecorder::~MediaRecorder()
|
|
|
|
{
|
2014-08-27 14:40:00 -07:00
|
|
|
if (mPipeStream != nullptr) {
|
|
|
|
mInputPort->Destroy();
|
|
|
|
mPipeStream->Destroy();
|
|
|
|
}
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("~MediaRecorder (%p)", this));
|
2014-06-18 19:11:34 -07:00
|
|
|
UnRegisterActivityObserver();
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
MediaRecorder::MediaRecorder(DOMMediaStream& aSourceMediaStream,
|
|
|
|
nsPIDOMWindow* aOwnerWindow)
|
|
|
|
: DOMEventTargetHelper(aOwnerWindow)
|
|
|
|
, mState(RecordingState::Inactive)
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
2014-01-06 18:53:23 -08:00
|
|
|
MOZ_ASSERT(aOwnerWindow);
|
|
|
|
MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
|
2014-08-27 14:40:00 -07:00
|
|
|
mDOMStream = &aSourceMediaStream;
|
2014-03-25 10:11:58 -07:00
|
|
|
#ifdef PR_LOGGING
|
|
|
|
if (!gMediaRecorderLog) {
|
|
|
|
gMediaRecorderLog = PR_NewLogModule("MediaRecorder");
|
|
|
|
}
|
|
|
|
#endif
|
2014-06-18 19:11:34 -07:00
|
|
|
RegisterActivityObserver();
|
|
|
|
}
|
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
MediaRecorder::MediaRecorder(AudioNode& aSrcAudioNode,
|
|
|
|
uint32_t aSrcOutput,
|
|
|
|
nsPIDOMWindow* aOwnerWindow)
|
|
|
|
: DOMEventTargetHelper(aOwnerWindow)
|
|
|
|
, mState(RecordingState::Inactive)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aOwnerWindow);
|
|
|
|
MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
|
|
|
|
|
|
|
|
// Only AudioNodeStream of kind EXTERNAL_STREAM stores output audio data in
|
|
|
|
// the track (see AudioNodeStream::AdvanceOutputSegment()). That means track
|
|
|
|
// union stream in recorder session won't be able to copy data from the
|
|
|
|
// stream of non-destination node. Create a pipe stream in this case.
|
|
|
|
if (aSrcAudioNode.NumberOfOutputs() > 0) {
|
|
|
|
AudioContext* ctx = aSrcAudioNode.Context();
|
|
|
|
AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
|
|
|
|
mPipeStream = ctx->Graph()->CreateAudioNodeStream(engine,
|
|
|
|
MediaStreamGraph::EXTERNAL_STREAM,
|
|
|
|
ctx->SampleRate());
|
|
|
|
mInputPort = mPipeStream->AllocateInputPort(aSrcAudioNode.Stream(),
|
|
|
|
MediaInputPort::FLAG_BLOCK_INPUT,
|
|
|
|
0,
|
|
|
|
aSrcOutput);
|
|
|
|
}
|
|
|
|
mAudioNode = &aSrcAudioNode;
|
|
|
|
#ifdef PR_LOGGING
|
|
|
|
if (!gMediaRecorderLog) {
|
|
|
|
gMediaRecorderLog = PR_NewLogModule("MediaRecorder");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
RegisterActivityObserver();
|
|
|
|
}
|
|
|
|
|
2014-06-18 19:11:34 -07:00
|
|
|
void
|
|
|
|
MediaRecorder::RegisterActivityObserver()
|
|
|
|
{
|
|
|
|
nsPIDOMWindow* window = GetOwner();
|
|
|
|
if (window) {
|
|
|
|
nsIDocument* doc = window->GetExtantDoc();
|
|
|
|
if (doc) {
|
|
|
|
doc->RegisterActivityObserver(
|
|
|
|
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaRecorder::UnRegisterActivityObserver()
|
|
|
|
{
|
|
|
|
nsPIDOMWindow* window = GetOwner();
|
|
|
|
if (window) {
|
|
|
|
nsIDocument* doc = window->GetExtantDoc();
|
|
|
|
if (doc) {
|
|
|
|
doc->UnregisterActivityObserver(
|
|
|
|
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
|
|
|
|
}
|
|
|
|
}
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2013-09-26 06:40:40 -07:00
|
|
|
MediaRecorder::SetMimeType(const nsString &aMimeType)
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
2013-09-26 06:40:40 -07:00
|
|
|
mMimeType = aMimeType;
|
|
|
|
}
|
2013-07-04 18:50:25 -07:00
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
void
|
|
|
|
MediaRecorder::GetMimeType(nsString &aMimeType)
|
|
|
|
{
|
|
|
|
aMimeType = mMimeType;
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("MediaRecorder.Start %p", this));
|
2013-07-04 18:50:25 -07:00
|
|
|
if (mState != RecordingState::Inactive) {
|
|
|
|
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
if (GetSourceMediaStream()->IsFinished() || GetSourceMediaStream()->IsDestroyed()) {
|
2013-08-06 23:20:42 -07:00
|
|
|
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
// Check if source media stream is valid. See bug 919051.
|
|
|
|
if (mDOMStream && !mDOMStream->GetPrincipal()) {
|
2013-09-27 02:56:16 -07:00
|
|
|
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!CheckPrincipal()) {
|
|
|
|
aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
int32_t timeSlice = 0;
|
2013-07-04 18:50:25 -07:00
|
|
|
if (aTimeSlice.WasPassed()) {
|
|
|
|
if (aTimeSlice.Value() < 0) {
|
|
|
|
aResult.Throw(NS_ERROR_INVALID_ARG);
|
|
|
|
return;
|
|
|
|
}
|
2013-07-14 00:31:59 -07:00
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
timeSlice = aTimeSlice.Value();
|
|
|
|
}
|
2013-07-14 00:31:59 -07:00
|
|
|
|
2013-09-26 06:40:40 -07:00
|
|
|
mState = RecordingState::Recording;
|
2014-06-26 02:22:05 -07:00
|
|
|
// Start a session.
|
2014-03-25 10:11:58 -07:00
|
|
|
mSessions.AppendElement();
|
2014-08-17 20:42:49 -07:00
|
|
|
mSessions.LastElement() = new Session(this, timeSlice);
|
2014-03-25 10:11:58 -07:00
|
|
|
mSessions.LastElement()->Start();
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaRecorder::Stop(ErrorResult& aResult)
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("MediaRecorder.Stop %p", this));
|
2013-07-04 18:50:25 -07:00
|
|
|
if (mState == RecordingState::Inactive) {
|
|
|
|
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
2013-12-18 02:39:45 -08:00
|
|
|
mState = RecordingState::Inactive;
|
2014-06-18 19:11:34 -07:00
|
|
|
MOZ_ASSERT(mSessions.Length() > 0);
|
|
|
|
mSessions.LastElement()->Stop();
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
2013-07-14 00:31:59 -07:00
|
|
|
void
|
|
|
|
MediaRecorder::Pause(ErrorResult& aResult)
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("MediaRecorder.Pause"));
|
2013-07-14 00:31:59 -07:00
|
|
|
if (mState != RecordingState::Recording) {
|
|
|
|
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-25 10:11:58 -07:00
|
|
|
MOZ_ASSERT(mSessions.Length() > 0);
|
|
|
|
nsresult rv = mSessions.LastElement()->Pause();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NotifyError(rv);
|
|
|
|
return;
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
2014-03-25 10:11:58 -07:00
|
|
|
mState = RecordingState::Paused;
|
2013-07-14 00:31:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaRecorder::Resume(ErrorResult& aResult)
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
LOG(PR_LOG_DEBUG, ("MediaRecorder.Resume"));
|
2013-07-14 00:31:59 -07:00
|
|
|
if (mState != RecordingState::Paused) {
|
|
|
|
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
2013-09-26 06:40:40 -07:00
|
|
|
|
2014-03-25 10:11:58 -07:00
|
|
|
MOZ_ASSERT(mSessions.Length() > 0);
|
|
|
|
nsresult rv = mSessions.LastElement()->Resume();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NotifyError(rv);
|
|
|
|
return;
|
2013-09-26 06:40:40 -07:00
|
|
|
}
|
2014-03-25 10:11:58 -07:00
|
|
|
mState = RecordingState::Recording;
|
2013-07-14 00:31:59 -07:00
|
|
|
}
|
|
|
|
|
2014-03-15 12:00:17 -07:00
|
|
|
class CreateAndDispatchBlobEventRunnable : public nsRunnable {
|
|
|
|
nsCOMPtr<nsIDOMBlob> mBlob;
|
|
|
|
nsRefPtr<MediaRecorder> mRecorder;
|
|
|
|
public:
|
|
|
|
CreateAndDispatchBlobEventRunnable(already_AddRefed<nsIDOMBlob>&& aBlob,
|
|
|
|
MediaRecorder* aRecorder)
|
|
|
|
: mBlob(aBlob), mRecorder(aRecorder)
|
|
|
|
{ }
|
|
|
|
|
|
|
|
NS_IMETHOD
|
|
|
|
Run();
|
|
|
|
};
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
CreateAndDispatchBlobEventRunnable::Run()
|
|
|
|
{
|
|
|
|
return mRecorder->CreateAndDispatchBlobEvent(mBlob.forget());
|
|
|
|
}
|
|
|
|
|
2013-07-04 18:50:25 -07:00
|
|
|
void
|
|
|
|
MediaRecorder::RequestData(ErrorResult& aResult)
|
|
|
|
{
|
|
|
|
if (mState != RecordingState::Recording) {
|
|
|
|
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
2014-06-18 19:11:34 -07:00
|
|
|
MOZ_ASSERT(mSessions.Length() > 0);
|
2014-06-26 02:22:05 -07:00
|
|
|
if (NS_FAILED(NS_DispatchToMainThread(
|
|
|
|
new CreateAndDispatchBlobEventRunnable(
|
|
|
|
mSessions.LastElement()->GetEncodedData(), this)))) {
|
|
|
|
MOZ_ASSERT(false, "NS_DispatchToMainThread CreateAndDispatchBlobEventRunnable failed");
|
|
|
|
}
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
JSObject*
|
2014-04-08 15:27:18 -07:00
|
|
|
MediaRecorder::WrapObject(JSContext* aCx)
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
Bug 991742 part 6. Remove the "aScope" argument of binding Wrap() methods. r=bholley
This patch was mostly generated with this command:
find . -name "*.h" -o -name "*.cpp" | xargs sed -e 's/Binding::Wrap(aCx, aScope, this/Binding::Wrap(aCx, this/' -e 's/Binding_workers::Wrap(aCx, aScope, this/Binding_workers::Wrap(aCx, this/' -e 's/Binding::Wrap(cx, scope, this/Binding::Wrap(cx, this/' -i ""
plus a few manual fixes to dom/bindings/Codegen.py, js/xpconnect/src/event_impl_gen.py, and a few C++ files that were not caught in the search-and-replace above.
2014-04-08 15:27:17 -07:00
|
|
|
return MediaRecorderBinding::Wrap(aCx, this);
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ already_AddRefed<MediaRecorder>
|
2013-08-22 22:17:08 -07:00
|
|
|
MediaRecorder::Constructor(const GlobalObject& aGlobal,
|
2014-03-18 23:52:45 -07:00
|
|
|
DOMMediaStream& aStream,
|
|
|
|
const MediaRecorderOptions& aInitDict,
|
|
|
|
ErrorResult& aRv)
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
2014-08-27 14:40:00 -07:00
|
|
|
nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
if (!ownerWindow) {
|
2013-07-04 18:50:25 -07:00
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
nsRefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
|
|
|
|
object->SetMimeType(aInitDict.mMimeType);
|
|
|
|
return object.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* static */ already_AddRefed<MediaRecorder>
|
|
|
|
MediaRecorder::Constructor(const GlobalObject& aGlobal,
|
|
|
|
AudioNode& aSrcAudioNode,
|
|
|
|
uint32_t aSrcOutput,
|
|
|
|
const MediaRecorderOptions& aInitDict,
|
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
// Allow recording from audio node only when pref is on.
|
|
|
|
if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
|
|
|
|
// Pretending that this constructor is not defined.
|
|
|
|
NS_NAMED_LITERAL_STRING(argStr, "Argument 1 of MediaRecorder.constructor");
|
|
|
|
NS_NAMED_LITERAL_STRING(typeStr, "MediaStream");
|
|
|
|
aRv.ThrowTypeError(MSG_DOES_NOT_IMPLEMENT_INTERFACE, &argStr, &typeStr);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2013-08-22 22:17:08 -07:00
|
|
|
nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
|
2013-07-04 18:50:25 -07:00
|
|
|
if (!ownerWindow) {
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
// aSrcOutput doesn't matter to destination node because it has no output.
|
|
|
|
if (aSrcAudioNode.NumberOfOutputs() > 0 &&
|
|
|
|
aSrcOutput >= aSrcAudioNode.NumberOfOutputs()) {
|
|
|
|
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
|
|
|
|
aSrcOutput,
|
|
|
|
ownerWindow);
|
2014-03-18 23:52:45 -07:00
|
|
|
object->SetMimeType(aInitDict.mMimeType);
|
2013-07-04 18:50:25 -07:00
|
|
|
return object.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2014-03-15 12:00:17 -07:00
|
|
|
MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
|
2013-07-04 18:50:25 -07:00
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
|
|
|
|
if (!CheckPrincipal()) {
|
|
|
|
// Media is not same-origin, don't allow the data out.
|
2014-03-25 10:11:58 -07:00
|
|
|
nsRefPtr<nsIDOMBlob> blob = aBlob;
|
2013-07-04 18:50:25 -07:00
|
|
|
return NS_ERROR_DOM_SECURITY_ERR;
|
|
|
|
}
|
2013-10-09 09:05:22 -07:00
|
|
|
BlobEventInit init;
|
2013-09-10 11:27:39 -07:00
|
|
|
init.mBubbles = false;
|
|
|
|
init.mCancelable = false;
|
2014-01-13 00:11:22 -08:00
|
|
|
init.mData = aBlob;
|
2013-09-10 11:27:39 -07:00
|
|
|
nsRefPtr<BlobEvent> event =
|
|
|
|
BlobEvent::Constructor(this,
|
|
|
|
NS_LITERAL_STRING("dataavailable"),
|
|
|
|
init);
|
2013-07-04 18:50:25 -07:00
|
|
|
event->SetTrusted(true);
|
|
|
|
return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaRecorder::DispatchSimpleEvent(const nsAString & aStr)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
|
|
|
|
nsresult rv = CheckInnerWindowCorrectness();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsCOMPtr<nsIDOMEvent> event;
|
|
|
|
rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_WARNING("Failed to create the error event!!!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
rv = event->InitEvent(aStr, false, false);
|
|
|
|
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_WARNING("Failed to init the error event!!!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
event->SetTrusted(true);
|
|
|
|
|
|
|
|
rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_ERROR("Failed to dispatch the event!!!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaRecorder::NotifyError(nsresult aRv)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
|
|
|
|
nsresult rv = CheckInnerWindowCorrectness();
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
nsString errorMsg;
|
|
|
|
switch (aRv) {
|
|
|
|
case NS_ERROR_DOM_SECURITY_ERR:
|
|
|
|
errorMsg = NS_LITERAL_STRING("SecurityError");
|
|
|
|
break;
|
|
|
|
case NS_ERROR_OUT_OF_MEMORY:
|
|
|
|
errorMsg = NS_LITERAL_STRING("OutOfMemoryError");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
errorMsg = NS_LITERAL_STRING("GenericError");
|
|
|
|
}
|
|
|
|
|
2014-06-30 16:02:02 -07:00
|
|
|
RecordErrorEventInit init;
|
|
|
|
init.mBubbles = false;
|
|
|
|
init.mCancelable = false;
|
|
|
|
init.mName = errorMsg;
|
2013-07-04 18:50:25 -07:00
|
|
|
|
2014-06-30 16:02:02 -07:00
|
|
|
nsRefPtr<RecordErrorEvent> event =
|
|
|
|
RecordErrorEvent::Constructor(this, NS_LITERAL_STRING("error"), init);
|
2013-07-04 18:50:25 -07:00
|
|
|
event->SetTrusted(true);
|
2014-06-30 16:02:02 -07:00
|
|
|
|
2013-07-04 18:50:25 -07:00
|
|
|
rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
NS_ERROR("Failed to dispatch the error event!!!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MediaRecorder::CheckPrincipal()
|
|
|
|
{
|
2014-03-25 10:11:58 -07:00
|
|
|
NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
|
2014-08-27 14:40:00 -07:00
|
|
|
if (!mDOMStream && !mAudioNode) {
|
2014-03-25 10:11:58 -07:00
|
|
|
return false;
|
|
|
|
}
|
2013-07-09 21:52:29 -07:00
|
|
|
if (!GetOwner())
|
|
|
|
return false;
|
2013-07-04 18:50:25 -07:00
|
|
|
nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
|
2014-08-27 14:40:00 -07:00
|
|
|
if (!doc) {
|
2013-07-04 18:50:25 -07:00
|
|
|
return false;
|
2014-08-27 14:40:00 -07:00
|
|
|
}
|
|
|
|
nsIPrincipal* srcPrincipal = GetSourcePrincipal();
|
|
|
|
if (!srcPrincipal) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-07-04 18:50:25 -07:00
|
|
|
bool subsumes;
|
2014-08-27 14:40:00 -07:00
|
|
|
if (NS_FAILED(doc->NodePrincipal()->Subsumes(srcPrincipal, &subsumes))) {
|
2013-07-04 18:50:25 -07:00
|
|
|
return false;
|
2014-08-27 14:40:00 -07:00
|
|
|
}
|
2013-07-04 18:50:25 -07:00
|
|
|
return subsumes;
|
|
|
|
}
|
|
|
|
|
2014-03-25 10:11:58 -07:00
|
|
|
void
|
|
|
|
MediaRecorder::RemoveSession(Session* aSession)
|
|
|
|
{
|
|
|
|
LOG(PR_LOG_DEBUG, ("MediaRecorder.RemoveSession (%p)", aSession));
|
|
|
|
mSessions.RemoveElement(aSession);
|
|
|
|
}
|
|
|
|
|
2014-06-18 19:11:34 -07:00
|
|
|
void
|
|
|
|
MediaRecorder::NotifyOwnerDocumentActivityChanged()
|
|
|
|
{
|
|
|
|
nsPIDOMWindow* window = GetOwner();
|
|
|
|
NS_ENSURE_TRUE_VOID(window);
|
|
|
|
nsIDocument* doc = window->GetExtantDoc();
|
|
|
|
NS_ENSURE_TRUE_VOID(doc);
|
|
|
|
|
|
|
|
LOG(PR_LOG_DEBUG, ("MediaRecorder %p document IsActive %d isVisible %d\n",
|
|
|
|
this, doc->IsActive(), doc->IsVisible()));
|
|
|
|
if (!doc->IsActive() || !doc->IsVisible()) {
|
|
|
|
// Stop the session.
|
|
|
|
ErrorResult result;
|
|
|
|
Stop(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-27 14:40:00 -07:00
|
|
|
MediaStream*
|
|
|
|
MediaRecorder::GetSourceMediaStream()
|
|
|
|
{
|
|
|
|
if (mDOMStream != nullptr) {
|
|
|
|
return mDOMStream->GetStream();
|
|
|
|
}
|
|
|
|
MOZ_ASSERT(mAudioNode != nullptr);
|
|
|
|
return mPipeStream != nullptr ? mPipeStream : mAudioNode->Stream();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIPrincipal*
|
|
|
|
MediaRecorder::GetSourcePrincipal()
|
|
|
|
{
|
|
|
|
if (mDOMStream != nullptr) {
|
|
|
|
return mDOMStream->GetPrincipal();
|
|
|
|
}
|
|
|
|
MOZ_ASSERT(mAudioNode != nullptr);
|
|
|
|
nsIDocument* doc = mAudioNode->GetOwner()->GetExtantDoc();
|
|
|
|
return doc ? doc->NodePrincipal() : nullptr;
|
|
|
|
}
|
|
|
|
|
2013-07-04 18:50:25 -07:00
|
|
|
}
|
|
|
|
}
|