This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 25199 - EME should use Promises
Summary: EME should use Promises
Status: RESOLVED FIXED
Alias: None
Product: HTML WG
Classification: Unclassified
Component: Encrypted Media Extensions (show other bugs)
Version: unspecified
Hardware: All All
: P1 normal
Target Milestone: ---
Assignee: David Dorwin
QA Contact: HTML WG Bugzilla archive list
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 17750 21798 24081 24216 24771
  Show dependency treegraph
 
Reported: 2014-03-29 00:39 UTC by David Dorwin
Modified: 2014-04-21 23:05 UTC (History)
2 users (show)

See Also:


Attachments

Description David Dorwin 2014-03-29 00:39:02 UTC
=== Summary ===
Most EME methods - setMediaKeys(), MediaKeys initialization, createSession(), loadSession(), update(), and release() - should return a Promise to report the result of their asynchronous operation.

From the Abstract of the TAG’s Writing Promise-Using Specifications (https://github.com/w3ctag/promises-guide): "Promises are now the web platform's paradigm for all "one and done" asynchronous operations… Going forward, all asynchronous operations of this type should be specified to instead return promises..."


=== Analysis ===
All of the EME methods except isTypeSupported() and mediaKeys() are asynchronous, primarily because they must interact with the CDM.

Although createSession(), loadSession(), update(), and release() calls could also result in message events (not "one and done"), they do have a well-defined one-time asynchronous algorithms that either succeed or fail.

Note that "Rejections Should Be Used for Exceptional Situations" (https://github.com/w3ctag/promises-guide#rejections-should-be-used-for-exceptional-situations). This means that failing to find a sessionId in loadSession() should not result in a rejection. Instead, the promise should be rejected with "undefined". Other failures - for example, how to handle unsupported |contentType| - is unclear but may fall into the judgement call category.

"message" events are recurring, so they should remain events. https://github.com/w3ctag/promises-guide#recurring-events
Errors during playback (i.e. resulting from license/key use) should also remain events.


=== Current State ===
EME currently defines two categories of errors:
1) Errors directly resulting from a method call.
 * When to report these is defined in the algorithms.
 * These are further divided into:
    - Synchronous DOMExceptions reported by the UA.
    - Asynchronous error events (and MediaKeyError objects) generally originating in the CDM.
2) Errors that may occur at any time and do not directly result from an EME method call.
 * When to report these is *not* defined in the algorithms. (Like "message" and "close" events, they may occur at any time for unspecified reasons.)
 * These are always reported asynchronously as error events (and MediaKeyError objects) from the CDM.
 * This includes errors during decryption/playback of media data.
 * These could be considered status/notifications and/or message events intended for the application instead of the license server. 

