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 an option to support forced subtitles #1329

Merged
merged 2 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
dzianis-dashkevich marked this conversation as resolved.
Show resolved Hide resolved
// 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_: {}
dzianis-dashkevich marked this conversation as resolved.
Show resolved Hide resolved
},
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 @@ -3352,6 +3352,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