Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
41 changes: 38 additions & 3 deletions src/HTMLVideo/HTMLVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,19 @@ function HTMLVideo(options) {
return [];
}

if (hls !== null && Array.isArray(hls.subtitleTracks)) {
return hls.subtitleTracks.map(function(track, index) {
return Object.freeze({
id: 'EMBEDDED_' + String(index),
lang: typeof track.lang === 'string' && track.lang.length > 0 ? track.lang : '',
label: typeof track.name === 'string' && track.name.length > 0 ? track.name : null,
origin: 'EMBEDDED',
embedded: true,
_hlsSubtitlePlaylistURL: typeof track.url === 'string' ? track.url : null
});
});
}

return Array.from(videoElement.textTracks)
.map(function(track, index) {
return Object.freeze({
Expand All @@ -211,6 +224,10 @@ function HTMLVideo(options) {
return null;
}

if (hls !== null) {
return hls.subtitleTrack >= 0 ? 'EMBEDDED_' + String(hls.subtitleTrack) : null;
}

return Array.from(videoElement.textTracks)
.reduce(function(result, track, index) {
if (result === null && track.mode === 'showing') {
Expand Down Expand Up @@ -422,10 +439,17 @@ function HTMLVideo(options) {
}
case 'selectedSubtitlesTrackId': {
if (stream !== null) {
Array.from(videoElement.textTracks)
.forEach(function(track, index) {
track.mode = 'EMBEDDED_' + String(index) === propValue ? 'showing' : 'disabled';
if (hls !== null) {
var selectedIndex = getProp('subtitlesTracks').findIndex(function(track) {
return track.id === propValue;
});
hls.subtitleTrack = selectedIndex;
} else {
Array.from(videoElement.textTracks)
.forEach(function(track, index) {
track.mode = 'EMBEDDED_' + String(index) === propValue ? 'showing' : 'disabled';
});
}
var selecterdSubtitlesTrack = getProp('subtitlesTracks')
.find(function(track) {
return track.id === propValue;
Expand Down Expand Up @@ -619,6 +643,17 @@ function HTMLVideo(options) {
onPropChanged('audioTracks');
onPropChanged('selectedAudioTrackId');
});
hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, function() {
onPropChanged('subtitlesTracks');
onPropChanged('selectedSubtitlesTrackId');
});
hls.on(Hls.Events.SUBTITLE_TRACKS_CLEARED, function() {
onPropChanged('subtitlesTracks');
onPropChanged('selectedSubtitlesTrackId');
});
hls.on(Hls.Events.SUBTITLE_TRACK_SWITCH, function() {
onPropChanged('selectedSubtitlesTrackId');
});
hls.on(Hls.Events.MANIFEST_LOADING, function() {
hls.subtitleTrack = -1;
});
Expand Down
162 changes: 162 additions & 0 deletions src/withHTMLSubtitles/withHTMLSubtitles.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ function withHTMLSubtitles(Video) {
var assRenderer = createASSRenderer({
containerElement: containerElement,
onError: function(error, track) {
if (track && track.id === selectedEmbeddedASSTrackId) {
fallbackFromEmbeddedASS(error, track);
return;
}
fallbackFromASS(error, track, selectedSubtitleText)
.catch(function(fallbackError) {
reportSubtitleError(fallbackError, track);
Expand Down Expand Up @@ -143,6 +147,10 @@ function withHTMLSubtitles(Video) {
var usingASSRenderer = false;
var selectedSubtitleText = null;
var selectionRequestId = 0;
var embeddedASSSources = [];
var videoSubtitleTracks = [];
var selectedEmbeddedASSTrackId = null;
var embeddedSubtitlesOpacity = 1;
var nativeAssSubtitlesSupported = options.shellTransport &&
options.shellTransport.capabilities &&
options.shellTransport.capabilities.nativeAssSubtitles === true;
Expand Down Expand Up @@ -296,6 +304,7 @@ function withHTMLSubtitles(Video) {
break;
}
case 'subtitlesTracks': {
videoSubtitleTracks = Array.isArray(propValue) ? propValue : [];
updateNativeASSTrack(propValue);
break;
}
Expand Down Expand Up @@ -390,11 +399,33 @@ function withHTMLSubtitles(Video) {
}
function getPublicSubtitleTrack(track) {
var publicTrack = Object.assign({}, track);
if (getEmbeddedASSSource(track) !== null) {
publicTrack.ass = true;
}
delete publicTrack.nativeExternal;
delete publicTrack.nativeExternalFilename;
delete publicTrack.nativeExternalTitle;
delete publicTrack._hlsSubtitlePlaylistURL;
return publicTrack;
}
function getEmbeddedSubtitleId(track) {
if (!track || typeof track._hlsSubtitlePlaylistURL !== 'string') {
return null;
}

var match = track._hlsSubtitlePlaylistURL.match(/(?:^|\/)subtitle(\d+)\.m3u8(?:$|[?#])/i);
return match === null ? null : match[1];
}
function getEmbeddedASSSource(track) {
var subtitleId = getEmbeddedSubtitleId(track);
if (subtitleId === null) {
return null;
}

return embeddedASSSources.find(function(source) {
return String(source.id) === subtitleId;
}) || null;
}
function isManagedNativeASSTrack(track) {
return track.id === nativeAssTrackId ||
removingNativeAssTrackIds.includes(track.id) ||
Expand Down Expand Up @@ -522,6 +553,82 @@ function withHTMLSubtitles(Video) {
loadPlainSubtitles(track, text, true, currentRequestId) :
Promise.resolve(null);
}
function clearEmbeddedASS() {
if (selectedEmbeddedASSTrackId === null) {
return;
}

selectedEmbeddedASSTrackId = null;
usingASSRenderer = false;
setASSSubtitlesStylingActive(false);
assRenderer.destroy();
subtitlesElement.style.display = '';
if (selectedTrackId === null) {
stopRenderLoop();
}
}
function fallbackFromEmbeddedASS(error, track, currentRequestId) {
currentRequestId = currentRequestId || selectionRequestId;
if (!track || currentRequestId !== selectionRequestId || selectedEmbeddedASSTrackId !== track.id) {
return;
}

var trackId = selectedEmbeddedASSTrackId;
reportSubtitleError(error, getPublicSubtitleTrack(track));
clearEmbeddedASS();
video.dispatch({
type: 'setProp',
propName: 'selectedSubtitlesTrackId',
propValue: trackId,
});
}
function loadEmbeddedASS(track, source, currentRequestId) {
var assTrack = Object.assign({}, getPublicSubtitleTrack(track), {
ass: true,
codec: source.codec,
fonts: Array.isArray(source.fonts) ? source.fonts : [],
url: source.url,
});

cuesByTime = null;
usingASSRenderer = true;
setASSSubtitlesStylingActive(false);
subtitlesElement.style.display = 'none';
while (subtitlesElement.hasChildNodes()) {
subtitlesElement.removeChild(subtitlesElement.lastChild);
}
lastTimeIndex = null;
assRenderer.setDelay(0);
assRenderer.setOpacity(embeddedSubtitlesOpacity);
startRenderLoop();

return getSubtitlesData(assTrack, false)
.then(function(text) {
if (currentRequestId !== selectionRequestId || selectedEmbeddedASSTrackId !== track.id) {
return null;
}

return assRenderer.load(assTrack, text);
})
.then(function(loadedTrack) {
if (currentRequestId !== selectionRequestId || selectedEmbeddedASSTrackId !== track.id) {
return null;
}
if (loadedTrack !== null) {
video.dispatch({
type: 'setProp',
propName: 'selectedSubtitlesTrackId',
propValue: null,
});
setASSSubtitlesStylingActive(true);
}
return loadedTrack;
})
.catch(function(error) {
fallbackFromEmbeddedASS(error, assTrack, currentRequestId);
return null;
});
}
function loadASSSubtitles(track, text, currentRequestId) {
cuesByTime = null;
usingASSRenderer = true;
Expand Down Expand Up @@ -725,6 +832,9 @@ function withHTMLSubtitles(Video) {
return videoPropValue;
}
case 'selectedSubtitlesTrackId': {
if (selectedEmbeddedASSTrackId !== null) {
return selectedEmbeddedASSTrackId;
}
if (isNativeASSSelection(videoPropValue)) {
return null;
}
Expand Down Expand Up @@ -795,10 +905,44 @@ function withHTMLSubtitles(Video) {

return true;
}
case 'selectedSubtitlesTrackId': {
var selectedVideoTrack = videoSubtitleTracks.find(function(track) {
return track.id === propValue;
});
var embeddedASSSource = getEmbeddedASSSource(selectedVideoTrack);
if (assSubtitlesStylingEnabled && selectedVideoTrack && embeddedASSSource !== null) {
if (selectedEmbeddedASSTrackId === propValue) {
return true;
}
if (selectedTrackId !== null) {
setProp('selectedExtraSubtitlesTrackId', null);
}
var embeddedRequestId = ++selectionRequestId;
clearEmbeddedASS();
selectedEmbeddedASSTrackId = propValue;
video.dispatch({
type: 'setProp',
propName: 'selectedSubtitlesTrackId',
propValue: propValue,
});
loadEmbeddedASS(selectedVideoTrack, embeddedASSSource, embeddedRequestId);
return true;
}
if (selectedEmbeddedASSTrackId !== null) {
selectionRequestId = selectionRequestId + 1;
clearEmbeddedASS();
}

return false;
}
case 'selectedExtraSubtitlesTrackId': {
if (propValue !== null && selectedTrackId === propValue) {
return true;
}
if (propValue !== null && selectedEmbeddedASSTrackId !== null) {
selectionRequestId = selectionRequestId + 1;
clearEmbeddedASS();
}
clearNativeASS();
if (propValue !== null) {
video.dispatch({
Expand Down Expand Up @@ -944,6 +1088,16 @@ function withHTMLSubtitles(Video) {

return true;
}
case 'subtitlesOpacity': {
if (typeof propValue === 'number') {
embeddedSubtitlesOpacity = Math.min(Math.max(propValue / 100, 0), 1);
if (selectedEmbeddedASSTrackId !== null) {
assRenderer.setOpacity(embeddedSubtitlesOpacity);
}
}

return false;
}
default: {
return false;
}
Expand Down Expand Up @@ -1003,6 +1157,10 @@ function withHTMLSubtitles(Video) {
case 'load': {
command('unload');
assSubtitlesStylingEnabled = commandArgs.assSubtitlesStyling === true;
embeddedASSSources = commandArgs.stream && Array.isArray(commandArgs.stream._embeddedASSSources) ?
commandArgs.stream._embeddedASSSources
:
[];
if (commandArgs.stream && Array.isArray(commandArgs.stream.subtitles)) {
command('addExtraSubtitlesTracks', {
tracks: commandArgs.stream.subtitles.map(function(track) {
Expand All @@ -1029,6 +1187,10 @@ function withHTMLSubtitles(Video) {
tracks = [];
selectedTrackId = null;
selectedSubtitleText = null;
embeddedASSSources = [];
videoSubtitleTracks = [];
selectedEmbeddedASSTrackId = null;
embeddedSubtitlesOpacity = 1;
delay = null;
usingASSRenderer = false;
setASSSubtitlesStylingActive(false);
Expand Down
Loading