Checkpoint: aard status indicator + trigger zone editor now kinda works

This commit is contained in:
Tamius Han 2024-12-26 14:58:14 +01:00
parent aabd5e75d8
commit 13cfb9ff14
20 changed files with 951 additions and 242 deletions

View File

@ -52,6 +52,8 @@ export type SettingsReloadComponent = 'PlayerData' | 'VideoData';
export type SettingsReloadFlags = true | SettingsReloadComponent; export type SettingsReloadFlags = true | SettingsReloadComponent;
export interface AardSettings { export interface AardSettings {
aardType: 'webgl' | 'legacy' | 'auto';
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.

View File

@ -2,12 +2,11 @@
<div <div
class="context-spawn uw-ui-trigger" class="context-spawn uw-ui-trigger"
style="z-index: 1000" style="z-index: 1000"
@mouseenter="(ev) => setTriggerZoneActive(true, ev)"
@mouseleave="(ev) => setTriggerZoneActive(false, ev)"
> >
<div <div
class="spawn-container uw-trigger" class="spawn-container uw-trigger"
:style="triggerZoneStyles" :style="triggerZoneStyles"
@mouseenter="(ev) => setTriggerZoneActive(true, ev)"
> >
&nbsp; &nbsp;
</div> </div>
@ -30,99 +29,126 @@
</div> </div>
</template> </template>
<slot> <slot>
<div class="menu-width">
<GhettoContextMenuItem :disableHover="true" :css="{'ard-blocked': true}">
<div v-if="statusFlags.hasDrm || true" class="smallcaps text-center">
<b>NOTE:</b><br/>
<b>Autodetection<br/>blocked by website</b>
</div>
<div>
<!--
Didn't manage to ensure that extension status pops up above other menu items in less than 3 minutes with z-index,
so wrapping 'status' and 'real menu items' in two different divs, ordering them in the opposite way, and then
ensuring correct ordering with flex-direction: column-reverse ended up being easier and faster.
-->
<div class="menu-width flex-reverse-order">
<div style="z-index: 1000">
<GhettoContextMenu alignment="right">
<template v-slot:activator>
Crop
</template>
<slot>
<GhettoContextMenuOption
v-for="(command, index) of settings?.active.commands.crop"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)"
>
</GhettoContextMenuOption>
</slot>
</GhettoContextMenu>
<GhettoContextMenu alignment="right">
<template v-slot:activator>
Stretch
</template>
<slot>
<GhettoContextMenuOption
v-for="(command, index) of settings?.active.commands.stretch"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)"
>
</GhettoContextMenuOption>
</slot>
</GhettoContextMenu>
<GhettoContextMenu alignment="right">
<template v-slot:activator>
<div class="context-item">
Align
</div>
</template>
<slot>
<GhettoContextMenuItem :disableHover="true" :css="{'reduced-padding': true}">
<AlignmentOptionsControlComponent
:eventBus="eventBus"
>
</AlignmentOptionsControlComponent>
</GhettoContextMenuItem>
</slot>
</GhettoContextMenu>
<!-- shortcut for configuring UI -->
<GhettoContextMenuOption
v-if="settings.active.newFeatureTracker?.['uw6.ui-popup']?.show > 0"
@click="showUwWindow('playerUiSettings')"
>
<span style="color: #fa6;">I hate this popup<br/></span>
<span style="font-size: 0.8em">
<span style="text-transform: uppercase; font-size: 0.8em">
<a @click="showUwWindow('playerUiSettings')">
Do something about it
</a> × <a @click="acknowledgeNewFeature('uw6.ui-popup')">keep the popup</a>
</span>
<br/>
<span style="opacity: 0.5">This menu option will show {{settings.active.newFeatureTracker?.['uw6.ui-popup']?.show}} more<br/> times; or until clicked or dismissed.<br/>
Also accessible via <span style="font-variant: small-caps">extension settings</span>.
</span>
</span>
</GhettoContextMenuOption>
<!-- -->
<GhettoContextMenuOption
@click="showUwWindow()"
label="Extension settings"
>
</GhettoContextMenuOption>
<GhettoContextMenuOption
@click="showUwWindow('playerDetection')"
label="Incorrect cropping?"
>
</GhettoContextMenuOption>
<GhettoContextMenuOption
@click="showUwWindow('about')"
label="Not working?"
>
</GhettoContextMenuOption>
</div> </div>
</GhettoContextMenuItem>
<GhettoContextMenu alignment="right"> <div style="z-index: 10000">
<template v-slot:activator> <GhettoContextMenuItem
Crop class="extension-status-messages"
</template> :disableHover="true"
<slot>
<GhettoContextMenuOption
v-for="(command, index) of settings?.active.commands.crop"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)"
> >
</GhettoContextMenuOption> Site compatibility:
</slot> <SupportLevelIndicator
</GhettoContextMenu> :siteSupportLevel="siteSupportLevel"
<GhettoContextMenu alignment="right">
<template v-slot:activator>
Stretch
</template>
<slot>
<GhettoContextMenuOption
v-for="(command, index) of settings?.active.commands.stretch"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)"
>
</GhettoContextMenuOption>
</slot>
</GhettoContextMenu>
<GhettoContextMenu alignment="right">
<template v-slot:activator>
<div class="context-item">
Align
</div>
</template>
<slot>
<GhettoContextMenuItem :disableHover="true" :css="{'reduced-padding': true}">
<AlignmentOptionsControlComponent
:eventBus="eventBus"
> >
</AlignmentOptionsControlComponent> </SupportLevelIndicator>
<div v-if="statusFlags.hasDrm" class="aard-blocked">
Autodetection potentially<br/>
unavailable due to <a href="https://en.wikipedia.org/wiki/Digital_rights_management">DRM</a>.
</div>
<div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked">
Autodetection blocked<br/>
by site/browser (CORS).
</div>
<div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked">
Autodetection unavailable<br/>
due to webgl error.
</div>
</GhettoContextMenuItem> </GhettoContextMenuItem>
</slot> </div>
</GhettoContextMenu>
<!-- shortcut for configuring UI -->
<GhettoContextMenuOption
v-if="settings.active.newFeatureTracker?.['uw6.ui-popup']?.show > 0"
@click="showUwWindow('playerUiSettings')"
>
<span style="color: #fa6;">I hate this popup<br/></span>
<span style="font-size: 0.8em">
<span style="text-transform: uppercase; font-size: 0.8em">
<a @click="showUwWindow('playerUiSettings')">
Do something about it
</a> × <a @click="acknowledgeNewFeature('uw6.ui-popup')">keep the popup</a>
</span>
<br/>
<span style="opacity: 0.5">This menu option will show {{settings.active.newFeatureTracker?.['uw6.ui-popup']?.show}} more<br/> times; or until clicked or dismissed.<br/>
Also accessible via <span style="font-variant: small-caps">extension settings</span>.
</span>
</span>
</GhettoContextMenuOption>
<!-- -->
<GhettoContextMenuOption
@click="showUwWindow()"
label="Extension settings"
>
</GhettoContextMenuOption>
<GhettoContextMenuOption
@click="showUwWindow('about')"
label="Not working?"
>
</GhettoContextMenuOption>
</div> </div>
</slot> </slot>
</GhettoContextMenu> </GhettoContextMenu>
</div> </div>
<div <div
v-if="settingsInitialized && uwWindowVisible" v-if="settingsInitialized && uwWindowVisible"
class="uw-window flex flex-col uw-clickable" class="uw-window flex flex-col uw-clickable"
@ -139,6 +165,19 @@
@preventClose="(event) => uwWindowFadeOutDisabled = event" @preventClose="(event) => uwWindowFadeOutDisabled = event"
></PlayerUIWindow> ></PlayerUIWindow>
</div> </div>
<div
class="context-spawn uw-ui-trigger"
style="z-index: 1000;"
>
<TriggerZoneEditor
class="uw-clickable"
:settings="settings"
:playerDimensions="playerDimensions"
>
</TriggerZoneEditor>
</div>
</template> </template>
<script> <script>
@ -154,6 +193,8 @@ import EventBus from '../ext/lib/EventBus';
import UIProbeMixin from './src/utils/UIProbeMixin'; import UIProbeMixin from './src/utils/UIProbeMixin';
import KeyboardShortcutParserMixin from './src/utils/KeyboardShortcutParserMixin'; import KeyboardShortcutParserMixin from './src/utils/KeyboardShortcutParserMixin';
import CommsMixin from './src/utils/CommsMixin'; import CommsMixin from './src/utils/CommsMixin';
import SupportLevelIndicator from './src/components/SupportLevelIndicator.vue';
import TriggerZoneEditor from './src/components/TriggerZoneEditor.vue';
export default { export default {
components: { components: {
@ -162,6 +203,8 @@ export default {
GhettoContextMenuItem, GhettoContextMenuItem,
GhettoContextMenuOption, GhettoContextMenuOption,
AlignmentOptionsControlComponent, AlignmentOptionsControlComponent,
SupportLevelIndicator,
TriggerZoneEditor,
}, },
mixins: [ mixins: [
UIProbeMixin, UIProbeMixin,
@ -220,10 +263,13 @@ export default {
statusFlags: { statusFlags: {
hasDrm: undefined, hasDrm: undefined,
aardErrors: undefined,
}, },
defaultWindowTab: 'videoSettings', defaultWindowTab: 'videoSettings',
saveState: {}, saveState: {},
siteSettings: undefined,
previewZoneVisible: false,
}; };
}, },
computed: { computed: {
@ -236,6 +282,12 @@ export default {
windowHeight() { windowHeight() {
return window.innerHeight; return window.innerHeight;
}, },
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
// IS SUPER HARAM
// THINGS WILL NOT WORK IF YOU USE ARROWS
siteSupportLevel() {
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
}
}, },
watch: { watch: {
showUi(visible) { showUi(visible) {
@ -262,6 +314,8 @@ export default {
}); });
this.settings = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger}); this.settings = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger});
this.settings.listenAfterChange(() => this.updateTriggerZones());
await this.settings.init(); await this.settings.init();
this.settingsInitialized = true; this.settingsInitialized = true;
@ -272,12 +326,20 @@ export default {
}); });
this.eventBus.subscribe('uw-config-broadcast', {function: (data) => { this.eventBus.subscribe('uw-config-broadcast', {function: (data) => {
if (data.type === 'drm-status') { switch (data.type) {
this.statusFlags.hasDrm = data.hasDrm; case 'drm-status':
this.statusFlags.hasDrm = data.hasDrm;
break;
case 'aard-error':
this.statusFlags.aardErrors = data.aardErrors;
break;
case 'player-dimensions':
console.log('player dimensions response received.', data);
this.playerDimensionsUpdate(data.data);
break;
} }
}}); }});
this.eventBus.subscribe('uw-set-ui-state', { function: (data) => { this.eventBus.subscribe('uw-set-ui-state', { function: (data) => {
if (data.globalUiVisible !== undefined) { if (data.globalUiVisible !== undefined) {
if (this.isGlobal) { if (this.isGlobal) {
@ -318,11 +380,19 @@ export default {
} }
); );
this.eventBus.subscribe('ui-trigger-zone-update', {
function: (data) => {
this.showTriggerZonePreview = data.previewZoneVisible;
// this.;
}
});
this.sendToParentLowLevel('uwui-get-role', null); this.sendToParentLowLevel('uwui-get-role', null);
this.sendToParentLowLevel('uwui-get-theme', null); this.sendToParentLowLevel('uwui-get-theme', null);
// this.sendToParentLowLevel('uw-bus-tunnel', {
action: 'get-player-dimensions'
});
}, },
methods: { methods: {
@ -344,6 +414,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(this.site);
} }
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':
@ -552,4 +623,34 @@ export default {
// } // }
} }
.extension-status-messages {
z-index: 1000;
text-transform: uppercase;
display: flex;
flex-direction: column;
text-align: center;
width: 112.25%;
transform: translate(-12.5%, 12.5%) scale(0.75);
> * {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
}
.flex-reverse-order {
display: flex;
flex-direction: column-reverse;
}
.aard-blocked {
color: #fa6;
}
.trigger-zone-preview {
border: 4px solid #fa4;
}
</style> </style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

View File

@ -5,41 +5,13 @@
<div class="popup-window-header"> <div class="popup-window-header">
<div class="header-title"> <div class="header-title">
<div class="popup-title">Ultrawidify <small>{{settings?.active?.version}} - {{BrowserDetect.processEnvChannel}}</small></div> <div class="popup-title">Ultrawidify <small>{{settings?.active?.version}} - {{BrowserDetect.processEnvChannel}}</small></div>
<div class="site-support-info"> <div class="site-support-info flex flex-row">
<div class="site-support-site">{{site}}</div> <div class="site-support-site">{{site}}</div>
<template v-if="inPlayer"> <SupportLevelIndicator
<div v-if="siteSupportLevel === 'official'" class="site-support official"> v-if="inPlayer"
<mdicon name="check-decagram" /> :siteSupportLevel="siteSupportLevel"
<div>Verified</div> >
<div class="tooltip">The extension is being tested and should work on this site.</div> </SupportLevelIndicator>
</div>
<div v-if="siteSupportLevel === 'community'" class="site-support community">
<mdicon name="handshake" />
<div>Community</div>
<div class="tooltip">
People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/>
Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or
is geoblocked.
</div>
</div>
<div v-if="siteSupportLevel === 'no-support'" class="site-support no-support">
<mdicon name="help-circle-outline" />
<div>Unknown</div>
<div class="tooltip">
Not officially supported. Extension will try to fix things, but no promises.<br/><br/>
Tamius (the dev) does not test the extension on this site for various reasons
(unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use).
</div>
</div>
<div v-if="siteSupportLevel === 'user-added'" class="site-support user-added">
<mdicon name="account" />
<div>Custom</div>
<div class="tooltip">
You have manually changed settings for this site. The extension is doing what you told it to do.
</div>
</div>
<mdicon v-if="siteSupportLevel === 'community'" class="site-support supported" name="checkbox-marked-circle" />
</template>
</div> </div>
</div> </div>
<div class="header-buttons"> <div class="header-buttons">
@ -116,7 +88,6 @@
:settings="settings" :settings="settings"
:eventBus="eventBus" :eventBus="eventBus"
> >
</PlayerUiSettings> </PlayerUiSettings>
<BaseExtensionSettings <BaseExtensionSettings
v-if="selectedTab === 'extensionSettings'" v-if="selectedTab === 'extensionSettings'"
@ -146,6 +117,11 @@
v-if="selectedTab === 'about'" v-if="selectedTab === 'about'"
> >
</AboutPanel> </AboutPanel>
<ResetBackupPanel
v-if="selectedTab === 'resetBackup'"
:settings="settings"
>
</ResetBackupPanel>
</div> </div>
</div> </div>
</div> </div>
@ -161,6 +137,9 @@ import BrowserDetect from '../../ext/conf/BrowserDetect'
import ChangelogPanel from './PlayerUiPanels/ChangelogPanel.vue' import ChangelogPanel from './PlayerUiPanels/ChangelogPanel.vue'
import AboutPanel from './PlayerUiPanels/AboutPanel.vue' import AboutPanel from './PlayerUiPanels/AboutPanel.vue'
import PlayerUiSettings from './PlayerUiPanels/PlayerUiSettings.vue' import PlayerUiSettings from './PlayerUiPanels/PlayerUiSettings.vue'
import ResetBackupPanel from './PlayerUiPanels/ResetBackupPanel.vue'
import SupportLevelIndicator from './components/SupportLevelIndicator.vue'
export default { export default {
components: { components: {
@ -171,7 +150,9 @@ export default {
DebugPanel, DebugPanel,
PlayerUiSettings, PlayerUiSettings,
ChangelogPanel, ChangelogPanel,
AboutPanel AboutPanel,
SupportLevelIndicator,
ResetBackupPanel,
}, },
mixins: [], mixins: [],
data() { data() {
@ -185,11 +166,12 @@ export default {
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' }, {id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
{id: 'playerUiSettings', label: 'In-player interface', icon: 'movie-cog-outline' }, {id: 'playerUiSettings', label: 'In-player interface', icon: 'movie-cog-outline' },
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'}, {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
{id: 'autodetectionSettings', label: 'Autodetection options', icon: ''}, {id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' }, // {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
// {id: 'debugging', label: 'Debugging', icon: 'bug-outline' } // {id: 'debugging', label: 'Debugging', icon: 'bug-outline' }
{id: 'changelog', label: 'What\'s new', icon: 'newspaper-plus' }, {id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
{id: 'about', label: 'About', icon: 'star-four-points-circle'} {id: 'about', label: 'About', icon: 'information-outline'},
{id: 'resetBackup', label: 'Reset and backup', icon: 'file-restore-outline'},
], ],
selectedTab: 'extensionSettings', selectedTab: 'extensionSettings',
BrowserDetect: BrowserDetect, BrowserDetect: BrowserDetect,
@ -272,86 +254,6 @@ export default {
overflow: hidden; overflow: hidden;
} }
.site-support-info {
display: flex;
flex-direction: row;
align-items: center;
.site-support-site {
font-size: 1.5em;
}
.site-support {
display: inline-flex;
flex-direction: row;
align-items: center;
margin-left: 1rem;
border-radius: 8px;
padding: 0rem 1.5rem 0rem 1rem;
position: relative;
.tooltip {
padding: 1rem;
display: none;
position: absolute;
bottom: 0;
transform: translateY(110%);
width: 42em;
background-color: rgba(0,0,0,0.90);
color: #ccc;
}
&:hover {
.tooltip {
display: block;
}
}
.mdi {
margin-right: 1rem;
}
&.official {
background-color: #fa6;
color: #000;
.mdi {
fill: #000 !important;
}
}
&.community {
background-color: rgb(85, 85, 179);
color: #fff;
.mdi {
fill: #fff !important;
}
}
&.no-support {
background-color: rgb(138, 65, 126);
color: #eee;
.mdi {
fill: #eee !important;
}
}
&.user-added {
border: 1px solid #ff0;
color: #ff0;
.mdi {
fill: #ff0 !important;
}
}
}
}
.content { .content {
flex-grow: 1; flex-grow: 1;
@ -392,6 +294,16 @@ export default {
} }
.site-support-info {
display: flex;
flex-direction: row;
align-items: bottom;
.site-support-site {
font-size: 1.5em;
}
}
.popup-panel { .popup-panel {
background-color: rgba(0,0,0,0.50); background-color: rgba(0,0,0,0.50);
color: #fff; color: #fff;

View File

@ -92,7 +92,7 @@ export default({
], ],
mounted() { mounted() {
this.settings.active.whatsNewChecked = true; this.settings.active.whatsNewChecked = true;
this.settings.save(); this.settings.saveWithoutReload();
} }
}); });
</script> </script>

View File

@ -9,7 +9,7 @@
<div class="label">Enable in-player UI</div> <div class="label">Enable in-player UI</div>
<input type="checkbox" v-model="settings.active.ui.inPlayer.enabled" /> <input type="checkbox" v-model="settings.active.ui.inPlayer.enabled" />
</div> </div>
<!--
<div <div
class="flex flex-col" class="flex flex-col"
:class="{disabled: settings.active.ui.inPlayer.enabled}" :class="{disabled: settings.active.ui.inPlayer.enabled}"
@ -53,8 +53,90 @@
</div> </div>
</div> </div>
<div class="field">
<div class="label">Edit trigger zone:</div>
<button>Edit</button>
</div>
<div v-if="settings.active.ui.inPlayer.activation === 'trigger-zone'"> <div v-if="settings.active.ui.inPlayer.activation === 'trigger-zone'">
Trigger zone size: <div class="trigger-zone-editor">
<div class="heading">
<h3>Trigger zone editor</h3>
</div>
<div class="field">
<div class="label">Trigger zone width:</div>
<div class="input range-input">
<input
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.width"
class="slider"
type="range"
min="0.1"
max="1"
step="0.01"
>
<input
:value="(settings.active.ui.inPlayer.triggerZoneDimensions.width * 100).toFixed(2)"
@input="(event) => setTriggerZoneSize('width', event.target.value)"
>
</div>
<div class="hint">
Width of the trigger zone (% of player area).
</div>
</div>
<div class="field">
<div class="label">Trigger zone height:</div>
<div class="input range-input">
<input
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.height"
type="range"
min="0.1"
max="1"
step="0.01"
>
<input
:value="(settings.active.ui.inPlayer.triggerZoneDimensions.height * 100).toFixed(2)"
@input="(event) => setTriggerZoneSize('width', event.target.value)"
>
</div>
<div class="hint">
Height of the trigger zone (% of player area).
</div>
</div>
<div class="field">
<div class="label">Trigger zone horizontal offset:</div>
<div class="input range-input">
<input
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetX"
type="range"
min="-100"
max="100"
>
<input
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetX"
>
</div>
<div class="hint">
By default, trigger zone is centered around the button. This option moves trigger zone left and right.
</div>
</div>
<div class="field">
<div class="label">Trigger zone vertical offset:</div>
<div class="input range-input">
<input
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetY"
type="range"
min="-100"
max="100"
>
<input
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetY"
>
</div>
<div class="hint">
By default, trigger zone is centered around the button. This option moves trigger zone up and down.
</div>
</div>
</div>
</div> </div>
<div class="field"> <div class="field">
@ -63,7 +145,7 @@
</div> </div>
<div>TODO: slider</div> <div>TODO: slider</div>
</div> </div>
</div> --> </div>
</div> </div>
</div> </div>
@ -96,6 +178,24 @@ export default {
setUiPage(key, event) { setUiPage(key, event) {
}, },
forceNumber(value) {
// Change EU format to US if needed
// | remove everything after second period if necessary
// | | | remove non-numeric characters
// | | | |
return value.replaceAll(',', '.').split('.', 2).join('.').replace(/[^0-9.]/g, '');
},
setTriggerZoneSize(key, value) {
let size = (+this.forceNumber(value) / 100);
if (isNaN(+size)) {
size = 0.5;
}
this.settings.active.ui.inPlayer.triggerZoneDimensions[key] = size;
this.settings.saveWithoutReload();
},
async openOptionsPage() { async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage(); BrowserDetect.runtime.openOptionsPage();
@ -118,4 +218,37 @@ export default {
.mt-4{ .mt-4{
margin-top: 1rem; margin-top: 1rem;
} }
.input {
max-width: 24rem;
}
.range-input {
display: flex;
flex-direction: row;
* {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
input {
max-width: 5rem;
}
input[type=range] {
max-width: none;
}
}
.trigger-zone-editor {
background-color: rgba(0,0,0,0.25);
padding-bottom: 2rem;
.field {
margin-bottom: -1em;
}
}
</style> </style>

View File

@ -0,0 +1,30 @@
<template>
<div class="flex flex-col">
<h1>Reset and backup</h1>
<p>
Pressing the button will reset settings to default without asking.
</p>
<button
class="danger"
@click="resetSettings"
>
Reset settings
</button>
</div>
</template>
<script>
export default {
props: {
settings: Object
},
methods: {
resetSettings() {
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
this.settings.saveWithoutReload();
}
}
}
</script>
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
<style lang="scss" src="../res-common/panels.scss" scoped module></style>
<style lang="scss" src="../res-common/common.scss" scoped module></style>

View File

@ -0,0 +1,17 @@
<template>
<div>
</div>
</template>
<script>
export default {
props: {
siteSettings: Object,
hasDrm: Boolean,
problems: Object,
}
}
</script>
<style lang="scss">
</style>

View File

@ -0,0 +1,137 @@
<template>
<div v-if="siteSupportLevel === 'official'" class="site-support official">
<mdicon name="check-decagram" />
<div v-if="!small">Verified</div>
<div class="tooltip">
<template v-if="small">Verified&nbsp;&nbsp;</template>
The extension is being tested and should work on this site.
</div>
</div>
<div v-if="siteSupportLevel === 'community'" class="site-support community">
<mdicon name="handshake" />
<div v-if="!small">Community</div>
<div class="tooltip">
<template v-if="small">Community&nbsp;&nbsp;</template>
People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/>
Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or
is geoblocked.
</div>
</div>
<div v-if="siteSupportLevel === 'no-support'" class="site-support no-support">
<mdicon name="help-circle-outline" />
<div v-if="!small">Unknown</div>
<div class="tooltip">
<template v-if="small">Unknown&nbsp;&nbsp;</template>
Not officially supported. Extension will try to fix things, but no promises.<br/><br/>
Tamius (the dev) does not test the extension on this site for various reasons
(unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use).
</div>
</div>
<div v-if="siteSupportLevel === 'user-added'" class="site-support user-added">
<mdicon name="account" />
<div v-if="!small">Custom</div>
<div class="tooltip">
<template v-if="small">Custom&nbsp;&nbsp;</template>
You have manually changed settings for this site. The extension is doing what you told it to do.
</div>
</div>
<div v-if="siteSupportLevel === 'community'">
<mdicon class="site-support no-support" name="checkbox-marked-circle" />
<div v-if="!small">Not supported</div>
<div class="tooltip">
<template v-if="small">Not supported&nbsp;&nbsp;</template>
Extension is known to not work with this site.
</div>
</div>
</template>
<script>
export default {
props: {
siteSupportLevel: String,
small: Boolean,
}
}
</script>
<style lang="scss" scoped>
.site-support {
display: inline-flex;
flex-direction: row;
align-items: center;
margin-left: 1rem;
border-radius: 8px;
padding: 0rem 1.5rem 0rem 1rem;
position: relative;
.tooltip {
padding: 1rem;
display: none;
position: absolute;
bottom: 0;
transform: translateY(110%);
width: 42em;
background-color: rgba(0,0,0,0.90);
color: #ccc;
z-index: 99999 !important;
white-space: normal;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
}
&:hover {
.tooltip {
display: block;
}
}
.mdi {
margin-right: 1rem;
}
&.official {
background-color: #fa6;
color: #000;
.mdi {
fill: #000 !important;
}
}
&.community {
background-color: rgb(85, 85, 179);
color: #fff;
.mdi {
fill: #fff !important;
}
}
&.no-support {
background-color: rgb(132, 24, 40);
color: #eee;
.mdi {
fill: #eee !important;
}
}
&.user-added {
border: 1px solid #ff0;
color: #ff0;
.mdi {
fill: #ff0 !important;
}
}
}
</style>

View File

@ -0,0 +1,196 @@
<template>
<div
v-if="settings?.active?.ui"
class="active-trigger-area uw-clickable"
:style="triggerZoneStyles"
>
<div class="trigger-zone-editor"
@mousedown="(event) => handleMouseDown('offset', event)"
>
<div
class="uw-clickable tl"
@mousedown.stop="(event) => handleMouseDown('tl', event)"
>
XX
</div>
<div
class="uw-clickable tr"
@mousedown.stop="(event) => handleMouseDown('tr', event)"
>
XX
</div>
<div
class="uw-clickable bl"
@mousedown.stop="(event) => handleMouseDown('bl', event)"
>
XX
</div>
<div
class="uw-clickable br"
@mousedown.stop="(event) => handleMouseDown('br', event)"
>
XX
</div>
</div>
</div>
</template>
<script>
export default {
props: [
'settings',
'playerDimensions',
],
watch: {
playerDimensions(newVal, oldVal) {
console.log('triggerzone -- dimensions changed!', this.playerDimensions, newVal, oldVal);
this.updateTriggerZones();
}
},
data() {
return {
triggerZoneStyles: {},
activeCornerDrag: undefined,
dragStartPosition: undefined,
dragStartConfiguration: undefined,
}
},
created() {
document.addEventListener("mouseup", this.handleMouseUp);
document.addEventListener("mousemove", this.handleMouseMove);
},
methods: {
updateTriggerZones() {
if (this.playerDimensions && this.settings?.active?.ui?.inPlayer?.triggerZoneDimensions) {
this.triggerZoneStyles = {
width: `${Math.round(this.playerDimensions.width * this.settings.active.ui.inPlayer.triggerZoneDimensions.width)}px`,
height: `${Math.round(this.playerDimensions.height * this.settings.active.ui.inPlayer.triggerZoneDimensions.height)}px`,
transform: `translate(${(this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX)}%, ${this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY}%)`,
};
}
},
handleMouseDown(corner, event) {
this.activeCornerDrag = corner;
// we need to save this because we don't know the location of the player element,
// just its dimensions ... that means we need to
this.dragStartPosition = {
x: event.clientX,
y: event.clientY
};
this.dragStartConfiguration = JSON.parse(JSON.stringify(this.settings.active.ui.inPlayer.triggerZoneDimensions));
console.log(`Mousedown on ${corner}`);
},
handleMouseUp(event) {
if (!this.activeCornerDrag) {
return;
}
this.activeCornerDrag = undefined;
this.settings.saveWithoutReload();
},
handleMouseMove(event) {
if (!this.activeCornerDrag) {
return;
}
if (this.activeCornerDrag === 'offset') {
this.handleMove(event);
} else {
this.handleResize(event);
}
this.updateTriggerZones();
},
handleResize(event) {
// drag distance in px
const dx = event.clientX - this.dragStartPosition.x;
const dy = event.clientY - this.dragStartPosition.y;
// convert drag distance to % of current width:
const dxr = dx / this.playerDimensions.width * 2;
const dyr = dy / this.playerDimensions.height * 2;
// // update settings:
let nw, nh;
switch (this.activeCornerDrag) {
case 'tl':
nw = this.dragStartConfiguration.width - dxr;
nh = this.dragStartConfiguration.height - dyr;
break;
case 'tr':
nw = this.dragStartConfiguration.width + dxr;
nh = this.dragStartConfiguration.height - dyr;
break;
case 'bl':
nw = this.dragStartConfiguration.width - dxr;
nh = this.dragStartConfiguration.height + dyr;
break;
case 'br':
nw = this.dragStartConfiguration.width + dxr;
nh = this.dragStartConfiguration.height + dyr;
break;
}
// ensure everything is properly limited
const cw = Math.min(0.95, Math.max(0.125, nw));
const ch = Math.min(0.95, Math.max(0.125, nh));
// // update properties
this.settings.active.ui.inPlayer.triggerZoneDimensions.width = cw;
this.settings.active.ui.inPlayer.triggerZoneDimensions.height = ch;
},
handleMove(event) {
const dx = event.clientX - this.dragStartPosition.x;
const dy = event.clientY - this.dragStartPosition.y;
// convert drag distance to % of current width:
const dxr = dx / this.playerDimensions.width;
const dyr = dy / this.playerDimensions.height;
// const [min, max] = this.settings.active.ui.inPlayer.popupAlignment === 'right' ? [5, 90] : [-90, -5];
// const [minCrossAxis, maxCrossAxis] = [-90, 90];
const min = -90;
const max = -5;
const minCrossAxis = -90;
const maxCrossAxis = 90;
const cx = Math.min(max, Math.max(min, this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX + dxr));
const cy = Math.min(maxCrossAxis, Math.max(minCrossAxis, this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY + dyr));
this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX = cx;
this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY = cy;
}
}
}
</script>
<style lang="scss" scoped>
.active-trigger-area {
background-image: url('/res/img/grid_512.webp');
}
.trigger-zone-editor {
width: 100%;
height: 100%;
position: relative;
> * {
position: absolute;
}
.tr, .tl {
top: 0;
}
.br, .bl {
bottom: 0;
}
.tl, .bl {
left: 0;
}
.tr, .br {
right: 0;
}
}
</style>

View File

@ -30,7 +30,7 @@ h1, h2, h3 {
padding: 0; padding: 0;
} }
.button { button, .button {
background-color: rgba($blackBg, $normalTransparentOpacity); background-color: rgba($blackBg, $normalTransparentOpacity);
padding: 0.5rem 2rem; padding: 0.5rem 2rem;
@ -51,6 +51,11 @@ h1, h2, h3 {
background-color: $primaryBg; background-color: $primaryBg;
border-color: rgba($primary, .5); border-color: rgba($primary, .5);
} }
&.danger {
background-color: #ff2211 !important;
color:#000;
}
} }
.b3 { .b3 {
margin: 0.25rem; margin: 0.25rem;

View File

@ -5,14 +5,14 @@ export default {
* We can handle events with the same function we use to handle events from * We can handle events with the same function we use to handle events from
* the content script. * the content script.
*/ */
document.addEventListener('mousemove', (event) => { document.addEventListener('mousemove', (event) => {
this.handleProbe({ this.handleProbe({
coords: { coords: {
x: event.clientX, x: event.clientX,
y: event.clientY y: event.clientY
} }
}, this.origin); }, this.origin);
}); });
}, },
data() { data() {
return { return {
@ -26,14 +26,28 @@ export default {
}, },
methods: { methods: {
playerDimensionsUpdate(dimensions) { playerDimensionsUpdate(dimensions) {
if (!dimensions.width || !dimensions.height) {
this.playerDimensions = undefined;
}
console.log('player dimensions update received:', dimensions);
if (dimensions?.width !== this.playerDimensions?.width || dimensions?.height !== this.playerDimensions?.height) { if (dimensions?.width !== this.playerDimensions?.width || dimensions?.height !== this.playerDimensions?.height) {
this.playerDimensions = dimensions; console.log('Player dimensions changed!', dimensions);
this.playerDimensions = dimensions;
this.updateTriggerZones();
}
},
updateTriggerZones() {
console.log('triggered zone style recheck. player dims:', this.playerDimensions, 'in player settings:', this.settings.active.ui);
if (this.playerDimensions && this.settings) {
this.triggerZoneStyles = { this.triggerZoneStyles = {
height: `${this.playerDimensions.height * 0.5}px`, width: `${Math.round(this.playerDimensions.width * this.settings.active.ui.inPlayer.triggerZoneDimensions.width)}px`,
width: `${this.playerDimensions.width * 0.5}px`, height: `${Math.round(this.playerDimensions.height * this.settings.active.ui.inPlayer.triggerZoneDimensions.height)}px`,
transform: `translate(${(this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX)}%, ${this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY}%)`, transform: `translate(${(this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX)}%, ${this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY}%)`,
}; };
console.log(
'player trigger zone css:', this.triggerZoneStyles
);
} }
}, },

View File

@ -166,6 +166,7 @@ const ExtensionConfPatch = [
for (const domOption in userOptions.sites[site].DOMConfig) for (const domOption in userOptions.sites[site].DOMConfig)
userOptions.sites[site].DOMConfig[domOption].customCss; userOptions.sites[site].DOMConfig[domOption].customCss;
} }
userOptions.arDetect.aardType = 'auto';
userOptions.ui = { userOptions.ui = {
inPlayer: { inPlayer: {
enabled: true, // enable by default on new installs enabled: true, // enable by default on new installs

View File

@ -15,6 +15,7 @@ if(Debug.debug)
const ExtensionConf: SettingsInterface = { const ExtensionConf: SettingsInterface = {
arDetect: { arDetect: {
aardType: 'auto',
disabledReason: "", // if automatic aspect ratio has been disabled, show reason disabledReason: "", // if automatic aspect ratio has been disabled, show reason
allowedMisaligned: 0.05, // top and bottom letterbox thickness can differ by this much. allowedMisaligned: 0.05, // 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.

View File

@ -36,6 +36,9 @@ class Settings {
//#region callbacks //#region callbacks
onSettingsChanged: any; onSettingsChanged: any;
afterSettingsSaved: any; afterSettingsSaved: any;
onChangedCallbacks: any[] = [];
afterSettingsChangedCallbacks: any[] = [];
//#endregion //#endregion
constructor(options) { constructor(options) {
@ -63,15 +66,31 @@ class Settings {
this.logger?.log('info', 'debug', 'Does parsedSettings.preventReload exist?', parsedSettings.preventReload, "Does callback exist?", !!this.onSettingsChanged); this.logger?.log('info', 'debug', 'Does parsedSettings.preventReload exist?', parsedSettings.preventReload, "Does callback exist?", !!this.onSettingsChanged);
if (!parsedSettings.preventReload && this.onSettingsChanged) { if (!parsedSettings.preventReload) {
try { try {
this.onSettingsChanged(); for (const fn of this.onChangedCallbacks) {
try {
fn();
} catch (e) {
this.logger?.log('warn', 'settings', "[Settings] afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
}
}
if (this.onSettingsChanged) {
this.onSettingsChanged();
}
this.logger?.log('info', 'settings', '[Settings] Update callback finished.') this.logger?.log('info', 'settings', '[Settings] Update callback finished.')
} catch (e) { } catch (e) {
this.logger?.log('error', 'settings', "[Settings] CALLING UPDATE CALLBACK FAILED. Reason:", e) this.logger?.log('error', 'settings', "[Settings] CALLING UPDATE CALLBACK FAILED. Reason:", e)
} }
} }
for (const fn of this.afterSettingsChangedCallbacks) {
try {
fn();
} catch (e) {
this.logger?.log('warn', 'settings', "[Settings] afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
}
}
if (this.afterSettingsSaved) { if (this.afterSettingsSaved) {
this.afterSettingsSaved(); this.afterSettingsSaved();
} }
@ -179,6 +198,7 @@ class Settings {
updateFn(this.active, this.getDefaultSettings()); updateFn(this.active, this.getDefaultSettings());
} catch (e) { } catch (e) {
this.logger?.log('error', 'settings', '[Settings::applySettingsPatches] Failed to execute update function. Keeping settings object as-is. Error:', e); this.logger?.log('error', 'settings', '[Settings::applySettingsPatches] Failed to execute update function. Keeping settings object as-is. Error:', e);
} }
} }
@ -358,6 +378,13 @@ class Settings {
getSiteSettings(site: string = window.location.hostname): SiteSettings { getSiteSettings(site: string = window.location.hostname): SiteSettings {
return new SiteSettings(this, site); return new SiteSettings(this, site);
} }
listenOnChange(fn: () => void): void {
this.onChangedCallbacks.push(fn);
}
listenAfterChange(fn: () => void): void {
this.afterSettingsChangedCallbacks.push(fn);
}
} }
export default Settings; export default Settings;

View File

@ -5,6 +5,7 @@ import Settings from '../Settings';
import VideoData from '../video-data/VideoData'; import VideoData from '../video-data/VideoData';
import { Corner } from './enums/corner.enum'; import { Corner } from './enums/corner.enum';
import { VideoPlaybackState } from './enums/video-playback-state.enum'; import { VideoPlaybackState } from './enums/video-playback-state.enum';
import { FallbackCanvas } from './gl/FallbackCanvas';
import { GlCanvas } from './gl/GlCanvas'; import { GlCanvas } from './gl/GlCanvas';
import { AardCanvasStore } from './interfaces/aard-canvas-store.interface'; import { AardCanvasStore } from './interfaces/aard-canvas-store.interface';
import { AardDetectionSample, generateSampleArray, resetSamples } from './interfaces/aard-detection-sample.interface'; import { AardDetectionSample, generateSampleArray, resetSamples } from './interfaces/aard-detection-sample.interface';
@ -234,6 +235,8 @@ export class Aard {
//#region internal state //#region internal state
public status: AardStatus = initAardStatus(); public status: AardStatus = initAardStatus();
private timers: AardTimers = initAardTimers(); private timers: AardTimers = initAardTimers();
private inFallback: boolean = false;
private fallbackReason: any;
private canvasStore: AardCanvasStore; private canvasStore: AardCanvasStore;
private testResults: AardTestResults; private testResults: AardTestResults;
private canvasSamples: AardDetectionSample; private canvasSamples: AardDetectionSample;
@ -245,6 +248,8 @@ export class Aard {
return undefined; return undefined;
} }
this.video.setAttribute('crossOrigin', 'anonymous');
const ratio = this.video.videoWidth / this.video.videoHeight; const ratio = this.video.videoWidth / this.video.videoHeight;
if (isNaN(ratio)) { if (isNaN(ratio)) {
return undefined; return undefined;
@ -284,8 +289,10 @@ export class Aard {
* This method should only ever be called from constructor. * This method should only ever be called from constructor.
*/ */
private init() { private init() {
this.canvasStore = { this.canvasStore = {
main: new GlCanvas(new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'})), main: this.createCanvas('main-gl')
}; };
@ -302,6 +309,42 @@ export class Aard {
this.start(); this.start();
} }
private createCanvas(canvasId: string, canvasType?: 'webgl' | 'fallback') {
if (canvasType) {
if (canvasType === this.settings.active.arDetect.aardType || this.settings.active.arDetect.aardType === 'auto') {
if (canvasType === 'webgl') {
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
} else if (canvasType === 'fallback') {
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
} else {
// TODO: throw error
}
} else {
// TODO: throw error
}
}
if (['auto', 'webgl'].includes(this.settings.active.arDetect.aardType)) {
try {
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
} catch (e) {
if (this.settings.active.arDetect.aardType !== 'webgl') {
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
}
console.error('[ultrawidify|Aard::createCanvas] could not create webgl canvas:', e);
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {webglError: true}});
throw e;
}
} else if (this.settings.active.arDetect.aardType === 'legacy') {
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
} else {
console.error('[ultrawidify|Aard::createCanvas] invalid value in settings.arDetect.aardType:', this.settings.active.arDetect.aardType);
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {invalidSettings: true}});
throw 'AARD_INVALID_SETTINGS';
}
}
//#endregion //#endregion
/** /**
@ -393,8 +436,35 @@ export class Aard {
do { do {
const imageData = await new Promise<Uint8Array>( const imageData = await new Promise<Uint8Array>(
resolve => { resolve => {
this.canvasStore.main.drawVideoFrame(this.video); try {
resolve(this.canvasStore.main.getImageData()); this.canvasStore.main.drawVideoFrame(this.video);
resolve(this.canvasStore.main.getImageData());
} catch (e) {
if (e.name === 'SecurityError') {
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {cors: true}});
this.stop();
}
if (this.canvasStore.main instanceof FallbackCanvas) {
if (this.inFallback) {
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: this.fallbackReason});
this.stop();
} else {
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {fallbackCanvasError: true}});
this.stop();
}
} else {
if (this.settings.active.arDetect.aardType === 'auto') {
this.canvasStore.main.destroy();
this.canvasStore.main = this.createCanvas('main-gl', 'fallback');
}
this.inFallback = true;
this.fallbackReason = {cors: true};
if (this.settings.active.arDetect.aardType !== 'auto') {
this.stop();
}
}
}
} }
); );
@ -407,6 +477,7 @@ export class Aard {
); );
if (this.testResults.notLetterbox) { if (this.testResults.notLetterbox) {
// TODO: reset aspect ratio to "AR not applied" // TODO: reset aspect ratio to "AR not applied"
console.log('NOT LETTERBOX!');
this.testResults.lastStage = 1; this.testResults.lastStage = 1;
break; break;
} }
@ -420,6 +491,7 @@ export class Aard {
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width, this.settings.active.arDetect.canvasDimensions.sampleCanvas.width,
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
); );
console.log('LETTERBOX SHRINK CHECK RESULT — IS GUARDLINE INVALIDATED?', this.testResults.guardLine.invalidated)
if (! this.testResults.guardLine.invalidated) { if (! this.testResults.guardLine.invalidated) {
this.checkLetterboxGrow( this.checkLetterboxGrow(
imageData, imageData,
@ -452,12 +524,17 @@ export class Aard {
// if detection is uncertain, we don't do anything at all // if detection is uncertain, we don't do anything at all
if (this.testResults.aspectRatioUncertain) { if (this.testResults.aspectRatioUncertain) {
console.info('aspect ratio not cettain.');
console.warn('check finished:', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
return; return;
} }
// TODO: emit debug values if debugging is enabled // TODO: emit debug values if debugging is enabled
this.testResults.isFinished = true; this.testResults.isFinished = true;
console.warn('check finished:', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
// if edge width changed, emit update event. // if edge width changed, emit update event.
if (this.testResults.aspectRatioUpdated) { if (this.testResults.aspectRatioUpdated) {
this.videoData.resizer.updateAr({ this.videoData.resizer.updateAr({
@ -1008,7 +1085,12 @@ export class Aard {
// fact that it makes the 'if' statement governing gradient detection // fact that it makes the 'if' statement governing gradient detection
// bit more nicely visible (instead of hidden among spagheti) // bit more nicely visible (instead of hidden among spagheti)
this.edgeScan(imageData, width, height); this.edgeScan(imageData, width, height);
console.log('edge scan:', JSON.parse(JSON.stringify(this.canvasSamples)));
this.validateEdgeScan(imageData, width, height); this.validateEdgeScan(imageData, width, height);
console.log('edge scan post valid:', JSON.parse(JSON.stringify(this.canvasSamples)));
// TODO: _if gradient detection is enabled, then: // TODO: _if gradient detection is enabled, then:
this.sampleForGradient(imageData, width, height); this.sampleForGradient(imageData, width, height);
@ -1061,6 +1143,7 @@ export class Aard {
x = 0; x = 0;
isImage = false; isImage = false;
finishedRows = 0; finishedRows = 0;
while (row < topEnd) { while (row < topEnd) {
i = 0; i = 0;
rowOffset = row * 4 * width; rowOffset = row * 4 * width;
@ -1126,6 +1209,7 @@ export class Aard {
|| imageData[rowOffset + x + 2] > this.testResults.blackLevel; || imageData[rowOffset + x + 2] > this.testResults.blackLevel;
if (!isImage) { if (!isImage) {
// console.log('(row:', row, ')', 'val:', imageData[rowOffset + x], 'col', x >> 2, x, 'pxoffset:', rowOffset + x, 'len:', imageData.length)
// TODO: maybe some day mark this pixel as checked by writing to alpha channel // TODO: maybe some day mark this pixel as checked by writing to alpha channel
i++; i++;
continue; continue;
@ -1272,6 +1356,7 @@ export class Aard {
// didn't change meaningfully from the first, in which chance we aren't. If the brightness increased // didn't change meaningfully from the first, in which chance we aren't. If the brightness increased
// anywhere between 'not enough' and 'too much', we mark the measurement as invalid. // anywhere between 'not enough' and 'too much', we mark the measurement as invalid.
if (lastSubpixel - firstSubpixel > this.settings.active.arDetect.edgeDetection.gradientTestMinDelta) { if (lastSubpixel - firstSubpixel > this.settings.active.arDetect.edgeDetection.gradientTestMinDelta) {
console.log('sample invalidated cus gradient:');
this.canvasSamples.top[i] = -1; this.canvasSamples.top[i] = -1;
} }
} }
@ -1645,6 +1730,26 @@ export class Aard {
const compensatedWidth = fileAr === canvasAr ? this.canvasStore.main.width : this.canvasStore.main.width * fileAr; const compensatedWidth = fileAr === canvasAr ? this.canvasStore.main.width : this.canvasStore.main.width * fileAr;
console.log(`
ASPECT RATIO CALCULATION:
canvas size: ${this.canvasStore.main.width} x ${this.canvasStore.main.height} (1:${this.canvasStore.main.width / this.canvasStore.main.height})
file size: ${this.video.videoWidth} x ${this.video.videoHeight} (1:${this.video.videoWidth / this.video.videoHeight})
compensated size: ${compensatedWidth} x ${this.canvasStore.main.height} (1:${compensatedWidth / this.canvasStore.main.height})
letterbox height: ${this.testResults.letterboxWidth}
net video height: ${this.canvasStore.main.height - (this.testResults.letterboxWidth * 2)}
calculated aspect ratio -----
${compensatedWidth} ${compensatedWidth} ${compensatedWidth}
= = = ${compensatedWidth / (this.canvasStore.main.height - (this.testResults.letterboxWidth * 2))}
${this.canvasStore.main.height} - 2 x ${this.testResults.letterboxWidth} ${this.canvasStore.main.height} - ${2 * this.testResults.letterboxWidth} ${this.canvasStore.main.height - (this.testResults.letterboxWidth * 2)}
`);
return compensatedWidth / (this.canvasStore.main.height - (this.testResults.letterboxWidth * 2)); return compensatedWidth / (this.canvasStore.main.height - (this.testResults.letterboxWidth * 2));
} }

View File

@ -7,7 +7,6 @@ export class FallbackCanvas extends GlCanvas {
constructor(options: GlCanvasOptions) { constructor(options: GlCanvasOptions) {
super(options); super(options);
this.context = this.canvas.getContext('2d');
} }
/** /**
@ -18,9 +17,14 @@ export class FallbackCanvas extends GlCanvas {
destroy() { } destroy() { }
protected initWebgl() { } protected initContext() {
this.context = this.canvas.getContext('2d', {desynchronized: true});
}
protected initWebgl() { }
drawVideoFrame(video: HTMLVideoElement) { drawVideoFrame(video: HTMLVideoElement) {
console.log('context:', this.context, 'canvas:', this.canvas );
this.context.drawImage(video, this.context.canvas.width, this.context.canvas.height); this.context.drawImage(video, this.context.canvas.width, this.context.canvas.height);
} }

View File

@ -95,16 +95,7 @@ export class GlCanvas {
this.canvas.setAttribute('width', `${options.width}`); this.canvas.setAttribute('width', `${options.width}`);
this.canvas.setAttribute('height', `${options.height}`); this.canvas.setAttribute('height', `${options.height}`);
this.gl = this.canvas.getContext('webgl'); this.initContext(options);
if (!this.gl) {
throw new Error('WebGL not supported');
}
if(options.id) {
this.canvas.setAttribute('id', options.id);
}
this.frameBufferSize = options.width * options.height * 4;
this.initWebgl(); this.initWebgl();
} }
@ -156,6 +147,24 @@ export class GlCanvas {
this.gl.deleteTexture(this.texture); this.gl.deleteTexture(this.texture);
} }
protected initContext(options: GlCanvasOptions) {
this.gl = this.canvas.getContext(
'webgl2',
{
preserveDrawingBuffer: true
}
);
if (!this.gl) {
throw new Error('WebGL not supported');
}
if(options.id) {
this.canvas.setAttribute('id', options.id);
}
this.frameBufferSize = options.width * options.height * 4;
}
protected initWebgl() { protected initWebgl() {
// Initialize the GL context // Initialize the GL context
this.gl.clearColor(0.0, 0.0, 0.0, 1.0); this.gl.clearColor(0.0, 0.0, 0.0, 1.0);

View File

@ -97,6 +97,16 @@ class PlayerData {
'get-player-tree': [{ 'get-player-tree': [{
function: () => this.handlePlayerTreeRequest() function: () => this.handlePlayerTreeRequest()
}], }],
'get-player-dimensions': [{
function: () => {
console.log('received get player dimensions! -- returning:', this.dimensions)
this.eventBus.send('uw-config-broadcast', {
type: 'player-dimensions',
data: this.dimensions
});
}
}],
'set-mark-element': [{ // NOTE: is this still used? 'set-mark-element': [{ // NOTE: is this still used?
function: (data) => this.markElement(data) function: (data) => this.markElement(data)
}], }],
@ -371,6 +381,10 @@ class PlayerData {
this.eventBus.send('restore-ar', null); this.eventBus.send('restore-ar', null);
this.eventBus.send('delayed-restore-ar', {delay: 500}); this.eventBus.send('delayed-restore-ar', {delay: 500});
// this.videoData.resizer?.restore(); // this.videoData.resizer?.restore();
this.eventBus.send('uw-config-broadcast', {
type: 'player-dimensions',
data: newDimensions
});
} }
} }
@ -378,6 +392,7 @@ class PlayerData {
this.trackDimensionChanges(); this.trackDimensionChanges();
} }
//#region player element change detection //#region player element change detection
/** /**
* Starts change detection. * Starts change detection.

BIN
src/res/img/grid_512.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB