2021-02-08 23:04:54 +01:00
|
|
|
import StretchType from '../../../common/enums/StretchType.enum';
|
2021-01-30 13:07:19 +01:00
|
|
|
import BrowserDetect from '../../conf/BrowserDetect';
|
2021-02-08 23:04:54 +01:00
|
|
|
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
2021-02-08 22:45:51 +01:00
|
|
|
import VideoData from '../video-data/VideoData';
|
|
|
|
import Logger from '../Logger';
|
|
|
|
import Settings from '../Settings';
|
2018-12-31 01:03:07 +01:00
|
|
|
|
2018-05-06 21:32:18 +02:00
|
|
|
// računa vrednosti za transform-scale (x, y)
|
|
|
|
// transform: scale(x,y) se uporablja za raztegovanje videa, ne pa za približevanje
|
|
|
|
// calculates values for transform scale(x, y)
|
2021-11-02 22:20:01 +01:00
|
|
|
// transform: scale(x,y) is used for stretching, not zooming.
|
2018-05-06 21:32:18 +02:00
|
|
|
|
|
|
|
class Stretcher {
|
2021-02-08 22:45:51 +01:00
|
|
|
//#region flags
|
2018-05-06 21:32:18 +02:00
|
|
|
|
2021-02-08 22:45:51 +01:00
|
|
|
//#endregion
|
|
|
|
|
|
|
|
//#region helper objects
|
|
|
|
conf: VideoData;
|
|
|
|
logger: Logger;
|
|
|
|
settings: Settings;
|
|
|
|
//#endregion
|
|
|
|
|
|
|
|
//#region misc data
|
|
|
|
mode: any;
|
|
|
|
fixedStretchRatio: any;
|
|
|
|
//#endregion
|
2018-05-06 21:32:18 +02:00
|
|
|
|
2018-05-07 21:58:11 +02:00
|
|
|
// functions
|
2019-09-03 22:55:10 +02:00
|
|
|
constructor(videoData) {
|
2018-05-18 23:26:20 +02:00
|
|
|
this.conf = videoData;
|
2019-09-03 23:01:23 +02:00
|
|
|
this.logger = videoData.logger;
|
2018-08-05 23:48:56 +02:00
|
|
|
this.settings = videoData.settings;
|
2021-11-23 01:32:42 +01:00
|
|
|
this.mode = this.settings.getDefaultStretchMode_legacy(window.location.hostname);
|
2019-12-06 00:17:09 +01:00
|
|
|
this.fixedStretchRatio = undefined;
|
2019-02-19 21:10:49 +01:00
|
|
|
}
|
|
|
|
|
2021-02-08 22:45:51 +01:00
|
|
|
setStretchMode(stretchMode, fixedStretchRatio?) {
|
2021-02-08 23:04:54 +01:00
|
|
|
if (stretchMode === StretchType.Default) {
|
2021-11-23 01:32:42 +01:00
|
|
|
this.mode = this.settings.getDefaultStretchMode_legacy(window.location.hostname);
|
2019-02-19 21:10:49 +01:00
|
|
|
} else {
|
2021-02-08 23:04:54 +01:00
|
|
|
if (stretchMode === StretchType.Fixed || stretchMode == StretchType.FixedSource) {
|
2019-12-06 00:17:09 +01:00
|
|
|
this.fixedStretchRatio = fixedStretchRatio;
|
|
|
|
}
|
2019-02-19 21:10:49 +01:00
|
|
|
this.mode = stretchMode;
|
|
|
|
}
|
2018-05-07 21:58:11 +02:00
|
|
|
}
|
|
|
|
|
2018-05-24 22:49:32 +02:00
|
|
|
applyConditionalStretch(stretchFactors, actualAr){
|
2021-04-10 04:08:09 +02:00
|
|
|
let playerAr = this.conf.player.aspectRatio;
|
|
|
|
let videoAr = this.conf.aspectRatio;
|
2018-05-07 21:58:11 +02:00
|
|
|
|
2018-05-24 23:50:46 +02:00
|
|
|
if (! actualAr){
|
|
|
|
actualAr = playerAr;
|
|
|
|
}
|
2018-05-07 21:58:11 +02:00
|
|
|
|
2021-02-18 22:38:32 +01:00
|
|
|
let newWidth = this.conf.video.offsetWidth * stretchFactors.xFactor;
|
|
|
|
let newHeight = this.conf.video.offsetHeight * stretchFactors.yFactor;
|
2018-05-07 21:58:11 +02:00
|
|
|
|
2021-02-18 22:38:32 +01:00
|
|
|
let actualWidth, actualHeight;
|
2018-05-07 21:58:11 +02:00
|
|
|
|
2018-05-24 23:50:46 +02:00
|
|
|
// determine the dimensions of the video (sans black bars) after scaling
|
|
|
|
if(actualAr < videoAr){
|
|
|
|
actualHeight = newHeight;
|
|
|
|
actualWidth = newHeight * actualAr;
|
|
|
|
} else {
|
|
|
|
actualHeight = newWidth / actualAr;
|
|
|
|
actualWidth = newWidth;
|
2018-05-07 21:58:11 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 22:38:32 +01:00
|
|
|
let minW = this.conf.player.dimensions.width * (1 - this.settings.active.stretch.conditionalDifferencePercent);
|
|
|
|
let maxW = this.conf.player.dimensions.width * (1 + this.settings.active.stretch.conditionalDifferencePercent);
|
2018-05-24 23:50:46 +02:00
|
|
|
|
2021-02-18 22:38:32 +01:00
|
|
|
let minH = this.conf.player.dimensions.height * (1 - this.settings.active.stretch.conditionalDifferencePercent);
|
|
|
|
let maxH = this.conf.player.dimensions.height * (1 + this.settings.active.stretch.conditionalDifferencePercent);
|
2018-05-24 23:50:46 +02:00
|
|
|
|
|
|
|
if (actualWidth >= minW && actualWidth <= maxW) {
|
|
|
|
stretchFactors.xFactor *= this.conf.player.dimensions.width / actualWidth;
|
|
|
|
}
|
|
|
|
if (actualHeight >= minH && actualHeight <= maxH) {
|
|
|
|
stretchFactors.yFactor *= this.conf.player.dimensions.height / actualHeight;
|
2018-05-06 21:32:18 +02:00
|
|
|
}
|
2018-05-07 21:58:11 +02:00
|
|
|
}
|
|
|
|
|
2018-06-15 00:33:10 +02:00
|
|
|
calculateBasicStretch() {
|
2021-11-02 22:20:01 +01:00
|
|
|
// video.videoWidth in video.videoHeight predstavljata velikost datoteke.
|
|
|
|
// velikost video datoteke je lahko drugačna kot velikost <video> elementa.
|
2018-07-10 20:36:12 +02:00
|
|
|
// Zaradi tega lahko pride do te situacije:
|
|
|
|
// * Ločljivost videa je 850x480 (videoWidth & videoHeight)
|
2021-11-02 22:20:01 +01:00
|
|
|
// * Velikost <video> značke je 1920x720.
|
2018-07-10 20:36:12 +02:00
|
|
|
// Znotraj te video značke bo video prikazan v 1280x720 pravokotniku. Raztegovanje
|
|
|
|
// torej hočemo računati z uporabo vrednosti 1280 in 720. Teh vrednosti pa ne
|
|
|
|
// poznamo. Torej jih moramo računati.
|
|
|
|
//
|
|
|
|
//
|
2020-05-16 20:52:37 +02:00
|
|
|
// video.videoWidth and video.videoHeight describe the size of the video file.
|
2018-07-10 20:36:12 +02:00
|
|
|
// Size of the video file can be different than the size of the <video> tag.
|
|
|
|
// This can leave us with the following situation:
|
|
|
|
// * Video resolution is 850x480-ish (as reported by videoWidth and videoHeight)
|
|
|
|
// * Size of the <video> tag is 1920x720
|
2021-11-02 22:20:01 +01:00
|
|
|
// The video will be displayed in a 1280x720 rectangle inside that <video> tag.
|
2018-07-10 20:36:12 +02:00
|
|
|
// This means we want to calculate stretching using those values, but we don't know
|
|
|
|
// them. This means we have to calculate them.
|
|
|
|
|
2021-04-10 04:08:09 +02:00
|
|
|
const streamAr = this.conf.aspectRatio;
|
|
|
|
if (this.conf.player.dimensions.width > this.conf.player.dimensions.height * streamAr) {
|
2018-07-10 20:36:12 +02:00
|
|
|
return {
|
2021-04-10 04:08:09 +02:00
|
|
|
xFactor: this.conf.player.dimensions.width / (this.conf.player.dimensions.height * streamAr),
|
2018-07-10 20:36:12 +02:00
|
|
|
yFactor: 1
|
|
|
|
};
|
|
|
|
}
|
2018-06-15 00:33:10 +02:00
|
|
|
|
|
|
|
return {
|
2018-07-10 20:36:12 +02:00
|
|
|
xFactor: 1,
|
2021-04-10 04:08:09 +02:00
|
|
|
yFactor: this.conf.player.dimensions.height / (this.conf.player.dimensions.width / streamAr)
|
2018-06-15 00:33:10 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-12-06 00:17:09 +01:00
|
|
|
applyStretchFixedSource(postCropStretchFactors) {
|
2021-04-10 04:08:09 +02:00
|
|
|
const streamAr = this.conf.aspectRatio;
|
|
|
|
const playerAr = this.conf.player.aspectRatio;
|
2019-12-06 00:17:09 +01:00
|
|
|
|
2021-04-10 04:08:09 +02:00
|
|
|
const squeezeFactor = this.fixedStretchRatio / streamAr;
|
2019-12-06 00:17:09 +01:00
|
|
|
|
|
|
|
// Whether squeezing happens on X or Y axis depends on whether required AR is wider or narrower than
|
|
|
|
// the player, in which the video is displayed
|
|
|
|
// * we squeeze X axis, if target AR is narrower than player size
|
|
|
|
// * we squeeze Y axis, if target AR is wider than the player size
|
|
|
|
|
|
|
|
this.logger.log('info', 'stretcher', `[Stretcher::applyStretchFixedSource] here's what we got:
|
|
|
|
postCropStretchFactors: x=${postCropStretchFactors.xFactor} y=${postCropStretchFactors.yFactor}
|
|
|
|
fixedStretchRatio: ${this.fixedStretchRatio}
|
2021-04-10 04:08:09 +02:00
|
|
|
videoAr: ${streamAr}
|
2019-12-06 00:17:09 +01:00
|
|
|
playerAr: ${playerAr}
|
2020-05-16 20:52:37 +02:00
|
|
|
squeezeFactor: ${squeezeFactor}`, '\nvideo', this.conf.video);
|
2019-12-06 00:17:09 +01:00
|
|
|
|
2021-05-12 00:01:40 +02:00
|
|
|
postCropStretchFactors.xFactor *= squeezeFactor;
|
2019-12-06 00:17:09 +01:00
|
|
|
|
|
|
|
this.logger.log('info', 'stretcher', `[Stretcher::applyStretchFixedSource] here's what we'll apply:\npostCropStretchFactors: x=${postCropStretchFactors.x} y=${postCropStretchFactors.y}`);
|
|
|
|
|
|
|
|
return postCropStretchFactors;
|
|
|
|
}
|
|
|
|
|
|
|
|
calculateStretchFixed(actualAr) {
|
|
|
|
return this.calculateStretch(actualAr, this.fixedStretchRatio);
|
|
|
|
}
|
|
|
|
|
2020-12-18 01:44:45 +01:00
|
|
|
getArCorrectionFactor() {
|
|
|
|
let arCorrectionFactor = 1;
|
2020-12-19 03:18:14 +01:00
|
|
|
arCorrectionFactor = this.conf.player.dimensions.width / this.conf.video.offsetWidth;
|
|
|
|
|
2020-12-18 01:44:45 +01:00
|
|
|
return arCorrectionFactor;
|
|
|
|
}
|
|
|
|
|
2021-02-08 22:45:51 +01:00
|
|
|
calculateStretch(actualAr, playerArOverride?) {
|
2021-04-10 04:08:09 +02:00
|
|
|
const playerAr = playerArOverride || this.conf.player.aspectRatio;
|
|
|
|
const streamAr = this.conf.aspectRatio;
|
2018-05-07 21:58:11 +02:00
|
|
|
|
2018-05-24 22:49:32 +02:00
|
|
|
if (! actualAr){
|
|
|
|
actualAr = playerAr;
|
2018-05-07 21:58:11 +02:00
|
|
|
}
|
|
|
|
|
2021-02-18 22:38:32 +01:00
|
|
|
let stretchFactors: any = {
|
2018-05-24 22:49:32 +02:00
|
|
|
xFactor: 1,
|
|
|
|
yFactor: 1
|
|
|
|
};
|
|
|
|
|
2020-12-19 03:18:14 +01:00
|
|
|
if (playerAr >= streamAr){
|
2018-05-28 23:56:44 +02:00
|
|
|
// player adds PILLARBOX
|
|
|
|
|
|
|
|
if(actualAr >= playerAr){
|
2018-05-29 00:36:14 +02:00
|
|
|
// VERIFIED WORKS
|
|
|
|
|
|
|
|
// actual > player > video — video is letterboxed
|
|
|
|
// solution: horizontal stretch according to difference between video and player AR
|
|
|
|
// vertical stretch according to difference between actual AR and player AR
|
2020-12-19 03:18:14 +01:00
|
|
|
stretchFactors.xFactor = playerAr / streamAr;
|
|
|
|
stretchFactors.yFactor = actualAr / streamAr;
|
2018-05-28 23:56:44 +02:00
|
|
|
|
2019-07-18 21:25:58 +02:00
|
|
|
this.logger.log('info', 'stretcher', "[Stretcher.js::calculateStretch] stretching strategy 1")
|
2020-12-19 03:18:14 +01:00
|
|
|
} else if ( actualAr >= streamAr) {
|
2018-05-29 00:36:14 +02:00
|
|
|
// VERIFIED WORKS
|
|
|
|
|
|
|
|
// player > actual > video — video is still letterboxed
|
2018-05-28 23:56:44 +02:00
|
|
|
// we need vertical stretch to remove black bars in video
|
|
|
|
// we need horizontal stretch to make video fit width
|
2020-12-19 03:18:14 +01:00
|
|
|
stretchFactors.xFactor = playerAr / streamAr;
|
|
|
|
stretchFactors.yFactor = actualAr / streamAr;
|
2018-05-28 23:56:44 +02:00
|
|
|
|
2019-07-18 21:25:58 +02:00
|
|
|
this.logger.log('info', 'stretcher', "[Stretcher.js::calculateStretch] stretching strategy 2")
|
2018-05-28 23:56:44 +02:00
|
|
|
} else {
|
2018-05-29 00:36:14 +02:00
|
|
|
// NEEDS CHECKING
|
2018-05-28 23:56:44 +02:00
|
|
|
// player > video > actual — double pillarbox
|
2018-06-27 23:55:37 +02:00
|
|
|
stretchFactors.xFactor = actualAr / playerAr;
|
2018-05-28 23:56:44 +02:00
|
|
|
stretchFactors.yFactor = 1;
|
2021-11-02 22:20:01 +01:00
|
|
|
|
2019-07-18 21:25:58 +02:00
|
|
|
this.logger.log('info', 'stretcher', "[Stretcher.js::calculateStretch] stretching strategy 3")
|
2018-05-24 22:49:32 +02:00
|
|
|
}
|
2021-01-30 12:16:27 +01:00
|
|
|
|
2018-05-24 22:49:32 +02:00
|
|
|
} else {
|
2018-05-28 23:56:44 +02:00
|
|
|
// player adds LETTERBOX
|
|
|
|
|
|
|
|
if (actualAr < playerAr) {
|
2018-05-29 00:36:14 +02:00
|
|
|
// NEEDS CHECKING
|
|
|
|
|
2018-05-28 23:56:44 +02:00
|
|
|
// video > player > actual
|
2018-05-29 00:36:14 +02:00
|
|
|
// video is PILLARBOXED
|
2018-05-28 23:56:44 +02:00
|
|
|
stretchFactors.xFactor = actualAr / playerAr;
|
2020-12-19 03:18:14 +01:00
|
|
|
stretchFactors.yFactor = streamAr / playerAr;
|
2018-05-28 23:56:44 +02:00
|
|
|
|
2019-07-18 21:25:58 +02:00
|
|
|
this.logger.log('info', 'stretcher', "[Stretcher.js::calculateStretch] stretching strategy 4")
|
2020-12-19 03:18:14 +01:00
|
|
|
} else if ( actualAr < streamAr ) {
|
2021-11-02 22:20:01 +01:00
|
|
|
// NEEDS CHECKING
|
2018-05-29 00:36:14 +02:00
|
|
|
|
2018-05-28 23:56:44 +02:00
|
|
|
// video > actual > player
|
|
|
|
// video is letterboxed by player
|
|
|
|
// actual is pillarboxed by video
|
|
|
|
stretchFactors.xFactor = actualAr / playerAr;
|
2019-12-06 00:17:09 +01:00
|
|
|
stretchFactors.yFactor = actualAr / playerAr;
|
2018-05-28 23:56:44 +02:00
|
|
|
|
2019-07-18 21:25:58 +02:00
|
|
|
this.logger.log('info', 'stretcher', "[Stretcher.js::calculateStretch] stretching strategy 5")
|
2018-05-24 22:49:32 +02:00
|
|
|
} else {
|
2018-05-29 00:36:14 +02:00
|
|
|
// VERIFIED CORRECT
|
|
|
|
|
2018-05-28 23:56:44 +02:00
|
|
|
// actual > video > player
|
|
|
|
// actual fits width. Letterboxed by both.
|
|
|
|
stretchFactors.xFactor = 1;
|
|
|
|
stretchFactors.yFactor = actualAr / playerAr;
|
|
|
|
|
2019-07-18 21:25:58 +02:00
|
|
|
this.logger.log('info', 'stretcher', "[Stretcher.js::calculateStretch] stretching strategy 6")
|
2018-05-24 22:49:32 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-28 23:56:44 +02:00
|
|
|
|
2020-12-20 01:00:06 +01:00
|
|
|
// const arCorrectionFactor = this.getArCorrectionFactor();
|
2020-12-19 03:18:14 +01:00
|
|
|
// correct factors, unless we're trying to reset
|
2020-12-20 01:00:06 +01:00
|
|
|
// stretchFactors.xFactor *= arCorrectionFactor;
|
|
|
|
// stretchFactors.yFactor *= arCorrectionFactor;
|
2020-12-19 03:18:14 +01:00
|
|
|
stretchFactors.arCorrectionFactor = this.getArCorrectionFactor();
|
2020-12-18 01:44:45 +01:00
|
|
|
|
2018-05-24 22:49:32 +02:00
|
|
|
return stretchFactors;
|
2018-05-07 21:58:11 +02:00
|
|
|
}
|
2021-01-30 12:16:37 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensure that <video> element is never both taller-ish and wider-ish than the screen, while in fullscreen
|
2021-11-02 22:20:01 +01:00
|
|
|
* on Chromium-based browsers.
|
|
|
|
*
|
2021-01-30 12:16:37 +01:00
|
|
|
* Workaround for Chrome/Edge issue where zooming too much results in video being stretched incorrectly.
|
2021-11-02 22:20:01 +01:00
|
|
|
*
|
2021-01-30 12:16:37 +01:00
|
|
|
* Bug description — if the following are true:
|
|
|
|
* * user is using Chrome or Edge (but surprisingly not Opera)
|
|
|
|
* * user is using hardware acceleration
|
|
|
|
* * user is using a noVideo card
|
|
|
|
* * user is in full screen mode
|
|
|
|
* * the video is both roughly taller and roughly wider than the monitor
|
2021-02-08 23:04:54 +01:00
|
|
|
* Then the video will do StretchType.Basic no matter what you put in `transform: scale(x,y)`.
|
2021-11-02 22:20:01 +01:00
|
|
|
*
|
2021-01-30 12:16:37 +01:00
|
|
|
* In practice, the issue appears slightly _before_ the last condition is met (video needs to be ~3434 px wide
|
|
|
|
* in order for this bug to trigger on my 3440x1440 display).
|
2021-11-02 22:20:01 +01:00
|
|
|
*
|
2021-01-30 12:16:37 +01:00
|
|
|
* Because this issue happens regardless of how you upscale the video (doesn't matter if you use transform:scale
|
|
|
|
* or width+height or anything else), the aspect ratio needs to be limited _before_ applying arCorrectionFactor
|
|
|
|
* (note that arCorrectionFactor is usually <= 1, as it conpensates for zooming that height=[>100%] on <video>
|
|
|
|
* style attribute does).
|
|
|
|
*/
|
|
|
|
chromeBugMitigation(stretchFactors) {
|
2021-04-04 03:42:18 +02:00
|
|
|
if (
|
2021-11-02 22:20:01 +01:00
|
|
|
BrowserDetect.anyChromium
|
2021-04-04 15:48:46 +02:00
|
|
|
&& (this.conf.player?.dimensions?.fullscreen || ! this.settings?.active?.mitigations?.zoomLimit?.fullscreenOnly)
|
2021-04-04 03:42:18 +02:00
|
|
|
&& this.settings?.active?.mitigations?.zoomLimit?.enabled
|
|
|
|
) {
|
2021-04-10 04:08:09 +02:00
|
|
|
const playerAr = this.conf.player.aspectRatio;
|
|
|
|
const streamAr = this.conf.aspectRatio;
|
2021-11-02 22:20:01 +01:00
|
|
|
|
2021-04-04 15:48:46 +02:00
|
|
|
let maxSafeAr: number;
|
2021-04-04 03:42:18 +02:00
|
|
|
let arLimitFactor = this.settings?.active?.mitigations?.zoomLimit?.limit ?? 0.997;
|
2021-01-30 13:31:30 +01:00
|
|
|
|
|
|
|
if (playerAr >= (streamAr * 1.1)) {
|
2021-04-04 03:42:18 +02:00
|
|
|
maxSafeAr = (window.innerWidth * arLimitFactor) / window.innerHeight;
|
2021-01-30 13:31:30 +01:00
|
|
|
} else if (playerAr < (streamAr * 0.95)) {
|
2021-04-04 03:42:18 +02:00
|
|
|
maxSafeAr = window.innerWidth / (window.innerHeight * arLimitFactor);
|
2021-01-30 13:31:30 +01:00
|
|
|
} else {
|
|
|
|
// in some cases, we tolerate minor stretch to avoid tiny black bars
|
|
|
|
return;
|
2021-01-30 12:16:37 +01:00
|
|
|
}
|
2021-11-02 22:20:01 +01:00
|
|
|
|
2021-01-31 17:44:24 +01:00
|
|
|
const maxSafeStretchFactor = this.conf.resizer.scaler.calculateCropCore(
|
2021-11-02 22:20:01 +01:00
|
|
|
{
|
2021-01-31 17:44:24 +01:00
|
|
|
xFactor: 1,
|
|
|
|
yFactor: 1,
|
|
|
|
arCorrectionFactor: stretchFactors.arCorrectionFactor
|
|
|
|
},
|
|
|
|
maxSafeAr,
|
|
|
|
streamAr,
|
|
|
|
playerAr
|
|
|
|
).xFactor;
|
|
|
|
|
|
|
|
// console.info('Stretch factors before:', stretchFactors.xFactor, stretchFactors.yFactor, "max safe:", maxSafeStretchFactor, "max safe ar:", maxSafeAr);
|
2021-01-30 13:31:30 +01:00
|
|
|
|
|
|
|
stretchFactors.xFactor = Math.min(stretchFactors.xFactor, maxSafeStretchFactor);
|
|
|
|
stretchFactors.yFactor = Math.min(stretchFactors.yFactor, maxSafeStretchFactor);
|
2021-01-31 23:32:32 +01:00
|
|
|
|
|
|
|
return stretchFactors;
|
2021-01-30 13:31:30 +01:00
|
|
|
}
|
2021-01-30 12:16:37 +01:00
|
|
|
}
|
2018-12-31 01:03:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export default Stretcher;
|