Skip to content

Commit

Permalink
Move MediaSource setup to MediaSourceEngine
Browse files Browse the repository at this point in the history
This isolates MediaSource code without changing the early
initialization of MediaSource, which was designed for low-latency
startup.

Prerequisite for #1087, #880, #997, #816

Change-Id: I61acedd95d73610d3e67436d9c7767d643fe2d29
  • Loading branch information
joeyparrish committed Feb 20, 2018
1 parent 917a01d commit b7bcafc
Show file tree
Hide file tree
Showing 12 changed files with 589 additions and 535 deletions.
113 changes: 75 additions & 38 deletions lib/media/media_source_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,18 @@ goog.require('shaka.util.PublicPromise');
* internally synchronized and serialized as needed. Operations that can
* be done in parallel will be done in parallel.
*
* @param {HTMLMediaElement} video The video element, used to read error codes
* when MediaSource operations fail.
* @param {MediaSource} mediaSource The MediaSource, which must be in the
* 'open' state.
* @param {HTMLMediaElement} video The video element, whose source is tied to
* MediaSource during the lifetime of the MediaSourceEngine.
* @param {shakaExtern.TextDisplayer} textDisplayer
*
* @struct
* @constructor
* @implements {shaka.util.IDestroyable}
*/
shaka.media.MediaSourceEngine = function(video, mediaSource, textDisplayer) {
goog.asserts.assert(mediaSource.readyState == 'open',
'The MediaSource should be in the \'open\' state.');

shaka.media.MediaSourceEngine = function(video, textDisplayer) {
/** @private {HTMLMediaElement} */
this.video_ = video;

/** @private {MediaSource} */
this.mediaSource_ = mediaSource;

/** @private {shakaExtern.TextDisplayer} */
this.textDisplayer_ = textDisplayer;

Expand Down Expand Up @@ -85,6 +77,32 @@ shaka.media.MediaSourceEngine = function(video, mediaSource, textDisplayer) {

/** @private {boolean} */
this.useEmbeddedText_ = false;

/** @private {!shaka.util.PublicPromise} */
this.mediaSourceOpen_ = new shaka.util.PublicPromise();

/** @private {MediaSource} */
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
};


/**
* Create a MediaSource object, attach it to the video element, and return it.
* Resolves the given promise when the MediaSource is ready.
*
* Replaced by unit tests.
*
* @param {!shaka.util.PublicPromise} p
* @return {!MediaSource}
*/
shaka.media.MediaSourceEngine.prototype.createMediaSource = function(p) {
let mediaSource = new MediaSource();

// Set up MediaSource on the video element.
this.eventManager_.listenOnce(mediaSource, 'sourceopen', p.resolve);
this.video_.src = window.URL.createObjectURL(mediaSource);

return mediaSource;
};


Expand Down Expand Up @@ -222,6 +240,10 @@ shaka.media.MediaSourceEngine.prototype.destroy = function() {
cleanup.push(this.transmuxers_[contentType].destroy());
}

if (this.video_) {
this.video_.removeAttribute('src');
this.video_.load();
}

return Promise.all(cleanup).then(function() {
this.eventManager_.destroy();
Expand Down Expand Up @@ -251,43 +273,49 @@ shaka.media.MediaSourceEngine.prototype.destroy = function() {
* reinitialize text streams.
*
* @param {!Object.<shaka.util.ManifestParserUtils.ContentType,
shakaExtern.Stream>} streamsByType
* shakaExtern.Stream>} streamsByType
* A map of content types to streams. All streams must be supported according
* to MediaSourceEngine.isStreamSupported.
*
* @return {!Promise}
*
* @throws InvalidAccessError if blank MIME types are given
* @throws NotSupportedError if unsupported MIME types are given
* @throws QuotaExceededError if the browser can't support that many buffers
*/
shaka.media.MediaSourceEngine.prototype.init = function(streamsByType) {
var ContentType = shaka.util.ManifestParserUtils.ContentType;

for (var contentType in streamsByType) {
var stream = streamsByType[contentType];
goog.asserts.assert(
shaka.media.MediaSourceEngine.isStreamSupported(stream),
'Type negotiation should happen before MediaSourceEngine.init!');

var mimeType = shaka.util.MimeUtils.getFullType(
stream.mimeType, stream.codecs);
if (contentType == ContentType.TEXT) {
this.reinitText(mimeType);
} else {
if (!MediaSource.isTypeSupported(mimeType) &&
shaka.media.Transmuxer.isSupported(contentType, mimeType)) {
this.transmuxers_[contentType] = new shaka.media.Transmuxer();
mimeType =
shaka.media.Transmuxer.convertTsCodecs(contentType, mimeType);
const ContentType = shaka.util.ManifestParserUtils.ContentType;

return this.mediaSourceOpen_.then(() => {
for (let contentType in streamsByType) {
let stream = streamsByType[contentType];
goog.asserts.assert(
shaka.media.MediaSourceEngine.isStreamSupported(stream),
'Type negotiation should happen before MediaSourceEngine.init!');

let mimeType = shaka.util.MimeUtils.getFullType(
stream.mimeType, stream.codecs);
if (contentType == ContentType.TEXT) {
this.reinitText(mimeType);
} else {
if (!MediaSource.isTypeSupported(mimeType) &&
shaka.media.Transmuxer.isSupported(contentType, mimeType)) {
this.transmuxers_[contentType] = new shaka.media.Transmuxer();
mimeType =
shaka.media.Transmuxer.convertTsCodecs(contentType, mimeType);
}
let sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
this.eventManager_.listen(
sourceBuffer, 'error',
this.onError_.bind(this, contentType));
this.eventManager_.listen(
sourceBuffer, 'updateend',
this.onUpdateEnd_.bind(this, contentType));
this.sourceBuffers_[contentType] = sourceBuffer;
this.queues_[contentType] = [];
}
var sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
this.eventManager_.listen(
sourceBuffer, 'error', this.onError_.bind(this, contentType));
this.eventManager_.listen(
sourceBuffer, 'updateend', this.onUpdateEnd_.bind(this, contentType));
this.sourceBuffers_[contentType] = sourceBuffer;
this.queues_[contentType] = [];
}
}
});
};


Expand All @@ -303,6 +331,15 @@ shaka.media.MediaSourceEngine.prototype.reinitText = function(mimeType) {
};


/**
* @return {boolean} True if the MediaSource is in an "ended" state, or if the
* object has been destroyed.
*/
shaka.media.MediaSourceEngine.prototype.ended = function() {
return this.mediaSource_ ? this.mediaSource_.readyState == 'ended' : true;
};


/**
* Gets the first timestamp in buffer for the given content type.
*
Expand Down
12 changes: 6 additions & 6 deletions lib/media/playhead_observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ goog.require('shaka.util.StreamUtils');
* </ul>
*
* @param {HTMLMediaElement} video
* @param {MediaSource} mediaSource
* @param {shaka.media.MediaSourceEngine} mediaSourceEngine
* @param {shakaExtern.Manifest} manifest
* @param {shakaExtern.StreamingConfiguration} config
* @param {function(boolean)} onBuffering Called and passed true when stopped
Expand All @@ -53,13 +53,13 @@ goog.require('shaka.util.StreamUtils');
* @implements {shaka.util.IDestroyable}
*/
shaka.media.PlayheadObserver = function(
video, mediaSource, manifest, config, onBuffering, onEvent,
video, mediaSourceEngine, manifest, config, onBuffering, onEvent,
onChangePeriod) {
/** @private {HTMLMediaElement} */
this.video_ = video;

/** @private {MediaSource} */
this.mediaSource_ = mediaSource;
/** @private {shaka.media.MediaSourceEngine} */
this.mediaSourceEngine_ = mediaSourceEngine;

/** @private {?shakaExtern.Manifest} */
this.manifest_ = manifest;
Expand Down Expand Up @@ -141,7 +141,7 @@ shaka.media.PlayheadObserver.prototype.destroy = function() {
this.cancelWatchdogTimer_();

this.video_ = null;
this.mediaSource_ = null;
this.mediaSourceEngine_ = null;
this.manifest_ = null;
this.config_ = null;
this.onBuffering_ = null;
Expand Down Expand Up @@ -311,7 +311,7 @@ shaka.media.PlayheadObserver.prototype.onWatchdogTimer_ = function() {
var timeline = this.manifest_.presentationTimeline;
var liveEdge = timeline.getSegmentAvailabilityEnd();
var bufferedToLiveEdge = timeline.isLive() && bufferEnd >= liveEdge;
var noMoreSegments = this.mediaSource_.readyState == 'ended';
let noMoreSegments = this.mediaSourceEngine_.ended();

var atEnd = bufferedToLiveEdge || this.video_.ended || noMoreSegments;
if (!this.buffering_) {
Expand Down
28 changes: 16 additions & 12 deletions lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -508,9 +508,12 @@ shaka.media.StreamingEngine.prototype.loadNewTextStream = function(stream) {
this.textStreamSequenceId_++;
this.unloadingTextStream_ = false;
var currentSequenceId = this.textStreamSequenceId_;
this.playerInterface_.mediaSourceEngine.init({text: stream});

return this.setupStreams_([stream]).then(function() {
let mediaSourceEngine = this.playerInterface_.mediaSourceEngine;

return mediaSourceEngine.init({text: stream}).then(() => {
return this.setupStreams_([stream]);
}).then(() => {
if ((this.textStreamSequenceId_ == currentSequenceId) &&
!this.mediaStates_[ContentType.TEXT] && !this.unloadingTextStream_) {
var playheadTime = this.playerInterface_.playhead.getTime();
Expand All @@ -519,7 +522,7 @@ shaka.media.StreamingEngine.prototype.loadNewTextStream = function(stream) {
this.createMediaState_(stream, needPeriodIndex);
this.scheduleUpdate_(this.mediaStates_[ContentType.TEXT], 0);
}
}.bind(this));
});
};


Expand Down Expand Up @@ -827,24 +830,25 @@ shaka.media.StreamingEngine.prototype.initStreams_ = function(
}

// Init MediaSourceEngine.
this.playerInterface_.mediaSourceEngine.init(streamsByType);
let mediaSourceEngine = this.playerInterface_.mediaSourceEngine;
return mediaSourceEngine.init(streamsByType).then(() => {
this.setDuration_();

this.setDuration_();

// Setup the initial set of Streams and then begin each update cycle. After
// startup completes onUpdate_() will set up the remaining Periods.
return this.setupStreams_(streams).then(function() {
// Setup the initial set of Streams and then begin each update cycle. After
// startup completes onUpdate_() will set up the remaining Periods.
return this.setupStreams_(streams);
}).then(() => {
if (this.destroyed_) return;

for (var type in streamsByType) {
var stream = streamsByType[type];
for (let type in streamsByType) {
let stream = streamsByType[type];
if (!this.mediaStates_[type]) {
this.mediaStates_[type] =
this.createMediaState_(stream, needPeriodIndex, opt_resumeAt);
this.scheduleUpdate_(this.mediaStates_[type], 0);
}
}
}.bind(this));
});
};


Expand Down
Loading

0 comments on commit b7bcafc

Please sign in to comment.