import Debug from '../../conf/Debug'; import Scaler from './Scaler'; import Stretcher from './Stretcher'; import Zoom from './Zoom'; import PlayerData from '../video-data/PlayerData'; import ExtensionMode from '../../../common/enums/extension-mode.enum'; import Stretch from '../../../common/enums/stretch.enum'; import VideoAlignment from '../../../common/enums/video-alignment.enum'; import AspectRatio from '../../../common/enums/aspect-ratio.enum'; import CropModePersistance from '../../../common/enums/crop-mode-persistence.enum'; import { sleep } from '../Util'; if(Debug.debug) { console.log("Loading: Resizer.js"); } class Resizer { constructor(videoData) { this.resizerId = (Math.random(99)*100).toFixed(0); this.conf = videoData; this.logger = videoData.logger; this.video = videoData.video; this.settings = videoData.settings; this.extensionMode = videoData.extensionMode; this.scaler = new Scaler(this.conf); this.stretcher = new Stretcher(this.conf); this.zoom = new Zoom(this.conf); // load up default values this.correctedVideoDimensions = {}; this.currentCss = {}; this.currentStyleString = ""; this.currentPlayerStyleString = ""; this.currentCssValidFor = {}; this.lastAr = {type: AspectRatio.Initial}; this.videoAlignment = this.settings.getDefaultVideoAlignment(window.location.hostname); // this is initial video alignment this.destroyed = false; if (this.settings.active.pan) { this.canPan = this.settings.active.miscSettings.mousePan.enabled; } else { this.canPan = false; } this.userCss = ''; this.userCssClassName = videoData.userCssClassName; } injectCss(css) { this.conf.pageInfo.injectCss(css); } ejectCss(css) { this.conf.pageInfo.ejectCss(css); } replaceCss(oldCss, newCss) { this.conf.pageInfo.replaceCss(oldCss, newCss); } prepareCss(css) { return `.${this.userCssClassName} {${css}}`; } destroy(){ this.logger.log('info', ['debug', 'init'], `[Resizer::destroy] received destroy command.`); this.destroyed = true; } calculateRatioForLegacyOptions(ar){ // also present as modeToAr in Scaler.js if (ar.type !== AspectRatio.FitWidth && ar.type !== AspectRatio.FitHeight && ar.ratio) { return ar; } // Skrbi za "stare" možnosti, kot na primer "na širino zaslona", "na višino zaslona" in "ponastavi". // Približevanje opuščeno. // handles "legacy" options, such as 'fit to widht', 'fit to height' and AspectRatio.Reset. No zoom tho var ratioOut; if (!this.conf.video) { this.logger.log('info', 'debug', "[Scaler.js::modeToAr] No video??",this.conf.video, "killing videoData"); this.conf.destroy(); return null; } if (! this.conf.player.dimensions) { ratioOut = screen.width / screen.height; } else { this.logger.log('info', 'debug', `[Resizer::calculateRatioForLegacyOptions] Player dimensions:`, this.conf.player.dimensions.width ,'x', this.conf.player.dimensions.height,'aspect ratio:', this.conf.player.dimensions.width / this.conf.player.dimensions.height) ratioOut = this.conf.player.dimensions.width / this.conf.player.dimensions.height; } // POMEMBNO: lastAr je potrebno nastaviti šele po tem, ko kličemo _res_setAr(). _res_setAr() predvideva, // da želimo nastaviti statično (type: 'static') razmerje stranic — tudi, če funkcijo kličemo tu oz. v ArDetect. // // IMPORTANT NOTE: lastAr needs to be set after _res_setAr() is called, as _res_setAr() assumes we're // setting a static aspect ratio (even if the function is called from here or ArDetect). var fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight; if (ar.type === AspectRatio.FitWidth){ ar.ratio = ratioOut > fileAr ? ratioOut : fileAr; } else if(ar.type === AspectRatio.FitHeight){ ar.ratio = ratioOut < fileAr ? ratioOut : fileAr; } else if(ar.type === AspectRatio.Reset){ this.logger.log('info', 'debug', "[Scaler.js::modeToAr] Using original aspect ratio -", fileAr); ar.ratio = fileAr; } else { return null; } return ar; } updateAr(ar) { if (!ar) { return; } // Some options require a bit more testing re: whether they make sense // if they don't, we refuse to update aspect ratio until they do if (ar.type === AspectRatio.Automatic || ar.type === AspectRatio.Fixed) { if (!ar.ratio || isNaN(ar.ratio)) { return; } } // Only update aspect ratio if there's a difference between the old and the new state if (!this.lastAr || ar.type !== this.lastAr.type || ar.ratio !== this.lastAr.ratio) { this.setAr(ar); } } async setAr(ar, lastAr) { if (this.destroyed) { return; } this.logger.log('info', 'debug', '[Resizer::setAr] trying to set ar. New ar:', ar) if (ar == null) { return; } const siteSettings = this.settings.active.sites[window.location.hostname]; // reset zoom, but only on aspect ratio switch. We also know that aspect ratio gets converted to // AspectRatio.Fixed when zooming, so let's keep that in mind if ( (ar.type !== AspectRatio.Fixed && ar.type !== AspectRatio.Manual) // anything not these two _always_ changes AR || ar.type !== this.lastAr.type // this also means aspect ratio has changed || ar.ratio !== this.lastAr.ratio // this also means aspect ratio has changed ) { this.zoom.reset(); this.resetPan(); } // most everything that could go wrong went wrong by this stage, and returns can happen afterwards // this means here's the optimal place to set or forget aspect ratio. Saving of current crop ratio // is handled in pageInfo.updateCurrentCrop(), which also makes sure to persist aspect ratio if ar // is set to persist between videos / through current session / until manual reset. if (ar.type === AspectRatio.Automatic || ar.type === AspectRatio.Reset || ar.type === AspectRatio.Initial ) { // reset/undo default this.conf.pageInfo.updateCurrentCrop(undefined); } else { this.conf.pageInfo.updateCurrentCrop(ar); } if (ar.type === AspectRatio.Automatic || ar.type === AspectRatio.Reset && this.lastAr.type === AspectRatio.Initial) { // some sites do things that interfere with our site (and aspect ratio setting in general) // first, we check whether video contains anything we don't like if (siteSettings?.autoarPreventConditions?.videoStyleString) { const styleString = (this.video.getAttribute('style') || '').split(';'); if (siteSettings.autoarPreventConditions.videoStyleString.containsProperty) { const bannedProperties = siteSettings.autoarPreventConditions.videoStyleString.containsProperty; for (const prop in bannedProperties) { for (const s of styleString) { if (s.trim().startsWith(prop)) { // check if css property has a list of allowed values: if (bannedProperties[prop].allowedValues) { const styleValue = s.split(':')[1].trim(); // check if property value is on the list of allowed values // if it's not, we aren't allowed to start aard if (bannedProperties[prop].allowedValues.indexOf(styleValue) === -1) { this.logger.log('error', 'debug', "%c[Resizer::setAr] video style contains forbidden css property/value combo: ", "color: #900, background: #100", prop, " — we aren't allowed to start autoar.") return; } } else { // no allowed values, no problem. We have forbidden property // and this means aard can't start. this.logger.log('info', 'debug', "%c[Resizer::setAr] video style contains forbidden css property: ", "color: #900, background: #100", prop, " — we aren't allowed to start autoar.") return; } } } } } } } if (lastAr) { this.lastAr = this.calculateRatioForLegacyOptions(lastAr); ar = this.calculateRatioForLegacyOptions(ar); } else { // NOTE: "fitw" "fith" and "reset" should ignore ar.ratio bit, but // I'm not sure whether they do. Check that. ar = this.calculateRatioForLegacyOptions(ar); if (! ar) { this.logger.log('info', 'resizer', `[Resizer::setAr] <${this.resizerId}> Something wrong with ar or the player. Doing nothing.`); return; } this.lastAr = {type: ar.type, ratio: ar.ratio} } if (this.extensionMode === ExtensionMode.Basic && !PlayerData.isFullScreen() && ar.type !== AspectRatio.Reset) { // don't actually apply or calculate css when using basic mode if not in fullscreen // ... unless we're resetting the aspect ratio to original return; } if (! this.video) { this.conf.destroy(); } // pause AR on basic stretch, unpause when using other modes // for sine reason unpause doesn't unpause. investigate that later try { if (this.stretcher.mode === Stretch.Basic) { this.conf.arDetector.pause(); } else { if (this.lastAr.type === AspectRatio.Automatic) { this.conf.arDetector.unpause(); } } } catch (e) { // resizer starts before arDetector. this will do nothing but fail if arDetector isn't setup } // do stretch thingy if (this.stretcher.mode === Stretch.NoStretch || this.stretcher.mode === Stretch.Conditional || this.stretcher.mode === Stretch.FixedSource){ var stretchFactors = this.scaler.calculateCrop(ar); this.logger.log('error', 'debug', `[Resizer::setAr] failed to set AR due to problem with calculating crop. Error:`, stretchFactors && stretchFactors.error); if(! stretchFactors || stretchFactors.error){ if (stretchFactors?.error === 'no_video'){ this.conf.destroy(); return; } // we could have issued calculate crop too early. Let's tell VideoData that there's something wrong // and exit this function. When