Files
shaka-player/test/media/media_source_engine_integration.js
T
Theodore Abshire 4327153007 Loosen tolerance on CEA-708 captions test
On some platforms, the sample used in that test leads to a
buffer start time of 1.4; on other platforms, it leads
to a buffer start time of 1.4667333333333332.
Perhaps this is a result of the implementation-specific rounding
differences in different mediaSource implementations, or perhaps there
us a very small gap in the content that some platforms handle slightly
differently.

Either way, this change loosens the tolerance on the check. That buffer
start time check wasn't the important part of the test, anyway.

Fixes: 77471871
Change-Id: I866fafe671566cc7116e7c99222f75225cffd476
2018-04-02 14:11:31 -07:00

402 lines
15 KiB
JavaScript

/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
describe('MediaSourceEngine', function() {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
const presentationDuration = 840;
/** @type {!HTMLVideoElement} */
let video;
/** @type {!MediaSource} */
let mediaSource;
/** @type {!shaka.media.MediaSourceEngine} */
let mediaSourceEngine;
let generators;
let metadata;
// TODO: add text streams to MSE integration tests
beforeAll(function() {
video = /** @type {!HTMLVideoElement} */ (document.createElement('video'));
video.width = 600;
video.height = 400;
document.body.appendChild(video);
});
beforeEach(function(done) {
metadata = shaka.test.TestScheme.DATA['sintel'];
generators = shaka.test.TestScheme.GENERATORS['sintel'];
mediaSourceEngine = new shaka.media.MediaSourceEngine(video);
mediaSource = /** @type {?} */(mediaSourceEngine)['mediaSource_'];
expect(video.src).toBeTruthy();
mediaSourceEngine.init({}, false).then(done);
});
afterEach(function(done) {
mediaSourceEngine.destroy().catch(fail).then(done);
});
afterAll(function() {
document.body.removeChild(video);
});
function appendInit(type) {
let segment = generators[type].getInitSegment(Date.now() / 1000);
return mediaSourceEngine.appendBuffer(type, segment, null, null);
}
function append(type, segmentNumber) {
let segment = generators[type].
getSegment(segmentNumber, 0, Date.now() / 1000);
return mediaSourceEngine.appendBuffer(type, segment, null, null);
}
function buffered(type, time) {
return mediaSourceEngine.bufferedAheadOf(type, time);
}
function bufferStart(type) {
return mediaSourceEngine.bufferStart(type);
}
function remove(type, segmentNumber) {
let start = (segmentNumber - 1) * metadata[type].segmentDuration;
let end = segmentNumber * metadata[type].segmentDuration;
return mediaSourceEngine.remove(type, start, end);
}
function getFakeStream(streamMetadata) {
return {
mimeType: streamMetadata.mimeType,
codecs: streamMetadata.codecs
};
}
it('buffers MP4 video', function(done) {
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.init(initObject, false).then(() => {
return mediaSourceEngine.setDuration(presentationDuration);
}).then(() => {
return appendInit(ContentType.VIDEO);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBe(0);
return append(ContentType.VIDEO, 1);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(10);
return append(ContentType.VIDEO, 2);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(20);
return append(ContentType.VIDEO, 3);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(30);
}).catch(fail).then(done);
});
it('removes segments', function(done) {
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.init(initObject, false).then(() => {
return mediaSourceEngine.setDuration(presentationDuration);
}).then(() => {
return appendInit(ContentType.VIDEO);
}).then(() => {
return Promise.all([
append(ContentType.VIDEO, 1),
append(ContentType.VIDEO, 2),
append(ContentType.VIDEO, 3)
]);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(30);
return remove(ContentType.VIDEO, 1);
}).then(() => {
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(10);
expect(buffered(ContentType.VIDEO, 10)).toBeCloseTo(20);
return remove(ContentType.VIDEO, 2);
}).then(() => {
expect(bufferStart(ContentType.VIDEO)).toBe(20);
expect(buffered(ContentType.VIDEO, 20)).toBeCloseTo(10);
return remove(ContentType.VIDEO, 3);
}).then(() => {
expect(bufferStart(ContentType.VIDEO)).toBe(null);
}).catch(fail).then(done);
});
it('extends the duration', function(done) {
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.init(initObject, false).then(() => {
return mediaSourceEngine.setDuration(0);
}).then(() => {
return appendInit(ContentType.VIDEO);
}).then(() => {
return mediaSourceEngine.setDuration(20);
}).then(() => {
expect(mediaSource.duration).toBeCloseTo(20);
return append(ContentType.VIDEO, 1);
}).then(() => {
expect(mediaSource.duration).toBeCloseTo(20);
return mediaSourceEngine.setDuration(35);
}).then(() => {
expect(mediaSource.duration).toBeCloseTo(35);
return Promise.all([
append(ContentType.VIDEO, 2),
append(ContentType.VIDEO, 3),
append(ContentType.VIDEO, 4)
]);
}).then(() => {
expect(mediaSource.duration).toBeCloseTo(40);
return mediaSourceEngine.setDuration(60);
}).then(() => {
expect(mediaSource.duration).toBeCloseTo(60);
}).catch(fail).then(done);
});
it('ends the stream, truncating the duration', function(done) {
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.init(initObject, false).then(() => {
return mediaSourceEngine.setDuration(presentationDuration);
}).then(() => {
return appendInit(ContentType.VIDEO);
}).then(() => {
return append(ContentType.VIDEO, 1);
}).then(() => {
return append(ContentType.VIDEO, 2);
}).then(() => {
return append(ContentType.VIDEO, 3);
}).then(() => {
return mediaSourceEngine.endOfStream();
}).then(() => {
expect(mediaSource.duration).toBeCloseTo(30);
}).catch(fail).then(done);
});
it('queues operations', function(done) {
let resolutionOrder = [];
let requests = [];
function checkOrder(p) {
let nextIndex = requests.length;
requests.push(p);
p.then(function() { resolutionOrder.push(nextIndex); });
}
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.init(initObject, false).then(() => {
checkOrder(mediaSourceEngine.setDuration(presentationDuration));
checkOrder(appendInit(ContentType.VIDEO));
checkOrder(append(ContentType.VIDEO, 1));
checkOrder(append(ContentType.VIDEO, 2));
checkOrder(append(ContentType.VIDEO, 3));
checkOrder(mediaSourceEngine.endOfStream());
return Promise.all(requests);
}).then(() => {
expect(resolutionOrder).toEqual([0, 1, 2, 3, 4, 5]);
}).catch(fail).then(done);
});
it('buffers MP4 audio', function(done) {
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.AUDIO] = getFakeStream(metadata.audio);
mediaSourceEngine.init(initObject, false).then(() => {
return mediaSourceEngine.setDuration(presentationDuration);
}).then(() => {
// NOTE: For some reason, this appendInit never resolves on my Windows VM.
// The test operates correctly on real hardware.
return appendInit(ContentType.AUDIO);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBe(0);
return append(ContentType.AUDIO, 1);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(10, 1);
return append(ContentType.AUDIO, 2);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(20, 1);
return append(ContentType.AUDIO, 3);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(30, 1);
}).catch(fail).then(done);
});
it('buffers MP4 video and audio', function(done) {
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.AUDIO] = getFakeStream(metadata.audio);
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.init(initObject, false).then(() => {
return mediaSourceEngine.setDuration(presentationDuration);
}).then(() => {
let audioStreaming = appendInit(ContentType.AUDIO).then(() => {
return append(ContentType.AUDIO, 1);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(10, 1);
return append(ContentType.AUDIO, 2);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(20, 1);
return append(ContentType.AUDIO, 3);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(30, 1);
return append(ContentType.AUDIO, 4);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(40, 1);
return append(ContentType.AUDIO, 5);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(50, 1);
return append(ContentType.AUDIO, 6);
}).then(() => {
expect(buffered(ContentType.AUDIO, 0)).toBeCloseTo(60, 1);
}).catch(fail);
let videoStreaming = appendInit(ContentType.VIDEO).then(() => {
return append(ContentType.VIDEO, 1);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(10);
return append(ContentType.VIDEO, 2);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(20);
return append(ContentType.VIDEO, 3);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(30);
return append(ContentType.VIDEO, 4);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(40);
return append(ContentType.VIDEO, 5);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(50);
return append(ContentType.VIDEO, 6);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(60);
}).catch(fail);
return Promise.all([audioStreaming, videoStreaming]);
}).then(() => {
return mediaSourceEngine.endOfStream();
}).then(() => {
expect(mediaSource.duration).toBeCloseTo(60, 1);
}).catch(fail).then(done);
});
it('trims content at the append window', function(done) {
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.init(initObject, false).then(() => {
return mediaSourceEngine.setDuration(presentationDuration);
}).then(() => {
return appendInit(ContentType.VIDEO);
}).then(() => {
return mediaSourceEngine.setStreamProperties(ContentType.VIDEO,
/* timestampOffset */ 0,
/* appendWindowStart */ 5,
/* appendWindowEnd */ 18);
}).then(() => {
expect(buffered(ContentType.VIDEO, 0)).toBe(0);
return append(ContentType.VIDEO, 1);
}).then(() => {
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(5, 1);
expect(buffered(ContentType.VIDEO, 5)).toBeCloseTo(5, 1);
return append(ContentType.VIDEO, 2);
}).then(() => {
expect(buffered(ContentType.VIDEO, 5)).toBeCloseTo(13, 1);
}).catch(fail).then(done);
});
it('does not remove when overlap is outside append window', function(done) {
// Create empty object first and initialize the fields through
// [] to allow field names to be expressions.
let initObject = {};
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.init(initObject, false).then(() => {
return mediaSourceEngine.setDuration(presentationDuration);
}).then(() => {
return appendInit(ContentType.VIDEO);
}).then(() => {
// Simulate period 1, with 20 seconds of content, no timestamp offset
return mediaSourceEngine.setStreamProperties(ContentType.VIDEO,
/* timestampOffset */ 0,
/* appendWindowStart */ 0,
/* appendWindowEnd */ 20);
}).then(() => {
return append(ContentType.VIDEO, 1);
}).then(() => {
return append(ContentType.VIDEO, 2);
}).then(() => {
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(0, 1);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(20, 1);
// Simulate period 2, with 20 seconds of content offset back by 5 seconds.
// The 5 seconds of overlap should be trimmed off, and we should still
// have a continuous stream with 35 seconds of content.
return mediaSourceEngine.setStreamProperties(ContentType.VIDEO,
/* timestampOffset */ 15,
/* appendWindowStart */ 20,
/* appendWindowEnd */ 35);
}).then(() => {
return append(ContentType.VIDEO, 1);
}).then(() => {
return append(ContentType.VIDEO, 2);
}).then(() => {
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(0, 1);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(35, 1);
}).catch(fail).then(done);
});
it('extracts CEA-708 captions', function(done) {
// Load TS files with CEA-708 captions.
metadata = shaka.test.TestScheme.DATA['cea-708_ts'];
generators = shaka.test.TestScheme.GENERATORS['cea-708_ts'];
// Create a mock text displayer, to intercept text cues.
let cues = [];
let mockTextDisplayer = /** @type {shakaExtern.TextDisplayer} */ ({
append: (newCues) => { cues = cues.concat(newCues); }
});
mediaSourceEngine.setTextDisplayer(mockTextDisplayer);
let initObject = {};
initObject[ContentType.VIDEO] = getFakeStream(metadata.video);
mediaSourceEngine.setUseEmbeddedText(true);
// Call with forceTransmuxTS = true, so that it will transmux even on
// platforms with native TS support.
mediaSourceEngine.init(initObject, /** forceTransmuxTS */ true).then(() => {
return append(ContentType.VIDEO, 0);
}).then(() => {
expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(1, 0);
expect(buffered(ContentType.VIDEO, 0)).toBeCloseTo(20, 1);
expect(cues.length).toBe(3);
}).catch(fail).then(done);
});
});