1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "modules/encryptedmedia/HTMLMediaElementEncryptedMedia.h"
8 #include "bindings/core/v8/ExceptionState.h"
9 #include "bindings/core/v8/ScriptPromise.h"
10 #include "bindings/core/v8/ScriptPromiseResolver.h"
11 #include "bindings/core/v8/ScriptState.h"
12 #include "core/dom/DOMException.h"
13 #include "core/dom/ExceptionCode.h"
14 #include "core/html/HTMLMediaElement.h"
15 #include "core/html/MediaKeyError.h"
16 #include "core/html/MediaKeyEvent.h"
17 #include "modules/encryptedmedia/MediaKeyNeededEvent.h"
18 #include "modules/encryptedmedia/MediaKeys.h"
19 #include "modules/encryptedmedia/SimpleContentDecryptionModuleResult.h"
20 #include "platform/ContentDecryptionModuleResult.h"
21 #include "platform/Logging.h"
22 #include "platform/RuntimeEnabledFeatures.h"
23 #include "wtf/Functional.h"
24 #include "wtf/Uint8Array.h"
28 static void throwExceptionIfMediaKeyExceptionOccurred(const String& keySystem, const String& sessionId, WebMediaPlayer::MediaKeyException exception, ExceptionState& exceptionState)
31 case WebMediaPlayer::MediaKeyExceptionNoError:
33 case WebMediaPlayer::MediaKeyExceptionInvalidPlayerState:
34 exceptionState.throwDOMException(InvalidStateError, "The player is in an invalid state.");
36 case WebMediaPlayer::MediaKeyExceptionKeySystemNotSupported:
37 exceptionState.throwDOMException(NotSupportedError, "The key system provided ('" + keySystem +"') is not supported.");
39 case WebMediaPlayer::MediaKeyExceptionInvalidAccess:
40 exceptionState.throwDOMException(InvalidAccessError, "The session ID provided ('" + sessionId + "') is invalid.");
48 // This class allows MediaKeys to be set asynchronously.
49 class SetMediaKeysHandler : public ScriptPromiseResolver {
50 WTF_MAKE_NONCOPYABLE(SetMediaKeysHandler);
53 static ScriptPromise create(ScriptState*, HTMLMediaElement&, MediaKeys*);
54 virtual ~SetMediaKeysHandler();
57 SetMediaKeysHandler(ScriptState*, HTMLMediaElement&, MediaKeys*);
58 void timerFired(Timer<SetMediaKeysHandler>*);
60 void clearExistingMediaKeys();
61 void setNewMediaKeys();
64 void reportSetFailed(ExceptionCode, const String& errorMessage);
66 // Keep media element alive until promise is fulfilled
67 RefPtrWillBePersistent<HTMLMediaElement> m_element;
68 Persistent<MediaKeys> m_newMediaKeys;
69 Timer<SetMediaKeysHandler> m_timer;
72 typedef Function<void()> SuccessCallback;
73 typedef Function<void(ExceptionCode, const String&)> FailureCallback;
75 // Represents the result used when setContentDecryptionModule() is called.
76 // Calls |success| if result is resolved, |failure| is result is rejected.
77 class SetContentDecryptionModuleResult FINAL : public ContentDecryptionModuleResult {
79 SetContentDecryptionModuleResult(SuccessCallback success, FailureCallback failure)
80 : m_successCallback(success)
81 , m_failureCallback(failure)
85 // ContentDecryptionModuleResult implementation.
86 virtual void complete() OVERRIDE
91 virtual void completeWithSession(blink::WebContentDecryptionModuleResult::SessionStatus status) OVERRIDE
94 m_failureCallback(InvalidStateError, "Unexpected completion.");
97 virtual void completeWithError(blink::WebContentDecryptionModuleException code, unsigned long systemCode, const blink::WebString& message) OVERRIDE
99 m_failureCallback(WebCdmExceptionToExceptionCode(code), message);
103 SuccessCallback m_successCallback;
104 FailureCallback m_failureCallback;
107 ScriptPromise SetMediaKeysHandler::create(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
109 RefPtr<SetMediaKeysHandler> handler = adoptRef(new SetMediaKeysHandler(scriptState, element, mediaKeys));
110 handler->suspendIfNeeded();
111 handler->keepAliveWhilePending();
112 return handler->promise();
115 SetMediaKeysHandler::SetMediaKeysHandler(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
116 : ScriptPromiseResolver(scriptState)
118 , m_newMediaKeys(mediaKeys)
119 , m_timer(this, &SetMediaKeysHandler::timerFired)
121 WTF_LOG(Media, "SetMediaKeysHandler::SetMediaKeysHandler");
123 // 3. Run the remaining steps asynchronously.
124 m_timer.startOneShot(0, FROM_HERE);
127 SetMediaKeysHandler::~SetMediaKeysHandler()
131 void SetMediaKeysHandler::timerFired(Timer<SetMediaKeysHandler>*)
133 clearExistingMediaKeys();
136 void SetMediaKeysHandler::clearExistingMediaKeys()
138 WTF_LOG(Media, "SetMediaKeysHandler::clearExistingMediaKeys");
139 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element);
141 // 3.1 If mediaKeys is not null, it is already in use by another media
142 // element, and the user agent is unable to use it with this element,
143 // reject promise with a new DOMException whose name is
144 // "QuotaExceededError".
145 // FIXME: Need to check whether mediaKeys is already in use by another
147 // 3.2 If the mediaKeys attribute is not null, run the following steps:
148 if (thisElement.m_mediaKeys) {
149 // 3.2.1 If the user agent or CDM do not support removing the
150 // association, return a promise rejected with a new DOMException
151 // whose name is "NotSupportedError".
152 // (supported by blink).
153 // 3.2.2 If the association cannot currently be removed (i.e. during
154 // playback), return a promise rejected with a new DOMException
155 // whose name is "InvalidStateError".
156 if (m_element->webMediaPlayer()) {
157 reject(DOMException::create(InvalidStateError, "The existing MediaKeys object cannot be removed while a media resource is loaded."));
161 // (next 2 steps not required as there is no player connected).
162 // 3.2.3 Stop using the CDM instance represented by the mediaKeys
163 // attribute to decrypt media data and remove the association
164 // with the media element.
165 // 3.2.4 If the preceding step failed, reject promise with a new
166 // DOMException whose name is the appropriate error name and
167 // that has an appropriate message.
170 // MediaKeys not currently set or no player connected, so continue on.
174 void SetMediaKeysHandler::setNewMediaKeys()
176 WTF_LOG(Media, "SetMediaKeysHandler::setNewMediaKeys");
178 // 3.3 If mediaKeys is not null, run the following steps:
179 if (m_newMediaKeys) {
180 // 3.3.1 Associate the CDM instance represented by mediaKeys with the
181 // media element for decrypting media data.
182 // 3.3.2 If the preceding step failed, run the following steps:
183 // (done in reportSetFailed()).
184 // 3.3.3 Run the Attempt to Resume Playback If Necessary algorithm on
185 // the media element. The user agent may choose to skip this
186 // step if it knows resuming will fail (i.e. mediaKeys has no
188 // (Handled in Chromium).
189 if (m_element->webMediaPlayer()) {
190 SuccessCallback successCallback = bind(&SetMediaKeysHandler::finish, this);
191 FailureCallback failureCallback = bind<ExceptionCode, const String&>(&SetMediaKeysHandler::reportSetFailed, this);
192 ContentDecryptionModuleResult* result = new SetContentDecryptionModuleResult(successCallback, failureCallback);
193 m_element->webMediaPlayer()->setContentDecryptionModule(m_newMediaKeys->contentDecryptionModule(), result->result());
195 // Don't do anything more until |result| is resolved (or rejected).
200 // MediaKeys doesn't need to be set on the player, so continue on.
204 void SetMediaKeysHandler::finish()
206 WTF_LOG(Media, "SetMediaKeysHandler::finish");
207 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element);
209 // 3.4 Set the mediaKeys attribute to mediaKeys.
210 thisElement.m_mediaKeys = m_newMediaKeys;
212 // 3.5 Resolve promise with undefined.
216 void SetMediaKeysHandler::reportSetFailed(ExceptionCode code, const String& errorMessage)
218 WTF_LOG(Media, "SetMediaKeysHandler::reportSetFailed");
219 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(*m_element);
221 // 3.3.2 If the preceding step failed, run the following steps:
222 // 3.3.2.1 Set the mediaKeys attribute to null.
223 thisElement.m_mediaKeys.clear();
225 // 3.3.2.2 Reject promise with a new DOMException whose name is the
226 // appropriate error name and that has an appropriate message.
227 reject(DOMException::create(code, errorMessage));
230 HTMLMediaElementEncryptedMedia::HTMLMediaElementEncryptedMedia()
231 : m_emeMode(EmeModeNotSelected)
235 DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(HTMLMediaElementEncryptedMedia)
237 const char* HTMLMediaElementEncryptedMedia::supplementName()
239 return "HTMLMediaElementEncryptedMedia";
242 HTMLMediaElementEncryptedMedia& HTMLMediaElementEncryptedMedia::from(HTMLMediaElement& element)
244 HTMLMediaElementEncryptedMedia* supplement = static_cast<HTMLMediaElementEncryptedMedia*>(WillBeHeapSupplement<HTMLMediaElement>::from(element, supplementName()));
246 supplement = new HTMLMediaElementEncryptedMedia();
247 provideTo(element, supplementName(), adoptPtrWillBeNoop(supplement));
252 bool HTMLMediaElementEncryptedMedia::setEmeMode(EmeMode emeMode)
254 if (m_emeMode != EmeModeNotSelected && m_emeMode != emeMode)
261 WebContentDecryptionModule* HTMLMediaElementEncryptedMedia::contentDecryptionModule()
263 return m_mediaKeys ? m_mediaKeys->contentDecryptionModule() : 0;
266 MediaKeys* HTMLMediaElementEncryptedMedia::mediaKeys(HTMLMediaElement& element)
268 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
269 return thisElement.m_mediaKeys.get();
272 ScriptPromise HTMLMediaElementEncryptedMedia::setMediaKeys(ScriptState* scriptState, HTMLMediaElement& element, MediaKeys* mediaKeys)
274 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
275 WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::setMediaKeys current(%p), new(%p)", thisElement.m_mediaKeys.get(), mediaKeys);
277 if (!thisElement.setEmeMode(EmeModeUnprefixed))
278 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed."));
280 // 1. If mediaKeys and the mediaKeys attribute are the same object, return
281 // a promise resolved with undefined.
282 if (thisElement.m_mediaKeys == mediaKeys)
283 return ScriptPromise::cast(scriptState, V8ValueTraits<V8UndefinedType>::toV8Value(V8UndefinedType(), scriptState->context()->Global(), scriptState->isolate()));
285 // 2. Let promise be a new promise. Remaining steps done in handler.
286 return SetMediaKeysHandler::create(scriptState, element, mediaKeys);
289 // Create a MediaKeyNeededEvent for WD EME.
290 static PassRefPtrWillBeRawPtr<Event> createNeedKeyEvent(const String& contentType, const unsigned char* initData, unsigned initDataLength)
292 MediaKeyNeededEventInit initializer;
293 initializer.contentType = contentType;
294 initializer.initData = Uint8Array::create(initData, initDataLength);
295 initializer.bubbles = false;
296 initializer.cancelable = false;
298 return MediaKeyNeededEvent::create(EventTypeNames::needkey, initializer);
301 // Create a 'needkey' MediaKeyEvent for v0.1b EME.
302 static PassRefPtrWillBeRawPtr<Event> createWebkitNeedKeyEvent(const String& contentType, const unsigned char* initData, unsigned initDataLength)
304 MediaKeyEventInit webkitInitializer;
305 webkitInitializer.keySystem = String();
306 webkitInitializer.sessionId = String();
307 webkitInitializer.initData = Uint8Array::create(initData, initDataLength);
308 webkitInitializer.bubbles = false;
309 webkitInitializer.cancelable = false;
311 return MediaKeyEvent::create(EventTypeNames::webkitneedkey, webkitInitializer);
314 void HTMLMediaElementEncryptedMedia::webkitGenerateKeyRequest(HTMLMediaElement& element, const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionState& exceptionState)
316 HTMLMediaElementEncryptedMedia::from(element).generateKeyRequest(element.webMediaPlayer(), keySystem, initData, exceptionState);
319 void HTMLMediaElementEncryptedMedia::generateKeyRequest(WebMediaPlayer* webMediaPlayer, const String& keySystem, PassRefPtr<Uint8Array> initData, ExceptionState& exceptionState)
321 WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitGenerateKeyRequest");
323 if (!setEmeMode(EmeModePrefixed)) {
324 exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
328 if (keySystem.isEmpty()) {
329 exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
333 if (!webMediaPlayer) {
334 exceptionState.throwDOMException(InvalidStateError, "No media has been loaded.");
338 const unsigned char* initDataPointer = 0;
339 unsigned initDataLength = 0;
341 initDataPointer = initData->data();
342 initDataLength = initData->length();
345 WebMediaPlayer::MediaKeyException result = webMediaPlayer->generateKeyRequest(keySystem, initDataPointer, initDataLength);
346 throwExceptionIfMediaKeyExceptionOccurred(keySystem, String(), result, exceptionState);
349 void HTMLMediaElementEncryptedMedia::webkitGenerateKeyRequest(HTMLMediaElement& mediaElement, const String& keySystem, ExceptionState& exceptionState)
351 webkitGenerateKeyRequest(mediaElement, keySystem, Uint8Array::create(0), exceptionState);
354 void HTMLMediaElementEncryptedMedia::webkitAddKey(HTMLMediaElement& element, const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionState& exceptionState)
356 HTMLMediaElementEncryptedMedia::from(element).addKey(element.webMediaPlayer(), keySystem, key, initData, sessionId, exceptionState);
359 void HTMLMediaElementEncryptedMedia::addKey(WebMediaPlayer* webMediaPlayer, const String& keySystem, PassRefPtr<Uint8Array> key, PassRefPtr<Uint8Array> initData, const String& sessionId, ExceptionState& exceptionState)
361 WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitAddKey");
363 if (!setEmeMode(EmeModePrefixed)) {
364 exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
368 if (keySystem.isEmpty()) {
369 exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
374 exceptionState.throwDOMException(SyntaxError, "The key provided is invalid.");
378 if (!key->length()) {
379 exceptionState.throwDOMException(TypeMismatchError, "The key provided is invalid.");
383 if (!webMediaPlayer) {
384 exceptionState.throwDOMException(InvalidStateError, "No media has been loaded.");
388 const unsigned char* initDataPointer = 0;
389 unsigned initDataLength = 0;
391 initDataPointer = initData->data();
392 initDataLength = initData->length();
395 WebMediaPlayer::MediaKeyException result = webMediaPlayer->addKey(keySystem, key->data(), key->length(), initDataPointer, initDataLength, sessionId);
396 throwExceptionIfMediaKeyExceptionOccurred(keySystem, sessionId, result, exceptionState);
399 void HTMLMediaElementEncryptedMedia::webkitAddKey(HTMLMediaElement& mediaElement, const String& keySystem, PassRefPtr<Uint8Array> key, ExceptionState& exceptionState)
401 webkitAddKey(mediaElement, keySystem, key, Uint8Array::create(0), String(), exceptionState);
404 void HTMLMediaElementEncryptedMedia::webkitCancelKeyRequest(HTMLMediaElement& element, const String& keySystem, const String& sessionId, ExceptionState& exceptionState)
406 HTMLMediaElementEncryptedMedia::from(element).cancelKeyRequest(element.webMediaPlayer(), keySystem, sessionId, exceptionState);
409 void HTMLMediaElementEncryptedMedia::cancelKeyRequest(WebMediaPlayer* webMediaPlayer, const String& keySystem, const String& sessionId, ExceptionState& exceptionState)
411 WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::webkitCancelKeyRequest");
413 if (!setEmeMode(EmeModePrefixed)) {
414 exceptionState.throwDOMException(InvalidStateError, "Mixed use of EME prefixed and unprefixed API not allowed.");
418 if (keySystem.isEmpty()) {
419 exceptionState.throwDOMException(SyntaxError, "The key system provided is empty.");
423 if (!webMediaPlayer) {
424 exceptionState.throwDOMException(InvalidStateError, "No media has been loaded.");
428 WebMediaPlayer::MediaKeyException result = webMediaPlayer->cancelKeyRequest(keySystem, sessionId);
429 throwExceptionIfMediaKeyExceptionOccurred(keySystem, sessionId, result, exceptionState);
432 void HTMLMediaElementEncryptedMedia::keyAdded(HTMLMediaElement& element, const String& keySystem, const String& sessionId)
434 WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::mediaPlayerKeyAdded");
436 MediaKeyEventInit initializer;
437 initializer.keySystem = keySystem;
438 initializer.sessionId = sessionId;
439 initializer.bubbles = false;
440 initializer.cancelable = false;
442 RefPtrWillBeRawPtr<Event> event = MediaKeyEvent::create(EventTypeNames::webkitkeyadded, initializer);
443 event->setTarget(&element);
444 element.scheduleEvent(event.release());
447 void HTMLMediaElementEncryptedMedia::keyError(HTMLMediaElement& element, const String& keySystem, const String& sessionId, WebMediaPlayerClient::MediaKeyErrorCode errorCode, unsigned short systemCode)
449 WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::mediaPlayerKeyError: sessionID=%s, errorCode=%d, systemCode=%d", sessionId.utf8().data(), errorCode, systemCode);
451 MediaKeyError::Code mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
453 case WebMediaPlayerClient::MediaKeyErrorCodeUnknown:
454 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_UNKNOWN;
456 case WebMediaPlayerClient::MediaKeyErrorCodeClient:
457 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_CLIENT;
459 case WebMediaPlayerClient::MediaKeyErrorCodeService:
460 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_SERVICE;
462 case WebMediaPlayerClient::MediaKeyErrorCodeOutput:
463 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_OUTPUT;
465 case WebMediaPlayerClient::MediaKeyErrorCodeHardwareChange:
466 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_HARDWARECHANGE;
468 case WebMediaPlayerClient::MediaKeyErrorCodeDomain:
469 mediaKeyErrorCode = MediaKeyError::MEDIA_KEYERR_DOMAIN;
473 MediaKeyEventInit initializer;
474 initializer.keySystem = keySystem;
475 initializer.sessionId = sessionId;
476 initializer.errorCode = MediaKeyError::create(mediaKeyErrorCode);
477 initializer.systemCode = systemCode;
478 initializer.bubbles = false;
479 initializer.cancelable = false;
481 RefPtrWillBeRawPtr<Event> event = MediaKeyEvent::create(EventTypeNames::webkitkeyerror, initializer);
482 event->setTarget(&element);
483 element.scheduleEvent(event.release());
486 void HTMLMediaElementEncryptedMedia::keyMessage(HTMLMediaElement& element, const String& keySystem, const String& sessionId, const unsigned char* message, unsigned messageLength, const WebURL& defaultURL)
488 WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::mediaPlayerKeyMessage: sessionID=%s", sessionId.utf8().data());
490 MediaKeyEventInit initializer;
491 initializer.keySystem = keySystem;
492 initializer.sessionId = sessionId;
493 initializer.message = Uint8Array::create(message, messageLength);
494 initializer.defaultURL = KURL(defaultURL);
495 initializer.bubbles = false;
496 initializer.cancelable = false;
498 RefPtrWillBeRawPtr<Event> event = MediaKeyEvent::create(EventTypeNames::webkitkeymessage, initializer);
499 event->setTarget(&element);
500 element.scheduleEvent(event.release());
503 void HTMLMediaElementEncryptedMedia::keyNeeded(HTMLMediaElement& element, const String& contentType, const unsigned char* initData, unsigned initDataLength)
505 WTF_LOG(Media, "HTMLMediaElementEncryptedMedia::mediaPlayerKeyNeeded: contentType=%s", contentType.utf8().data());
507 if (RuntimeEnabledFeatures::encryptedMediaEnabled()) {
508 // Send event for WD EME.
509 RefPtrWillBeRawPtr<Event> event = createNeedKeyEvent(contentType, initData, initDataLength);
510 event->setTarget(&element);
511 element.scheduleEvent(event.release());
514 if (RuntimeEnabledFeatures::prefixedEncryptedMediaEnabled()) {
515 // Send event for v0.1b EME.
516 RefPtrWillBeRawPtr<Event> event = createWebkitNeedKeyEvent(contentType, initData, initDataLength);
517 event->setTarget(&element);
518 element.scheduleEvent(event.release());
522 void HTMLMediaElementEncryptedMedia::playerDestroyed(HTMLMediaElement& element)
525 // FIXME: Oilpan: remove this once the media player is on the heap. crbug.com/378229
526 if (element.isFinalizing())
530 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
531 if (!thisElement.m_mediaKeys)
534 ASSERT(thisElement.m_emeMode == EmeModeUnprefixed);
535 thisElement.m_mediaKeys.clear();
538 WebContentDecryptionModule* HTMLMediaElementEncryptedMedia::contentDecryptionModule(HTMLMediaElement& element)
540 HTMLMediaElementEncryptedMedia& thisElement = HTMLMediaElementEncryptedMedia::from(element);
541 return thisElement.contentDecryptionModule();
544 void HTMLMediaElementEncryptedMedia::trace(Visitor* visitor)
546 visitor->trace(m_mediaKeys);
547 WillBeHeapSupplement<HTMLMediaElement>::trace(visitor);