You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
5.5 KiB
201 lines
5.5 KiB
/**
|
|
* @file source-updater.js
|
|
*/
|
|
import videojs from 'video.js';
|
|
import { printableRange } from './ranges';
|
|
import logger from './util/logger';
|
|
import noop from './util/noop';
|
|
|
|
/**
|
|
* A queue of callbacks to be serialized and applied when a
|
|
* MediaSource and its associated SourceBuffers are not in the
|
|
* updating state. It is used by the segment loader to update the
|
|
* underlying SourceBuffers when new data is loaded, for instance.
|
|
*
|
|
* @class SourceUpdater
|
|
* @param {MediaSource} mediaSource the MediaSource to create the
|
|
* SourceBuffer from
|
|
* @param {String} mimeType the desired MIME type of the underlying
|
|
* SourceBuffer
|
|
* @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is
|
|
* added to the media source
|
|
*/
|
|
export default class SourceUpdater {
|
|
constructor(mediaSource, mimeType, type, sourceBufferEmitter) {
|
|
this.callbacks_ = [];
|
|
this.pendingCallback_ = null;
|
|
this.timestampOffset_ = 0;
|
|
this.mediaSource = mediaSource;
|
|
this.processedAppend_ = false;
|
|
this.type_ = type;
|
|
this.mimeType_ = mimeType;
|
|
this.logger_ = logger(`SourceUpdater[${type}][${mimeType}]`);
|
|
|
|
if (mediaSource.readyState === 'closed') {
|
|
mediaSource.addEventListener(
|
|
'sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter));
|
|
} else {
|
|
this.createSourceBuffer_(mimeType, sourceBufferEmitter);
|
|
}
|
|
}
|
|
|
|
createSourceBuffer_(mimeType, sourceBufferEmitter) {
|
|
this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType);
|
|
|
|
this.logger_('created SourceBuffer');
|
|
|
|
if (sourceBufferEmitter) {
|
|
sourceBufferEmitter.trigger('sourcebufferadded');
|
|
|
|
if (this.mediaSource.sourceBuffers.length < 2) {
|
|
// There's another source buffer we must wait for before we can start updating
|
|
// our own (or else we can get into a bad state, i.e., appending video/audio data
|
|
// before the other video/audio source buffer is available and leading to a video
|
|
// or audio only buffer).
|
|
sourceBufferEmitter.on('sourcebufferadded', () => {
|
|
this.start_();
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.start_();
|
|
}
|
|
|
|
start_() {
|
|
this.started_ = true;
|
|
|
|
// run completion handlers and process callbacks as updateend
|
|
// events fire
|
|
this.onUpdateendCallback_ = () => {
|
|
let pendingCallback = this.pendingCallback_;
|
|
|
|
this.pendingCallback_ = null;
|
|
|
|
this.logger_(`buffered [${printableRange(this.buffered())}]`);
|
|
|
|
if (pendingCallback) {
|
|
pendingCallback();
|
|
}
|
|
|
|
this.runCallback_();
|
|
};
|
|
|
|
this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_);
|
|
|
|
this.runCallback_();
|
|
}
|
|
|
|
/**
|
|
* Aborts the current segment and resets the segment parser.
|
|
*
|
|
* @param {Function} done function to call when done
|
|
* @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
|
|
*/
|
|
abort(done) {
|
|
if (this.processedAppend_) {
|
|
this.queueCallback_(() => {
|
|
this.sourceBuffer_.abort();
|
|
}, done);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Queue an update to append an ArrayBuffer.
|
|
*
|
|
* @param {ArrayBuffer} bytes
|
|
* @param {Function} done the function to call when done
|
|
* @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
|
|
*/
|
|
appendBuffer(bytes, done) {
|
|
this.processedAppend_ = true;
|
|
this.queueCallback_(() => {
|
|
this.sourceBuffer_.appendBuffer(bytes);
|
|
}, done);
|
|
}
|
|
|
|
/**
|
|
* Indicates what TimeRanges are buffered in the managed SourceBuffer.
|
|
*
|
|
* @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
|
|
*/
|
|
buffered() {
|
|
if (!this.sourceBuffer_) {
|
|
return videojs.createTimeRanges();
|
|
}
|
|
return this.sourceBuffer_.buffered;
|
|
}
|
|
|
|
/**
|
|
* Queue an update to remove a time range from the buffer.
|
|
*
|
|
* @param {Number} start where to start the removal
|
|
* @param {Number} end where to end the removal
|
|
* @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
|
|
*/
|
|
remove(start, end) {
|
|
if (this.processedAppend_) {
|
|
this.queueCallback_(() => {
|
|
this.logger_(`remove [${start} => ${end}]`);
|
|
this.sourceBuffer_.remove(start, end);
|
|
}, noop);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether the underlying sourceBuffer is updating or not
|
|
*
|
|
* @return {Boolean} the updating status of the SourceBuffer
|
|
*/
|
|
updating() {
|
|
return !this.sourceBuffer_ || this.sourceBuffer_.updating || this.pendingCallback_;
|
|
}
|
|
|
|
/**
|
|
* Set/get the timestampoffset on the SourceBuffer
|
|
*
|
|
* @return {Number} the timestamp offset
|
|
*/
|
|
timestampOffset(offset) {
|
|
if (typeof offset !== 'undefined') {
|
|
this.queueCallback_(() => {
|
|
this.sourceBuffer_.timestampOffset = offset;
|
|
});
|
|
this.timestampOffset_ = offset;
|
|
}
|
|
return this.timestampOffset_;
|
|
}
|
|
|
|
/**
|
|
* Queue a callback to run
|
|
*/
|
|
queueCallback_(callback, done) {
|
|
this.callbacks_.push([callback.bind(this), done]);
|
|
this.runCallback_();
|
|
}
|
|
|
|
/**
|
|
* Run a queued callback
|
|
*/
|
|
runCallback_() {
|
|
let callbacks;
|
|
|
|
if (!this.updating() &&
|
|
this.callbacks_.length &&
|
|
this.started_) {
|
|
callbacks = this.callbacks_.shift();
|
|
this.pendingCallback_ = callbacks[1];
|
|
callbacks[0]();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* dispose of the source updater and the underlying sourceBuffer
|
|
*/
|
|
dispose() {
|
|
this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
|
|
if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') {
|
|
this.sourceBuffer_.abort();
|
|
}
|
|
}
|
|
}
|