Skip to content

Commit

Permalink
feat: add an option to support forced subtitles (#1329)
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Waldron <[email protected]>
  • Loading branch information
amtins and adrums86 authored Apr 3, 2023
1 parent c90863c commit 6bd98d0
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 5 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Video.js Compatibility: 7.x, 8.x
- [liveRangeSafeTimeDelta](#liverangesafetimedelta)
- [useNetworkInformationApi](#usenetworkinformationapi)
- [useDtsForTimestampOffset](#usedtsfortimestampoffset)
- [useForcedSubtitles](#useforcedsubtitles)
- [captionServices](#captionservices)
- [Format](#format)
- [Example](#example)
Expand Down Expand Up @@ -461,6 +462,14 @@ This option defaults to `false`.
* Default: `false`
* Use [Decode Timestamp](https://www.w3.org/TR/media-source/#decode-timestamp) instead of [Presentation Timestamp](https://www.w3.org/TR/media-source/#presentation-timestamp) for [timestampOffset](https://www.w3.org/TR/media-source/#dom-sourcebuffer-timestampoffset) calculation. This option was introduced to align with DTS-based browsers. This option affects only transmuxed data (eg: transport stream). For more info please check the following [issue](https://github.com/videojs/http-streaming/issues/1247).

##### useForcedSubtitles
* Type: `boolean`
* Default: `false`
* can be used as a source option
* can be used as an initialization option

If true, this option allows the player to display forced subtitles. When available, forced subtitles allow to translate foreign language dialogues or images containing foreign language characters.

##### captionServices
* Type: `object`
* Default: undefined
Expand Down
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@
<label class="form-check-label" for="mirror-source">Mirror sources from player.src (reloads player, uses EXPERIMENTAL sourceset option)</label>
</div>

<div class="form-check">
<input id="forced-subtitles" type="checkbox" class="form-check-input">
<label class="form-check-label" for="forced-subtitles">Use Forced Subtitles (reloads player)</label>
</div>

<div class="input-group">
<span class="input-group-text"><label for=preload>Preload (reloads player)</label></span>
<select id=preload class="form-select">
Expand Down
9 changes: 6 additions & 3 deletions scripts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,8 @@
'dts-offset',
'override-native',
'preload',
'mirror-source'
'mirror-source',
'forced-subtitles'
].forEach(function(name) {
stateEls[name] = document.getElementById(name);
});
Expand Down Expand Up @@ -503,7 +504,8 @@
'pixel-diff-selector',
'network-info',
'dts-offset',
'exact-manifest-timings'
'exact-manifest-timings',
'forced-subtitles'
].forEach(function(name) {
stateEls[name].addEventListener('change', function(event) {
saveState();
Expand Down Expand Up @@ -585,7 +587,8 @@
exactManifestTimings: getInputValue(stateEls['exact-manifest-timings']),
leastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector']),
useNetworkInformationApi: getInputValue(stateEls['network-info']),
useDtsForTimestampOffset: getInputValue(stateEls['dts-offset'])
useDtsForTimestampOffset: getInputValue(stateEls['dts-offset']),
useForcedSubtitles: getInputValue(stateEls['forced-subtitles'])
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/media-groups.js
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ export const initialize = {
}

for (const variantLabel in mediaGroups[type][groupId]) {
if (mediaGroups[type][groupId][variantLabel].forced) {
if (!vhs.options_.useForcedSubtitles && mediaGroups[type][groupId][variantLabel].forced) {
// Subtitle playlists with the forced attribute are not selectable in Safari.
// According to Apple's HLS Authoring Specification:
// If content has forced subtitles and regular subtitles in a given language,
Expand Down
2 changes: 2 additions & 0 deletions src/videojs-http-streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ class VhsHandler extends Component {
typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ?
this.source_.useBandwidthFromLocalStorage :
this.options_.useBandwidthFromLocalStorage || false;
this.options_.useForcedSubtitles = this.options_.useForcedSubtitles || false;
this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
this.options_.customTagParsers = this.options_.customTagParsers || [];
Expand Down Expand Up @@ -645,6 +646,7 @@ class VhsHandler extends Component {
'bufferBasedABR',
'liveRangeSafeTimeDelta',
'llhls',
'useForcedSubtitles',
'useNetworkInformationApi',
'useDtsForTimestampOffset',
'exactManifestTimings',
Expand Down
4 changes: 3 additions & 1 deletion test/media-groups.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,9 @@ QUnit.module('MediaGroups', function() {
this.settings = {
mode: 'html5',
mainPlaylistLoader: {main: this.main},
vhs: {},
vhs: {
options_: {}
},
tech: {
options_: {},
addRemoteTextTrack(track) {
Expand Down
69 changes: 69 additions & 0 deletions test/playlist-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3232,6 +3232,75 @@ QUnit.test('adds subtitle tracks when a media playlist is loaded', function(asse
assert.equal(this.player.textTracks().length, 0, 'text tracks cleaned');
});

QUnit.test('adds subtitle tracks including forced subtitles when a media playlist is loaded', function(assert) {
let vhsWebvttEvents = 0;

this.requests.length = 0;
this.player.dispose();
this.player = createPlayer({
html5: {
vhs: { useForcedSubtitles: true }
}
});
this.player.src({
src: 'manifest/main-subtitles.m3u8',
type: 'application/vnd.apple.mpegurl'
});

this.clock.tick(1);

this.player.tech_.on('usage', (event) => {
if (event.name === 'vhs-webvtt') {
vhsWebvttEvents++;
}
});

const playlistController = this.player.tech_.vhs.playlistController_;

assert.equal(vhsWebvttEvents, 0, 'there is no webvtt detected');
assert.equal(this.player.textTracks().length, 1, 'one text track to start');
assert.equal(
this.player.textTracks()[0].label,
'segment-metadata',
'only segment-metadata text track'
);

// main, contains media groups for subtitles
this.standardXHRResponse(this.requests.shift());

// we wait for loadedmetadata before setting subtitle tracks, so we need to wait for a
// media playlist
assert.equal(this.player.textTracks().length, 1, 'only one text track after main');

// media
this.standardXHRResponse(this.requests.shift());

const main = playlistController.mainPlaylistLoader_.main;
const subs = main.mediaGroups.SUBTITLES.subs;
const subsArr = Object.keys(subs).map(key => subs[key]);

assert.equal(subsArr.length, 4, 'got 4 subtitles');
assert.equal(subsArr.filter(sub => sub.forced === false).length, 2, '2 forced');
assert.equal(subsArr.filter(sub => sub.forced === true).length, 2, '2 non-forced');

const textTracks = this.player.textTracks();

assert.equal(textTracks.length, 5, 'forced text tracks were added');
assert.equal(textTracks[1].mode, 'disabled', 'track starts disabled');
assert.equal(textTracks[2].mode, 'disabled', 'track starts disabled');
assert.equal(vhsWebvttEvents, 1, 'there is webvtt detected in the rendition');

// change source to make sure tracks are cleaned up
this.player.src({
src: 'http://example.com/media.mp4',
type: 'video/mp4'
});

this.clock.tick(1);

assert.equal(this.player.textTracks().length, 0, 'text tracks cleaned');
});

QUnit.test('switches off subtitles on subtitle errors', function(assert) {
this.requests.length = 0;
this.player.dispose();
Expand Down

0 comments on commit 6bd98d0

Please sign in to comment.