Index: Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp |
diff --git a/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp b/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp |
index f8a82cc0dbd426d4876ae0a230eae3cd0a139d99..e3e2d6eea141e5501ca34c85eeddd2ae09ef89f2 100644 |
--- a/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp |
+++ b/Source/modules/encryptedmedia/HTMLMediaElementEncryptedMedia.cpp |
@@ -6,14 +6,21 @@ |
#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/Functional.h" |
#include "wtf/Uint8Array.h" |
namespace blink { |
@@ -38,6 +45,187 @@ static void throwExceptionIfMediaKeyExceptionOccurred(const String& keySystem, c |
return; |
} |
+// This class allows MediaKeys to be set asynchronously. |
+class SetMediaKeysHandler : public ScriptPromiseResolver { |
+ WTF_MAKE_NONCOPYABLE(SetMediaKeysHandler); |
+ |
+public: |
+ static ScriptPromise create(ScriptState*, HTMLMediaElement&, MediaKeys*); |
+ virtual ~SetMediaKeysHandler(); |
+ |
+private: |
+ SetMediaKeysHandler(ScriptState*, HTMLMediaElement&, MediaKeys*); |
+ void timerFired(Timer<SetMediaKeysHandler>*); |
+ |
+ void clearExistingMediaKeys(); |
+ void setNewMediaKeys(); |
+ void finish(); |
+ |
+ void reportSetFailed(ExceptionCode, const String& errorMessage); |
+ |
+ RawPtrWillBeMember<HTMLMediaElement> m_element; |
+ Persistent<MediaKeys> m_newMediaKeys; |
+ Timer<SetMediaKeysHandler> m_timer; |
+}; |
+ |
+typedef Function<void()> SuccessCallback; |
+typedef Function<void(ExceptionCode, const String&)> FailureCallback; |
+ |
+// Represents the result used when setContentDecryptionModule() is called. |
+// Calls |success| if result is resolved, |failure| is result is rejected. |
+class SetContentDecryptionModuleResult FINAL : public ContentDecryptionModuleResult { |
+public: |
+ SetContentDecryptionModuleResult(SuccessCallback success, FailureCallback failure) |
+ : m_successCallback(success) |
+ , m_failureCallback(failure) |
+ { |
+ } |
+ |
+ // ContentDecryptionModuleResult implementation. |
+ virtual void complete() OVERRIDE |
+ { |
+ m_successCallback(); |
+ } |
+ |
+ virtual void completeWithSession(blink::WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE |
+ { |
+ ASSERT_NOT_REACHED(); |
+ m_failureCallback(InvalidStateError, "Unexpected completion."); |
+ } |
+ |
+ virtual void completeWithError(blink::WebContentDecryptionModuleException code, unsigned long systemCode, const blink::WebString& message) OVERRIDE |
+ { |
+ m_failureCallback(WebCdmExceptionToExceptionCode(code), message); |
+ } |
+ |
+private: |
+ SuccessCallback m_successCallback; |
+ FailureCallback m_failureCallback; |
+}; |
+ |
+ScriptPromise SetMediaKeysHandler::create(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys) |
+{ |
+ RefPtr<SetMediaKeysHandler> handler = adoptRef(new SetMediaKeysHandler(scriptState, element, mediaKeys)); |
+ handler->suspendIfNeeded(); |
+ handler->keepAliveWhilePending(); |
+ return handler->promise(); |
+} |
+ |
+SetMediaKeysHandler::SetMediaKeysHandler(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys) |
+ : ScriptPromiseResolver(scriptState) |
+ , m_element(element) |
+ , m_newMediaKeys(mediaKeys) |
+ , m_timer(this, &SetMediaKeysHandler::timerFired) |
+{ |
+ WTF_LOG(Media, "SetMediaKeysHandler::SetMediaKeysHandler"); |
+ |
+ // 3. Run the remaining steps asynchronously. |
+ m_timer.startOneShot(0, FROM_HERE); |
+} |
+ |
+SetMediaKeysHandler::~SetMediaKeysHandler() |
+{ |
+} |
+ |
+void SetMediaKeysHandler::timerFired(Timer<SetMediaKeysHandler>*) |
+{ |
+ clearExistingMediaKeys(); |
+} |
+ |
+void SetMediaKeysHandler::clearExistingMediaKeys() |
+{ |
+ WTF_LOG(Media, "SetMediaKeysHandler::clearExistingMediaKeys"); |
+ HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element); |
+ |
+ // 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". |
+ // FIXME: Need to check whether mediaKeys is already in use by another |
+ // media element. |
+ // 3.2 If the mediaKeys attribute is not null, run the following steps: |
+ 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". |
+ // (supported by blink). |
+ // 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->webMediaPlayer()) { |
+ reject(DOMException::create(InvalidStateError, "The existing MediaKeys object cannot be removed while a media resource is loaded.")); |
+ return; |
+ } |
+ |
+ // (next 2 steps not required as there is no player connected). |
+ // 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. |
+ } |
+ |
+ // MediaKeys not currently set or no player connected, so continue on. |
+ setNewMediaKeys(); |
+} |
+ |
+void SetMediaKeysHandler::setNewMediaKeys() |
+{ |
+ WTF_LOG(Media, "SetMediaKeysHandler::setNewMediaKeys"); |
+ |
+ // 3.3 If mediaKeys is not null, run the following steps: |
+ if (m_newMediaKeys) { |
+ // 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: |
+ // (done in reportSetFailed()). |
+ // 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). |
+ // (Handled in Chromium). |
+ if (m_element->webMediaPlayer()) { |
+ SuccessCallback successCallback = bind(&SetMediaKeysHandler::finish, this); |
+ FailureCallback failureCallback = bind<ExceptionCode, const String&>(&SetMediaKeysHandler::reportSetFailed, this); |
+ ContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(successCallback, failureCallback); |
+ m_element->webMediaPlayer()->setContentDecryptionModule(m_newMediaKeys->contentDecryptionModule(), result->result()); |
+ |
+ // Don't do anything more until |result| is resolved (or rejected). |
+ return; |
+ } |
+ } |
+ |
+ // MediaKeys doesn't need to be set on the player, so continue on. |
+ finish(); |
+} |
+ |
+void SetMediaKeysHandler::finish() |
+{ |
+ WTF_LOG(Media, "SetMediaKeysHandler::finish"); |
+ HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element); |
+ |
+ // 3.4 Set the mediaKeys attribute to mediaKeys. |
+ thisElement.m_mediaKeys = m_newMediaKeys; |
+ |
+ // 3.5 Resolve promise with undefined. |
+ resolve(); |
+} |
+ |
+void SetMediaKeysHandler::reportSetFailed(ExceptionCode code, const String& errorMessage) |
+{ |
+ WTF_LOG(Media, "SetMediaKeysHandler::reportSetFailed"); |
+ HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element); |
+ |
+ // 3.3.2 If the preceding step failed, run the following steps: |
+ // 3.3.2.1 Set the mediaKeys attribute to null. |
+ thisElement.m_mediaKeys.clear(); |
+ |
+ // 3.3.2.2 Reject promise with a new DOMException whose name is the |
+ // appropriate error name and that has an appropriate message. |
+ reject(DOMException::create(code, errorMessage)); |
+} |
+ |
HTMLMediaElementEncryptedMedia::HTMLMediaElementEncryptedMedia() |
: m_emeMode(EmeModeNotSelected) |
{ |
@@ -60,12 +248,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 +268,21 @@ MediaKeys* HTMLMediaElementEncryptedMedia::mediaKeys(HTMLMediaElement& element) |
return thisElement.m_mediaKeys.get(); |
} |
-void HTMLMediaElementEncryptedMedia::setMediaKeysInternal(HTMLMediaElement& element, MediaKeys* mediaKeys) |
+ScriptPromise HTMLMediaElementEncryptedMedia::setMediaKeys(ScriptState* scriptState, 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) |
-{ |
- 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 handler. |
+ return SetMediaKeysHandler::create(scriptState, element, mediaKeys); |
} |
// Create a MediaKeyNeededEvent for WD EME. |
@@ -139,8 +319,10 @@ void HTMLMediaElementEncryptedMedia::generateKeyRequest(WebMediaPlayer* webMedia |
{ |
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 +359,10 @@ void HTMLMediaElementEncryptedMedia::addKey(WebMediaPlayer* webMediaPlayer, cons |
{ |
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 +409,10 @@ void HTMLMediaElementEncryptedMedia::cancelKeyRequest(WebMediaPlayer* webMediaPl |
{ |
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 +527,11 @@ 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(); |
} |
WebContentDecryptionModule* HTMLMediaElementEncryptedMedia::contentDecryptionModule(HTMLMediaElement& element) |