Skip to content

Commit

Permalink
Add test cases for CEA closed captions
Browse files Browse the repository at this point in the history
Issue #1404

Change-Id: I35e679ef985ac5cef1f8d8673192845cae29b114
  • Loading branch information
michellezhuogg committed Nov 8, 2018
1 parent f2b9e3f commit b9a806d
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 9 deletions.
21 changes: 20 additions & 1 deletion lib/media/media_source_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ shaka.media.MediaSourceEngine.prototype.appendBuffer =
return this.enqueueOperation_(contentType,
this.append_.bind(this, contentType, transmuxedData.data.buffer));
}.bind(this));
} else if (hasClosedCaptions) {
} else if (hasClosedCaptions && window.muxjs) {
if (!this.textEngine_) {
this.reinitText('text/vtt');
}
Expand Down Expand Up @@ -1095,3 +1095,22 @@ shaka.media.MediaSourceEngine.prototype.popFromQueue_ = function(contentType) {
}
}
};


/**
* Set the closed caption parser. Used only for unit tests.
* @param {muxjs.mp4.CaptionParser} captionParser
*/
shaka.media.MediaSourceEngine.prototype.setCaptionParser = function(
captionParser) {
this.captionParser_ = captionParser;
};


/**
* Get the closed caption parser. Used only for unit tests.
* @return {muxjs.mp4.CaptionParser} captionParser
*/
shaka.media.MediaSourceEngine.prototype.getCaptionParser = function() {
return this.captionParser_;
};
12 changes: 10 additions & 2 deletions lib/text/text_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,16 @@ shaka.text.TextEngine.unregisterParser = function(mimeType) {
* @return {boolean}
*/
shaka.text.TextEngine.isTypeSupported = function(mimeType) {
return !!shaka.text.TextEngine.parserMap_[mimeType] ||
mimeType == shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE;
if (shaka.text.TextEngine.parserMap_[mimeType]) {
// An actual parser is available.
return true;
}
if (window.muxjs &&
mimeType == shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE) {
// Will be handled by mux.js.
return true;
}
return false;
};


Expand Down
4 changes: 0 additions & 4 deletions test/dash/dash_parser_manifest_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ describe('DashParser Manifest', function() {
.build());
});


