Skip to content

Commit

Permalink
feat(text): Add MediaSource.modifyCueCallback (#6167)
Browse files Browse the repository at this point in the history
This callback gives developers a chance to modify cues after they are
parsed but before they are appended.
  • Loading branch information
theodab committed Jan 26, 2024
1 parent 658386b commit bd944d1
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 3 deletions.
7 changes: 6 additions & 1 deletion externs/shaka/player.js
Expand Up @@ -1328,7 +1328,8 @@ shaka.extern.StreamingConfiguration;
* codecSwitchingStrategy: shaka.config.CodecSwitchingStrategy,
* sourceBufferExtraFeatures: string,
* forceTransmux: boolean,
* insertFakeEncryptionInInit: boolean
* insertFakeEncryptionInInit: boolean,
* modifyCueCallback: shaka.extern.TextParser.ModifyCueCallback
* }}
*
* @description
Expand Down Expand Up @@ -1358,6 +1359,10 @@ shaka.extern.StreamingConfiguration;
* time.
* <br><br>
* This value defaults to <code>true</code>.
* @property {shaka.extern.TextParser.ModifyCueCallback} modifyCueCallback
* A callback called for each cue after it is parsed, but right before it
* is appended to the presentation.
* Gives a chance for client-side editing of cue text, cue timing, etc.
* @exportDoc
*/
shaka.extern.MediaSourceConfiguration;
Expand Down
10 changes: 10 additions & 0 deletions externs/shaka/text.js
Expand Up @@ -85,6 +85,16 @@ shaka.extern.TextParser = class {
shaka.extern.TextParser.TimeContext;


/**
* A callback used for editing cues before appending.
* Provides the cue, and the URI of the captions file the cue was parsed from.
* You can edit the cue object passed in.
* @typedef {function(!shaka.text.Cue, ?string)}
* @exportDoc
*/
shaka.extern.TextParser.ModifyCueCallback;


/**
* @typedef {function():!shaka.extern.TextParser}
* @exportDoc
Expand Down
6 changes: 6 additions & 0 deletions lib/media/media_source_engine.js
Expand Up @@ -550,6 +550,9 @@ shaka.media.MediaSourceEngine = class {
*/
configure(config) {
this.config_ = config;
if (this.textEngine_) {
this.textEngine_.setModifyCueCallback(config.modifyCueCallback);
}
}

/**
Expand All @@ -571,6 +574,9 @@ shaka.media.MediaSourceEngine = class {
reinitText(mimeType, sequenceMode, external) {
if (!this.textEngine_) {
this.textEngine_ = new shaka.text.TextEngine(this.textDisplayer_);
if (this.textEngine_) {
this.textEngine_.setModifyCueCallback(this.config_.modifyCueCallback);
}
}
this.textEngine_.initParser(mimeType, sequenceMode,
external || this.segmentRelativeVttTiming_, this.manifestType_);
Expand Down
11 changes: 11 additions & 0 deletions lib/text/text_engine.js
Expand Up @@ -51,6 +51,9 @@ shaka.text.TextEngine = class {
/** @private {string} */
this.selectedClosedCaptionId_ = '';

/** @private {shaka.extern.TextParser.ModifyCueCallback} */
this.modifyCueCallback_ = (cue, uri) => {};

/**
* The closed captions map stores the CEA closed captions by closed captions
* id and start and end time.
Expand Down Expand Up @@ -162,6 +165,11 @@ shaka.text.TextEngine = class {
this.segmentRelativeVttTiming_ = segmentRelativeVttTiming;
}

/** @param {shaka.extern.TextParser.ModifyCueCallback} modifyCueCallback */
setModifyCueCallback(modifyCueCallback) {
this.modifyCueCallback_ = modifyCueCallback;
}

/**
* @param {BufferSource} buffer
* @param {?number} startTime relative to the start of the presentation
Expand Down Expand Up @@ -200,6 +208,9 @@ shaka.text.TextEngine = class {
// Parse the buffer and add the new cues.
const allCues = this.parser_.parseMedia(
shaka.util.BufferUtils.toUint8(buffer), time, uri);
for (const cue of allCues) {
this.modifyCueCallback_(cue, uri || null);
}
const cuesToAppend = allCues.filter((cue) => {
return cue.startTime >= this.appendWindowStart_ &&
cue.startTime < this.appendWindowEnd_;
Expand Down
5 changes: 5 additions & 0 deletions lib/util/player_configuration.js
Expand Up @@ -340,6 +340,11 @@ shaka.util.PlayerConfiguration = class {
sourceBufferExtraFeatures: '',
forceTransmux: false,
insertFakeEncryptionInInit: true,
modifyCueCallback: (cue, uri) => {
return shaka.util.ConfigUtils.referenceParametersAndReturn(
[cue, uri],
undefined);
},
};

const ads = {
Expand Down
3 changes: 2 additions & 1 deletion test/demo/demo_unit.js
Expand Up @@ -81,7 +81,8 @@ describe('Demo', () => {
.add('drm.keySystemsMapping')
.add('manifest.raiseFatalErrorOnManifestUpdateRequestFailure')
.add('drm.persistentSessionOnlinePlayback')
.add('drm.persistentSessionsMetadata');
.add('drm.persistentSessionsMetadata')
.add('mediaSource.modifyCueCallback');

/**
* @param {!Object} section
Expand Down
2 changes: 1 addition & 1 deletion test/media/media_source_engine_unit.js
Expand Up @@ -1496,7 +1496,7 @@ describe('MediaSourceEngine', () => {
mockTextEngine = jasmine.createSpyObj('TextEngine', [
'initParser', 'destroy', 'appendBuffer', 'remove', 'setTimestampOffset',
'setAppendWindow', 'bufferStart', 'bufferEnd', 'bufferedAheadOf',
'storeAndAppendClosedCaptions',
'storeAndAppendClosedCaptions', 'setModifyCueCallback',
]);

const resolve = () => Promise.resolve();
Expand Down
12 changes: 12 additions & 0 deletions test/text/text_engine_unit.js
Expand Up @@ -135,6 +135,18 @@ describe('TextEngine', () => {
textEngine.destroy();
await p;
});

it('calls modifyCueCallback', async () => {
const cue1 = createFakeCue(0, 1);
const cue2 = createFakeCue(1, 2);
const modifyCueCallback = jasmine.createSpy('modifyCueCallback');
textEngine.setModifyCueCallback(
shaka.test.Util.spyFunc(modifyCueCallback));
mockParseMedia.and.returnValue([cue1, cue2]);
await textEngine.appendBuffer(dummyData, 0, 3, 'uri');
expect(modifyCueCallback).toHaveBeenCalledWith(cue1, 'uri');
expect(modifyCueCallback).toHaveBeenCalledWith(cue2, 'uri');
});
});

describe('storeAndAppendClosedCaptions', () => {
Expand Down

0 comments on commit bd944d1

Please sign in to comment.