This document is a draft specification proposal with no official status. Send comments to Aaron Colwell. It is inappropriate to cite this document except as a work in progress.
This proposal extends HTMLMediaElement to allow JavaScript to generate media streams for playback. Allowing JavaScript to generate streams facilitates a variety of use cases like adaptive streaming and time shifting live streams.
Historically <audio> and <video> have only allowed media playback from container file formats. Support for use cases like live have required hacks to the container format, like 0 size segments in WebM, or required the browser to support manifest formats like HLS. Supporting new adaptive streaming proposals like 3GP-DASH also require significant browser changes and the adaptation algorithms cannot be changed once the browser is released. The file container model also puts constraints on on-demand playback. Efficient seek support requires an index to be transfered to the browser which results in either delayed startup, or delays during the first seek. All of these issues stem from the fact that the browser is expected to manage all media fetching and do so based on the information contained in a single URL.
This proposal introduces a new object called MediaSource that allows JavaScript to control how media data is fetched. This will enable significant flexibility for media fetching without requiring browser changes. Manifest formats like HLS & 3GP-DASH could be handled in JavaScript. Indexes for on-demand playback would not be needed because JavaScript could make an AJAX call to a web service to find an appropriate seek point. Adaptive streaming algorithms could be implemented and evolved over time without needing to upgrade the browser. Fetching media from multiple CDNs or splicing in media from the local file store could also be implemented without browser changes. Allowing JavaScript to control media stream construction opens may avenues for experimentation and innovation.
We extend HTML media elements to allow media data to be streamed into them from JavaScript.
partial interface HTMLMediaElement {
// URL passed to src attribute to enable the media source logic.
readonly attribute [URL] DOMString webkitMediaSourceURL;
bool webkitSourceAppend(in Uint8Array data);
// end of stream status codes.
const unsigned short EOS_NO_ERROR = 0;
const unsigned short EOS_NETWORK_ERR = 1;
const unsigned short EOS_DECODE_ERR = 2;
void webkitSourceEndOfStream(in unsigned short status);
// states
const unsigned short SOURCE_CLOSED = 0;
const unsigned short SOURCE_OPEN = 1;
const unsigned short SOURCE_ENDED = 2;
readonly attribute unsigned short webkitSourceState;
};
The webkitMediaSourceURL attribute returns the URL used to enable the MediaSource extension methods. To enable the MediaSource extensions on a media element, assign this URL to the src attribute.
The webkitSourceAppend(data) method must run the following steps:
INVALID_ACCESS_ERR exception and abort these steps.webkitSourceState attribute is not in the SOURCE_OPEN state then throw an INVALID_STATE_ERR exception and abort these steps.EOS_NO_ERROR (numeric value 0)EOS_NETWORK_ERR (numeric value 1)error attribute to be set to MediaError.MEDIA_ERR_NETWORK
EOS_DECODE_ERR (numeric value 2)error attribute to be set to MediaError.MEDIA_ERR_DECODE
The webkitSourceEndOfStream(status) method must run the following steps:
webkitSourceState attribute is not in the SOURCE_OPEN state then throw an INVALID_STATE_ERR exception and abort these steps.webkitSourceState attribute's value to SOURCE_ENDED.EOS_NO_ERROR
webkitSourceAppend() has been played.EOS_NETWORK_ERR
EOS_DECODE_ERR
INVALID_ACCESS_ERR exception.The webkitSourceState attribute represents the state of the source. It can have the following values:
SOURCE_CLOSED (numeric value 0)SOURCE_OPEN (numeric value 1)SOURCE_ENDED (numeric value 2)webkitSourceEndOfStream() has been called on this source.When the media element is created webkitSourceState must be set to SOURCE_CLOSED (0).
| Event name | Interface | Dispatched when... |
|---|---|---|
webkitsourceopen |
Event |
When the source transitions from SOURCE_CLOSED to SOURCE_OPEN or from SOURCE_ENDED to SOURCE_OPEN. |
webkitsourceended |
Event |
When the source transitions from SOURCE_OPEN to SOURCE_ENDED. |
webkitsourceclose |
Event |
When the source transitions from SOURCE_OPEN or SOURCE_ENDED to SOURCE_ENDED. |
src attribute on a media element or the src attribute on a <source> element associated with a media element to webkitMediaSourceURL
When the media element attempts the resource fetch algorithm with the URL from webkitMediaSourceURL it will take one of the following actions:
webkitSourceState is NOT set to SOURCE_CLOSED
MEDIA_ERR_SRC_NOT_SUPPORTED error.webkitSourceState attribute to SOURCE_OPEN
webkitsourceopen.webkitSourceAppend()
The following steps are run in any case where the media element is going to transition to NETWORK_EMPTY and fire an emptied event. These steps should be run right before the transition.
webkitSourceState attribute to SOURCE_CLOSED
webkitsourceclose.seeking algorithm starts and has reached the stage where it is about to fire the seeking event.webkitSourceState attribute is set to SOURCE_ENDED, then set it to SOURCE_OPEN
webkitSourceAppend() calls.seeking algorithm fires the seeking event and will proceed once data is passed to the media element via webkitSourceAppend().
This seeking algorithm assumes that the data passed to webkitSourceAppend() is packetized media with timestamps (ie Ogg pages or WebM clusters). This allows the "new playback position" to be determined by the timestamps in the data. If we want to support formats that are not packetized, like WAV files, then we will need a different mechanism to communicate the "new playback position" when a seek occurs.
Example use of MediaSource
<script>
function onOpen(e) {
var video = e.target;
var headers = GetStreamHeaders();
if (headers == null) {
// Error fetching headers. Signal end of stream with an error.
video.webkitSourceEndOfStream(HTMLMediaElement.EOS_NETWORK_ERR);
}
// Append the stream headers (ie WebM Header, Info, and Tracks elements)
video.webkitSourceAppend(headers);
// Append some initial media data.
video.webkitSourceAppend(GetNextCluster());
}
function onSeeking(e) {
var video = e.target;
// Notify the cluster loading code to start fetching data at the
// new playback position.
SeekToClusterAt(video.currentTime);
// Append clusters from the new playback position.
video.webkitSourceAppend(GetNextCluster());
video.webkitSourceAppend(GetNextCluster());
}
function onProgress(e) {
var video = e.target;
if (video.webkitSourceState == mediaSource.SOURCE_ENDED)
return;
// If we have run out of stream data, then signal end of stream.
if (!HaveMoreClusters()) {
video.webkitSourceEndOfStream(HTMLMediaElement.EOS_NO_ERROR);
return;
}
video.webkitSourceAppend(GetNextCluster());
}
var video = document.getElementById('v');
video.addEventListener('webkitsourceopen', onOpen);
video.addEventListener('seeking', onSeeking);
video.addEventListener('progress', onProgress);
</script>
<video id="v" autoplay> </video>
<script>
var video = document.getElementById('v');
video.src = video.webkitMediaSourceURL;
</script>
| Version | Comment |
|---|---|
| 0.2 | Updates to reflect initial WebKit implementation. |
| 0.1 | Initial Proposal |