2013-07-05 05:50:25 +04: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"
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
2014-08-28 01:40:00 +04:00
|
|
|
|
#include "AudioNodeEngine.h"
|
2019-10-02 13:23:02 +03:00
|
|
|
|
#include "AudioNodeTrack.h"
|
2014-08-28 01:40:00 +04:00
|
|
|
|
#include "DOMMediaStream.h"
|
2016-03-09 05:32:02 +03:00
|
|
|
|
#include "MediaDecoder.h"
|
2013-07-05 05:50:25 +04:00
|
|
|
|
#include "MediaEncoder.h"
|
2019-10-02 13:23:02 +03:00
|
|
|
|
#include "MediaTrackGraphImpl.h"
|
2017-12-15 23:45:35 +03:00
|
|
|
|
#include "VideoUtils.h"
|
2014-04-01 10:13:50 +04:00
|
|
|
|
#include "mozilla/DOMEventTargetHelper.h"
|
2014-08-28 01:40:00 +04:00
|
|
|
|
#include "mozilla/dom/AudioStreamTrack.h"
|
|
|
|
|
#include "mozilla/dom/BlobEvent.h"
|
2019-10-21 08:33:33 +03:00
|
|
|
|
#include "mozilla/dom/EmptyBlobImpl.h"
|
2014-10-08 20:15:23 +04:00
|
|
|
|
#include "mozilla/dom/File.h"
|
2017-08-07 00:48:42 +03:00
|
|
|
|
#include "mozilla/dom/MediaRecorderErrorEvent.h"
|
2014-08-28 01:40:00 +04:00
|
|
|
|
#include "mozilla/dom/VideoStreamTrack.h"
|
2017-05-24 19:51:47 +03:00
|
|
|
|
#include "mozilla/media/MediaUtils.h"
|
|
|
|
|
#include "mozilla/MemoryReporting.h"
|
|
|
|
|
#include "mozilla/Preferences.h"
|
|
|
|
|
#include "mozilla/StaticPtr.h"
|
|
|
|
|
#include "mozilla/TaskQueue.h"
|
|
|
|
|
#include "nsContentTypeParser.h"
|
2016-02-17 00:55:33 +03:00
|
|
|
|
#include "nsContentUtils.h"
|
2018-03-19 17:52:36 +03:00
|
|
|
|
#include "nsDocShell.h"
|
2013-07-05 05:50:25 +04:00
|
|
|
|
#include "nsError.h"
|
2019-01-02 16:05:23 +03:00
|
|
|
|
#include "mozilla/dom/Document.h"
|
2014-03-19 10:52:45 +04:00
|
|
|
|
#include "nsIPrincipal.h"
|
2016-02-01 17:47:17 +03:00
|
|
|
|
#include "nsIScriptError.h"
|
2014-03-19 10:52:45 +04:00
|
|
|
|
#include "nsMimeTypes.h"
|
2014-06-26 13:22:05 +04:00
|
|
|
|
#include "nsProxyRelease.h"
|
2020-11-23 19:21:38 +03:00
|
|
|
|
#include "nsServiceManagerUtils.h"
|
2014-08-28 01:40:00 +04:00
|
|
|
|
#include "nsTArray.h"
|
2013-12-18 14:39:45 +04:00
|
|
|
|
|
2015-11-15 16:49:01 +03:00
|
|
|
|
mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
|
2015-05-21 23:22:04 +03:00
|
|
|
|
#define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
|
2014-03-25 21:11:58 +04:00
|
|
|
|
|
2021-02-11 16:39:01 +03:00
|
|
|
|
constexpr int MIN_VIDEO_BITRATE_BPS = 10e3; // 10kbps
|
|
|
|
|
constexpr int DEFAULT_VIDEO_BITRATE_BPS = 2500e3; // 2.5Mbps
|
|
|
|
|
constexpr int MAX_VIDEO_BITRATE_BPS = 100e6; // 100Mbps
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
2021-02-11 16:39:01 +03:00
|
|
|
|
constexpr int MIN_AUDIO_BITRATE_BPS = 500; // 500bps
|
|
|
|
|
constexpr int DEFAULT_AUDIO_BITRATE_BPS = 128e3; // 128kbps
|
|
|
|
|
constexpr int MAX_AUDIO_BITRATE_BPS = 512e3; // 512kbps
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
2020-11-04 20:04:01 +03:00
|
|
|
|
namespace mozilla::dom {
|
2013-07-05 05:50:25 +04:00
|
|
|
|
|
2017-05-24 19:51:47 +03:00
|
|
|
|
using namespace mozilla::media;
|
|
|
|
|
|
2014-07-01 09:25:29 +04:00
|
|
|
|
/**
|
2017-05-24 19:51:47 +03:00
|
|
|
|
* MediaRecorderReporter measures memory being used by the Media Recorder.
|
|
|
|
|
*
|
|
|
|
|
* It is a singleton reporter and the single class object lives as long as at
|
|
|
|
|
* least one Recorder is registered. In MediaRecorder, the reporter is
|
|
|
|
|
* unregistered when it is destroyed.
|
|
|
|
|
*/
|
2015-03-21 19:28:04 +03:00
|
|
|
|
class MediaRecorderReporter final : public nsIMemoryReporter {
|
2014-07-01 09:25:29 +04:00
|
|
|
|
public:
|
|
|
|
|
static void AddMediaRecorder(MediaRecorder* aRecorder) {
|
2017-11-10 13:58:33 +03:00
|
|
|
|
if (!sUniqueInstance) {
|
|
|
|
|
sUniqueInstance = MakeAndAddRef<MediaRecorderReporter>();
|
|
|
|
|
RegisterWeakAsyncMemoryReporter(sUniqueInstance);
|
|
|
|
|
}
|
|
|
|
|
sUniqueInstance->mRecorders.AppendElement(aRecorder);
|
2014-07-01 09:25:29 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void RemoveMediaRecorder(MediaRecorder* aRecorder) {
|
2017-11-10 13:58:33 +03:00
|
|
|
|
if (!sUniqueInstance) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sUniqueInstance->mRecorders.RemoveElement(aRecorder);
|
|
|
|
|
if (sUniqueInstance->mRecorders.IsEmpty()) {
|
|
|
|
|
UnregisterWeakMemoryReporter(sUniqueInstance);
|
2014-07-01 09:25:29 +04:00
|
|
|
|
sUniqueInstance = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-10 13:58:33 +03:00
|
|
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
|
|
|
|
|
|
MediaRecorderReporter() = default;
|
|
|
|
|
|
2016-08-08 03:54:50 +03:00
|
|
|
|
NS_IMETHOD
|
2014-07-01 09:25:29 +04:00
|
|
|
|
CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
|
2015-03-21 19:28:04 +03:00
|
|
|
|
bool aAnonymize) override {
|
2017-05-24 19:51:47 +03:00
|
|
|
|
nsTArray<RefPtr<MediaRecorder::SizeOfPromise>> promises;
|
2017-11-10 13:58:33 +03:00
|
|
|
|
for (const RefPtr<MediaRecorder>& recorder : mRecorders) {
|
2017-05-24 19:51:47 +03:00
|
|
|
|
promises.AppendElement(recorder->SizeOfExcludingThis(MallocSizeOf));
|
2014-07-01 09:25:29 +04:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-24 19:51:47 +03:00
|
|
|
|
nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
|
|
|
|
|
nsCOMPtr<nsISupports> data = aData;
|
2020-06-23 08:05:36 +03:00
|
|
|
|
MediaRecorder::SizeOfPromise::All(GetCurrentSerialEventTarget(), promises)
|
2017-05-24 19:51:47 +03:00
|
|
|
|
->Then(
|
2020-06-23 08:05:36 +03:00
|
|
|
|
GetCurrentSerialEventTarget(), __func__,
|
2017-05-24 19:51:47 +03:00
|
|
|
|
[handleReport, data](const nsTArray<size_t>& sizes) {
|
2017-11-10 13:59:43 +03:00
|
|
|
|
nsCOMPtr<nsIMemoryReporterManager> manager =
|
|
|
|
|
do_GetService("@mozilla.org/memory-reporter-manager;1");
|
|
|
|
|
if (!manager) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-11-19 16:25:37 +03:00
|
|
|
|
|
2017-05-24 19:51:47 +03:00
|
|
|
|
size_t sum = 0;
|
|
|
|
|
for (const size_t& size : sizes) {
|
|
|
|
|
sum += size;
|
|
|
|
|
}
|
2018-11-19 16:25:37 +03:00
|
|
|
|
|
2020-09-23 18:17:15 +03:00
|
|
|
|
handleReport->Callback(""_ns, "explicit/media/recorder"_ns,
|
|
|
|
|
KIND_HEAP, UNITS_BYTES, sum,
|
|
|
|
|
"Memory used by media recorder."_ns, data);
|
2018-11-19 16:25:37 +03:00
|
|
|
|
|
2017-11-10 13:59:43 +03:00
|
|
|
|
manager->EndReport();
|
2017-05-24 19:51:47 +03:00
|
|
|
|
},
|
|
|
|
|
[](size_t) { MOZ_CRASH("Unexpected reject"); });
|
2014-07-01 09:25:29 +04:00
|
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
|
2017-11-10 13:58:33 +03:00
|
|
|
|
|
|
|
|
|
virtual ~MediaRecorderReporter() {
|
|
|
|
|
MOZ_ASSERT(mRecorders.IsEmpty(), "All recorders must have been removed");
|
2014-07-01 09:25:29 +04:00
|
|
|
|
}
|
2017-11-10 13:58:33 +03:00
|
|
|
|
|
|
|
|
|
static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
|
|
|
|
|
|
|
|
|
|
nsTArray<RefPtr<MediaRecorder>> mRecorders;
|
2014-07-01 09:25:29 +04:00
|
|
|
|
};
|
|
|
|
|
NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
|
|
|
|
|
|
2016-11-18 14:29:13 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
|
|
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
|
|
|
|
|
DOMEventTargetHelper)
|
2019-10-04 11:54:16 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStream)
|
2016-11-18 14:29:13 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
|
2021-03-04 22:39:27 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOtherDomException)
|
2017-08-07 00:49:24 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityDomException)
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnknownDomException)
|
2016-11-18 14:29:13 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
|
|
|
|
|
DOMEventTargetHelper)
|
2019-10-04 11:54:16 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mStream)
|
2016-11-18 14:29:13 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
|
2021-03-04 22:39:27 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOtherDomException)
|
2017-08-07 00:49:24 +03:00
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityDomException)
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnknownDomException)
|
2016-11-18 14:29:13 +03:00
|
|
|
|
tmp->UnRegisterActivityObserver();
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
2013-07-05 05:50:25 +04:00
|
|
|
|
|
2017-08-30 02:02:48 +03:00
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaRecorder)
|
2014-06-19 06:11:34 +04:00
|
|
|
|
NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
|
2014-04-01 10:13:50 +04:00
|
|
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
2013-07-05 05:50:25 +04:00
|
|
|
|
|
2014-04-01 10:13:50 +04:00
|
|
|
|
NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
|
|
|
|
|
NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
|
2013-07-05 05:50:25 +04:00
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
namespace {
|
|
|
|
|
bool PrincipalSubsumes(MediaRecorder* aRecorder, nsIPrincipal* aPrincipal) {
|
2019-10-04 11:54:16 +03:00
|
|
|
|
if (!aRecorder->GetOwner()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
nsCOMPtr<Document> doc = aRecorder->GetOwner()->GetExtantDoc();
|
|
|
|
|
if (!doc) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!aPrincipal) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
bool subsumes;
|
|
|
|
|
if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return subsumes;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
bool MediaStreamTracksPrincipalSubsumes(
|
2019-10-04 11:54:16 +03:00
|
|
|
|
MediaRecorder* aRecorder,
|
|
|
|
|
const nsTArray<RefPtr<MediaStreamTrack>>& aTracks) {
|
|
|
|
|
nsCOMPtr<nsIPrincipal> principal = nullptr;
|
|
|
|
|
for (const auto& track : aTracks) {
|
|
|
|
|
nsContentUtils::CombineResourcePrincipals(&principal,
|
|
|
|
|
track->GetPrincipal());
|
|
|
|
|
}
|
|
|
|
|
return PrincipalSubsumes(aRecorder, principal);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
bool AudioNodePrincipalSubsumes(MediaRecorder* aRecorder,
|
|
|
|
|
AudioNode* aAudioNode) {
|
2019-10-04 11:54:16 +03:00
|
|
|
|
MOZ_ASSERT(aAudioNode);
|
|
|
|
|
Document* doc =
|
|
|
|
|
aAudioNode->GetOwner() ? aAudioNode->GetOwner()->GetExtantDoc() : nullptr;
|
|
|
|
|
nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
|
|
|
|
|
return PrincipalSubsumes(aRecorder, principal);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
enum class TypeSupport {
|
|
|
|
|
Supported,
|
|
|
|
|
MediaTypeInvalid,
|
|
|
|
|
NoVideoWithAudioType,
|
|
|
|
|
ContainersDisabled,
|
|
|
|
|
CodecsDisabled,
|
|
|
|
|
ContainerUnsupported,
|
|
|
|
|
CodecUnsupported,
|
|
|
|
|
CodecDuplicated,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
nsCString TypeSupportToCString(TypeSupport aSupport,
|
|
|
|
|
const nsAString& aMimeType) {
|
|
|
|
|
nsAutoCString mime = NS_ConvertUTF16toUTF8(aMimeType);
|
|
|
|
|
switch (aSupport) {
|
|
|
|
|
case TypeSupport::Supported:
|
|
|
|
|
return nsPrintfCString("%s is supported", mime.get());
|
|
|
|
|
case TypeSupport::MediaTypeInvalid:
|
|
|
|
|
return nsPrintfCString("%s is not a valid media type", mime.get());
|
|
|
|
|
case TypeSupport::NoVideoWithAudioType:
|
|
|
|
|
return nsPrintfCString(
|
|
|
|
|
"Video cannot be recorded with %s as it is an audio type",
|
|
|
|
|
mime.get());
|
|
|
|
|
case TypeSupport::ContainersDisabled:
|
|
|
|
|
return "All containers are disabled"_ns;
|
|
|
|
|
case TypeSupport::CodecsDisabled:
|
|
|
|
|
return "All codecs are disabled"_ns;
|
|
|
|
|
case TypeSupport::ContainerUnsupported:
|
|
|
|
|
return nsPrintfCString("%s indicates an unsupported container",
|
|
|
|
|
mime.get());
|
|
|
|
|
case TypeSupport::CodecUnsupported:
|
|
|
|
|
return nsPrintfCString("%s indicates an unsupported codec", mime.get());
|
|
|
|
|
case TypeSupport::CodecDuplicated:
|
|
|
|
|
return nsPrintfCString("%s contains the same codec multiple times",
|
|
|
|
|
mime.get());
|
|
|
|
|
default:
|
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unknown TypeSupport");
|
|
|
|
|
return "Unknown error"_ns;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TypeSupport CanRecordAudioTrackWith(const Maybe<MediaContainerType>& aMimeType,
|
2019-10-04 01:09:38 +03:00
|
|
|
|
const nsAString& aMimeTypeString) {
|
|
|
|
|
if (aMimeTypeString.IsEmpty()) {
|
|
|
|
|
// For the empty string we just need to check whether we have support for an
|
|
|
|
|
// audio container and an audio codec.
|
|
|
|
|
if (!MediaEncoder::IsWebMEncoderEnabled() &&
|
|
|
|
|
!MediaDecoder::IsOggEnabled()) {
|
|
|
|
|
// No container support for audio.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::ContainersDisabled;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!MediaDecoder::IsOpusEnabled()) {
|
|
|
|
|
// No codec support for audio.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::CodecsDisabled;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::Supported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!aMimeType) {
|
|
|
|
|
// A mime type string was set, but it couldn't be parsed to a valid
|
|
|
|
|
// MediaContainerType.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::MediaTypeInvalid;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM) &&
|
|
|
|
|
aMimeType->Type() != MEDIAMIMETYPE(AUDIO_WEBM) &&
|
|
|
|
|
aMimeType->Type() != MEDIAMIMETYPE(AUDIO_OGG)) {
|
|
|
|
|
// Any currently supported container can record audio.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::ContainerUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (aMimeType->Type() == MEDIAMIMETYPE(VIDEO_WEBM) &&
|
|
|
|
|
!MediaEncoder::IsWebMEncoderEnabled()) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::ContainerUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_WEBM) &&
|
|
|
|
|
!MediaEncoder::IsWebMEncoderEnabled()) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::ContainerUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (aMimeType->Type() == MEDIAMIMETYPE(AUDIO_OGG) &&
|
|
|
|
|
!MediaDecoder::IsOggEnabled()) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::ContainerUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!MediaDecoder::IsOpusEnabled()) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::CodecUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!aMimeType->ExtendedType().HaveCodecs()) {
|
|
|
|
|
// No codecs constrained, we can pick opus.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::Supported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t opus = 0;
|
|
|
|
|
size_t unknown = 0;
|
|
|
|
|
for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
|
|
|
|
|
// Ignore video codecs.
|
|
|
|
|
if (codec.EqualsLiteral("vp8")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (codec.EqualsLiteral("vp8.0")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (codec.EqualsLiteral("opus")) {
|
|
|
|
|
// All containers support opus
|
|
|
|
|
opus++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
unknown++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unknown > 0) {
|
|
|
|
|
// Unsupported codec.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::CodecUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (opus == 0) {
|
|
|
|
|
// Codecs specified but not opus. Unsupported for audio.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::CodecUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (opus > 1) {
|
|
|
|
|
// Opus specified more than once. Bad form.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::CodecDuplicated;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::Supported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
TypeSupport CanRecordVideoTrackWith(const Maybe<MediaContainerType>& aMimeType,
|
2019-10-04 01:09:38 +03:00
|
|
|
|
const nsAString& aMimeTypeString) {
|
|
|
|
|
if (aMimeTypeString.IsEmpty()) {
|
|
|
|
|
// For the empty string we just need to check whether we have support for a
|
|
|
|
|
// video container and a video codec. The VP8 encoder is always available.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
if (!MediaEncoder::IsWebMEncoderEnabled()) {
|
|
|
|
|
// No container support for video.
|
|
|
|
|
return TypeSupport::ContainersDisabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TypeSupport::Supported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!aMimeType) {
|
|
|
|
|
// A mime type string was set, but it couldn't be parsed to a valid
|
|
|
|
|
// MediaContainerType.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::MediaTypeInvalid;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!aMimeType->Type().HasVideoMajorType()) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::NoVideoWithAudioType;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (aMimeType->Type() != MEDIAMIMETYPE(VIDEO_WEBM)) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::ContainerUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!MediaEncoder::IsWebMEncoderEnabled()) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::ContainerUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!aMimeType->ExtendedType().HaveCodecs()) {
|
|
|
|
|
// No codecs constrained, we can pick vp8.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::Supported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t vp8 = 0;
|
|
|
|
|
size_t unknown = 0;
|
|
|
|
|
for (const auto& codec : aMimeType->ExtendedType().Codecs().Range()) {
|
|
|
|
|
if (codec.EqualsLiteral("opus")) {
|
|
|
|
|
// Ignore audio codecs.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (codec.EqualsLiteral("vp8")) {
|
|
|
|
|
vp8++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (codec.EqualsLiteral("vp8.0")) {
|
|
|
|
|
vp8++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
unknown++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unknown > 0) {
|
|
|
|
|
// Unsupported codec.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::CodecUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (vp8 == 0) {
|
|
|
|
|
// Codecs specified but not vp8. Unsupported for video.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::CodecUnsupported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (vp8 > 1) {
|
|
|
|
|
// Vp8 specified more than once. Bad form.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::CodecDuplicated;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return TypeSupport::Supported;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
TypeSupport CanRecordWith(MediaStreamTrack* aTrack,
|
2019-10-04 01:09:38 +03:00
|
|
|
|
const Maybe<MediaContainerType>& aMimeType,
|
|
|
|
|
const nsAString& aMimeTypeString) {
|
|
|
|
|
if (aTrack->AsAudioStreamTrack()) {
|
|
|
|
|
return CanRecordAudioTrackWith(aMimeType, aMimeTypeString);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (aTrack->AsVideoStreamTrack()) {
|
|
|
|
|
return CanRecordVideoTrackWith(aMimeType, aMimeTypeString);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MOZ_CRASH("Unexpected track type");
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
TypeSupport IsTypeSupportedImpl(const nsAString& aMIMEType) {
|
|
|
|
|
if (aMIMEType.IsEmpty()) {
|
|
|
|
|
// Lie and return true even if no container/codec support is enabled,
|
|
|
|
|
// because the spec mandates it.
|
|
|
|
|
return TypeSupport::Supported;
|
|
|
|
|
}
|
|
|
|
|
Maybe<MediaContainerType> mime = MakeMediaContainerType(aMIMEType);
|
|
|
|
|
TypeSupport rv = CanRecordAudioTrackWith(mime, aMIMEType);
|
|
|
|
|
if (rv == TypeSupport::Supported) {
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
return CanRecordVideoTrackWith(mime, aMIMEType);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-08 02:07:07 +03:00
|
|
|
|
nsString SelectMimeType(bool aHasVideo, bool aHasAudio,
|
2019-10-04 01:10:24 +03:00
|
|
|
|
const nsString& aConstrainedMimeType) {
|
2019-11-08 02:07:07 +03:00
|
|
|
|
MOZ_ASSERT(aHasVideo || aHasAudio);
|
2019-10-04 12:06:08 +03:00
|
|
|
|
|
|
|
|
|
Maybe<MediaContainerType> constrainedType =
|
|
|
|
|
MakeMediaContainerType(aConstrainedMimeType);
|
|
|
|
|
|
2019-11-12 13:30:16 +03:00
|
|
|
|
// If we are recording video, Start() should have rejected any non-video mime
|
|
|
|
|
// types.
|
|
|
|
|
MOZ_ASSERT_IF(constrainedType && aHasVideo,
|
|
|
|
|
constrainedType->Type().HasVideoMajorType());
|
|
|
|
|
// IsTypeSupported() rejects application mime types.
|
|
|
|
|
MOZ_ASSERT_IF(constrainedType,
|
|
|
|
|
!constrainedType->Type().HasApplicationMajorType());
|
|
|
|
|
|
|
|
|
|
nsString result;
|
|
|
|
|
if (constrainedType && constrainedType->ExtendedType().HaveCodecs()) {
|
|
|
|
|
// The constrained mime type is fully defined (it has codecs!). No need to
|
|
|
|
|
// select anything.
|
2020-09-02 12:54:37 +03:00
|
|
|
|
CopyUTF8toUTF16(constrainedType->OriginalString(), result);
|
2019-11-12 13:30:16 +03:00
|
|
|
|
} else {
|
|
|
|
|
// There is no constrained mime type, or there is and it is not fully
|
|
|
|
|
// defined but still valid. Select what's missing, so that we have major
|
|
|
|
|
// type, container and codecs.
|
|
|
|
|
|
|
|
|
|
// If there is a constrained mime type it should not have codecs defined,
|
|
|
|
|
// because then it is fully defined and used unchanged (covered earlier).
|
|
|
|
|
MOZ_ASSERT_IF(constrainedType,
|
|
|
|
|
!constrainedType->ExtendedType().HaveCodecs());
|
|
|
|
|
|
|
|
|
|
nsCString majorType;
|
|
|
|
|
{
|
|
|
|
|
if (constrainedType) {
|
|
|
|
|
// There is a constrained type. It has both major type and container in
|
|
|
|
|
// order to be valid. Use them as is.
|
|
|
|
|
majorType = constrainedType->Type().AsString();
|
|
|
|
|
} else if (aHasVideo) {
|
|
|
|
|
majorType = nsLiteralCString(VIDEO_WEBM);
|
|
|
|
|
} else {
|
|
|
|
|
majorType = nsLiteralCString(AUDIO_OGG);
|
|
|
|
|
}
|
2019-10-04 12:06:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-12 13:30:16 +03:00
|
|
|
|
nsCString codecs;
|
|
|
|
|
{
|
2019-11-08 02:07:07 +03:00
|
|
|
|
if (aHasVideo && aHasAudio) {
|
2019-11-12 13:30:16 +03:00
|
|
|
|
codecs = "\"vp8, opus\""_ns;
|
2019-11-08 02:07:07 +03:00
|
|
|
|
} else if (aHasVideo) {
|
2019-11-12 13:30:16 +03:00
|
|
|
|
codecs = "vp8"_ns;
|
2019-10-04 12:06:08 +03:00
|
|
|
|
} else {
|
2019-11-12 13:30:16 +03:00
|
|
|
|
codecs = "opus"_ns;
|
2019-10-04 12:06:08 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-12 13:30:16 +03:00
|
|
|
|
result = NS_ConvertUTF8toUTF16(
|
|
|
|
|
nsPrintfCString("%s; codecs=%s", majorType.get(), codecs.get()));
|
2019-10-04 12:06:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-08 02:07:07 +03:00
|
|
|
|
MOZ_ASSERT_IF(aHasAudio,
|
2019-10-04 01:10:24 +03:00
|
|
|
|
CanRecordAudioTrackWith(MakeMediaContainerType(result),
|
|
|
|
|
result) == TypeSupport::Supported);
|
2019-11-08 02:07:07 +03:00
|
|
|
|
MOZ_ASSERT_IF(aHasVideo,
|
2019-10-04 01:10:24 +03:00
|
|
|
|
CanRecordVideoTrackWith(MakeMediaContainerType(result),
|
|
|
|
|
result) == TypeSupport::Supported);
|
2019-10-04 12:06:08 +03:00
|
|
|
|
return result;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:10:24 +03:00
|
|
|
|
void SelectBitrates(uint32_t aBitsPerSecond, uint8_t aNumVideoTracks,
|
|
|
|
|
uint32_t* aOutVideoBps, uint8_t aNumAudioTracks,
|
|
|
|
|
uint32_t* aOutAudioBps) {
|
2019-10-04 11:54:16 +03:00
|
|
|
|
uint32_t vbps = 0;
|
|
|
|
|
uint32_t abps = 0;
|
|
|
|
|
|
|
|
|
|
const uint32_t minVideoBps = MIN_VIDEO_BITRATE_BPS * aNumVideoTracks;
|
|
|
|
|
const uint32_t maxVideoBps = MAX_VIDEO_BITRATE_BPS * aNumVideoTracks;
|
|
|
|
|
|
|
|
|
|
const uint32_t minAudioBps = MIN_AUDIO_BITRATE_BPS * aNumAudioTracks;
|
|
|
|
|
const uint32_t maxAudioBps = MAX_AUDIO_BITRATE_BPS * aNumAudioTracks;
|
|
|
|
|
|
|
|
|
|
if (aNumVideoTracks == 0) {
|
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aNumAudioTracks > 0);
|
|
|
|
|
abps = std::min(maxAudioBps, std::max(minAudioBps, aBitsPerSecond));
|
|
|
|
|
} else if (aNumAudioTracks == 0) {
|
|
|
|
|
vbps = std::min(maxVideoBps, std::max(minVideoBps, aBitsPerSecond));
|
|
|
|
|
} else {
|
|
|
|
|
// Scale the bits so that video gets 20 times the bits of audio.
|
|
|
|
|
// Since we must account for varying number of tracks of each type we weight
|
|
|
|
|
// them by type; video = weight 20, audio = weight 1.
|
|
|
|
|
const uint32_t videoWeight = aNumVideoTracks * 20;
|
|
|
|
|
const uint32_t audioWeight = aNumAudioTracks;
|
|
|
|
|
const uint32_t totalWeights = audioWeight + videoWeight;
|
|
|
|
|
const uint32_t videoBitrate =
|
|
|
|
|
uint64_t(aBitsPerSecond) * videoWeight / totalWeights;
|
|
|
|
|
const uint32_t audioBitrate =
|
|
|
|
|
uint64_t(aBitsPerSecond) * audioWeight / totalWeights;
|
|
|
|
|
vbps = std::min(maxVideoBps, std::max(minVideoBps, videoBitrate));
|
|
|
|
|
abps = std::min(maxAudioBps, std::max(minAudioBps, audioBitrate));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*aOutVideoBps = vbps;
|
|
|
|
|
*aOutAudioBps = abps;
|
|
|
|
|
}
|
2019-10-04 01:10:24 +03:00
|
|
|
|
} // namespace
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
2013-09-26 17:40:40 +04:00
|
|
|
|
/**
|
|
|
|
|
* Session is an object to represent a single recording event.
|
|
|
|
|
* In original design, all recording context is stored in MediaRecorder, which
|
2017-09-29 17:11:47 +03:00
|
|
|
|
* causes a problem if someone calls MediaRecorder::Stop and
|
2013-09-26 17:40:40 +04:00
|
|
|
|
* MediaRecorder::Start quickly. To prevent blocking main thread, media encoding
|
2021-02-11 16:39:01 +03:00
|
|
|
|
* is executed in a second thread, named encoder thread. For the same reason, we
|
|
|
|
|
* do not await encoder thread shutdown in MediaRecorder::Stop.
|
|
|
|
|
* If someone calls MediaRecorder::Start before encoder thread shutdown, the
|
|
|
|
|
* same recording context in MediaRecorder might be accessed by two distinct
|
|
|
|
|
* encoder threads, which would be racy. With the recording context, including
|
|
|
|
|
* the encoder thread, in a Session object the problem is solved.
|
2013-09-26 17:40:40 +04:00
|
|
|
|
*
|
2014-06-19 06:11:34 +04:00
|
|
|
|
* Lifetime of MediaRecorder and Session objects.
|
2021-02-11 16:39:01 +03:00
|
|
|
|
* 1) MediaRecorder creates a Session in MediaRecorder::Start() and holds
|
|
|
|
|
* a reference to it. Then the Session registers itself to a ShutdownBlocker
|
|
|
|
|
* and also holds a reference to MediaRecorder.
|
2014-08-18 07:42:49 +04:00
|
|
|
|
* Therefore, the reference dependency in gecko is:
|
2017-05-24 19:51:47 +03:00
|
|
|
|
* ShutdownBlocker -> Session <-> MediaRecorder, note that there is a cycle
|
2014-08-18 07:42:49 +04:00
|
|
|
|
* reference between Session and MediaRecorder.
|
2021-02-11 16:39:01 +03:00
|
|
|
|
* 2) A Session is destroyed after Session::DoSessionEndTask() has been called
|
|
|
|
|
* _and_ all encoded media data has been passed to OnDataAvailable handler.
|
|
|
|
|
* In some cases the encoded media can be discarded before being passed to
|
|
|
|
|
* the OnDataAvailable handler.
|
|
|
|
|
* 3) Session::DoSessionEndTask is called by an application through
|
|
|
|
|
* MediaRecorder::Stop(), from a MediaEncoder Shutdown notification, from the
|
|
|
|
|
* document going inactive or invisible, or from the ShutdownBlocker.
|
2013-09-26 17:40:40 +04:00
|
|
|
|
*/
|
2017-05-24 19:51:47 +03:00
|
|
|
|
class MediaRecorder::Session : public PrincipalChangeObserver<MediaStreamTrack>,
|
2016-02-01 17:47:17 +03:00
|
|
|
|
public DOMMediaStream::TrackListener {
|
2017-05-24 19:51:47 +03:00
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Session)
|
2013-11-05 02:27:39 +04:00
|
|
|
|
|
2019-11-08 02:07:07 +03:00
|
|
|
|
struct TrackTypeComparator {
|
|
|
|
|
enum Type {
|
|
|
|
|
AUDIO,
|
|
|
|
|
VIDEO,
|
|
|
|
|
};
|
|
|
|
|
static bool Equals(const RefPtr<MediaStreamTrack>& aTrack, Type aType) {
|
|
|
|
|
return (aType == AUDIO && aTrack->AsAudioStreamTrack()) ||
|
|
|
|
|
(aType == VIDEO && aTrack->AsVideoStreamTrack());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2013-09-26 17:40:40 +04:00
|
|
|
|
public:
|
2019-10-04 11:54:16 +03:00
|
|
|
|
Session(MediaRecorder* aRecorder,
|
|
|
|
|
nsTArray<RefPtr<MediaStreamTrack>> aMediaStreamTracks,
|
2021-02-11 16:38:58 +03:00
|
|
|
|
uint32_t aVideoBitsPerSecond, uint32_t aAudioBitsPerSecond)
|
2015-04-22 10:09:49 +03:00
|
|
|
|
: mRecorder(aRecorder),
|
2019-10-04 11:54:16 +03:00
|
|
|
|
mMediaStreamTracks(std::move(aMediaStreamTracks)),
|
2019-08-03 20:27:20 +03:00
|
|
|
|
mMainThread(mRecorder->GetOwner()->EventTargetFor(TaskCategory::Other)),
|
2019-11-08 02:07:07 +03:00
|
|
|
|
mMimeType(SelectMimeType(
|
|
|
|
|
mMediaStreamTracks.Contains(TrackTypeComparator::VIDEO,
|
|
|
|
|
TrackTypeComparator()),
|
|
|
|
|
mRecorder->mAudioNode ||
|
|
|
|
|
mMediaStreamTracks.Contains(TrackTypeComparator::AUDIO,
|
|
|
|
|
TrackTypeComparator()),
|
|
|
|
|
mRecorder->mConstrainedMimeType)),
|
2019-10-04 11:54:16 +03:00
|
|
|
|
mVideoBitsPerSecond(aVideoBitsPerSecond),
|
|
|
|
|
mAudioBitsPerSecond(aAudioBitsPerSecond),
|
2019-08-03 20:27:15 +03:00
|
|
|
|
mStartTime(TimeStamp::Now()),
|
2017-10-31 20:34:23 +03:00
|
|
|
|
mRunningState(RunningState::Idling) {
|
2013-09-26 17:40:40 +04:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2019-08-03 20:27:15 +03:00
|
|
|
|
Telemetry::ScalarAdd(Telemetry::ScalarID::MEDIARECORDER_RECORDING_COUNT, 1);
|
2013-09-26 17:40:40 +04:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-01 17:47:17 +03:00
|
|
|
|
void PrincipalChanged(MediaStreamTrack* aTrack) override {
|
|
|
|
|
NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
|
|
|
|
|
"Principal changed for unrecorded track");
|
2019-10-04 11:54:16 +03:00
|
|
|
|
if (!MediaStreamTracksPrincipalSubsumes(mRecorder, mMediaStreamTracks)) {
|
2016-02-01 17:47:17 +03:00
|
|
|
|
DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
|
|
|
|
|
LOG(LogLevel::Warning,
|
|
|
|
|
("Session.NotifyTrackAdded %p Raising error due to track set change",
|
|
|
|
|
this));
|
2020-11-05 22:57:19 +03:00
|
|
|
|
// There's a chance we have a sensible JS stack here.
|
2021-03-04 22:39:27 +03:00
|
|
|
|
if (!mRecorder->mOtherDomException) {
|
|
|
|
|
mRecorder->mOtherDomException = DOMException::Create(
|
2020-11-05 22:57:19 +03:00
|
|
|
|
NS_ERROR_DOM_INVALID_MODIFICATION_ERR,
|
|
|
|
|
"An attempt was made to add a track to the recorded MediaStream "
|
|
|
|
|
"during the recording"_ns);
|
|
|
|
|
}
|
|
|
|
|
DoSessionEndTask(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
|
2016-02-01 17:47:17 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
|
2017-05-24 19:51:47 +03:00
|
|
|
|
if (aTrack->Ended()) {
|
|
|
|
|
// TrackEncoder will pickup tracks that end itself.
|
|
|
|
|
return;
|
2016-02-01 17:47:17 +03:00
|
|
|
|
}
|
|
|
|
|
LOG(LogLevel::Warning,
|
|
|
|
|
("Session.NotifyTrackRemoved %p Raising error due to track set change",
|
|
|
|
|
this));
|
2020-11-05 22:57:19 +03:00
|
|
|
|
// There's a chance we have a sensible JS stack here.
|
2021-03-04 22:39:27 +03:00
|
|
|
|
if (!mRecorder->mOtherDomException) {
|
|
|
|
|
mRecorder->mOtherDomException = DOMException::Create(
|
2020-11-05 22:57:19 +03:00
|
|
|
|
NS_ERROR_DOM_INVALID_MODIFICATION_ERR,
|
|
|
|
|
"An attempt was made to remove a track from the recorded MediaStream "
|
|
|
|
|
"during the recording"_ns);
|
|
|
|
|
}
|
|
|
|
|
DoSessionEndTask(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
|
2016-02-01 17:47:17 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 16:38:58 +03:00
|
|
|
|
void Start(TimeDuration aTimeslice) {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session.Start %p", this));
|
2013-09-26 17:40:40 +04:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
if (mRecorder->mStream) {
|
|
|
|
|
// The TrackListener reports back when tracks are added or removed from
|
|
|
|
|
// the MediaStream.
|
|
|
|
|
mMediaStream = mRecorder->mStream;
|
2018-11-23 18:02:35 +03:00
|
|
|
|
mMediaStream->RegisterTrackListener(this);
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
|
|
|
|
uint8_t trackTypes = 0;
|
|
|
|
|
int32_t audioTracks = 0;
|
|
|
|
|
int32_t videoTracks = 0;
|
|
|
|
|
for (const auto& track : mMediaStreamTracks) {
|
|
|
|
|
if (track->AsAudioStreamTrack()) {
|
|
|
|
|
++audioTracks;
|
|
|
|
|
trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
|
|
|
|
|
} else if (track->AsVideoStreamTrack()) {
|
|
|
|
|
++videoTracks;
|
|
|
|
|
trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
|
|
|
|
|
} else {
|
|
|
|
|
MOZ_CRASH("Unexpected track type");
|
|
|
|
|
}
|
2018-11-23 18:02:35 +03:00
|
|
|
|
}
|
2017-02-24 16:34:25 +03:00
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
for (const auto& t : mMediaStreamTracks) {
|
|
|
|
|
t->AddPrincipalChangeObserver(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG(LogLevel::Debug, ("Session.Start track types = (%d)", trackTypes));
|
2021-02-11 16:38:58 +03:00
|
|
|
|
InitEncoder(trackTypes, mMediaStreamTracks[0]->Graph()->GraphRate(),
|
|
|
|
|
aTimeslice);
|
2019-10-04 11:54:16 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mRecorder->mAudioNode) {
|
2017-05-24 19:51:47 +03:00
|
|
|
|
TrackRate trackRate =
|
|
|
|
|
mRecorder->mAudioNode->Context()->Graph()->GraphRate();
|
2016-02-01 17:47:17 +03:00
|
|
|
|
|
2016-01-05 05:16:29 +03:00
|
|
|
|
// Web Audio node has only audio.
|
2021-02-11 16:38:58 +03:00
|
|
|
|
InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate, aTimeslice);
|
2017-02-24 16:34:25 +03:00
|
|
|
|
return;
|
2016-01-05 05:16:29 +03:00
|
|
|
|
}
|
2017-02-24 16:34:25 +03:00
|
|
|
|
|
|
|
|
|
MOZ_ASSERT(false, "Unknown source");
|
2013-09-26 17:40:40 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Stop() {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session.Stop %p", this));
|
2013-09-26 17:40:40 +04:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
|
|
|
|
if (mEncoder) {
|
2021-02-11 16:38:55 +03:00
|
|
|
|
mEncoder->DisconnectTracks();
|
2017-05-24 19:51:47 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-30 21:39:21 +03:00
|
|
|
|
// Remove main thread state added in Start().
|
|
|
|
|
if (mMediaStream) {
|
|
|
|
|
mMediaStream->UnregisterTrackListener(this);
|
|
|
|
|
mMediaStream = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2019-10-04 11:54:16 +03:00
|
|
|
|
for (const auto& track : mMediaStreamTracks) {
|
2019-04-30 21:39:21 +03:00
|
|
|
|
track->RemovePrincipalChangeObserver(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-31 20:34:23 +03:00
|
|
|
|
if (mRunningState.isOk() &&
|
2019-08-13 11:26:18 +03:00
|
|
|
|
mRunningState.inspect() == RunningState::Idling) {
|
2017-10-31 20:34:23 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session.Stop Explicit end task %p", this));
|
2019-08-03 20:27:15 +03:00
|
|
|
|
// End the Session directly if there is no encoder.
|
2015-04-22 10:09:49 +03:00
|
|
|
|
DoSessionEndTask(NS_OK);
|
2017-10-31 20:34:23 +03:00
|
|
|
|
} else if (mRunningState.isOk() &&
|
2019-08-13 11:26:18 +03:00
|
|
|
|
(mRunningState.inspect() == RunningState::Starting ||
|
|
|
|
|
mRunningState.inspect() == RunningState::Running)) {
|
2021-02-11 16:38:53 +03:00
|
|
|
|
if (mRunningState.inspect() == RunningState::Starting) {
|
|
|
|
|
// The MediaEncoder might not report started, but by spec we must fire
|
|
|
|
|
// "start".
|
2021-02-11 16:39:00 +03:00
|
|
|
|
mStartedListener.DisconnectIfExists();
|
|
|
|
|
NS_DispatchToMainThread(NewRunnableMethod(
|
|
|
|
|
"MediaRecorder::Session::Stop", this, &Session::OnStarted));
|
2021-02-11 16:38:53 +03:00
|
|
|
|
}
|
2017-10-31 20:34:23 +03:00
|
|
|
|
mRunningState = RunningState::Stopping;
|
2015-04-22 10:09:49 +03:00
|
|
|
|
}
|
2013-09-26 17:40:40 +04:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:09:07 +03:00
|
|
|
|
void Pause() {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session.Pause"));
|
2014-02-19 08:15:53 +04:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2019-10-04 01:09:07 +03:00
|
|
|
|
MOZ_ASSERT_IF(mRunningState.isOk(),
|
|
|
|
|
mRunningState.unwrap() != RunningState::Idling);
|
|
|
|
|
if (mRunningState.isErr() ||
|
|
|
|
|
mRunningState.unwrap() == RunningState::Stopping ||
|
|
|
|
|
mRunningState.unwrap() == RunningState::Stopped) {
|
|
|
|
|
return;
|
2016-03-19 08:04:50 +03:00
|
|
|
|
}
|
2019-10-04 01:09:07 +03:00
|
|
|
|
MOZ_ASSERT(mEncoder);
|
2019-03-22 14:42:55 +03:00
|
|
|
|
mEncoder->Suspend();
|
2013-09-26 17:40:40 +04:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:09:07 +03:00
|
|
|
|
void Resume() {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session.Resume"));
|
2014-02-19 08:15:53 +04:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2019-10-04 01:09:07 +03:00
|
|
|
|
MOZ_ASSERT_IF(mRunningState.isOk(),
|
|
|
|
|
mRunningState.unwrap() != RunningState::Idling);
|
|
|
|
|
if (mRunningState.isErr() ||
|
|
|
|
|
mRunningState.unwrap() == RunningState::Stopping ||
|
|
|
|
|
mRunningState.unwrap() == RunningState::Stopped) {
|
|
|
|
|
return;
|
2016-03-19 08:04:50 +03:00
|
|
|
|
}
|
2019-10-04 01:09:07 +03:00
|
|
|
|
MOZ_ASSERT(mEncoder);
|
2019-03-22 14:42:55 +03:00
|
|
|
|
mEncoder->Resume();
|
2013-09-26 17:40:40 +04:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-03 20:27:20 +03:00
|
|
|
|
void RequestData() {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session.RequestData"));
|
2014-10-17 13:26:11 +04:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2021-02-11 16:38:58 +03:00
|
|
|
|
MOZ_ASSERT(mEncoder);
|
2014-10-17 13:26:11 +04:00
|
|
|
|
|
2021-02-11 16:38:58 +03:00
|
|
|
|
InvokeAsync(mEncoderThread, mEncoder.get(), __func__,
|
2021-02-11 16:38:58 +03:00
|
|
|
|
&MediaEncoder::RequestData)
|
2021-02-11 16:38:56 +03:00
|
|
|
|
->Then(
|
|
|
|
|
mMainThread, __func__,
|
|
|
|
|
[this, self = RefPtr<Session>(this)](
|
2021-02-11 16:38:58 +03:00
|
|
|
|
const MediaEncoder::BlobPromise::ResolveOrRejectValue& aRrv) {
|
|
|
|
|
if (aRrv.IsReject()) {
|
2021-02-11 16:38:58 +03:00
|
|
|
|
LOG(LogLevel::Warning, ("RequestData failed"));
|
2021-02-11 16:38:58 +03:00
|
|
|
|
DoSessionEndTask(aRrv.RejectValue());
|
2021-02-11 16:38:56 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-08-03 20:27:20 +03:00
|
|
|
|
|
2021-02-11 16:38:56 +03:00
|
|
|
|
nsresult rv =
|
2021-02-11 16:38:58 +03:00
|
|
|
|
mRecorder->CreateAndDispatchBlobEvent(aRrv.ResolveValue());
|
2021-02-11 16:38:56 +03:00
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
|
DoSessionEndTask(NS_OK);
|
|
|
|
|
}
|
|
|
|
|
});
|
2014-10-17 13:26:11 +04:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-19 16:54:59 +03:00
|
|
|
|
public:
|
2017-05-24 19:51:47 +03:00
|
|
|
|
RefPtr<SizeOfPromise> SizeOfExcludingThis(
|
|
|
|
|
mozilla::MallocSizeOf aMallocSizeOf) {
|
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
if (!mEncoder) {
|
2021-02-11 16:38:58 +03:00
|
|
|
|
return SizeOfPromise::CreateAndResolve(0, __func__);
|
2013-11-25 03:57:52 +04:00
|
|
|
|
}
|
2014-06-19 06:11:34 +04:00
|
|
|
|
|
2021-02-11 16:38:58 +03:00
|
|
|
|
return mEncoder->SizeOfExcludingThis(aMallocSizeOf);
|
2014-07-01 09:25:29 +04:00
|
|
|
|
}
|
|
|
|
|
|
2013-07-05 05:50:25 +04:00
|
|
|
|
private:
|
2014-06-24 20:36:43 +04:00
|
|
|
|
virtual ~Session() {
|
2017-05-24 19:51:47 +03:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
MOZ_ASSERT(mShutdownPromise);
|
2019-11-08 02:07:07 +03:00
|
|
|
|
MOZ_ASSERT(!mShutdownBlocker);
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
|
2014-06-24 20:36:43 +04:00
|
|
|
|
}
|
2019-08-03 20:27:20 +03:00
|
|
|
|
|
2021-02-11 16:38:58 +03:00
|
|
|
|
void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate,
|
|
|
|
|
TimeDuration aTimeslice) {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
|
2013-12-18 14:39:45 +04:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
2017-10-31 20:34:23 +03:00
|
|
|
|
if (!mRunningState.isOk() ||
|
2019-08-13 11:26:18 +03:00
|
|
|
|
mRunningState.inspect() != RunningState::Idling) {
|
2017-10-31 20:34:23 +03:00
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Double-init");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-24 19:51:47 +03:00
|
|
|
|
// Create a TaskQueue to read encode media data from MediaEncoder.
|
|
|
|
|
MOZ_RELEASE_ASSERT(!mEncoderThread);
|
|
|
|
|
RefPtr<SharedThreadPool> pool =
|
2017-12-15 23:45:35 +03:00
|
|
|
|
GetMediaThreadPool(MediaThreadType::WEBRTC_DECODER);
|
2017-05-24 19:51:47 +03:00
|
|
|
|
if (!pool) {
|
|
|
|
|
LOG(LogLevel::Debug, ("Session.InitEncoder %p Failed to create "
|
|
|
|
|
"MediaRecorderReadThread thread pool",
|
|
|
|
|
this));
|
|
|
|
|
DoSessionEndTask(NS_ERROR_FAILURE);
|
2015-04-22 10:09:49 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
2017-12-15 23:45:35 +03:00
|
|
|
|
mEncoderThread =
|
|
|
|
|
MakeAndAddRef<TaskQueue>(pool.forget(), "MediaRecorderReadThread");
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
2019-10-17 12:31:21 +03:00
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!mShutdownBlocker);
|
|
|
|
|
// Add a shutdown blocker so mEncoderThread can be shutdown async.
|
|
|
|
|
class Blocker : public ShutdownBlocker {
|
|
|
|
|
const RefPtr<Session> mSession;
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
2019-10-17 12:31:21 +03:00
|
|
|
|
public:
|
|
|
|
|
Blocker(RefPtr<Session> aSession, const nsString& aName)
|
|
|
|
|
: ShutdownBlocker(aName), mSession(std::move(aSession)) {}
|
|
|
|
|
|
|
|
|
|
NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient*) override {
|
2021-02-11 16:38:56 +03:00
|
|
|
|
mSession->DoSessionEndTask(NS_ERROR_ABORT);
|
2019-10-17 12:31:21 +03:00
|
|
|
|
return NS_OK;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
nsString name;
|
|
|
|
|
name.AppendPrintf("MediaRecorder::Session %p shutdown", this);
|
|
|
|
|
mShutdownBlocker = MakeAndAddRef<Blocker>(this, name);
|
|
|
|
|
nsresult rv = GetShutdownBarrier()->AddBlocker(
|
|
|
|
|
mShutdownBlocker, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
|
|
|
|
|
u"MediaRecorder::Session: shutdown"_ns);
|
|
|
|
|
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
2021-02-11 16:38:58 +03:00
|
|
|
|
uint32_t maxMemory = Preferences::GetUint("media.recorder.max_memory",
|
|
|
|
|
MAX_ALLOW_MEMORY_BUFFER);
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
mEncoder = MediaEncoder::CreateEncoder(
|
|
|
|
|
mEncoderThread, mMimeType, mAudioBitsPerSecond, mVideoBitsPerSecond,
|
2021-02-11 16:38:58 +03:00
|
|
|
|
aTrackTypes, aTrackRate, maxMemory, aTimeslice);
|
2013-12-18 14:39:45 +04:00
|
|
|
|
|
|
|
|
|
if (!mEncoder) {
|
2017-05-24 19:51:47 +03:00
|
|
|
|
LOG(LogLevel::Error, ("Session.InitEncoder !mEncoder %p", this));
|
2013-12-18 14:39:45 +04:00
|
|
|
|
DoSessionEndTask(NS_ERROR_ABORT);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 16:39:00 +03:00
|
|
|
|
mStartedListener = mEncoder->StartedEvent().Connect(mMainThread, this,
|
|
|
|
|
&Session::OnStarted);
|
2021-02-11 16:38:59 +03:00
|
|
|
|
mDataAvailableListener = mEncoder->DataAvailableEvent().Connect(
|
|
|
|
|
mMainThread, this, &Session::OnDataAvailable);
|
2021-02-11 16:39:00 +03:00
|
|
|
|
mErrorListener =
|
|
|
|
|
mEncoder->ErrorEvent().Connect(mMainThread, this, &Session::OnError);
|
|
|
|
|
mShutdownListener = mEncoder->ShutdownEvent().Connect(mMainThread, this,
|
|
|
|
|
&Session::OnShutdown);
|
2021-02-11 16:38:59 +03:00
|
|
|
|
|
2017-05-24 19:51:47 +03:00
|
|
|
|
if (mRecorder->mAudioNode) {
|
|
|
|
|
mEncoder->ConnectAudioNode(mRecorder->mAudioNode,
|
|
|
|
|
mRecorder->mAudioNodeOutput);
|
2016-06-15 18:48:44 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
for (const auto& track : mMediaStreamTracks) {
|
2017-05-24 19:51:47 +03:00
|
|
|
|
mEncoder->ConnectMediaStreamTrack(track);
|
2013-12-18 14:39:45 +04:00
|
|
|
|
}
|
|
|
|
|
|
2019-08-03 20:27:15 +03:00
|
|
|
|
// Set mRunningState to Running so that DoSessionEndTask will
|
2017-10-31 20:34:23 +03:00
|
|
|
|
// take the responsibility to end the session.
|
|
|
|
|
mRunningState = RunningState::Starting;
|
2013-09-26 17:40:40 +04:00
|
|
|
|
}
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
2019-08-03 20:27:15 +03:00
|
|
|
|
// This is the task that will stop recording per spec:
|
2021-02-11 16:38:56 +03:00
|
|
|
|
// - If rv is NS_ERROR_ABORT or NS_ERROR_DOM_SECURITY_ERR, cancel the encoders
|
|
|
|
|
// - Otherwise, stop the encoders gracefully, this still encodes buffered data
|
2019-08-03 20:27:15 +03:00
|
|
|
|
// - Set state to "inactive"
|
|
|
|
|
// - Fire an error event, if NS_FAILED(rv)
|
|
|
|
|
// - Discard blob data if rv is NS_ERROR_DOM_SECURITY_ERR
|
|
|
|
|
// - Fire a Blob event
|
|
|
|
|
// - Fire an event named stop
|
2013-12-18 14:39:45 +04:00
|
|
|
|
void DoSessionEndTask(nsresult rv) {
|
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2017-10-31 20:34:23 +03:00
|
|
|
|
if (mRunningState.isErr()) {
|
|
|
|
|
// We have already ended with an error.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mRunningState.isOk() &&
|
2019-08-13 11:26:18 +03:00
|
|
|
|
mRunningState.inspect() == RunningState::Stopped) {
|
2017-10-31 20:34:23 +03:00
|
|
|
|
// We have already ended gracefully.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-03 20:27:15 +03:00
|
|
|
|
bool needsStartEvent = false;
|
2017-10-31 20:34:23 +03:00
|
|
|
|
if (mRunningState.isOk() &&
|
2019-08-13 11:26:18 +03:00
|
|
|
|
(mRunningState.inspect() == RunningState::Idling ||
|
|
|
|
|
mRunningState.inspect() == RunningState::Starting)) {
|
2019-08-03 20:27:15 +03:00
|
|
|
|
needsStartEvent = true;
|
2017-10-31 20:34:23 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 16:38:56 +03:00
|
|
|
|
// Set a terminated running state. Future DoSessionEnd tasks will exit
|
|
|
|
|
// early.
|
2017-10-31 20:34:23 +03:00
|
|
|
|
if (rv == NS_OK) {
|
|
|
|
|
mRunningState = RunningState::Stopped;
|
|
|
|
|
} else {
|
|
|
|
|
mRunningState = Err(rv);
|
2017-09-03 23:28:01 +03:00
|
|
|
|
}
|
2015-08-27 10:35:30 +03:00
|
|
|
|
|
2021-03-04 22:39:27 +03:00
|
|
|
|
RefPtr<MediaEncoder::BlobPromise> blobPromise;
|
|
|
|
|
if (!mEncoder) {
|
|
|
|
|
blobPromise = MediaEncoder::BlobPromise::CreateAndReject(NS_OK, __func__);
|
|
|
|
|
} else {
|
|
|
|
|
blobPromise =
|
|
|
|
|
(rv == NS_ERROR_ABORT || rv == NS_ERROR_DOM_SECURITY_ERR
|
|
|
|
|
? mEncoder->Cancel()
|
|
|
|
|
: mEncoder->Stop())
|
|
|
|
|
->Then(mEncoderThread, __func__,
|
|
|
|
|
[encoder = mEncoder](
|
|
|
|
|
const GenericNonExclusivePromise::ResolveOrRejectValue&
|
|
|
|
|
aValue) {
|
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aValue.IsResolve());
|
|
|
|
|
return encoder->RequestData();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
blobPromise
|
2019-10-21 08:33:33 +03:00
|
|
|
|
->Then(
|
|
|
|
|
mMainThread, __func__,
|
2021-03-04 22:39:27 +03:00
|
|
|
|
[this, self = RefPtr<Session>(this), rv, needsStartEvent](
|
|
|
|
|
const MediaEncoder::BlobPromise::ResolveOrRejectValue& aRv) {
|
2019-10-21 08:33:33 +03:00
|
|
|
|
if (mRecorder->mSessions.LastElement() == this) {
|
|
|
|
|
// Set state to inactive, but only if the recorder is not
|
|
|
|
|
// controlled by another session already.
|
|
|
|
|
mRecorder->Inactivate();
|
|
|
|
|
}
|
2019-08-03 20:27:15 +03:00
|
|
|
|
|
2019-10-21 08:33:33 +03:00
|
|
|
|
if (needsStartEvent) {
|
|
|
|
|
mRecorder->DispatchSimpleEvent(u"start"_ns);
|
|
|
|
|
}
|
2019-08-03 20:27:15 +03:00
|
|
|
|
|
2019-10-21 08:33:33 +03:00
|
|
|
|
// If there was an error, Fire the appropriate one
|
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
|
mRecorder->NotifyError(rv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fire a blob event named dataavailable
|
|
|
|
|
RefPtr<BlobImpl> blobImpl;
|
2021-03-04 22:39:27 +03:00
|
|
|
|
if (rv == NS_ERROR_DOM_SECURITY_ERR || aRv.IsReject()) {
|
2019-10-21 08:33:33 +03:00
|
|
|
|
// In case of SecurityError, the blob data must be discarded.
|
|
|
|
|
// We create a new empty one and throw the blob with its data
|
|
|
|
|
// away.
|
|
|
|
|
// In case we failed to gather blob data, we create an empty
|
|
|
|
|
// memory blob instead.
|
|
|
|
|
blobImpl = new EmptyBlobImpl(mMimeType);
|
|
|
|
|
} else {
|
2021-03-04 22:39:27 +03:00
|
|
|
|
blobImpl = aRv.ResolveValue();
|
2019-10-21 08:33:33 +03:00
|
|
|
|
}
|
|
|
|
|
if (NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(blobImpl))) {
|
|
|
|
|
// Failed to dispatch blob event. That's unexpected. It's
|
|
|
|
|
// probably all right to fire an error event if we haven't
|
|
|
|
|
// already.
|
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
|
|
|
mRecorder->NotifyError(NS_ERROR_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fire an event named stop
|
|
|
|
|
mRecorder->DispatchSimpleEvent(u"stop"_ns);
|
|
|
|
|
|
|
|
|
|
// And finally, Shutdown and destroy the Session
|
|
|
|
|
return Shutdown();
|
|
|
|
|
})
|
2019-08-03 20:27:15 +03:00
|
|
|
|
->Then(mMainThread, __func__, [this, self = RefPtr<Session>(this)] {
|
2019-10-17 12:31:21 +03:00
|
|
|
|
GetShutdownBarrier()->RemoveBlocker(mShutdownBlocker);
|
|
|
|
|
mShutdownBlocker = nullptr;
|
2019-08-03 20:27:15 +03:00
|
|
|
|
});
|
2013-12-18 14:39:45 +04:00
|
|
|
|
}
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
2021-02-11 16:39:00 +03:00
|
|
|
|
void OnStarted() {
|
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
if (mRunningState.isErr()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
RunningState state = mRunningState.inspect();
|
|
|
|
|
if (state == RunningState::Starting || state == RunningState::Stopping) {
|
|
|
|
|
if (state == RunningState::Starting) {
|
|
|
|
|
// We set it to Running in the runnable since we can only assign
|
|
|
|
|
// mRunningState on main thread. We set it before running the start
|
|
|
|
|
// event runnable since that dispatches synchronously (and may cause
|
|
|
|
|
// js calls to methods depending on mRunningState).
|
|
|
|
|
mRunningState = RunningState::Running;
|
|
|
|
|
|
|
|
|
|
mRecorder->mMimeType = mEncoder->mMimeType;
|
2017-10-31 20:34:23 +03:00
|
|
|
|
}
|
2021-02-11 16:39:00 +03:00
|
|
|
|
mRecorder->DispatchSimpleEvent(u"start"_ns);
|
|
|
|
|
}
|
2017-05-24 19:51:47 +03:00
|
|
|
|
}
|
2017-04-07 17:50:55 +03:00
|
|
|
|
|
2021-02-11 16:38:59 +03:00
|
|
|
|
void OnDataAvailable(const RefPtr<BlobImpl>& aBlob) {
|
|
|
|
|
if (mRunningState.isErr() &&
|
|
|
|
|
mRunningState.unwrapErr() == NS_ERROR_DOM_SECURITY_ERR) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (NS_WARN_IF(NS_FAILED(mRecorder->CreateAndDispatchBlobEvent(aBlob)))) {
|
|
|
|
|
LOG(LogLevel::Warning,
|
|
|
|
|
("MediaRecorder %p Creating or dispatching BlobEvent failed", this));
|
|
|
|
|
DoSessionEndTask(NS_OK);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 16:39:00 +03:00
|
|
|
|
void OnError() {
|
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
DoSessionEndTask(NS_ERROR_FAILURE);
|
2017-05-24 19:51:47 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 16:39:00 +03:00
|
|
|
|
void OnShutdown() {
|
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
DoSessionEndTask(NS_OK);
|
2013-11-05 02:27:39 +04:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-24 19:51:47 +03:00
|
|
|
|
RefPtr<ShutdownPromise> Shutdown() {
|
2014-08-18 07:42:49 +04:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2017-05-24 19:51:47 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("Session Shutdown %p", this));
|
|
|
|
|
|
|
|
|
|
if (mShutdownPromise) {
|
|
|
|
|
return mShutdownPromise;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-03 20:27:15 +03:00
|
|
|
|
// This is a coarse calculation and does not reflect the duration of the
|
|
|
|
|
// final recording for reasons such as pauses. However it allows us an
|
|
|
|
|
// idea of how long people are running their recorders for.
|
|
|
|
|
TimeDuration timeDelta = TimeStamp::Now() - mStartTime;
|
|
|
|
|
Telemetry::Accumulate(Telemetry::MEDIA_RECORDER_RECORDING_DURATION,
|
|
|
|
|
timeDelta.ToSeconds());
|
|
|
|
|
|
2017-05-24 19:51:47 +03:00
|
|
|
|
mShutdownPromise = ShutdownPromise::CreateAndResolve(true, __func__);
|
|
|
|
|
|
|
|
|
|
if (mEncoder) {
|
2019-10-17 12:31:07 +03:00
|
|
|
|
mShutdownPromise =
|
|
|
|
|
mShutdownPromise
|
|
|
|
|
->Then(mMainThread, __func__,
|
2021-02-11 16:38:59 +03:00
|
|
|
|
[this, self = RefPtr<Session>(this)] {
|
2021-02-11 16:39:00 +03:00
|
|
|
|
mStartedListener.DisconnectIfExists();
|
2021-02-11 16:38:59 +03:00
|
|
|
|
mDataAvailableListener.DisconnectIfExists();
|
2021-02-11 16:39:00 +03:00
|
|
|
|
mErrorListener.DisconnectIfExists();
|
|
|
|
|
mShutdownListener.DisconnectIfExists();
|
2021-02-11 16:38:59 +03:00
|
|
|
|
return mEncoder->Cancel();
|
|
|
|
|
})
|
2019-10-17 12:31:07 +03:00
|
|
|
|
->Then(mEncoderThread, __func__, [] {
|
|
|
|
|
// Meh, this is just to convert the promise type to match
|
|
|
|
|
// mShutdownPromise.
|
|
|
|
|
return ShutdownPromise::CreateAndResolve(true, __func__);
|
|
|
|
|
});
|
2017-05-24 19:51:47 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-30 21:39:21 +03:00
|
|
|
|
// Remove main thread state. This could be needed if Stop() wasn't called.
|
2017-05-24 19:51:47 +03:00
|
|
|
|
if (mMediaStream) {
|
|
|
|
|
mMediaStream->UnregisterTrackListener(this);
|
|
|
|
|
mMediaStream = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2018-05-30 22:15:35 +03:00
|
|
|
|
auto tracks(std::move(mMediaStreamTracks));
|
2017-05-24 19:51:47 +03:00
|
|
|
|
for (RefPtr<MediaStreamTrack>& track : tracks) {
|
|
|
|
|
track->RemovePrincipalChangeObserver(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Break the cycle reference between Session and MediaRecorder.
|
2019-08-03 20:27:15 +03:00
|
|
|
|
mShutdownPromise = mShutdownPromise->Then(
|
2019-10-17 12:31:07 +03:00
|
|
|
|
mMainThread, __func__,
|
|
|
|
|
[self = RefPtr<Session>(this)]() {
|
2019-08-03 20:27:15 +03:00
|
|
|
|
self->mRecorder->RemoveSession(self);
|
|
|
|
|
return ShutdownPromise::CreateAndResolve(true, __func__);
|
|
|
|
|
},
|
|
|
|
|
[]() {
|
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unexpected reject");
|
|
|
|
|
return ShutdownPromise::CreateAndReject(false, __func__);
|
|
|
|
|
});
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
|
|
|
|
if (mEncoderThread) {
|
|
|
|
|
mShutdownPromise = mShutdownPromise->Then(
|
2019-10-17 12:31:07 +03:00
|
|
|
|
mMainThread, __func__,
|
|
|
|
|
[encoderThread = mEncoderThread]() {
|
|
|
|
|
return encoderThread->BeginShutdown();
|
|
|
|
|
},
|
2017-05-24 19:51:47 +03:00
|
|
|
|
[]() {
|
|
|
|
|
MOZ_ASSERT_UNREACHABLE("Unexpected reject");
|
|
|
|
|
return ShutdownPromise::CreateAndReject(false, __func__);
|
|
|
|
|
});
|
2014-08-18 07:42:49 +04:00
|
|
|
|
}
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
|
|
|
|
return mShutdownPromise;
|
2014-08-18 07:42:49 +04:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-26 17:40:40 +04:00
|
|
|
|
private:
|
2017-10-31 20:34:23 +03:00
|
|
|
|
enum class RunningState {
|
|
|
|
|
Idling, // Session has been created
|
|
|
|
|
Starting, // MediaEncoder started, waiting for data
|
2021-02-11 16:38:53 +03:00
|
|
|
|
Running, // MediaEncoder has received data
|
2017-10-31 20:34:23 +03:00
|
|
|
|
Stopping, // Stop() has been called
|
|
|
|
|
Stopped, // Session has stopped without any error
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-03 20:27:15 +03:00
|
|
|
|
// Our associated MediaRecorder.
|
|
|
|
|
const RefPtr<MediaRecorder> mRecorder;
|
2013-09-26 17:40:40 +04:00
|
|
|
|
|
2016-02-01 17:47:17 +03:00
|
|
|
|
// Stream currently recorded.
|
|
|
|
|
RefPtr<DOMMediaStream> mMediaStream;
|
|
|
|
|
|
|
|
|
|
// Tracks currently recorded. This should be a subset of mMediaStream's track
|
|
|
|
|
// set.
|
|
|
|
|
nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
|
2013-09-26 17:40:40 +04:00
|
|
|
|
|
2019-08-03 20:27:20 +03:00
|
|
|
|
// Main thread used for MozPromise operations.
|
|
|
|
|
const RefPtr<nsISerialEventTarget> mMainThread;
|
2017-05-24 19:51:47 +03:00
|
|
|
|
// Runnable thread for reading data from MediaEncoder.
|
|
|
|
|
RefPtr<TaskQueue> mEncoderThread;
|
2013-09-26 17:40:40 +04:00
|
|
|
|
// MediaEncoder pipeline.
|
2015-10-18 08:24:48 +03:00
|
|
|
|
RefPtr<MediaEncoder> mEncoder;
|
2021-02-11 16:39:00 +03:00
|
|
|
|
// Listener connected to mMediaEncoder::StartedEvent().
|
|
|
|
|
MediaEventListener mStartedListener;
|
2021-02-11 16:38:59 +03:00
|
|
|
|
// Listener connected to mMediaEncoder::DataAvailableEvent().
|
|
|
|
|
MediaEventListener mDataAvailableListener;
|
2021-02-11 16:39:00 +03:00
|
|
|
|
// Listener connected to mMediaEncoder::ErrorEvent().
|
|
|
|
|
MediaEventListener mErrorListener;
|
|
|
|
|
// Listener connected to mMediaEncoder::ShutdownEvent().
|
|
|
|
|
MediaEventListener mShutdownListener;
|
2017-05-24 19:51:47 +03:00
|
|
|
|
// Set in Shutdown() and resolved when shutdown is complete.
|
|
|
|
|
RefPtr<ShutdownPromise> mShutdownPromise;
|
2019-10-04 01:09:38 +03:00
|
|
|
|
// Session mimeType
|
|
|
|
|
const nsString mMimeType;
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// The video bitrate the recorder was configured with.
|
|
|
|
|
const uint32_t mVideoBitsPerSecond;
|
|
|
|
|
// The audio bitrate the recorder was configured with.
|
|
|
|
|
const uint32_t mAudioBitsPerSecond;
|
2019-08-03 20:27:15 +03:00
|
|
|
|
// The time this session started, for telemetry.
|
|
|
|
|
const TimeStamp mStartTime;
|
|
|
|
|
// The session's current main thread state. The error type gets set when
|
|
|
|
|
// ending a recording with an error. An NS_OK error is invalid.
|
2015-04-22 10:09:49 +03:00
|
|
|
|
// Main thread only.
|
2017-10-31 20:34:23 +03:00
|
|
|
|
Result<RunningState, nsresult> mRunningState;
|
2019-10-17 12:31:21 +03:00
|
|
|
|
// Shutdown blocker unique for this Session. Main thread only.
|
|
|
|
|
RefPtr<ShutdownBlocker> mShutdownBlocker;
|
2013-07-05 05:50:25 +04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MediaRecorder::~MediaRecorder() {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
|
2014-06-19 06:11:34 +04:00
|
|
|
|
UnRegisterActivityObserver();
|
2013-07-05 05:50:25 +04:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
MediaRecorder::MediaRecorder(nsPIDOMWindowInner* aOwnerWindow)
|
|
|
|
|
: DOMEventTargetHelper(aOwnerWindow) {
|
2014-01-07 06:53:23 +04:00
|
|
|
|
MOZ_ASSERT(aOwnerWindow);
|
2014-08-28 01:40:00 +04:00
|
|
|
|
RegisterActivityObserver();
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-19 06:11:34 +04:00
|
|
|
|
void MediaRecorder::RegisterActivityObserver() {
|
2016-01-30 20:05:36 +03:00
|
|
|
|
if (nsPIDOMWindowInner* window = GetOwner()) {
|
2016-11-18 14:29:13 +03:00
|
|
|
|
mDocument = window->GetExtantDoc();
|
|
|
|
|
if (mDocument) {
|
|
|
|
|
mDocument->RegisterActivityObserver(
|
2014-06-19 06:11:34 +04:00
|
|
|
|
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaRecorder::UnRegisterActivityObserver() {
|
2016-11-18 14:29:13 +03:00
|
|
|
|
if (mDocument) {
|
|
|
|
|
mDocument->UnregisterActivityObserver(
|
|
|
|
|
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
|
2014-06-19 06:11:34 +04:00
|
|
|
|
}
|
2013-07-05 05:50:25 +04:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-26 17:40:40 +04:00
|
|
|
|
void MediaRecorder::GetMimeType(nsString& aMimeType) { aMimeType = mMimeType; }
|
2013-07-05 05:50:25 +04:00
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
void MediaRecorder::Start(const Optional<uint32_t>& aTimeslice,
|
2013-07-05 05:50:25 +04:00
|
|
|
|
ErrorResult& aResult) {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
|
2017-08-07 00:49:24 +03:00
|
|
|
|
|
|
|
|
|
InitializeDomExceptions();
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// When a MediaRecorder object’s start() method is invoked, the UA MUST run
|
|
|
|
|
// the following steps:
|
|
|
|
|
|
|
|
|
|
// 1. Let recorder be the MediaRecorder object on which the method was
|
|
|
|
|
// invoked.
|
|
|
|
|
|
|
|
|
|
// 2. Let timeslice be the method’s first argument, if provided, or undefined.
|
|
|
|
|
TimeDuration timeslice =
|
|
|
|
|
aTimeslice.WasPassed()
|
|
|
|
|
? TimeDuration::FromMilliseconds(aTimeslice.Value())
|
|
|
|
|
: TimeDuration::Forever();
|
|
|
|
|
|
|
|
|
|
// 3. Let stream be the value of recorder’s stream attribute.
|
|
|
|
|
|
|
|
|
|
// 4. Let tracks be the set of live tracks in stream’s track set.
|
|
|
|
|
nsTArray<RefPtr<MediaStreamTrack>> tracks;
|
|
|
|
|
if (mStream) {
|
|
|
|
|
mStream->GetTracks(tracks);
|
|
|
|
|
}
|
2020-06-08 11:50:15 +03:00
|
|
|
|
tracks.RemoveLastElements(
|
|
|
|
|
tracks.end() - std::remove_if(tracks.begin(), tracks.end(),
|
|
|
|
|
[](const auto& t) { return t->Ended(); }));
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
|
|
|
|
// 5. If the value of recorder’s state attribute is not inactive, throw an
|
|
|
|
|
// InvalidStateError DOMException and abort these steps.
|
2013-07-05 05:50:25 +04:00
|
|
|
|
if (mState != RecordingState::Inactive) {
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aResult.ThrowInvalidStateError(
|
|
|
|
|
"The MediaRecorder has already been started");
|
2013-07-05 05:50:25 +04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// 6. If the isolation properties of stream disallow access from recorder,
|
|
|
|
|
// throw a SecurityError DOMException and abort these steps.
|
|
|
|
|
if (mStream) {
|
|
|
|
|
RefPtr<nsIPrincipal> streamPrincipal = mStream->GetPrincipal();
|
|
|
|
|
if (!PrincipalSubsumes(this, streamPrincipal)) {
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aResult.ThrowSecurityError(
|
|
|
|
|
"The MediaStream's isolation properties disallow access from "
|
|
|
|
|
"MediaRecorder");
|
2016-04-19 17:37:40 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-04 11:54:16 +03:00
|
|
|
|
if (mAudioNode && !AudioNodePrincipalSubsumes(this, mAudioNode)) {
|
|
|
|
|
LOG(LogLevel::Warning,
|
|
|
|
|
("MediaRecorder %p Start AudioNode principal check failed", this));
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aResult.ThrowSecurityError(
|
|
|
|
|
"The AudioNode's isolation properties disallow access from "
|
|
|
|
|
"MediaRecorder");
|
2019-10-04 11:54:16 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2016-04-19 17:37:40 +03:00
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// 7. If stream is inactive, throw a NotSupportedError DOMException and abort
|
|
|
|
|
// these steps.
|
|
|
|
|
if (mStream && !mStream->Active()) {
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aResult.ThrowNotSupportedError("The MediaStream is inactive");
|
2019-10-04 11:54:16 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 8. If the [[ConstrainedMimeType]] slot specifies a media type, container,
|
|
|
|
|
// or codec, then run the following sub steps:
|
|
|
|
|
// 1. Constrain the configuration of recorder to the media type, container,
|
|
|
|
|
// and codec specified in the [[ConstrainedMimeType]] slot.
|
|
|
|
|
// 2. For each track in tracks, if the User Agent cannot record the track
|
|
|
|
|
// using the current configuration, then throw a NotSupportedError
|
|
|
|
|
// DOMException and abort all steps.
|
2019-10-04 01:09:38 +03:00
|
|
|
|
Maybe<MediaContainerType> mime;
|
|
|
|
|
if (mConstrainedMimeType.Length() > 0) {
|
|
|
|
|
mime = MakeMediaContainerType(mConstrainedMimeType);
|
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
|
|
|
mime,
|
|
|
|
|
"Invalid media MIME type should have been caught by IsTypeSupported");
|
|
|
|
|
}
|
|
|
|
|
for (const auto& track : tracks) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
TypeSupport support = CanRecordWith(track, mime, mConstrainedMimeType);
|
|
|
|
|
if (support != TypeSupport::Supported) {
|
2019-10-04 01:09:38 +03:00
|
|
|
|
nsString id;
|
|
|
|
|
track->GetId(id);
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aResult.ThrowNotSupportedError(nsPrintfCString(
|
|
|
|
|
"%s track cannot be recorded: %s",
|
|
|
|
|
track->AsAudioStreamTrack() ? "An audio" : "A video",
|
|
|
|
|
TypeSupportToCString(support, mConstrainedMimeType).get()));
|
2019-10-04 01:09:38 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-04 01:10:24 +03:00
|
|
|
|
if (mAudioNode) {
|
|
|
|
|
TypeSupport support = CanRecordAudioTrackWith(mime, mConstrainedMimeType);
|
|
|
|
|
if (support != TypeSupport::Supported) {
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aResult.ThrowNotSupportedError(nsPrintfCString(
|
|
|
|
|
"An AudioNode cannot be recorded: %s",
|
|
|
|
|
TypeSupportToCString(support, mConstrainedMimeType).get()));
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-10-04 01:09:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-08 02:07:07 +03:00
|
|
|
|
// 9. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
|
|
|
|
|
// recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
|
|
|
|
|
// values the User Agent deems reasonable for the respective media types,
|
|
|
|
|
// for recording all tracks in tracks, such that the sum of
|
|
|
|
|
// videoBitsPerSecond and audioBitsPerSecond is close to the value of
|
|
|
|
|
// recorder’s
|
|
|
|
|
// [[ConstrainedBitsPerSecond]] slot.
|
2021-04-06 12:10:04 +03:00
|
|
|
|
uint8_t numVideoTracks = 0;
|
|
|
|
|
uint8_t numAudioTracks = 0;
|
|
|
|
|
for (const auto& t : tracks) {
|
|
|
|
|
if (t->AsVideoStreamTrack() && numVideoTracks < UINT8_MAX) {
|
|
|
|
|
++numVideoTracks;
|
|
|
|
|
} else if (t->AsAudioStreamTrack() && numAudioTracks < UINT8_MAX) {
|
2019-10-04 11:54:16 +03:00
|
|
|
|
++numAudioTracks;
|
|
|
|
|
}
|
2021-04-06 12:10:04 +03:00
|
|
|
|
}
|
|
|
|
|
if (mAudioNode) {
|
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!mStream);
|
|
|
|
|
++numAudioTracks;
|
|
|
|
|
}
|
|
|
|
|
if (mConstrainedBitsPerSecond) {
|
2019-10-04 11:54:16 +03:00
|
|
|
|
SelectBitrates(*mConstrainedBitsPerSecond, numVideoTracks,
|
|
|
|
|
&mVideoBitsPerSecond, numAudioTracks, &mAudioBitsPerSecond);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-08 02:07:07 +03:00
|
|
|
|
// 10. Let videoBitrate be the value of recorder’s videoBitsPerSecond
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// attribute, and constrain the configuration of recorder to target an
|
|
|
|
|
// aggregate bitrate of videoBitrate bits per second for all video tracks
|
|
|
|
|
// recorder will be recording. videoBitrate is a hint for the encoder and
|
|
|
|
|
// the value might be surpassed, not achieved, or only be achieved over a
|
|
|
|
|
// long period of time.
|
|
|
|
|
const uint32_t videoBitrate = mVideoBitsPerSecond;
|
|
|
|
|
|
2019-11-08 02:07:07 +03:00
|
|
|
|
// 11. Let audioBitrate be the value of recorder’s audioBitsPerSecond
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// attribute, and constrain the configuration of recorder to target an
|
|
|
|
|
// aggregate bitrate of audioBitrate bits per second for all audio tracks
|
|
|
|
|
// recorder will be recording. audioBitrate is a hint for the encoder and
|
|
|
|
|
// the value might be surpassed, not achieved, or only be achieved over a
|
|
|
|
|
// long period of time.
|
|
|
|
|
const uint32_t audioBitrate = mAudioBitsPerSecond;
|
|
|
|
|
|
2021-04-06 12:10:04 +03:00
|
|
|
|
// 12. Constrain the configuration of recorder to encode using the BitrateMode
|
|
|
|
|
// specified by the value of recorder’s audioBitrateMode attribute for all
|
|
|
|
|
// audio tracks recorder will be recording.
|
|
|
|
|
// -- NOT IMPLEMENTED
|
|
|
|
|
|
|
|
|
|
// 13. For each track in tracks, if the User Agent cannot record the track
|
|
|
|
|
// using the current configuration, then throw a NotSupportedError
|
|
|
|
|
// DOMException and abort these steps.
|
|
|
|
|
if (numVideoTracks > 1) {
|
|
|
|
|
aResult.ThrowNotSupportedError(
|
|
|
|
|
"MediaRecorder does not support recording more than one video track"_ns);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (numAudioTracks > 1) {
|
|
|
|
|
aResult.ThrowNotSupportedError(
|
|
|
|
|
"MediaRecorder does not support recording more than one audio track"_ns);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 14. Set recorder’s state to recording
|
2013-09-26 17:40:40 +04:00
|
|
|
|
mState = RecordingState::Recording;
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
|
|
|
|
MediaRecorderReporter::AddMediaRecorder(this);
|
2014-06-26 13:22:05 +04:00
|
|
|
|
// Start a session.
|
2014-03-25 21:11:58 +04:00
|
|
|
|
mSessions.AppendElement();
|
2021-02-11 16:38:58 +03:00
|
|
|
|
mSessions.LastElement() =
|
|
|
|
|
new Session(this, std::move(tracks), videoBitrate, audioBitrate);
|
|
|
|
|
mSessions.LastElement()->Start(timeslice);
|
2013-07-05 05:50:25 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaRecorder::Stop(ErrorResult& aResult) {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
|
2014-07-01 09:25:29 +04:00
|
|
|
|
MediaRecorderReporter::RemoveMediaRecorder(this);
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
|
|
|
|
// When a MediaRecorder object’s stop() method is invoked, the UA MUST run the
|
|
|
|
|
// following steps:
|
|
|
|
|
|
|
|
|
|
// 1. Let recorder be the MediaRecorder object on which the method was
|
|
|
|
|
// invoked.
|
|
|
|
|
|
|
|
|
|
// 2. If recorder’s state attribute is inactive, abort these steps.
|
2013-07-05 05:50:25 +04:00
|
|
|
|
if (mState == RecordingState::Inactive) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
|
|
|
|
// 3. Inactivate the recorder with recorder.
|
2019-10-04 11:54:16 +03:00
|
|
|
|
Inactivate();
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
|
|
|
|
// 4. Queue a task, using the DOM manipulation task source, that runs the
|
|
|
|
|
// following steps:
|
|
|
|
|
// 1. Stop gathering data.
|
|
|
|
|
// 2. Let blob be the Blob of collected data so far, then fire a blob event
|
|
|
|
|
// named dataavailable at recorder with blob.
|
|
|
|
|
// 3. Fire an event named stop at recorder.
|
2014-06-19 06:11:34 +04:00
|
|
|
|
MOZ_ASSERT(mSessions.Length() > 0);
|
|
|
|
|
mSessions.LastElement()->Stop();
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
|
|
|
|
// 5. return undefined.
|
2013-07-05 05:50:25 +04:00
|
|
|
|
}
|
|
|
|
|
|
2013-07-14 11:31:59 +04:00
|
|
|
|
void MediaRecorder::Pause(ErrorResult& aResult) {
|
2019-01-14 21:32:54 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.Pause %p", this));
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
|
|
|
|
// When a MediaRecorder object’s pause() method is invoked, the UA MUST run
|
|
|
|
|
// the following steps:
|
|
|
|
|
|
|
|
|
|
// 1. If state is inactive, throw an InvalidStateError DOMException and abort
|
|
|
|
|
// these steps.
|
2018-10-08 12:06:07 +03:00
|
|
|
|
if (mState == RecordingState::Inactive) {
|
2020-02-07 00:16:10 +03:00
|
|
|
|
aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
|
2013-07-14 11:31:59 +04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:09:07 +03:00
|
|
|
|
// 2. If state is paused, abort these steps.
|
2019-01-14 21:32:56 +03:00
|
|
|
|
if (mState == RecordingState::Paused) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:09:07 +03:00
|
|
|
|
// 3. Set state to paused, and queue a task, using the DOM manipulation task
|
|
|
|
|
// source, that runs the following steps:
|
2014-03-25 21:11:58 +04:00
|
|
|
|
mState = RecordingState::Paused;
|
2019-10-23 16:04:12 +03:00
|
|
|
|
|
|
|
|
|
// XXX - We pause synchronously pending spec issue
|
|
|
|
|
// https://github.com/w3c/mediacapture-record/issues/131
|
|
|
|
|
// 1. Stop gathering data into blob (but keep it available so that
|
|
|
|
|
// recording can be resumed in the future).
|
2019-10-04 01:09:07 +03:00
|
|
|
|
MOZ_ASSERT(!mSessions.IsEmpty());
|
2019-10-23 16:04:12 +03:00
|
|
|
|
mSessions.LastElement()->Pause();
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
2019-10-23 16:04:12 +03:00
|
|
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
|
|
|
"MediaRecorder::Pause", [recorder = RefPtr<MediaRecorder>(this)] {
|
2019-10-04 01:09:07 +03:00
|
|
|
|
// 2. Let target be the MediaRecorder context object. Fire an event
|
|
|
|
|
// named pause at target.
|
|
|
|
|
recorder->DispatchSimpleEvent(u"pause"_ns);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 4. return undefined.
|
2013-07-14 11:31:59 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaRecorder::Resume(ErrorResult& aResult) {
|
2019-01-14 21:32:54 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.Resume %p", this));
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
|
|
|
|
// When a MediaRecorder object’s resume() method is invoked, the UA MUST run
|
|
|
|
|
// the following steps:
|
|
|
|
|
|
|
|
|
|
// 1. If state is inactive, throw an InvalidStateError DOMException and abort
|
|
|
|
|
// these steps.
|
2018-10-08 12:06:07 +03:00
|
|
|
|
if (mState == RecordingState::Inactive) {
|
2020-02-07 00:16:10 +03:00
|
|
|
|
aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
|
2013-07-14 11:31:59 +04:00
|
|
|
|
return;
|
|
|
|
|
}
|
2013-09-26 17:40:40 +04:00
|
|
|
|
|
2019-10-04 01:09:07 +03:00
|
|
|
|
// 2. If state is recording, abort these steps.
|
2019-01-14 21:32:56 +03:00
|
|
|
|
if (mState == RecordingState::Recording) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 01:09:07 +03:00
|
|
|
|
// 3. Set state to recording, and queue a task, using the DOM manipulation
|
|
|
|
|
// task source, that runs the following steps:
|
2014-03-25 21:11:58 +04:00
|
|
|
|
mState = RecordingState::Recording;
|
2019-10-23 16:04:12 +03:00
|
|
|
|
|
|
|
|
|
// XXX - We resume synchronously pending spec issue
|
|
|
|
|
// https://github.com/w3c/mediacapture-record/issues/131
|
|
|
|
|
// 1. Resume (or continue) gathering data into the current blob.
|
2019-10-04 01:09:07 +03:00
|
|
|
|
MOZ_ASSERT(!mSessions.IsEmpty());
|
2019-10-23 16:04:12 +03:00
|
|
|
|
mSessions.LastElement()->Resume();
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
2019-10-23 16:04:12 +03:00
|
|
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
|
|
|
"MediaRecorder::Resume", [recorder = RefPtr<MediaRecorder>(this)] {
|
2019-10-04 01:09:07 +03:00
|
|
|
|
// 2. Let target be the MediaRecorder context object. Fire an event
|
|
|
|
|
// named resume at target.
|
|
|
|
|
recorder->DispatchSimpleEvent(u"resume"_ns);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 4. return undefined.
|
2013-07-14 11:31:59 +04:00
|
|
|
|
}
|
|
|
|
|
|
2013-07-05 05:50:25 +04:00
|
|
|
|
void MediaRecorder::RequestData(ErrorResult& aResult) {
|
2019-10-04 01:09:07 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.RequestData %p", this));
|
|
|
|
|
|
|
|
|
|
// When a MediaRecorder object’s requestData() method is invoked, the UA MUST
|
|
|
|
|
// run the following steps:
|
|
|
|
|
|
|
|
|
|
// 1. If state is inactive throw an InvalidStateError DOMException and
|
|
|
|
|
// terminate these steps. Otherwise the UA MUST queue a task, using the DOM
|
|
|
|
|
// manipulation task source, that runs the following steps:
|
|
|
|
|
// 1. Let blob be the Blob of collected data so far and let target be the
|
|
|
|
|
// MediaRecorder context object, then fire a blob event named
|
|
|
|
|
// dataavailable at target with blob. (Note that blob will be empty if no
|
|
|
|
|
// data has been gathered yet.)
|
|
|
|
|
// 2. Create a new Blob and gather subsequent data into it.
|
2017-05-14 23:53:06 +03:00
|
|
|
|
if (mState == RecordingState::Inactive) {
|
2020-02-07 00:16:10 +03:00
|
|
|
|
aResult.ThrowInvalidStateError("The MediaRecorder is inactive");
|
2013-07-05 05:50:25 +04:00
|
|
|
|
return;
|
|
|
|
|
}
|
2014-06-19 06:11:34 +04:00
|
|
|
|
MOZ_ASSERT(mSessions.Length() > 0);
|
2019-08-03 20:27:20 +03:00
|
|
|
|
mSessions.LastElement()->RequestData();
|
2019-10-04 01:09:07 +03:00
|
|
|
|
|
|
|
|
|
// 2. return undefined.
|
2013-07-05 05:50:25 +04:00
|
|
|
|
}
|
|
|
|
|
|
Bug 1117172 part 3. Change the wrappercached WrapObject methods to allow passing in aGivenProto. r=peterv
The only manual changes here are to BindingUtils.h, BindingUtils.cpp,
Codegen.py, Element.cpp, IDBFileRequest.cpp, IDBObjectStore.cpp,
dom/workers/Navigator.cpp, WorkerPrivate.cpp, DeviceStorageRequestChild.cpp,
Notification.cpp, nsGlobalWindow.cpp, MessagePort.cpp, nsJSEnvironment.cpp,
Sandbox.cpp, XPCConvert.cpp, ExportHelpers.cpp, and DataStoreService.cpp. The
rest of this diff was generated by running the following commands:
find . -name "*.h" -o -name "*.cpp" | xargs perl -pi -e 'BEGIN { $/ = undef } s/(WrapObjectInternal\(JSContext *\* *(?:aCx|cx|aContext|aCtx|js))\)/\1, JS::Handle<JSObject*> aGivenProto)/g'
find . -name "*.h" -o -name "*.cpp" | xargs perl -pi -e 'BEGIN { $/ = undef } s/(WrapObjectInternal\((?:aCx|cx|aContext|aCtx|js))\)/\1, aGivenProto)/g'
find . -name "*.h" -o -name "*.cpp" | xargs perl -pi -e 'BEGIN { $/ = undef } s/(WrapNode\(JSContext *\* *(?:aCx|cx|aContext|aCtx|js))\)/\1, JS::Handle<JSObject*> aGivenProto)/g'
find . -name "*.h" -o -name "*.cpp" | xargs perl -pi -e 'BEGIN { $/ = undef } s/(WrapNode\((?:aCx|cx|aContext|aCtx|js))\)/\1, aGivenProto)/g'
find . -name "*.h" -o -name "*.cpp" | xargs perl -pi -e 'BEGIN { $/ = undef } s/(WrapObject\(JSContext *\* *(?:aCx|cx|aContext|aCtx|js))\)/\1, JS::Handle<JSObject*> aGivenProto)/g'
find . -name "*.h" -o -name "*.cpp" | xargs perl -pi -e 'BEGIN { $/ = undef } s/(Binding(?:_workers)?::Wrap\((?:aCx|cx|aContext|aCtx|js), [^,)]+)\)/\1, aGivenProto)/g'
2015-03-19 17:13:33 +03:00
|
|
|
|
JSObject* MediaRecorder::WrapObject(JSContext* aCx,
|
|
|
|
|
JS::Handle<JSObject*> aGivenProto) {
|
2018-06-26 00:20:54 +03:00
|
|
|
|
return MediaRecorder_Binding::Wrap(aCx, this, aGivenProto);
|
2013-07-05 05:50:25 +04:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-26 01:05:29 +03:00
|
|
|
|
/* static */
|
|
|
|
|
already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
|
2013-08-23 09:17:08 +04:00
|
|
|
|
const GlobalObject& aGlobal, DOMMediaStream& aStream,
|
2019-10-04 11:54:16 +03:00
|
|
|
|
const MediaRecorderOptions& aOptions, ErrorResult& aRv) {
|
2016-01-30 20:05:36 +03:00
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
|
|
|
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
2014-08-28 01:40:00 +04:00
|
|
|
|
if (!ownerWindow) {
|
2013-07-05 05:50:25 +04:00
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// When the MediaRecorder() constructor is invoked, the User Agent MUST run
|
|
|
|
|
// the following steps:
|
|
|
|
|
|
|
|
|
|
// 1. Let stream be the constructor’s first argument.
|
|
|
|
|
|
|
|
|
|
// 2. Let options be the constructor’s second argument.
|
|
|
|
|
|
|
|
|
|
// 3. If invoking is type supported with options’ mimeType member as its
|
|
|
|
|
// argument returns false, throw a NotSupportedError DOMException and abort
|
|
|
|
|
// these steps.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
|
|
|
|
|
if (support != TypeSupport::Supported) {
|
2019-10-04 01:09:38 +03:00
|
|
|
|
// This catches also the empty string mimeType when support for any encoders
|
|
|
|
|
// has been disabled.
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aRv.ThrowNotSupportedError(
|
|
|
|
|
TypeSupportToCString(support, aOptions.mMimeType));
|
2016-03-07 06:48:16 +03:00
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// 4. Let recorder be a newly constructed MediaRecorder object.
|
|
|
|
|
RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
|
|
|
|
|
|
2019-10-04 01:09:38 +03:00
|
|
|
|
// 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
|
|
|
|
|
// to the value of options' mimeType member.
|
|
|
|
|
recorder->mConstrainedMimeType = aOptions.mMimeType;
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
|
|
|
|
// 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
|
|
|
|
|
// initialized to the value of options’ bitsPerSecond member, if it is
|
|
|
|
|
// present, otherwise undefined.
|
|
|
|
|
recorder->mConstrainedBitsPerSecond =
|
|
|
|
|
aOptions.mBitsPerSecond.WasPassed()
|
|
|
|
|
? Some(aOptions.mBitsPerSecond.Value())
|
|
|
|
|
: Nothing();
|
|
|
|
|
|
|
|
|
|
// 7. Initialize recorder’s stream attribute to stream.
|
|
|
|
|
recorder->mStream = &aStream;
|
|
|
|
|
|
2019-10-04 01:09:38 +03:00
|
|
|
|
// 8. Initialize recorder’s mimeType attribute to the value of recorder’s
|
|
|
|
|
// [[ConstrainedMimeType]] slot.
|
|
|
|
|
recorder->mMimeType = recorder->mConstrainedMimeType;
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// 9. Initialize recorder’s state attribute to inactive.
|
|
|
|
|
recorder->mState = RecordingState::Inactive;
|
|
|
|
|
|
|
|
|
|
// 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
|
|
|
|
|
// options’ videoBitsPerSecond member, if it is present. Otherwise, choose
|
|
|
|
|
// a target value the User Agent deems reasonable for video.
|
|
|
|
|
recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
|
|
|
|
|
? aOptions.mVideoBitsPerSecond.Value()
|
|
|
|
|
: DEFAULT_VIDEO_BITRATE_BPS;
|
|
|
|
|
|
|
|
|
|
// 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
|
|
|
|
|
// options’ audioBitsPerSecond member, if it is present. Otherwise, choose
|
|
|
|
|
// a target value the User Agent deems reasonable for audio.
|
|
|
|
|
recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
|
|
|
|
|
? aOptions.mAudioBitsPerSecond.Value()
|
|
|
|
|
: DEFAULT_AUDIO_BITRATE_BPS;
|
|
|
|
|
|
|
|
|
|
// 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
|
|
|
|
|
// recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
|
|
|
|
|
// values the User Agent deems reasonable for the respective media types,
|
|
|
|
|
// such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
|
|
|
|
|
// to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
|
|
|
|
|
if (recorder->mConstrainedBitsPerSecond) {
|
|
|
|
|
SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
|
|
|
|
|
&recorder->mVideoBitsPerSecond, 1,
|
|
|
|
|
&recorder->mAudioBitsPerSecond);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 13. Return recorder.
|
|
|
|
|
return recorder.forget();
|
2014-08-28 01:40:00 +04:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-26 01:05:29 +03:00
|
|
|
|
/* static */
|
|
|
|
|
already_AddRefed<MediaRecorder> MediaRecorder::Constructor(
|
2019-10-04 11:54:16 +03:00
|
|
|
|
const GlobalObject& aGlobal, AudioNode& aAudioNode,
|
|
|
|
|
uint32_t aAudioNodeOutput, const MediaRecorderOptions& aOptions,
|
|
|
|
|
ErrorResult& aRv) {
|
2014-08-28 01:40:00 +04:00
|
|
|
|
// 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.
|
2020-03-07 00:08:02 +03:00
|
|
|
|
aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("Argument 1",
|
|
|
|
|
"MediaStream");
|
2014-08-28 01:40:00 +04:00
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-30 20:05:36 +03:00
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
|
|
|
|
|
do_QueryInterface(aGlobal.GetAsSupports());
|
2013-07-05 05:50:25 +04:00
|
|
|
|
if (!ownerWindow) {
|
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// aAudioNodeOutput doesn't matter to destination node because it has no
|
|
|
|
|
// output.
|
|
|
|
|
if (aAudioNode.NumberOfOutputs() > 0 &&
|
|
|
|
|
aAudioNodeOutput >= aAudioNode.NumberOfOutputs()) {
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aRv.ThrowIndexSizeError("Invalid AudioNode output index");
|
2014-08-28 01:40:00 +04:00
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// When the MediaRecorder() constructor is invoked, the User Agent MUST run
|
|
|
|
|
// the following steps:
|
|
|
|
|
|
|
|
|
|
// 1. Let stream be the constructor’s first argument. (we'll let audioNode be
|
|
|
|
|
// the first arg, and audioNodeOutput the second)
|
|
|
|
|
|
|
|
|
|
// 2. Let options be the constructor’s second argument. (we'll let options be
|
|
|
|
|
// the third arg)
|
|
|
|
|
|
|
|
|
|
// 3. If invoking is type supported with options’ mimeType member as its
|
|
|
|
|
// argument returns false, throw a NotSupportedError DOMException and abort
|
|
|
|
|
// these steps.
|
2019-10-04 01:10:24 +03:00
|
|
|
|
TypeSupport support = IsTypeSupportedImpl(aOptions.mMimeType);
|
|
|
|
|
if (support != TypeSupport::Supported) {
|
2019-10-04 01:09:38 +03:00
|
|
|
|
// This catches also the empty string mimeType when support for any encoders
|
|
|
|
|
// has been disabled.
|
2020-02-03 23:19:11 +03:00
|
|
|
|
aRv.ThrowNotSupportedError(
|
|
|
|
|
TypeSupportToCString(support, aOptions.mMimeType));
|
2016-03-07 06:48:16 +03:00
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// 4. Let recorder be a newly constructed MediaRecorder object.
|
|
|
|
|
RefPtr<MediaRecorder> recorder = new MediaRecorder(ownerWindow);
|
|
|
|
|
|
2019-10-04 01:09:38 +03:00
|
|
|
|
// 5. Let recorder have a [[ConstrainedMimeType]] internal slot, initialized
|
|
|
|
|
// to the value of options' mimeType member.
|
|
|
|
|
recorder->mConstrainedMimeType = aOptions.mMimeType;
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
|
|
|
|
// 6. Let recorder have a [[ConstrainedBitsPerSecond]] internal slot,
|
|
|
|
|
// initialized to the value of options’ bitsPerSecond member, if it is
|
|
|
|
|
// present, otherwise undefined.
|
|
|
|
|
recorder->mConstrainedBitsPerSecond =
|
|
|
|
|
aOptions.mBitsPerSecond.WasPassed()
|
|
|
|
|
? Some(aOptions.mBitsPerSecond.Value())
|
|
|
|
|
: Nothing();
|
|
|
|
|
|
|
|
|
|
// 7. Initialize recorder’s stream attribute to stream. (make that the
|
|
|
|
|
// audioNode and audioNodeOutput equivalents)
|
|
|
|
|
recorder->mAudioNode = &aAudioNode;
|
|
|
|
|
recorder->mAudioNodeOutput = aAudioNodeOutput;
|
|
|
|
|
|
2019-10-04 01:09:38 +03:00
|
|
|
|
// 8. Initialize recorder’s mimeType attribute to the value of recorder’s
|
|
|
|
|
// [[ConstrainedMimeType]] slot.
|
|
|
|
|
recorder->mMimeType = recorder->mConstrainedMimeType;
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
// 9. Initialize recorder’s state attribute to inactive.
|
|
|
|
|
recorder->mState = RecordingState::Inactive;
|
|
|
|
|
|
|
|
|
|
// 10. Initialize recorder’s videoBitsPerSecond attribute to the value of
|
|
|
|
|
// options’ videoBitsPerSecond member, if it is present. Otherwise, choose
|
|
|
|
|
// a target value the User Agent deems reasonable for video.
|
|
|
|
|
recorder->mVideoBitsPerSecond = aOptions.mVideoBitsPerSecond.WasPassed()
|
|
|
|
|
? aOptions.mVideoBitsPerSecond.Value()
|
|
|
|
|
: DEFAULT_VIDEO_BITRATE_BPS;
|
|
|
|
|
|
|
|
|
|
// 11. Initialize recorder’s audioBitsPerSecond attribute to the value of
|
|
|
|
|
// options’ audioBitsPerSecond member, if it is present. Otherwise, choose
|
|
|
|
|
// a target value the User Agent deems reasonable for audio.
|
|
|
|
|
recorder->mAudioBitsPerSecond = aOptions.mAudioBitsPerSecond.WasPassed()
|
|
|
|
|
? aOptions.mAudioBitsPerSecond.Value()
|
|
|
|
|
: DEFAULT_AUDIO_BITRATE_BPS;
|
|
|
|
|
|
|
|
|
|
// 12. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
|
|
|
|
|
// recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
|
|
|
|
|
// values the User Agent deems reasonable for the respective media types,
|
|
|
|
|
// such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
|
|
|
|
|
// to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
|
|
|
|
|
if (recorder->mConstrainedBitsPerSecond) {
|
|
|
|
|
SelectBitrates(*recorder->mConstrainedBitsPerSecond, 1,
|
|
|
|
|
&recorder->mVideoBitsPerSecond, 1,
|
|
|
|
|
&recorder->mAudioBitsPerSecond);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 13. Return recorder.
|
|
|
|
|
return recorder.forget();
|
2015-05-25 18:49:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 06:48:16 +03:00
|
|
|
|
/* static */
|
|
|
|
|
bool MediaRecorder::IsTypeSupported(GlobalObject& aGlobal,
|
|
|
|
|
const nsAString& aMIMEType) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return MediaRecorder::IsTypeSupported(aMIMEType);
|
2016-03-07 06:48:16 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* static */
|
|
|
|
|
bool MediaRecorder::IsTypeSupported(const nsAString& aMIMEType) {
|
2019-10-04 01:10:24 +03:00
|
|
|
|
return IsTypeSupportedImpl(aMIMEType) == TypeSupport::Supported;
|
2016-03-07 06:48:16 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-21 08:33:33 +03:00
|
|
|
|
nsresult MediaRecorder::CreateAndDispatchBlobEvent(BlobImpl* aBlobImpl) {
|
2015-02-10 01:34:50 +03:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
|
2016-02-01 17:47:17 +03:00
|
|
|
|
|
2019-10-21 08:33:19 +03:00
|
|
|
|
if (!GetOwnerGlobal()) {
|
|
|
|
|
// This MediaRecorder has been disconnected in the meantime.
|
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-21 08:33:33 +03:00
|
|
|
|
RefPtr<Blob> blob = Blob::Create(GetOwnerGlobal(), aBlobImpl);
|
|
|
|
|
if (NS_WARN_IF(!blob)) {
|
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-09 20:05:22 +04:00
|
|
|
|
BlobEventInit init;
|
2013-09-10 22:27:39 +04:00
|
|
|
|
init.mBubbles = false;
|
|
|
|
|
init.mCancelable = false;
|
2019-10-21 08:33:33 +03:00
|
|
|
|
init.mData = blob;
|
2014-10-08 20:15:22 +04:00
|
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
|
RefPtr<BlobEvent> event =
|
2013-09-10 22:27:39 +04:00
|
|
|
|
BlobEvent::Constructor(this, u"dataavailable"_ns, init);
|
2013-07-05 05:50:25 +04:00
|
|
|
|
event->SetTrusted(true);
|
2018-04-05 20:42:41 +03:00
|
|
|
|
ErrorResult rv;
|
|
|
|
|
DispatchEvent(*event, rv);
|
|
|
|
|
return rv.StealNSResult();
|
2013-07-05 05:50:25 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaRecorder::DispatchSimpleEvent(const nsAString& aStr) {
|
2015-02-10 01:34:50 +03:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
|
2019-04-01 19:46:46 +03:00
|
|
|
|
nsresult rv = CheckCurrentGlobalCorrectness();
|
2013-07-05 05:50:25 +04:00
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-14 21:32:54 +03:00
|
|
|
|
rv = DOMEventTargetHelper::DispatchTrustedEvent(aStr);
|
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
|
LOG(LogLevel::Error,
|
|
|
|
|
("MediaRecorder.DispatchSimpleEvent: DispatchTrustedEvent failed %p",
|
|
|
|
|
this));
|
2013-07-05 05:50:25 +04:00
|
|
|
|
NS_ERROR("Failed to dispatch the event!!!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MediaRecorder::NotifyError(nsresult aRv) {
|
2015-02-10 01:34:50 +03:00
|
|
|
|
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
|
2019-04-01 19:46:46 +03:00
|
|
|
|
nsresult rv = CheckCurrentGlobalCorrectness();
|
2013-07-05 05:50:25 +04:00
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-08-07 00:48:42 +03:00
|
|
|
|
MediaRecorderErrorEventInit init;
|
2017-08-11 02:32:12 +03:00
|
|
|
|
init.mBubbles = false;
|
|
|
|
|
init.mCancelable = false;
|
2017-08-07 00:49:24 +03:00
|
|
|
|
// These DOMExceptions have been created earlier so they can contain stack
|
|
|
|
|
// traces. We attach the appropriate one here to be fired. We should have
|
|
|
|
|
// exceptions here, but defensively check.
|
2017-08-07 00:48:42 +03:00
|
|
|
|
switch (aRv) {
|
|
|
|
|
case NS_ERROR_DOM_SECURITY_ERR:
|
2017-08-07 00:49:24 +03:00
|
|
|
|
if (!mSecurityDomException) {
|
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
|
|
|
|
|
"mSecurityDomException was not initialized"));
|
|
|
|
|
mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
|
|
|
|
|
}
|
2020-02-13 17:38:48 +03:00
|
|
|
|
init.mError = std::move(mSecurityDomException);
|
2017-08-07 00:48:42 +03:00
|
|
|
|
break;
|
|
|
|
|
default:
|
2021-03-04 22:39:27 +03:00
|
|
|
|
if (mOtherDomException && aRv == mOtherDomException->GetResult()) {
|
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
|
|
|
|
|
"mOtherDomException being fired for aRv: %X",
|
|
|
|
|
uint32_t(aRv)));
|
|
|
|
|
init.mError = std::move(mOtherDomException);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-08-07 00:49:24 +03:00
|
|
|
|
if (!mUnknownDomException) {
|
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
|
|
|
|
|
"mUnknownDomException was not initialized"));
|
|
|
|
|
mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
|
|
|
|
|
}
|
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
|
|
|
|
|
"mUnknownDomException being fired for aRv: %X",
|
|
|
|
|
uint32_t(aRv)));
|
2020-02-13 17:38:48 +03:00
|
|
|
|
init.mError = std::move(mUnknownDomException);
|
2021-03-04 22:39:27 +03:00
|
|
|
|
break;
|
2017-08-07 00:48:42 +03:00
|
|
|
|
}
|
2017-08-11 02:32:12 +03:00
|
|
|
|
|
2017-08-07 00:48:42 +03:00
|
|
|
|
RefPtr<MediaRecorderErrorEvent> event =
|
|
|
|
|
MediaRecorderErrorEvent::Constructor(this, u"error"_ns, init);
|
2013-07-05 05:50:25 +04:00
|
|
|
|
event->SetTrusted(true);
|
2014-07-01 03:02:02 +04:00
|
|
|
|
|
2018-04-05 20:42:41 +03:00
|
|
|
|
IgnoredErrorResult res;
|
|
|
|
|
DispatchEvent(*event, res);
|
|
|
|
|
if (res.Failed()) {
|
2013-07-05 05:50:25 +04:00
|
|
|
|
NS_ERROR("Failed to dispatch the error event!!!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-25 21:11:58 +04:00
|
|
|
|
void MediaRecorder::RemoveSession(Session* aSession) {
|
2015-06-04 01:25:57 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.RemoveSession (%p)", aSession));
|
2014-03-25 21:11:58 +04:00
|
|
|
|
mSessions.RemoveElement(aSession);
|
|
|
|
|
}
|
|
|
|
|
|
2014-06-19 06:11:34 +04:00
|
|
|
|
void MediaRecorder::NotifyOwnerDocumentActivityChanged() {
|
2016-01-30 20:05:36 +03:00
|
|
|
|
nsPIDOMWindowInner* window = GetOwner();
|
2014-06-19 06:11:34 +04:00
|
|
|
|
NS_ENSURE_TRUE_VOID(window);
|
2019-01-02 16:05:23 +03:00
|
|
|
|
Document* doc = window->GetExtantDoc();
|
2014-06-19 06:11:34 +04:00
|
|
|
|
NS_ENSURE_TRUE_VOID(doc);
|
|
|
|
|
|
2018-03-19 17:52:36 +03:00
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder %p NotifyOwnerDocumentActivityChanged "
|
|
|
|
|
"IsActive=%d, "
|
2019-09-23 13:16:44 +03:00
|
|
|
|
"IsVisible=%d, ",
|
|
|
|
|
this, doc->IsActive(), doc->IsVisible()));
|
|
|
|
|
if (!doc->IsActive() || !doc->IsVisible()) {
|
2014-06-19 06:11:34 +04:00
|
|
|
|
// Stop the session.
|
|
|
|
|
ErrorResult result;
|
|
|
|
|
Stop(result);
|
2015-11-21 00:29:41 +03:00
|
|
|
|
result.SuppressException();
|
2014-06-19 06:11:34 +04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-04 11:54:16 +03:00
|
|
|
|
void MediaRecorder::Inactivate() {
|
|
|
|
|
LOG(LogLevel::Debug, ("MediaRecorder.Inactivate %p", this));
|
|
|
|
|
// The Inactivate the recorder algorithm given a recorder, is as follows:
|
|
|
|
|
|
|
|
|
|
// 1. Set recorder’s mimeType attribute to the value of the
|
|
|
|
|
// [[ConstrainedMimeType]] slot.
|
2019-10-04 01:09:38 +03:00
|
|
|
|
mMimeType = mConstrainedMimeType;
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
|
|
|
|
// 2. Set recorder’s state attribute to inactive.
|
2017-09-01 02:38:52 +03:00
|
|
|
|
mState = RecordingState::Inactive;
|
2019-10-04 11:54:16 +03:00
|
|
|
|
|
|
|
|
|
// 3. If recorder’s [[ConstrainedBitsPerSecond]] slot is not undefined, set
|
|
|
|
|
// recorder’s videoBitsPerSecond and audioBitsPerSecond attributes to
|
|
|
|
|
// values the User Agent deems reasonable for the respective media types,
|
|
|
|
|
// such that the sum of videoBitsPerSecond and audioBitsPerSecond is close
|
|
|
|
|
// to the value of recorder’s [[ConstrainedBitsPerSecond]] slot.
|
|
|
|
|
if (mConstrainedBitsPerSecond) {
|
|
|
|
|
SelectBitrates(*mConstrainedBitsPerSecond, 1, &mVideoBitsPerSecond, 1,
|
|
|
|
|
&mAudioBitsPerSecond);
|
|
|
|
|
}
|
2017-09-01 02:38:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-07 00:49:24 +03:00
|
|
|
|
void MediaRecorder::InitializeDomExceptions() {
|
|
|
|
|
mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
|
|
|
|
|
mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-24 19:51:47 +03:00
|
|
|
|
RefPtr<MediaRecorder::SizeOfPromise> MediaRecorder::SizeOfExcludingThis(
|
|
|
|
|
mozilla::MallocSizeOf aMallocSizeOf) {
|
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
|
|
// The return type of a chained MozPromise cannot be changed, so we create a
|
|
|
|
|
// holder for our desired return type and resolve that from All()->Then().
|
|
|
|
|
auto holder = MakeRefPtr<Refcountable<MozPromiseHolder<SizeOfPromise>>>();
|
|
|
|
|
RefPtr<SizeOfPromise> promise = holder->Ensure(__func__);
|
|
|
|
|
|
|
|
|
|
nsTArray<RefPtr<SizeOfPromise>> promises(mSessions.Length());
|
|
|
|
|
for (const RefPtr<Session>& session : mSessions) {
|
|
|
|
|
promises.AppendElement(session->SizeOfExcludingThis(aMallocSizeOf));
|
2014-07-01 09:25:29 +04:00
|
|
|
|
}
|
2017-05-24 19:51:47 +03:00
|
|
|
|
|
2020-06-23 08:05:36 +03:00
|
|
|
|
SizeOfPromise::All(GetCurrentSerialEventTarget(), promises)
|
2017-05-24 19:51:47 +03:00
|
|
|
|
->Then(
|
2020-06-23 08:05:36 +03:00
|
|
|
|
GetCurrentSerialEventTarget(), __func__,
|
2017-05-24 19:51:47 +03:00
|
|
|
|
[holder](const nsTArray<size_t>& sizes) {
|
|
|
|
|
size_t total = 0;
|
|
|
|
|
for (const size_t& size : sizes) {
|
|
|
|
|
total += size;
|
|
|
|
|
}
|
|
|
|
|
holder->Resolve(total, __func__);
|
|
|
|
|
},
|
|
|
|
|
[]() { MOZ_CRASH("Unexpected reject"); });
|
|
|
|
|
|
|
|
|
|
return promise;
|
2014-07-01 09:25:29 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
|
|
|
|
|
|
2020-11-04 20:04:01 +03:00
|
|
|
|
} // namespace mozilla::dom
|
2019-08-05 13:05:28 +03:00
|
|
|
|
|
|
|
|
|
#undef LOG
|