mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into blurred-pdf-fixed
This commit is contained in:
commit
2848f6295b
109 changed files with 2259 additions and 1324 deletions
|
@ -48,6 +48,9 @@ function supportsFade() {
|
|||
|
||||
function requireHlsPlayer(callback) {
|
||||
import('hls.js').then(({ default: hls }) => {
|
||||
hls.DefaultConfig.lowLatencyMode = false;
|
||||
hls.DefaultConfig.backBufferLength = Infinity;
|
||||
hls.DefaultConfig.liveBackBufferLength = 90;
|
||||
window.Hls = hls;
|
||||
callback();
|
||||
});
|
||||
|
|
|
@ -32,6 +32,7 @@ import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
|||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
||||
import Events from '../../utils/events.ts';
|
||||
import { includesAny } from '../../utils/container.ts';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
|
||||
/**
|
||||
* Returns resolved URL.
|
||||
|
@ -106,6 +107,9 @@ function tryRemoveElement(elem) {
|
|||
|
||||
function requireHlsPlayer(callback) {
|
||||
import('hls.js').then(({default: hls}) => {
|
||||
hls.DefaultConfig.lowLatencyMode = false;
|
||||
hls.DefaultConfig.backBufferLength = Infinity;
|
||||
hls.DefaultConfig.liveBackBufferLength = 90;
|
||||
window.Hls = hls;
|
||||
callback();
|
||||
});
|
||||
|
@ -155,6 +159,9 @@ function tryRemoveElement(elem) {
|
|||
return profileBuilder({});
|
||||
}
|
||||
|
||||
const PRIMARY_TEXT_TRACK_INDEX = 0;
|
||||
const SECONDARY_TEXT_TRACK_INDEX = 1;
|
||||
|
||||
export class HtmlVideoPlayer {
|
||||
/**
|
||||
* @type {string}
|
||||
|
@ -178,7 +185,6 @@ function tryRemoveElement(elem) {
|
|||
* @type {boolean}
|
||||
*/
|
||||
isFetching = false;
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement | null | undefined}
|
||||
*/
|
||||
|
@ -187,6 +193,10 @@ function tryRemoveElement(elem) {
|
|||
* @type {number | undefined}
|
||||
*/
|
||||
#subtitleTrackIndexToSetOnPlaying;
|
||||
/**
|
||||
* @type {number | undefined}
|
||||
*/
|
||||
#secondarySubtitleTrackIndexToSetOnPlaying;
|
||||
/**
|
||||
* @type {number | null}
|
||||
*/
|
||||
|
@ -207,6 +217,10 @@ function tryRemoveElement(elem) {
|
|||
* @type {number | undefined}
|
||||
*/
|
||||
#customTrackIndex;
|
||||
/**
|
||||
* @type {number | undefined}
|
||||
*/
|
||||
#customSecondaryTrackIndex;
|
||||
/**
|
||||
* @type {boolean | undefined}
|
||||
*/
|
||||
|
@ -215,14 +229,26 @@ function tryRemoveElement(elem) {
|
|||
* @type {number | undefined}
|
||||
*/
|
||||
#currentTrackOffset;
|
||||
/**
|
||||
* @type {HTMLElement | null | undefined}
|
||||
*/
|
||||
#secondaryTrackOffset;
|
||||
/**
|
||||
* @type {HTMLElement | null | undefined}
|
||||
*/
|
||||
#videoSubtitlesElem;
|
||||
/**
|
||||
* @type {HTMLElement | null | undefined}
|
||||
*/
|
||||
#videoSecondarySubtitlesElem;
|
||||
/**
|
||||
* @type {any | null | undefined}
|
||||
*/
|
||||
#currentTrackEvents;
|
||||
/**
|
||||
* @type {any | null | undefined}
|
||||
*/
|
||||
#currentSecondaryTrackEvents;
|
||||
/**
|
||||
* @type {string[] | undefined}
|
||||
*/
|
||||
|
@ -356,7 +382,7 @@ function tryRemoveElement(elem) {
|
|||
|
||||
this.#currentTime = null;
|
||||
|
||||
this.resetSubtitleOffset();
|
||||
if (options.resetSubtitleOffset !== false) this.resetSubtitleOffset();
|
||||
|
||||
return this.createMediaElement(options).then(elem => {
|
||||
return this.updateVideoUrl(options).then(() => {
|
||||
|
@ -448,18 +474,39 @@ function tryRemoveElement(elem) {
|
|||
destroyFlvPlayer(this);
|
||||
destroyCastPlayer(this);
|
||||
|
||||
let secondaryTrackValid = true;
|
||||
|
||||
this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex;
|
||||
if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) {
|
||||
const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying];
|
||||
if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') {
|
||||
this.#subtitleTrackIndexToSetOnPlaying = -1;
|
||||
secondaryTrackValid = false;
|
||||
}
|
||||
// secondary track should not be shown if primary track is no longer a valid pair
|
||||
if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream, this)) {
|
||||
secondaryTrackValid = false;
|
||||
}
|
||||
} else {
|
||||
secondaryTrackValid = false;
|
||||
}
|
||||
|
||||
this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex;
|
||||
|
||||
this._currentPlayOptions = options;
|
||||
|
||||
if (secondaryTrackValid) {
|
||||
this.#secondarySubtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSecondarySubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSecondarySubtitleStreamIndex;
|
||||
if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) {
|
||||
const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying];
|
||||
if (!initialSecondarySubtitleStream || !playbackManager.trackHasSecondarySubtitleSupport(initialSecondarySubtitleStream, this)) {
|
||||
this.#secondarySubtitleTrackIndexToSetOnPlaying = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.#secondarySubtitleTrackIndexToSetOnPlaying = -1;
|
||||
}
|
||||
|
||||
const crossOrigin = getCrossOriginValue(options.mediaSource);
|
||||
if (crossOrigin) {
|
||||
elem.crossOrigin = crossOrigin;
|
||||
|
@ -490,8 +537,13 @@ function tryRemoveElement(elem) {
|
|||
this.setCurrentTrackElement(index);
|
||||
}
|
||||
|
||||
setSecondarySubtitleStreamIndex(index) {
|
||||
this.setCurrentTrackElement(index, SECONDARY_TEXT_TRACK_INDEX);
|
||||
}
|
||||
|
||||
resetSubtitleOffset() {
|
||||
this.#currentTrackOffset = 0;
|
||||
this.#secondaryTrackOffset = 0;
|
||||
this.#showTrackOffset = false;
|
||||
}
|
||||
|
||||
|
@ -510,11 +562,11 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
getTextTrack() {
|
||||
getTextTracks() {
|
||||
const videoElement = this.#mediaElement;
|
||||
if (videoElement) {
|
||||
return Array.from(videoElement.textTracks)
|
||||
.find(function (trackElement) {
|
||||
.filter(function (trackElement) {
|
||||
// get showing .vtt textTack
|
||||
return trackElement.mode === 'showing';
|
||||
});
|
||||
|
@ -523,10 +575,12 @@ function tryRemoveElement(elem) {
|
|||
}
|
||||
}
|
||||
|
||||
setSubtitleOffset = debounce(this._setSubtitleOffset, 100);
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setSubtitleOffset(offset) {
|
||||
_setSubtitleOffset(offset) {
|
||||
const offsetValue = parseFloat(offset);
|
||||
|
||||
// if .ass currently rendering
|
||||
|
@ -534,12 +588,15 @@ function tryRemoveElement(elem) {
|
|||
this.updateCurrentTrackOffset(offsetValue);
|
||||
this.#currentSubtitlesOctopus.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue;
|
||||
} else {
|
||||
const trackElement = this.getTextTrack();
|
||||
const trackElements = this.getTextTracks();
|
||||
// if .vtt currently rendering
|
||||
if (trackElement) {
|
||||
this.setTextTrackSubtitleOffset(trackElement, offsetValue);
|
||||
} else if (this.#currentTrackEvents) {
|
||||
this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue);
|
||||
if (trackElements?.length > 0) {
|
||||
trackElements.forEach((trackElement, index) => {
|
||||
this.setTextTrackSubtitleOffset(trackElement, offsetValue, index);
|
||||
});
|
||||
} else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) {
|
||||
this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, PRIMARY_TEXT_TRACK_INDEX);
|
||||
this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, SECONDARY_TEXT_TRACK_INDEX);
|
||||
} else {
|
||||
console.debug('No available track, cannot apply offset: ', offsetValue);
|
||||
}
|
||||
|
@ -549,37 +606,100 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
updateCurrentTrackOffset(offsetValue) {
|
||||
updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) {
|
||||
let offsetToCompare = this.#currentTrackOffset;
|
||||
if (this.isSecondaryTrack(currentTrackIndex)) {
|
||||
offsetToCompare = this.#secondaryTrackOffset;
|
||||
}
|
||||
|
||||
let relativeOffset = offsetValue;
|
||||
const newTrackOffset = offsetValue;
|
||||
if (this.#currentTrackOffset) {
|
||||
relativeOffset -= this.#currentTrackOffset;
|
||||
|
||||
if (offsetToCompare) {
|
||||
relativeOffset -= offsetToCompare;
|
||||
}
|
||||
this.#currentTrackOffset = newTrackOffset;
|
||||
|
||||
if (this.isSecondaryTrack(currentTrackIndex)) {
|
||||
this.#secondaryTrackOffset = newTrackOffset;
|
||||
} else {
|
||||
this.#currentTrackOffset = newTrackOffset;
|
||||
}
|
||||
|
||||
// relative to currentTrackOffset
|
||||
return relativeOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* These browsers will not clear the existing active cue when setting an offset
|
||||
* for native TextTracks.
|
||||
* Any previous text tracks that are on the screen when the offset changes will remain next
|
||||
* to the new tracks until they reach the end time of the new offset's instance of the track.
|
||||
*/
|
||||
setTextTrackSubtitleOffset(currentTrack, offsetValue) {
|
||||
if (currentTrack.cues) {
|
||||
offsetValue = this.updateCurrentTrackOffset(offsetValue);
|
||||
Array.from(currentTrack.cues)
|
||||
.forEach(function (cue) {
|
||||
cue.startTime -= offsetValue;
|
||||
cue.endTime -= offsetValue;
|
||||
});
|
||||
requiresHidingActiveCuesOnOffsetChange() {
|
||||
return !!browser.firefox;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
hideTextTrackWithActiveCues(currentTrack) {
|
||||
if (currentTrack.activeCues) {
|
||||
currentTrack.mode = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the active cue to clear by disabling then re-enabling the track.
|
||||
* The track mode is reverted inside of a 0ms timeout to free up the track
|
||||
* and allow it to disable and clear the active cue.
|
||||
* @private
|
||||
*/
|
||||
forceClearTextTrackActiveCues(currentTrack) {
|
||||
if (currentTrack.activeCues) {
|
||||
currentTrack.mode = 'disabled';
|
||||
setTimeout(() => {
|
||||
currentTrack.mode = 'showing';
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setTrackEventsSubtitleOffset(trackEvents, offsetValue) {
|
||||
setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) {
|
||||
if (currentTrack.cues) {
|
||||
offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex);
|
||||
if (offsetValue === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldClearActiveCues = this.requiresHidingActiveCuesOnOffsetChange();
|
||||
if (shouldClearActiveCues) {
|
||||
this.hideTextTrackWithActiveCues(currentTrack);
|
||||
}
|
||||
|
||||
Array.from(currentTrack.cues)
|
||||
.forEach(function (cue) {
|
||||
cue.startTime -= offsetValue;
|
||||
cue.endTime -= offsetValue;
|
||||
});
|
||||
|
||||
if (shouldClearActiveCues) {
|
||||
this.forceClearTextTrackActiveCues(currentTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) {
|
||||
if (Array.isArray(trackEvents)) {
|
||||
offsetValue = this.updateCurrentTrackOffset(offsetValue) * 1e7; // ticks
|
||||
offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks
|
||||
if (offsetValue === 0) {
|
||||
return;
|
||||
}
|
||||
trackEvents.forEach(function (trackEvent) {
|
||||
trackEvent.StartPositionTicks -= offsetValue;
|
||||
trackEvent.EndPositionTicks -= offsetValue;
|
||||
|
@ -591,6 +711,14 @@ function tryRemoveElement(elem) {
|
|||
return this.#currentTrackOffset;
|
||||
}
|
||||
|
||||
isPrimaryTrack(textTrackIndex) {
|
||||
return textTrackIndex === PRIMARY_TEXT_TRACK_INDEX;
|
||||
}
|
||||
|
||||
isSecondaryTrack(textTrackIndex) {
|
||||
return textTrackIndex === SECONDARY_TEXT_TRACK_INDEX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
@ -697,6 +825,8 @@ function tryRemoveElement(elem) {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
this.setSubtitleOffset.cancel();
|
||||
|
||||
destroyHlsPlayer(this);
|
||||
destroyFlvPlayer(this);
|
||||
|
||||
|
@ -819,6 +949,16 @@ function tryRemoveElement(elem) {
|
|||
if (this.#audioTrackIndexToSetOnPlaying != null && this.canSetAudioStreamIndex()) {
|
||||
this.setAudioStreamIndex(this.#audioTrackIndexToSetOnPlaying);
|
||||
}
|
||||
|
||||
if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) {
|
||||
/**
|
||||
* Using a 0ms timeout to set the secondary subtitles because of some weird race condition when
|
||||
* setting both primary and secondary tracks at the same time.
|
||||
* The `TextTrack` content and cues will somehow get mixed up and each track will play a mix of both languages.
|
||||
* Putting this in a timeout fixes it completely.
|
||||
*/
|
||||
setTimeout(() => this.setSecondarySubtitleStreamIndex(this.#secondarySubtitleTrackIndexToSetOnPlaying), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -956,27 +1096,75 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
destroyCustomTrack(videoElement) {
|
||||
if (this.#videoSubtitlesElem) {
|
||||
const subtitlesContainer = this.#videoSubtitlesElem.parentNode;
|
||||
if (subtitlesContainer) {
|
||||
tryRemoveElement(subtitlesContainer);
|
||||
destroyCustomRenderedTrackElements(targetTrackIndex) {
|
||||
if (this.isPrimaryTrack(targetTrackIndex)) {
|
||||
if (this.#videoSubtitlesElem) {
|
||||
tryRemoveElement(this.#videoSubtitlesElem);
|
||||
this.#videoSubtitlesElem = null;
|
||||
}
|
||||
} else if (this.isSecondaryTrack(targetTrackIndex)) {
|
||||
if (this.#videoSecondarySubtitlesElem) {
|
||||
tryRemoveElement(this.#videoSecondarySubtitlesElem);
|
||||
this.#videoSecondarySubtitlesElem = null;
|
||||
}
|
||||
} else { // destroy all
|
||||
if (this.#videoSubtitlesElem) {
|
||||
const subtitlesContainer = this.#videoSubtitlesElem.parentNode;
|
||||
if (subtitlesContainer) {
|
||||
tryRemoveElement(subtitlesContainer);
|
||||
}
|
||||
this.#videoSubtitlesElem = null;
|
||||
this.#videoSecondarySubtitlesElem = null;
|
||||
}
|
||||
this.#videoSubtitlesElem = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.#currentTrackEvents = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroyNativeTracks(videoElement, targetTrackIndex) {
|
||||
if (videoElement) {
|
||||
const destroySingleTrack = typeof targetTrackIndex === 'number';
|
||||
const allTracks = videoElement.textTracks || []; // get list of tracks
|
||||
for (const track of allTracks) {
|
||||
for (let index = 0; index < allTracks.length; index++) {
|
||||
const track = allTracks[index];
|
||||
// Skip all other tracks if we are targeting just one
|
||||
if (destroySingleTrack && targetTrackIndex !== index) {
|
||||
continue;
|
||||
}
|
||||
if (track.label.includes('manualTrack')) {
|
||||
track.mode = 'disabled';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroyStoredTrackInfo(targetTrackIndex) {
|
||||
if (this.isPrimaryTrack(targetTrackIndex)) {
|
||||
this.#customTrackIndex = -1;
|
||||
this.#currentTrackEvents = null;
|
||||
} else if (this.isSecondaryTrack(targetTrackIndex)) {
|
||||
this.#customSecondaryTrackIndex = -1;
|
||||
this.#currentSecondaryTrackEvents = null;
|
||||
} else { // destroy all
|
||||
this.#customTrackIndex = -1;
|
||||
this.#customSecondaryTrackIndex = -1;
|
||||
this.#currentTrackEvents = null;
|
||||
this.#currentSecondaryTrackEvents = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroyCustomTrack(videoElement, targetTrackIndex) {
|
||||
this.destroyCustomRenderedTrackElements(targetTrackIndex);
|
||||
this.destroyNativeTracks(videoElement, targetTrackIndex);
|
||||
this.destroyStoredTrackInfo(targetTrackIndex);
|
||||
|
||||
this.#customTrackIndex = -1;
|
||||
this.#currentClock = null;
|
||||
this._currentAspectRatio = null;
|
||||
|
||||
|
@ -1029,23 +1217,34 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
setTrackForDisplay(videoElement, track) {
|
||||
setTrackForDisplay(videoElement, track, targetTextTrackIndex = PRIMARY_TEXT_TRACK_INDEX) {
|
||||
if (!track) {
|
||||
this.destroyCustomTrack(videoElement);
|
||||
// Destroy all tracks by passing undefined if there is no valid primary track
|
||||
this.destroyCustomTrack(videoElement, this.isSecondaryTrack(targetTextTrackIndex) ? targetTextTrackIndex : undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let targetTrackIndex = this.#customTrackIndex;
|
||||
if (this.isSecondaryTrack(targetTextTrackIndex)) {
|
||||
targetTrackIndex = this.#customSecondaryTrackIndex;
|
||||
}
|
||||
|
||||
// skip if already playing this track
|
||||
if (this.#customTrackIndex === track.Index) {
|
||||
if (targetTrackIndex === track.Index) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetSubtitleOffset();
|
||||
const item = this._currentPlayOptions.item;
|
||||
|
||||
this.destroyCustomTrack(videoElement);
|
||||
this.#customTrackIndex = track.Index;
|
||||
this.renderTracksEvents(videoElement, track, item);
|
||||
this.destroyCustomTrack(videoElement, targetTextTrackIndex);
|
||||
|
||||
if (this.isSecondaryTrack(targetTextTrackIndex)) {
|
||||
this.#customSecondaryTrackIndex = track.Index;
|
||||
} else {
|
||||
this.#customTrackIndex = track.Index;
|
||||
}
|
||||
this.renderTracksEvents(videoElement, track, item, targetTextTrackIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1155,16 +1354,39 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
renderSubtitlesWithCustomElement(videoElement, track, item) {
|
||||
this.fetchSubtitles(track, item).then((data) => {
|
||||
if (!this.#videoSubtitlesElem) {
|
||||
const subtitlesContainer = document.createElement('div');
|
||||
subtitlesContainer.classList.add('videoSubtitles');
|
||||
subtitlesContainer.innerHTML = '<div class="videoSubtitlesInner"></div>';
|
||||
this.#videoSubtitlesElem = subtitlesContainer.querySelector('.videoSubtitlesInner');
|
||||
renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex) {
|
||||
Promise.all([import('../../scripts/settings/userSettings'), this.fetchSubtitles(track, item)]).then((results) => {
|
||||
const [userSettings, subtitleData] = results;
|
||||
const subtitleAppearance = userSettings.getSubtitleAppearanceSettings();
|
||||
const subtitleVerticalPosition = parseInt(subtitleAppearance.verticalPosition, 10);
|
||||
|
||||
if (!this.#videoSubtitlesElem && !this.isSecondaryTrack(targetTextTrackIndex)) {
|
||||
let subtitlesContainer = document.querySelector('.videoSubtitles');
|
||||
if (!subtitlesContainer) {
|
||||
subtitlesContainer = document.createElement('div');
|
||||
subtitlesContainer.classList.add('videoSubtitles');
|
||||
}
|
||||
const subtitlesElement = document.createElement('div');
|
||||
subtitlesElement.classList.add('videoSubtitlesInner');
|
||||
subtitlesContainer.appendChild(subtitlesElement);
|
||||
this.#videoSubtitlesElem = subtitlesElement;
|
||||
this.setSubtitleAppearance(subtitlesContainer, this.#videoSubtitlesElem);
|
||||
videoElement.parentNode.appendChild(subtitlesContainer);
|
||||
this.#currentTrackEvents = data.TrackEvents;
|
||||
this.#currentTrackEvents = subtitleData.TrackEvents;
|
||||
} else if (!this.#videoSecondarySubtitlesElem && this.isSecondaryTrack(targetTextTrackIndex)) {
|
||||
const subtitlesContainer = document.querySelector('.videoSubtitles');
|
||||
if (!subtitlesContainer) return;
|
||||
const secondarySubtitlesElement = document.createElement('div');
|
||||
secondarySubtitlesElement.classList.add('videoSecondarySubtitlesInner');
|
||||
// determine the order of the subtitles
|
||||
if (subtitleVerticalPosition < 0) {
|
||||
subtitlesContainer.insertBefore(secondarySubtitlesElement, subtitlesContainer.firstChild);
|
||||
} else {
|
||||
subtitlesContainer.appendChild(secondarySubtitlesElement);
|
||||
}
|
||||
this.#videoSecondarySubtitlesElem = secondarySubtitlesElement;
|
||||
this.setSubtitleAppearance(subtitlesContainer, this.#videoSecondarySubtitlesElem);
|
||||
this.#currentSecondaryTrackEvents = subtitleData.TrackEvents;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1211,7 +1433,7 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
renderTracksEvents(videoElement, track, item) {
|
||||
renderTracksEvents(videoElement, track, item, targetTextTrackIndex = PRIMARY_TEXT_TRACK_INDEX) {
|
||||
if (!itemHelper.isLocalItem(item) || track.IsExternal) {
|
||||
const format = (track.Codec || '').toLowerCase();
|
||||
if (format === 'ssa' || format === 'ass') {
|
||||
|
@ -1220,15 +1442,15 @@ function tryRemoveElement(elem) {
|
|||
}
|
||||
|
||||
if (this.requiresCustomSubtitlesElement()) {
|
||||
this.renderSubtitlesWithCustomElement(videoElement, track, item);
|
||||
this.renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let trackElement = null;
|
||||
if (videoElement.textTracks && videoElement.textTracks.length > 0) {
|
||||
trackElement = videoElement.textTracks[0];
|
||||
|
||||
const updatingTrack = videoElement.textTracks && videoElement.textTracks.length > (this.isSecondaryTrack(targetTextTrackIndex) ? 1 : 0);
|
||||
if (updatingTrack) {
|
||||
trackElement = videoElement.textTracks[targetTextTrackIndex];
|
||||
// This throws an error in IE, but is fine in chrome
|
||||
// In IE it's not necessary anyway because changing the src seems to be enough
|
||||
try {
|
||||
|
@ -1288,24 +1510,29 @@ function tryRemoveElement(elem) {
|
|||
return;
|
||||
}
|
||||
|
||||
const trackEvents = this.#currentTrackEvents;
|
||||
const subtitleTextElement = this.#videoSubtitlesElem;
|
||||
const allTrackEvents = [this.#currentTrackEvents, this.#currentSecondaryTrackEvents];
|
||||
const subtitleTextElements = [this.#videoSubtitlesElem, this.#videoSecondarySubtitlesElem];
|
||||
|
||||
if (trackEvents && subtitleTextElement) {
|
||||
const ticks = timeMs * 10000;
|
||||
let selectedTrackEvent;
|
||||
for (const trackEvent of trackEvents) {
|
||||
if (trackEvent.StartPositionTicks <= ticks && trackEvent.EndPositionTicks >= ticks) {
|
||||
selectedTrackEvent = trackEvent;
|
||||
break;
|
||||
for (let i = 0; i < allTrackEvents.length; i++) {
|
||||
const trackEvents = allTrackEvents[i];
|
||||
const subtitleTextElement = subtitleTextElements[i];
|
||||
|
||||
if (trackEvents && subtitleTextElement) {
|
||||
const ticks = timeMs * 10000;
|
||||
let selectedTrackEvent;
|
||||
for (const trackEvent of trackEvents) {
|
||||
if (trackEvent.StartPositionTicks <= ticks && trackEvent.EndPositionTicks >= ticks) {
|
||||
selectedTrackEvent = trackEvent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTrackEvent && selectedTrackEvent.Text) {
|
||||
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true);
|
||||
subtitleTextElement.classList.remove('hide');
|
||||
} else {
|
||||
subtitleTextElement.classList.add('hide');
|
||||
if (selectedTrackEvent && selectedTrackEvent.Text) {
|
||||
subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true);
|
||||
subtitleTextElement.classList.remove('hide');
|
||||
} else {
|
||||
subtitleTextElement.classList.add('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1313,7 +1540,7 @@ function tryRemoveElement(elem) {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
setCurrentTrackElement(streamIndex) {
|
||||
setCurrentTrackElement(streamIndex, targetTextTrackIndex) {
|
||||
console.debug(`setting new text track index to: ${streamIndex}`);
|
||||
|
||||
const mediaStreamTextTracks = getMediaStreamTextTracks(this._currentPlayOptions.mediaSource);
|
||||
|
@ -1322,7 +1549,7 @@ function tryRemoveElement(elem) {
|
|||
return t.Index === streamIndex;
|
||||
})[0];
|
||||
|
||||
this.setTrackForDisplay(this.#mediaElement, track);
|
||||
this.setTrackForDisplay(this.#mediaElement, track, targetTextTrackIndex);
|
||||
if (enableNativeTrackSupport(this.#currentSrc, track)) {
|
||||
if (streamIndex !== -1) {
|
||||
this.setCueAppearance();
|
||||
|
@ -1500,6 +1727,7 @@ function tryRemoveElement(elem) {
|
|||
|
||||
list.push('SetBrightness');
|
||||
list.push('SetAspectRatio');
|
||||
list.push('SecondarySubtitles');
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -65,13 +65,22 @@ video[controls]::-webkit-media-controls {
|
|||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.videoSubtitlesInner {
|
||||
max-width: 70%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.videoSecondarySubtitlesInner {
|
||||
max-width: 70%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
min-height: 0 !important;
|
||||
margin-top: 0.5em !important;
|
||||
margin-bottom: 0.5em !important;
|
||||
}
|
||||
|
||||
@keyframes htmlvideoplayer-zoomin {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue