Whitespace
This commit is contained in:
parent
746a78577e
commit
a4948d3eef
@ -14,7 +14,7 @@ interface ActionScopeInterface {
|
|||||||
code?: string,
|
code?: string,
|
||||||
ctrlKey?: boolean,
|
ctrlKey?: boolean,
|
||||||
metaKey?: boolean,
|
metaKey?: boolean,
|
||||||
altKey?: boolean,
|
altKey?: boolean,
|
||||||
shiftKey?: boolean,
|
shiftKey?: boolean,
|
||||||
onKeyUp?: boolean,
|
onKeyUp?: boolean,
|
||||||
onKeyDown?: boolean,
|
onKeyDown?: boolean,
|
||||||
@ -22,10 +22,17 @@ interface ActionScopeInterface {
|
|||||||
}[],
|
}[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RestrictionsSettings {
|
||||||
|
disableOnSmallPlayers?: boolean; // Whether ultrawidify should disable itself when the player is small
|
||||||
|
minAllowedWidth?: number; // if player is less than this many px wide, ultrawidify will disable itself
|
||||||
|
minAllowedHeight?: number; // if player is less than this many px tall, ultrawidify will disable itself
|
||||||
|
onlyAllowInFullscreen?: boolean; // if enabled, ultrawidify will be disabled when not in full screen regardless of what previous two options say
|
||||||
|
}
|
||||||
|
|
||||||
interface SettingsInterface {
|
interface SettingsInterface {
|
||||||
arDetect: {
|
arDetect: {
|
||||||
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
|
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
|
||||||
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
|
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
|
||||||
// Any more and we don't adjust ar.
|
// Any more and we don't adjust ar.
|
||||||
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
|
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
|
||||||
timers: { // autodetection frequency
|
timers: { // autodetection frequency
|
||||||
@ -56,14 +63,14 @@ interface SettingsInterface {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// samplingInterval: 10, // we sample at columns at (width/this) * [ 1 .. this - 1]
|
// samplingInterval: 10, // we sample at columns at (width/this) * [ 1 .. this - 1]
|
||||||
blackframe: {
|
blackframe: {
|
||||||
sufficientColorVariance: number, // calculate difference between average intensity and pixel, for every pixel for every color
|
sufficientColorVariance: number, // calculate difference between average intensity and pixel, for every pixel for every color
|
||||||
// component. Average intensity is normalized to where 0 is black and 1 is biggest value for
|
// component. Average intensity is normalized to where 0 is black and 1 is biggest value for
|
||||||
// that component. If sum of differences between normalized average intensity and normalized
|
// that component. If sum of differences between normalized average intensity and normalized
|
||||||
// component varies more than this % between color components, we can afford to use less strict
|
// component varies more than this % between color components, we can afford to use less strict
|
||||||
// cumulative threshold.
|
// cumulative threshold.
|
||||||
cumulativeThresholdLax: number,
|
cumulativeThresholdLax: number,
|
||||||
cumulativeThresholdStrict: number,// if we add values of all pixels together and get more than this, the frame is bright enough.
|
cumulativeThresholdStrict: number,// if we add values of all pixels together and get more than this, the frame is bright enough.
|
||||||
// (note: blackframe is 16x9 px -> 144px total. cumulative threshold can be reached fast)
|
// (note: blackframe is 16x9 px -> 144px total. cumulative threshold can be reached fast)
|
||||||
blackPixelsCondition: number, // How much pixels must be black (1 all, 0 none) before we consider frame as black. Takes
|
blackPixelsCondition: number, // How much pixels must be black (1 all, 0 none) before we consider frame as black. Takes
|
||||||
@ -103,7 +110,7 @@ interface SettingsInterface {
|
|||||||
randomCols: number, // we add this many randomly selected columns to the static columns
|
randomCols: number, // we add this many randomly selected columns to the static columns
|
||||||
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks
|
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks
|
||||||
},
|
},
|
||||||
guardLine: { // all pixels on the guardline need to be black, or else we trigger AR recalculation
|
guardLine: { // all pixels on the guardline need to be black, or else we trigger AR recalculation
|
||||||
// (if AR fails to be recalculated, we reset AR)
|
// (if AR fails to be recalculated, we reset AR)
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
ignoreEdgeMargin: number, // we ignore anything that pokes over the black line this close to the edge
|
ignoreEdgeMargin: number, // we ignore anything that pokes over the black line this close to the edge
|
||||||
@ -117,14 +124,14 @@ interface SettingsInterface {
|
|||||||
safetyBorderPx: number, // determines the thickness of safety border in fallback mode
|
safetyBorderPx: number, // determines the thickness of safety border in fallback mode
|
||||||
noTriggerZonePx: number // if we detect edge less than this many pixels thick, we don't correct.
|
noTriggerZonePx: number // if we detect edge less than this many pixels thick, we don't correct.
|
||||||
},
|
},
|
||||||
arSwitchLimiter: { // to be implemented
|
arSwitchLimiter: { // to be implemented
|
||||||
switches: number, // we can switch this many times
|
switches: number, // we can switch this many times
|
||||||
period: number // per this period
|
period: number // per this period
|
||||||
},
|
},
|
||||||
edgeDetection: {
|
edgeDetection: {
|
||||||
sampleWidth: number, // we take a sample this wide for edge detection
|
sampleWidth: number, // we take a sample this wide for edge detection
|
||||||
detectionThreshold: number, // sample needs to have this many non-black pixels to be a valid edge
|
detectionThreshold: number, // sample needs to have this many non-black pixels to be a valid edge
|
||||||
confirmationThreshold: number, //
|
confirmationThreshold: number, //
|
||||||
singleSideConfirmationThreshold: number, // we need this much edges (out of all samples, not just edges) in order
|
singleSideConfirmationThreshold: number, // we need this much edges (out of all samples, not just edges) in order
|
||||||
// to confirm an edge in case there's no edges on top or bottom (other
|
// to confirm an edge in case there's no edges on top or bottom (other
|
||||||
// than logo, of course)
|
// than logo, of course)
|
||||||
@ -138,11 +145,11 @@ interface SettingsInterface {
|
|||||||
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
|
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
|
||||||
},
|
},
|
||||||
pillarTest: {
|
pillarTest: {
|
||||||
ignoreThinPillarsPx: number, // ignore pillars that are less than this many pixels thick.
|
ignoreThinPillarsPx: number, // ignore pillars that are less than this many pixels thick.
|
||||||
allowMisaligned: number // left and right edge can vary this much (%)
|
allowMisaligned: number // left and right edge can vary this much (%)
|
||||||
},
|
},
|
||||||
textLineTest: {
|
textLineTest: {
|
||||||
nonTextPulse: number, // if a single continuous pulse has this many non-black pixels, we aren't dealing
|
nonTextPulse: number, // if a single continuous pulse has this many non-black pixels, we aren't dealing
|
||||||
// with text. This value is relative to canvas width (%)
|
// with text. This value is relative to canvas width (%)
|
||||||
pulsesToConfirm: number, // this is a threshold to confirm we're seeing text.
|
pulsesToConfirm: number, // this is a threshold to confirm we're seeing text.
|
||||||
pulsesToConfirmIfHalfBlack: number, // this is the threshold to confirm we're seeing text if longest black pulse
|
pulsesToConfirmIfHalfBlack: number, // this is the threshold to confirm we're seeing text if longest black pulse
|
||||||
@ -150,11 +157,15 @@ interface SettingsInterface {
|
|||||||
testRowOffset: number // we test this % of height from detected edge
|
testRowOffset: number // we test this % of height from detected edge
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
restrictions?: RestrictionsSettings;
|
||||||
|
|
||||||
zoom: {
|
zoom: {
|
||||||
minLogZoom: number,
|
minLogZoom: number,
|
||||||
maxLogZoom: number,
|
maxLogZoom: number,
|
||||||
announceDebounce: number // we wait this long before announcing new zoom
|
announceDebounce: number // we wait this long before announcing new zoom
|
||||||
},
|
},
|
||||||
|
|
||||||
miscSettings: {
|
miscSettings: {
|
||||||
mousePan: {
|
mousePan: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
@ -197,8 +208,8 @@ interface SettingsInterface {
|
|||||||
// ::: ACTIONS :::
|
// ::: ACTIONS :::
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
// Nastavitve za ukaze. Zamenja stare nastavitve za bližnične tipke.
|
// Nastavitve za ukaze. Zamenja stare nastavitve za bližnične tipke.
|
||||||
//
|
//
|
||||||
// Polje 'shortcut' je tabela, če se slučajno lotimo kdaj delati choordov.
|
// Polje 'shortcut' je tabela, če se slučajno lotimo kdaj delati choordov.
|
||||||
actions: {
|
actions: {
|
||||||
name?: string, // name displayed in settings
|
name?: string, // name displayed in settings
|
||||||
label?: string, // name displayed in ui (can be overridden in scope/playerUi)
|
label?: string, // name displayed in ui (can be overridden in scope/playerUi)
|
||||||
@ -226,31 +237,31 @@ interface SettingsInterface {
|
|||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
// Nastavitve za posamezno stran
|
// Nastavitve za posamezno stran
|
||||||
// Config for a given page:
|
// Config for a given page:
|
||||||
//
|
//
|
||||||
// <hostname> : {
|
// <hostname> : {
|
||||||
// status: <option> // should extension work on this site?
|
// status: <option> // should extension work on this site?
|
||||||
// arStatus: <option> // should we do autodetection on this site?
|
// arStatus: <option> // should we do autodetection on this site?
|
||||||
//
|
//
|
||||||
// defaultAr?: <ratio> // automatically apply this aspect ratio on this side. Use extension defaults if undefined.
|
// defaultAr?: <ratio> // automatically apply this aspect ratio on this side. Use extension defaults if undefined.
|
||||||
// stretch? <stretch mode> // automatically stretch video on this site in this manner
|
// stretch? <stretch mode> // automatically stretch video on this site in this manner
|
||||||
// videoAlignment? <left|center|right>
|
// videoAlignment? <left|center|right>
|
||||||
//
|
//
|
||||||
// type: <official|community|user> // 'official' — blessed by Tam.
|
// type: <official|community|user> // 'official' — blessed by Tam.
|
||||||
// // 'community' — blessed by reddit.
|
// // 'community' — blessed by reddit.
|
||||||
// // 'user' — user-defined (not here)
|
// // 'user' — user-defined (not here)
|
||||||
// override: <true|false> // override user settings for this site on update
|
// override: <true|false> // override user settings for this site on update
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Veljavne vrednosti za možnosti
|
// Veljavne vrednosti za možnosti
|
||||||
// Valid values for options:
|
// Valid values for options:
|
||||||
//
|
//
|
||||||
// status, arStatus, statusEmbedded:
|
// status, arStatus, statusEmbedded:
|
||||||
//
|
//
|
||||||
// * enabled — always allow, full
|
// * enabled — always allow, full
|
||||||
// * basic — allow, but only the basic version without playerData
|
// * basic — allow, but only the basic version without playerData
|
||||||
// * default — allow if default is to allow, block if default is to block
|
// * default — allow if default is to allow, block if default is to block
|
||||||
// * disabled — never allow
|
// * disabled — never allow
|
||||||
//
|
//
|
||||||
sites: {
|
sites: {
|
||||||
[x: string]: {
|
[x: string]: {
|
||||||
mode?: ExtensionMode,
|
mode?: ExtensionMode,
|
||||||
@ -286,8 +297,10 @@ interface SettingsInterface {
|
|||||||
},
|
},
|
||||||
css?: string;
|
css?: string;
|
||||||
usePlayerArInFullscreen?: boolean;
|
usePlayerArInFullscreen?: boolean;
|
||||||
|
|
||||||
|
restrictions?: RestrictionsSettings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsInterface;
|
export default SettingsInterface;
|
||||||
|
@ -39,7 +39,7 @@ class PageInfo {
|
|||||||
currentCrop: any;
|
currentCrop: any;
|
||||||
actionHandlerInitQueue: any[] = [];
|
actionHandlerInitQueue: any[] = [];
|
||||||
currentZoomScale: number = 1;
|
currentZoomScale: number = 1;
|
||||||
|
|
||||||
actionHandler: any;
|
actionHandler: any;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ class PageInfo {
|
|||||||
this.extensionMode = extensionMode;
|
this.extensionMode = extensionMode;
|
||||||
this.readOnly = readOnly;
|
this.readOnly = readOnly;
|
||||||
|
|
||||||
if (comms){
|
if (comms){
|
||||||
this.comms = comms;
|
this.comms = comms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,10 +179,10 @@ class PageInfo {
|
|||||||
* of videos. Destroys all videoData objects for all the videos that don't have their
|
* of videos. Destroys all videoData objects for all the videos that don't have their
|
||||||
* own <video> html element on the page.
|
* own <video> html element on the page.
|
||||||
* @param rescanReason Why was the rescan triggered. Mostly used for logging.
|
* @param rescanReason Why was the rescan triggered. Mostly used for logging.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
rescan(rescanReason?: RescanReason){
|
rescan(rescanReason?: RescanReason){
|
||||||
// is there any video data objects that had their HTML elements removed but not yet
|
// is there any video data objects that had their HTML elements removed but not yet
|
||||||
// destroyed? We clean that up here.
|
// destroyed? We clean that up here.
|
||||||
const orphans = this.videos.filter(x => !document.body.contains(x.element));
|
const orphans = this.videos.filter(x => !document.body.contains(x.element));
|
||||||
for (const orphan of orphans) {
|
for (const orphan of orphans) {
|
||||||
@ -198,7 +198,7 @@ class PageInfo {
|
|||||||
|
|
||||||
if(!vids || vids.length == 0){
|
if(!vids || vids.length == 0){
|
||||||
this.hasVideos = false;
|
this.hasVideos = false;
|
||||||
|
|
||||||
if(rescanReason == RescanReason.PERIODIC){
|
if(rescanReason == RescanReason.PERIODIC){
|
||||||
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] Scheduling normal rescan.")
|
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] Scheduling normal rescan.")
|
||||||
this.scheduleRescan(RescanReason.PERIODIC);
|
this.scheduleRescan(RescanReason.PERIODIC);
|
||||||
@ -221,14 +221,14 @@ class PageInfo {
|
|||||||
if(!videoElement.offsetWidth || !videoElement.offsetHeight) {
|
if(!videoElement.offsetWidth || !videoElement.offsetHeight) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// at this point, we're certain that we found new videos. Let's update some properties:
|
// at this point, we're certain that we found new videos. Let's update some properties:
|
||||||
this.hasVideos = true;
|
this.hasVideos = true;
|
||||||
|
|
||||||
// if PageInfo is marked as "readOnly", we actually aren't adding any videos to anything because
|
// if PageInfo is marked as "readOnly", we actually aren't adding any videos to anything because
|
||||||
// that's super haram. We're only interested in whether
|
// that's super haram. We're only interested in whether
|
||||||
if (this.readOnly) {
|
if (this.readOnly) {
|
||||||
// in lite mode, we're done. This is all the info we want, but we want to actually start doing
|
// in lite mode, we're done. This is all the info we want, but we want to actually start doing
|
||||||
// things that interfere with the website. We still want to be running a rescan, tho.
|
// things that interfere with the website. We still want to be running a rescan, tho.
|
||||||
|
|
||||||
if(rescanReason == RescanReason.PERIODIC){
|
if(rescanReason == RescanReason.PERIODIC){
|
||||||
@ -241,11 +241,11 @@ class PageInfo {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const newVideo = new VideoData(videoElement, this.settings, this);
|
const newVideo = new VideoData(videoElement, this.settings, this);
|
||||||
this.videos.push({videoData: newVideo, element: videoElement});
|
this.videos.push({videoData: newVideo, element: videoElement});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
|
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log('info', 'videoRescan', "END VIDEO INITIALIZATION\n\n\n-------------------------------------\nvideos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
|
this.logger.log('info', 'videoRescan', "END VIDEO INITIALIZATION\n\n\n-------------------------------------\nvideos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,24 +254,24 @@ class PageInfo {
|
|||||||
// if we're left without videos on the current page, we unregister the page.
|
// if we're left without videos on the current page, we unregister the page.
|
||||||
// if we have videos, we call register.
|
// if we have videos, we call register.
|
||||||
if (this.comms) {
|
if (this.comms) {
|
||||||
// We used to send "register video" requests only on the first load, or if the number of
|
// We used to send "register video" requests only on the first load, or if the number of
|
||||||
// videos on the page has changed. However, since Chrome Web Store started to require every
|
// videos on the page has changed. However, since Chrome Web Store started to require every
|
||||||
// extension requiring "broad permissions" to undergo manual review
|
// extension requiring "broad permissions" to undergo manual review
|
||||||
// ... and since Chrome Web Store is known for taking their sweet ass time reviewing extensions,
|
// ... and since Chrome Web Store is known for taking their sweet ass time reviewing extensions,
|
||||||
// with review times north of an entire fucking month
|
// with review times north of an entire fucking month
|
||||||
// ... and since the legacy way of checking whether our frames-with-videos cache in background
|
// ... and since the legacy way of checking whether our frames-with-videos cache in background
|
||||||
// script contains any frames that no longer exist required us to use webNavigation.getFrame()/
|
// script contains any frames that no longer exist required us to use webNavigation.getFrame()/
|
||||||
// webNavigation.getAllFrames(), which requires a permission that triggers a review.
|
// webNavigation.getAllFrames(), which requires a permission that triggers a review.
|
||||||
//
|
//
|
||||||
// While the extension uses some other permissions that trigger manual review, it's said that
|
// While the extension uses some other permissions that trigger manual review, it's said that
|
||||||
// less is better / has a positive effect on your manual review times ... So I guess we'll do
|
// less is better / has a positive effect on your manual review times ... So I guess we'll do
|
||||||
// things in the less-than-optimal. more-than-retarded way.
|
// things in the less-than-optimal. more-than-retarded way.
|
||||||
//
|
//
|
||||||
// no but honestly fuck Chrome.
|
// no but honestly fuck Chrome.
|
||||||
|
|
||||||
// if (this.videos.length != oldVideoCount) {
|
// if (this.videos.length != oldVideoCount) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (this.videos.length > 0) {
|
if (this.videos.length > 0) {
|
||||||
// this.comms.registerVideo({host: window.location.hostname, location: window.location});
|
// this.comms.registerVideo({host: window.location.hostname, location: window.location});
|
||||||
this.comms.registerVideo();
|
this.comms.registerVideo();
|
||||||
@ -313,7 +313,7 @@ class PageInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ths = this;
|
let ths = this;
|
||||||
|
|
||||||
this.rescanTimer = setTimeout(function(rescanReason){
|
this.rescanTimer = setTimeout(function(rescanReason){
|
||||||
ths.rescanTimer = null;
|
ths.rescanTimer = null;
|
||||||
ths.rescan(rescanReason);
|
ths.rescan(rescanReason);
|
||||||
@ -331,7 +331,7 @@ class PageInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ths = this;
|
let ths = this;
|
||||||
|
|
||||||
this.urlCheckTimer = setTimeout(function(){
|
this.urlCheckTimer = setTimeout(function(){
|
||||||
ths.urlCheckTimer = null;
|
ths.urlCheckTimer = null;
|
||||||
ths.ghettoUrlCheck();
|
ths.ghettoUrlCheck();
|
||||||
@ -345,7 +345,7 @@ class PageInfo {
|
|||||||
ghettoUrlCheck() {
|
ghettoUrlCheck() {
|
||||||
if (this.lastUrl != window.location.href){
|
if (this.lastUrl != window.location.href){
|
||||||
this.logger.log('error', 'videoRescan', "[PageInfo::ghettoUrlCheck] URL has changed. Triggering a rescan!");
|
this.logger.log('error', 'videoRescan', "[PageInfo::ghettoUrlCheck] URL has changed. Triggering a rescan!");
|
||||||
|
|
||||||
this.rescan(RescanReason.URL_CHANGE);
|
this.rescan(RescanReason.URL_CHANGE);
|
||||||
this.lastUrl = window.location.href;
|
this.lastUrl = window.location.href;
|
||||||
}
|
}
|
||||||
@ -377,7 +377,7 @@ class PageInfo {
|
|||||||
if (vd.videoData.isPlaying()) {
|
if (vd.videoData.isPlaying()) {
|
||||||
vd.videoData.pause();
|
vd.videoData.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for(let vd of this.videos){
|
for(let vd of this.videos){
|
||||||
vd.videoData.pause();
|
vd.videoData.pause();
|
||||||
@ -426,7 +426,7 @@ class PageInfo {
|
|||||||
stopArDetection(playingOnly){
|
stopArDetection(playingOnly){
|
||||||
if (playingOnly) {
|
if (playingOnly) {
|
||||||
for(let vd of this.videos){
|
for(let vd of this.videos){
|
||||||
if (vd.videoData.isPlaying()) {
|
if (vd.videoData.isPlaying()) {
|
||||||
vd.videoData.stopArDetection();
|
vd.videoData.stopArDetection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -474,11 +474,11 @@ class PageInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoAlignment(videoAlignment, playingOnly) {
|
setVideoAlignment(videoAlignment, playingOnly) {
|
||||||
if (playingOnly) {
|
if (playingOnly) {
|
||||||
for(let vd of this.videos) {
|
for(let vd of this.videos) {
|
||||||
if (vd.videoData.isPlaying()) {
|
if (vd.videoData.isPlaying()) {
|
||||||
vd.videoData.setVideoAlignment(videoAlignment)
|
vd.videoData.setVideoAlignment(videoAlignment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -555,7 +555,7 @@ class PageInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
markPlayer(name, color) {
|
markPlayer(name, color) {
|
||||||
for (let vd of this.videos) {
|
for (let vd of this.videos) {
|
||||||
vd.videoData.markPlayer(name,color);
|
vd.videoData.markPlayer(name,color);
|
||||||
}
|
}
|
||||||
@ -604,7 +604,7 @@ class PageInfo {
|
|||||||
setArPersistence(persistenceMode) {
|
setArPersistence(persistenceMode) {
|
||||||
// name of this function is mildly misleading — we don't really _set_ ar persistence. (Ar persistence
|
// name of this function is mildly misleading — we don't really _set_ ar persistence. (Ar persistence
|
||||||
// mode is set and saved via popup or keyboard shortcuts, if user defined them) We just save the current
|
// mode is set and saved via popup or keyboard shortcuts, if user defined them) We just save the current
|
||||||
// aspect ratio whenever aspect ratio persistence mode changes.
|
// aspect ratio whenever aspect ratio persistence mode changes.
|
||||||
if (persistenceMode === CropModePersistence.CurrentSession) {
|
if (persistenceMode === CropModePersistence.CurrentSession) {
|
||||||
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(this.currentCrop));
|
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(this.currentCrop));
|
||||||
} else if (persistenceMode === CropModePersistence.Forever) {
|
} else if (persistenceMode === CropModePersistence.Forever) {
|
||||||
@ -615,7 +615,7 @@ class PageInfo {
|
|||||||
this.settings.active.sites[window.location.hostname] = this.settings.getDefaultOption();
|
this.settings.active.sites[window.location.hostname] = this.settings.getDefaultOption();
|
||||||
this.settings.active.sites[window.location.hostname]['defaultAr'] = this.currentCrop;
|
this.settings.active.sites[window.location.hostname]['defaultAr'] = this.currentCrop;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.settings.saveWithoutReload();
|
this.settings.saveWithoutReload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -632,7 +632,7 @@ class PageInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.defaultCrop = ar;
|
this.defaultCrop = ar;
|
||||||
|
|
||||||
if (cropModePersistence === CropModePersistence.CurrentSession) {
|
if (cropModePersistence === CropModePersistence.CurrentSession) {
|
||||||
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(ar));
|
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(ar));
|
||||||
} else if (cropModePersistence === CropModePersistence.Forever) {
|
} else if (cropModePersistence === CropModePersistence.Forever) {
|
||||||
@ -643,7 +643,7 @@ class PageInfo {
|
|||||||
this.settings.active.sites[window.location.hostname] = this.settings.getDefaultOption();
|
this.settings.active.sites[window.location.hostname] = this.settings.getDefaultOption();
|
||||||
this.settings.active.sites[window.location.hostname]['defaultAr'] = ar;
|
this.settings.active.sites[window.location.hostname]['defaultAr'] = ar;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.settings.saveWithoutReload();
|
this.settings.saveWithoutReload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ class PlayerData {
|
|||||||
// attributeFilter: ['style', 'class'],
|
// attributeFilter: ['style', 'class'],
|
||||||
attributeOldValue: true,
|
attributeOldValue: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.observer.observe(this.element);
|
this.observer.observe(this.element);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("failed to set observer",e )
|
console.error("failed to set observer",e )
|
||||||
@ -361,7 +361,6 @@ class PlayerData {
|
|||||||
element = element.parentNode;
|
element = element.parentNode;
|
||||||
}
|
}
|
||||||
if (element) {
|
if (element) {
|
||||||
this.updatePlayerDimensions(element);
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
} else if (this.settings.active.sites[host]?.DOM?.player?.querySelectors) {
|
} else if (this.settings.active.sites[host]?.DOM?.player?.querySelectors) {
|
||||||
@ -372,7 +371,7 @@ class PlayerData {
|
|||||||
// Let's see how this works
|
// Let's see how this works
|
||||||
if (this.collectionHas(allSelectors, element)) {
|
if (this.collectionHas(allSelectors, element)) {
|
||||||
score = 100; // every matching element gets a baseline 100 points
|
score = 100; // every matching element gets a baseline 100 points
|
||||||
|
|
||||||
// elements that match the size get a hefty bonus
|
// elements that match the size get a hefty bonus
|
||||||
if ( (element.offsetWidth >= videoWidth && this.equalish(element.offsetHeight, videoHeight, 2))
|
if ( (element.offsetWidth >= videoWidth && this.equalish(element.offsetHeight, videoHeight, 2))
|
||||||
|| (element.offsetHeight >= videoHeight && this.equalish(element.offsetWidth, videoHeight, 2))) {
|
|| (element.offsetHeight >= videoHeight && this.equalish(element.offsetWidth, videoHeight, 2))) {
|
||||||
@ -414,12 +413,12 @@ class PlayerData {
|
|||||||
element = element.parentNode;
|
element = element.parentNode;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// element is player, if at least one of the sides is as long as the video
|
// element is player, if at least one of the sides is as long as the video
|
||||||
// note that we can't make any additional assumptions with regards to player
|
// note that we can't make any additional assumptions with regards to player
|
||||||
// size, since there are both cases where the other side is bigger _and_ cases
|
// size, since there are both cases where the other side is bigger _and_ cases
|
||||||
// where other side is smaller than the video.
|
// where other side is smaller than the video.
|
||||||
//
|
//
|
||||||
// Don't bother thinking about this too much, as any "thinking" was quickly
|
// Don't bother thinking about this too much, as any "thinking" was quickly
|
||||||
// corrected by bugs caused by various edge cases.
|
// corrected by bugs caused by various edge cases.
|
||||||
if (
|
if (
|
||||||
@ -439,7 +438,7 @@ class PlayerData {
|
|||||||
score -= scorePenalty * penaltyMultiplier++;
|
score -= scorePenalty * penaltyMultiplier++;
|
||||||
|
|
||||||
// the bigger the size difference between the video and the player,
|
// the bigger the size difference between the video and the player,
|
||||||
// the more penalty we'll incur. Since we did some grace ith
|
// the more penalty we'll incur. Since we did some grace ith
|
||||||
let playerSizePenalty = 1;
|
let playerSizePenalty = 1;
|
||||||
if ( element.offsetHeight > (videoHeight + 5)) {
|
if ( element.offsetHeight > (videoHeight + 5)) {
|
||||||
playerSizePenalty = (element.offsetWidth - videoHeight) * sizePenaltyMultiplier;
|
playerSizePenalty = (element.offsetWidth - videoHeight) * sizePenaltyMultiplier;
|
||||||
@ -455,7 +454,7 @@ class PlayerData {
|
|||||||
score: score,
|
score: score,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
element = element.parentNode;
|
element = element.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,7 +464,7 @@ class PlayerData {
|
|||||||
if (elementQ.length) {
|
if (elementQ.length) {
|
||||||
// return element with biggest score
|
// return element with biggest score
|
||||||
const playerElement = elementQ.sort( (a,b) => b.score - a.score)[0].element;
|
const playerElement = elementQ.sort( (a,b) => b.score - a.score)[0].element;
|
||||||
|
|
||||||
this.updatePlayerDimensions(playerElement);
|
this.updatePlayerDimensions(playerElement);
|
||||||
return playerElement;
|
return playerElement;
|
||||||
}
|
}
|
||||||
@ -487,7 +486,7 @@ class PlayerData {
|
|||||||
forceDetectPlayerElementChange() {
|
forceDetectPlayerElementChange() {
|
||||||
// Player dimension changes get calculated every time updatePlayerDimensions is called (which happens
|
// Player dimension changes get calculated every time updatePlayerDimensions is called (which happens
|
||||||
// every time getPlayer() detects an element). If updatePlayerDimension detects dimensions were changed,
|
// every time getPlayer() detects an element). If updatePlayerDimension detects dimensions were changed,
|
||||||
// it will always re-apply current crop, rendering this function little more than a fancy alias for
|
// it will always re-apply current crop, rendering this function little more than a fancy alias for
|
||||||
// getPlayer().
|
// getPlayer().
|
||||||
this.getPlayer();
|
this.getPlayer();
|
||||||
}
|
}
|
||||||
|
@ -57,10 +57,10 @@ class VideoData {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(video, settings, pageInfo){
|
constructor(video, settings, pageInfo){
|
||||||
(window as any).ultrawidify.addVideo(this);
|
(window as any).ultrawidify.addVideo(this);
|
||||||
|
|
||||||
this.logger = pageInfo.logger;
|
this.logger = pageInfo.logger;
|
||||||
this.arSetupComplete = false;
|
this.arSetupComplete = false;
|
||||||
this.video = video;
|
this.video = video;
|
||||||
@ -87,15 +87,15 @@ class VideoData {
|
|||||||
|
|
||||||
async onVideoLoaded() {
|
async onVideoLoaded() {
|
||||||
if (!this.videoLoaded) {
|
if (!this.videoLoaded) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* video.readyState 101:
|
* video.readyState 101:
|
||||||
* 0 — no info. Can't play.
|
* 0 — no info. Can't play.
|
||||||
* 1 — we have metadata but nothing else
|
* 1 — we have metadata but nothing else
|
||||||
* 2 — we have data for current playback position, but not future <--- meaning current frame, meaning Aard can work here or higher
|
* 2 — we have data for current playback position, but not future <--- meaning current frame, meaning Aard can work here or higher
|
||||||
* 3 — we have a lil bit for the future
|
* 3 — we have a lil bit for the future
|
||||||
* 4 — we'll survive to the end
|
* 4 — we'll survive to the end
|
||||||
*/
|
*/
|
||||||
if (!this.video?.videoWidth || !this.video?.videoHeight || this.video.readyState < 2) {
|
if (!this.video?.videoWidth || !this.video?.videoHeight || this.video.readyState < 2) {
|
||||||
return; // onVideoLoaded is a lie in this case
|
return; // onVideoLoaded is a lie in this case
|
||||||
}
|
}
|
||||||
@ -199,7 +199,7 @@ class VideoData {
|
|||||||
// INIT OBSERVERS
|
// INIT OBSERVERS
|
||||||
try {
|
try {
|
||||||
if (BrowserDetect.firefox) {
|
if (BrowserDetect.firefox) {
|
||||||
this.observer = new ResizeObserver(
|
this.observer = new ResizeObserver(
|
||||||
_.debounce(
|
_.debounce(
|
||||||
this.onVideoDimensionsChanged,
|
this.onVideoDimensionsChanged,
|
||||||
250,
|
250,
|
||||||
@ -212,11 +212,11 @@ class VideoData {
|
|||||||
} else {
|
} else {
|
||||||
// Chrome for some reason insists that this.onPlayerDimensionsChanged is not a function
|
// Chrome for some reason insists that this.onPlayerDimensionsChanged is not a function
|
||||||
// when it's not wrapped into an anonymous function
|
// when it's not wrapped into an anonymous function
|
||||||
this.observer = new ResizeObserver(
|
this.observer = new ResizeObserver(
|
||||||
_.debounce(
|
_.debounce(
|
||||||
(m, o) => {
|
(m, o) => {
|
||||||
this.onVideoDimensionsChanged(m, o)
|
this.onVideoDimensionsChanged(m, o)
|
||||||
},
|
},
|
||||||
250,
|
250,
|
||||||
{
|
{
|
||||||
leading: true,
|
leading: true,
|
||||||
@ -252,7 +252,7 @@ class VideoData {
|
|||||||
// start fallback video/player size detection
|
// start fallback video/player size detection
|
||||||
this.fallbackChangeDetection();
|
this.fallbackChangeDetection();
|
||||||
|
|
||||||
// force reload last aspect ratio (if default crop ratio exists), but only after the video is
|
// force reload last aspect ratio (if default crop ratio exists), but only after the video is
|
||||||
if (this.pageInfo.defaultCrop) {
|
if (this.pageInfo.defaultCrop) {
|
||||||
this.resizer.setAr(this.pageInfo.defaultCrop);
|
this.resizer.setAr(this.pageInfo.defaultCrop);
|
||||||
}
|
}
|
||||||
@ -315,7 +315,7 @@ class VideoData {
|
|||||||
|
|
||||||
if (this.video) {
|
if (this.video) {
|
||||||
this.video.classList.remove(this.userCssClassName);
|
this.video.classList.remove(this.userCssClassName);
|
||||||
this.video.classList.remove('uw-ultrawidify-base-wide-screen');
|
this.video.classList.remove('uw-ultrawidify-base-wide-screen');
|
||||||
|
|
||||||
this.video.removeEventListener('onloadeddata', this.onLoadedData);
|
this.video.removeEventListener('onloadeddata', this.onLoadedData);
|
||||||
this.video.removeEventListener('onloadedmetadata', this.onLoadedMetadata);
|
this.video.removeEventListener('onloadedmetadata', this.onLoadedMetadata);
|
||||||
@ -354,14 +354,14 @@ class VideoData {
|
|||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
restoreCrop() {
|
restoreCrop() {
|
||||||
this.logger.log('info', 'debug', '[VideoData::restoreCrop] Attempting to reset aspect ratio.')
|
this.logger.log('info', 'debug', '[VideoData::restoreCrop] Attempting to reset aspect ratio.')
|
||||||
// if we have default crop set for this page, apply this.
|
// if we have default crop set for this page, apply this.
|
||||||
// otherwise, reset crop
|
// otherwise, reset crop
|
||||||
if (this.pageInfo.defaultCrop) {
|
if (this.pageInfo.defaultCrop) {
|
||||||
this.resizer.setAr(this.pageInfo.defaultCrop);
|
this.resizer.setAr(this.pageInfo.defaultCrop);
|
||||||
} else {
|
} else {
|
||||||
this.resizer.reset();
|
this.resizer.reset();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.stopArDetection();
|
this.stopArDetection();
|
||||||
@ -500,7 +500,7 @@ class VideoData {
|
|||||||
this.player.forceDetectPlayerElementChange();
|
this.player.forceDetectPlayerElementChange();
|
||||||
this.restoreAr();
|
this.restoreAr();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error('Validating video offsets failed:', e)
|
console.error('Validating video offsets failed:', e)
|
||||||
}
|
}
|
||||||
@ -518,7 +518,7 @@ class VideoData {
|
|||||||
// This will _always_ give us an array. Empty string gives an array
|
// This will _always_ give us an array. Empty string gives an array
|
||||||
// that contains one element. That element is an empty string.
|
// that contains one element. That element is an empty string.
|
||||||
const styleArray = (this.video.getAttribute('style') || '').split(';');
|
const styleArray = (this.video.getAttribute('style') || '').split(';');
|
||||||
|
|
||||||
const styleObject = {};
|
const styleObject = {};
|
||||||
|
|
||||||
for (const style of styleArray) {
|
for (const style of styleArray) {
|
||||||
@ -537,10 +537,10 @@ class VideoData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some sites try to accomodate ultrawide users by "cropping" videos
|
* Some sites try to accommodate ultrawide users by "cropping" videos
|
||||||
* by setting 'style' attribute of the video element to 'height: X%',
|
* by setting 'style' attribute of the video element to 'height: X%',
|
||||||
* where 'X' is something greater than 100.
|
* where 'X' is something greater than 100.
|
||||||
*
|
*
|
||||||
* This function gets that percentage and converts it into a factor.
|
* This function gets that percentage and converts it into a factor.
|
||||||
*/
|
*/
|
||||||
getHeightCompensationFactor() {
|
getHeightCompensationFactor() {
|
||||||
@ -579,7 +579,7 @@ class VideoData {
|
|||||||
this.arDetector.init();
|
this.arDetector.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startArDetection() {
|
startArDetection() {
|
||||||
this.logger.log('info', 'debug', "[VideoData::startArDetection] starting AR detection")
|
this.logger.log('info', 'debug', "[VideoData::startArDetection] starting AR detection")
|
||||||
if(this.destroyed || this.invalid) {
|
if(this.destroyed || this.invalid) {
|
||||||
@ -670,7 +670,7 @@ class VideoData {
|
|||||||
if (this.invalid) {
|
if (this.invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ar.type === AspectRatioType.Fixed || ar.type === AspectRatioType.FitHeight || ar.type === AspectRatioType.FitHeight) {
|
if (ar.type === AspectRatioType.Fixed || ar.type === AspectRatioType.FitHeight || ar.type === AspectRatioType.FitHeight) {
|
||||||
this.player.forceRefreshPlayerElement();
|
this.player.forceRefreshPlayerElement();
|
||||||
}
|
}
|
||||||
@ -788,7 +788,7 @@ class VideoData {
|
|||||||
this.logger.log('info', 'debug', "[VideoDetect] player size changed. reason: dimension change. Old dimensions?", this.dimensions.width, this.dimensions.height, "new dimensions:", this.video.offsetWidth, this.video.offsetHeight);
|
this.logger.log('info', 'debug', "[VideoDetect] player size changed. reason: dimension change. Old dimensions?", this.dimensions.width, this.dimensions.height, "new dimensions:", this.video.offsetWidth, this.video.offsetHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if size doesn't match, update & return true
|
// if size doesn't match, update & return true
|
||||||
if (this.dimensions?.width != videoWidth
|
if (this.dimensions?.width != videoWidth
|
||||||
|| this.dimensions?.height != videoHeight ){
|
|| this.dimensions?.height != videoHeight ){
|
||||||
|
@ -59,7 +59,7 @@ class Resizer {
|
|||||||
this.settings = videoData.settings;
|
this.settings = videoData.settings;
|
||||||
|
|
||||||
this.scaler = new Scaler(this.conf);
|
this.scaler = new Scaler(this.conf);
|
||||||
this.stretcher = new Stretcher(this.conf);
|
this.stretcher = new Stretcher(this.conf);
|
||||||
this.zoom = new Zoom(this.conf);
|
this.zoom = new Zoom(this.conf);
|
||||||
|
|
||||||
this.videoAlignment = this.settings.getDefaultVideoAlignment(window.location.hostname); // this is initial video alignment
|
this.videoAlignment = this.settings.getDefaultVideoAlignment(window.location.hostname); // this is initial video alignment
|
||||||
@ -72,17 +72,17 @@ class Resizer {
|
|||||||
this.canPan = false;
|
this.canPan = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userCssClassName = videoData.userCssClassName;
|
this.userCssClassName = videoData.userCssClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
injectCss(css) {
|
injectCss(css) {
|
||||||
this.conf.pageInfo.injectCss(css);
|
this.conf.pageInfo.injectCss(css);
|
||||||
}
|
}
|
||||||
|
|
||||||
ejectCss(css) {
|
ejectCss(css) {
|
||||||
this.conf.pageInfo.ejectCss(css);
|
this.conf.pageInfo.ejectCss(css);
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceCss(oldCss, newCss) {
|
replaceCss(oldCss, newCss) {
|
||||||
this.conf.pageInfo.replaceCss(oldCss, newCss);
|
this.conf.pageInfo.replaceCss(oldCss, newCss);
|
||||||
}
|
}
|
||||||
@ -101,7 +101,7 @@ class Resizer {
|
|||||||
if (ar.type !== AspectRatioType.FitWidth && ar.type !== AspectRatioType.FitHeight && ar.ratio) {
|
if (ar.type !== AspectRatioType.FitWidth && ar.type !== AspectRatioType.FitHeight && ar.ratio) {
|
||||||
return ar;
|
return ar;
|
||||||
}
|
}
|
||||||
// Skrbi za "stare" možnosti, kot na primer "na širino zaslona", "na višino zaslona" in "ponastavi".
|
// Skrbi za "stare" možnosti, kot na primer "na širino zaslona", "na višino zaslona" in "ponastavi".
|
||||||
// Približevanje opuščeno.
|
// Približevanje opuščeno.
|
||||||
// handles "legacy" options, such as 'fit to widht', 'fit to height' and AspectRatioType.Reset. No zoom tho
|
// handles "legacy" options, such as 'fit to widht', 'fit to height' and AspectRatioType.Reset. No zoom tho
|
||||||
let ratioOut;
|
let ratioOut;
|
||||||
@ -112,22 +112,22 @@ class Resizer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (! this.conf.player.dimensions) {
|
if (! this.conf.player.dimensions) {
|
||||||
ratioOut = screen.width / screen.height;
|
ratioOut = screen.width / screen.height;
|
||||||
} else {
|
} else {
|
||||||
this.logger.log('info', 'debug', `[Resizer::calculateRatioForLegacyOptions] <rid:${this.resizerId}> 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)
|
this.logger.log('info', 'debug', `[Resizer::calculateRatioForLegacyOptions] <rid:${this.resizerId}> 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;
|
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,
|
// 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.
|
// 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
|
// 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).
|
// setting a static aspect ratio (even if the function is called from here or ArDetect).
|
||||||
|
|
||||||
let fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
let fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||||
|
|
||||||
if (ar.type === AspectRatioType.FitWidth){
|
if (ar.type === AspectRatioType.FitWidth){
|
||||||
ar.ratio = ratioOut > fileAr ? ratioOut : fileAr;
|
ar.ratio = ratioOut > fileAr ? ratioOut : fileAr;
|
||||||
}
|
}
|
||||||
@ -168,12 +168,12 @@ class Resizer {
|
|||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.video.videoWidth || !this.video.videoHeight) {
|
if (!this.video.videoWidth || !this.video.videoHeight) {
|
||||||
this.logger.log('warning', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> Video has no width or no height. This is not allowed. Aspect ratio will not be set, and videoData will be uninitialized.');
|
this.logger.log('warning', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> Video has no width or no height. This is not allowed. Aspect ratio will not be set, and videoData will be uninitialized.');
|
||||||
this.conf.videoUnloaded();
|
this.conf.videoUnloaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log('info', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> trying to set ar. New ar:', ar);
|
this.logger.log('info', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> trying to set ar. New ar:', ar);
|
||||||
|
|
||||||
if (ar == null) {
|
if (ar == null) {
|
||||||
@ -198,10 +198,10 @@ class Resizer {
|
|||||||
// this means here's the optimal place to set or forget aspect ratio. Saving of current crop ratio
|
// 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 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.
|
// is set to persist between videos / through current session / until manual reset.
|
||||||
if (ar.type === AspectRatioType.Automatic ||
|
if (ar.type === AspectRatioType.Automatic ||
|
||||||
ar.type === AspectRatioType.Reset ||
|
ar.type === AspectRatioType.Reset ||
|
||||||
ar.type === AspectRatioType.Initial ) {
|
ar.type === AspectRatioType.Initial ) {
|
||||||
// reset/undo default
|
// reset/undo default
|
||||||
this.conf.pageInfo.updateCurrentCrop(undefined);
|
this.conf.pageInfo.updateCurrentCrop(undefined);
|
||||||
} else {
|
} else {
|
||||||
this.conf.pageInfo.updateCurrentCrop(ar);
|
this.conf.pageInfo.updateCurrentCrop(ar);
|
||||||
@ -224,7 +224,7 @@ class Resizer {
|
|||||||
// if (this.extensionMode === ExtensionMode.Basic && !PlayerData.isFullScreen() && ar.type !== AspectRatioType.Reset) {
|
// if (this.extensionMode === ExtensionMode.Basic && !PlayerData.isFullScreen() && ar.type !== AspectRatioType.Reset) {
|
||||||
// // don't actually apply or calculate css when using basic mode if not in fullscreen
|
// // don't actually apply or calculate css when using basic mode if not in fullscreen
|
||||||
// // ... unless we're resetting the aspect ratio to original
|
// // ... unless we're resetting the aspect ratio to original
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (! this.video) {
|
if (! this.video) {
|
||||||
@ -234,7 +234,7 @@ class Resizer {
|
|||||||
// pause AR on:
|
// pause AR on:
|
||||||
// * ar.type NOT automatic
|
// * ar.type NOT automatic
|
||||||
// * ar.type is auto, but stretch is set to basic basic stretch
|
// * ar.type is auto, but stretch is set to basic basic stretch
|
||||||
//
|
//
|
||||||
// unpause when using other modes
|
// unpause when using other modes
|
||||||
if (ar.type !== AspectRatioType.Automatic || this.stretcher.mode === StretchType.Basic) {
|
if (ar.type !== AspectRatioType.Automatic || this.stretcher.mode === StretchType.Basic) {
|
||||||
this.conf?.arDetector?.pause();
|
this.conf?.arDetector?.pause();
|
||||||
@ -245,10 +245,10 @@ class Resizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do stretch thingy
|
// do stretch thingy
|
||||||
if (this.stretcher.mode === StretchType.NoStretch
|
if (this.stretcher.mode === StretchType.NoStretch
|
||||||
|| this.stretcher.mode === StretchType.Conditional
|
|| this.stretcher.mode === StretchType.Conditional
|
||||||
|| this.stretcher.mode === StretchType.FixedSource){
|
|| this.stretcher.mode === StretchType.FixedSource){
|
||||||
|
|
||||||
stretchFactors = this.scaler.calculateCrop(ar);
|
stretchFactors = this.scaler.calculateCrop(ar);
|
||||||
|
|
||||||
if(! stretchFactors || stretchFactors.error){
|
if(! stretchFactors || stretchFactors.error){
|
||||||
@ -274,7 +274,7 @@ class Resizer {
|
|||||||
this.stretcher.applyStretchFixedSource(stretchFactors);
|
this.stretcher.applyStretchFixedSource(stretchFactors);
|
||||||
}
|
}
|
||||||
this.logger.log('info', 'debug', "[Resizer::setAr] Processed stretch factors for ",
|
this.logger.log('info', 'debug', "[Resizer::setAr] Processed stretch factors for ",
|
||||||
this.stretcher.mode === StretchType.NoStretch ? 'stretch-free crop.' :
|
this.stretcher.mode === StretchType.NoStretch ? 'stretch-free crop.' :
|
||||||
this.stretcher.mode === StretchType.Conditional ? 'crop with conditional StretchType.' : 'crop with fixed stretch',
|
this.stretcher.mode === StretchType.Conditional ? 'crop with conditional StretchType.' : 'crop with fixed stretch',
|
||||||
'Stretch factors are:', stretchFactors
|
'Stretch factors are:', stretchFactors
|
||||||
);
|
);
|
||||||
@ -343,7 +343,7 @@ class Resizer {
|
|||||||
|
|
||||||
const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth;
|
const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth;
|
||||||
const relativeY = (event.pageY - player.offsetTop) / player.offsetHeight;
|
const relativeY = (event.pageY - player.offsetTop) / player.offsetHeight;
|
||||||
|
|
||||||
this.logger.log('info', 'mousemove', "[Resizer::panHandler] mousemove.pageX, pageY:", event.pageX, event.pageY, "\nrelativeX/Y:", relativeX, relativeY)
|
this.logger.log('info', 'mousemove', "[Resizer::panHandler] mousemove.pageX, pageY:", event.pageX, event.pageY, "\nrelativeX/Y:", relativeX, relativeY)
|
||||||
|
|
||||||
this.setPan(relativeX, relativeY);
|
this.setPan(relativeX, relativeY);
|
||||||
@ -356,7 +356,7 @@ class Resizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setPan(relativeMousePosX, relativeMousePosY){
|
setPan(relativeMousePosX, relativeMousePosY){
|
||||||
// relativeMousePos[X|Y] - on scale from 0 to 1, how close is the mouse to player edges.
|
// relativeMousePos[X|Y] - on scale from 0 to 1, how close is the mouse to player edges.
|
||||||
// use these values: top, left: 0, bottom, right: 1
|
// use these values: top, left: 0, bottom, right: 1
|
||||||
if(! this.pan){
|
if(! this.pan){
|
||||||
this.pan = {x: 0, y: 0};
|
this.pan = {x: 0, y: 0};
|
||||||
@ -379,7 +379,7 @@ class Resizer {
|
|||||||
|
|
||||||
restore() {
|
restore() {
|
||||||
this.logger.log('info', 'debug', "[Resizer::restore] <rid:"+this.resizerId+"> attempting to restore aspect ratio", {'lastAr': this.lastAr} );
|
this.logger.log('info', 'debug', "[Resizer::restore] <rid:"+this.resizerId+"> attempting to restore aspect ratio", {'lastAr': this.lastAr} );
|
||||||
|
|
||||||
// this is true until we verify that css has actually been applied
|
// this is true until we verify that css has actually been applied
|
||||||
if(this.lastAr.type === AspectRatioType.Initial){
|
if(this.lastAr.type === AspectRatioType.Initial){
|
||||||
this.setAr({type: AspectRatioType.Reset});
|
this.setAr({type: AspectRatioType.Reset});
|
||||||
@ -414,7 +414,7 @@ class Resizer {
|
|||||||
setZoom(zoomLevel, no_announce) {
|
setZoom(zoomLevel, no_announce) {
|
||||||
this.zoom.setZoom(zoomLevel, no_announce);
|
this.zoom.setZoom(zoomLevel, no_announce);
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomStep(step){
|
zoomStep(step){
|
||||||
this.zoom.zoomStep(step);
|
this.zoom.zoomStep(step);
|
||||||
}
|
}
|
||||||
@ -439,23 +439,23 @@ class Resizer {
|
|||||||
/**
|
/**
|
||||||
* Returns the size of the video file _as displayed_ on the screen.
|
* Returns the size of the video file _as displayed_ on the screen.
|
||||||
* Consider the following example:
|
* Consider the following example:
|
||||||
*
|
*
|
||||||
* * player dimensions are 2560x1080
|
* * player dimensions are 2560x1080
|
||||||
* * <video> is child of player
|
* * <video> is child of player
|
||||||
* * <video> has the following css: {width: 100%, height: 100%}
|
* * <video> has the following css: {width: 100%, height: 100%}
|
||||||
* * video file dimensions are 1280x720
|
* * video file dimensions are 1280x720
|
||||||
*
|
*
|
||||||
* CSS will ensure that the dimensions of <video> tag are equal to the dimension of the
|
* CSS will ensure that the dimensions of <video> tag are equal to the dimension of the
|
||||||
* player element — that is, 2560x1080px. This is no bueno, because the browser will upscale
|
* player element — that is, 2560x1080px. This is no bueno, because the browser will upscale
|
||||||
* the video file to take up as much space as it can (without stretching it). This means
|
* the video file to take up as much space as it can (without stretching it). This means
|
||||||
* we'll get a 1920x1080 video (as displayed) and a letterbox.
|
* we'll get a 1920x1080 video (as displayed) and a letterbox.
|
||||||
*
|
*
|
||||||
* We can't get that number out of anywhere: video.videoWidth will return 1280 (video file
|
* We can't get that number out of anywhere: video.videoWidth will return 1280 (video file
|
||||||
* dimensions) and .offsetWidth (and the likes) will return the <video> tag dimension. Neither
|
* dimensions) and .offsetWidth (and the likes) will return the <video> tag dimension. Neither
|
||||||
* will return the actual size of video as displayed, which we need in order to calculate the
|
* will return the actual size of video as displayed, which we need in order to calculate the
|
||||||
* extra space to the left and right of the video.
|
* extra space to the left and right of the video.
|
||||||
*
|
*
|
||||||
* We make the assumption of the
|
* We make the assumption of the
|
||||||
*/
|
*/
|
||||||
computeVideoDisplayedDimensions() {
|
computeVideoDisplayedDimensions() {
|
||||||
const offsetWidth = this.conf.video.offsetWidth;
|
const offsetWidth = this.conf.video.offsetWidth;
|
||||||
@ -464,7 +464,7 @@ class Resizer {
|
|||||||
const scaleX = offsetWidth / this.conf.video.videoWidth;
|
const scaleX = offsetWidth / this.conf.video.videoWidth;
|
||||||
const scaleY = offsetHeight / this.conf.video.videoHeight;
|
const scaleY = offsetHeight / this.conf.video.videoHeight;
|
||||||
|
|
||||||
// if differences between the scale factors are minimal, we presume offsetWidth and
|
// if differences between the scale factors are minimal, we presume offsetWidth and
|
||||||
// offsetHeight are the accurate enough for our needs
|
// offsetHeight are the accurate enough for our needs
|
||||||
if (Math.abs(scaleX - scaleY) < 0.02) {
|
if (Math.abs(scaleX - scaleY) < 0.02) {
|
||||||
return {
|
return {
|
||||||
@ -505,19 +505,19 @@ class Resizer {
|
|||||||
* and let the browser figure out the height through the magic of height:auto. This is bad,
|
* and let the browser figure out the height through the magic of height:auto. This is bad,
|
||||||
* because our addon generally relies of videos always being 100% of the height of the
|
* because our addon generally relies of videos always being 100% of the height of the
|
||||||
* container.
|
* container.
|
||||||
*
|
*
|
||||||
* This sometimes leads to a situation where realVideoHeight and realVideoWidth — at least
|
* This sometimes leads to a situation where realVideoHeight and realVideoWidth — at least
|
||||||
* one of which should be roughly equal to the player width or hight with the other one being
|
* one of which should be roughly equal to the player width or hight with the other one being
|
||||||
* either smaller or equal — are both smaller than player width or height; and sometimes
|
* either smaller or equal — are both smaller than player width or height; and sometimes
|
||||||
* rather substantially. Fortunately for us, realVideo[Width|Height] and player dimensions
|
* rather substantially. Fortunately for us, realVideo[Width|Height] and player dimensions
|
||||||
* never lie, which allows us to calculate the extra scale factor we need.
|
* never lie, which allows us to calculate the extra scale factor we need.
|
||||||
*
|
*
|
||||||
* Returned factor for this function should do fit:contain, not fit:cover.
|
* Returned factor for this function should do fit:contain, not fit:cover.
|
||||||
* @param realVideoWidth real video width
|
* @param realVideoWidth real video width
|
||||||
* @param realVideoHeight real video height
|
* @param realVideoHeight real video height
|
||||||
* @param playerWidth player width
|
* @param playerWidth player width
|
||||||
* @param playerHeight player height
|
* @param playerHeight player height
|
||||||
* @param mode whether to
|
* @param mode whether to
|
||||||
*/
|
*/
|
||||||
computeAutoHeightCompensationFactor(realVideoWidth: number, realVideoHeight: number, playerWidth: number, playerHeight: number, mode: 'height' | 'width'): number {
|
computeAutoHeightCompensationFactor(realVideoWidth: number, realVideoHeight: number, playerWidth: number, playerHeight: number, mode: 'height' | 'width'): number {
|
||||||
const widthFactor = playerWidth / realVideoWidth;
|
const widthFactor = playerWidth / realVideoWidth;
|
||||||
@ -538,7 +538,7 @@ class Resizer {
|
|||||||
// We also don't compensate for height:auto if height is provided via element style
|
// We also don't compensate for height:auto if height is provided via element style
|
||||||
let autoHeightCompensationFactor;
|
let autoHeightCompensationFactor;
|
||||||
if (
|
if (
|
||||||
stretchFactors.cropStrategy === CropStrategy.CropLetterbox
|
stretchFactors.cropStrategy === CropStrategy.CropLetterbox
|
||||||
&& (!stretchFactors.styleHeightCompensationFactor || stretchFactors.styleHeightCompensationFactor === 1)
|
&& (!stretchFactors.styleHeightCompensationFactor || stretchFactors.styleHeightCompensationFactor === 1)
|
||||||
) {
|
) {
|
||||||
autoHeightCompensationFactor = this.computeAutoHeightCompensationFactor(realVideoWidth, realVideoHeight, this.conf.player.dimensions.width, this.conf.player.dimensions.height, 'height');
|
autoHeightCompensationFactor = this.computeAutoHeightCompensationFactor(realVideoWidth, realVideoHeight, this.conf.player.dimensions.width, this.conf.player.dimensions.height, 'height');
|
||||||
@ -551,14 +551,14 @@ class Resizer {
|
|||||||
|
|
||||||
const wdiffAfterZoom = realVideoWidth * stretchFactors.xFactor - this.conf.player.dimensions.width;
|
const wdiffAfterZoom = realVideoWidth * stretchFactors.xFactor - this.conf.player.dimensions.width;
|
||||||
const hdiffAfterZoom = realVideoHeight * stretchFactors.yFactor - this.conf.player.dimensions.height;
|
const hdiffAfterZoom = realVideoHeight * stretchFactors.yFactor - this.conf.player.dimensions.height;
|
||||||
|
|
||||||
const translate = {
|
const translate = {
|
||||||
x: wdiff * 0.5,
|
x: wdiff * 0.5,
|
||||||
y: hdiff * 0.5,
|
y: hdiff * 0.5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (this.pan.relativeOffsetX || this.pan.relativeOffsetY) {
|
if (this.pan.relativeOffsetX || this.pan.relativeOffsetY) {
|
||||||
// don't offset when video is smaller than player
|
// don't offset when video is smaller than player
|
||||||
@ -574,7 +574,7 @@ class Resizer {
|
|||||||
translate.x -= wdiffAfterZoom * 0.5;
|
translate.x -= wdiffAfterZoom * 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
'info', ['debug', 'resizer'], "[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:",
|
'info', ['debug', 'resizer'], "[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:",
|
||||||
'\n\n---- elements ----',
|
'\n\n---- elements ----',
|
||||||
@ -588,21 +588,21 @@ class Resizer {
|
|||||||
'\nstretch factors: ', stretchFactors,
|
'\nstretch factors: ', stretchFactors,
|
||||||
'\npan & zoom: ', this.pan, this.zoom.scale,
|
'\npan & zoom: ', this.pan, this.zoom.scale,
|
||||||
'\nwdiff, hdiff: ', wdiff, 'x', hdiff,
|
'\nwdiff, hdiff: ', wdiff, 'x', hdiff,
|
||||||
'\nwdiff, hdiffAfterZoom:', wdiffAfterZoom, 'x', hdiffAfterZoom,
|
'\nwdiff, hdiffAfterZoom:', wdiffAfterZoom, 'x', hdiffAfterZoom,
|
||||||
'\n\n---- data out ----\n',
|
'\n\n---- data out ----\n',
|
||||||
'translate:', translate
|
'translate:', translate
|
||||||
);
|
);
|
||||||
|
|
||||||
// by the way, let's do a quick sanity check whether video player is doing any fuckies wuckies
|
// by the way, let's do a quick sanity check whether video player is doing any fuckies wuckies
|
||||||
// fucky wucky examples:
|
// fucky wucky examples:
|
||||||
//
|
//
|
||||||
// * video width is bigger than player width AND video height is bigger than player height
|
// * video width is bigger than player width AND video height is bigger than player height
|
||||||
// * video width is smaller than player width AND video height is smaller than player height
|
// * video width is smaller than player width AND video height is smaller than player height
|
||||||
//
|
//
|
||||||
// In both examples, at most one of the two conditions can be true at the same time. If both
|
// In both examples, at most one of the two conditions can be true at the same time. If both
|
||||||
// conditions are true at the same time, we need to go 'chiny reckon' and recheck our player
|
// conditions are true at the same time, we need to go 'chiny reckon' and recheck our player
|
||||||
// element. Chances are our video is not getting aligned correctly
|
// element. Chances are our video is not getting aligned correctly
|
||||||
if (
|
if (
|
||||||
(this.conf.video.offsetWidth > this.conf.player.dimensions.width && this.conf.video.offsetHeight > this.conf.player.dimensions.height) ||
|
(this.conf.video.offsetWidth > this.conf.player.dimensions.width && this.conf.video.offsetHeight > this.conf.player.dimensions.height) ||
|
||||||
(this.conf.video.offsetWidth < this.conf.player.dimensions.width && this.conf.video.offsetHeight < this.conf.player.dimensions.height)
|
(this.conf.video.offsetWidth < this.conf.player.dimensions.width && this.conf.video.offsetHeight < this.conf.player.dimensions.height)
|
||||||
) {
|
) {
|
||||||
@ -622,9 +622,9 @@ class Resizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return translate;
|
return translate;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region css handling
|
//#region css handling
|
||||||
buildStyleArray(existingStyleString, extraStyleString) {
|
buildStyleArray(existingStyleString, extraStyleString) {
|
||||||
if (existingStyleString) {
|
if (existingStyleString) {
|
||||||
@ -648,15 +648,15 @@ class Resizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i in styleArray) {
|
for (let i in styleArray) {
|
||||||
styleArray[i] = styleArray[i].trim();
|
styleArray[i] = styleArray[i].trim();
|
||||||
// some sites do 'top: 50%; left: 50%; transform: <transform>' to center videos.
|
// some sites do 'top: 50%; left: 50%; transform: <transform>' to center videos.
|
||||||
// we dont wanna, because we already center videos on our own
|
// we dont wanna, because we already center videos on our own
|
||||||
if (styleArray[i].startsWith("transform:")
|
if (styleArray[i].startsWith("transform:")
|
||||||
|| styleArray[i].startsWith("top:")
|
|| styleArray[i].startsWith("top:")
|
||||||
|| styleArray[i].startsWith("left:")
|
|| styleArray[i].startsWith("left:")
|
||||||
|| styleArray[i].startsWith("right:")
|
|| styleArray[i].startsWith("right:")
|
||||||
|| styleArray[i].startsWith("bottom:")
|
|| styleArray[i].startsWith("bottom:")
|
||||||
|| styleArray[i].startsWith("margin")
|
|| styleArray[i].startsWith("margin")
|
||||||
){
|
){
|
||||||
@ -682,7 +682,7 @@ class Resizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyCss(stretchFactors, translate){
|
applyCss(stretchFactors, translate){
|
||||||
// apply extra CSS here. In case of duplicated properties, extraCss overrides
|
// apply extra CSS here. In case of duplicated properties, extraCss overrides
|
||||||
// default styleString
|
// default styleString
|
||||||
if (! this.video) {
|
if (! this.video) {
|
||||||
this.logger.log('warn', 'debug', "[Resizer::applyCss] <rid:"+this.resizerId+"> Video went missing, doing nothing.");
|
this.logger.log('warn', 'debug', "[Resizer::applyCss] <rid:"+this.resizerId+"> Video went missing, doing nothing.");
|
||||||
@ -692,7 +692,7 @@ class Resizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log('info', ['debug', 'resizer'], "[Resizer::applyCss] <rid:"+this.resizerId+"> will apply css.", {stretchFactors, translate});
|
this.logger.log('info', ['debug', 'resizer'], "[Resizer::applyCss] <rid:"+this.resizerId+"> will apply css.", {stretchFactors, translate});
|
||||||
|
|
||||||
// save stuff for quick tests (before we turn numbers into css values):
|
// save stuff for quick tests (before we turn numbers into css values):
|
||||||
this.currentVideoSettings = {
|
this.currentVideoSettings = {
|
||||||
validFor: this.conf.player.dimensions,
|
validFor: this.conf.player.dimensions,
|
||||||
@ -716,7 +716,7 @@ class Resizer {
|
|||||||
// important — guarantees video will be properly aligned
|
// important — guarantees video will be properly aligned
|
||||||
// Note that position:absolute cannot be put here, otherwise old.reddit /w RES breaks — videos embedded
|
// Note that position:absolute cannot be put here, otherwise old.reddit /w RES breaks — videos embedded
|
||||||
// from certain hosts will get a height: 0px. This is bad.
|
// from certain hosts will get a height: 0px. This is bad.
|
||||||
styleArray.push("top: 0px !important; left: 0px !important; bottom: 0px !important; right: 0px;");
|
styleArray.push("top: 0px !important; left: 0px !important; bottom: 0px !important; right: 0px;");
|
||||||
|
|
||||||
// important — some websites (cough reddit redesign cough) may impose some dumb max-width and max-height
|
// important — some websites (cough reddit redesign cough) may impose some dumb max-width and max-height
|
||||||
// restrictions. If site has dumb shit like 'max-width: 100%' and 'max-height: 100vh' in their CSS, that
|
// restrictions. If site has dumb shit like 'max-width: 100%' and 'max-height: 100vh' in their CSS, that
|
||||||
|
Loading…
Reference in New Issue
Block a user