Try to reduce message-passing to improve performance, make ultrawidify not run on videos smaller than 720p

This commit is contained in:
Tamius Han 2024-12-30 23:02:55 +01:00
parent 162318b439
commit cf01dd9397
26 changed files with 700 additions and 709 deletions

View File

@ -54,12 +54,20 @@ export type SettingsReloadFlags = true | SettingsReloadComponent;
export interface AardSettings { export interface AardSettings {
aardType: 'webgl' | 'legacy' | 'auto'; aardType: 'webgl' | 'legacy' | 'auto';
earlyStopOptions: {
stopAfterFirstDetection: boolean;
stopAfterTimeout: boolean;
stopTimeout: number;
},
disabledReason: string, // if automatic aspect ratio has been disabled, show reason disabledReason: string, // if automatic aspect ratio has been disabled, show reason
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much. allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
// Any more and we don't adjust ar. // Any more and we don't adjust ar.
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%) allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
timers: { // autodetection frequency timers: { // autodetection frequency
playing: number, // while playing playing: number, // while playing
playingReduced: number, // while video/player element has insufficient size
paused: number, // while paused paused: number, // while paused
error: number, // after error error: number, // after error
minimumTimeout: number, minimumTimeout: number,
@ -86,51 +94,6 @@ export interface AardSettings {
}, },
}, },
// NOTE: Black Frame is currently not in use.
blackframe: {
sufficientColorVariance: number, // calculate difference between average intensity and pixel, for every pixel for every color
// component. Average intensity is normalized to where 0 is black and 1 is biggest value for
// that component. If sum of differences between normalized average intensity and normalized
// component varies more than this % between color components, we can afford to use less strict
// cumulative threshold.
cumulativeThresholdLax: number,
cumulativeThresholdStrict: number,// if we add values of all pixels together and get more than this, the frame is bright enough.
// (note: blackframe is 16x9 px -> 144px total. cumulative threshold can be reached fast)
blackPixelsCondition: number, // How much pixels must be black (1 all, 0 none) before we consider frame as black. Takes
// precedence over cumulative threshold: if blackPixelsCondition is met, the frame is dark
// regardless of whether cumulative threshold has been reached.
},
// Used by old aspect ratio detection algorithm. Pls remove.
blackbar: {
blackLevel: number, // everything darker than 10/255 across all RGB components is considered black by
// default. blackLevel can decrease if we detect darker black.
threshold: number, // if pixel is darker than the sum of black level and this value, we count it as black
// on 0-255. Needs to be fairly high (8 might not cut it) due to compression
// artifacts in the video itself
frameThreshold: number, // threshold, but when doing blackframe test
imageThreshold: number, // in order to detect pixel as "not black", the pixel must be brighter than
// the sum of black level, threshold and this value.
gradientThreshold: number, // When trying to determine thickness of the black bars, we take 2 values: position of
// the last pixel that's darker than our threshold, and position of the first pixel that's
// brighter than our image threshold. If positions are more than this many pixels apart,
// we assume we aren't looking at letterbox and thus don't correct the aspect ratio.
gradientSampleSize: number, // How far do we look to find the gradient
maxGradient: number, // if two neighboring pixels in gradientSampleSize differ by more than this, then we aren't
// looking at a gradient
gradientNegativeTreshold: number,
gradientMaxSD: number, // reserved for future use
antiGradientMode: AntiGradientMode
},
// Also not in use, probs.
variableBlackbarThresholdOptions: { // In case of poor bitrate videos, jpeg artifacts may cause us issues
// FOR FUTURE USE
enabled: boolean, // allow increasing blackbar threshold
disableArDetectOnMax: boolean, // disable autodetection when threshold goes over max blackbar threshold
maxBlackbarThreshold: number, // max threshold (don't increase past this)
thresholdStep: number, // when failing to set aspect ratio, increase threshold by this much
increaseAfterConsecutiveResets: number // increase if AR resets this many times in a row
},
blackLevels: { blackLevels: {
defaultBlack: number, // By default, pixels darker than this are considered black. defaultBlack: number, // By default, pixels darker than this are considered black.
// (If detection algorithm detects darker blacks, black is considered darkest detected pixel) // (If detection algorithm detects darker blacks, black is considered darkest detected pixel)
@ -144,20 +107,6 @@ export interface AardSettings {
randomCols: number, // we add this many randomly selected columns to the static columns randomCols: number, // we add this many randomly selected columns to the static columns
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks, staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks,
}, },
guardLine: { // all pixels on the guardline need to be black, or else we trigger AR recalculation
// (if AR fails to be recalculated, we reset AR)
enabled: boolean,
ignoreEdgeMargin: number, // we ignore anything that pokes over the black line this close to the edge
// (relative to width of the sample)
imageTestThreshold: number, // when testing for image, this much pixels must be over blackbarThreshold
edgeTolerancePx: number, // black edge violation is performed this far from reported 'last black pixel'
edgeTolerancePercent: null // unused. same as above, except use % of canvas height instead of pixels
},
arSwitchLimiter: { // to be implemented
switches: number, // we can switch this many times
period: number // per this period
},
// pls deprecate and move things used // pls deprecate and move things used
edgeDetection: { edgeDetection: {

View File

@ -321,95 +321,107 @@ export default {
this.handleMessage(event); this.handleMessage(event);
}); });
this.eventBus.subscribe('uw-config-broadcast', {function: (data) => { this.eventBus.subscribeMulti(
switch (data.type) {
case 'drm-status':
this.statusFlags.hasDrm = data.hasDrm;
break;
case 'aard-error':
this.statusFlags.aardErrors = data.aardErrors;
break;
case 'player-dimensions':
this.playerDimensionsUpdate(data.data);
break;
}
}});
this.eventBus.subscribe('uw-set-ui-state', { function: (data) => {
if (data.globalUiVisible !== undefined) {
if (this.isGlobal) {
if (data.globalUiVisible) {
this.showUwWindow();
} else {
this.hideUwWindow(true);
}
// this.showPlayerUIAfterClose = data.showPlayerUIAfterClose;
} else {
// non global UIs are hidden while global overlay
// is visible and vice versa
// this.disabled = data.globalUiVisible;
this.saveState = {
uwWindowVisible: this.uwWindowVisible,
uwWindowFadeOutDisabled: this.uwWindowFadeOutDisabled,
uwWindowFadeOut: this.uwWindowFadeOut
};
this.uwWindowFadeOutDisabled = false;
this.hideUwWindow(true);
}
}
}});
this.eventBus.subscribe(
'uw-restore-ui-state',
{ {
function: (data) => { 'uw-config-broadcast': {
if (this.saveState) { function:
if (this.saveState.uwWindowVisible) { (data) => {
this.showUwWindow(); switch (data.type) {
case 'drm-status':
this.statusFlags.hasDrm = data.hasDrm;
break;
case 'aard-error':
this.statusFlags.aardErrors = data.aardErrors;
break;
case 'player-dimensions':
this.playerDimensionsUpdate(data.data);
break;
}
}
},
'uw-set-ui-state': {
function: (data) => {
if (data.globalUiVisible !== undefined) {
if (this.isGlobal) {
if (data.globalUiVisible) {
this.showUwWindow();
} else {
this.hideUwWindow(true);
}
// this.showPlayerUIAfterClose = data.showPlayerUIAfterClose;
} else {
// non global UIs are hidden while global overlay
// is visible and vice versa
// this.disabled = data.globalUiVisible;
this.saveState = {
uwWindowVisible: this.uwWindowVisible,
uwWindowFadeOutDisabled: this.uwWindowFadeOutDisabled,
uwWindowFadeOut: this.uwWindowFadeOut
};
this.uwWindowFadeOutDisabled = false;
this.hideUwWindow(true);
}
} }
this.uwWindowFadeOutDisabled = this.saveState.uwWindowFadeOutDisabled;
this.uwWindowFadeOut = this.saveState.uwWindowFadeOut;
} }
this.saveState = {}; },
} 'uw-restore-ui-state': {
} function: (data) => {
); if (this.saveState) {
if (this.saveState.uwWindowVisible) {
this.eventBus.subscribe('ui-trigger-zone-update', { this.showUwWindow();
function: (data) => { }
this.showTriggerZonePreview = data.previewZoneVisible; this.uwWindowFadeOutDisabled = this.saveState.uwWindowFadeOutDisabled;
// this.; this.uwWindowFadeOut = this.saveState.uwWindowFadeOut;
} }
}); this.saveState = {};
}
this.eventBus.subscribe( },
'start-trigger-zone-edit', 'uw-restore-ui-state': {
{ function: (data) => {
function: () => { if (this.saveState) {
this.triggerZoneEditorVisible = true; if (this.saveState.uwWindowVisible) {
this.uwWindowVisible = false; this.showUwWindow();
} }
} this.uwWindowFadeOutDisabled = this.saveState.uwWindowFadeOutDisabled;
); this.uwWindowFadeOut = this.saveState.uwWindowFadeOut;
}
this.eventBus.subscribe( this.saveState = {};
'finish-trigger-zone-edit', }
{ },
function: () => { 'ui-trigger-zone-update': {
this.triggerZoneEditorVisible = false; function: (data) => {
this.showUwWindow('playerUiSettings'); this.showTriggerZonePreview = data.previewZoneVisible;
} // this.;
} }
},
'start-trigger-zone-edit': {
function: () => {
this.triggerZoneEditorVisible = true;
this.uwWindowVisible = false;
}
},
'finish-trigger-zone-edit': {
function: () => {
this.triggerZoneEditorVisible = false;
this.showUwWindow('playerUiSettings');
}
},
},
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);
console.log('player overlay created — get player dims:')
this.sendToParentLowLevel('uw-bus-tunnel', { this.sendToParentLowLevel('uw-bus-tunnel', {
action: 'get-player-dimensions' action: 'get-player-dimensions'
}); });
}, },
destroyed() {
this.eventBus.unsubscribeAll(this)
},
methods: { methods: {
/** /**
* Gets URL of the browser settings page (i think?) * Gets URL of the browser settings page (i think?)

View File

@ -148,6 +148,7 @@ export default {
this.eventBus.subscribe( this.eventBus.subscribe(
'set-current-site', 'set-current-site',
{ {
source: this,
function: (config, context) => { function: (config, context) => {
if (this.site) { if (this.site) {
if (!this.site.host) { if (!this.site.host) {
@ -169,7 +170,7 @@ export default {
this.loadFrames(this.site); this.loadFrames(this.site);
} }
} },
); );
this.comms = new CommsClient('popup-port', this.logger, this.eventBus); this.comms = new CommsClient('popup-port', this.logger, this.eventBus);

View File

@ -204,13 +204,19 @@ export default {
this.eventBus.subscribe( this.eventBus.subscribe(
'uw-show-ui', 'uw-show-ui',
() => { {
if (this.inPlayer) { source: this,
return; // show-ui is only intended for global overlay function: () => {
} if (this.inPlayer) {
return; // show-ui is only intended for global overlay
}
},
} }
) )
}, },
destroyed() {
this.eventBus.unsubscribeAll(this);
},
methods: { methods: {
/** /**
* Gets URL of the browser settings page (i think?) * Gets URL of the browser settings page (i think?)

View File

@ -3,222 +3,252 @@
<div class="flex flex-row flex-wrap"> <div class="flex flex-row flex-wrap">
<!-- AARD performance metrics --> <!-- AARD performance metrics -->
<div class="sub-panel"> <div>
<div class="flex flex-row"> <div class="flex flex-row">
<h1><mdicon name="television-play" :size="32" /> Automatic Aspect Ratio Detection</h1> <h1>Automatic Aspect Ratio Detection</h1>
</div> </div>
<div class="sub-panel-content"> <div class="aard-settings-group">
<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"> <!-- the last time i tried to comment out this block, it didn't work properly v-if="false" it is -->
<div class="average-case"> <div v-if="false">
<div class="stats"> <p>
<b>Average: </b> <b>Autodetection performance</b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.averageTime ?? 0).toFixed(1)}} ms</span> | </p>
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.averageTime ?? 0).toFixed(1)}} ms</span> | <p>
<span class="processing"> Automatic aspect ratio detection is a resource-hungry feature.
processing {{ This page allows you to trade autodetection accuracy and/or frequency for
Math.max( better performance.
(performanceData?.total?.averageTime ?? 0) </p>
- (performanceData?.imageDraw?.averageTime ?? 0) <p>
- (performanceData?.blackFrame?.averageTime ?? 0), 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.
0 </p>
).toFixed(1) <div class="performance-graph-container">
}} ms <div class="performance-graph">
</span> <div class="time-budget hz144"></div>
</div> <div class="time-budget hz120"></div>
<div class="bar"> <div class="time-budget hz60"></div>
<div <div class="time-budget hz30"></div>
class="draw" <div class="time-budget hz24"></div>
:style="{'width': (performanceData?.imageDraw?.averageTime ?? 0) + '%'}" <div class="time-budget rest"></div>
>
</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="bar-container">
<div class="stats"> <div class="average-case">
<b>AR change (average): </b> <div class="stats">
<span class="draw">draw (main) {{(performanceData?.imageDraw?.averageTime ?? 0).toFixed(1)}} ms</span> | <b>Average: </b>
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.averageTime ?? 0).toFixed(1)}} ms</span> | <span class="draw">draw (main) {{(performanceData?.imageDraw?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">processing {{ <span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.averageTime ?? 0).toFixed(1)}} ms</span> |
( <span class="processing">
(performanceData?.fastLetterbox?.averageTime ?? 0) processing {{
+ (performanceData?.edgeDetect?.averageTime ?? 0) Math.max(
).toFixed(1) (performanceData?.total?.averageTime ?? 0)
}} ms</span> - (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>
<div class="bar"> <div class="worst-case">
<div <div class="stats">
class="draw" <b>Worst: </b>
:style="{'width': (performanceData?.imageDraw?.averageTime ?? 0) + '%'}" <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>
<div <div class="bar">
class="draw-blackframe" <div
:style="{'width': (performanceData?.blackFrame?.averageTime ?? 0) + '%'}" 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 </div>
class="processing"
:style="{ <div class="average-case">
'width': ( <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?.fastLetterbox?.averageTime ?? 0)
+ (performanceData?.edgeDetect?.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> </div>
</div> <div class="worst-case">
<div class="worst-case"> <div class="stats">
<div class="stats"> <b>AR change (worst): </b>
<b>AR change (worst): </b> <span class="draw">draw (main) {{(performanceData?.imageDraw?.worstTime ?? 0).toFixed(1)}} ms</span> |
<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="draw-blackframe">blackframe {{(performanceData?.blackFrame?.worstTime ?? 0).toFixed(1)}} ms</span> | <span class="processing">processing {{
<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?.fastLetterbox?.worstTime ?? 0)
+ (performanceData?.edgeDetect?.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>
</div>
</div> </div>
</div> </div>
<div class="settings-segment">
<h2>Basic settings</h2>
<div class="option">
<div class="name"> <div class="settings-segment">
Autodetection frequency <!-- <h2>Basic settings</h2> -->
<!-- <div class="field">
<div class="label">
Stop autodetection after first detection:
</div> </div>
<div class="description"> <div class="">
Shorter intervals (left side of the slider) are more responsive to changes in aspect ratio detections, <input type="checkbox" v-model="settings.active.arDetect.earlyStopOptions.stopAfterFirstDetection" />
but requires more system resources.
</div> </div>
<div class="indent"> </div>
<div class="flex flex-row row-padding"> <div class="field">
<div class="flex flex-input"> <div class="label">
More often&nbsp;<small>(~60/s)</small> Stop detection after a period of time:
<input type="range" </div>
:value="Math.log(settings.active.arDetect.timers.playing)" <div class="">
@change="setArCheckFrequency($event.target.value)" <input type="checkbox" v-model="settings.active.arDetect.earlyStopOptions.stopAfterTimeout" />
min="2.3" </div>
max="9.3" </div>
step="any" <div class="field">
/> <div class="label">
&nbsp; Less often&nbsp;<small>(~1/10s)</small> Stop detection after:
</div> </div>
</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="label">Autodetection frequency (time between samples)</div>
<div class="range-input">
<input
type="range"
:value="Math.log(settings.active.arDetect.timers.playing)"
@change="setArCheckFrequency($event.target.value)"
min="2.3"
max="9.3"
step="0.01"
/>
<input
v-model="settings.active.arDetect.timers.playing"
class="input"
type="text"
>
<div class="unit">ms</div>
</div> </div>
</div> </div>
<div class="option"> <div class="field">
<div class="name"> <div class="label">Frame extraction canvas type:</div>
Autodetection sensitivity <div class="select">
</div> <select v-model="settings.active.arDetect.aardType">
<div class="description"> <option value="auto">Automatic</option>
<option value="webgl">WebGL only</option>
<option value="fallback">Legacy / fallback</option>
</select>
</div> </div>
</div> </div>
@ -227,30 +257,13 @@
<div class="input"> <div class="input">
<input v-model="settings.active.arDetect.allowedMisaligned" /> <input v-model="settings.active.arDetect.allowedMisaligned" />
</div> </div>
<div class="description"> <div class="hint">
Ultrawidify detects letterbox only if video is vertically centered. Some people are bad at vertically Ultrawidify detects letterbox only if video is vertically centered. Some people are bad at vertically
centering the content, though. This is how off-center the video can be before autodetection will centering the content, though. This is how off-center the video can be before autodetection will
refuse to crop it. refuse to crop it (% of total height).
</div> </div>
</div> </div>
<div class="option">
<div class="name">Video sample size</div>
<div class="input">
<input v-model="settings.active.arDetect.canvasDimensions.sampleCanvas.width" /> x <input v-model="settings.active.arDetect.canvasDimensions.sampleCanvas.height" />
</div>
</div>
<div class="field">
<div class="label">Sample columns:</div>
<div class="input"><input v-model="settings.active.arDetect.sampling.staticCols" /></div>
</div>
<div class="field">
<div class="label">Sample rows:</div>
<div class="input"><input v-model="settings.active.arDetect.sampling.staticRows" /></div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -285,7 +298,16 @@ export default {
'site' 'site'
], ],
created() { created() {
this.eventBus.subscribe('uw-config-broadcast', {function: (config) => this.handleConfigBroadcast(config)}); this.eventBus.subscribe(
'uw-config-broadcast',
{
source: this,
function: (config) => this.handleConfigBroadcast(config)
}
);
},
destroyed() {
this.eventBus.unsubscribeAll(this);
}, },
mounted() { mounted() {
this.eventBus.sendToTunnel('get-aard-timing'); this.eventBus.sendToTunnel('get-aard-timing');
@ -306,6 +328,9 @@ export default {
async openOptionsPage() { async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage(); BrowserDetect.runtime.openOptionsPage();
}, },
setArCheckFrequency(event) {
this.settings.active.arDetect.timers.playing = Math.floor(Math.pow(Math.E, event));
},
refreshGraph() { refreshGraph() {
this.eventBus.sendToTunnel('get-aard-timing'); this.eventBus.sendToTunnel('get-aard-timing');
}, },
@ -326,6 +351,10 @@ export default {
<style lang="scss" scoped module> <style lang="scss" scoped module>
@import '../res-common/variables'; @import '../res-common/variables';
// .aard-settings-group {
// max-width: 69rem;
// }
.performance-graph-container { .performance-graph-container {
position: relative; position: relative;

View File

@ -373,11 +373,20 @@ export default({
'isPopup' 'isPopup'
], ],
created() { created() {
this.eventBus.subscribe('uw-config-broadcast', {function: (config) => this.handleElementStack(config)}); this.eventBus.subscribe(
'uw-config-broadcast',
{
source: this,
function: (config) => this.handleElementStack(config)
}
);
}, },
mounted() { mounted() {
this.getPlayerTree(); this.getPlayerTree();
}, },
destroyed() {
this.eventBus.unsubscribeAll(this);
},
computed: {}, computed: {},
methods: { methods: {
getPlayerTree() { getPlayerTree() {

View File

@ -241,24 +241,7 @@ export default {
max-width: 24rem; 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 { .trigger-zone-editor {
background-color: rgba(0,0,0,0.25); background-color: rgba(0,0,0,0.25);

View File

@ -66,49 +66,6 @@
></StretchOptionsPanel> ></StretchOptionsPanel>
</div> </div>
</div> </div>
<div class="flex flex-col">
<!-- VIDEO ALIGNMENT -->
<div class="sub-panel">
<div class="flex flex-row">
<mdicon name="align-horizontal-center" :size="32" />
<h1>Video alignment:</h1>
</div>
<div class="flex flex-row justify-center mt-4">
<alignment-options-control-component
:eventBus="eventBus"
>
</alignment-options-control-component>
</div>
<!-- <div class="flex flex-row flex-wrap">
<div class="m-t-0-33em display-block">
<input id="_input_zoom_site_allow_pan"
type="checkbox"
/>
Pan with mouse
</div>
</div> -->
</div>
<!-- ZOOM OPTIONS -->
<!-- <div class="sub-panel">
<div class="flex flex-row">
<mdicon name="magnify-plus-outline" :size="32" />
<h1>Manual zoom:</h1>
</div>
<ZoomOptionsPanel
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:isEditing="editMode"
></ZoomOptionsPanel>
</div> -->
</div>
</div> </div>
</div> </div>
</template> </template>
@ -153,11 +110,20 @@ export default {
'site' 'site'
], ],
created() { created() {
this.eventBus.subscribe('uw-config-broadcast', {function: (config) => this.handleConfigBroadcast(config)}); this.eventBus.subscribe(
'uw-config-broadcast',
{
source: this,
function: (config) => this.handleConfigBroadcast(config)
}
);
}, },
mounted() { mounted() {
this.eventBus.sendToTunnel('get-ar'); this.eventBus.sendToTunnel('get-ar');
}, },
destroyed() {
this.eventBus.unsubscribeAll(this);
},
components: { components: {
ShortcutButton, ShortcutButton,
EditShortcutButton, EditShortcutButton,

View File

@ -303,8 +303,6 @@ export default {
return value.replaceAll(',', '.').split('.', 2).join('.').replace(/[^0-9.\-]/g, ''); return value.replaceAll(',', '.').split('.', 2).join('.').replace(/[^0-9.\-]/g, '');
}, },
setValue(key, originalValue, isTextInput) { setValue(key, originalValue, isTextInput) {
console.log('trying to set value:', key, value, isTextInput);
let value = originalValue; let value = originalValue;
if (isTextInput) { if (isTextInput) {
value = (+this.forceNumber(value) / 100); value = (+this.forceNumber(value) / 100);
@ -312,8 +310,6 @@ export default {
value = +this.forceNumber(value); value = +this.forceNumber(value);
} }
console.log('rocessed value:', value);
if (isNaN(+value)) { if (isNaN(+value)) {
value = 0.5; value = 0.5;
} }

View File

@ -32,28 +32,6 @@
<h1>Zoom:</h1> <h1>Zoom:</h1>
</div> </div>
<ZoomOptionsPanel
style="margin-top: -2rem"
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:isEditing="false"
>
</ZoomOptionsPanel>
<div class="flex flex-row">
<mdicon name="crop" :size="24" />&nbsp;&nbsp;
<h1>Video alignment:</h1>
</div>
<div class="flex flex-row">
<alignment-options-control-component
:eventBus="eventBus"
>
</alignment-options-control-component>
</div>
</div> </div>
</template> </template>
@ -81,11 +59,20 @@ export default {
CropOptionsPanel, StretchOptionsPanel, ZoomOptionsPanel CropOptionsPanel, StretchOptionsPanel, ZoomOptionsPanel
}, },
created() { created() {
this.eventBus.subscribe('uw-config-broadcast', {function: (config) => this.handleConfigBroadcast(config)}); this.eventBus.subscribe(
'uw-config-broadcast',
{
source: this,
function: (config) => this.handleConfigBroadcast(config)
}
);
}, },
mounted() { mounted() {
this.eventBus.sendToTunnel('get-ar'); this.eventBus.sendToTunnel('get-ar');
}, },
destroyed() {
this.eventBus.unsubscribeAll(this);
},
methods: { methods: {
} }

View File

@ -73,18 +73,25 @@ button, .button {
align-items: center; align-items: center;
&.l2 {
margin-left: 4rem;
}
.label { .label {
flex: 0 0 25%; flex: 0 0 25%;
text-align: right; text-align: right;
padding-right: 1rem; padding-right: 1rem;
} }
.input {
.input, .range-input {
flex: 0 0 70%; flex: 0 0 70%;
max-width: 24rem;
background-color: rgba($blackBg, $normalTransparentOpacity); background-color: rgba($blackBg, $normalTransparentOpacity);
border: 1px solid transparent; border: 1px solid transparent;
border-bottom: 1px solid rgba(255,255,255,0.5); border-bottom: 1px solid rgba(255,255,255,0.5);
padding-top: 0.25rem; padding-top: 0.25rem;
padding-bottom: 0.25rem; padding-bottom: 0.25rem;
position: relative;
&:active, &:focus, &:focus-within { &:active, &:focus, &:focus-within {
border-bottom: 1px solid rgba($primary, 0.5); border-bottom: 1px solid rgba($primary, 0.5);
@ -97,7 +104,37 @@ button, .button {
background-color: transparent; background-color: transparent;
color: #fff; color: #fff;
} }
.unit {
position: absolute;
right: 0px;
pointer-events: none;
opacity: 0.69;
font-size: 0.8rem;
top: 0;
transform: translateY(69%);
}
} }
.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;
}
}
.hint { .hint {
padding-left: calc(25% + 1rem); padding-left: calc(25% + 1rem);
font-size: 0.8rem; font-size: 0.8rem;

View File

@ -77,6 +77,7 @@ export default class UWContent {
this.eventBus.subscribe( this.eventBus.subscribe(
'uw-restart', 'uw-restart',
{ {
source: this,
function: () => this.initPhase2() function: () => this.initPhase2()
} }
); );
@ -121,6 +122,7 @@ export default class UWContent {
} }
destroy() { destroy() {
this.eventBus.unsubscribeAll(this);
if (this.pageInfo) { if (this.pageInfo) {
this.pageInfo.destroy(); this.pageInfo.destroy();
} }

View File

@ -24,27 +24,27 @@ export default class UWServer {
} }
eventBusCommands = { eventBusCommands = {
'popup-set-selected-tab': [{ 'popup-set-selected-tab': {
function: (message) => this.setSelectedTab(message.selectedMenu, message.selectedSubitem) function: (message) => this.setSelectedTab(message.selectedMenu, message.selectedSubitem)
}], },
'has-video': [{ 'has-video': {
function: (message, context) => this.registerVideo(context.comms.sender) function: (message, context) => this.registerVideo(context.comms.sender)
}], },
'noVideo' : [{ 'noVideo' : {
function: (message, context) => this.unregisterVideo(context.comms.sender) function: (message, context) => this.unregisterVideo(context.comms.sender)
}], },
'inject-css': [{ 'inject-css': {
function: (message, context) => this.injectCss(message.cssString, context.comms.sender) function: (message, context) => this.injectCss(message.cssString, context.comms.sender)
}], },
'eject-css': [{ 'eject-css': {
function: (message, context) => this.removeCss(message.cssString, context.comms.sender) function: (message, context) => this.removeCss(message.cssString, context.comms.sender)
}], },
'replace-css': [{ 'replace-css': {
function: (message, context) => this.replaceCss(message.oldCssString, message.newCssString, context.comms.sender) function: (message, context) => this.replaceCss(message.oldCssString, message.newCssString, context.comms.sender)
}], },
'get-current-site': [{ 'get-current-site': {
function: (message, context) => this.getCurrentSite() function: (message, context) => this.getCurrentSite()
}] }
}; };
private gcTimeout: any; private gcTimeout: any;
@ -84,11 +84,8 @@ export default class UWServer {
this.eventBus = new EventBus({isUWServer: true}); this.eventBus = new EventBus({isUWServer: true});
for (const action in this.eventBusCommands) { this.eventBus.subscribeMulti(this.eventBusCommands, this);
for (const command of this.eventBusCommands[action]) {
this.eventBus.subscribe(action, command);
}
}
this.comms = new CommsServer(this); this.comms = new CommsServer(this);
this.eventBus.setComms(this.comms); this.eventBus.setComms(this.comms);

View File

@ -16,12 +16,20 @@ if(Debug.debug)
const ExtensionConf: SettingsInterface = { const ExtensionConf: SettingsInterface = {
arDetect: { arDetect: {
aardType: 'auto', aardType: 'auto',
earlyStopOptions: {
stopAfterFirstDetection: false,
stopAfterTimeout: false,
stopTimeout: 30,
},
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.
allowedArVariance: 0.075, // amount by which old ar can differ from the new (1 = 100%) allowedArVariance: 0.075, // amount by which old ar can differ from the new (1 = 100%)
timers: { // autodetection frequency timers: { // autodetection frequency
playing: 333, // while playing playing: 333, // while playing
playingReduced: 5000, // while playing at small sizes
paused: 3000, // while paused paused: 3000, // while paused
error: 3000, // after error error: 3000, // after error
minimumTimeout: 5, minimumTimeout: 5,
@ -48,48 +56,6 @@ const ExtensionConf: SettingsInterface = {
}, },
}, },
// samplingInterval: 10, // we sample at columns at (width/this) * [ 1 .. this - 1]
blackframe: {
sufficientColorVariance: 0.10, // calculate difference between average intensity and pixel, for every pixel for every color
// component. Average intensity is normalized to where 0 is black and 1 is biggest value for
// that component. If sum of differences between normalized average intensity and normalized
// component varies more than this % between color components, we can afford to use less strict
// cumulative threshold.
cumulativeThresholdLax: 1600,
cumulativeThresholdStrict: 2560,// if we add values of all pixels together and get more than this, the frame is bright enough.
// (note: blackframe is 16x9 px -> 144px total. cumulative threshold can be reached fast)
blackPixelsCondition: 0.6, // How much pixels must be black (1 all, 0 none) before we consider frame as black. Takes
// precedence over cumulative threshold: if blackPixelsCondition is met, the frame is dark
// regardless of whether cumulative threshold has been reached.
},
blackbar: {
blackLevel: 10, // everything darker than 10/255 across all RGB components is considered black by
// default. blackLevel can decrease if we detect darker black.
threshold: 16, // if pixel is darker than the sum of black level and this value, we count it as black
// on 0-255. Needs to be fairly high (8 might not cut it) due to compression
// artifacts in the video itself
frameThreshold: 4, // threshold, but when doing blackframe test
imageThreshold: 16, // in order to detect pixel as "not black", the pixel must be brighter than
// the sum of black level, threshold and this value.
gradientThreshold: 2, // When trying to determine thickness of the black bars, we take 2 values: position of
// the last pixel that's darker than our threshold, and position of the first pixel that's
// brighter than our image threshold. If positions are more than this many pixels apart,
// we assume we aren't looking at letterbox and thus don't correct the aspect ratio.
gradientSampleSize: 16, // How far do we look to find the gradient
maxGradient: 6, // if two neighboring pixels in gradientSampleSize differ by more than this, then we aren't
// looking at a gradient
gradientNegativeTreshold: -2,
gradientMaxSD: 6, // reserved for future use
antiGradientMode: AntiGradientMode.Lax,
},
variableBlackbarThresholdOptions: { // In case of poor bitrate videos, jpeg artifacts may cause us issues
// FOR FUTURE USE
enabled: true, // allow increasing blackbar threshold
disableArDetectOnMax: true, // disable autodetection when threshold goes over max blackbar threshold
maxBlackbarThreshold: 48, // max threshold (don't increase past this)
thresholdStep: 8, // when failing to set aspect ratio, increase threshold by this much
increaseAfterConsecutiveResets: 2 // increase if AR resets this many times in a row
},
blackLevels: { blackLevels: {
defaultBlack: 16, defaultBlack: 16,
blackTolerance: 4, blackTolerance: 4,
@ -101,19 +67,6 @@ const ExtensionConf: SettingsInterface = {
randomCols: 0, // we add this many randomly selected columns to the static columns randomCols: 0, // we add this many randomly selected columns to the static columns
staticRows: 9, // forms grid with staticSampleCols. Determined in the same way. For black frame checks staticRows: 9, // forms grid with staticSampleCols. Determined in the same way. For black frame checks
}, },
guardLine: { // all pixels on the guardline need to be black, or else we trigger AR recalculation
// (if AR fails to be recalculated, we reset AR)
enabled: true,
ignoreEdgeMargin: 0.20, // we ignore anything that pokes over the black line this close to the edge
// (relative to width of the sample)
imageTestThreshold: 0.1, // when testing for image, this much pixels must be over blackbarThreshold
edgeTolerancePx: 2, // black edge violation is performed this far from reported 'last black pixel'
edgeTolerancePercent: null // unused. same as above, except use % of canvas height instead of pixels
},
arSwitchLimiter: { // to be implemented
switches: 2, // we can switch this many times
period: 2.0 // per this period
},
edgeDetection: { edgeDetection: {
slopeTestWidth: 8, slopeTestWidth: 8,
gradientTestSamples: 8, gradientTestSamples: 8,

View File

@ -4,6 +4,7 @@ import CommsServer from './comms/CommsServer';
export interface EventBusCommand { export interface EventBusCommand {
isGlobal?: boolean, isGlobal?: boolean,
source?: any,
function: (commandData: any, context?: any) => void | Promise<void> function: (commandData: any, context?: any) => void | Promise<void>
} }
@ -24,8 +25,6 @@ export interface EventBusContext {
export default class EventBus { export default class EventBus {
private commands: { [x: string]: EventBusCommand[]} = {}; private commands: { [x: string]: EventBusCommand[]} = {};
private downstreamBuses: EventBus[] = [];
private upstreamBus?: EventBus;
private comms?: CommsClient | CommsServer; private comms?: CommsClient | CommsServer;
private disableTunnel: boolean = false; private disableTunnel: boolean = false;
@ -46,9 +45,6 @@ export default class EventBus {
//#region lifecycle //#region lifecycle
destroy() { destroy() {
this.commands = null; this.commands = null;
for (const bus of this.downstreamBuses) {
bus.destroy();
}
this.destroyIframeTunnelling(); this.destroyIframeTunnelling();
} }
//#endregion //#endregion
@ -57,38 +53,6 @@ export default class EventBus {
this.comms = comms; this.comms = comms;
} }
//#region bus hierarchy management (single page)
setUpstreamBus(eventBus: EventBus, stopRecursing: boolean = false) {
this.upstreamBus = eventBus;
if (!stopRecursing) {
this.upstreamBus.addDownstreamBus(this, true);
}
}
unsetUpstreamBus(stopRecursing: boolean = false) {
if (!stopRecursing) {
this.upstreamBus.removeDownstreamBus(this, false);
}
this.upstreamBus = undefined;
}
addDownstreamBus(eventBus: EventBus, stopRecursing: boolean = false) {
if (!this.downstreamBuses.includes(eventBus)) {
this.downstreamBuses.push(eventBus);
if (!stopRecursing) {
eventBus.setUpstreamBus(this, true);
}
}
}
removeDownstreamBus(eventBus: EventBus, stopRecursing: boolean = false) {
this.downstreamBuses = this.downstreamBuses.filter(x => x !== eventBus);
if (!stopRecursing) {
eventBus.unsetUpstreamBus(true);
}
}
subscribe(commandString: string, command: EventBusCommand) { subscribe(commandString: string, command: EventBusCommand) {
if (!this.commands[commandString]) { if (!this.commands[commandString]) {
this.commands[commandString] = [command]; this.commands[commandString] = [command];
@ -97,8 +61,31 @@ export default class EventBus {
} }
} }
subscribeMulti(commands: {[commandString: string]: EventBusCommand}, source?: any) {
for (const key in commands) {
this.subscribe(
key,
{
...commands[key],
source: source ?? commands[key].source
}
);
}
}
/**
* Removes all commands from a given source
* @param source
*/
unsubscribeAll(source: any) {
for (const commandString in this.commands) {
this.commands[commandString] = this.commands[commandString].filter(x => x.source !== source);
}
}
send(command: string, commandData: any, context?: EventBusContext) { send(command: string, commandData: any, context?: EventBusContext) {
// execute commands we have subscriptions for // execute commands we have subscriptions for
if (this.commands?.[command]) { if (this.commands?.[command]) {
for (const eventBusCommand of this.commands[command]) { for (const eventBusCommand of this.commands[command]) {
eventBusCommand.function(commandData, context); eventBusCommand.function(commandData, context);
@ -114,10 +101,6 @@ export default class EventBus {
if (context?.stopPropagation) { if (context?.stopPropagation) {
return; return;
} }
// propagate commands across the bus
this.sendUpstream(command, commandData, context);
this.sendDownstream(command, commandData, context);
} }
//#endregion //#endregion
@ -127,6 +110,7 @@ export default class EventBus {
* @param config * @param config
*/ */
sendToTunnel(command: string, config: any) { sendToTunnel(command: string, config: any) {
console.log('sending to tunnel from eventBus ....', this)
if (!this.disableTunnel) { if (!this.disableTunnel) {
window.parent.postMessage( window.parent.postMessage(
{ {
@ -145,25 +129,6 @@ export default class EventBus {
} }
} }
private sendDownstream(command: string, config: any, context?: EventBusContext, sourceEventBus?: EventBus) {
for (const eventBus of this.downstreamBuses) {
if (eventBus !== sourceEventBus) {
// prevent eventBus.send from auto-propagating the command
eventBus.send(command, config, {...context, stopPropagation: true});
eventBus.sendDownstream(command, config);
}
}
}
private sendUpstream(command: string, config: any, context?: EventBusContext) {
if (this.upstreamBus) {
// prevent eventBus.send from auto-propagating the command
this.upstreamBus.send(command, config, {...context, stopPropagation: true});
this.upstreamBus.sendUpstream(command, config, context);
this.upstreamBus.sendDownstream(command, config, context, this);
}
}
//#region iframe tunnelling //#region iframe tunnelling
private setupIframeTunnelling() { private setupIframeTunnelling() {
// forward messages coming from iframe tunnels // forward messages coming from iframe tunnels

View File

@ -222,9 +222,9 @@ export class Aard {
private arid: string; private arid: string;
private eventBusCommands = { private eventBusCommands = {
// 'get-aard-timing': [{ // 'get-aard-timing': {
// function: () => this.handlePerformanceDataRequest() // function: () => this.handlePerformanceDataRequest()
// }] // }
}; };
//#endregion //#endregion
@ -240,6 +240,8 @@ export class Aard {
private canvasStore: AardCanvasStore; private canvasStore: AardCanvasStore;
private testResults: AardTestResults; private testResults: AardTestResults;
private canvasSamples: AardDetectionSample; private canvasSamples: AardDetectionSample;
private forceFullRecheck: boolean = true;
//#endregion //#endregion
//#region getters //#region getters
@ -267,7 +269,8 @@ export class Aard {
this.settings = videoData.settings; this.settings = videoData.settings;
this.eventBus = videoData.eventBus; this.eventBus = videoData.eventBus;
this.initEventBus(); this.eventBus.subscribeMulti(this.eventBusCommands, this);
this.arid = (Math.random()*100).toFixed(); this.arid = (Math.random()*100).toFixed();
// we can tick manually, for debugging // we can tick manually, for debugging
@ -276,21 +279,12 @@ export class Aard {
this.init(); this.init();
} }
private initEventBus() {
for (const action in this.eventBusCommands) {
for (const command of this.eventBusCommands[action]) {
this.eventBus.subscribe(action, command);
}
}
}
/** /**
* Initializes Aard with default values and starts autodetection loop. * Initializes Aard with default values and starts autodetection loop.
* 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: this.createCanvas('main-gl') main: this.createCanvas('main-gl')
}; };
@ -351,6 +345,7 @@ export class Aard {
* Starts autodetection loop. * Starts autodetection loop.
*/ */
start() { start() {
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
this.videoData.resizer.lastAr = {type: AspectRatioType.AutomaticUpdate, ratio: this.defaultAr}; this.videoData.resizer.lastAr = {type: AspectRatioType.AutomaticUpdate, ratio: this.defaultAr};
@ -406,11 +401,14 @@ export class Aard {
} }
this.status.lastVideoStatus = videoState; this.status.lastVideoStatus = videoState;
if (Date.now() < this.timers.nextFrameCheckTime) { const now = Date.now();
if (now < (this.videoData.player.isTooSmall ? this.timers.reducedPollingNextCheckTime : this.timers.nextFrameCheckTime)) {
return false; return false;
} }
this.timers.nextFrameCheckTime = Date.now() + this.settings.active.arDetect.timers.playing; this.timers.nextFrameCheckTime = now + this.settings.active.arDetect.timers.playing;
this.timers.reducedPollingNextCheckTime = now + this.settings.active.arDetect.timers.playingReduced;
return true; return true;
} }
@ -419,6 +417,7 @@ export class Aard {
resetAardTestResults(this.testResults); resetAardTestResults(this.testResults);
resetSamples(this.canvasSamples); resetSamples(this.canvasSamples);
this.main(); this.main();
this.forceFullRecheck = false;
} else { } else {
} }
this.animationFrame = window.requestAnimationFrame( (ts: DOMHighResTimeStamp) => this.onAnimationFrame(ts)); this.animationFrame = window.requestAnimationFrame( (ts: DOMHighResTimeStamp) => this.onAnimationFrame(ts));
@ -477,7 +476,6 @@ 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;
} }
@ -486,19 +484,29 @@ export class Aard {
// Check if previously detected aspect ratio is still gucci. If it is, then // Check if previously detected aspect ratio is still gucci. If it is, then
// we can quit the loop without applying any aspect ratios (unless subtitle // we can quit the loop without applying any aspect ratios (unless subtitle
// detection is enabled, in which case we still run the subtitle test) // detection is enabled, in which case we still run the subtitle test)
this.checkLetterboxShrink( // If we stopped autodetection because of manual aspect ratio input, then
imageData, // checkLetterboxShrink and checkLetterboxGrow may return invalid results.
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width, // This is why we skip this check and force full recheck if forceFullRecheck
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height // flag is set.
); if (this.forceFullRecheck) {
console.log('LETTERBOX SHRINK CHECK RESULT — IS GUARDLINE INVALIDATED?', this.testResults.guardLine.invalidated) this.testResults.imageLine.invalidated = true;
if (! this.testResults.guardLine.invalidated) { this.testResults.guardLine.invalidated = true;
this.checkLetterboxGrow( } else {
this.checkLetterboxShrink(
imageData, imageData,
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
); );
if (! this.testResults.guardLine.invalidated) {
this.checkLetterboxGrow(
imageData,
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width,
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
);
}
} }
// Both need to be checked // Both need to be checked
if (! (this.testResults.imageLine.invalidated || this.testResults.guardLine.invalidated)) { if (! (this.testResults.imageLine.invalidated || this.testResults.guardLine.invalidated)) {
// TODO: ensure no aspect ratio changes happen // TODO: ensure no aspect ratio changes happen
@ -522,18 +530,34 @@ export class Aard {
// Also note that subtitle check should run on newest aspect ratio data, rather than lag one frame behind // Also note that subtitle check should run on newest aspect ratio data, rather than lag one frame behind
// But implementation details are something for future Tam to figure out // But implementation details are something for future Tam to figure out
// if detection is uncertain, we don't do anything at all // If forceFullRecheck is set, then 'not letterbox' should always force-reset the aspect ratio
// (as aspect ratio may have been set manually while autodetection was off)
if (this.testResults.notLetterbox) {
this.videoData.resizer.updateAr({
type: AspectRatioType.AutomaticUpdate,
ratio: this.defaultAr,
})
}
// if detection is uncertain, we don't do anything at all (unless if guardline was broken, in which case we reset)
if (this.testResults.aspectRatioUncertain) { if (this.testResults.aspectRatioUncertain) {
console.info('aspect ratio not cettain.'); console.info('aspect ratio not certain:', this.testResults.aspectRatioUncertainReason);
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'); 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 (this.testResults.guardLine.invalidated) {
this.videoData.resizer.setAr({type: AspectRatioType.AutomaticUpdate, ratio: this.defaultAr});
}
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'); console.warn(
`[${(+new Date() % 10000) / 100} | ${this.arid}]`,'check finished — aspect ratio updated:', this.testResults.aspectRatioUpdated,
'\nis video playing?', this.getVideoPlaybackState() === VideoPlaybackState.Playing,
'\n\n', 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) {
@ -543,12 +567,15 @@ export class Aard {
offset: this.testResults.letterboxOffset offset: this.testResults.letterboxOffset
}); });
} }
// if we got "no letterbox" OR aspectRatioUpdated
} catch (e) { } catch (e) {
console.warn('[Ultrawidify] Aspect ratio autodetection crashed for some reason.\n\nsome reason:', e); console.warn('[Ultrawidify] Aspect ratio autodetection crashed for some reason.\n\nsome reason:', e);
this.videoData.resizer.setAr({type: AspectRatioType.AutomaticUpdate, ratio: this.defaultAr}); this.videoData.resizer.setAr({type: AspectRatioType.AutomaticUpdate, ratio: this.defaultAr});
} }
} }
private getVideoPlaybackState(): VideoPlaybackState { private getVideoPlaybackState(): VideoPlaybackState {
try { try {
if (this.video.ended) { if (this.video.ended) {
@ -677,6 +704,8 @@ export class Aard {
// NOTE: but maybe we could, if blackLevel can only get lower than // NOTE: but maybe we could, if blackLevel can only get lower than
// the default value. // the default value.
if (this.testResults.notLetterbox) { if (this.testResults.notLetterbox) {
this.testResults.aspectRatioUncertain = false;
if (min < this.testResults.blackLevel) { if (min < this.testResults.blackLevel) {
this.testResults.blackLevel = min; this.testResults.blackLevel = min;
this.testResults.blackThreshold = min + 16; this.testResults.blackThreshold = min + 16;
@ -1611,6 +1640,7 @@ export class Aard {
// TODO: maybe we can figure out to guess aspect ratio in scenarios like this. // TODO: maybe we can figure out to guess aspect ratio in scenarios like this.
// But for the time being, just slap it with "inconclusive". // But for the time being, just slap it with "inconclusive".
this.testResults.aspectRatioUncertain = true; this.testResults.aspectRatioUncertain = true;
this.testResults.aspectRatioUncertainReason = 'TOP_ROW_MISMATCH';
return; return;
} else { } else {
// center matches one of the corners // center matches one of the corners
@ -1661,6 +1691,7 @@ export class Aard {
// TODO: maybe we can figure out to guess aspect ratio in scenarios like this. // TODO: maybe we can figure out to guess aspect ratio in scenarios like this.
// But for the time being, just slap it with "inconclusive". // But for the time being, just slap it with "inconclusive".
this.testResults.aspectRatioUncertain = true; this.testResults.aspectRatioUncertain = true;
this.testResults.aspectRatioUncertainReason += 'BOTTOM_ROW_MISMATCH';
return; return;
} else { } else {
// center matches one of the corners // center matches one of the corners
@ -1697,6 +1728,7 @@ export class Aard {
|| candidateB < this.settings.active.arDetect.edgeDetection.thresholds.minQualitySecondEdge || candidateB < this.settings.active.arDetect.edgeDetection.thresholds.minQualitySecondEdge
) { ) {
this.testResults.aspectRatioUncertain = true; this.testResults.aspectRatioUncertain = true;
this.testResults.aspectRatioUncertainReason = 'INSUFFICIENT_EDGE_DETECTION_QUALITY';
return; return;
} }
@ -1706,6 +1738,7 @@ export class Aard {
if (diff > maxOffset) { if (diff > maxOffset) {
this.testResults.aspectRatioUncertain = true; this.testResults.aspectRatioUncertain = true;
this.testResults.aspectRatioUncertainReason = 'LETTERBOX_NOT_CENTERED_ENOUGH';
return; return;
} }
if (maxOffset > 2) { if (maxOffset > 2) {
@ -1731,23 +1764,23 @@ 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(` // console.log(`
ASPECT RATIO CALCULATION: // ———— ASPECT RATIO CALCULATION: —————
canvas size: ${this.canvasStore.main.width} x ${this.canvasStore.main.height} (1:${this.canvasStore.main.width / this.canvasStore.main.height}) // 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}) // 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}) // compensated size: ${compensatedWidth} x ${this.canvasStore.main.height} (1:${compensatedWidth / this.canvasStore.main.height})
letterbox height: ${this.testResults.letterboxWidth} // letterbox height: ${this.testResults.letterboxWidth}
net video height: ${this.canvasStore.main.height - (this.testResults.letterboxWidth * 2)} // net video height: ${this.canvasStore.main.height - (this.testResults.letterboxWidth * 2)}
calculated aspect ratio ----- // calculated aspect ratio -----
${compensatedWidth} ${compensatedWidth} ${compensatedWidth} // ${compensatedWidth} ${compensatedWidth} ${compensatedWidth}
= = = ${compensatedWidth / (this.canvasStore.main.height - (this.testResults.letterboxWidth * 2))} // ——————————————— = —————————————— = —————— = ${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)} // ${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

@ -0,0 +1,7 @@
enum AardMode {
Continuous = 0,
UntilDetection = 1,
Timeout
}
export default AardMode;

View File

@ -2,6 +2,7 @@ import {VideoPlaybackState} from '../enums/video-playback-state.enum';
export interface AardStatus { export interface AardStatus {
aardActive: boolean, aardActive: boolean,
aardReducedPolling: boolean,
checkInProgress: boolean, checkInProgress: boolean,
lastVideoStatus: VideoPlaybackState, lastVideoStatus: VideoPlaybackState,
@ -10,6 +11,7 @@ export interface AardStatus {
export function initAardStatus(): AardStatus { export function initAardStatus(): AardStatus {
return { return {
aardActive: false, aardActive: false,
aardReducedPolling: true,
checkInProgress: false, checkInProgress: false,
lastVideoStatus: VideoPlaybackState.NotInitialized, lastVideoStatus: VideoPlaybackState.NotInitialized,
} }

View File

@ -34,6 +34,7 @@ export interface AardTestResults {
letterboxWidth: number, letterboxWidth: number,
letterboxOffset: number, letterboxOffset: number,
logoDetected: [boolean, boolean, boolean, boolean] logoDetected: [boolean, boolean, boolean, boolean]
aspectRatioUncertainReason?: string
} }
export function initAardTestResults(settings: AardSettings): AardTestResults { export function initAardTestResults(settings: AardSettings): AardTestResults {
@ -91,4 +92,5 @@ export function resetAardTestResults(results: AardTestResults): void {
results.letterboxWidth = 0; results.letterboxWidth = 0;
results.letterboxOffset = 0; results.letterboxOffset = 0;
results.aspectRatioUpdated = false; results.aspectRatioUpdated = false;
results.aspectRatioUncertainReason = null;
} }

View File

@ -1,9 +1,11 @@
export interface AardTimers { export interface AardTimers {
nextFrameCheckTime: number; nextFrameCheckTime: number;
reducedPollingNextCheckTime: number;
} }
export function initAardTimers(): AardTimers { export function initAardTimers(): AardTimers {
return { return {
nextFrameCheckTime: 0 nextFrameCheckTime: 0,
reducedPollingNextCheckTime: 0,
}; };
} }

View File

@ -148,14 +148,7 @@ class UI {
initMessaging() { initMessaging() {
// subscribe to events coming back to us. Unsubscribe if iframe vanishes. // subscribe to events coming back to us. Unsubscribe if iframe vanishes.
const messageHandlerFn = (message) => { window.addEventListener('message', this.messageHandlerFn);
if (!this.uiIframe?.contentWindow) {
window.removeEventListener('message', messageHandlerFn);
return;
}
this.handleMessage(message);
}
window.addEventListener('message', messageHandlerFn);
/* set up event bus tunnel from content script to UI necessary if we want to receive /* set up event bus tunnel from content script to UI necessary if we want to receive
@ -163,39 +156,45 @@ class UI {
* necessary for UI display in the popup. * necessary for UI display in the popup.
*/ */
this.eventBus.subscribe( this.eventBus.subscribeMulti(
'uw-config-broadcast',
{ {
function: (config, routingData) => { 'uw-config-broadcast': {
this.sendToIframe('uw-config-broadcast', config, routingData); function: (config, routingData) => {
} console.log('sending config broadcast from eventBus subscription:', this.eventBus)
} this.sendToIframe('uw-config-broadcast', config, routingData);
); }
this.eventBus.subscribe( },
'uw-set-ui-state', 'uw-set-ui-state': {
{ function: (config, routingData) => {
function: (config, routingData) => { if (config.globalUiVisible !== undefined) {
if (config.globalUiVisible !== undefined) { if (this.isGlobal) {
if (this.isGlobal) { this.setUiVisibility(config.globalUiVisible);
this.setUiVisibility(config.globalUiVisible); } else {
} else { this.setUiVisibility(!config.globalUiVisible);
this.setUiVisibility(!config.globalUiVisible); }
}
this.sendToIframe('uw-set-ui-state', {...config, isGlobal: this.isGlobal}, routingData);
}
},
'uw-restore-ui-state': {
function: (config, routingData) => {
if (!this.isGlobal) {
this.setUiVisibility(true);
this.sendToIframe('uw-restore-ui-state', config, routingData);
} }
} }
this.sendToIframe('uw-set-ui-state', {...config, isGlobal: this.isGlobal}, routingData);
} }
} },
) this
this.eventBus.subscribe( );
'uw-restore-ui-state', { }
function: (config, routingData) => {
if (!this.isGlobal) { messageHandlerFn = (message) => {
this.setUiVisibility(true); if (!this.uiIframe?.contentWindow) {
this.sendToIframe('uw-restore-ui-state', config, routingData); window.removeEventListener('message', this.messageHandlerFn);
} return;
} }
} this.handleMessage(message);
)
} }
setUiVisibility(visible) { setUiVisibility(visible) {
@ -280,6 +279,7 @@ class UI {
this.eventBus.send(busCommand.action, busCommand.config, busCommand.routingData); this.eventBus.send(busCommand.action, busCommand.config, busCommand.routingData);
break; break;
case 'uwui-get-role': case 'uwui-get-role':
console.log('handle get role!');
this.sendToIframeLowLevel('uwui-set-role', {role: this.isGlobal ? 'global' : 'player'}); this.sendToIframeLowLevel('uwui-set-role', {role: this.isGlobal ? 'global' : 'player'});
break; break;
case 'uwui-interface-ready': case 'uwui-interface-ready':
@ -306,6 +306,7 @@ class UI {
// because existence of UI is not guaranteed — UI is not shown when extension is inactive. // because existence of UI is not guaranteed — UI is not shown when extension is inactive.
// If extension is inactive due to "player element isn't big enough to justify it", however, // If extension is inactive due to "player element isn't big enough to justify it", however,
// we can still receive eventBus messages. // we can still receive eventBus messages.
console.log('sending to iframe - low level.')
if (this.element && this.uiIframe) { if (this.element && this.uiIframe) {
this.uiIframe.contentWindow?.postMessage( this.uiIframe.contentWindow?.postMessage(
{ {
@ -335,6 +336,7 @@ class UI {
// } // }
// routingData.crossedConnections.push(EventBusConnector.IframeBoundaryIn); // routingData.crossedConnections.push(EventBusConnector.IframeBoundaryIn);
console.warn('send to iframe — uw bus tunnel. Action:', action, actionConfig)
this.sendToIframeLowLevel( this.sendToIframeLowLevel(
'uw-bus-tunnel', 'uw-bus-tunnel',
{ {
@ -360,6 +362,8 @@ class UI {
} }
destroy() { destroy() {
window.removeEventListener('message', this.messageHandlerFn);
this.eventBus.unsubscribeAll(this);
// this.comms?.destroy(); // this.comms?.destroy();
this.uiIframe?.remove(); this.uiIframe?.remove();
this.element?.remove(); this.element?.remove();

View File

@ -36,18 +36,24 @@ export default class IframeManager {
if (this.isIframe) { if (this.isIframe) {
window.addEventListener('beforeunload', this.destroy); window.addEventListener('beforeunload', this.destroy);
this.eventBus.subscribe( this.eventBus.subscribe(
'uw-frame-ping', {function: (cmd, context) => this.handleIframePing(context)} 'uw-frame-ping',
{
source: this,
function: (cmd, context) => this.handleIframePing(context)
}
); );
this.eventBus.send( this.eventBus.send(
'uw-frame-register', {host: window.location.hostname}, {comms: {forwardTo: 'all-frames'}} 'uw-frame-register', {host: window.location.hostname}, {comms: {forwardTo: 'all-frames'}}
); );
} else { } else {
this.eventBus.subscribe( this.eventBus.subscribeMulti(
'uw-frame-register', {function: (data, context) => this.handleIframeRegister(data, context)} {
); 'uw-frame-register': {function: (data, context) => this.handleIframeRegister(data, context)},
this.eventBus.subscribe( 'uw-frame-destroyed': {function: (cmd, context) => this.handleFrameDestroyed(context)}
'uw-frame-destroyed', {function: (cmd, context) => this.handleFrameDestroyed(context)} },
this
); );
// register all frames to re-register themselves // register all frames to re-register themselves
@ -69,6 +75,7 @@ export default class IframeManager {
} }
}, },
) )
this.eventBus.unsubscribeAll(this);
} }
private handleIframePing(context) { private handleIframePing(context) {

View File

@ -179,16 +179,22 @@ class PageInfo {
} }
} }
getVideos(host) { getVideos(): HTMLVideoElement[] {
const videoQs = this.siteSettings.getCustomDOMQuerySelector('video'); const videoQs = this.siteSettings.getCustomDOMQuerySelector('video');
if (videoQs){ let videos: HTMLVideoElement[] = [];
const videos = document.querySelectorAll(videoQs) as NodeListOf<HTMLVideoElement>;
if (videos.length) { if (videoQs){
return videos; videos = Array.from(document.querySelectorAll(videoQs) as NodeListOf<HTMLVideoElement> ?? []);
} } else{
videos = Array.from(document.getElementsByTagName('video') ?? []);
} }
return document.getElementsByTagName('video');
// filter out videos that aren't big enough
videos = videos.filter(
(v: HTMLVideoElement) => v.clientHeight > 720 && v.clientWidth > 1208
);
return videos;
} }
hasVideo() { hasVideo() {
@ -206,6 +212,7 @@ class PageInfo {
// is there any video data objects that had their HTML elements removed but not yet // is there any video data objects that had their HTML elements removed but not yet
// destroyed? We clean that up here. // destroyed? We clean that up here.
const orphans = this.videos.filter(x => !document.body.contains(x.element)); const orphans = this.videos.filter(x => !document.body.contains(x.element));
for (const orphan of orphans) { for (const orphan of orphans) {
orphan.videoData.destroy(); orphan.videoData.destroy();
} }
@ -215,7 +222,7 @@ class PageInfo {
// add new videos // add new videos
try{ try{
let vids = this.getVideos(window.location.hostname); let vids = this.getVideos();
if(!vids || vids.length == 0){ if(!vids || vids.length == 0){
this.hasVideos = false; this.hasVideos = false;

View File

@ -80,6 +80,7 @@ class PlayerData {
halted: boolean = true; halted: boolean = true;
isFullscreen: boolean = !!document.fullscreenElement; isFullscreen: boolean = !!document.fullscreenElement;
isTheaterMode: boolean = false; // note: fullscreen mode will count as theaterMode if player was in theater mode before fs switch. This is desired, so far. isTheaterMode: boolean = false; // note: fullscreen mode will count as theaterMode if player was in theater mode before fs switch. This is desired, so far.
isTooSmall: boolean = true;
//#region misc stuff //#region misc stuff
extensionMode: any; extensionMode: any;
@ -87,6 +88,8 @@ class PlayerData {
private playerIdElement: any; private playerIdElement: any;
private observer: ResizeObserver; private observer: ResizeObserver;
private trackChangesTimeout: any;
private ui: UI; private ui: UI;
elementStack: any[] = []; elementStack: any[] = [];
@ -99,7 +102,8 @@ class PlayerData {
}], }],
'get-player-dimensions': [{ 'get-player-dimensions': [{
function: () => { function: () => {
this.eventBus.send('uw-config-broadcast', { console.log('got get-player-dimensions');
this.eventBus.send('—————————————————————————— uw-config-broadcast', {
type: 'player-dimensions', type: 'player-dimensions',
data: this.dimensions data: this.dimensions
}); });
@ -157,20 +161,12 @@ class PlayerData {
// do the rest // do the rest
this.invalid = false; this.invalid = false;
this.element = this.getPlayer(); this.element = this.getPlayer();
this.isTooSmall = (this.element.clientWidth < 1208 || this.element.clientHeight < 720);
this.initEventBus(); this.initEventBus();
// this.notificationService = new PlayerNotificationUi(this.element, this.settings, this.eventBus); // we defer UI creation until player element is big enough
if (this.videoData.settings.active.ui?.inPlayer?.enabled) { // this happens in trackDimensionChanges!
this.ui = new UI(
'ultrawidifyUi',
{
parentElement: this.element,
eventBus: this.eventBus,
playerData: this,
uiSettings: this.videoData.settings.active.ui
}
);
}
this.dimensions = undefined; this.dimensions = undefined;
this.overlayNode = undefined; this.overlayNode = undefined;
@ -233,10 +229,46 @@ class PlayerData {
destroy() { destroy() {
document.removeEventListener('fullscreenchange', this.dimensionChangeListener); document.removeEventListener('fullscreenchange', this.dimensionChangeListener);
this.stopChangeDetection(); this.stopChangeDetection();
this.ui?.destroy();
this.notificationService?.destroy(); this.notificationService?.destroy();
} }
//#endregion //#endregion
deferredUiInitialization(playerDimensions) {
if (this.ui || ! this.videoData.settings.active.ui?.inPlayer?.enabled) {
return;
}
if (
this.isFullscreen
|| (
playerDimensions.width > 1208
&& playerDimensions.height > 720
)
) {
this.ui = new UI(
'ultrawidifyUi',
{
parentElement: this.element,
eventBus: this.eventBus,
playerData: this,
uiSettings: this.videoData.settings.active.ui
}
);
if (this.runLevel < RunLevel.UIOnly) {
this.ui?.disable();
}
if (this.runLevel >= RunLevel.UIOnly) {
this.ui?.enable();
this.startChangeDetection();
}
if (this.runLevel >= RunLevel.CustomCSSActive) {
this.element.classList.add(this.playerCssClass);
}
}
}
/** /**
* Sets extension runLevel and sets or unsets appropriate css classes as necessary * Sets extension runLevel and sets or unsets appropriate css classes as necessary
* @param runLevel * @param runLevel
@ -253,11 +285,11 @@ class PlayerData {
this.element.classList.remove(this.playerCssClass); this.element.classList.remove(this.playerCssClass);
} }
if (runLevel < RunLevel.UIOnly) { if (runLevel < RunLevel.UIOnly) {
this.ui.disable(); this.ui?.disable();
} }
} else { } else {
if (runLevel >= RunLevel.UIOnly) { if (runLevel >= RunLevel.UIOnly) {
this.ui.enable(); this.ui?.enable();
this.startChangeDetection(); this.startChangeDetection();
} }
if (runLevel >= RunLevel.CustomCSSActive) { if (runLevel >= RunLevel.CustomCSSActive) {
@ -313,12 +345,20 @@ class PlayerData {
this.detectTheaterMode(); this.detectTheaterMode();
} }
// defer creating UI
this.deferredUiInitialization(currentPlayerDimensions);
// if dimensions of the player box are the same as the last known // if dimensions of the player box are the same as the last known
// dimensions, we don't have to do anything // dimensions, we don't have to do anything ... in theory. In practice,
// sometimes restore-ar doesn't appear to register the first time, and
// this function doesn't really run often enough to warrant finding a
// real, optimized fix.
if ( if (
this.dimensions?.width == currentPlayerDimensions.width this.dimensions?.width == currentPlayerDimensions.width
&& this.dimensions?.height == currentPlayerDimensions.height && this.dimensions?.height == currentPlayerDimensions.height
) { ) {
this.eventBus.send('restore-ar', null);
this.eventBus.send('delayed-restore-ar', {delay: 500});
this.dimensions = currentPlayerDimensions; this.dimensions = currentPlayerDimensions;
return; return;
} }
@ -326,7 +366,7 @@ class PlayerData {
// in every other case, we need to check if the player is still // in every other case, we need to check if the player is still
// big enough to warrant our extension running. // big enough to warrant our extension running.
this.handleSizeConstraints(currentPlayerDimensions); this.handleSizeConstraints(currentPlayerDimensions);
this.handleDimensionChanges(currentPlayerDimensions, this.dimensions); // this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
// Save current dimensions to avoid triggering this function pointlessly // Save current dimensions to avoid triggering this function pointlessly
this.dimensions = currentPlayerDimensions; this.dimensions = currentPlayerDimensions;
@ -343,14 +383,11 @@ class PlayerData {
const canEnable = this.siteSettings.isEnabledForEnvironment(this.isFullscreen, this.isTheaterMode) === ExtensionMode.Enabled; const canEnable = this.siteSettings.isEnabledForEnvironment(this.isFullscreen, this.isTheaterMode) === ExtensionMode.Enabled;
if (this.runLevel === RunLevel.Off && canEnable) { if (this.runLevel === RunLevel.Off && canEnable) {
console.log('runLevel: off -> [anything]');
this.eventBus.send('restore-ar', null); this.eventBus.send('restore-ar', null);
// must be called after // must be called after
this.handleDimensionChanges(currentPlayerDimensions, this.dimensions); this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
} else if (!canEnable && this.runLevel !== RunLevel.Off) { } else if (!canEnable && this.runLevel !== RunLevel.Off) {
// must be called before // must be called before
console.log('runLevel: [anything] -> off');
this.handleDimensionChanges(currentPlayerDimensions, this.dimensions); this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
this.setRunLevel(RunLevel.Off); this.setRunLevel(RunLevel.Off);
} }
@ -358,7 +395,6 @@ class PlayerData {
private handleDimensionChanges(newDimensions: PlayerDimensions, oldDimensions: PlayerDimensions) { private handleDimensionChanges(newDimensions: PlayerDimensions, oldDimensions: PlayerDimensions) {
console.log('handling dimension changes\n\nold dimensions:', oldDimensions, '\nnew dimensions:', newDimensions, '\n\nis enabled:', this.enabled, this.runLevel, RunLevel);
if (this.runLevel === RunLevel.Off ) { if (this.runLevel === RunLevel.Off ) {
this.logger.log('info', 'debug', "[PlayerDetect] player size changed, but PlayerDetect is in disabled state. The player element is probably too small."); this.logger.log('info', 'debug', "[PlayerDetect] player size changed, but PlayerDetect is in disabled state. The player element is probably too small.");
return; return;
@ -374,7 +410,6 @@ class PlayerData {
|| newDimensions?.height != oldDimensions?.height || newDimensions?.height != oldDimensions?.height
|| newDimensions?.fullscreen != oldDimensions?.fullscreen || newDimensions?.fullscreen != oldDimensions?.fullscreen
){ ){
console.log('dimensions changed + we are enabled. Sending restore-ar ...');
// If player size changes, we restore aspect ratio // If player size changes, we restore aspect ratio
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});
@ -383,6 +418,9 @@ class PlayerData {
type: 'player-dimensions', type: 'player-dimensions',
data: newDimensions data: newDimensions
}); });
this.isTooSmall = !newDimensions.fullscreen && (newDimensions.width < 1208 || newDimensions.height < 720);
} }
} }
@ -706,6 +744,7 @@ class PlayerData {
// this populates this.elementStack fully // this populates this.elementStack fully
this.getPlayer({verbose: true}); this.getPlayer({verbose: true});
console.log('tree:', JSON.parse(JSON.stringify(this.elementStack))); console.log('tree:', JSON.parse(JSON.stringify(this.elementStack)));
console.log('————————————————————— handling player tree request!')
this.eventBus.send('uw-config-broadcast', {type: 'player-tree', config: JSON.parse(JSON.stringify(this.elementStack))}); this.eventBus.send('uw-config-broadcast', {type: 'player-tree', config: JSON.parse(JSON.stringify(this.elementStack))});
} }

View File

@ -88,15 +88,15 @@ class VideoData {
} }
private eventBusCommands = { private eventBusCommands = {
'get-drm-status': [{ 'get-drm-status': {
function: () => { function: () => {
this.hasDrm = hasDrm(this.video); this.hasDrm = hasDrm(this.video);
this.eventBus.send('uw-config-broadcast', {type: 'drm-status', hasDrm: this.hasDrm}); this.eventBus.send('uw-config-broadcast', {type: 'drm-status', hasDrm: this.hasDrm});
} }
}], },
'set-run-level': [{ 'set-run-level': {
function: (runLevel: RunLevel) => this.setRunLevel(runLevel) function: (runLevel: RunLevel) => this.setRunLevel(runLevel)
}] }
} }
/** /**
@ -128,19 +128,18 @@ class VideoData {
height: this.video.offsetHeight, height: this.video.offsetHeight,
}; };
this.eventBus = new EventBus(); if (!pageInfo.eventBus) {
this.eventBus = new EventBus();
} else {
this.eventBus = pageInfo.eventBus;
}
this.extensionStatus = new ExtensionStatus(siteSettings, pageInfo.eventBus, pageInfo.fsStatus); this.extensionStatus = new ExtensionStatus(siteSettings, pageInfo.eventBus, pageInfo.fsStatus);
if (pageInfo.eventBus) { this.eventBus.subscribeMulti(
this.eventBus.setUpstreamBus(pageInfo.eventBus); this.eventBusCommands,
this
for (const action in this.eventBusCommands) { );
for (const command of this.eventBusCommands[action]) {
this.eventBus.subscribe(action, command);
}
}
}
this.setupEventListeners(); this.setupEventListeners();
} }
@ -270,7 +269,6 @@ class VideoData {
* Must be triggered on first action. TODO * Must be triggered on first action. TODO
*/ */
preparePage() { preparePage() {
console.log('preparing page ...')
this.injectBaseCss(); this.injectBaseCss();
this.pageInfo.initMouseActionHandler(this); this.pageInfo.initMouseActionHandler(this);
@ -365,7 +363,7 @@ class VideoData {
this.eventBus.send('set-run-level', RunLevel.Off); this.eventBus.send('set-run-level', RunLevel.Off);
this.destroyed = true; this.destroyed = true;
this.eventBus?.unsetUpstreamBus(); this.eventBus.unsubscribeAll(this);
try { try {
this.aard.stop(); this.aard.stop();
@ -389,16 +387,12 @@ class VideoData {
setRunLevel(runLevel: RunLevel, options?: {fromPlayer?: boolean}) { setRunLevel(runLevel: RunLevel, options?: {fromPlayer?: boolean}) {
console.log('setting new runlevel for videodata. current:', this.runLevel, 'new', runLevel)
if (this.runLevel === runLevel) { if (this.runLevel === runLevel) {
return; // also no need to propagate to the player return; // also no need to propagate to the player
} }
console.log('setting run level ...')
// Run level decreases towards 'off' // Run level decreases towards 'off'
if (this.runLevel > runLevel) { if (this.runLevel > runLevel) {
console.log('decreasing css.')
if (runLevel < RunLevel.CustomCSSActive) { if (runLevel < RunLevel.CustomCSSActive) {
this.video.classList.remove(this.baseCssName); this.video.classList.remove(this.baseCssName);
this.video.classList.remove(this.userCssClassName); this.video.classList.remove(this.userCssClassName);

View File

@ -63,9 +63,11 @@ class Resizer {
_lastAr: Ar = {type: AspectRatioType.Initial}; _lastAr: Ar = {type: AspectRatioType.Initial};
set lastAr(x: Ar) { set lastAr(x: Ar) {
// emit updates for UI when setting lastAr, but only if AR really changed
if (this._lastAr?.type !== x.type || this._lastAr?.ratio !== x.ratio) {
this.eventBus.send('uw-config-broadcast', {type: 'ar', config: x});
}
this._lastAr = x; this._lastAr = x;
// emit updates for UI when setting lastAr
this.eventBus.send('uw-config-broadcast', {type: 'ar', config: x})
} }
get lastAr() { get lastAr() {
return this._lastAr; return this._lastAr;