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 introduces a MediaSource object which allows JavaScript to generate media streams for playback in HTMLMediaElements. 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.
This interface provides a way to stream data to a media element.
[Constructor(in DOMString type)]
interface MediaSource {
readonly attribute DOMString type;
// states
const unsigned short CLOSED = 0;
const unsigned short OPEN = 1;
const unsigned short ENDED = 2;
readonly attribute unsigned short readyState;
readonly attribute unsigned long bytesAvailable;
bool append(in ArrayBuffer data);
void endOfStream();
// event handler attributes
attribute Function onopen;
attribute Function onwaiting;
attribute Function onflush;
attribute Function onprogress;
attribute Function onended;
attribute Function onclose;
};
MediaSource implements EventTarget;
The MediaSource(type) constructor takes a single argument. This argument, type, specifies the mimetype for data that will be streamed to the media element. This is similar to the type attribute on a <source> element. If type is a null or empty string a SYNTAX_ERR exception will be thrown.
The type attribute returns the type value passed into the constructor.
The readyState attribute represents the state of the source. It can have the following values:
CLOSED (numeric value 0)OPEN (numeric value 1)ENDED (numeric value 2)When the object is created readyState must be set to CLOSED (0).
The bytesAvailable attribute indicates the current number of bytes available in the source buffer. append() will fail if called with an ArrayBuffer larger than this value.
The append(data) method must run the following steps:
INVALID_ACCESS_ERR exception and abort these steps.readyState attribute is not in the OPEN state then throw an INVALID_STATE_ERR exception and abort these steps.Run the first matching steps from the following list:
bytesAvailable attributeReturn false.
Copy contents of data into the media element's decode buffer.
Decrement bytesAvailable attribute by data.byteLength.
Return true.
The endOfStream() method must run the following steps:
readyState attribute is not in the OPEN state then throw an INVALID_STATE_ERR exception and abort these steps.readyState attribute's value to ENDED.ended at the source.
| Event handler | Event handler event type |
|---|---|
onopen |
open |
onwaiting |
waiting |
onflush |
flush |
onprogress |
progress |
onended |
ended |
onclose |
close |
| Event name | Interface | Dispatched when... |
|---|---|---|
open |
Event |
When the source transitions from CLOSED to OPEN |
waiting |
Event |
When the media element has played all the data passed to append() and needs more data to prevent playback from stalling. |
flush |
Event |
When the media element initiates a seek. |
progress |
Event |
Every 350ms(+/-200ms) or every time bytesAvailable increases, whichever is least frequent. |
ended |
Event |
When the source transitions from OPEN to ENDED. |
close |
Event |
When the source transitions to CLOSED |
MediaSource object with newURI.createObjectURL()src attribute on a media element or the src attribute on a <source> element associated with a media element.When the media element attempts the resource fetch algorithm with the URL for the source object it will take one of the following actions:
readyState is NOT set to CLOSEDMEDIA_ERR_SRC_NOT_SUPPORTED error.type attribute contains a mimetype that is not supported by the media element.readyState attribute to OPENopen at the source.append()The follwing 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.
readyState attribute to CLOSEDclose at the source.append() and still has data to decode, abort these steps.waiting at the source.seeking event on the media elementreadyState attribute is set to ENDED, then set it to OPENflush at the source.append().This seeking algorithm assumes that the data passed to the MediaSource 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 mediaSource = e.target;
// Append the stream headers (ie WebM Header, Info, and Tracks elements)
mediaSource.append(GetStreamHeaders());
// Append some initial media data.
mediaSource.append(GetNextCluster());
}
function onFlush(e) {
var mediaSource = e.target;
var video = document.getElementById('v');
// Notify the cluster loading code to start fetching data at the
// new playback position.
SeekToClusterAt(video.currentTime);
// Append clusters from the new playback position.
mediaSource.append(GetNextCluster());
mediaSource.append(GetNextCluster());
}
function onProgress(e) {
var mediaSource = e.target;
if (mediaSource.readyState == mediaSource.ENDED)
return;
// If we have run out of stream data, then signal end of stream.
if (!HaveMoreClusters()) {
mediaSource.endOfStream();
return;
}
// Is the next cluster larger than available buffer space?
if (PeekNextCluster().length > mediaSource.bytesAvailable)
return;
mediaSource.append(GetNextCluster());
}
mediaSource = new MediaSource('video/webm; codecs="vp8, vorbis"');
mediaSource.addEventListener('open', onOpen);
mediaSource.addEventListener('flush', onFlush);
mediaSource.addEventListener('progress', onProgress);
</script>
<video id="v" autoplay> </video>
<script>
document.getElementById('v').src = URI.createObject(mediaSource);
</script>