Issues:
* EME may report two different types of errors, meaning applications need to handle both.
* Errors resulting from method calls (type 1) cannot be tied back to the call. (This becomes an issue in the discussion of things like a store() method.)
* The use of MediaKeyError and the error event for all asynchronous errors leads to a very strange error reporting mechanism for the MediaKeys constructor (saving an error to be reported when a session is created; see https://www.w3.org/Bugs/Public/show_bug.cgi?id=17750#c26 for further complications).
* EME is not consistent with the "web platform's [new] paradigm for… asynchronous operations."


=== Advantages of Using Promises in EME ===
Promises would allow us to:
* Have a consistent error reporting mechanism for method calls, simplifying applications.
* Directly associate errors with the call that caused them, where applicable.
  - * This is even more important if we add additional methods (i.e. store()) that can report errors.
* Report errors on the right object during MediaKeys/CDM initialization.
  - And thus avoid saving error information and spinning the event loop in other methods (https://www.w3.org/Bugs/Public/show_bug.cgi?id=17750#c26).
* Address the use case for the "open" event (https://www.w3.org/Bugs/Public/show_bug.cgi?id=24081#c4).
* Simplify the algorithms - no more synchronous and asynchronous sections or tasks.
* Explicitly separate playback-related errors from method call failures.
* Potentially simplify lifetime and/or event guarantees (bug 24771).
* Address the asynchronous nature of setMediaKeys (bug 24216).
* MediaKeySession states may be unnecessary.
  - The session object can’t be in the CREATED state because it is not provided until it is OPEN.
  - ERROR doesn’t appear to be used for anything meaningful.
  - CLOSED could be replaced by "is closed" if necessary.


=== Changes ===

= Methods =
ALL EME methods except isTypeSupported() and mediaKeys() will return a Promise.

HTMLMediaElement.setMediaKeys():
* resolve: "undefined" to indicate a successful change of the attached MediaKeys object.
   - This would occur after any related changes to the media stack, including stopping decoders.
* reject: Error indicating the reason for failure, which is likely related to the MediaKeys object being in use by another element or the UA not supporting detaching of MediaKeys objects (see bug 24216).

MediaKeys construction:
* (Unless constructors can somehow return a Promise,) we can’t directly use a Promise in the MediaKeys constructor, which involves asynchronous loading and initialization of the CDM.
* We have two options:
  1) Add a static creation method that returns a Promise and remove the constructor.
    Options for where to attach this static method:
       * MediaKeys.create()
       * Window.createMediaKeys()
       * HTMLMediaElement.createMediaKeys()
  2) Move the asynchronous portion of the constructor to an initialize() method that returns a Promise
* I prefer the first option because it is more consistent with how MediaKeySession objects are created and all applications will immediately call initialize() anyway.
* resolve: Provides a MediaKeys object that is initialized and ready for immediate use. (The CDM instance is also initialized.)
* reject: Error indicating the reason for failure, likely a CDM initialization error.

MediaKeys.createSession():
* resolve: Provides a MediaKeySession object in the OPEN state with a valid sessionId.
* reject: Error indicating the reason for failure.
* Note: The "message" event is fired *after* the Promise is resolved, allowing the application to add an event listener.

MediaKeys.loadSession():
* resolve: Provides a MediaKeySession object in the OPEN state with a valid sessionId and all session data loaded.
  - The provided value will be "undefined" if the sessionId was not found (per https://github.com/w3ctag/promises-guide#rejections-should-be-used-for-exceptional-situations).
* reject: Error indicating the reason for failure.
* Note: Any "message" event is fired *after* the Promise is resolved, allowing the application to add an event listener.

MediaKeySession.update():
* resolve: "undefined" to indicate successful processing of the |response|.
* reject: Error indicating the reason for failing to process |response|.

MediaKeySession.release():
* resolve: "undefined" to acknowledge the call. This includes when the session is already CLOSED.
* reject: Error indicating the reason for failure.
* Note: The "close" event would be fired whenever the CDM decides and does not delay settling the Promise.

= Events =
The "ready"-now-"open" event is removed from MediaKeySession.

The second category of errors should remain unchanged, continuing to fire an "error" event at the session. There is no way to use a Promise for these in the same way as the first category.

= Errors =
"Rejection Reasons Should Be _Error_s" and "you should never use DOMError, but instead use DOMException, which per WebIDL extends Error."
  -- https://github.com/w3ctag/promises-guide#rejection-reasons-should-be-error
(There is also this WebIDL bug 21740, which would appear to be resolved by the previous link.)

It appears that DOMExceptions can have any name (see the second Note at http://heycam.github.io/webidl/#idl-exceptions). If we still need to report system codes in the Promise rejection, we may be able to further extend DOMException.

The "error" event would continue to report a MediaKeyError, though this might now inherit from DOMException. (We could - but do not necessarily need to - use the same type of object for the two categories of errors.)

Since the use of Promises means that not all categories of errors will result in an error event and corresponding setting of the error attribute, we may want to reconsider the current behavior of using a simple event with error attribute. We may instead want to fire a complex event or use some other mechanism.


=== Other Possible Changes ===
The following are changes that are not strictly necessary but that we may wish to consider while changing the APIs.

= Session Creation =
We may also want to reconsider session creation and allow an empty MediaKeySession to be created without generating a license request, loading, etc.

In this scenario:
* void MediaKeys.createSession(), which would take no parameters, returns an empty MediaKeySession in the CREATED state.
* Promise<TBD> session.generateRequest(DOMString contentType, Uint8Array initData) would execute the existing createSession() algorithm except for the construction of the new MediaKeySession object (step 6).
* Promise<TBD> session.load(DOMString sessionId) would likewise execute the existing loadSession() algorithm except for the construction of the new MediaKeySession object (step 3).

I’m not sure which is better for applications. If, for some reason, the resolving of createSession()’s Promise is too late for applications to add event listeners and still receive the "message" event, we might be forced into this model.

If we went with this model, we might also consider separating MediaKeys construction from initialization to maintain consistency.

= Events =
* The "close" event could potentially become a One-Time "Event" (https://github.com/w3ctag/promises-guide#one-time-events)
* If we do end up with some type of session ready event, it should be a One-Time "Event".
* The "error" event could instead be a "status" event, especially depending on the outcome of bug 21798. (The remaining "errors" may not actually indicate fatal errors.)
* We could convert the "close" event into a "status"/"error" event, leaving just this event and the the "message" event.
Comment 1 David Dorwin 2014-04-02 21:36:06 UTC
There was some initial support in the April 1 telecon. People should review the full proposal before the f2f on April 9.


Below is a proposal for an IDL that reflects the proposal. In addition, the only MediaKeySession events would be "message" and "error" (possibly with a TBD better name).


enum MediaWaitingFor { "none", "data", "key" };

partial interface HTMLMediaElement {
  // Encrypted Media
  readonly attribute MediaKeys mediaKeys;
  Promise<any> setMediaKeys(MediaKeys mediaKeys);
  
  attribute EventHandler onneedkey;

  readonly attribute MediaWaitingFor waitingFor;
};

interface MediaKeys {
  readonly attribute DOMString keySystem;

  Promise<MediaKeySession> createSession(DOMString initDataType, Uint8Array initData);
  Promise<MediaKeySession> loadSession(DOMString sessionId);

  static Promise<MediaKeys> create(DOMString keySystem)
  static bool isTypeSupported(DOMstring keySystem, optional DOMString contentType);
};

interface MediaKeySession : EventTarget {
  // error state
  readonly attribute MediaKeyError? error;

  // session properties
  readonly attribute DOMString keySystem;
  readonly attribute DOMString sessionId;
  readonly attribute Promise<any> close;

  // session operations
  Promise<any> update(Uint8Array response);
  Promise<any> release();
};

partial interface HTMLSourceElement {
  attribute DOMString keySystem;
};
Comment 2 David Dorwin 2014-04-09 17:07:32 UTC
Agreed to implement at the f2f.
Comment 3 David Dorwin 2014-04-14 22:38:51 UTC
Implemented in https://dvcs.w3.org/hg/html-media/rev/9842af174b80.

As demonstrated in the examples, MediaKeys and MediaKeySession creation is more complex, especially when supporting more than one session in response to needkey events as in example 8.2. Since there may not be a MediaKeys object, initData must be queued. Also, we need an additional variable to determine whether there is a pending MediaKeys creation because video.mediaKeys is asynchronously set sometime after MediaKeys.create() asynchronously completes.
Comment 4 David Dorwin 2014-04-21 23:05:03 UTC
I have improved the examples and added a new one at https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#example-mediakeys-before-source