it('skips any periods after one without duration', async () => {
let periodContents = [
' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
Expand Down Expand Up @@ -359,7 +358,6 @@ describe('DashParser Manifest', function() {
expect(stream3.closedCaptions).toEqual(expectedClosedCaptions);
});


it('correctly parses closed captions without channel numbers', async () => {
const source = [
'<MPD minBufferTime="PT75S">',
Expand All @@ -386,7 +384,6 @@ describe('DashParser Manifest', function() {
expect(stream.closedCaptions).toEqual(expectedClosedCaptions);
});


it('correctly parses closed captions with no channel and language info',
async () => {
const source = [
Expand All @@ -410,7 +407,6 @@ describe('DashParser Manifest', function() {
expect(stream.closedCaptions).toEqual(expectedClosedCaptions);
});


it('correctly parses UTF-8', async () => {
let source = [
'<MPD>',
Expand Down
62 changes: 61 additions & 1 deletion test/media/media_source_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ describe('MediaSourceEngine', function() {
let video;
let mockMediaSource;
let mockTextEngine;
/** @type {shaka.test.FakeCaptionParser} */
let mockCaptionParser;
/** @type {!jasmine.Spy} */
let createMediaSourceSpy;

Expand Down Expand Up @@ -143,6 +145,7 @@ describe('MediaSourceEngine', function() {
};
video = /** @type {HTMLMediaElement} */(mockVideo);
mediaSourceEngine = new shaka.media.MediaSourceEngine(video);
mockCaptionParser = new shaka.test.FakeCaptionParser();
});

afterEach(function() {
Expand Down Expand Up @@ -524,6 +527,63 @@ describe('MediaSourceEngine', function() {
videoSourceBuffer.updateend();
});
});

it('appends parsed closed captions from CaptionParser', async () => {
const initObject = new Map();
initObject.set(ContentType.VIDEO, fakeVideoStream);
await mediaSourceEngine.init(initObject, false);

mediaSourceEngine.setUseEmbeddedText(false);
// Initialize the closed caption parser.
const appendInit = mediaSourceEngine.appendBuffer(
ContentType.VIDEO, buffer, null, null, true);
// In MediaSourceEngine, appendBuffer() is async and Promise-based, but
// at the browser level, it's event-based.
// MediaSourceEngine waits for the 'updateend' event from the
// SourceBuffer, and uses that to resolve the appendBuffer Promise.
// Here, we must trigger the event on the fake/mock SourceBuffer before
// waiting on the appendBuffer Promise.
videoSourceBuffer.updateend();
await appendInit;

mediaSourceEngine.setCaptionParser(mockCaptionParser);
expect(mockTextEngine.storeAndAppendClosedCaptions).not.
toHaveBeenCalled();
// Parse and append the closed captions embedded in video stream.
const appendVideo = mediaSourceEngine.appendBuffer(
ContentType.VIDEO, buffer, 0, Infinity, true);
videoSourceBuffer.updateend();
await appendVideo;

expect(mockTextEngine.storeAndAppendClosedCaptions).toHaveBeenCalled();
});

it('appends closed caption data only when mux.js is available',
async () => {
const originalMuxjs = window.muxjs;
try {
window['muxjs'] = null;
const initObject = new Map();
initObject.set(ContentType.VIDEO, fakeVideoStream);
await mediaSourceEngine.init(initObject, false);

mediaSourceEngine.setUseEmbeddedText(false);
const appendBuffer = mediaSourceEngine.appendBuffer(
ContentType.VIDEO, buffer, null, null, true);
// In MediaSourceEngine, appendBuffer() is async and Promise-based, but
// at the browser level, it's event-based.
// MediaSourceEngine waits for the 'updateend' event from the
// SourceBuffer, and uses that to resolve the appendBuffer Promise.
// Here, we must trigger the event on the fake/mock SourceBuffer before
// waiting on the appendBuffer Promise.
videoSourceBuffer.updateend();
await appendBuffer;

expect(mediaSourceEngine.getCaptionParser()).toBeNull();
} finally {
window['muxjs'] = originalMuxjs;
}
});
});

describe('remove', function() {
Expand Down Expand Up @@ -1101,7 +1161,7 @@ describe('MediaSourceEngine', function() {
mockTextEngine = jasmine.createSpyObj('TextEngine', [
'initParser', 'destroy', 'appendBuffer', 'remove', 'setTimestampOffset',
'setAppendWindow', 'bufferStart', 'bufferEnd', 'bufferedAheadOf',
'setDisplayer', 'appendCues',
'setDisplayer', 'appendCues', 'storeAndAppendClosedCaptions',
]);

let resolve = Promise.resolve.bind(Promise);
Expand Down
22 changes: 21 additions & 1 deletion test/media/streaming_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,6 @@ describe('StreamingEngine', function() {

streamingEngine.init();

// TODO: add another test case for loading the same text stream.
runTest(function() {
if (playheadTime == 20) {
mediaSourceEngine.clear.calls.reset();
Expand All @@ -642,6 +641,27 @@ describe('StreamingEngine', function() {
}
});
});

it('only clears MediaSourceEngine when it loads a different text stream',
function() {
setupVod();
mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();
playhead.getTime.and.returnValue(0);
onStartupComplete.and.callFake(function() { setupFakeGetTime(0); });
onChooseStreams.and.callFake(onChooseStreamsWithUnloadedText);

streamingEngine.init();

runTest(function() {
if (playheadTime == 20) {
mediaSourceEngine.clear.calls.reset();
mediaSourceEngine.init.calls.reset();
streamingEngine.loadNewTextStream(textStream1);
expect(mediaSourceEngine.clear).not.toHaveBeenCalledWith('text');
}
});
});
});

describe('unloadTextStream', function() {
Expand Down
23 changes: 23 additions & 0 deletions test/test/util/simple_fakes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

goog.provide('shaka.test.FakeAbrManager');
goog.provide('shaka.test.FakeCaptionParser');
goog.provide('shaka.test.FakeManifestParser');
goog.provide('shaka.test.FakePlayhead');
goog.provide('shaka.test.FakePlayheadObserver');
Expand Down Expand Up @@ -547,6 +548,28 @@ shaka.test.FakeTextTrack.prototype.addCue;
shaka.test.FakeTextTrack.prototype.removeCue;


/**
* Creates a mux.js closed caption parser.
*
* @constructor
* @struct
* @extends {muxjs.mp4.CaptionParser}
* @return {muxjs.mp4.CaptionParser}
*/
shaka.test.FakeCaptionParser = function() {
let parsedData = {
captionStreams: {},
captions: ['foo', 'bar'],
};
let captionParser = {
init: jasmine.createSpy('init'),
parse: jasmine.createSpy('parse'),
clearParsedCaptions: jasmine.createSpy('clearParsedCaptions'),
};
captionParser.parse.and.returnValue(parsedData);
return captionParser;
};

/**
* Creates a text track.
*
Expand Down
14 changes: 14 additions & 0 deletions test/text/text_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ describe('TextEngine', function() {
TextEngine.unregisterParser(dummyMimeType);
expect(TextEngine.isTypeSupported(dummyMimeType)).toBe(false);
});

it('reports support when it\'s closed captions and muxjs is available',
function() {
const closedCaptionsType =
shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE;
const originalMuxjs = window.muxjs;
expect(TextEngine.isTypeSupported(closedCaptionsType)).toBe(true);
try {
window['muxjs'] = null;
expect(TextEngine.isTypeSupported(closedCaptionsType)).toBe(false);
} finally {
window['muxjs'] = originalMuxjs;
}
});
});

describe('appendBuffer', function() {
Expand Down

0 comments on commit b9a806d

Please sign in to comment.