diff --git a/README.md b/README.md
index d8185e328..40a2ead35 100644
--- a/README.md
+++ b/README.md
@@ -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)
@@ -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
diff --git a/index.html b/index.html
index 3849c4023..04eae9142 100644
--- a/index.html
+++ b/index.html
@@ -171,6 +171,11 @@
Mirror sources from player.src (reloads player, uses EXPERIMENTAL sourceset option)
+
+
+ Use Forced Subtitles (reloads player)
+
+
Preload (reloads player)
diff --git a/scripts/index.js b/scripts/index.js
index 8ce519f89..4cf76f31a 100644
--- a/scripts/index.js
+++ b/scripts/index.js
@@ -451,7 +451,8 @@
'dts-offset',
'override-native',
'preload',
- 'mirror-source'
+ 'mirror-source',
+ 'forced-subtitles'
].forEach(function(name) {
stateEls[name] = document.getElementById(name);
});
@@ -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();
@@ -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'])
}
}
});
diff --git a/src/media-groups.js b/src/media-groups.js
index 48cfae0fe..66b7011a2 100644
--- a/src/media-groups.js
+++ b/src/media-groups.js
@@ -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,
diff --git a/src/videojs-http-streaming.js b/src/videojs-http-streaming.js
index 9486f96e1..e7833d285 100644
--- a/src/videojs-http-streaming.js
+++ b/src/videojs-http-streaming.js
@@ -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 || [];
@@ -645,6 +646,7 @@ class VhsHandler extends Component {
'bufferBasedABR',
'liveRangeSafeTimeDelta',
'llhls',
+ 'useForcedSubtitles',
'useNetworkInformationApi',
'useDtsForTimestampOffset',
'exactManifestTimings',
diff --git a/test/media-groups.test.js b/test/media-groups.test.js
index 64d872eb8..ce7c2be50 100644
--- a/test/media-groups.test.js
+++ b/test/media-groups.test.js
@@ -1076,7 +1076,9 @@ QUnit.module('MediaGroups', function() {
this.settings = {
mode: 'html5',
mainPlaylistLoader: {main: this.main},
- vhs: {},
+ vhs: {
+ options_: {}
+ },
tech: {
options_: {},
addRemoteTextTrack(track) {
diff --git a/test/playlist-controller.test.js b/test/playlist-controller.test.js
index 33c45e974..b70a0828d 100644
--- a/test/playlist-controller.test.js
+++ b/test/playlist-controller.test.js
@@ -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();