summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/agnostic/eme
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/platforms/agnostic/eme')
-rw-r--r--dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp156
-rw-r--r--dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h54
-rw-r--r--dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h103
-rw-r--r--dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp479
-rw-r--r--dom/media/platforms/agnostic/eme/EMEDecoderModule.h79
-rw-r--r--dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp79
-rw-r--r--dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h68
-rw-r--r--dom/media/platforms/agnostic/eme/moz.build22
8 files changed, 1040 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp
new file mode 100644
index 0000000000..e71632e6d3
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp
@@ -0,0 +1,156 @@
+/* -*- 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 "ChromiumCDMVideoDecoder.h"
+#include "ChromiumCDMProxy.h"
+#include "content_decryption_module.h"
+#include "GMPService.h"
+#include "GMPVideoDecoder.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+
+namespace mozilla {
+
+ChromiumCDMVideoDecoder::ChromiumCDMVideoDecoder(
+ const GMPVideoDecoderParams& aParams, CDMProxy* aCDMProxy)
+ : mCDMParent(aCDMProxy->AsChromiumCDMProxy()->GetCDMParent()),
+ mConfig(aParams.mConfig),
+ mCrashHelper(aParams.mCrashHelper),
+ mGMPThread(GetGMPThread()),
+ mImageContainer(aParams.mImageContainer),
+ mKnowsCompositor(aParams.mKnowsCompositor) {}
+
+ChromiumCDMVideoDecoder::~ChromiumCDMVideoDecoder() = default;
+
+static uint32_t ToCDMH264Profile(uint8_t aProfile) {
+ switch (aProfile) {
+ case 66:
+ return cdm::VideoCodecProfile::kH264ProfileBaseline;
+ case 77:
+ return cdm::VideoCodecProfile::kH264ProfileMain;
+ case 88:
+ return cdm::VideoCodecProfile::kH264ProfileExtended;
+ case 100:
+ return cdm::VideoCodecProfile::kH264ProfileHigh;
+ case 110:
+ return cdm::VideoCodecProfile::kH264ProfileHigh10;
+ case 122:
+ return cdm::VideoCodecProfile::kH264ProfileHigh422;
+ case 144:
+ return cdm::VideoCodecProfile::kH264ProfileHigh444Predictive;
+ }
+ return cdm::VideoCodecProfile::kUnknownVideoCodecProfile;
+}
+
+RefPtr<MediaDataDecoder::InitPromise> ChromiumCDMVideoDecoder::Init() {
+ if (!mCDMParent) {
+ // Must have failed to get the CDMParent from the ChromiumCDMProxy
+ // in our constructor; the MediaKeys must have shut down the CDM
+ // before we had a chance to start up the decoder.
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ gmp::CDMVideoDecoderConfig config;
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ config.mCodec() = cdm::VideoCodec::kCodecH264;
+ config.mProfile() =
+ ToCDMH264Profile(mConfig.mExtraData->SafeElementAt(1, 0));
+ config.mExtraData() = mConfig.mExtraData->Clone();
+ mConvertToAnnexB = true;
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ config.mCodec() = cdm::VideoCodec::kCodecVp8;
+ config.mProfile() = cdm::VideoCodecProfile::kProfileNotNeeded;
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ config.mCodec() = cdm::VideoCodec::kCodecVp9;
+ config.mProfile() = cdm::VideoCodecProfile::kProfileNotNeeded;
+ } else {
+ return MediaDataDecoder::InitPromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ config.mImageWidth() = mConfig.mImage.width;
+ config.mImageHeight() = mConfig.mImage.height;
+ config.mEncryptionScheme() = cdm::EncryptionScheme::kUnencrypted;
+ switch (mConfig.mCrypto.mCryptoScheme) {
+ case CryptoScheme::None:
+ break;
+ case CryptoScheme::Cenc:
+ config.mEncryptionScheme() = cdm::EncryptionScheme::kCenc;
+ break;
+ case CryptoScheme::Cbcs:
+ config.mEncryptionScheme() = cdm::EncryptionScheme::kCbcs;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Should not have unrecognized encryption type");
+ break;
+ }
+
+ RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+ VideoInfo info = mConfig;
+ RefPtr<layers::ImageContainer> imageContainer = mImageContainer;
+ RefPtr<layers::KnowsCompositor> knowsCompositor = mKnowsCompositor;
+ return InvokeAsync(mGMPThread, __func__,
+ [cdm, config, info, imageContainer, knowsCompositor]() {
+ return cdm->InitializeVideoDecoder(
+ config, info, imageContainer, knowsCompositor);
+ });
+}
+
+nsCString ChromiumCDMVideoDecoder::GetDescriptionName() const {
+ return "chromium cdm video decoder"_ns;
+}
+
+nsCString ChromiumCDMVideoDecoder::GetCodecName() const {
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ return "h264"_ns;
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ return "vp8"_ns;
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ return "vp9"_ns;
+ }
+ return "unknown"_ns;
+}
+
+MediaDataDecoder::ConversionRequired ChromiumCDMVideoDecoder::NeedsConversion()
+ const {
+ return mConvertToAnnexB ? ConversionRequired::kNeedAnnexB
+ : ConversionRequired::kNeedNone;
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> ChromiumCDMVideoDecoder::Decode(
+ MediaRawData* aSample) {
+ RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+ RefPtr<MediaRawData> sample = aSample;
+ return InvokeAsync(mGMPThread, __func__, [cdm, sample]() {
+ return cdm->DecryptAndDecodeFrame(sample);
+ });
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> ChromiumCDMVideoDecoder::Flush() {
+ MOZ_ASSERT(mCDMParent);
+ RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+ return InvokeAsync(mGMPThread, __func__,
+ [cdm]() { return cdm->FlushVideoDecoder(); });
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> ChromiumCDMVideoDecoder::Drain() {
+ MOZ_ASSERT(mCDMParent);
+ RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+ return InvokeAsync(mGMPThread, __func__, [cdm]() { return cdm->Drain(); });
+}
+
+RefPtr<ShutdownPromise> ChromiumCDMVideoDecoder::Shutdown() {
+ if (!mCDMParent) {
+ // Must have failed to get the CDMParent from the ChromiumCDMProxy
+ // in our constructor; the MediaKeys must have shut down the CDM
+ // before we had a chance to start up the decoder.
+ return ShutdownPromise::CreateAndResolve(true, __func__);
+ }
+ RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+ return InvokeAsync(mGMPThread, __func__,
+ [cdm]() { return cdm->ShutdownVideoDecoder(); });
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h
new file mode 100644
index 0000000000..c177bf2e48
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+#ifndef ChromiumCDMVideoDecoder_h_
+#define ChromiumCDMVideoDecoder_h_
+
+#include "ChromiumCDMParent.h"
+#include "PlatformDecoderModule.h"
+#include "mozilla/layers/KnowsCompositor.h"
+
+namespace mozilla {
+
+class CDMProxy;
+struct GMPVideoDecoderParams;
+
+DDLoggedTypeDeclNameAndBase(ChromiumCDMVideoDecoder, MediaDataDecoder);
+
+class ChromiumCDMVideoDecoder final
+ : public MediaDataDecoder,
+ public DecoderDoctorLifeLogger<ChromiumCDMVideoDecoder> {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMVideoDecoder, final);
+
+ ChromiumCDMVideoDecoder(const GMPVideoDecoderParams& aParams,
+ CDMProxy* aCDMProxy);
+
+ RefPtr<InitPromise> Init() override;
+ RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
+ RefPtr<FlushPromise> Flush() override;
+ RefPtr<DecodePromise> Drain() override;
+ RefPtr<ShutdownPromise> Shutdown() override;
+ nsCString GetDescriptionName() const override;
+ nsCString GetCodecName() const override;
+ ConversionRequired NeedsConversion() const override;
+
+ private:
+ ~ChromiumCDMVideoDecoder();
+
+ RefPtr<gmp::ChromiumCDMParent> mCDMParent;
+ const VideoInfo mConfig;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+ nsCOMPtr<nsISerialEventTarget> mGMPThread;
+ RefPtr<layers::ImageContainer> mImageContainer;
+ RefPtr<layers::KnowsCompositor> mKnowsCompositor;
+ MozPromiseHolder<InitPromise> mInitPromise;
+ bool mConvertToAnnexB = false;
+};
+
+} // namespace mozilla
+
+#endif // ChromiumCDMVideoDecoder_h_
diff --git a/dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h b/dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h
new file mode 100644
index 0000000000..bafb387f83
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/DecryptThroughputLimit.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DecryptThroughputLimit_h
+#define DecryptThroughputLimit_h
+
+#include <deque>
+
+#include "MediaTimer.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+// We throttle our decrypt so that we don't decrypt more than a certain
+// duration of samples per second. This is to work around bugs in the
+// Widevine CDM. See bugs 1338924, 1342822, 1718223.
+class DecryptThroughputLimit {
+ public:
+ explicit DecryptThroughputLimit(nsISerialEventTarget* aTargetThread,
+ uint32_t aMaxThroughputMs)
+ : mThrottleScheduler(aTargetThread),
+ mMaxThroughput(aMaxThroughputMs / 1000.0) {}
+
+ typedef MozPromise<RefPtr<MediaRawData>, MediaResult, true> ThrottlePromise;
+
+ // Resolves promise after a delay if necessary in order to reduce the
+ // throughput of samples sent through the CDM for decryption.
+ RefPtr<ThrottlePromise> Throttle(MediaRawData* aSample) {
+ // We should only have one decrypt request being processed at once.
+ MOZ_RELEASE_ASSERT(!mThrottleScheduler.IsScheduled());
+
+ const TimeDuration WindowSize = TimeDuration::FromSeconds(0.1);
+ const TimeDuration MaxThroughput =
+ TimeDuration::FromSeconds(mMaxThroughput);
+
+ // Forget decrypts that happened before the start of our window.
+ const TimeStamp now = TimeStamp::Now();
+ while (!mDecrypts.empty() &&
+ mDecrypts.front().mTimestamp < now - WindowSize) {
+ mDecrypts.pop_front();
+ }
+
+ // How much time duration of the media would we have decrypted inside the
+ // time window if we did decrypt this block?
+ TimeDuration sampleDuration = aSample->mDuration.ToTimeDuration();
+ TimeDuration durationDecrypted = sampleDuration;
+ for (const DecryptedJob& job : mDecrypts) {
+ durationDecrypted += job.mSampleDuration;
+ }
+
+ if (durationDecrypted < MaxThroughput) {
+ // If we decrypted a sample of this duration, we would *not* have
+ // decrypted more than our threshold for max throughput, over the
+ // preceding wall time window. So we're safe to proceed with this
+ // decrypt.
+ mDecrypts.push_back(DecryptedJob({now, sampleDuration}));
+ return ThrottlePromise::CreateAndResolve(aSample, __func__);
+ }
+
+ // Otherwise, we need to delay until decrypting won't exceed our
+ // throughput threshold.
+
+ RefPtr<ThrottlePromise> p = mPromiseHolder.Ensure(__func__);
+
+ TimeDuration delay = durationDecrypted - MaxThroughput;
+ TimeStamp target = now + delay;
+ RefPtr<MediaRawData> sample(aSample);
+ mThrottleScheduler.Ensure(
+ target,
+ [this, sample, sampleDuration]() {
+ mThrottleScheduler.CompleteRequest();
+ mDecrypts.push_back(DecryptedJob({TimeStamp::Now(), sampleDuration}));
+ mPromiseHolder.Resolve(sample, __func__);
+ },
+ []() { MOZ_DIAGNOSTIC_ASSERT(false); });
+
+ return p;
+ }
+
+ void Flush() {
+ mThrottleScheduler.Reset();
+ mPromiseHolder.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ }
+
+ private:
+ DelayedScheduler mThrottleScheduler;
+ MozPromiseHolder<ThrottlePromise> mPromiseHolder;
+
+ double mMaxThroughput;
+
+ struct DecryptedJob {
+ TimeStamp mTimestamp;
+ TimeDuration mSampleDuration;
+ };
+ std::deque<DecryptedJob> mDecrypts;
+};
+
+} // namespace mozilla
+
+#endif // DecryptThroughputLimit_h
diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
new file mode 100644
index 0000000000..d4477bd6cd
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -0,0 +1,479 @@
+/* -*- 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 "EMEDecoderModule.h"
+
+#include <inttypes.h>
+
+#include "Adts.h"
+#include "BlankDecoderModule.h"
+#include "ChromiumCDMVideoDecoder.h"
+#include "DecryptThroughputLimit.h"
+#include "GMPDecoderModule.h"
+#include "GMPService.h"
+#include "GMPVideoDecoder.h"
+#include "MP4Decoder.h"
+#include "MediaInfo.h"
+#include "PDMFactory.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsClassHashtable.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+
+typedef MozPromiseRequestHolder<DecryptPromise> DecryptPromiseRequestHolder;
+
+DDLoggedTypeDeclNameAndBase(EMEDecryptor, MediaDataDecoder);
+
+class ADTSSampleConverter {
+ public:
+ explicit ADTSSampleConverter(const AudioInfo& aInfo)
+ : mNumChannels(aInfo.mChannels)
+ // Note: we set profile to 2 if we encounter an extended profile (which
+ // set mProfile to 0 and then set mExtendedProfile) such as HE-AACv2
+ // (profile 5). These can then pass through conversion to ADTS and back.
+ // This is done as ADTS only has 2 bits for profile, and the transform
+ // subtracts one from the value. We check if the profile supplied is > 4
+ // for safety. 2 is used as a fallback value, though it seems the CDM
+ // doesn't care what is set.
+ ,
+ mProfile(aInfo.mProfile < 1 || aInfo.mProfile > 4 ? 2 : aInfo.mProfile),
+ mFrequencyIndex(Adts::GetFrequencyIndex(aInfo.mRate)) {
+ EME_LOG("ADTSSampleConvertor(): aInfo.mProfile=%" PRIi8
+ " aInfo.mExtendedProfile=%" PRIi8,
+ aInfo.mProfile, aInfo.mExtendedProfile);
+ if (aInfo.mProfile < 1 || aInfo.mProfile > 4) {
+ EME_LOG(
+ "ADTSSampleConvertor(): Profile not in [1, 4]! Samples will "
+ "their profile set to 2!");
+ }
+ }
+ bool Convert(MediaRawData* aSample) const {
+ return Adts::ConvertSample(mNumChannels, mFrequencyIndex, mProfile,
+ aSample);
+ }
+ bool Revert(MediaRawData* aSample) const {
+ return Adts::RevertSample(aSample);
+ }
+
+ private:
+ const uint32_t mNumChannels;
+ const uint8_t mProfile;
+ const uint8_t mFrequencyIndex;
+};
+
+class EMEDecryptor final : public MediaDataDecoder,
+ public DecoderDoctorLifeLogger<EMEDecryptor> {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(EMEDecryptor, final);
+
+ EMEDecryptor(MediaDataDecoder* aDecoder, CDMProxy* aProxy,
+ TrackInfo::TrackType aType,
+ const std::function<MediaEventProducer<TrackInfo::TrackType>*()>&
+ aOnWaitingForKey,
+ UniquePtr<ADTSSampleConverter> aConverter = nullptr)
+ : mDecoder(aDecoder),
+ mProxy(aProxy),
+ mSamplesWaitingForKey(
+ new SamplesWaitingForKey(mProxy, aType, aOnWaitingForKey)),
+ mADTSSampleConverter(std::move(aConverter)),
+ mIsShutdown(false) {
+ DDLINKCHILD("decoder", mDecoder.get());
+ }
+
+ RefPtr<InitPromise> Init() override {
+ MOZ_ASSERT(!mIsShutdown);
+ mThread = GetCurrentSerialEventTarget();
+ uint32_t maxThroughputMs = StaticPrefs::media_eme_max_throughput_ms();
+ EME_LOG("EME max-throughput-ms=%" PRIu32, maxThroughputMs);
+ mThroughputLimiter.emplace(mThread, maxThroughputMs);
+
+ return mDecoder->Init();
+ }
+
+ RefPtr<DecodePromise> Decode(MediaRawData* aSample) override {
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+ MOZ_RELEASE_ASSERT(mDecrypts.Count() == 0,
+ "Can only process one sample at a time");
+ RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
+
+ RefPtr<EMEDecryptor> self = this;
+ mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)
+ ->Then(
+ mThread, __func__,
+ [self](const RefPtr<MediaRawData>& aSample) {
+ self->mKeyRequest.Complete();
+ self->ThrottleDecode(aSample);
+ },
+ [self]() { self->mKeyRequest.Complete(); })
+ ->Track(mKeyRequest);
+ return p;
+ }
+
+ void ThrottleDecode(MediaRawData* aSample) {
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+
+ RefPtr<EMEDecryptor> self = this;
+ mThroughputLimiter->Throttle(aSample)
+ ->Then(
+ mThread, __func__,
+ [self](RefPtr<MediaRawData> aSample) {
+ self->mThrottleRequest.Complete();
+ self->AttemptDecode(aSample);
+ },
+ [self]() { self->mThrottleRequest.Complete(); })
+ ->Track(mThrottleRequest);
+ }
+
+ void AttemptDecode(MediaRawData* aSample) {
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+ if (mIsShutdown) {
+ NS_WARNING("EME encrypted sample arrived after shutdown");
+ mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ return;
+ }
+
+ if (mADTSSampleConverter && !mADTSSampleConverter->Convert(aSample)) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failed to convert encrypted AAC sample to ADTS")),
+ __func__);
+ return;
+ }
+
+ const auto& decrypt = mDecrypts.InsertOrUpdate(
+ aSample, MakeUnique<DecryptPromiseRequestHolder>());
+ mProxy->Decrypt(aSample)
+ ->Then(mThread, __func__, this, &EMEDecryptor::Decrypted,
+ &EMEDecryptor::Decrypted)
+ ->Track(*decrypt);
+ }
+
+ void Decrypted(const DecryptResult& aDecrypted) {
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+ MOZ_ASSERT(aDecrypted.mSample);
+
+ UniquePtr<DecryptPromiseRequestHolder> holder;
+ mDecrypts.Remove(aDecrypted.mSample, &holder);
+ if (holder) {
+ holder->Complete();
+ } else {
+ // Decryption is not in the list of decrypt operations waiting
+ // for a result. It must have been flushed or drained. Ignore result.
+ return;
+ }
+
+ if (mADTSSampleConverter &&
+ !mADTSSampleConverter->Revert(aDecrypted.mSample)) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Failed to revert decrypted ADTS sample to AAC")),
+ __func__);
+ return;
+ }
+
+ if (mIsShutdown) {
+ NS_WARNING("EME decrypted sample arrived after shutdown");
+ return;
+ }
+
+ if (aDecrypted.mStatus == eme::NoKeyErr) {
+ // Key became unusable after we sent the sample to CDM to decrypt.
+ // Call Decode() again, so that the sample is enqueued for decryption
+ // if the key becomes usable again.
+ AttemptDecode(aDecrypted.mSample);
+ } else if (aDecrypted.mStatus != eme::Ok) {
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("decrypted.mStatus=%u",
+ uint32_t(aDecrypted.mStatus))),
+ __func__);
+ } else {
+ MOZ_ASSERT(!mIsShutdown);
+ // The sample is no longer encrypted, so clear its crypto metadata.
+ UniquePtr<MediaRawDataWriter> writer(aDecrypted.mSample->CreateWriter());
+ writer->mCrypto = CryptoSample();
+ RefPtr<EMEDecryptor> self = this;
+ mDecoder->Decode(aDecrypted.mSample)
+ ->Then(mThread, __func__,
+ [self](DecodePromise::ResolveOrRejectValue&& aValue) {
+ self->mDecodeRequest.Complete();
+ self->mDecodePromise.ResolveOrReject(std::move(aValue),
+ __func__);
+ })
+ ->Track(mDecodeRequest);
+ }
+ }
+
+ RefPtr<FlushPromise> Flush() override {
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mIsShutdown);
+ mKeyRequest.DisconnectIfExists();
+ mThrottleRequest.DisconnectIfExists();
+ mDecodeRequest.DisconnectIfExists();
+ mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ mThroughputLimiter->Flush();
+ for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+ auto holder = iter.UserData();
+ holder->DisconnectIfExists();
+ iter.Remove();
+ }
+ RefPtr<SamplesWaitingForKey> k = mSamplesWaitingForKey;
+ return mDecoder->Flush()->Then(mThread, __func__, [k]() {
+ k->Flush();
+ return FlushPromise::CreateAndResolve(true, __func__);
+ });
+ }
+
+ RefPtr<DecodePromise> Drain() override {
+ MOZ_ASSERT(mThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mIsShutdown);
+ MOZ_ASSERT(mDecodePromise.IsEmpty() && !mDecodeRequest.Exists(),
+ "Must wait for decoding to complete");
+ for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+ auto holder = iter.UserData();
+ holder->DisconnectIfExists();
+ iter.Remove();
+ }
+ return mDecoder->Drain();
+ }
+
+ RefPtr<ShutdownPromise> Shutdown() override {
+ // mThread may not be set if Init hasn't been called first.
+ MOZ_ASSERT(!mThread || mThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mIsShutdown);
+ mIsShutdown = true;
+ mSamplesWaitingForKey->BreakCycles();
+ mSamplesWaitingForKey = nullptr;
+ RefPtr<MediaDataDecoder> decoder = std::move(mDecoder);
+ mProxy = nullptr;
+ return decoder->Shutdown();
+ }
+
+ nsCString GetDescriptionName() const override {
+ return mDecoder->GetDescriptionName();
+ }
+
+ nsCString GetCodecName() const override { return mDecoder->GetCodecName(); }
+
+ ConversionRequired NeedsConversion() const override {
+ return mDecoder->NeedsConversion();
+ }
+
+ private:
+ ~EMEDecryptor() = default;
+
+ RefPtr<MediaDataDecoder> mDecoder;
+ nsCOMPtr<nsISerialEventTarget> mThread;
+ RefPtr<CDMProxy> mProxy;
+ nsClassHashtable<nsRefPtrHashKey<MediaRawData>, DecryptPromiseRequestHolder>
+ mDecrypts;
+ RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+ MozPromiseRequestHolder<SamplesWaitingForKey::WaitForKeyPromise> mKeyRequest;
+ Maybe<DecryptThroughputLimit> mThroughputLimiter;
+ MozPromiseRequestHolder<DecryptThroughputLimit::ThrottlePromise>
+ mThrottleRequest;
+ MozPromiseHolder<DecodePromise> mDecodePromise;
+ MozPromiseHolder<DecodePromise> mDrainPromise;
+ MozPromiseHolder<FlushPromise> mFlushPromise;
+ MozPromiseRequestHolder<DecodePromise> mDecodeRequest;
+ UniquePtr<ADTSSampleConverter> mADTSSampleConverter;
+ bool mIsShutdown;
+};
+
+EMEMediaDataDecoderProxy::EMEMediaDataDecoderProxy(
+ const CreateDecoderParams& aParams,
+ already_AddRefed<MediaDataDecoder> aProxyDecoder,
+ already_AddRefed<nsISerialEventTarget> aProxyThread, CDMProxy* aProxy)
+ : MediaDataDecoderProxy(std::move(aProxyDecoder), std::move(aProxyThread)),
+ mThread(GetCurrentSerialEventTarget()),
+ mSamplesWaitingForKey(new SamplesWaitingForKey(
+ aProxy, aParams.mType, aParams.mOnWaitingForKeyEvent)),
+ mProxy(aProxy) {}
+
+EMEMediaDataDecoderProxy::EMEMediaDataDecoderProxy(
+ const CreateDecoderParams& aParams,
+ already_AddRefed<MediaDataDecoder> aProxyDecoder, CDMProxy* aProxy)
+ : MediaDataDecoderProxy(std::move(aProxyDecoder),
+ do_AddRef(GetCurrentSerialEventTarget())),
+ mThread(GetCurrentSerialEventTarget()),
+ mSamplesWaitingForKey(new SamplesWaitingForKey(
+ aProxy, aParams.mType, aParams.mOnWaitingForKeyEvent)),
+ mProxy(aProxy) {}
+
+RefPtr<MediaDataDecoder::DecodePromise> EMEMediaDataDecoderProxy::Decode(
+ MediaRawData* aSample) {
+ RefPtr<EMEMediaDataDecoderProxy> self = this;
+ RefPtr<MediaRawData> sample = aSample;
+ return InvokeAsync(mThread, __func__, [self, this, sample]() {
+ RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
+ mSamplesWaitingForKey->WaitIfKeyNotUsable(sample)
+ ->Then(
+ mThread, __func__,
+ [self, this](RefPtr<MediaRawData> aSample) {
+ mKeyRequest.Complete();
+
+ MediaDataDecoderProxy::Decode(aSample)
+ ->Then(mThread, __func__,
+ [self,
+ this](DecodePromise::ResolveOrRejectValue&& aValue) {
+ mDecodeRequest.Complete();
+ mDecodePromise.ResolveOrReject(std::move(aValue),
+ __func__);
+ })
+ ->Track(mDecodeRequest);
+ },
+ [self]() {
+ self->mKeyRequest.Complete();
+ MOZ_CRASH("Should never get here");
+ })
+ ->Track(mKeyRequest);
+
+ return p;
+ });
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> EMEMediaDataDecoderProxy::Flush() {
+ RefPtr<EMEMediaDataDecoderProxy> self = this;
+ return InvokeAsync(mThread, __func__, [self, this]() {
+ mKeyRequest.DisconnectIfExists();
+ mDecodeRequest.DisconnectIfExists();
+ mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ return MediaDataDecoderProxy::Flush();
+ });
+}
+
+RefPtr<ShutdownPromise> EMEMediaDataDecoderProxy::Shutdown() {
+ RefPtr<EMEMediaDataDecoderProxy> self = this;
+ return InvokeAsync(mThread, __func__, [self, this]() {
+ mSamplesWaitingForKey->BreakCycles();
+ mSamplesWaitingForKey = nullptr;
+ mProxy = nullptr;
+ return MediaDataDecoderProxy::Shutdown();
+ });
+}
+
+EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM)
+ : mProxy(aProxy), mPDM(aPDM) {}
+
+EMEDecoderModule::~EMEDecoderModule() = default;
+
+static already_AddRefed<MediaDataDecoderProxy> CreateDecoderWrapper(
+ CDMProxy* aProxy, const CreateDecoderParams& aParams) {
+ RefPtr<gmp::GeckoMediaPluginService> s(
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (!s) {
+ return nullptr;
+ }
+ nsCOMPtr<nsISerialEventTarget> thread(s->GetGMPThread());
+ if (!thread) {
+ return nullptr;
+ }
+ RefPtr<MediaDataDecoderProxy> decoder(
+ new EMEMediaDataDecoderProxy(aParams,
+ do_AddRef(new ChromiumCDMVideoDecoder(
+ GMPVideoDecoderParams(aParams), aProxy)),
+ thread.forget(), aProxy));
+ return decoder.forget();
+}
+
+RefPtr<EMEDecoderModule::CreateDecoderPromise>
+EMEDecoderModule::AsyncCreateDecoder(const CreateDecoderParams& aParams) {
+ MOZ_ASSERT(aParams.mConfig.mCrypto.IsEncrypted());
+ MOZ_ASSERT(mPDM);
+
+ if (aParams.mConfig.IsVideo()) {
+ if (StaticPrefs::media_eme_video_blank()) {
+ EME_LOG(
+ "EMEDecoderModule::CreateVideoDecoder() creating a blank decoder.");
+ RefPtr<PlatformDecoderModule> m(BlankDecoderModule::Create());
+ RefPtr<MediaDataDecoder> decoder = m->CreateVideoDecoder(aParams);
+ return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve(decoder,
+ __func__);
+ }
+
+ if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr) !=
+ media::DecodeSupport::Unsupported) {
+ // GMP decodes. Assume that means it can decrypt too.
+ return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve(
+ CreateDecoderWrapper(mProxy, aParams), __func__);
+ }
+
+ RefPtr<EMEDecoderModule::CreateDecoderPromise> p =
+ mPDM->CreateDecoder(aParams)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this},
+ params = CreateDecoderParamsForAsync(aParams)](
+ RefPtr<MediaDataDecoder>&& aDecoder) {
+ RefPtr<MediaDataDecoder> emeDecoder(
+ new EMEDecryptor(aDecoder, self->mProxy, params.mType,
+ params.mOnWaitingForKeyEvent));
+ return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve(
+ emeDecoder, __func__);
+ },
+ [](const MediaResult& aError) {
+ return EMEDecoderModule::CreateDecoderPromise::CreateAndReject(
+ aError, __func__);
+ });
+ return p;
+ }
+
+ MOZ_ASSERT(aParams.mConfig.IsAudio());
+
+ // We don't support using the GMP to decode audio.
+ MOZ_ASSERT(SupportsMimeType(aParams.mConfig.mMimeType, nullptr) ==
+ media::DecodeSupport::Unsupported);
+ MOZ_ASSERT(mPDM);
+
+ if (StaticPrefs::media_eme_audio_blank()) {
+ EME_LOG("EMEDecoderModule::CreateAudioDecoder() creating a blank decoder.");
+ RefPtr<PlatformDecoderModule> m(BlankDecoderModule::Create());
+ RefPtr<MediaDataDecoder> decoder = m->CreateAudioDecoder(aParams);
+ return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve(decoder,
+ __func__);
+ }
+
+ UniquePtr<ADTSSampleConverter> converter = nullptr;
+ if (MP4Decoder::IsAAC(aParams.mConfig.mMimeType)) {
+ // The CDM expects encrypted AAC to be in ADTS format.
+ // See bug 1433344.
+ converter = MakeUnique<ADTSSampleConverter>(aParams.AudioConfig());
+ }
+
+ RefPtr<EMEDecoderModule::CreateDecoderPromise> p =
+ mPDM->CreateDecoder(aParams)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, params = CreateDecoderParamsForAsync(aParams),
+ converter = std::move(converter)](
+ RefPtr<MediaDataDecoder>&& aDecoder) mutable {
+ RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(
+ aDecoder, self->mProxy, params.mType,
+ params.mOnWaitingForKeyEvent, std::move(converter)));
+ return EMEDecoderModule::CreateDecoderPromise::CreateAndResolve(
+ emeDecoder, __func__);
+ },
+ [](const MediaResult& aError) {
+ return EMEDecoderModule::CreateDecoderPromise::CreateAndReject(
+ aError, __func__);
+ });
+ return p;
+}
+
+media::DecodeSupportSet EMEDecoderModule::SupportsMimeType(
+ const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const {
+ Maybe<nsCString> keySystem;
+ keySystem.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+ return GMPDecoderModule::SupportsMimeType(
+ aMimeType, nsLiteralCString(CHROMIUM_CDM_API), keySystem);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
new file mode 100644
index 0000000000..06fea6e650
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#if !defined(EMEDecoderModule_h_)
+# define EMEDecoderModule_h_
+
+# include "MediaDataDecoderProxy.h"
+# include "PlatformDecoderModule.h"
+# include "SamplesWaitingForKey.h"
+
+namespace mozilla {
+
+class CDMProxy;
+class PDMFactory;
+
+class EMEDecoderModule : public PlatformDecoderModule {
+ public:
+ EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM);
+
+ protected:
+ RefPtr<CreateDecoderPromise> AsyncCreateDecoder(
+ const CreateDecoderParams& aParams) override;
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder> CreateVideoDecoder(
+ const CreateDecoderParams& aParams) override {
+ MOZ_CRASH("Not used");
+ }
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder> CreateAudioDecoder(
+ const CreateDecoderParams& aParams) override {
+ MOZ_CRASH("Not used");
+ }
+
+ media::DecodeSupportSet SupportsMimeType(
+ const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+ private:
+ virtual ~EMEDecoderModule();
+ RefPtr<CDMProxy> mProxy;
+ // Will be null if CDM has decoding capability.
+ RefPtr<PDMFactory> mPDM;
+};
+
+DDLoggedTypeDeclNameAndBase(EMEMediaDataDecoderProxy, MediaDataDecoderProxy);
+
+class EMEMediaDataDecoderProxy
+ : public MediaDataDecoderProxy,
+ public DecoderDoctorLifeLogger<EMEMediaDataDecoderProxy> {
+ public:
+ EMEMediaDataDecoderProxy(const CreateDecoderParams& aParams,
+ already_AddRefed<MediaDataDecoder> aProxyDecoder,
+ already_AddRefed<nsISerialEventTarget> aProxyThread,
+ CDMProxy* aProxy);
+ EMEMediaDataDecoderProxy(const CreateDecoderParams& aParams,
+ already_AddRefed<MediaDataDecoder> aProxyDecoder,
+ CDMProxy* aProxy);
+
+ RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
+ RefPtr<FlushPromise> Flush() override;
+ RefPtr<ShutdownPromise> Shutdown() override;
+
+ private:
+ nsCOMPtr<nsISerialEventTarget> mThread;
+ RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+ MozPromiseRequestHolder<SamplesWaitingForKey::WaitForKeyPromise> mKeyRequest;
+ MozPromiseHolder<DecodePromise> mDecodePromise;
+ MozPromiseRequestHolder<DecodePromise> mDecodeRequest;
+ RefPtr<CDMProxy> mProxy;
+};
+
+} // namespace mozilla
+
+#endif // EMEDecoderModule_h_
diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
new file mode 100644
index 0000000000..23d79ce56a
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "SamplesWaitingForKey.h"
+
+#include "MediaData.h"
+#include "MediaEventSource.h"
+#include "mozilla/CDMCaps.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/TaskQueue.h"
+
+namespace mozilla {
+
+SamplesWaitingForKey::SamplesWaitingForKey(
+ CDMProxy* aProxy, TrackInfo::TrackType aType,
+ const std::function<MediaEventProducer<TrackInfo::TrackType>*()>&
+ aOnWaitingForKeyEvent)
+ : mMutex("SamplesWaitingForKey"),
+ mProxy(aProxy),
+ mType(aType),
+ mOnWaitingForKeyEvent(aOnWaitingForKeyEvent) {}
+
+SamplesWaitingForKey::~SamplesWaitingForKey() { Flush(); }
+
+RefPtr<SamplesWaitingForKey::WaitForKeyPromise>
+SamplesWaitingForKey::WaitIfKeyNotUsable(MediaRawData* aSample) {
+ if (!aSample || !aSample->mCrypto.IsEncrypted() || !mProxy) {
+ return WaitForKeyPromise::CreateAndResolve(aSample, __func__);
+ }
+ auto caps = mProxy->Capabilites().Lock();
+ const auto& keyid = aSample->mCrypto.mKeyId;
+ if (caps->IsKeyUsable(keyid)) {
+ return WaitForKeyPromise::CreateAndResolve(aSample, __func__);
+ }
+ SampleEntry entry;
+ entry.mSample = aSample;
+ RefPtr<WaitForKeyPromise> p = entry.mPromise.Ensure(__func__);
+ {
+ MutexAutoLock lock(mMutex);
+ mSamples.AppendElement(std::move(entry));
+ }
+ if (mOnWaitingForKeyEvent && mOnWaitingForKeyEvent()) {
+ mOnWaitingForKeyEvent()->Notify(mType);
+ }
+ caps->NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this);
+ return p;
+}
+
+void SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId) {
+ MutexAutoLock lock(mMutex);
+ size_t i = 0;
+ while (i < mSamples.Length()) {
+ auto& entry = mSamples[i];
+ if (aKeyId == entry.mSample->mCrypto.mKeyId) {
+ entry.mPromise.Resolve(entry.mSample, __func__);
+ mSamples.RemoveElementAt(i);
+ } else {
+ i++;
+ }
+ }
+}
+
+void SamplesWaitingForKey::Flush() {
+ MutexAutoLock lock(mMutex);
+ for (auto& sample : mSamples) {
+ sample.mPromise.Reject(true, __func__);
+ }
+ mSamples.Clear();
+}
+
+void SamplesWaitingForKey::BreakCycles() {
+ MutexAutoLock lock(mMutex);
+ mProxy = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
new file mode 100644
index 0000000000..06d72e3aae
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
@@ -0,0 +1,68 @@
+/* -*- 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/. */
+
+#ifndef SamplesWaitingForKey_h_
+#define SamplesWaitingForKey_h_
+
+#include <functional>
+
+#include "MediaInfo.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+typedef nsTArray<uint8_t> CencKeyId;
+
+class CDMProxy;
+template <typename... Es>
+class MediaEventProducer;
+class MediaRawData;
+
+// Encapsulates the task of waiting for the CDMProxy to have the necessary
+// keys to decrypt a given sample.
+class SamplesWaitingForKey {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
+
+ typedef MozPromise<RefPtr<MediaRawData>, bool, /* IsExclusive = */ true>
+ WaitForKeyPromise;
+
+ SamplesWaitingForKey(
+ CDMProxy* aProxy, TrackInfo::TrackType aType,
+ const std::function<MediaEventProducer<TrackInfo::TrackType>*()>&
+ aOnWaitingForKeyEvent);
+
+ // Returns a promise that will be resolved if or when a key for decoding the
+ // sample becomes usable.
+ RefPtr<WaitForKeyPromise> WaitIfKeyNotUsable(MediaRawData* aSample);
+
+ void NotifyUsable(const CencKeyId& aKeyId);
+
+ void Flush();
+
+ void BreakCycles();
+
+ protected:
+ ~SamplesWaitingForKey();
+
+ private:
+ Mutex mMutex MOZ_UNANNOTATED;
+ RefPtr<CDMProxy> mProxy;
+ struct SampleEntry {
+ RefPtr<MediaRawData> mSample;
+ MozPromiseHolder<WaitForKeyPromise> mPromise;
+ };
+ nsTArray<SampleEntry> mSamples;
+ const TrackInfo::TrackType mType;
+ const std::function<MediaEventProducer<TrackInfo::TrackType>*()>
+ mOnWaitingForKeyEvent;
+};
+
+} // namespace mozilla
+
+#endif // SamplesWaitingForKey_h_
diff --git a/dom/media/platforms/agnostic/eme/moz.build b/dom/media/platforms/agnostic/eme/moz.build
new file mode 100644
index 0000000000..34f0007b3b
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS += [
+ "ChromiumCDMVideoDecoder.h",
+ "DecryptThroughputLimit.h",
+ "EMEDecoderModule.h",
+ "SamplesWaitingForKey.h",
+]
+
+UNIFIED_SOURCES += [
+ "ChromiumCDMVideoDecoder.cpp",
+ "EMEDecoderModule.cpp",
+ "SamplesWaitingForKey.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"