fix: update client-side subtitle positioning logic and vertical offset alignment
Build and Release Plugin / release (push) Successful in 33s

This commit is contained in:
2026-09-17 23:45:19 +02:00
parent 512dabc611
commit d88f910b58
+123 -36
View File
@@ -72,31 +72,96 @@ namespace JellyfinPlugin.Api
let interceptedItemId = null;
let currentCropData = null;
window._currentPlayMethod = null;
window._isVideoDirect = null;
function isTranscoding() {
if (window._currentPlayMethod === 'Transcode' || window._currentPlayMethod === 'DirectStream') {
if (window._isVideoDirect === false) {
return true;
}
try {
if (window.playbackManager) {
const currentPlayer = window.playbackManager.getCurrentPlayer();
if (currentPlayer && typeof currentPlayer.getPlayerState === 'function') {
const state = currentPlayer.getPlayerState();
if (state && state.PlayMethod) {
return state.PlayMethod === 'Transcode' || state.PlayMethod === 'DirectStream';
if (window._isVideoDirect === true) {
return false;
}
return false;
}
function ensureBoundingBoxHook(video) {
if (!video._origGetBoundingClientRect) {
video._origGetBoundingClientRect = video.getBoundingClientRect;
video.getBoundingClientRect = function() {
if (video._isCroppedByPlugin && video.parentElement) {
const rect = video.parentElement.getBoundingClientRect();
return {
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left,
width: rect.width,
height: rect.height,
x: rect.x !== undefined ? rect.x : rect.left,
y: rect.y !== undefined ? rect.y : rect.top,
toJSON: function() { return this; }
};
}
return video._origGetBoundingClientRect.apply(this, arguments);
};
}
}
let lastBottomPercent = 0;
function updateSubtitleStyles(bottomPercent) {
lastBottomPercent = bottomPercent;
let styleEl = document.getElementById('black-bar-remover-sub-style');
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = 'black-bar-remover-sub-style';
document.head.appendChild(styleEl);
}
if (bottomPercent > 0) {
const offsetVh = (bottomPercent * 0.4 + 2.5).toFixed(2);
styleEl.textContent = `
.videoSubtitles {
display: flex !important;
flex-direction: column !important;
justify-content: flex-end !important;
transform: translateY(-${offsetVh}vh) !important;
}
.track-text-output,
.custom-subtitle-overlay,
.subtitleSyncContainer,
.videoSubtitlesContainer,
.libassjs-canvas-parent,
canvas.libassjs-canvas,
.subtitles-octopus-canvas,
.htmlvideoplayer::-webkit-media-text-track-container,
.htmlvideoplayer::-webkit-media-text-track-display {
transform: translateY(-${offsetVh}vh) !important;
}
`;
console.log('Black Bar Remover: Subtitle CSS style updated translateY(-' + offsetVh + 'vh)');
} else {
styleEl.textContent = '';
}
}
function adjustVttCues(bottomPercent) {
const video = document.querySelector('video');
if (!video || !video.textTracks) return;
const targetLine = Math.max(10, 92 - Math.round(bottomPercent * 0.4 + 2.5));
for (let i = 0; i < video.textTracks.length; i++) {
const track = video.textTracks[i];
if (track && track.cues) {
for (let j = 0; j < track.cues.length; j++) {
const cue = track.cues[j];
if (cue && !cue._pluginAdjusted) {
cue.snapToLines = false;
cue.line = targetLine;
cue._pluginAdjusted = true;
}
}
}
} catch (e) {}
const video = document.querySelector('video');
if (video && video.src) {
const src = video.src.toLowerCase();
if (src.includes('transcode') || src.includes('.m3u8') || src.includes('transcodereasons')) {
return true;
}
}
return false;
}
function applyCrop(cropData) {
@@ -104,9 +169,16 @@ namespace JellyfinPlugin.Api
if (!video) return;
if (isTranscoding()) {
console.log('Black Bar Remover: Video is transcoded by server (PlayMethod:', window._currentPlayMethod, '), skipping CSS crop.');
console.log('Black Bar Remover: Video is transcoded by server (IsVideoDirect: false), skipping CSS crop.');
video.style.position = '';
video.style.objectFit = '';
video.style.width = '';
video.style.height = '';
video.style.top = '';
video.style.left = '';
video.style.clipPath = '';
video.style.transform = '';
updateSubtitleStyles(0);
return;
}
@@ -117,21 +189,28 @@ namespace JellyfinPlugin.Api
const x = parseInt(parts[2]);
const y = parseInt(parts[3]);
const topPercent = (y / 1080) * 100;
const bottomPercent = ((1080 - (y + h)) / 1080) * 100;
const leftPercent = (x / 1920) * 100;
const rightPercent = ((1920 - (x + w)) / 1920) * 100;
video.style.clipPath = `inset(${topPercent}% ${rightPercent}% ${bottomPercent}% ${leftPercent}%)`;
const scaleY = 1 / (1 - (topPercent + bottomPercent)/100);
const scaleX = 1 / (1 - (leftPercent + rightPercent)/100);
const scale = Math.max(scaleY, scaleX);
if (scale > 1.01) {
video.style.transform = `scale(${scale})`;
if (h > 0 && h < 1080) {
const heightPercent = (1080 / h) * 100;
const topPercent = -(y / h) * 100;
const bottomPercent = ((1080 - (y + h)) / 1080) * 100;
video.style.position = 'absolute';
video.style.objectFit = 'fill';
video.style.width = '100%';
video.style.height = heightPercent.toFixed(2) + '%';
video.style.top = topPercent.toFixed(2) + '%';
video.style.left = '0%';
video.style.transform = 'none';
video.style.clipPath = 'none';
if (video.parentElement) {
video.parentElement.style.overflow = 'hidden';
}
updateSubtitleStyles(bottomPercent);
adjustVttCues(bottomPercent);
console.log('Black Bar Remover: Applied non-scaling crop height:', heightPercent.toFixed(2) + '% top:', topPercent.toFixed(2) + '% subOffset:', (bottomPercent * 0.4 + 2.5).toFixed(2) + 'vh');
}
console.log('Black Bar Remover: Applied crop', cropData, 'scale', scale);
}
}
@@ -161,6 +240,7 @@ namespace JellyfinPlugin.Api
if (id && id !== interceptedItemId) {
interceptedItemId = id;
window._currentPlayMethod = null;
window._isVideoDirect = null;
console.log('Black Bar Remover: Intercepted itemId from fetch:', id);
}
}
@@ -174,8 +254,9 @@ namespace JellyfinPlugin.Api
clone.json().then(data => {
if (data && data.MediaSources && data.MediaSources[0]) {
const ms = data.MediaSources[0];
window._currentPlayMethod = ms.PlayMethod || (ms.TranscodingUrl ? 'Transcode' : 'DirectPlay');
console.log('Black Bar Remover: PlayMethod from fetch PlaybackInfo:', window._currentPlayMethod);
window._currentPlayMethod = ms.PlayMethod;
window._isVideoDirect = ms.IsVideoDirect !== false;
console.log('Black Bar Remover: PlaybackInfo detected -> PlayMethod:', window._currentPlayMethod, 'IsVideoDirect:', window._isVideoDirect);
if (currentCropData) {
applyCrop(currentCropData);
}
@@ -201,6 +282,7 @@ namespace JellyfinPlugin.Api
if (id && id !== interceptedItemId) {
interceptedItemId = id;
window._currentPlayMethod = null;
window._isVideoDirect = null;
console.log('Black Bar Remover: Intercepted itemId from XHR:', id);
}
}
@@ -210,8 +292,9 @@ namespace JellyfinPlugin.Api
const data = JSON.parse(xhr.responseText);
if (data && data.MediaSources && data.MediaSources[0]) {
const ms = data.MediaSources[0];
window._currentPlayMethod = ms.PlayMethod || (ms.TranscodingUrl ? 'Transcode' : 'DirectPlay');
console.log('Black Bar Remover: PlayMethod from XHR PlaybackInfo:', window._currentPlayMethod);
window._currentPlayMethod = ms.PlayMethod;
window._isVideoDirect = ms.IsVideoDirect !== false;
console.log('Black Bar Remover: XHR PlaybackInfo detected -> PlayMethod:', window._currentPlayMethod, 'IsVideoDirect:', window._isVideoDirect);
if (currentCropData) {
applyCrop(currentCropData);
}
@@ -299,8 +382,12 @@ namespace JellyfinPlugin.Api
console.log('Black Bar Remover: Video element found for intercepted item:', currentItemId);
fetchCrop(currentItemId);
checkServerCrop(currentItemId);
} else if (video && lastBottomPercent > 0) {
updateSubtitleStyles(lastBottomPercent);
adjustVttCues(lastBottomPercent);
} else if (!video) {
currentItemId = null;
lastBottomPercent = 0;
window._isServerCropping = false;
}
}, 2000);