--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -180,16 +180,200 @@ ResolvePromisesWithUndefined(const nsTAr
static void
RejectPromises(const nsTArray<RefPtr<Promise>>& aPromises, nsresult aError)
{
for (auto& promise : aPromises) {
promise->MaybeReject(aError);
}
}
+class HTMLMediaElement::SetMediaKeysRunner : public Runnable
+{
+public:
+ explicit SetMediaKeysRunner(const char* aName, HTMLMediaElement* aElement)
+ : Runnable(aName)
+ , mElement(aElement)
+ {
+ MOZ_ASSERT(mElement);
+ }
+ ~SetMediaKeysRunner() {}
+
+ void RemoveAssociation()
+ {
+ LOG(LogLevel::Debug, ("%s", __func__));
+ // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
+ // to decrypt media data and remove the association with the media element.
+ mElement->mMediaKeys->Unbind();
+ mElement->mMediaKeys = nullptr;
+ }
+
+ bool TryAsyncRemoveCDMAssociation()
+ {
+ LOG(LogLevel::Debug, ("%s", __func__));
+ // 5.2.1 If the user agent or CDM do not support removing the association,
+ // let this object's attaching media keys value be false and reject promise
+ // with a new DOMException whose name is NotSupportedError.
+ // 5.2.2 If the association cannot currently be removed, let this object's
+ // attaching media keys value be false and reject promise with a new
+ // DOMException whose name is InvalidStateError.
+ if (mElement->mDecoder) {
+ // Log the playback time when MediaKeys is going to be set to another.
+ // Once the media pipeline has done the operation, FF may resume the
+ // playback if the new MediaKeys is appropriate.
+ mElement->mResumeEMEPlaybackTime = mElement->CurrentTime();
+ RefPtr<HTMLMediaElement::SetMediaKeysRunner> self = this;
+ mElement->mDecoder->SetCDMProxy(nullptr)
+ ->Then(mElement->mAbstractMainThread,
+ __func__,
+ [self](const MediaResult& aResult) {
+ self->mElement->mSetCDMRequest.Complete();
+ self->RemoveAssociation();
+ if (self->AttachNewMediaKeysCDMProxy()) {
+ // No incoming MediaKeys object or MediaDecoder is not
+ // created yet.
+ self->mElement->mResumeEMEPlaybackTime = 0.f;
+ self->SetCDMProxySuccess();
+ }
+ },
+ [self](const MediaResult& aResult) {
+ self->mElement->mSetCDMRequest.Complete();
+ // 5.2.4 If the preceding step failed, let this object's attaching media
+ // keys value be false and reject promise with a new DOMException whose
+ // name is the appropriate error name.
+ self->SetCDMProxyFailure(aResult);
+ })
+ ->Track(mElement->mSetCDMRequest);
+ return false;
+ }
+ RemoveAssociation();
+ return true;
+ }
+
+ void MakeAssociationWithCDMResolved()
+ {
+ LOG(LogLevel::Debug, ("%s", __func__));
+ mElement->mSetCDMRequest.Complete();
+ SetCDMProxySuccess();
+ }
+
+ void MakeAssociationWithCDMRejected(const MediaResult& aResult)
+ {
+ LOG(LogLevel::Debug, ("%s", __func__));
+ mElement->mSetCDMRequest.Complete();
+ SetCDMProxyFailure(aResult);
+ }
+
+ bool TryAsyncMakeAssociationWithCDM(CDMProxy* aProxy)
+ {
+ MOZ_ASSERT(aProxy);
+ LOG(LogLevel::Debug, ("%s", __func__));
+ // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
+ // algorithm on the media element.
+ // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
+ if (mElement->mDecoder) {
+ // CDMProxy is set asynchronously in MediaFormatReader, once it's done,
+ // HTMLMediaElement::AsyncResolveOrRejectSetCDMPromise will be called to
+ // resolve or reject the DOM promise.
+ RefPtr<HTMLMediaElement::SetMediaKeysRunner> self = this;
+ mElement->mDecoder->SetCDMProxy(aProxy)
+ ->Then(mElement->mAbstractMainThread,
+ __func__,
+ [self](const MediaResult& aResult) {
+ self->MakeAssociationWithCDMResolved();
+ },
+ [self](const MediaResult& aResult) {
+ self->MakeAssociationWithCDMRejected(aResult);
+ })
+ ->Track(mElement->mSetCDMRequest);
+ return false;
+ }
+ return true;
+ }
+
+ void SetCDMProxySuccess()
+ {
+ mElement->AsyncResolveOrRejectSetCDMPromise(MediaResult(NS_OK));
+ }
+
+ void SetCDMProxyFailure(const MediaResult& aError)
+ {
+ mElement->mResumeEMEPlaybackTime = 0.f;
+ mElement->AsyncResolveOrRejectSetCDMPromise(aError);
+ }
+
+ bool DetachExistingMediaKeys()
+ {
+ LOG(LogLevel::Debug, ("%s", __func__));
+ // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
+ // already in use by another media element, and the user agent is unable
+ // to use it with this element, let this object's attaching media keys
+ // value be false and reject promise with a new DOMException whose name
+ // is QuotaExceededError.
+ if (mElement->mIncomingMediaKeys &&
+ mElement->mIncomingMediaKeys->IsBoundToMediaElement()) {
+ SetCDMProxyFailure(MediaResult(
+ NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
+ "MediaKeys object is already bound to another HTMLMediaElement"));
+ return false;
+ }
+
+ // 5.2 If the mediaKeys attribute is not null, run the following steps:
+ if (mElement->mMediaKeys) {
+ return TryAsyncRemoveCDMAssociation();
+ }
+ return true;
+ }
+
+ bool AttachNewMediaKeysCDMProxy()
+ {
+ LOG(LogLevel::Debug,
+ ("%s incoming MediaKeys(%p)", __func__, mElement->mIncomingMediaKeys.get()));
+ // 5.3. If mediaKeys is not null, run the following steps:
+ if (mElement->mIncomingMediaKeys) {
+ auto cdmProxy = mElement->mIncomingMediaKeys->GetCDMProxy();
+ if (!cdmProxy) {
+ SetCDMProxyFailure(MediaResult(
+ NS_ERROR_DOM_INVALID_STATE_ERR,
+ "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
+ return false;
+ }
+
+ // 5.3.1 Associate the CDM instance represented by mediaKeys with the
+ // media element for decrypting media data.
+ if (NS_FAILED(mElement->mIncomingMediaKeys->Bind(mElement))) {
+ // 5.3.2 If the preceding step failed, run the following steps:
+
+ // 5.3.2.1 Set the mediaKeys attribute to null.
+ mElement->mMediaKeys = nullptr;
+ // 5.3.2.2 Let this object's attaching media keys value be false.
+ // 5.3.2.3 Reject promise with a new DOMException whose name is
+ // the appropriate error name.
+ SetCDMProxyFailure(
+ MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
+ "Failed to bind MediaKeys object to HTMLMediaElement"));
+ return false;
+ }
+
+ return TryAsyncMakeAssociationWithCDM(cdmProxy);
+ }
+ return true;
+ }
+
+ NS_IMETHOD Run()
+ {
+ if (!DetachExistingMediaKeys() || !AttachNewMediaKeysCDMProxy()) {
+ return NS_OK;
+ }
+
+ SetCDMProxySuccess();
+ }
+
+protected:
+ RefPtr<HTMLMediaElement> mElement;
+};
// Under certain conditions there may be no-one holding references to
// a media element from script, DOM parent, etc, but the element may still
// fire meaningful events in the future so we can't destroy it yet:
// 1) If the element is delaying the load event (or would be, if it were
// in a document), then events up to loadeddata or error could be fired,
// so we need to stay alive.
// 2) If the element is not paused and playback has not ended, then
// we will (or might) play, sending timeupdate and ended events and possibly
@@ -1457,16 +1641,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
tmp->RemoveMutationObserver(tmp);
if (tmp->mSrcStream) {
// Need to EndMediaStreamPlayback to clear mSrcStream and make sure everything
// gets unhooked correctly.
tmp->EndSrcMediaStreamPlayback();
@@ -1488,16 +1674,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement)
NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLMediaElement)
NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
// nsIDOMHTMLMediaElement
NS_IMPL_URI_ATTR(HTMLMediaElement, Src, src)
@@ -3803,80 +3991,85 @@ private:
}
// Guaranteed to be valid by HTMLMediaElement.
HTMLMediaElement* mWeak = nullptr;
Phase mPhase = Phase::Init;
};
NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver, nsIObserver)
-HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
- : nsGenericHTMLElement(aNodeInfo),
- mMainThreadEventTarget(OwnerDoc()->EventTargetFor(TaskCategory::Other)),
- mAbstractMainThread(OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other)),
- mWatchManager(this, mAbstractMainThread),
- mSrcStreamTracksAvailable(false),
- mSrcStreamPausedCurrentTime(-1),
- mShutdownObserver(new ShutdownObserver),
- mSourcePointer(nullptr),
- mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
- mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING, "HTMLMediaElement::mReadyState"),
- mCurrentLoadID(0),
- mLoadWaitStatus(NOT_WAITING),
- mVolume(1.0),
- mIsAudioTrackAudible(false),
- mMuted(0),
- mPreloadAction(PRELOAD_UNDEFINED),
- mLastCurrentTime(0.0),
- mFragmentStart(-1.0),
- mFragmentEnd(-1.0),
- mDefaultPlaybackRate(1.0),
- mPlaybackRate(1.0),
- mPreservesPitch(true),
- mPlayed(new TimeRanges(ToSupports(OwnerDoc()))),
- mCurrentPlayRangeStart(-1.0),
- mBegun(false),
- mLoadedDataFired(false),
- mAutoplaying(true),
- mAutoplayEnabled(true),
- mPaused(true, *this),
- mStatsShowing(false),
- mAllowCasting(false),
- mIsCasting(false),
- mAudioCaptured(false),
- mPlayingBeforeSeek(false),
- mPausedForInactiveDocumentOrChannel(false),
- mEventDeliveryPaused(false),
- mIsRunningLoadMethod(false),
- mIsDoingExplicitLoad(false),
- mIsLoadingFromSourceChildren(false),
- mDelayingLoadEvent(false),
- mIsRunningSelectResource(false),
- mHaveQueuedSelectResource(false),
- mSuspendedAfterFirstFrame(false),
- mAllowSuspendAfterFirstFrame(true),
- mHasPlayedOrSeeked(false),
- mHasSelfReference(false),
- mShuttingDown(false),
- mSuspendedForPreloadNone(false),
- mSrcStreamIsPlaying(false),
- mMediaSecurityVerified(false),
- mCORSMode(CORS_NONE),
- mIsEncrypted(false),
- mWaitingForKey(NOT_WAITING_FOR_KEY),
- mDownloadSuspendedByCache(false, "HTMLMediaElement::mDownloadSuspendedByCache"),
- mDisableVideo(false),
- mHasUserInteraction(false),
- mFirstFrameLoaded(false),
- mDefaultPlaybackStartPosition(0.0),
- mHasSuspendTaint(false),
- mMediaTracksConstructed(false),
- mVisibilityState(Visibility::UNTRACKED),
- mErrorSink(new ErrorSink(this)),
- mAudioChannelWrapper(new AudioChannelAgentCallback(this))
+HTMLMediaElement::HTMLMediaElement(
+ already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ , mMainThreadEventTarget(OwnerDoc()->EventTargetFor(TaskCategory::Other))
+ , mAbstractMainThread(OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other))
+ , mWatchManager(this, mAbstractMainThread)
+ , mSrcStreamTracksAvailable(false)
+ , mSrcStreamPausedCurrentTime(-1)
+ , mShutdownObserver(new ShutdownObserver)
+ , mSourcePointer(nullptr)
+ , mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY)
+ , mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING,
+ "HTMLMediaElement::mReadyState")
+ , mCurrentLoadID(0)
+ , mLoadWaitStatus(NOT_WAITING)
+ , mVolume(1.0)
+ , mIsAudioTrackAudible(false)
+ , mMuted(0)
+ , mPreloadAction(PRELOAD_UNDEFINED)
+ , mLastCurrentTime(0.0)
+ , mFragmentStart(-1.0)
+ , mFragmentEnd(-1.0)
+ , mDefaultPlaybackRate(1.0)
+ , mPlaybackRate(1.0)
+ , mPreservesPitch(true)
+ , mPlayed(new TimeRanges(ToSupports(OwnerDoc())))
+ , mAttachingMediaKey(false)
+ , mCurrentPlayRangeStart(-1.0)
+ , mBegun(false)
+ , mLoadedDataFired(false)
+ , mAutoplaying(true)
+ , mAutoplayEnabled(true)
+ , mPaused(true, *this)
+ , mStatsShowing(false)
+ , mAllowCasting(false)
+ , mIsCasting(false)
+ , mAudioCaptured(false)
+ , mPlayingBeforeSeek(false)
+ , mPausedForInactiveDocumentOrChannel(false)
+ , mEventDeliveryPaused(false)
+ , mIsRunningLoadMethod(false)
+ , mIsDoingExplicitLoad(false)
+ , mIsLoadingFromSourceChildren(false)
+ , mDelayingLoadEvent(false)
+ , mIsRunningSelectResource(false)
+ , mHaveQueuedSelectResource(false)
+ , mSuspendedAfterFirstFrame(false)
+ , mAllowSuspendAfterFirstFrame(true)
+ , mHasPlayedOrSeeked(false)
+ , mHasSelfReference(false)
+ , mShuttingDown(false)
+ , mSuspendedForPreloadNone(false)
+ , mSrcStreamIsPlaying(false)
+ , mMediaSecurityVerified(false)
+ , mCORSMode(CORS_NONE)
+ , mIsEncrypted(false)
+ , mWaitingForKey(NOT_WAITING_FOR_KEY)
+ , mDownloadSuspendedByCache(false,
+ "HTMLMediaElement::mDownloadSuspendedByCache")
+ , mDisableVideo(false)
+ , mHasUserInteraction(false)
+ , mFirstFrameLoaded(false)
+ , mDefaultPlaybackStartPosition(0.0)
+ , mHasSuspendTaint(false)
+ , mMediaTracksConstructed(false)
+ , mVisibilityState(Visibility::UNTRACKED)
+ , mErrorSink(new ErrorSink(this))
+ , mAudioChannelWrapper(new AudioChannelAgentCallback(this))
+ , mResumeEMEPlaybackTime(0.f)
{
MOZ_ASSERT(mMainThreadEventTarget);
MOZ_ASSERT(mAbstractMainThread);
ErrorResult rv;
double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
SetVolume(defaultVolume, rv);
@@ -6862,99 +7055,34 @@ HTMLMediaElement::SetMediaKeys(mozilla::
// 1. If mediaKeys and the mediaKeys attribute are the same object,
// return a resolved promise.
if (mMediaKeys == aMediaKeys) {
promise->MaybeResolveWithUndefined();
return promise.forget();
}
- // Note: Our attaching code is synchronous, so we can skip the following steps.
-
// 2. If this object's attaching media keys value is true, return a
// promise rejected with a new DOMException whose name is InvalidStateError.
+ if (mAttachingMediaKey) {
+ promise->MaybeReject(
+ NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("A MediaKeys object is in attaching operation."));
+ return promise.forget();
+ }
+
// 3. Let this object's attaching media keys value be true.
+ mAttachingMediaKey = true;
+ mIncomingMediaKeys = aMediaKeys;
+ mSetMediaKeysDOMPromise = promise;
// 4. Let promise be a new promise.
// 5. Run the following steps in parallel:
-
- // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
- // already in use by another media element, and the user agent is unable
- // to use it with this element, let this object's attaching media keys
- // value be false and reject promise with a new DOMException whose name
- // is QuotaExceededError.
- if (aMediaKeys && aMediaKeys->IsBoundToMediaElement()) {
- promise->MaybeReject(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
- NS_LITERAL_CSTRING("MediaKeys object is already bound to another HTMLMediaElement"));
- return promise.forget();
- }
-
- // 5.2 If the mediaKeys attribute is not null, run the following steps:
- if (mMediaKeys) {
- // 5.2.1 If the user agent or CDM do not support removing the association,
- // let this object's attaching media keys value be false and reject promise
- // with a new DOMException whose name is NotSupportedError.
-
- // 5.2.2 If the association cannot currently be removed, let this object's
- // attaching media keys value be false and reject promise with a new
- // DOMException whose name is InvalidStateError.
- if (mDecoder) {
- // We don't support swapping out the MediaKeys once we've started to
- // setup the playback pipeline. Note this also means we don't need to worry
- // about handling disassociating the MediaKeys from the MediaDecoder.
- promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
- NS_LITERAL_CSTRING("Can't change MediaKeys on HTMLMediaElement after load has started"));
- return promise.forget();
- }
-
- // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
- // to decrypt media data and remove the association with the media element.
- mMediaKeys->Unbind();
- mMediaKeys = nullptr;
-
- // 5.2.4 If the preceding step failed, let this object's attaching media
- // keys value be false and reject promise with a new DOMException whose
- // name is the appropriate error name.
- }
-
- // 5.3. If mediaKeys is not null, run the following steps:
- if (aMediaKeys) {
- if (!aMediaKeys->GetCDMProxy()) {
- promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
- NS_LITERAL_CSTRING("CDM crashed before binding MediaKeys object to HTMLMediaElement"));
- return promise.forget();
- }
-
- // 5.3.1 Associate the CDM instance represented by mediaKeys with the
- // media element for decrypting media data.
- if (NS_FAILED(aMediaKeys->Bind(this))) {
- // 5.3.2 If the preceding step failed, run the following steps:
- // 5.3.2.1 Set the mediaKeys attribute to null.
- mMediaKeys = nullptr;
- // 5.3.2.2 Let this object's attaching media keys value be false.
- // 5.3.2.3 Reject promise with a new DOMException whose name is
- // the appropriate error name.
- promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
- NS_LITERAL_CSTRING("Failed to bind MediaKeys object to HTMLMediaElement"));
- return promise.forget();
- }
- // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
- // algorithm on the media element.
- // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
- if (mDecoder) {
- mDecoder->SetCDMProxy(aMediaKeys->GetCDMProxy());
- }
- }
-
- // 5.4 Set the mediaKeys attribute to mediaKeys.
- mMediaKeys = aMediaKeys;
-
- // 5.5 Let this object's attaching media keys value be false.
-
- // 5.6 Resolve promise.
- promise->MaybeResolveWithUndefined();
+ RefPtr<SetMediaKeysRunner> r =
+ new SetMediaKeysRunner("SetMediaKeysRunner", this);
+ mAbstractMainThread->Dispatch(r.forget());
// 6. Return promise.
return promise.forget();
}
EventHandlerNonNull*
HTMLMediaElement::GetOnencrypted()
{
@@ -7586,16 +7714,56 @@ HTMLMediaElement::AsyncRejectSeekDOMProm
"dom::HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists",
[=]() { promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); });
mAbstractMainThread->Dispatch(r.forget());
mSeekDOMPromise = nullptr;
}
}
void
+HTMLMediaElement::AsyncResolveOrRejectSetCDMPromise(const MediaResult& aResult)
+{
+ LOG(LogLevel::Debug, ("%s", __func__));
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mSetMediaKeysDOMPromise) {
+ MediaResult result = aResult;
+ RefPtr<DetailedPromise> promise = mSetMediaKeysDOMPromise.forget();
+ RefPtr<HTMLMediaElement> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "dom::HTMLMediaElement::AsyncResolveOrRejectSetCDMPromise", [=]() {
+ if (NS_FAILED(aResult)) {
+ // 5.2.2 If the association cannot currently be removed, let this object's
+ // attaching media keys value be false and reject promise with a new
+ // DOMException whose name is InvalidStateError.
+ self->mAttachingMediaKey = false;
+ promise->MaybeReject(result.Code(), result.Message());
+ } else {
+ // 5.4 Set the mediaKeys attribute to mediaKeys.
+ self->mMediaKeys = self->mIncomingMediaKeys;
+ // 5.5 Let this object's attaching media keys value be false.
+ self->mAttachingMediaKey = false;
+ // 5.6 Resolve promise.
+ promise->MaybeResolveWithUndefined();
+
+ if (self->mResumeEMEPlaybackTime != 0.f) {
+ // Since setting CDMProxy will unblock playback now, seeking to the
+ // position where last playback stopped when MediaKeys was removing
+ // should provide a better experience.
+ IgnoredErrorResult rv;
+ self->FastSeek(self->mResumeEMEPlaybackTime, rv);
+ }
+ }
+ self->mResumeEMEPlaybackTime = 0.f;
+ self->mIncomingMediaKeys = nullptr;
+ });
+ mAbstractMainThread->Dispatch(r.forget());
+ }
+}
+
+void
HTMLMediaElement::ReportCanPlayTelemetry()
{
LOG(LogLevel::Debug, ("%s", __func__));
RefPtr<nsIThread> thread;
nsresult rv = NS_NewNamedThread("MediaTelemetry", getter_AddRefs(thread));
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -38,19 +38,21 @@ using mozilla::layers::LayersBackend;
static mozilla::LazyLogModule sFormatDecoderLog("MediaFormatReader");
mozilla::LazyLogModule gMediaDemuxerLog("MediaDemuxer");
#define LOG(arg, ...) MOZ_LOG(sFormatDecoderLog, mozilla::LogLevel::Debug, ("MediaFormatReader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
#define LOGV(arg, ...) MOZ_LOG(sFormatDecoderLog, mozilla::LogLevel::Verbose, ("MediaFormatReader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
+#define ATTACHING_CDMPROXY_VIDEO 0x01
+#define ATTACHING_CDMPROXY_AUDIO 0x02
+
namespace mozilla {
-
typedef void* MediaDataDecoderID;
/**
* This helper class is used to report telemetry of the time used to recover a
* decoder from GPU crash.
* It uses MediaDecoderOwnerID to identify which video we're dealing with.
* It uses MediaDataDecoderID to make sure that the old MediaDataDecoder has
* been deleted and we're already recovered.
@@ -1192,16 +1194,17 @@ MediaFormatReader::MediaFormatReader(Med
, mCrashHelper(aInit.mCrashHelper)
, mDecoderFactory(new DecoderFactory(this))
, mShutdownPromisePool(new ShutdownPromisePool())
, mBuffered(mTaskQueue,
TimeIntervals(),
"MediaFormatReader::mBuffered (Canonical)")
, mFrameStats(aInit.mFrameStats)
, mMediaDecoderOwnerID(aInit.mMediaDecoderOwnerID)
+ , mAttachingCDMProxy(0)
{
MOZ_ASSERT(aDemuxer);
MOZ_COUNT_CTOR(MediaFormatReader);
mOnTrackWaitingForKeyListener = OnTrackWaitingForKey().Connect(
mTaskQueue, this, &MediaFormatReader::NotifyWaitingForKey);
}
MediaFormatReader::~MediaFormatReader()
@@ -1216,16 +1219,20 @@ MediaFormatReader::Shutdown()
MOZ_ASSERT(OnTaskQueue());
LOG("");
mDemuxerInitRequest.DisconnectIfExists();
mNotifyDataArrivedPromise.DisconnectIfExists();
mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
mSeekPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
mSkipRequest.DisconnectIfExists();
+ mSetCDMPromise.RejectIfExists(
+ MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
+ "MediaFormatReader is shutting down"),
+ __func__);
if (mAudio.HasPromise()) {
mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
}
if (mVideo.HasPromise()) {
mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
}
@@ -1306,33 +1313,46 @@ MediaFormatReader::Init()
mVideo.mTaskQueue = new TaskQueue(
GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
"MFR::mVideo::mTaskQueue");
return NS_OK;
}
-void
+RefPtr<MediaDecoderOwner::SetCDMPromise>
MediaFormatReader::SetCDMProxy(CDMProxy* aProxy)
{
+ if (!mSetCDMPromise.IsEmpty()) {
+ return MediaDecoderOwner::SetCDMPromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
+ "A CDMProxy attaching operation is in process."),
+ __func__);
+ }
+
+ RefPtr<MediaDecoderOwner::SetCDMPromise> p = mSetCDMPromise.Ensure(__func__);
RefPtr<CDMProxy> proxy = aProxy;
RefPtr<MediaFormatReader> self = this;
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction("MediaFormatReader::SetCDMProxy", [=]() {
MOZ_ASSERT(self->OnTaskQueue());
- self->mCDMProxy = proxy;
- if (HasAudio()) {
- self->ScheduleUpdate(TrackInfo::kAudioTrack);
+ LOGV("SetCDMProxy (%x)", proxy.get());
+
+ self->mAttachingCDMProxy |= (ATTACHING_CDMPROXY_AUDIO | ATTACHING_CDMPROXY_VIDEO);
+ if (!proxy) {
+ // Decoders should be shut down when CDMProxy is going to be detached as
+ // MFR should not use the old decoders if any new CDMProxy is set.
+ self->ReleaseResources();
}
- if (HasVideo()) {
- self->ScheduleUpdate(TrackInfo::kVideoTrack);
- }
+ self->mCDMProxy = proxy;
+ self->ScheduleUpdate(TrackInfo::kVideoTrack);
+ self->ScheduleUpdate(TrackInfo::kAudioTrack);
});
OwnerThread()->Dispatch(r.forget());
+ return p;
}
bool
MediaFormatReader::IsWaitingOnCDMResource()
{
MOZ_ASSERT(OnTaskQueue());
return IsEncrypted() && !mCDMProxy;
}
@@ -2214,16 +2234,35 @@ MediaFormatReader::DrainDecoder(TrackTyp
decoder.mDrainRequest.Complete();
NotifyError(aTrack, aError);
})
->Track(decoder.mDrainRequest);
LOG("Requesting %s decoder to drain", TrackTypeToStr(aTrack));
}
void
+MediaFormatReader::CheckCDMProxyAttachingStatus(TrackType aTrack)
+{
+ MOZ_ASSERT(OnTaskQueue());
+ auto& decoder = GetDecoderData(aTrack);
+ if (decoder.mFlushed && mAttachingCDMProxy != 0) {
+ MOZ_ASSERT(!mSetCDMPromise.IsEmpty());
+ uint8_t flag = aTrack == TrackType::kVideoTrack
+ ? ATTACHING_CDMPROXY_VIDEO
+ : ATTACHING_CDMPROXY_AUDIO;
+ mAttachingCDMProxy ^= flag;
+
+ if (mAttachingCDMProxy == 0) {
+ mSetCDMPromise.Resolve(MediaResult(NS_OK), __func__);
+ }
+ ScheduleUpdate(aTrack);
+ }
+}
+
+void
MediaFormatReader::Update(TrackType aTrack)
{
MOZ_ASSERT(OnTaskQueue());
if (mShutdown) {
return;
}
@@ -2232,16 +2271,18 @@ MediaFormatReader::Update(TrackType aTra
bool needOutput = false;
auto& decoder = GetDecoderData(aTrack);
decoder.mUpdateScheduled = false;
if (!mInitDone) {
return;
}
+ CheckCDMProxyAttachingStatus(aTrack);
+
if (aTrack == TrackType::kVideoTrack && mSkipRequest.Exists()) {
LOGV("Skipping in progress, nothing more to do");
return;
}
if (UpdateReceivedNewData(aTrack)) {
LOGV("Nothing more to do");
return;