Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1836)

Unified Diff: Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp

Issue 423633002: Make HTMLMediaElement.setMediaKeys() asynchronous. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
diff --git a/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp b/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
index 1a3969e9b1750239c11716657ba29156140c10f2..1378d09347b1d540e47a12ede471195f73f2a385 100644
--- a/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
+++ b/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp
@@ -6,12 +6,18 @@
#include "modules/encryptedmedia/HTMLMediaElementEncryptedMedia.h"
#include "bindings/core/v8/ExceptionState.h"
+#include "bindings/core/v8/ScriptPromise.h"
+#include "bindings/core/v8/ScriptPromiseResolver.h"
+#include "bindings/core/v8/ScriptState.h"
+#include "core/dom/DOMException.h"
#include "core/dom/ExceptionCode.h"
#include "core/html/HTMLMediaElement.h"
#include "core/html/MediaKeyError.h"
#include "core/html/MediaKeyEvent.h"
#include "modules/encryptedmedia/MediaKeyNeededEvent.h"
#include "modules/encryptedmedia/MediaKeys.h"
+#include "modules/encryptedmedia/SimpleContentDecryptionModuleResult.h"
+#include "platform/ContentDecryptionModuleResult.h"
#include "platform/Logging.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "wtf/Uint8Array.h"
@@ -38,6 +44,191 @@ static void throwExceptionIfMediaKeyExceptionOccurred(const String& keySystem, c
return;
}
+// This class allows MediaKeys to be set asynchronously.
+class SetMediaKeysWorker : public ScriptPromiseResolver {
+ WTF_MAKE_NONCOPYABLE(SetMediaKeysWorker);
+
+public:
+ static ScriptPromise create(ScriptState*, HTMLMediaElement&, MediaKeys*);
+ virtual ~SetMediaKeysWorker();
+
+ void completeWithDOMException(ExceptionCode, const String& errorMessage);
+ void timerFired(Timer<SetMediaKeysWorker>*);
+ void proceedToNextStage();
+
+private:
+ enum Stage {
+ Starting,
+ Clearing,
+ Setting
+ };
+ SetMediaKeysWorker(ScriptState*, HTMLMediaElement&, MediaKeys*);
+
+ RawPtrWillBeMember<HTMLMediaElement> m_element;
+ Persistent<MediaKeys> m_mediaKeys;
+
+ Stage m_stage;
+ Timer<SetMediaKeysWorker> m_timer;
+};
+
+// Represents the result used when setContentDecryptionModule() is called.
+// Needed as SetMediaKeysWorker may need to call setContentDecryptionModule()
+// multiple times, and not resolve the promise until the very end. Any errors
+// that happen will reject the promise immediately.
+//
+// This class can be used without specifying a worker to call. Useful when
+// calling setContentDecryptionModule() while tearing down a player.
+class SetContentDecryptionModuleResult FINAL : public ContentDecryptionModuleResult {
+public:
+ explicit SetContentDecryptionModuleResult(SetMediaKeysWorker* worker)
+ : m_worker(worker)
+ {
+ }
+
+ // ContentDecryptionModuleResult implementation.
+ virtual void complete() OVERRIDE
+ {
+ if (m_worker)
+ m_worker->proceedToNextStage();
+ }
+
+ virtual void completeWithSession(blink::WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE
+ {
+ ASSERT_NOT_REACHED();
+ if (m_worker)
+ m_worker->completeWithDOMException(InvalidStateError, "Unexpected completion.");
+ }
+
+ virtual void completeWithError(blink::WebContentDecryptionModuleException code, unsigned long systemCode, const blink::WebString& message) OVERRIDE
+ {
+ if (m_worker)
+ m_worker->completeWithDOMException(WebCdmExceptionToExceptionCode(code), message);
+ }
+
+private:
+ SetMediaKeysWorker* m_worker;
+};
+
+ScriptPromise SetMediaKeysWorker::create(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
+{
+ RefPtr<SetMediaKeysWorker> worker = adoptRef(new SetMediaKeysWorker(scriptState, element, mediaKeys));
+ worker->suspendIfNeeded();
+ worker->keepAliveWhilePending();
+ return worker->promise();
+}
+
+SetMediaKeysWorker::SetMediaKeysWorker(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
+ : ScriptPromiseResolver(scriptState)
+ , m_element(element)
+ , m_mediaKeys(mediaKeys)
+ , m_stage(Starting)
+ , m_timer(this, &SetMediaKeysWorker::timerFired)
+{
+ WTF_LOG(Media, "SetMediaKeysWorker::SetMediaKeysWorker");
+
+ // 3. Complete setting this asynchronously.
+ m_timer.startOneShot(0, FROM_HERE);
+}
+
+SetMediaKeysWorker::~SetMediaKeysWorker()
+{
+}
+
+void SetMediaKeysWorker::completeWithDOMException(ExceptionCode code, const String& errorMessage)
+{
+ reject(DOMException::create(code, errorMessage));
+}
+
+void SetMediaKeysWorker::timerFired(Timer<SetMediaKeysWorker>*)
+{
+ ASSERT(m_stage == Starting);
+ proceedToNextStage();
+}
+
+void SetMediaKeysWorker::proceedToNextStage()
+{
+ WTF_LOG(Media, "SetMediaKeysWorker::proceedToNextStage(%d)", m_stage);
+ HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element);
+
+ switch (m_stage) {
+ case Starting:
+ // 3.1 If mediaKeys is not null, it is already in use by another media
+ // element, and the user agent is unable to use it with this element,
+ // reject promise with a new DOMException whose name is
+ // "QuotaExceededError". (Currently no restrictions on using the same
+ // MediaKeys with multiple media elements.)
+ // 3.2 If the mediaKeys attribute is not null, run the following steps:
+ m_stage = Clearing;
+ if (thisElement.m_mediaKeys) {
+ // 3.2.1 If the user agent or CDM do not support removing the
+ // association, return a promise rejected with a new DOMException
+ // whose name is "NotSupportedError".
+ // 3.2.2 If the association cannot currently be removed (i.e. during
+ // playback), return a promise rejected with a new DOMException
+ // whose name is "InvalidStateError".
+ if (m_element->isPlaying()) {
+ completeWithDOMException(InvalidStateError, "The existing MediaKeys object cannot be removed at this time.");
+ return;
+ }
+
+ // 3.2.3 Stop using the CDM instance represented by the mediaKeys
+ // attribute to decrypt media data and remove the association
+ // with the media element.
+ // 3.2.4 If the preceding step failed, reject promise with a new
+ // DOMException whose name is the appropriate error name and
+ // that has an appropriate message.
+ if (m_element->webMediaPlayer()) {
+ ContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(this);
+ m_element->webMediaPlayer()->setContentDecryptionModule(nullptr, result->result());
+
+ // Don't do anything more until SetContentDecryptionModuleResult
+ // finishes (and calls back to proceedToNextStage()).
+ return;
+ }
+ }
+
+ // MediaKeys not currently set or no player connected. Continue to next
+ // stage.
+
+ case Clearing:
+ // Successfully cleared MediaKeys, so drop reference to previous value.
+ thisElement.m_mediaKeys.clear();
+
+ // 3.3 If mediaKeys is not null, run the following steps:
+ m_stage = Setting;
+ if (m_mediaKeys) {
+ // 3.3.1 Associate the CDM instance represented by mediaKeys with the
+ // media element for decrypting media data.
+ // 3.3.2 If the preceding step failed, run the following steps:
+ // 3.3.2.1 Set the mediaKeys attribute to null.
+ // 3.3.2.2 Reject promise with a new DOMException whose name is the
+ // appropriate error name and that has an appropriate message.
+ // 3.3.3 Run the Attempt to Resume Playback If Necessary algorithm on
+ // the media element. The user agent may choose to skip this
+ // step if it knows resuming will fail (i.e. mediaKeys has no
+ // sessions).
+ if (m_element->webMediaPlayer()) {
+ ContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(this);
+ m_element->webMediaPlayer()->setContentDecryptionModule(m_mediaKeys->contentDecryptionModule(), result->result());
+
+ // Don't do anything more until SetContentDecryptionModuleResult
+ // finishes (and calls back to proceedToNextStage()).
+ return;
+ }
+ }
+ // MediaKeys doesn't need to be set on the player, so continue on to
+ // next stage.
+
+ case Setting:
+ // 3.4 Set the mediaKeys attribute to mediaKeys.
+ thisElement.m_mediaKeys = m_mediaKeys;
+
+ // 3.5 Resolve promise with undefined.
+ resolve();
+ return;
+ }
+}
+
HTMLMediaElementEncryptedMedia::HTMLMediaElementEncryptedMedia()
: m_emeMode(EmeModeNotSelected)
{
@@ -60,12 +251,11 @@ HTMLMediaElementEncryptedMedia& HTMLMediaElementEncryptedMedia::from(HTMLMediaEl
return *supplement;
}
-bool HTMLMediaElementEncryptedMedia::setEmeMode(EmeMode emeMode, ExceptionState& exceptionState)
+bool HTMLMediaElementEncryptedMedia::setEmeMode(EmeMode emeMode)
{
- if (m_emeMode != EmeModeNotSelected && m_emeMode != emeMode) {
- exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
+ if (m_emeMode != EmeModeNotSelected && m_emeMode != emeMode)
return false;
- }
+
m_emeMode = emeMode;
return true;
}
@@ -81,28 +271,21 @@ MediaKeys* HTMLMediaElementEncryptedMedia::mediaKeys(HTMLMediaElement& element)
return thisElement.m_mediaKeys.get();
}
-void HTMLMediaElementEncryptedMedia::setMediaKeysInternal(HTMLMediaElement& element, MediaKeys* mediaKeys)
-{
- if (m_mediaKeys == mediaKeys)
- return;
-
- ASSERT(m_emeMode == EmeModeUnprefixed);
- m_mediaKeys = mediaKeys;
-
- // If a player is connected, tell it that the CDM has changed.
- if (element.webMediaPlayer())
- element.webMediaPlayer()->setContentDecryptionModule(contentDecryptionModule());
-}
-
-void HTMLMediaElementEncryptedMedia::setMediaKeys(HTMLMediaElement& element, MediaKeys* mediaKeys, ExceptionState& exceptionState)
+ScriptPromise HTMLMediaElementEncryptedMedia::setMediaKeys(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
{
- WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::setMediaKeys");
HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
+ WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::setMediaKeys current(%p), new(%p)", thisElement.m_mediaKeys.get(), mediaKeys);
- if (!thisElement.setEmeMode(EmeModeUnprefixed, exceptionState))
- return;
+ if (!thisElement.setEmeMode(EmeModeUnprefixed))
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed."));
+
+ // 1. If mediaKeys and the mediaKeys attribute are the same object, return
+ // a promise resolved with undefined.
+ if (thisElement.m_mediaKeys == mediaKeys)
+ return ScriptPromise::cast(scriptState, V8ValueTraits<V8UndefinedType>::toV8Value(V8UndefinedType(), scriptState->context()->Global(), scriptState->isolate()));
- thisElement.setMediaKeysInternal(element, mediaKeys);
+ // 2. Let promise be a new promise. Remaining steps done in worker.
+ return SetMediaKeysWorker::create(scriptState, element, mediaKeys);
}
// Create a MediaKeyNeededEvent for WD EME.
@@ -139,8 +322,10 @@ void HTMLMediaElementEncryptedMedia::generateKeyRequest(blink::WebMediaPlayer* w
{
WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitGenerateKeyRequest");
- if (!setEmeMode(EmeModePrefixed, exceptionState))
+ if (!setEmeMode(EmeModePrefixed)) {
+ exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
return;
+ }
if (keySystem.isEmpty()) {
exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
@@ -177,8 +362,10 @@ void HTMLMediaElementEncryptedMedia::addKey(blink::WebMediaPlayer* webMediaPlaye
{
WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitAddKey");
- if (!setEmeMode(EmeModePrefixed, exceptionState))
+ if (!setEmeMode(EmeModePrefixed)) {
+ exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
return;
+ }
if (keySystem.isEmpty()) {
exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
@@ -225,8 +412,10 @@ void HTMLMediaElementEncryptedMedia::cancelKeyRequest(blink::WebMediaPlayer* web
{
WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitCancelKeyRequest");
- if (!setEmeMode(EmeModePrefixed, exceptionState))
+ if (!setEmeMode(EmeModePrefixed)) {
+ exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
return;
+ }
if (keySystem.isEmpty()) {
exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
@@ -341,7 +530,17 @@ void HTMLMediaElementEncryptedMedia::playerDestroyed(HTMLMediaElement& element)
#endif
HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
- thisElement.setMediaKeysInternal(element, 0);
+ if (!thisElement.m_mediaKeys)
+ return;
+
+ ASSERT(thisElement.m_emeMode == EmeModeUnprefixed);
+ thisElement.m_mediaKeys.clear();
+
+ // If a player is connected, tell it that the CDM has changed.
+ if (element.webMediaPlayer()) {
+ SetContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(0);
+ element.webMediaPlayer()->setContentDecryptionModule(thisElement.contentDecryptionModule(), result->result());
+ }
}
blink::WebContentDecryptionModule* HTMLMediaElementEncryptedMedia::contentDecryptionModule(HTMLMediaElement& element)

Powered by Google App Engine
This is Rietveld 408576698