Bug 1145011 - Implement waitingforkey event. r=jya
Bubble information from SamplesWaitingForKey to an HTMLMediaElement so that we
can emit a waitingForKey event. If we are unable to decode more samples due to
needing a key the event will be signalled. See
http://w3c.github.io/encrypted-media/#dom-evt-waitingforkey for more information
on this event.
The code in place before this patch handles updating readyState when we are
waiting for a key, this patch adds the event which should be emitted in such a
case. The spec defines certain preconditions should be the case before running
the algo to emit this event. For example, the element should potentially be
playing, and it should have at least HAVE_FUTURE_DATA ready state. This is not
strictly true for when the new code is run, due how existing code handles ready
state. We are honoring the spirit of the spec, though the letter of the spec is
lightly gone against in the handling of the preconditions.
MozReview-Commit-ID: LKlDd4wkRSE
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -902,16 +902,17 @@ HTMLMediaElement::OnChannelRedirect(nsIC
MOZ_ASSERT(mChannelLoader);
return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
}
void HTMLMediaElement::ShutdownDecoder()
{
RemoveMediaElementFromURITable();
NS_ASSERTION(mDecoder, "Must have decoder to shut down");
+ mWaitingForKeyListener.DisconnectIfExists();
mDecoder->Shutdown();
mDecoder = nullptr;
}
void HTMLMediaElement::AbortExistingLoads()
{
#ifdef MOZ_EME
// If there is no existing decoder then we don't have anything to
@@ -978,16 +979,17 @@ void HTMLMediaElement::AbortExistingLoad
mHaveQueuedSelectResource = false;
mSuspendedForPreloadNone = false;
mDownloadSuspendedByCache = false;
mMediaInfo = MediaInfo();
mIsEncrypted = false;
#ifdef MOZ_EME
mPendingEncryptedInitData.mInitDatas.Clear();
#endif // MOZ_EME
+ mWaitingForKey = false;
mSourcePointer = nullptr;
mTags = nullptr;
if (mNetworkState != nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
NS_ASSERTION(!mDecoder && !mSrcStream, "How did someone setup a new stream/decoder already?");
// ChangeNetworkState() will call UpdateAudioChannelPlayingState()
// indirectly which depends on mPaused. So we need to update mPaused first.
@@ -2504,16 +2506,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
mHasPlayedOrSeeked(false),
mHasSelfReference(false),
mShuttingDown(false),
mSuspendedForPreloadNone(false),
mSrcStreamIsPlaying(false),
mMediaSecurityVerified(false),
mCORSMode(CORS_NONE),
mIsEncrypted(false),
+ mWaitingForKey(false),
mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
mAudioChannelVolume(1.0),
mPlayingThroughTheAudioChannel(false),
mDisableVideo(false),
mPlayBlockedBecauseHidden(false),
mElementInTreeState(ELEMENT_NOT_INTREE),
mHasUserInteraction(false),
mFirstFrameLoaded(false),
@@ -3419,16 +3422,23 @@ nsresult HTMLMediaElement::FinishDecoder
} else {
// CDM must have crashed.
ShutdownDecoder();
return NS_ERROR_FAILURE;
}
}
#endif
+ MediaEventSource<void>* waitingForKeyProducer = mDecoder->WaitingForKeyEvent();
+ // Not every decoder will produce waitingForKey events, only add ones that can
+ if (waitingForKeyProducer) {
+ mWaitingForKeyListener = waitingForKeyProducer->Connect(
+ AbstractThread::MainThread(), this, &HTMLMediaElement::CannotDecryptWaitingForKey);
+ }
+
if (mChannelLoader) {
mChannelLoader->Done();
mChannelLoader = nullptr;
}
AddMediaElementToURITable();
// We may want to suspend the new stream now.
@@ -4481,16 +4491,17 @@ void HTMLMediaElement::ChangeReadyState(
DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
}
CheckAutoplayDataReady();
if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
IsPotentiallyPlaying()) {
+ mWaitingForKey = false;
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
}
if (oldState < nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA &&
mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
DispatchAsyncEvent(NS_LITERAL_STRING("canplaythrough"));
}
}
@@ -5866,16 +5877,37 @@ HTMLMediaElement::GetTopLevelPrincipal()
if (!doc) {
return nullptr;
}
principal = doc->NodePrincipal();
return principal.forget();
}
#endif // MOZ_EME
+void
+HTMLMediaElement::CannotDecryptWaitingForKey()
+{
+ // See: http://w3c.github.io/encrypted-media/#dom-evt-waitingforkey
+ // Spec: 7.5.4 Queue a "waitingforkey" Event
+ // Spec: 1. Let the media element be the specified HTMLMediaElement object.
+
+ // Note, existing code will handle the ready state of this element, as
+ // such this function does not handle changing or checking mReadyState.
+
+ // Spec: 2. If the media element's waiting for key value is true, abort these steps.
+ if (!mWaitingForKey) {
+ // Spec: 3. Set the media element's waiting for key value to true.
+ // Spec: 4. Queue a task to fire a simple event named waitingforkey at the media element.
+ DispatchAsyncEvent(NS_LITERAL_STRING("waitingforkey"));
+ mWaitingForKey = true;
+ // No need to explicitly suspend playback, it happens automatically when
+ // it's starving for decoded frames.
+ }
+}
+
NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged(bool aCapture)
{
MOZ_ASSERT(mAudioChannelAgent);
MOZ_ASSERT(HasAudio());
if (!OwnerDoc()->GetInnerWindow()) {
return NS_OK;
}
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -636,16 +636,18 @@ public:
// Returns the principal of the "top level" document; the origin displayed
// in the URL bar of the browser window.
already_AddRefed<nsIPrincipal> GetTopLevelPrincipal();
bool ContainsRestrictedContent();
#endif // MOZ_EME
+ void CannotDecryptWaitingForKey();
+
bool MozAutoplayEnabled() const
{
return mAutoplayEnabled;
}
already_AddRefed<DOMMediaStream> MozCaptureStream(ErrorResult& aRv,
MediaStreamGraph* aGraph = nullptr);
@@ -1532,16 +1534,24 @@ protected:
CORSMode mCORSMode;
// Info about the played media.
MediaInfo mMediaInfo;
// True if the media has encryption information.
bool mIsEncrypted;
+ // True when the CDM cannot decrypt the current block, and the
+ // waitingforkey event has been fired. Back to false when keys have become
+ // available and we can advance the current playback position.
+ bool mWaitingForKey;
+
+ // Listens for waitingForKey events from the owned decoder.
+ MediaEventListener mWaitingForKeyListener;
+
#ifdef MOZ_EME
// Init Data that needs to be sent in 'encrypted' events in MetadataLoaded().
EncryptionInfo mPendingEncryptedInitData;
#endif // MOZ_EME
// True if the media's channel's download has been suspended.
Watchable<bool> mDownloadSuspendedByCache;
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -67,16 +67,28 @@ public:
// MediaDecoderReader will register with this event to receive notifications
// in order to update buffer ranges.
// Return null if this decoder doesn't support the event.
virtual MediaEventSource<void>* DataArrivedEvent()
{
return nullptr;
}
+ // Notify the media decoder that a decryption key is required before emitting
+ // further output. This only needs to be overridden for decoders that expect
+ // encryption, such as the MediaSource decoder.
+ virtual void NotifyWaitingForKey() {}
+
+ // Return an event that will be notified when a decoder is waiting for a
+ // decryption key before it can return more output.
+ virtual MediaEventSource<void>* WaitingForKeyEvent()
+ {
+ return nullptr;
+ }
+
protected:
virtual void UpdateEstimatedMediaDuration(int64_t aDuration) {};
public:
void DispatchUpdateEstimatedMediaDuration(int64_t aDuration)
{
NS_DispatchToMainThread(NewRunnableMethod<int64_t>(this,
&AbstractMediaDecoder::UpdateEstimatedMediaDuration,
aDuration));
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1538,16 +1538,24 @@ MediaFormatReader::DropDecodedSamples(Tr
decoder.mOutput.Clear();
decoder.mSizeOfQueue -= lengthDecodedQueue;
if (aTrack == TrackInfo::kVideoTrack && mDecoder) {
mDecoder->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
}
}
void
+MediaFormatReader::WaitingForKey(TrackType aTrack)
+{
+ if (mDecoder) {
+ mDecoder->NotifyWaitingForKey();
+ }
+}
+
+void
MediaFormatReader::SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold)
{
MOZ_ASSERT(OnTaskQueue());
LOG("Skipping up to %lld", aTimeThreshold.ToMicroseconds());
// We've reached SkipVideoDemuxToNextKeyFrame when our decoding is late.
// As such we can drop all already decoded samples and discard all pending
// samples.
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -180,16 +180,17 @@ private:
// DecoderCallback proxies the MediaDataDecoderCallback calls to these
// functions.
void Output(TrackType aType, MediaData* aSample);
void InputExhausted(TrackType aTrack);
void Error(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
void Reset(TrackType aTrack);
void DrainComplete(TrackType aTrack);
void DropDecodedSamples(TrackType aTrack);
+ void WaitingForKey(TrackType aTrack);
bool ShouldSkip(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold);
void SetVideoDecodeThreshold();
size_t SizeOfQueue(TrackType aTrack);
RefPtr<PDMFactory> mPlatform;
@@ -214,16 +215,19 @@ private:
mReader->DrainComplete(mType);
}
void ReleaseMediaResources() override {
mReader->ReleaseResources();
}
bool OnReaderTaskQueue() override {
return mReader->OnTaskQueue();
}
+ void WaitingForKey() override {
+ mReader->WaitingForKey(mType);
+ }
private:
MediaFormatReader* mReader;
TrackType mType;
};
struct DecoderData {
DecoderData(MediaFormatReader* aOwner,
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -311,16 +311,28 @@ MediaSourceDecoder::CanPlayThrough()
TimeUnit timeAhead =
std::min(duration, currentPosition + TimeUnit::FromSeconds(30));
TimeInterval interval(currentPosition,
timeAhead,
MediaSourceDemuxer::EOS_FUZZ);
return GetBuffered().Contains(ClampIntervalToEnd(interval));
}
+void
+MediaSourceDecoder::NotifyWaitingForKey()
+{
+ mWaitingForKeyEvent.Notify();
+}
+
+MediaEventSource<void>*
+MediaSourceDecoder::WaitingForKeyEvent()
+{
+ return &mWaitingForKeyEvent;
+}
+
TimeInterval
MediaSourceDecoder::ClampIntervalToEnd(const TimeInterval& aInterval)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mEnded) {
return aInterval;
}
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -75,25 +75,30 @@ public:
// buffered data. Used for debugging purposes.
void GetMozDebugReaderData(nsAString& aString) override;
void AddSizeOfResources(ResourceSizes* aSizes) override;
MediaDecoderOwner::NextFrameStatus NextFrameBufferedStatus() override;
bool CanPlayThrough() override;
+ void NotifyWaitingForKey() override;
+
+ MediaEventSource<void>* WaitingForKeyEvent() override;
+
private:
void DoSetMediaSourceDuration(double aDuration);
media::TimeInterval ClampIntervalToEnd(const media::TimeInterval& aInterval);
// The owning MediaSource holds a strong reference to this decoder, and
// calls Attach/DetachMediaSource on this decoder to set and clear
// mMediaSource.
dom::MediaSource* mMediaSource;
RefPtr<MediaSourceDemuxer> mDemuxer;
RefPtr<MediaFormatReader> mReader;
+ MediaEventProducer<void> mWaitingForKeyEvent;
bool mEnded;
};
} // namespace mozilla
#endif /* MOZILLA_MEDIASOURCEDECODER_H_ */
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -174,19 +174,25 @@ public:
virtual void Error(MediaDataDecoderError aError) = 0;
// Denotes that the last input sample has been inserted into the decoder,
// and no more output can be produced unless more input is sent.
virtual void InputExhausted() = 0;
virtual void DrainComplete() = 0;
- virtual void ReleaseMediaResources() {};
+ virtual void ReleaseMediaResources() {}
virtual bool OnReaderTaskQueue() = 0;
+
+ // Denotes that a pending encryption key is preventing more input being fed
+ // into the decoder. This only needs to be overridden for callbacks that
+ // handle encryption. E.g. benchmarking does not use eme, so this need
+ // not be overridden in that case.
+ virtual void WaitingForKey() {}
};
// MediaDataDecoder is the interface exposed by decoders created by the
// PlatformDecoderModule's Create*Decoder() functions. The type of
// media data that the decoder accepts as valid input and produces as
// output is determined when the MediaDataDecoder is created.
//
// Unless otherwise noted, all functions are only called on the decode task
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -29,17 +29,18 @@ public:
EMEDecryptor(MediaDataDecoder* aDecoder,
MediaDataDecoderCallback* aCallback,
CDMProxy* aProxy,
TaskQueue* aDecodeTaskQueue)
: mDecoder(aDecoder)
, mCallback(aCallback)
, mTaskQueue(aDecodeTaskQueue)
, mProxy(aProxy)
- , mSamplesWaitingForKey(new SamplesWaitingForKey(this, mTaskQueue, mProxy))
+ , mSamplesWaitingForKey(new SamplesWaitingForKey(this, this->mCallback,
+ mTaskQueue, mProxy))
, mIsShutdown(false)
{
}
RefPtr<InitPromise> Init() override {
MOZ_ASSERT(!mIsShutdown);
return mDecoder->Init();
}
@@ -166,17 +167,18 @@ private:
class EMEMediaDataDecoderProxy : public MediaDataDecoderProxy {
public:
EMEMediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,
MediaDataDecoderCallback* aCallback,
CDMProxy* aProxy,
TaskQueue* aTaskQueue)
: MediaDataDecoderProxy(Move(aProxyThread), aCallback)
- , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aTaskQueue, aProxy))
+ , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback,
+ aTaskQueue, aProxy))
, mProxy(aProxy)
{
}
nsresult Input(MediaRawData* aSample) override;
nsresult Shutdown() override;
private:
--- a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
@@ -7,20 +7,22 @@
#include "SamplesWaitingForKey.h"
#include "mozilla/CDMProxy.h"
#include "mozilla/CDMCaps.h"
#include "MediaData.h"
namespace mozilla {
SamplesWaitingForKey::SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
TaskQueue* aTaskQueue,
CDMProxy* aProxy)
: mMutex("SamplesWaitingForKey")
, mDecoder(aDecoder)
+ , mDecoderCallback(aCallback)
, mTaskQueue(aTaskQueue)
, mProxy(aProxy)
{
}
SamplesWaitingForKey::~SamplesWaitingForKey()
{
}
@@ -33,16 +35,17 @@ SamplesWaitingForKey::WaitIfKeyNotUsable
}
CDMCaps::AutoLock caps(mProxy->Capabilites());
const auto& keyid = aSample->mCrypto.mKeyId;
if (!caps.IsKeyUsable(keyid)) {
{
MutexAutoLock lock(mMutex);
mSamples.AppendElement(aSample);
}
+ mDecoderCallback->WaitingForKey();
caps.NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this);
return true;
}
return false;
}
void
SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId)
--- a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
@@ -13,23 +13,24 @@
namespace mozilla {
typedef nsTArray<uint8_t> CencKeyId;
class CDMProxy;
// Encapsulates the task of waiting for the CDMProxy to have the necessary
-// keys to decypt a given sample.
+// keys to decrypt a given sample.
class SamplesWaitingForKey {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
explicit SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
TaskQueue* aTaskQueue,
CDMProxy* aProxy);
// Returns true if we need to wait for a key to become usable.
// Will callback MediaDataDecoder::Input(aSample) on mDecoder once the
// sample is ready to be decrypted. The order of input samples is
// preserved.
bool WaitIfKeyNotUsable(MediaRawData* aSample);
@@ -41,16 +42,17 @@ public:
void BreakCycles();
protected:
~SamplesWaitingForKey();
private:
Mutex mMutex;
RefPtr<MediaDataDecoder> mDecoder;
+ MediaDataDecoderCallback* mDecoderCallback;
RefPtr<TaskQueue> mTaskQueue;
RefPtr<CDMProxy> mProxy;
nsTArray<RefPtr<MediaRawData>> mSamples;
};
} // namespace mozilla
#endif // SamplesWaitingForKey_h_
--- a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
@@ -90,16 +90,21 @@ public:
void FlushComplete();
bool OnReaderTaskQueue() override
{
return mProxyCallback->OnReaderTaskQueue();
}
+ void WaitingForKey() override
+ {
+ mProxyCallback->WaitingForKey();
+ }
+
private:
MediaDataDecoderProxy* mProxyDecoder;
MediaDataDecoderCallback* mProxyCallback;
};
class MediaDataDecoderProxy : public MediaDataDecoder {
public:
MediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,