Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option for segment-relative VTT timings #4083

Merged
merged 3 commits into from Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions demo/common/message_ids.js
Expand Up @@ -230,6 +230,7 @@ shakaDemo.MessageIds = {
RESTRICTIONS_SECTION_HEADER: 'DEMO_RESTRICTIONS_SECTION_HEADER',
SAFE_SEEK_OFFSET: 'DEMO_SAFE_SEEK_OFFSET',
SAFE_SKIP_DISTANCE: 'DEMO_SAFE_SKIP_DISTANCE',
SEGMENT_RELATIVE_VTT_TIMING: 'DEMO_SEGMENT_RELATIVE_VTT_TIMING',
SESSION_ID: 'DEMO_SESSION_ID',
SHAKA_CONTROLS: 'DEMO_SHAKA_CONTROLS',
SLOW_HALF_LIFE: 'DEMO_SLOW_HALF_LIFE',
Expand Down
4 changes: 3 additions & 1 deletion demo/config.js
Expand Up @@ -237,7 +237,9 @@ shakaDemo.Config = class {
.addBoolInput_(MessageIds.DISABLE_TEXT,
'manifest.disableText')
.addBoolInput_(MessageIds.DISABLE_THUMBNAILS,
'manifest.disableThumbnails');
'manifest.disableThumbnails')
.addBoolInput_(MessageIds.SEGMENT_RELATIVE_VTT_TIMING,
'manifest.segmentRelativeVttTiming');

this.addRetrySection_('manifest', MessageIds.MANIFEST_RETRY_SECTION_HEADER);
}
Expand Down
1 change: 1 addition & 0 deletions demo/locales/en.json
Expand Up @@ -178,6 +178,7 @@
"DEMO_RESTRICTIONS_SECTION_HEADER": "Restrictions",
"DEMO_SAFE_SEEK_OFFSET": "Safe Seek Offset",
"DEMO_SAFE_SKIP_DISTANCE": "Safe Skip Distance",
"DEMO_SEGMENT_RELATIVE_VTT_TIMING": "Enable segment-relative VTT Timing",
"DEMO_SESSION_ID": "Session ID",
"DEMO_SAVE_BUTTON": "Save",
"DEMO_SHAKA": "Shaka",
Expand Down
4 changes: 4 additions & 0 deletions demo/locales/source.json
Expand Up @@ -719,6 +719,10 @@
"description": "A button to save a custom asset.",
"message": "Save"
},
"DEMO_SEGMENT_RELATIVE_VTT_TIMING": {
"description": "The name of a configuration value.",
"message": "Enable segment-relative VTT Timing"
},
"DEMO_SESSION_ID": {
"description": "The name of a configuration value.",
"message": "Session ID"
Expand Down
5 changes: 5 additions & 0 deletions externs/shaka/player.js
Expand Up @@ -782,6 +782,7 @@ shaka.extern.HlsManifestConfiguration;
* disableText: boolean,
* disableThumbnails: boolean,
* defaultPresentationDelay: number,
* segmentRelativeVttTiming: boolean,
* dash: shaka.extern.DashManifestConfiguration,
* hls: shaka.extern.HlsManifestConfiguration
* }}
Expand Down Expand Up @@ -813,6 +814,10 @@ shaka.extern.HlsManifestConfiguration;
* configured or set as 0.
* For HLS, the default value is 3 segments duration if not configured or
* set as 0.
* @property {boolean} segmentRelativeVttTiming
* Option to calculate VTT text timings relative to the segment start
* instead of relative to the period start (which is the default).
* Defaults to <code>false</code>.
* @property {shaka.extern.DashManifestConfiguration} dash
* Advanced parameters used by the DASH manifest parser.
* @property {shaka.extern.HlsManifestConfiguration} hls
Expand Down
6 changes: 5 additions & 1 deletion externs/shaka/text.js
Expand Up @@ -442,7 +442,8 @@ shaka.extern.TextParser = class {
* @typedef {{
* periodStart: number,
* segmentStart: number,
* segmentEnd: number
* segmentEnd: number,
* vttOffset: number
* }}
*
* @property {number} periodStart
Expand All @@ -451,6 +452,9 @@ shaka.extern.TextParser = class {
* The absolute start time of the segment in seconds.
* @property {number} segmentEnd
* The absolute end time of the segment in seconds.
* @property {number} vttOffset
* The start time relative to either segment or period start depending
* on <code>segmentRelativeVttTiming</code> configuration.
*
* @exportDoc
*/
Expand Down
13 changes: 12 additions & 1 deletion lib/media/media_source_engine.js
Expand Up @@ -66,6 +66,9 @@ shaka.media.MediaSourceEngine = class {
/** @private {shaka.text.TextEngine} */
this.textEngine_ = null;

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

const onMetadataNoOp = (metadata, timestampOffset, segmentEnd) => {};

/** @private {!function(!Array.<shaka.extern.ID3Metadata>,
Expand Down Expand Up @@ -367,7 +370,8 @@ shaka.media.MediaSourceEngine = class {
if (!this.textEngine_) {
this.textEngine_ = new shaka.text.TextEngine(this.textDisplayer_);
}
this.textEngine_.initParser(mimeType, sequenceMode);
this.textEngine_.initParser(mimeType, sequenceMode,
this.segmentRelativeVttTiming_);
}

/**
Expand Down Expand Up @@ -1113,6 +1117,13 @@ shaka.media.MediaSourceEngine = class {
}
}

/**
* @param {boolean} segmentRelativeVttTiming
*/
setSegmentRelativeVttTiming(segmentRelativeVttTiming) {
this.segmentRelativeVttTiming_ = segmentRelativeVttTiming;
}

/**
* Apply platform-specific transformations to this segment to work around
* issues in the platform.
Expand Down
7 changes: 7 additions & 0 deletions lib/player.js
Expand Up @@ -1571,6 +1571,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
(metadata, offset, endTime) => {
this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
});
const {segmentRelativeVttTiming} = this.config_.manifest;
mediaSourceEngine.setSegmentRelativeVttTiming(segmentRelativeVttTiming);

// Wait for media source engine to finish opening. This promise should
// NEVER be rejected as per the media source engine implementation.
Expand Down Expand Up @@ -3005,6 +3007,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}

if (this.mediaSourceEngine_) {
const {segmentRelativeVttTiming} = this.config_.manifest;
this.mediaSourceEngine_.setSegmentRelativeVttTiming(
segmentRelativeVttTiming);

const textDisplayerFactory = this.config_.textDisplayFactory;
if (this.lastTextFactory_ != textDisplayerFactory) {
const displayer =
Expand Down Expand Up @@ -4849,6 +4855,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
periodStart: 0,
segmentStart: 0,
segmentEnd: this.video_.duration,
vttOffset: 0,
};
const data = shaka.util.BufferUtils.toUint8(buffer);
const cues = obj.parseMedia(data, time);
Expand Down
11 changes: 10 additions & 1 deletion lib/text/text_engine.js
Expand Up @@ -31,6 +31,9 @@ shaka.text.TextEngine = class {
/** @private {shaka.extern.TextDisplayer} */
this.displayer_ = displayer;

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

/** @private {number} */
this.timestampOffset_ = 0;

Expand Down Expand Up @@ -130,8 +133,9 @@ shaka.text.TextEngine = class {
*
* @param {string} mimeType
* @param {boolean} sequenceMode
* @param {boolean} segmentRelativeVttTiming
*/
initParser(mimeType, sequenceMode) {
initParser(mimeType, sequenceMode, segmentRelativeVttTiming) {
// No parser for CEA, which is extracted from video and side-loaded
// into TextEngine and TextDisplayer.
if (mimeType == shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE ||
Expand All @@ -149,6 +153,7 @@ shaka.text.TextEngine = class {
shaka.log.alwaysWarn(
'Text parsers should have a "setSequenceMode" method!');
}
this.segmentRelativeVttTiming_ = segmentRelativeVttTiming;
}

/**
Expand All @@ -174,11 +179,15 @@ shaka.text.TextEngine = class {
return;
}

const vttOffset = this.segmentRelativeVttTiming_ ?
startTime : this.timestampOffset_;

/** @type {shaka.extern.TextParser.TimeContext} **/
const time = {
periodStart: this.timestampOffset_,
segmentStart: startTime,
segmentEnd: endTime,
vttOffset: vttOffset,
};

// Parse the buffer and add the new cues.
Expand Down
6 changes: 5 additions & 1 deletion lib/text/vtt_text_parser.js
Expand Up @@ -62,9 +62,13 @@ shaka.text.VttTextParser = class {
shaka.util.Error.Code.INVALID_TEXT_HEADER);
}

// Depending on "segmentRelativeVttTiming" configuration,
// "vttOffset" will correspond to either "periodStart" (default)
// or "segmentStart", for segmented VTT where timings are relative
// to the beginning of each segment.
// NOTE: "periodStart" is the timestamp offset applied via TextEngine.
// It is no longer closely tied to periods, but the name stuck around.
let offset = time.periodStart;
let offset = time.vttOffset;
elsassph marked this conversation as resolved.
Show resolved Hide resolved

// Do not honor the 'X-TIMESTAMP-MAP' value when in sequence mode.
// That is because it is used mainly (solely?) to account for the timestamp
Expand Down
1 change: 1 addition & 0 deletions lib/util/player_configuration.js
Expand Up @@ -86,6 +86,7 @@ shaka.util.PlayerConfiguration = class {
disableText: false,
disableThumbnails: false,
defaultPresentationDelay: 0,
segmentRelativeVttTiming: false,
dash: {
clockSyncUri: '',
ignoreDrmInfo: false,
Expand Down
2 changes: 2 additions & 0 deletions test/player_unit.js
Expand Up @@ -140,6 +140,8 @@ describe('Player', () => {
jasmine.createSpy('destroy').and.returnValue(Promise.resolve()),
setUseEmbeddedText: jasmine.createSpy('setUseEmbeddedText'),
getUseEmbeddedText: jasmine.createSpy('getUseEmbeddedText'),
setSegmentRelativeVttTiming:
jasmine.createSpy('setSegmentRelativeVttTiming'),
getTextDisplayer: () => textDisplayer,
ended: jasmine.createSpy('ended').and.returnValue(false),
};
Expand Down
4 changes: 4 additions & 0 deletions test/test/util/fake_media_source_engine.js
Expand Up @@ -132,6 +132,10 @@ shaka.test.FakeMediaSourceEngine = class {
this.getTextDisplayer =
jasmine.createSpy('getTextDisplayer')
.and.returnValue(new shaka.test.FakeTextDisplayer());

/** @type {!jasmine.Spy} */
this.setSegmentRelativeVttTiming =
jasmine.createSpy('setSegmentRelativeVttTiming').and.stub();
}

/** @override */
Expand Down
4 changes: 2 additions & 2 deletions test/text/cue_integration.js
Expand Up @@ -23,7 +23,7 @@ describe('Cue', () => {
'WEBVTT\n\n' +
'00:00:20.000 --> 00:00:40.000\n' +
'Test',
{periodStart: 7, segmentStart: 10, segmentEnd: 60});
{periodStart: 7, segmentStart: 10, segmentEnd: 60, vttOffset: 7});
expect(cues.length).toBe(1);
expect(cues[0].startTime).toBe(27);
expect(cues[0].endTime).toBe(47);
Expand All @@ -38,7 +38,7 @@ describe('Cue', () => {
'ID1\n' +
'00:00:20.000 --> 00:00:40.000 align:middle size:56% vertical:lr\n' +
'Test',
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
expect(cues.length).toBe(1);
});

Expand Down
13 changes: 6 additions & 7 deletions test/text/lrc_text_parser_unit.js
Expand Up @@ -12,7 +12,7 @@ describe('LrcTextParser', () => {
it('supports no cues', () => {
verifyHelper([],
'',
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
});

it('handles a blank line at the start of the file', () => {
Expand All @@ -22,7 +22,7 @@ describe('LrcTextParser', () => {
],
'\n\n' +
'[00:00.00]Test',
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
});

it('handles a blank line at the end of the file', () => {
Expand All @@ -32,7 +32,7 @@ describe('LrcTextParser', () => {
],
'[00:00.00]Test' +
'\n\n',
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
});

it('handles no blank line at the end of the file', () => {
Expand All @@ -41,8 +41,7 @@ describe('LrcTextParser', () => {
{startTime: 0, endTime: 2, payload: 'Test'},
],
'[00:00.00]Test',
{periodStart: 0, segmentStart: 0, segmentEnd: 0,
});
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
});

it('supports multiple cues', () => {
Expand All @@ -55,7 +54,7 @@ describe('LrcTextParser', () => {
'[00:00.00]Test\n' +
'[00:10.00]Test2\n' +
'[00:20.00]Test3',
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
});

it('supports different time formats', () => {
Expand All @@ -74,7 +73,7 @@ describe('LrcTextParser', () => {
'[00:30,1]Test4\n' +
'[00:40,001]Test5\n' +
'[00:50,02]Test6',
{periodStart: 0, segmentStart: 0, segmentEnd: 0});
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
});

/**
Expand Down
10 changes: 6 additions & 4 deletions test/text/mp4_ttml_parser_unit.js
Expand Up @@ -47,7 +47,7 @@ describe('Mp4TtmlParser', () => {
it('handles media segments with multiple mdats', () => {
const parser = new shaka.text.Mp4TtmlParser();
parser.parseInit(ttmlInitSegment);
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
const ret = parser.parseMedia(ttmlSegmentMultipleMDAT, time);
// Bodies.
expect(ret.length).toBe(2);
Expand All @@ -60,8 +60,10 @@ describe('Mp4TtmlParser', () => {
});

it('accounts for offset', () => {
const time1 = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
const time2 = {periodStart: 7, segmentStart: 0, segmentEnd: 0};
const time1 =
{periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
const time2 =
{periodStart: 7, segmentStart: 0, segmentEnd: 0, vttOffset: 7};

const parser = new shaka.text.Mp4TtmlParser();
parser.parseInit(ttmlInitSegment);
Expand Down Expand Up @@ -165,7 +167,7 @@ describe('Mp4TtmlParser', () => {
];
const parser = new shaka.text.Mp4TtmlParser();
parser.parseInit(ttmlInitSegment);
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
const result = parser.parseMedia(ttmlSegment, time);
shaka.test.TtmlUtils.verifyHelper(
cues, result, {startTime: 23, endTime: 53.5});
Expand Down
11 changes: 6 additions & 5 deletions test/text/mp4_vtt_parser_unit.js
Expand Up @@ -71,7 +71,7 @@ describe('Mp4VttParser', () => {

const parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
const result = parser.parseMedia(vttSegment, time);
verifyHelper(cues, result);
});
Expand Down Expand Up @@ -99,7 +99,7 @@ describe('Mp4VttParser', () => {

const parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
const result = parser.parseMedia(vttSegmentMultiPayload, time);
verifyHelper(cues, result);
});
Expand Down Expand Up @@ -127,7 +127,7 @@ describe('Mp4VttParser', () => {

const parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
const result = parser.parseMedia(vttSegSettings, time);
verifyHelper(cues, result);
});
Expand All @@ -149,7 +149,7 @@ describe('Mp4VttParser', () => {

const parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
const time = {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0};
const result = parser.parseMedia(vttSegNoDuration, time);
verifyHelper(cues, result);
});
Expand All @@ -171,7 +171,8 @@ describe('Mp4VttParser', () => {

const parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
const time = {periodStart: 10, segmentStart: 0, segmentEnd: 0};
const time =
{periodStart: 10, segmentStart: 0, segmentEnd: 0, vttOffset: 10};
const result = parser.parseMedia(vttSegment, time);
verifyHelper(cues, result);
});
Expand Down