mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add vtt and ass subtitle-offset in videoplayer
This commit is contained in:
parent
9677981344
commit
91241ceea1
5 changed files with 157 additions and 0 deletions
|
@ -188,6 +188,8 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
||||||
var currentAssRenderer;
|
var currentAssRenderer;
|
||||||
var customTrackIndex = -1;
|
var customTrackIndex = -1;
|
||||||
|
|
||||||
|
var currentTrackOffset;
|
||||||
|
|
||||||
var videoSubtitlesElem;
|
var videoSubtitlesElem;
|
||||||
var currentTrackEvents;
|
var currentTrackEvents;
|
||||||
|
|
||||||
|
@ -543,6 +545,65 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
||||||
setCurrentTrackElement(index);
|
setCurrentTrackElement(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.setSubtitleOffset = function(offset) {
|
||||||
|
|
||||||
|
var offsetValue = parseFloat(offset);
|
||||||
|
var videoElement = self._mediaElement;
|
||||||
|
var mediaStreamTextTracks = getMediaStreamTextTracks(self._currentPlayOptions.mediaSource);
|
||||||
|
|
||||||
|
Array.from(videoElement.textTracks)
|
||||||
|
.filter(trackElement => {
|
||||||
|
if (customTrackIndex === -1 ) {
|
||||||
|
// get showing .vtt textTacks
|
||||||
|
return trackElement.mode === 'showing';
|
||||||
|
} else {
|
||||||
|
// get current .ass textTrack
|
||||||
|
return ("textTrack" + customTrackIndex) === trackElement.id;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.forEach(trackElement => {
|
||||||
|
|
||||||
|
var track = mediaStreamTextTracks.filter(stream => {
|
||||||
|
return ("textTrack" + stream.Index) === trackElement.id;
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
if(track) {
|
||||||
|
offsetValue = updateCurrentTrackOffset(offsetValue);
|
||||||
|
var format = (track.Codec || '').toLowerCase();
|
||||||
|
if (format !== 'ass' && format !== 'ssa') {
|
||||||
|
setVttSubtitleOffset(trackElement, offsetValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("No available track, cannot apply offset : " + offsetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateCurrentTrackOffset(offsetValue) {
|
||||||
|
|
||||||
|
var relativeOffset = offsetValue;
|
||||||
|
var newTrackOffset = offsetValue;
|
||||||
|
if(currentTrackOffset){
|
||||||
|
relativeOffset -= currentTrackOffset;
|
||||||
|
}
|
||||||
|
currentTrackOffset = newTrackOffset;
|
||||||
|
// relative to currentTrackOffset
|
||||||
|
return relativeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVttSubtitleOffset(currentTrack, offsetValue) {
|
||||||
|
|
||||||
|
if(currentTrack.cues) {
|
||||||
|
Array.from(currentTrack.cues)
|
||||||
|
.forEach(cue => {
|
||||||
|
cue.startTime -= offsetValue;
|
||||||
|
cue.endTime -= offsetValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function isAudioStreamSupported(stream, deviceProfile) {
|
function isAudioStreamSupported(stream, deviceProfile) {
|
||||||
|
|
||||||
var codec = (stream.Codec || '').toLowerCase();
|
var codec = (stream.Codec || '').toLowerCase();
|
||||||
|
@ -1167,6 +1228,11 @@ define(['browser', 'require', 'events', 'apphost', 'loading', 'dom', 'playbackMa
|
||||||
|
|
||||||
function updateSubtitleText(timeMs) {
|
function updateSubtitleText(timeMs) {
|
||||||
|
|
||||||
|
// handle offset for ass tracks
|
||||||
|
if(currentTrackOffset) {
|
||||||
|
timeMs += (currentTrackOffset * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
var clock = currentClock;
|
var clock = currentClock;
|
||||||
if (clock) {
|
if (clock) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1635,6 +1635,28 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla
|
||||||
getPlayerData(player).subtitleStreamIndex = index;
|
getPlayerData(player).subtitleStreamIndex = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.supportSubtitleOffset = function(player) {
|
||||||
|
player = player || self._currentPlayer;
|
||||||
|
return 'setSubtitleOffset' in player;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isSubtitleStreamExternal = function(index, player) {
|
||||||
|
var stream = getSubtitleStream(player, index);
|
||||||
|
return stream ? getDeliveryMethod(stream) === 'External' : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setSubtitleOffset = function (sliderValue, player) {
|
||||||
|
player = player || self._currentPlayer;
|
||||||
|
player.setSubtitleOffset(self.getOffsetFromSliderValue(sliderValue));
|
||||||
|
};
|
||||||
|
|
||||||
|
self.getOffsetFromSliderValue = function(value) {
|
||||||
|
var offset = (value - 50) / 50;
|
||||||
|
// multiply by offset min/max range value (-x to +x) :
|
||||||
|
offset *= 30;
|
||||||
|
return offset.toFixed(1);
|
||||||
|
};
|
||||||
|
|
||||||
self.seek = function (ticks, player) {
|
self.seek = function (ticks, player) {
|
||||||
|
|
||||||
ticks = Math.max(0, ticks);
|
ticks = Math.max(0, ticks);
|
||||||
|
|
|
@ -280,6 +280,7 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med
|
||||||
btnFastForward.disabled = true;
|
btnFastForward.disabled = true;
|
||||||
btnRewind.disabled = true;
|
btnRewind.disabled = true;
|
||||||
view.querySelector(".btnSubtitles").classList.add("hide");
|
view.querySelector(".btnSubtitles").classList.add("hide");
|
||||||
|
view.querySelector(".subtitleSyncSliderContainer").classList.add("hide");
|
||||||
view.querySelector(".btnAudio").classList.add("hide");
|
view.querySelector(".btnAudio").classList.add("hide");
|
||||||
view.querySelector(".osdTitle").innerHTML = "";
|
view.querySelector(".osdTitle").innerHTML = "";
|
||||||
view.querySelector(".osdMediaInfo").innerHTML = "";
|
view.querySelector(".osdMediaInfo").innerHTML = "";
|
||||||
|
@ -295,8 +296,23 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med
|
||||||
|
|
||||||
if (playbackManager.subtitleTracks(player).length) {
|
if (playbackManager.subtitleTracks(player).length) {
|
||||||
view.querySelector(".btnSubtitles").classList.remove("hide");
|
view.querySelector(".btnSubtitles").classList.remove("hide");
|
||||||
|
|
||||||
|
if(playbackManager.supportSubtitleOffset()) {
|
||||||
|
var index = playbackManager.getSubtitleStreamIndex(player);
|
||||||
|
// if there is an external subtitle stream enabled
|
||||||
|
if(index !== -1 && playbackManager.isSubtitleStreamExternal(index, player)){
|
||||||
|
// show subtitle sync slider
|
||||||
|
subtitleSyncSliderContainer.classList.remove("hide");
|
||||||
|
}else{
|
||||||
|
// hide subtitle sync slider
|
||||||
|
subtitleSyncSliderContainer.classList.add("hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
view.querySelector(".btnSubtitles").classList.add("hide");
|
view.querySelector(".btnSubtitles").classList.add("hide");
|
||||||
|
// hide subtitle sync slider
|
||||||
|
subtitleSyncSliderContainer.classList.add("hide");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playbackManager.audioTracks(player).length > 1) {
|
if (playbackManager.audioTracks(player).length > 1) {
|
||||||
|
@ -1008,6 +1024,28 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med
|
||||||
if (index !== currentIndex) {
|
if (index !== currentIndex) {
|
||||||
playbackManager.setSubtitleStreamIndex(index, player);
|
playbackManager.setSubtitleStreamIndex(index, player);
|
||||||
}
|
}
|
||||||
|
return id;
|
||||||
|
|
||||||
|
}).then(function (id) {
|
||||||
|
var index = parseInt(id);
|
||||||
|
// on subtitle stream change
|
||||||
|
if (playbackManager.supportSubtitleOffset() && index !== currentIndex) {
|
||||||
|
|
||||||
|
/// if there is an external subtitle stream enabled
|
||||||
|
if (index !== -1 && playbackManager.isSubtitleStreamExternal(index, player)){
|
||||||
|
|
||||||
|
// set default offset to '0' (slider's middle value)
|
||||||
|
var subtitleSyncSliderMiddleValue = 50;
|
||||||
|
subtitleSyncSlider.value = subtitleSyncSliderMiddleValue.toString();
|
||||||
|
playbackManager.setSubtitleOffset(subtitleSyncSliderMiddleValue, player);
|
||||||
|
|
||||||
|
// show subtitle sync slider
|
||||||
|
subtitleSyncSliderContainer.classList.remove("hide");
|
||||||
|
} else {
|
||||||
|
// hide subtitle sync slider
|
||||||
|
subtitleSyncSliderContainer.classList.add("hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1146,6 +1184,8 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med
|
||||||
var programStartDateMs = 0;
|
var programStartDateMs = 0;
|
||||||
var programEndDateMs = 0;
|
var programEndDateMs = 0;
|
||||||
var playbackStartTimeTicks = 0;
|
var playbackStartTimeTicks = 0;
|
||||||
|
var subtitleSyncSlider = view.querySelector(".subtitleSyncSlider");
|
||||||
|
var subtitleSyncSliderContainer = view.querySelector(".subtitleSyncSliderContainer");
|
||||||
var nowPlayingVolumeSlider = view.querySelector(".osdVolumeSlider");
|
var nowPlayingVolumeSlider = view.querySelector(".osdVolumeSlider");
|
||||||
var nowPlayingVolumeSliderContainer = view.querySelector(".osdVolumeSliderContainer");
|
var nowPlayingVolumeSliderContainer = view.querySelector(".osdVolumeSliderContainer");
|
||||||
var nowPlayingPositionSlider = view.querySelector(".osdPositionSlider");
|
var nowPlayingPositionSlider = view.querySelector(".osdPositionSlider");
|
||||||
|
@ -1268,6 +1308,21 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med
|
||||||
nowPlayingVolumeSlider.addEventListener("touchmove", function () {
|
nowPlayingVolumeSlider.addEventListener("touchmove", function () {
|
||||||
playbackManager.setVolume(this.value, currentPlayer);
|
playbackManager.setVolume(this.value, currentPlayer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
subtitleSyncSlider.addEventListener("change", function () {
|
||||||
|
playbackManager.setSubtitleOffset(this.value, currentPlayer);
|
||||||
|
});
|
||||||
|
subtitleSyncSlider.addEventListener("touchmove", function () {
|
||||||
|
playbackManager.setSubtitleOffset(this.value, currentPlayer);
|
||||||
|
});
|
||||||
|
|
||||||
|
subtitleSyncSlider.getBubbleHtml = function (value) {
|
||||||
|
var newOffset = playbackManager.getOffsetFromSliderValue(value);
|
||||||
|
return '<h1 class="sliderBubbleText">' +
|
||||||
|
(newOffset > 0 ? "+" : "") + parseFloat(newOffset) + "s" +
|
||||||
|
"</h1>";
|
||||||
|
};
|
||||||
|
|
||||||
nowPlayingPositionSlider.addEventListener("change", function () {
|
nowPlayingPositionSlider.addEventListener("change", function () {
|
||||||
var player = currentPlayer;
|
var player = currentPlayer;
|
||||||
|
|
||||||
|
|
|
@ -144,6 +144,13 @@
|
||||||
flex-grow: 1
|
flex-grow: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subtitleSyncSliderContainer {
|
||||||
|
width: 12em;
|
||||||
|
-webkit-box-flex: 1;
|
||||||
|
-webkit-flex-grow: 1;
|
||||||
|
flex-grow: 1
|
||||||
|
}
|
||||||
|
|
||||||
.osdMediaInfo,
|
.osdMediaInfo,
|
||||||
.volumeButtons {
|
.volumeButtons {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
|
|
|
@ -59,6 +59,13 @@
|
||||||
<button is="paper-icon-button-light" class="btnSubtitles hide autoSize" title="${Subtitles}">
|
<button is="paper-icon-button-light" class="btnSubtitles hide autoSize" title="${Subtitles}">
|
||||||
<i class="xlargePaperIconButton md-icon"></i>
|
<i class="xlargePaperIconButton md-icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="subttitleSyncButtons hide-mouse-idle-tv">
|
||||||
|
<div class="sliderContainer subtitleSyncSliderContainer">
|
||||||
|
<input is="emby-slider" type="range" step="1" min="0" max="100" value="50" class="subtitleSyncSlider" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button is="paper-icon-button-light" class="btnVideoOsdSettings hide autoSize" title="${Settings}">
|
<button is="paper-icon-button-light" class="btnVideoOsdSettings hide autoSize" title="${Settings}">
|
||||||
<i class="largePaperIconButton md-icon"></i>
|
<i class="largePaperIconButton md-icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue