Compare commits

...

9 Commits

29 changed files with 576 additions and 440 deletions

View File

@ -4,7 +4,8 @@
### v3.4.0 ### v3.4.0
* In-player UI now appears in full-screen even for websites that use top layer * In-player UI now appears in full-screen even for websites that use top layer
* Embedded sites now inherit settings of the parent frame * Embedded sites now inherit settings of the parent frame by default
* Added validation to custom aspect ratio entry menu. Corrected parsing of aspect ratios given in the X:Y format, even though aspect ratios should be ideally given as a single number.
### v6.3.0 ### v6.3.0
* Added zoom segment to in-player UI and popup. * Added zoom segment to in-player UI and popup.

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "ultrawidify", "name": "ultrawidify",
"version": "6.3.9", "version": "6.3.93",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "ultrawidify", "name": "ultrawidify",
"version": "6.3.9", "version": "6.3.93",
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.", "description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
"author": "Tamius Han <tamius.han@gmail.com>", "author": "Tamius Han <tamius.han@gmail.com>",
"scripts": { "scripts": {

View File

@ -5,11 +5,27 @@
# any other way probably isn't going to work. # any other way probably isn't going to work.
# pre-build steps: # pre-build steps:
echo "—————————————————————————————————————————————————————";
echo " BUILDING ALL ULTRAWIDIFY VERSIONS";
echo "—————————————————————————————————————————————————————";
mkdir -p ./build/old mkdir -p ./build/old
npm run pre-build npm run pre-build
echo "::"
echo "::"
echo "::"
echo ":: Pre-build complete. Moving old builds from dist-zip ..."
rm ./dist-zip/uw-amo-source.zip rm ./dist-zip/uw-amo-source.zip
mv -f ./dist-zip/*.zip ./build/old mv -f ./dist-zip/*.zip ./build/old
echo "::"
echo "::"
echo "::"
echo ":: dist-zip is clean. Starting builds ..."
# lets force raise ram limit, but the improper way # lets force raise ram limit, but the improper way
# export NODE_OPTIONS=--max_old_space_size=4096 # export NODE_OPTIONS=--max_old_space_size=4096

View File

@ -0,0 +1,7 @@
enum EmbeddedContentSettingsOverridePolicy {
Always = 2,
UseAsDefault = 1,
Never = 0,
}
export default EmbeddedContentSettingsOverridePolicy;

View File

@ -1,7 +1,9 @@
import { Action } from '../../../node_modules/vuex/types/index' import { Action } from '../../../node_modules/vuex/types/index'
import { AardPollingOptions } from '../../ext/lib/aard/enums/aard-polling-options.enum'
import AntiGradientMode from '../enums/AntiGradientMode.enum' import AntiGradientMode from '../enums/AntiGradientMode.enum'
import AspectRatioType from '../enums/AspectRatioType.enum' import AspectRatioType from '../enums/AspectRatioType.enum'
import CropModePersistence from '../enums/CropModePersistence.enum' import CropModePersistence from '../enums/CropModePersistence.enum'
import EmbeddedContentSettingsOverridePolicy from '../enums/EmbeddedContentSettingsOverridePolicy.enum'
import ExtensionMode from '../enums/ExtensionMode.enum' import ExtensionMode from '../enums/ExtensionMode.enum'
import StretchType from '../enums/StretchType.enum' import StretchType from '../enums/StretchType.enum'
import VideoAlignmentType from '../enums/VideoAlignmentType.enum' import VideoAlignmentType from '../enums/VideoAlignmentType.enum'
@ -73,6 +75,10 @@ export interface AardSettings {
stopTimeout: number; stopTimeout: number;
}, },
polling: {
runInBackgroundTabs: AardPollingOptions;
runOnSmallVideos: AardPollingOptions;
}
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.
@ -86,13 +92,17 @@ export interface AardSettings {
minimumTimeout: number, minimumTimeout: number,
tickrate: number, // 1 tick every this many milliseconds tickrate: number, // 1 tick every this many milliseconds
}, },
autoDisable: { // settings for automatically disabling the extension
maxExecutionTime: number, // if execution time of main autodetect loop exceeds this many milliseconds,
// we disable it.
consecutiveTimeoutCount: number, // we only do it if it happens this many consecutive times
// FOR FUTURE USE subtitles: {
consecutiveArResets: number // if aspect ratio reverts immediately after AR change is applied, we disable everything resetIfDetected?: boolean, // reset if subtitles are detected
resumeAfter?: number, // resume after X ms of no subtitles
},
autoDisable: { // settings for automatically disabling the extension
onFirstChange: boolean,
ifNotChanged: boolean,
ifNotChangedTimeout: number,
ifSubtitles: boolean;
}, },
canvasDimensions: { canvasDimensions: {
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
@ -308,7 +318,8 @@ export interface SiteSettingsInterface {
enableKeyboard: ExtensionEnvironmentSettingsInterface; enableKeyboard: ExtensionEnvironmentSettingsInterface;
enableUI: ExtensionEnvironmentSettingsInterface; // Lies! enableUI doesn't use 'theater' property (but uses the other two) enableUI: ExtensionEnvironmentSettingsInterface; // Lies! enableUI doesn't use 'theater' property (but uses the other two)
applyToEmbeddedContent?: boolean; // presumed to be 'true' if not defined applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy; // presumed to be 'Always' if not defined
overrideWhenEmbedded?: boolean;
autocreated?: boolean; autocreated?: boolean;

View File

@ -463,7 +463,7 @@ export default {
if (!this.site) { if (!this.site) {
this.origin = event.origin; this.origin = event.origin;
this.site = event.origin.split('//')[1]; this.site = event.origin.split('//')[1];
this.siteSettings = this.settings.getSiteSettings({site: this.site}); this.siteSettings = this.settings.getSiteSettings({site: this.site, isIframe: event.data.isIframe});
} }
return this.handleProbe(event.data, event.origin); // handleProbe is defined in UIProbeMixin return this.handleProbe(event.data, event.origin); // handleProbe is defined in UIProbeMixin
case 'uw-bus-tunnel': case 'uw-bus-tunnel':

View File

@ -9,217 +9,7 @@
</div> </div>
<div class="aard-settings-group"> <div class="aard-settings-group">
<!-- the last time i tried to comment out this block, it didn't work properly v-if="false" it is -->
<div v-if="false">
<p>
<b>Autodetection performance</b>
</p>
<p>
Automatic aspect ratio detection is a resource-hungry feature.
This page allows you to trade autodetection accuracy and/or frequency for
better performance.
</p>
<p>
Note that some browsers <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/now" target="_blank">limit the accuracy of time measurements</a>, though once the bars go past the blue line those limitations are largely inconsequential.
</p>
<div class="performance-graph-container">
<div class="performance-graph">
<div class="time-budget hz144"></div>
<div class="time-budget hz120"></div>
<div class="time-budget hz60"></div>
<div class="time-budget hz30"></div>
<div class="time-budget hz24"></div>
<div class="time-budget rest"></div>
<div class="bar-container">
<div class="average-case">
<div class="stats">
<b>Average: </b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">
processing {{
Math.max(
(performanceData?.total?.averageTime ?? 0)
- (performanceData?.imageDraw?.averageTime ?? 0)
- (performanceData?.blackFrame?.averageTime ?? 0),
0
).toFixed(1)
}} ms
</span>
</div>
<div class="bar">
<div
class="draw"
:style="{'width': (performanceData?.imageDraw?.averageTime ?? 0) + '%'}"
>
</div>
<div
class="draw-blackframe"
:style="{'width': (performanceData?.blackFrame?.averageTime ?? 0) + '%'}"
>
</div>
<div
class="processing"
:style="{
'width': Math.max(
(performanceData?.total?.averageTime ?? 0)
- (performanceData?.imageDraw?.averageTime ?? 0)
- (performanceData?.blackFrame?.averageTime ?? 0),
0
) + '%'
}"
>
</div>
</div>
</div>
<div class="worst-case">
<div class="stats">
<b>Worst: </b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.worstTime ?? 0).toFixed(1)}} ms</span> |
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.worstTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">
processing {{
Math.max(
(performanceData?.total?.worstTime ?? 0)
- (performanceData?.imageDraw?.worstTime ?? 0)
- (performanceData?.blackFrame?.worstTime ?? 0),
0
).toFixed(1)
}} ms
</span>
</div>
<div class="bar">
<div
class="draw"
:style="{'width': (performanceData?.imageDraw?.worstTime ?? 0) + '%'}"
>
</div>
<div
class="draw-blackframe"
:style="{'width': (performanceData?.blackFrame?.worstTime ?? 0) + '%'}"
>
</div>
<div
class="processing"
:style="{
'width': Math.max(
(performanceData?.total?.worstTime ?? 0)
- (performanceData?.imageDraw?.worstTime ?? 0)
- (performanceData?.blackFrame?.worstTime ?? 0),
0
) + '%'
}"
>
</div>
</div>
</div>
<div class="average-case">
<div class="stats">
<b>AR change (average): </b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">processing {{
(
(performanceData?.fastLetterbox?.averageTime ?? 0)
+ (performanceData?.edgeDetect?.averageTime ?? 0)
).toFixed(1)
}} ms</span>
</div>
<div class="bar">
<div
class="draw"
:style="{'width': (performanceData?.imageDraw?.averageTime ?? 0) + '%'}"
>
</div>
<div
class="draw-blackframe"
:style="{'width': (performanceData?.blackFrame?.averageTime ?? 0) + '%'}"
>
</div>
<div
class="processing"
:style="{
'width': (
(performanceData?.fastLetterbox?.averageTime ?? 0)
+ (performanceData?.edgeDetect?.averageTime ?? 0)
) + '%'
}"
>
</div>
</div>
</div>
<div class="worst-case">
<div class="stats">
<b>AR change (worst): </b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.worstTime ?? 0).toFixed(1)}} ms</span> |
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.worstTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">processing {{
(
(performanceData?.fastLetterbox?.worstTime ?? 0)
+ (performanceData?.edgeDetect?.worstTime ?? 0)
).toFixed(1)
}} ms</span>
</div>
<div class="bar">
<div
class="draw"
:style="{'width': (performanceData?.imageDraw?.worstTime ?? 0) + '%'}"
>
</div>
<div
class="draw-blackframe"
:style="{'width': (performanceData?.blackFrame?.worstTime ?? 0) + '%'}"
>
</div>
<div
class="processing"
:style="{
'width': (
(performanceData?.fastLetterbox?.worstTime ?? 0)
+ (performanceData?.edgeDetect?.worstTime ?? 0)
) + '%'
}"
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="settings-segment"> <div class="settings-segment">
<!-- <h2>Basic settings</h2> -->
<!-- <div class="field">
<div class="label">
Stop autodetection after first detection:
</div>
<div class="">
<input type="checkbox" v-model="settings.active.arDetect.earlyStopOptions.stopAfterFirstDetection" />
</div>
</div>
<div class="field">
<div class="label">
Stop detection after a period of time:
</div>
<div class="">
<input type="checkbox" v-model="settings.active.arDetect.earlyStopOptions.stopAfterTimeout" />
</div>
</div>
<div class="field">
<div class="label">
Stop detection after:
</div>
<div class="input">
<input type="input" v-model="settings.active.arDetect.earlyStopOptions.stopTimeout" />
<div class="unit">seconds</div>
</div>
</div> -->
<div class="field"> <div class="field">
<div class="label">Autodetection frequency (time between samples)</div> <div class="label">Autodetection frequency (time between samples)</div>
@ -243,7 +33,97 @@
</div> </div>
<div class="field"> <div class="field">
<div class="label">Frame extraction canvas type:</div> <div class="label">Reduced autodetection frequency</div>
<div class="range-input">
<input
type="range"
:value="Math.log(settings.active.arDetect.timers.playingReduced)"
@change="setArCheckFrequency($event.target.value, 'playingReduced')"
min="2.3"
max="9.3"
step="0.01"
/>
<input
v-model="settings.active.arDetect.timers.playingReduced"
@change="setArCheckFrequency($event.target.value, 'playingReduced')"
class="input"
type="text"
>
<div class="unit">ms</div>
</div>
</div>
<div class="field">
<div class="label">Poll for aspect ratio changes in background tabs:</div>
<div class="select">
<select v-model="settings.active.arDetect.polling.runInBackgroundTabs" @change="settings.saveWithoutReload">
<option :value="AardPollingOptions.No">Never</option>
<option :value="AardPollingOptions.Reduced">Use reduced polling rate</option>
<option :value="AardPollingOptions.Full">Use normal polling rate</option>
</select>
</div>
</div>
<div v-if="settings.active.arDetect.polling.runInBackgroundTabs === AardPollingOptions.Full" class="hint warn">
Using normal polling rate in background tabs is NOT recommended.
</div>
<div class="field">
<div class="label">Poll for aspect ratio changes in small players:</div>
<div class="select">
<select v-model="settings.active.arDetect.polling.runOnSmallVideos" @change="settings.saveWithoutReload">
<option :value="AardPollingOptions.No">Never</option>
<option :value="AardPollingOptions.Reduced">Use reduced polling rate</option>
<option :value="AardPollingOptions.Full">Use normal polling rate</option>
</select>
</div>
</div>
<div v-if="settings.active.arDetect.polling.runOnSmallVideos === AardPollingOptions.Full" class="hint warn">
Using normal polling rate on small videos is NOT recommended.
</div>
<div class="field">
<div class="label">Stop autodetection after first detection:</div>
<div class="select">
<select v-model="settings.active.arDetect.autoDisable.onFirstChange" @change="settings.saveWithoutReload">
<option :value="true">Yes</option>
<option :value="false">No</option>
</select>
</div>
</div>
<div class="field" :class="{disabled: settings.active.arDetect.autoDisable.onFirstChange}">
<div class="label">Stop autodetection if aspect ratio doesn't change for some time:</div>
<div class="select">
<select v-model="settings.active.arDetect.autoDisable.ifNotChanged" @change="settings.saveWithoutReload">
<option :value="true">Yes</option>
<option :value="false">No</option>
</select>
</div>
</div>
<div v-if="settings.active.arDetect.autoDisable.ifNotChanged" class="field">
<div class="label">Stop autodetection if aspect ratio doesn't change for:</div>
<div class="range-input">
<input
type="range"
:value="Math.log(settings.active.arDetect.autoDisable.ifNotChangedTimeout / 10)"
@change="setAutoDisableTimeout($event.target.value, 10)"
min="2.3"
max="9.3"
step="0.01"
/>
<input
:value="settings.active.arDetect.autoDisable.ifNotChangedTimeout / 1000"
@change="setAutoDisableTimeout($event.target.value, 1000)"
class="input"
type="text"
>
<div class="unit">s</div>
</div>
</div>
<div class="field">
<div class="label">Autodetection canvas type:</div>
<div class="select"> <div class="select">
<select v-model="settings.active.arDetect.aardType" @change="settings.saveWithoutReload"> <select v-model="settings.active.arDetect.aardType" @change="settings.saveWithoutReload">
<option value="auto">Automatic</option> <option value="auto">Automatic</option>
@ -258,11 +138,11 @@
<div class="input"> <div class="input">
<input v-model="settings.active.arDetect.allowedMisaligned" /> <input v-model="settings.active.arDetect.allowedMisaligned" />
</div> </div>
<div class="hint"> </div>
Ultrawidify detects letterbox only if video is vertically centered. Some people are bad at vertically <div class="hint">
centering the content, though. This is how off-center the video can be before autodetection will Ultrawidify detects letterbox only if video is vertically centered. Some people are bad at vertically
refuse to crop it (% of total height). centering the content, though. This is how off-center the video can be before autodetection will
</div> refuse to crop it (% of total height).
</div> </div>
</div> </div>
@ -310,6 +190,7 @@ import StretchType from '../../../common/enums/StretchType.enum';
import CropModePersistence from '../../../common/enums/CropModePersistence.enum'; import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue'; import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
import JsonEditor from '@csui/src/components/JsonEditor'; import JsonEditor from '@csui/src/components/JsonEditor';
import {AardPollingOptions} from '@src/ext/lib/aard/enums/aard-polling-options.enum';
export default { export default {
components: { components: {
@ -333,6 +214,9 @@ export default {
performanceData: {}, performanceData: {},
graphRefreshInterval: undefined, graphRefreshInterval: undefined,
settingsJson: {}, settingsJson: {},
// enums n stuff
AardPollingOptions,
} }
}, },
computed: { computed: {
@ -348,7 +232,7 @@ export default {
}, },
mounted() { mounted() {
this.eventBus.sendToTunnel('get-aard-timing'); this.eventBus.sendToTunnel('get-aard-timing');
this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500); // this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500);
this.resetSettingsEditor(); this.resetSettingsEditor();
}, },
destroyed() { destroyed() {
@ -359,8 +243,13 @@ export default {
async openOptionsPage() { async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage(); BrowserDetect.runtime.openOptionsPage();
}, },
setArCheckFrequency(event) { setArCheckFrequency(event, timer) {
this.settings.active.arDetect.timers.playing = Math.floor(Math.pow(Math.E, event)); this.settings.active.arDetect.timers[timer ?? 'playing'] = Math.floor(Math.pow(Math.E, event));
this.settings.saveWithoutReload();
},
setAutoDisableTimeout(event, multiplier) {
this.settings.active.arDetect.autoDisable.ifNotChangedTimeout = Math.floor(Math.pow(Math.E, event)) * multiplier;
this.settings.saveWithoutReload();
}, },
refreshGraph() { refreshGraph() {
this.eventBus.sendToTunnel('get-aard-timing'); this.eventBus.sendToTunnel('get-aard-timing');

View File

@ -9,6 +9,7 @@
<ul> <ul>
<li>In-player UI can now appear in full-screen for websites that put players into top layer</li> <li>In-player UI can now appear in full-screen for websites that put players into top layer</li>
<li>Embedded sites now inherit settings of the parent frame</li> <li>Embedded sites now inherit settings of the parent frame</li>
<li>Added validation to custom aspect ratio entry menu. Corrected parsing of aspect ratios given in the X:Y format, even though aspect ratios should be ideally given as a single number.</li>
</ul> </ul>
</div> </div>

View File

@ -165,7 +165,7 @@
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<div class="label"> <div class="label">
Use these settings for <span class="color-emphasis">embedded content</span>? Use these settings for <span class="color-emphasis">embedded content</span>?
<!-- <span class="sub-label"><br/>under the following conditions:</span> --> <!-- <span class="sub-label"><br/>under the following conditions:</span> -->
@ -174,12 +174,35 @@
<select <select
:value="siteDefaultForEmbedded" :value="siteDefaultForEmbedded"
@click="setSiteOption('applyToEmbeddedContent', $event)" @click="setSiteOption('applyToEmbeddedContent', $event)"
>
<option :value="EmbeddedContentSettingsOverridePolicy.Always">
Always(-ish)
</option>
<option :value="EmbeddedContentSettingsOverridePolicy.UseAsDefault">
Use as default
</option>
<option :value="EmbeddedContentSettingsOverridePolicy.Never">
Never
</option>
</select>
</div>
</div>
<div class="field">
<div class="label">
Force these settings <span class="color-emphasis">when embedded</span>?
<!-- <span class="sub-label"><br/>under the following conditions:</span> -->
</div>
<div class="select">
<select
:value="siteDefaultOverrideEmbedded"
@click="setSiteOption('overrideWhenEmbedded', $event)"
> >
<option :value="true"> <option :value="true">
Unless overridden Yes
</option> </option>
<option :value="false"> <option :value="false">
Never No
</option> </option>
</select> </select>
</div> </div>
@ -290,12 +313,14 @@
import ExtensionMode from '@src/common/enums/ExtensionMode.enum'; import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum'; import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
import CropModePersistence from '@src/common/enums/CropModePersistence.enum'; import CropModePersistence from '@src/common/enums/CropModePersistence.enum';
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum';
export default { export default {
data() { data() {
return { return {
CropModePersistence: CropModePersistence, CropModePersistence: CropModePersistence,
ExtensionMode, ExtensionMode,
EmbeddedContentSettingsOverridePolicy,
alignmentOptions: [ alignmentOptions: [
{label: 'Top left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Top}}, {label: 'Top left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Top}},
{label: 'Top center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Top}}, {label: 'Top center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Top}},
@ -346,7 +371,10 @@ export default {
}; };
}, },
siteDefaultForEmbedded() { siteDefaultForEmbedded() {
return this.siteSettings.raw?.applyToEmbeddedContent ?? true; return this.siteSettings.raw?.applyToEmbeddedContent;
},
siteDefaultOverrideEmbedded() {
return this.siteSettings.raw?.overrideWhenEmbedded ?? false;
}, },
siteDefaultCrop() { siteDefaultCrop() {
return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true}); return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true});
@ -397,7 +425,6 @@ export default {
break; break;
} }
// console.log('getting data from:', settingsData);
try { try {
if ( if (
@ -558,11 +585,14 @@ export default {
this.$nextTick( () => this.$forceUpdate()); this.$nextTick( () => this.$forceUpdate());
}, },
setSiteOption(optionPath, event) {
const value = event.target.value;
this.siteSettings.set(optionPath, value);
},
setExtensionMode(component, event) { setExtensionMode(component, event) {
const option = event.target.value; const option = event.target.value;
console.log('SET EXTENSION MODE — OPTIONS:', option);
if (option === 'complex') { if (option === 'complex') {
return; return;
} }

View File

@ -55,10 +55,13 @@
<!-- We do an ugly in order to avoid spamming functions down at the bottom --> <!-- We do an ugly in order to avoid spamming functions down at the bottom -->
<input <input
v-model="editModeOptions.crop.selected.arguments.ratio" v-model="editModeOptions.crop.selected.arguments.ratio"
@blur="editModeOptions.crop.selected.label === 'New aspect ratio' ? editModeOptions.crop.selected.label = editModeOptions.crop.selected.arguments.ratio : null" @blur="updateLabel('crop')"
> >
</div> </div>
</div> </div>
<div v-if="editModeOptions.crop?.error" class="hint error">
{{editModeOptions.crop.error}}
</div>
<div class="hint"> <div class="hint">
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter (in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter

View File

@ -70,10 +70,13 @@
<!-- We do an ugly in order to avoid spamming functions down at the bottom --> <!-- We do an ugly in order to avoid spamming functions down at the bottom -->
<input <input
v-model="editModeOptions.stretch.selected.arguments.ratio" v-model="editModeOptions.stretch.selected.arguments.ratio"
@blur="editModeOptions.stretch.selected.label === 'Stretch to ...' ? editModeOptions.stretch.selected.label = `Stretch to ${editModeOptions.stretch.selected.arguments.ratio}` : null" @blur="updateLabel('stretch')"
> >
</div> </div>
</div> </div>
<div v-if="editModeOptions.stretch?.error" class="hint error">
{{editModeOptions.stretch.error}}
</div>
<div class="hint"> <div class="hint">
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter (in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter

View File

@ -48,16 +48,20 @@
<!-- We do an ugly in order to avoid spamming functions down at the bottom --> <!-- We do an ugly in order to avoid spamming functions down at the bottom -->
<input <input
v-model="editModeOptions.zoom.selected.arguments.ratio" v-model="editModeOptions.zoom.selected.arguments.ratio"
@blur="editModeOptions.zoom.selected.label === 'New aspect ratio' ? editModeOptions.zoom.selected.label = editModeOptions.zoom.selected.arguments.ratio : null" @blur="updateLabel('zoom')"
> >
</div> </div>
</div> </div>
<div v-if="editModeOptions.zoom?.error" class="hint error">
{{editModeOptions.zoom.error}}
</div>
<div class="hint"> <div class="hint">
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter (in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
your numbers without quote marks. Number will be converted to factor form on save. your numbers without quote marks. Number will be converted to factor form on save.
</div> </div>
<div class="field"> <div class="field">
<div class="label"> <div class="label">
Label: Label:

View File

@ -9,18 +9,21 @@
<div class="info"> <div class="info">
Extension is not enabled for this site. Extension is not enabled for this site.
</div> </div>
<div> <div class="text-center">
Please enable extension for this site. <p>Please enable extension for this site.</p>
</div> </div>
<div> <div>
<button <button
class="flex flex-row items-center" class="flex flex-row items-center"
style="background-color: transparent; padding: 0.25rem 0.5rem; margin-top: 1rem;" style="background-color: transparent; padding: 0.5rem 1rem; margin-top: 1rem; border: 1px solid #fa68;"
@click="openSettings()" @click="openSettings()"
> >
Open settings <mdicon style="margin-left: 0.5rem;" name="open-in-new" size="16"></mdicon> Open settings <mdicon style="margin-left: 0.5rem;" name="open-in-new" size="16"></mdicon>
</button> </button>
</div> </div>
<div class="text-center">
<p><small>Extension may work on content embedded from other sites.</small></p>
</div>
</div> </div>
</template> </template>

View File

@ -210,6 +210,10 @@ button, .button {
margin-left: 5rem; margin-left: 5rem;
// width: 100%; // width: 100%;
box-sizing:border-box; box-sizing:border-box;
&.error {
color: #f41;
}
} }
.options-bar { .options-bar {

View File

@ -52,7 +52,47 @@ export default {
} }
}, },
validateAspectRatio(actionType) {
// deal with potentially invalid aspect ratios
if (this.editModeOptions[actionType].selected.arguments.ratio.includes(':')) {
const ratioParts = this.editModeOptions[actionType].selected.arguments.ratio.split(':');
if (ratioParts.length !== 2) {
this.editModeOptions[actionType].error = 'Entered aspect ratio is not valid.';
return false;
} else {
const ratio = ratioParts[0] / ratioParts[1];
if (isNaN(ratio) || ratio <= 0) {
this.editModeOptions[actionType].error = 'Entered aspect ratio is not valid.';
return false;
}
this.editModeOptions[actionType].selected.arguments.ratio = ratio;
}
} else {
if (isNaN(this.editModeOptions[actionType].selected.arguments.ratio)) {
this.editModeOptions[actionType].error = "Entered aspect ratio is not valid.";
return false;
}
}
delete this.editModeOptions[actionType].error;
return true;
},
/**
* Updates button label, ideally after aspect ratio is entered,
* after input field blurred, and only if aspect ratio is valid.
*/
updateLabel(actionType) {
if (this.validateAspectRatio(actionType) && this.editModeOptions[actionType].selected.label === 'New aspect ratio') {
this.editModeOptions[actionType].selected.label = this.editModeOptions[actionType].selected.arguments.ratio;
}
},
saveShortcut(actionType) { saveShortcut(actionType) {
if (! this.validateAspectRatio(actionType)) {
return;
}
if (!this.editModeOptions[actionType]?.selectedIndex) { if (!this.editModeOptions[actionType]?.selectedIndex) {
this.settings.active.commands[actionType].push(this.editModeOptions[actionType].selected); this.settings.active.commands[actionType].push(this.editModeOptions[actionType].selected);
} }

View File

@ -9,6 +9,7 @@ import { _cp } from '../../common/js/utils';
import CropModePersistence from '../../common/enums/CropModePersistence.enum'; import CropModePersistence from '../../common/enums/CropModePersistence.enum';
import AspectRatioType from '../../common/enums/AspectRatioType.enum'; import AspectRatioType from '../../common/enums/AspectRatioType.enum';
import { update } from 'lodash'; import { update } from 'lodash';
import EmbeddedContentSettingsOverridePolicy from '../../common/enums/EmbeddedContentSettingsOverridePolicy.enum';
const ExtensionConfPatch = Object.freeze([ const ExtensionConfPatch = Object.freeze([
{ {
@ -35,8 +36,7 @@ const ExtensionConfPatch = Object.freeze([
}, { }, {
forVersion: '6.2.6', forVersion: '6.2.6',
updateFn: (userOptions: SettingsInterface, defaultOptions) => { updateFn: (userOptions: SettingsInterface, defaultOptions) => {
console.log('[ultrawidify] Migrating settings — applying patches for version 6.2.6');
console.warn('[ultrawidify] STARTING SETTINGS MIGRATION TO 6.2.6');
if (!userOptions.commands) { if (!userOptions.commands) {
userOptions.commands = { userOptions.commands = {
@ -239,7 +239,28 @@ const ExtensionConfPatch = Object.freeze([
} }
} }
} }
}, {
forVersion: '6.3.92',
updateFn: (userOptions: SettingsInterface) => {
// applyToEmbeddedContent is now an enum, and also no longer optional
for (const site in userOptions.sites) {
if (userOptions.sites[site].applyToEmbeddedContent === undefined) {
userOptions.sites[site].applyToEmbeddedContent = EmbeddedContentSettingsOverridePolicy.Always;
} else {
userOptions.sites[site].applyToEmbeddedContent = userOptions.sites[site].applyToEmbeddedContent ? EmbeddedContentSettingsOverridePolicy.Always : EmbeddedContentSettingsOverridePolicy.Never;
}
}
}
},
{
forVersion: '6.3.93',
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
userOptions.arDetect.polling = defaultOptions.arDetect.polling;
userOptions.arDetect.subtitles = defaultOptions.arDetect.subtitles;
userOptions.arDetect.autoDisable = defaultOptions.arDetect.autoDisable;
}
} }
]); ]);

View File

@ -9,6 +9,8 @@ import CropModePersistence from '../../common/enums/CropModePersistence.enum';
import SettingsInterface from '../../common/interfaces/SettingsInterface'; import SettingsInterface from '../../common/interfaces/SettingsInterface';
import BrowserDetect from './BrowserDetect'; import BrowserDetect from './BrowserDetect';
import { Extension } from 'typescript'; import { Extension } from 'typescript';
import EmbeddedContentSettingsOverridePolicy from '../../common/enums/EmbeddedContentSettingsOverridePolicy.enum';
import { AardPollingOptions } from '../lib/aard/enums/aard-polling-options.enum';
if(Debug.debug) if(Debug.debug)
console.log("Loading: ExtensionConf.js"); console.log("Loading: ExtensionConf.js");
@ -21,6 +23,16 @@ const ExtensionConf: SettingsInterface = {
arDetect: { arDetect: {
aardType: 'auto', aardType: 'auto',
polling: {
runInBackgroundTabs: AardPollingOptions.Reduced,
runOnSmallVideos: AardPollingOptions.Reduced
},
subtitles: {
resetIfDetected: true,
resumeAfter: 5000,
},
earlyStopOptions: { earlyStopOptions: {
stopAfterFirstDetection: false, stopAfterFirstDetection: false,
stopAfterTimeout: false, stopAfterTimeout: false,
@ -40,12 +52,10 @@ const ExtensionConf: SettingsInterface = {
tickrate: 10, // 1 tick every this many milliseconds tickrate: 10, // 1 tick every this many milliseconds
}, },
autoDisable: { // settings for automatically disabling the extension autoDisable: { // settings for automatically disabling the extension
maxExecutionTime: 6000, // if execution time of main autodetect loop exceeds this many milliseconds, onFirstChange: false, // disable once we have a stable aspect ratio
// we disable it. ifNotChanged: false, // disable if Ar hasn't changed for this long
consecutiveTimeoutCount: 5, // we only do it if it happens this many consecutive times ifNotChangedTimeout: 20000, // if user enables ifNotChangedTimeout, we default to 20s
ifSubtitles: false, // disable if subtitles are detected
// FOR FUTURE USE
consecutiveArResets: 5 // if aspect ratio reverts immediately after AR change is applied, we disable everything
}, },
canvasDimensions: { canvasDimensions: {
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
@ -739,6 +749,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Disabled, // Lies! means 'theater-ish' theater: ExtensionMode.Disabled, // Lies! means 'theater-ish'
normal: ExtensionMode.Disabled // Not actually used. normal: ExtensionMode.Disabled // Not actually used.
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
defaultType: 'unknown', defaultType: 'unknown',
persistCSA: CropModePersistence.Disabled, persistCSA: CropModePersistence.Disabled,
@ -770,6 +781,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Default, theater: ExtensionMode.Default,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'user-defined', type: 'user-defined',
defaultType: 'user-defined', defaultType: 'user-defined',
@ -801,7 +813,8 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
overrideWhenEmbedded: true,
override: false, // ignore value localStorage in favour of this override: false, // ignore value localStorage in favour of this
type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined') type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined')
defaultType: 'official', // if user mucks around with settings, type changes to 'user-defined'. defaultType: 'official', // if user mucks around with settings, type changes to 'user-defined'.
@ -841,6 +854,8 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
overrideWhenEmbedded: true,
override: false, // ignore value localStorage in favour of this override: false, // ignore value localStorage in favour of this
type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined') type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined')
@ -881,7 +896,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
override: false, override: false,
type: 'community', type: 'community',
defaultType: 'community', defaultType: 'community',
@ -907,6 +922,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Default normal: ExtensionMode.Default
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'community', type: 'community',
defaultType: 'community', defaultType: 'community',
activeDOMConfig: 'community-mstefan99', activeDOMConfig: 'community-mstefan99',
@ -948,6 +964,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Enabled normal: ExtensionMode.Enabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'official', type: 'official',
defaultType: 'official', defaultType: 'official',
}, },
@ -972,6 +989,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Disabled, theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
type: 'officially-disabled', type: 'officially-disabled',
defaultType: 'officially-disabled', defaultType: 'officially-disabled',
activeDOMConfig: 'official', activeDOMConfig: 'official',
@ -1009,6 +1027,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Disabled, theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
type: 'officially-disabled', type: 'officially-disabled',
defaultType: 'officially-disabled', defaultType: 'officially-disabled',
activeDOMConfig: 'official', activeDOMConfig: 'official',
@ -1046,54 +1065,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Disabled, theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
type: 'officially-disabled', applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
defaultType: 'officially-disabled',
},
"gfycat.com": {
enable: {
fullscreen: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled,
},
enableAard: {
fullscreen: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled,
},
enableKeyboard: {
fullscreen: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled,
},
enableUI: {
fullscreen: ExtensionMode.Default,
theater: ExtensionMode.Default,
normal: ExtensionMode.Default
},
type: 'officially-disabled',
defaultType: 'officially-disabled',
},
"giant.gfycat.com": {
enable: {
fullscreen: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled,
},
enableAard: {
fullscreen: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled,
},
enableKeyboard: {
fullscreen: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
normal: ExtensionMode.Disabled,
},
enableUI: {
fullscreen: ExtensionMode.Default,
theater: ExtensionMode.Default,
normal: ExtensionMode.Default
},
type: 'officially-disabled', type: 'officially-disabled',
defaultType: 'officially-disabled', defaultType: 'officially-disabled',
}, },
@ -1118,6 +1090,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Enabled normal: ExtensionMode.Enabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'community', type: 'community',
defaultType: 'community', defaultType: 'community',
activeDOMConfig: 'community', activeDOMConfig: 'community',
@ -1154,6 +1127,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'community', type: 'community',
defaultType: 'community', defaultType: 'community',
activeDOMConfig: 'community', activeDOMConfig: 'community',
@ -1185,6 +1159,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: "community", type: "community",
defaultType: "community", defaultType: "community",
activeDOMConfig: 'community', activeDOMConfig: 'community',
@ -1220,6 +1195,7 @@ const ExtensionConf: SettingsInterface = {
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
normal: ExtensionMode.Disabled normal: ExtensionMode.Disabled
}, },
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: "community", type: "community",
defaultType: "community", defaultType: "community",
activeDOMConfig: 'community', activeDOMConfig: 'community',

View File

@ -115,8 +115,19 @@ export default class EventBus {
// CommsServer's job. EventBus does not have enough data for this decision. // CommsServer's job. EventBus does not have enough data for this decision.
// We do, however, have enough data to prevent backflow of messages that // We do, however, have enough data to prevent backflow of messages that
// crossed CommsServer once already. // crossed CommsServer once already.
if (this.comms && context?.origin !== CommsOrigin.Server && !context?.borderCrossings?.commsServer) { if (
this.comms.sendMessage({command, config: commandData, context}, context); this.comms
&& context?.origin !== CommsOrigin.Server
&& !context?.borderCrossings?.commsServer
) {
try {
this.comms.sendMessage({command, config: commandData, context}, context);
} catch (e) {
if (command !== 'reload-required') {
// We shouldn't let reload-required command to trigger new reload-required commands.
this.send('reload-required', {});
}
}
}; };
// call forwarding functions if they exist // call forwarding functions if they exist
@ -161,7 +172,13 @@ export default class EventBus {
// also need to set up a detour because the tunnel is closed // also need to set up a detour because the tunnel is closed
// in the popup // in the popup
if (this.comms) { if (this.comms) {
this.comms.sendMessage({command, config, context: this.popupContext}, this.popupContext); try {
this.comms.sendMessage({command, config, context: this.popupContext}, this.popupContext);
} catch (e) {
if (command !== 'reload-required') {
this.send('reload-required', {});
}
}
} }
} }
} }

View File

@ -19,6 +19,7 @@ import { AardStatus, initAardStatus } from './interfaces/aard-status.interface';
import { AardTestResults, initAardTestResults, resetAardTestResults, resetGuardLine } from './interfaces/aard-test-results.interface'; import { AardTestResults, initAardTestResults, resetAardTestResults, resetGuardLine } from './interfaces/aard-test-results.interface';
import { AardTimers, initAardTimers } from './interfaces/aard-timers.interface'; import { AardTimers, initAardTimers } from './interfaces/aard-timers.interface';
import { ComponentLogger } from '../logging/ComponentLogger'; import { ComponentLogger } from '../logging/ComponentLogger';
import { AardPollingOptions } from './enums/aard-polling-options.enum';
/** /**
@ -232,7 +233,7 @@ export class Aard {
private eventBusCommands = { private eventBusCommands = {
'uw-environment-change': { 'uw-environment-change': {
function: (newEnvironment: ExtensionEnvironment) => { function: (newEnvironment: ExtensionEnvironment) => {
console.log('received extension environment:', newEnvironment, 'player env:', this.videoData?.player?.environment); // console.log('received extension environment:', newEnvironment, 'player env:', this.videoData?.player?.environment);
this.startCheck(); this.startCheck();
} }
}, },
@ -244,6 +245,11 @@ export class Aard {
this.hideDebugCanvas(); this.hideDebugCanvas();
} }
} }
},
'url-changed': {
function: () => {
this.clearAutoDisabled();
}
} }
// 'get-aard-timing': { // 'get-aard-timing': {
// function: () => this.handlePerformanceDataRequest() // function: () => this.handlePerformanceDataRequest()
@ -430,10 +436,19 @@ export class Aard {
} }
} }
/**
* Clears autoDisable flag
*/
clearAutoDisabled() {
this.status.autoDisabled = false;
this.timers.autoDisableAt = undefined;
}
/** /**
* Starts autodetection loop. * Starts autodetection loop.
*/ */
start() { start() {
this.clearAutoDisabled();
this.forceFullRecheck = true; this.forceFullRecheck = true;
if (this.videoData.resizer.lastAr.type === AspectRatioType.AutomaticUpdate) { if (this.videoData.resizer.lastAr.type === AspectRatioType.AutomaticUpdate) {
// ensure first autodetection will run in any case // ensure first autodetection will run in any case
@ -450,6 +465,11 @@ export class Aard {
this.status.aardActive = true; this.status.aardActive = true;
this.animationFrame = window.requestAnimationFrame( (ts: DOMHighResTimeStamp) => this.onAnimationFrame(ts)); this.animationFrame = window.requestAnimationFrame( (ts: DOMHighResTimeStamp) => this.onAnimationFrame(ts));
// set auto-disable timer if detection timeout is set
if (this.settings.active.arDetect.autoDisable.ifNotChanged) {
this.timers.autoDisableAt = Date.now() + this.settings.active.arDetect.autoDisable.ifNotChangedTimeout;
}
} }
/** /**
@ -482,24 +502,42 @@ export class Aard {
* @returns * @returns
*/ */
private canTriggerFrameCheck() { private canTriggerFrameCheck() {
// if (this._paused || this._halted || this._exited) {
// return false; // console.log('ard check status:', this.status);
// }
// if video was paused & we know that we already checked that frame, // if video was paused & we know that we already checked that frame,
// we will not check it again. // we will not check it again.
const videoState = this.getVideoPlaybackState(); const videoState = this.getVideoPlaybackState();
const polling = this.settings.active.arDetect.polling;
const now = Date.now();
if (videoState !== VideoPlaybackState.Playing) { if (videoState !== VideoPlaybackState.Playing) {
if (this.status.lastVideoStatus === videoState) { if (this.status.lastVideoStatus === videoState) {
return false; return false;
} }
} }
if (this.status.autoDisabled) {
return false;
}
if (this.timers.autoDisableAt < now) {
this.status.autoDisabled = true;
return false;
}
this.status.lastVideoStatus = videoState; this.status.lastVideoStatus = videoState;
const now = Date.now(); const tabVisible = document.visibilityState === 'visible';
if (!tabVisible && polling.runInBackgroundTabs === AardPollingOptions.No) {
return false;
}
if (this.videoData.player.isTooSmall && polling.runOnSmallVideos === AardPollingOptions.No) {
return false;
}
if (now < (this.videoData.player.isTooSmall ? this.timers.reducedPollingNextCheckTime : this.timers.nextFrameCheckTime)) { const isActive = (tabVisible || polling.runInBackgroundTabs !== AardPollingOptions.Reduced)
&& (!this.videoData.player.isTooSmall || polling.runOnSmallVideos !== AardPollingOptions.Reduced);
const nextCheck = isActive ? this.timers.nextFrameCheckTime : this.timers.reducedPollingNextCheckTime;
if (now < nextCheck) {
return false; return false;
} }
@ -508,6 +546,12 @@ export class Aard {
return true; return true;
} }
/**
* Bootstraps the main loop.
*
* Honestly this doesn't need description, but I want to put some green
* between two adjacent functions.
*/
private onAnimationFrame(ts: DOMHighResTimeStamp) { private onAnimationFrame(ts: DOMHighResTimeStamp) {
if (this.canTriggerFrameCheck()) { if (this.canTriggerFrameCheck()) {
resetAardTestResults(this.testResults); resetAardTestResults(this.testResults);
@ -671,7 +715,7 @@ export class Aard {
// console.warn('ASPECT RATIO UNCERTAIN, GUARD LINE INVALIDATED (resetting)') // console.warn('ASPECT RATIO UNCERTAIN, GUARD LINE INVALIDATED (resetting)')
this.timer.arChanged(); this.timer.arChanged();
this.updateAspectRatio(this.defaultAr); this.updateAspectRatio(this.defaultAr, {uncertainDetection: true});
break; break;
} }
@ -2027,14 +2071,12 @@ export class Aard {
/** /**
* Updates aspect ratio if new aspect ratio is different enough from the old one * Updates aspect ratio if new aspect ratio is different enough from the old one
*/ */
private updateAspectRatio(overrideAr?: number) { private updateAspectRatio(ar: number, options?: {uncertainDetection?: boolean}) {
const ar = overrideAr ?? this.getAr();
// Calculate difference between two ratios // Calculate difference between two ratios
const maxRatio = Math.max(ar, this.testResults.activeAspectRatio); const maxRatio = Math.max(ar, this.testResults.activeAspectRatio);
const diff = Math.abs(ar - this.testResults.activeAspectRatio); const diff = Math.abs(ar - this.testResults.activeAspectRatio);
if (overrideAr || (diff / maxRatio) > this.settings.active.arDetect.allowedArVariance) { if ((diff / maxRatio) > this.settings.active.arDetect.allowedArVariance) {
this.videoData.resizer.updateAr({ this.videoData.resizer.updateAr({
type: AspectRatioType.AutomaticUpdate, type: AspectRatioType.AutomaticUpdate,
ratio: this.getAr(), ratio: this.getAr(),
@ -2042,6 +2084,16 @@ export class Aard {
variant: this.arVariant variant: this.arVariant
}); });
this.testResults.activeAspectRatio = ar; this.testResults.activeAspectRatio = ar;
if (!options?.uncertainDetection) {
if (this.settings.active.arDetect.autoDisable.onFirstChange) {
this.status.autoDisabled = true;
}
if (this.settings.active.arDetect.autoDisable.ifNotChanged) {
this.timers.autoDisableAt = Date.now() + this.settings.active.arDetect.autoDisable.ifNotChangedTimeout;
}
}
} }
} }

View File

@ -0,0 +1,5 @@
export enum AardPollingOptions {
No = 0,
Reduced = 1,
Full = 2
}

View File

@ -5,7 +5,7 @@ export interface AardStatus {
aardReducedPolling: boolean, aardReducedPolling: boolean,
checkInProgress: boolean, checkInProgress: boolean,
lastVideoStatus: VideoPlaybackState, lastVideoStatus: VideoPlaybackState,
autoDisabled: boolean,
} }
export function initAardStatus(): AardStatus { export function initAardStatus(): AardStatus {
@ -14,5 +14,6 @@ export function initAardStatus(): AardStatus {
aardReducedPolling: true, aardReducedPolling: true,
checkInProgress: false, checkInProgress: false,
lastVideoStatus: VideoPlaybackState.NotInitialized, lastVideoStatus: VideoPlaybackState.NotInitialized,
autoDisabled: false,
} }
} }

View File

@ -1,4 +1,5 @@
export interface AardTimers { export interface AardTimers {
autoDisableAt: number | undefined;
nextFrameCheckTime: number; nextFrameCheckTime: number;
reducedPollingNextCheckTime: number; reducedPollingNextCheckTime: number;
} }
@ -7,5 +8,6 @@ export function initAardTimers(): AardTimers {
return { return {
nextFrameCheckTime: 0, nextFrameCheckTime: 0,
reducedPollingNextCheckTime: 0, reducedPollingNextCheckTime: 0,
autoDisableAt: undefined,
}; };
} }

View File

@ -93,38 +93,46 @@ function detectBackend(str) {
* @returns * @returns
*/ */
function detectANGLEBackend(): AngleVersion { function detectANGLEBackend(): AngleVersion {
const canvas = document.createElement("canvas"); try {
const gl = canvas.getContext("webgl2") || const canvas = document.createElement("canvas");
canvas.getContext("webgl") || const gl = canvas.getContext("webgl2") ||
canvas.getContext("experimental-webgl"); canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl");
const ext = (gl as any).getExtension("WEBGL_debug_shaders"); if (!gl) {
return AngleVersion.NotAvailable;
if (!ext) {
return AngleVersion.NotAvailable;
}
const isWebGL1 = gl instanceof WebGLRenderingContext;
const shader = (gl as any).createShader((gl as any).VERTEX_SHADER);
(gl as any).shaderSource(
shader,
`#version ${isWebGL1 ? "100" : "300 es"}
void main() {
gl_Position = vec4(__VERSION__, 1.0, 1.0, 1.0);
} }
`
);
(gl as any).compileShader(shader); const ext = (gl as any).getExtension("WEBGL_debug_shaders");
if (!(gl as any).getShaderParameter(shader, (gl as any).COMPILE_STATUS)) { if (!ext) {
console.error("invalid shader", (gl as any).getShaderInfoLog(shader)); return AngleVersion.NotAvailable;
}
const isWebGL1 = gl instanceof WebGLRenderingContext;
const shader = (gl as any).createShader((gl as any).VERTEX_SHADER);
(gl as any).shaderSource(
shader,
`#version ${isWebGL1 ? "100" : "300 es"}
void main() {
gl_Position = vec4(__VERSION__, 1.0, 1.0, 1.0);
}
`
);
(gl as any).compileShader(shader);
if (!(gl as any).getShaderParameter(shader, (gl as any).COMPILE_STATUS)) {
console.error("invalid shader", (gl as any).getShaderInfoLog(shader));
return AngleVersion.NotAvailable;
}
const source = ext.getTranslatedShaderSource(shader);
return detectBackend(source);
} catch (e) {
return AngleVersion.NotAvailable; return AngleVersion.NotAvailable;
} }
const source = ext.getTranslatedShaderSource(shader);
return detectBackend(source);
} }

View File

@ -5,6 +5,7 @@ import { _cp } from '../../../common/js/utils';
import Settings from './Settings'; import Settings from './Settings';
import StretchType from '../../../common/enums/StretchType.enum'; import StretchType from '../../../common/enums/StretchType.enum';
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum'; import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
import EmbeddedContentSettingsOverridePolicy from '../../../common/enums/EmbeddedContentSettingsOverridePolicy.enum';
export interface GetSiteSettingsOptions { export interface GetSiteSettingsOptions {
@ -79,30 +80,39 @@ export class SiteSettings {
* | different subdomain? | * | different subdomain? |
* | | * | |
* no yes > return settings for matching subdomain * no yes > return settings for matching subdomain
* |
* V * V
* [ are we inside of an iframe? ] * return default settings
* | | *
* no yes *
* V V * Inside iframes, we also get priorities. We fetch settings for both iframe and the parent page.
* return default | exact parent | *
* settings | hostname match? | * START HERE
* A | | * |
* | no yes > [ applyToEmbeddedContent set? ] * | are there settings for | +> | does iframe host have |
* | V | | * | iframe host? | | | overrideWhenEmbedded set? |
* | | parent matches hostname | no yes * | | | | |
* | | on different subdomain? | V V * no yes + no yes -> return iframe settings
* | | | use default use parent settings * V V
* +< no yes * | does parent host have | | does parent use settings for @global ? |
* | | * | applyToEmbeddedContent | | |
* +< no [ applyToEmbeddedContent set? ] * | set to Never? | no yes > return iframe settings
* | * | | V
* yes * no no | what's applyToEmbeddedContent |
* V * | | | set to on parent host? |
* use settings for matching subdomain of parent * V V | | |
* return parent return default Always UseAsDefault Never
* settings settings | | |
* V V V
* return parent return iframe
* settings settings
* *
* @returns * @returns
*/ */
private getSettingsForSite(options: GetSiteSettingsOptions) { private getSettingsForSite(options: GetSiteSettingsOptions): {siteSettings: SiteSettingsInterface, usesSettingsFor: string | undefined} {
// console.warn('getSettingsForSite: getting settings for site', options.site, ' — full options:', options);
if (!options.site) { if (!options.site) {
return { return {
siteSettings: this.settings.active.sites['@global'], siteSettings: this.settings.active.sites['@global'],
@ -110,52 +120,60 @@ export class SiteSettings {
}; };
} }
if (this.settings.active.sites[options.site]) { const defaultSettings = {
return {
siteSettings: this.settings.active.sites[options.site],
usesSettingsFor: undefined
};
}
const urlSegments = this.site.split('.').reverse();
siteLoop:
for (const cs in this.settings.active.sites) {
const configUrlSegments = cs.split('.').reverse();
// Match site with wildcard site definitions
// Also, if definition starts with 'www', match also other subdomains — e.g. if we have a configuration for
// `www.example.com`, this will also match `example.com`, `subdomain.example.com`, `nested.subdomain.example.com` ...
if (configUrlSegments[configUrlSegments.length - 1] === '*' || (configUrlSegments[configUrlSegments.length - 1] === 'www')) {
for (let i = 0; i < configUrlSegments.length - 1 && i < urlSegments.length; i++) {
if (configUrlSegments[i] !== urlSegments[i]) {
continue siteLoop;
}
}
return {
siteSettings: this.settings.active.sites[cs],
usesSettingsFor: cs
}
}
}
// If we're inside of an iframe, let's see whether we can use parent settings
if (options.isIframe) {
const potentialSettings = this.getSettingsForSite({site: options.parentHostname});
if (potentialSettings.siteSettings.applyToEmbeddedContent !== false) {
if (!potentialSettings.usesSettingsFor) {
potentialSettings.usesSettingsFor = options.parentHostname;
}
return potentialSettings;
}
}
return {
siteSettings: this.settings.active.sites['@global'], siteSettings: this.settings.active.sites['@global'],
usesSettingsFor: '@global' usesSettingsFor: '@global'
}; };
let siteSettings: {siteSettings: SiteSettingsInterface, usesSettingsFor: string | undefined};
if (this.settings.active.sites[options.site]) {
siteSettings = {
siteSettings: this.settings.active.sites[options.site],
usesSettingsFor: undefined
};
} else {
const urlSegments = this.site.split('.').reverse();
siteLoop:
for (const cs in this.settings.active.sites) {
const configUrlSegments = cs.split('.').reverse();
// Match site with wildcard site definitions
// Also, if definition starts with 'www', match also other subdomains — e.g. if we have a configuration for
// `www.example.com`, this will also match `example.com`, `subdomain.example.com`, `nested.subdomain.example.com` ...
if (configUrlSegments[configUrlSegments.length - 1] === '*' || (configUrlSegments[configUrlSegments.length - 1] === 'www')) {
for (let i = 0; i < configUrlSegments.length - 1 && i < urlSegments.length; i++) {
if (configUrlSegments[i] !== urlSegments[i]) {
continue siteLoop;
}
}
siteSettings = {
siteSettings: this.settings.active.sites[cs],
usesSettingsFor: cs
}
}
}
}
if (!options.isIframe) {
return siteSettings ?? defaultSettings;
} else {
const parentSettings = this.getSettingsForSite({site: options.parentHostname});
if (siteSettings) {
return (
siteSettings.siteSettings.overrideWhenEmbedded || parentSettings.usesSettingsFor === '@global' || (
parentSettings.siteSettings.applyToEmbeddedContent && parentSettings.siteSettings.applyToEmbeddedContent !== EmbeddedContentSettingsOverridePolicy.Always
)
) ? siteSettings : parentSettings;
} else {
if (parentSettings.usesSettingsFor !== '@global' && parentSettings.siteSettings.applyToEmbeddedContent === EmbeddedContentSettingsOverridePolicy.Never) {
return defaultSettings;
}
return parentSettings;
}
}
} }
/** /**
@ -414,6 +432,19 @@ export class SiteSettings {
//#endregion //#endregion
//#region set shit //#region set shit
/**
* Returns target site for settings application
*/
private getTargetSite() {
if (!this.usesSettingsFor || this.usesSettingsFor === '@global') {
return this.site;
}
if (this.settings.active.sites[this.usesSettingsFor].applyToEmbeddedContent === EmbeddedContentSettingsOverridePolicy.Always) {
return this.usesSettingsFor;
}
return this.site;
}
/** /**
* Sets option value. * Sets option value.
* @param optionPath path to value in object notation (dot separated) * @param optionPath path to value in object notation (dot separated)
@ -421,26 +452,29 @@ export class SiteSettings {
* @param reload whether we should trigger a reload in components that require it * @param reload whether we should trigger a reload in components that require it
*/ */
async set(optionPath: string, optionValue: any, options: {reload?: boolean, noSave?: boolean, scripted?: boolean} = {reload: false}) { async set(optionPath: string, optionValue: any, options: {reload?: boolean, noSave?: boolean, scripted?: boolean} = {reload: false}) {
// const targetSite = this.getTargetSite();
const targetSite = this.site;
// if no settings exist for this site, create an empty object. // if no settings exist for this site, create an empty object.
// If this function is not being called in response to user actin, // If this function is not being called in response to user actin,
// create fake settings object. // create fake settings object.
if (options.scripted && !this.settings.active.sites[this.site]) { if (options.scripted && !this.settings.active.sites[targetSite]) {
this.settings.active.sites[this.site] = _cp(this.settings.active.sites['@global']); this.settings.active.sites[targetSite] = _cp(this.settings.active.sites['@global']);
this.settings.active.sites[this.site].autocreated = true; this.settings.active.sites[targetSite].autocreated = true;
this.settings.active.sites[this.site].type = 'unknown'; this.settings.active.sites[targetSite].type = 'unknown';
} else { } else {
if (!this.settings.active.sites[this.site] || this.settings.active.sites[this.site].autocreated) { if (!this.settings.active.sites[targetSite] || this.settings.active.sites[targetSite].autocreated) {
this.settings.active.sites[this.site] = _cp(this.data); this.settings.active.sites[targetSite] = _cp(this.data);
this.settings.active.sites[this.site].type = 'user-defined'; this.settings.active.sites[targetSite].type = 'user-defined';
} }
} }
const pathParts = optionPath.split('.'); const pathParts = optionPath.split('.');
if (pathParts.length === 1) { if (pathParts.length === 1) {
this.settings.active.sites[this.site][optionPath] = optionValue; this.settings.active.sites[targetSite][optionPath] = optionValue;
} else { } else {
let iterator = this.settings.active.sites[this.site]; let iterator = this.settings.active.sites[targetSite];
let i; let i;
let iterated = ''; let iterated = '';
@ -474,7 +508,6 @@ export class SiteSettings {
}; };
} }
/** /**
* Sets temporary option value (for Persistence.UntilReload) * Sets temporary option value (for Persistence.UntilReload)
* @param optionPath path to value in object notation (dot separated) * @param optionPath path to value in object notation (dot separated)

View File

@ -61,10 +61,10 @@ class UI {
async init() { async init() {
if (!this.canRun()) { if (!this.canRun()) {
console.log('ui config: canRun returned false', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled) // console.log('ui config: canRun returned false', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled)
return; return;
} }
console.log('ui config: canRun returned truie', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled) // console.log('ui config: canRun returned truie', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled)
this.initUIContainer(); this.initUIContainer();
@ -190,6 +190,7 @@ class UI {
coords, coords,
playerDimensions: playerData.playerDimensions, playerDimensions: playerData.playerDimensions,
canShowUI: playerData.canShowUI, canShowUI: playerData.canShowUI,
isIframe: this.isIframe,
ts: +new Date() // this should be accurate enough for our purposes, ts: +new Date() // this should be accurate enough for our purposes,
}, },
uiURI uiURI
@ -253,7 +254,6 @@ class UI {
}, },
'uw-get-page-stats': { 'uw-get-page-stats': {
function: (config, routingData) => { function: (config, routingData) => {
console.log('got get page stats!');
this.eventBus.send( this.eventBus.send(
'uw-page-stats', 'uw-page-stats',
{ {

View File

@ -111,15 +111,6 @@ class PageInfo {
this.scheduleUrlCheck(); this.scheduleUrlCheck();
document.addEventListener('fullscreenchange', this.fsEventListener); document.addEventListener('fullscreenchange', this.fsEventListener);
this.eventBus.subscribeMulti({
'probe-video': {
function: () => {
console.log(`[${window.location}] probe-video received.`)
this.rescan();
}
}
});
} }
destroy() { destroy() {
@ -407,17 +398,20 @@ class PageInfo {
scheduleUrlCheck() { scheduleUrlCheck() {
try{ try{
if(this.urlCheckTimer){ if(this.urlCheckTimer){
clearTimeout(this.urlCheckTimer); clearTimeout(this.urlCheckTimer);
} }
let ths = this; let ths = this;
this.urlCheckTimer = setTimeout(function(){ this.urlCheckTimer = setTimeout(
ths.urlCheckTimer = null; function(){
ths.ghettoUrlCheck(); ths.urlCheckTimer = null;
ths = null; ths.ghettoUrlCheck();
}, this.settings.active.pageInfo.timeouts.urlCheck) ths = null;
},
this.settings.active.pageInfo.timeouts.urlCheck
);
} catch(e){ } catch(e){
this.logger.log('scheduleUrlCheck', "scheduling URL check failed. Here's why:",e) this.logger.log('scheduleUrlCheck', "scheduling URL check failed. Here's why:",e)
} }
@ -428,6 +422,7 @@ class PageInfo {
this.logger.warn('ghettoUrlCheck', "URL has changed. Triggering a rescan!"); this.logger.warn('ghettoUrlCheck', "URL has changed. Triggering a rescan!");
this.rescan(RescanReason.URL_CHANGE); this.rescan(RescanReason.URL_CHANGE);
this.eventBus.send("url-changed", {newUrl: window.location.href});
this.lastUrl = window.location.href; this.lastUrl = window.location.href;
} }

View File

@ -206,7 +206,7 @@ class PlayerData {
// do the rest // do the rest
this.invalid = false; this.invalid = false;
this.updatePlayer(); this.updatePlayer();
this.isTooSmall = (this.element.clientWidth < 1208 || this.element.clientHeight < 720); this.isTooSmall = this.checkIfTooSmall();;
this.initEventBus(); this.initEventBus();
this.dimensions = undefined; this.dimensions = undefined;
@ -236,6 +236,20 @@ class PlayerData {
} }
} }
/**
* Checks if player dimensions are too small for autodetection to run.
* Full screen is never too small, even if it's
* @param playerDimensions
* @returns
*/
private checkIfTooSmall(playerDimensions?: PlayerDimensions) {
if (playerDimensions) {
return !playerDimensions.fullscreen && (playerDimensions.width < 1208 || playerDimensions.height < 720);
} else {
return !document.fullscreenElement && (this.element.clientWidth < 1208 || this.element.clientHeight < 720);
}
}
private reloadPlayerDataConfig(siteConfUpdate) { private reloadPlayerDataConfig(siteConfUpdate) {
// this.siteSettings = siteConfUpdate; // this.siteSettings = siteConfUpdate;
this.updatePlayer(); this.updatePlayer();
@ -489,7 +503,7 @@ class PlayerData {
}); });
this.isTooSmall = !newDimensions.fullscreen && (newDimensions.width < 1208 || newDimensions.height < 720); this.isTooSmall = this.checkIfTooSmall(newDimensions);
} }
} }

View File

@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Ultrawidify", "name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.", "description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "6.3.9", "version": "6.3.93",
"icons": { "icons": {
"32":"res/icons/uw-32.png", "32":"res/icons/uw-32.png",
"64":"res/icons/uw-64.png" "64":"res/icons/uw-64.png"