Merge branch '4.0.1' into stable
This commit is contained in:
commit
13d0e29e46
26
.babelrc
Normal file
26
.babelrc
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"plugins": [
|
||||
],
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"useBuiltIns": false,
|
||||
"targets": {
|
||||
"esmodules": true,
|
||||
},
|
||||
}]
|
||||
]
|
||||
}
|
||||
// {
|
||||
// "plugins": [
|
||||
// "@babel/plugin-proposal-optional-chaining"
|
||||
// ],
|
||||
// "presets": [
|
||||
// ["@babel/preset-env", {
|
||||
// "useBuiltIns": "usage",
|
||||
// "targets": {
|
||||
// // https://jamie.build/last-2-versions
|
||||
// "browsers": ["> 0.25%", "not ie 11", "not op_mini all"]
|
||||
// }
|
||||
// }]
|
||||
// ]
|
||||
// }
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,4 +1,7 @@
|
||||
ultrawidify.zip
|
||||
ultrawidify-git.zip
|
||||
old/
|
||||
build/
|
||||
/node_modules
|
||||
/*.log
|
||||
/dist
|
||||
/dist-zip
|
||||
/uw-git_keys
|
||||
|
23
.gitlab-ci.yml
Normal file
23
.gitlab-ci.yml
Normal file
@ -0,0 +1,23 @@
|
||||
image: node:current
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
- .yarn
|
||||
|
||||
stages:
|
||||
- prep
|
||||
- build
|
||||
- pack
|
||||
|
||||
install deps:
|
||||
stage: prep
|
||||
script: yarn install
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script: npm run build
|
||||
|
||||
create zip:
|
||||
stage: pack
|
||||
script: npm run build-zip
|
17
.vscode/launch.json
vendored
17
.vscode/launch.json
vendored
@ -5,10 +5,19 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"name": "Launch addon",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"program": "${file}"
|
||||
"port":6000,
|
||||
"reAttach": true,
|
||||
"addonType": "webExtension",
|
||||
"addonPath": "${workspaceFolder}/dist",
|
||||
}
|
||||
]
|
||||
],
|
||||
"firefox": {
|
||||
"executable": "/usr/bin/firefox-developer-edition",
|
||||
"firefoxArgs": [
|
||||
"--start-debugger-server"
|
||||
]
|
||||
}
|
||||
}
|
32
CHANGELOG.md
32
CHANGELOG.md
@ -1,8 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
## v4.x
|
||||
|
||||
### Plans for the future
|
||||
|
||||
* Allow users to set autodetection sensitivity
|
||||
* Settings page looks ugly af right now. Maybe fix it some time later
|
||||
|
||||
### v4.0.0 (upcoming)
|
||||
|
||||
* Using vue for popup and settings page
|
||||
* Editable shortcuts
|
||||
* Per-site controls
|
||||
* You can now select which specific video on the page you control, provided each video is in its separate iframe
|
||||
* Basic mode added
|
||||
* Rewrote keyboard shortcuts and changed how they're handled. Massively.
|
||||
* Fixed the bug where saving settings wouldn't work
|
||||
* Fixed the bug where autodetection didn't calculate aspect ratio correctly. This bug would manifest in this extension cropping too much video even in cases where edge between letterbox and video was clearly defined.
|
||||
* Implemented/improved black frame detection
|
||||
* Autodetection now differentiates between legitimate letterbox and linear gradients. This should prevent incorrect auto-cropping on videos that look similar to IGN's [Hollow Knight](https://www.youtube.com/watch?v=hg25ONutphA) review.
|
||||
|
||||
## v3.x
|
||||
|
||||
### v3.2.2
|
||||
~~### v3.3.0~~
|
||||
|
||||
~~This will probably get promoted to 4.0, continuing the trend of version something.3 not happening. Eulul~~
|
||||
|
||||
* ~~Basic mode added~~
|
||||
* ~~Per-site controls in popup (to control embedded videos)~~
|
||||
* ~~Rewrote keyboard shortcuts and changed how they're handled. Massively.~~
|
||||
|
||||
Never happened, got bumped to 4.0.0.
|
||||
|
||||
### v3.2.2 (current)
|
||||
|
||||
* Pan event listener now gets properly unbound
|
||||
* Fixed 'reset zoom' button in popup
|
||||
|
21
README-AMO.md
Normal file
21
README-AMO.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Build guide for AMO
|
||||
|
||||
## Build platform
|
||||
|
||||
The extension is built on a PC running Manjaro Linux. Yarn is the package manager of choice.
|
||||
|
||||
Yarn version: 1.16.0
|
||||
Node version: v11.15.0
|
||||
|
||||
|
||||
## Installing dependencies
|
||||
|
||||
`yarn install --frozen-lockfile`
|
||||
|
||||
According to Yarn documentation, that should install the exact version of dependencies that the extension is using.
|
||||
|
||||
## Reproducing build
|
||||
|
||||
`npm run build`
|
||||
|
||||
The compiled code pops up in /dist.
|
60
README.md
60
README.md
@ -1,15 +1,20 @@
|
||||
# Ultrawidify — aspect ratio fixer for youtube and netflix
|
||||
|
||||
## Super TL;DR: I'm just looking for the install links, thanks
|
||||
|
||||
[Firefox](https://addons.mozilla.org/en/firefox/addon/ultrawidify/), [Chrome](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi), [Edge](#edge-speficic-limitations-important).
|
||||
|
||||
## TL;DR
|
||||
|
||||
If you own an ultrawide monitor, you have probably noticed that sometimes videos aren't encoded properly — they feature black bars on all four sides. This could happen because someone was incompetent (note: as far as youtube is concerned, improperly rendered videos might be due to youtube's implementation of certain new features). The extension kinda fixes that by doing this:
|
||||
|
||||
![Demo](img-demo/example-httyd2.png "Should these black bars be here? No [...] But an ultrawide user never forgets.")
|
||||
|
||||
|
||||
|
||||
## Known issues
|
||||
|
||||
* Netflix autodetection not working in Chrome, wontfix as issue is fundamentally unfixable.
|
||||
* Keyboard shortcut for automatic detection (A) doesn't really work for some reason.
|
||||
* Everything reported in [issues](https://github.com/xternal7/ultrawidify/issues)
|
||||
|
||||
### Limitations
|
||||
@ -17,7 +22,8 @@ If you own an ultrawide monitor, you have probably noticed that sometimes videos
|
||||
* Unclear how extension handles sites with more than one video per page.
|
||||
* Autodetection is only correct 95% of the time, most of the time.
|
||||
* That new stretching mode wasn't thoroughly tested yet. Issues may be present. (Same with zoom)
|
||||
* Enabling extension everywhere (as opposed to whitelisted sites) could break some websites.
|
||||
* Enabling extension everywhere (as opposed to whitelisted sites) could break some websites.
|
||||
* Edge has
|
||||
|
||||
### Features
|
||||
|
||||
@ -45,9 +51,15 @@ You can download this extension from Firefox' and Chrome's extension stores:
|
||||
* [Firefox](https://addons.mozilla.org/en/firefox/addon/ultrawidify/)
|
||||
* [Chrome](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
||||
|
||||
### Beggathon
|
||||
This extension also has a version for Microsoft Edge, but that requires a bit more effort. [See notes first](#edge-speficic-limitations-important).
|
||||
|
||||
Working on this extension takes time, coffee and motivation. If you want to buy me a beer or something, you can [use this link to send me motivation](https://www.paypal.me/tamius). **Any donations are well appreciated.**
|
||||
# Beggathon (donations)
|
||||
|
||||
If you want to support this project, please consider a donation. Working on this extension takes time, money, coffee and motivation. Sometimes also [a very precise amount of alco](https://xkcd.com/323/).
|
||||
|
||||
You can make a donation [via Paypal](https://www.paypal.me/tamius).
|
||||
|
||||
**Any donation — no matter how big or small — is well appreciated. Thanks.**
|
||||
|
||||
|
||||
|
||||
@ -211,6 +223,8 @@ is currently not possible. Settings page for this extension has been disabled so
|
||||
However, I do plan on implementing this feature. Hopefully by the end of the year, but given how consistently I've been breaking self-imposed deadlines and goals for this extension don't hold your breath. After all, [Hofstadter's a bitch](https://en.wikipedia.org/wiki/Hofstadter%27s_law).
|
||||
|
||||
|
||||
|
||||
|
||||
## Plans for the future
|
||||
|
||||
1. Handle porting of extension settings between versions.
|
||||
@ -220,24 +234,54 @@ However, I do plan on implementing this feature. Hopefully by the end of the yea
|
||||
5. figure the best way to do GUI (injecting buttons into the player bar is not a good way. Been there, done that, each site has its own way and some appear to be impossible). ~~Might get bumped to be released alongside #2~~no it wont lol
|
||||
6. Improvements to automatic aspect ratio detection
|
||||
|
||||
|
||||
## Installing
|
||||
|
||||
### Permanent install / stable
|
||||
|
||||
[Latest stable for Firefox — download from AMO](https://addons.mozilla.org/en/firefox/addon/ultrawidify/)
|
||||
|
||||
[Latest stafle for Chrome — download from Chrome store](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
||||
[Latest stable for Chrome — download from Chrome store](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
||||
|
||||
### Installing the current, github version
|
||||
|
||||
Requirements: npm, yarn.
|
||||
|
||||
1. Clone this repo
|
||||
2. run `yarn install`
|
||||
3. run `npm run watch:dev`
|
||||
|
||||
TODO: see if #3 already loads the extension in FF
|
||||
|
||||
2. Open up Firefox
|
||||
3. Go to `about:debugging`
|
||||
4. Add temporary addon
|
||||
5. Browse to wherever you saved it and select manifest.json
|
||||
5. Select `${ultrawidify_folder}/dist/manifest.json`
|
||||
|
||||
## Changelog
|
||||
|
||||
# Edge-specific limitations (IMPORTANT!)
|
||||
|
||||
For various reasons — most notably, I refuse to pay Microsoft €14 for the privilege of developing shit for their outright broken browser (and in addition to that, the extension needs to go through review process as well) — this extension isn't going to appear on Microsoft Store. (And I do not permit anyone else to do that in my name either). Full rant on why I've made this decision can be found [here](https://github.com/xternal7/ultrawidify/issues/14#issuecomment-424903335).
|
||||
|
||||
As a result, you'll have to download the extension and install it manually. This approach has some downsides.
|
||||
|
||||
1. You'll get this popup after starting Edge. If you've already opened Youtube or Netflix, **you will have to reload the page (or navigate to somewhere else) in order for extension to start.**
|
||||
![slika](https://user-images.githubusercontent.com/12505802/46114175-05912200-c1e1-11e8-91c7-2217f5bf79e3.png)
|
||||
|
||||
2. Certain WebExtension APIs that I rely on are outright broken in Edge. This bug would cause global extension settings (tab: Extension settings) and per-site settings (tab: Site settings) to reset to default values every time you'd open the popup. As a result, _Extension settings_ and _Site settings_ tabs are disabled in Edge:
|
||||
![Feast on dem popup](https://user-images.githubusercontent.com/12505802/46113923-d1693180-c1df-11e8-82f0-ad64cbc57558.png)
|
||||
Unfortunate consequence of this is that you won't be able to enable this extension for sites other than Youtube and Netflix, but then again. Let's be honest. You're only using Edge for Netflix, so that's probably no big deal for you.
|
||||
|
||||
**It's also worth noting that I'm not actively maintaining the Edge fork, so it's a few versions behind.**
|
||||
|
||||
## Installing Ultrawidify in M$ Edge
|
||||
|
||||
1. Download the zip file from [here](https://github.com/xternal7/ultrawidify/tree/master/releases/edge)
|
||||
2. Extract contents of the zip file in some folder. It really doesn't matter where, just keep it somewhere that won't be in your way when using your computer.
|
||||
3. Follow the steps in [this guide](https://docs.microsoft.com/en-us/microsoft-edge/extensions/guides/adding-and-removing-extensions)
|
||||
|
||||
# Changelog
|
||||
|
||||
see changelog.md
|
||||
|
||||
todo: add link to changelog.md here
|
||||
todo: add link to changelog.md here
|
||||
|
@ -1,49 +0,0 @@
|
||||
// Set prod to true when releasing
|
||||
_prod = true;
|
||||
// _prod = false;
|
||||
|
||||
Debug = {
|
||||
init: true,
|
||||
debug: false,
|
||||
keyboard: true,
|
||||
debugResizer: true,
|
||||
debugArDetect: true,
|
||||
debugStorage: false,
|
||||
comms: false,
|
||||
// showArDetectCanvas: true,
|
||||
flushStoredSettings: true,
|
||||
// flushStoredSettings: false,
|
||||
playerDetectDebug: true,
|
||||
periodic: true,
|
||||
videoRescan: true,
|
||||
arDetect: {
|
||||
edgeDetect: true
|
||||
},
|
||||
canvas: {
|
||||
debugDetection: true
|
||||
},
|
||||
debugCanvas: {
|
||||
// enabled: true,
|
||||
// guardLine: true
|
||||
enabled: false,
|
||||
guardLine: false
|
||||
}
|
||||
}
|
||||
|
||||
if(_prod){
|
||||
__disableAllDebug(Debug);
|
||||
}
|
||||
|
||||
function __disableAllDebug(obj) {
|
||||
for(key in obj) {
|
||||
if (obj.hasOwnProperty(key) ){
|
||||
if(obj[key] instanceof Object)
|
||||
__disableAllDebug(obj[key]);
|
||||
else
|
||||
obj[key] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("Guess we're debugging ultrawidify then. Debug.js must always load first, and others must follow.\nLoading: Debug.js");
|
@ -1,246 +0,0 @@
|
||||
if(Debug.debug)
|
||||
console.log("Loading: ExtensionConf.js");
|
||||
|
||||
var ExtensionConf = {
|
||||
extensionMode: "whitelist", // how should this extension work?
|
||||
// 'blacklist' - work everywhere except blacklist
|
||||
// 'whitelist' - only work on whitelisted sites
|
||||
// 'disabled' - work nowhere
|
||||
arDetect: {
|
||||
mode: "blacklist", // how should autodetection work?
|
||||
// 'blacklist' - work by default, problem sites need to be blocked
|
||||
// 'whitelist' - only work if site has been specifically approved
|
||||
// 'disabled' - don't work at all
|
||||
disabledReason: "", // if automatic aspect ratio has been disabled, show reason
|
||||
allowedMisaligned: 0.05, // top and bottom letterbox thickness can differ by this much.
|
||||
// Any more and we don't adjust ar.
|
||||
allowedArVariance: 0.075, // amount by which old ar can differ from the new (1 = 100%)
|
||||
timer_playing: 666, // we trigger ar this often (in ms) under this conditions
|
||||
timer_paused: 3000,
|
||||
timer_error: 3000,
|
||||
timer_minimumTimeout: 5, // but regardless of above, we wait this many msec before retriggering
|
||||
autoDisable: { // settings for automatically disabling the extension
|
||||
maxExecutionTime: 6000, // if execution time of main autodetect loop exceeds this many milliseconds,
|
||||
// we disable it.
|
||||
consecutiveTimeoutCount: 5, // we only do it if it happens this many consecutive times
|
||||
|
||||
// FOR FUTURE USE
|
||||
consecutiveArResets: 5 // if aspect ratio reverts immediately after AR change is applied, we disable everything
|
||||
},
|
||||
hSamples: 640,
|
||||
vSamples: 360,
|
||||
// samplingInterval: 10, // we sample at columns at (width/this) * [ 1 .. this - 1]
|
||||
blackLevel_default: 10, // everything darker than 10/255 across all RGB components is considered black by
|
||||
// default. blackLevel can decrease if we detect darker black.
|
||||
blackbarTreshold: 16, // if pixel is darker than blackLevel + blackbarTreshold, 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
|
||||
variableBlackbarTresholdOptions: { // 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 treshold goes over max blackbar treshold
|
||||
maxBlackbarTreshold: 48, // max threshold (don't increase past this)
|
||||
thresholdStep: 8, // when failing to set aspect ratio, increase treshold by this much
|
||||
increaseAfterConsecutiveResets: 2 // increase if AR resets this many times in a row
|
||||
},
|
||||
staticSampleCols: 9, // we take a column at [0-n]/n-th parts along the width and sample it
|
||||
randomSampleCols: 0, // we add this many randomly selected columns to the static columns
|
||||
staticSampleRows: 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)
|
||||
imageTestTreshold: 0.1, // when testing for image, this much pixels must be over blackbarTreshold
|
||||
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
|
||||
},
|
||||
fallbackMode: {
|
||||
enabled: true,
|
||||
safetyBorderPx: 5, // determines the thickness of safety border in fallback mode
|
||||
noTriggerZonePx: 8 // if we detect edge less than this many pixels thick, we don't correct.
|
||||
},
|
||||
arSwitchLimiter: { // to be implemented
|
||||
switches: 2, // we can switch this many times
|
||||
period: 2.0 // per this period
|
||||
},
|
||||
edgeDetection: {
|
||||
sampleWidth: 8, // we take a sample this wide for edge detection
|
||||
detectionTreshold: 4, // sample needs to have this many non-black pixels to be a valid edge
|
||||
singleSideConfirmationTreshold: 0.3, // we need this much edges (out of all samples, not just edges) in order
|
||||
// to confirm an edge in case there's no edges on top or bottom (other
|
||||
// than logo, of course)
|
||||
logoTreshold: 0.15, // if edge candidate sits with count greater than this*all_samples, it can't be logo
|
||||
// or watermark.
|
||||
edgeTolerancePx: 2, // we check for black edge violation this far from detection point
|
||||
edgeTolerancePercent: null, // we check for black edge detection this % of height from detection point. unused
|
||||
middleIgnoredArea: 0.2, // we ignore this % of canvas height towards edges while detecting aspect ratios
|
||||
minColsForSearch: 0.5, // if we hit the edge of blackbars for all but this many columns (%-wise), we don't
|
||||
// continue with search. It's pointless, because black edge is higher/lower than we
|
||||
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
|
||||
edgeTolerancePx: 1, // tests for edge detection are performed this far away from detected row
|
||||
},
|
||||
pillarTest: {
|
||||
ignoreThinPillarsPx: 5, // ignore pillars that are less than this many pixels thick.
|
||||
allowMisaligned: 0.05 // left and right edge can vary this much (%)
|
||||
},
|
||||
textLineTest: {
|
||||
nonTextPulse: 0.10, // if a single continuous pulse has this many non-black pixels, we aren't dealing
|
||||
// with text. This value is relative to canvas width (%)
|
||||
pulsesToConfirm: 10, // this is a treshold to confirm we're seeing text.
|
||||
pulsesToConfirmIfHalfBlack: 5, // this is the treshold to confirm we're seeing text if longest black pulse
|
||||
// is over 50% of the canvas width
|
||||
testRowOffset: 0.02 // we test this % of height from detected edge
|
||||
}
|
||||
},
|
||||
arChange: {
|
||||
samenessTreshold: 0.025, // if aspect ratios are within 2.5% within each other, don't resize
|
||||
},
|
||||
zoom: {
|
||||
minLogZoom: -1,
|
||||
maxLogZoom: 3,
|
||||
announceDebounce: 200 // we wait this long before announcing new zoom
|
||||
},
|
||||
miscFullscreenSettings: {
|
||||
videoFloat: "center",
|
||||
mousePan: {
|
||||
enabled: false
|
||||
},
|
||||
defaultAr: "original",
|
||||
},
|
||||
stretch: {
|
||||
initialMode: 0, // 0 - no stretch, 1 - basic, 2 - hybrid, 3 - conditional
|
||||
conditionalDifferencePercent: 0.05 // black bars less than this wide will trigger stretch
|
||||
// if mode is set to '1'. 1.0=100%
|
||||
},
|
||||
resizer: {
|
||||
setStyleString: {
|
||||
maxRetries: 3,
|
||||
retryTimeout: 200
|
||||
}
|
||||
},
|
||||
pageInfo: {
|
||||
timeouts: {
|
||||
urlCheck: 200,
|
||||
rescan: 1500
|
||||
}
|
||||
},
|
||||
colors:{
|
||||
// criticalFail: "background: #fa2; color: #000"
|
||||
},
|
||||
keyboard: {
|
||||
shortcuts: {
|
||||
// automatic
|
||||
"a": {
|
||||
action: "auto-ar",
|
||||
},
|
||||
//#region crop
|
||||
"r": {
|
||||
action: "crop",
|
||||
arg: "reset"
|
||||
},
|
||||
"w": {
|
||||
action: "crop",
|
||||
arg: "fitw"
|
||||
},
|
||||
"e": {
|
||||
action: "crop",
|
||||
arg: "fith"
|
||||
},
|
||||
"s": {
|
||||
action: "crop",
|
||||
arg: 1.78
|
||||
},
|
||||
"d": {
|
||||
action: "crop",
|
||||
arg: 2.39
|
||||
},
|
||||
"x": {
|
||||
action: "crop",
|
||||
arg: 2.0
|
||||
},
|
||||
"q": {
|
||||
action: "crop",
|
||||
arg: 2.35
|
||||
},
|
||||
//#endregion
|
||||
//#region zoom
|
||||
"z": {
|
||||
action: "zoom",
|
||||
arg: 0.1
|
||||
},
|
||||
"u": {
|
||||
action: "zoom",
|
||||
arg: -0.1
|
||||
},
|
||||
"p": {
|
||||
action: "pan",
|
||||
arg: 'toggle' // possible: 'enable', 'disable', 'toggle'
|
||||
},
|
||||
"shiftKey_shift": {
|
||||
action: "pan",
|
||||
arg: 'toggle',
|
||||
keyup: {
|
||||
action: 'pan',
|
||||
arg: 'toggle'
|
||||
}
|
||||
},
|
||||
"shift": {
|
||||
action: "",
|
||||
arg: "",
|
||||
keyup: {
|
||||
action: 'pan',
|
||||
arg: 'toggle',
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
},
|
||||
modKeys: ["ctrlKey", "shiftKey", "altKey"]
|
||||
},
|
||||
// -----------------------------------------
|
||||
// ::: SITE CONFIGURATION :::
|
||||
// -----------------------------------------
|
||||
// Nastavitve za posamezno stran
|
||||
// Config for a given page:
|
||||
//
|
||||
// <hostname> : {
|
||||
// status: <option> // should extension work on this site?
|
||||
// arStatus: <option> // should we do autodetection on this site?
|
||||
// statusEmbedded: <option> // reserved for future... maybe
|
||||
//
|
||||
// defaultAar?: <ratio> // automatically apply this aspect ratio on this side. Use extension defaults if undefined.
|
||||
// stretch? <stretch mode> // automatically stretch video on this site in this manner
|
||||
// videoAlignment? <left|center|right>
|
||||
//
|
||||
// type: <official|community|user> // 'official' — blessed by Tam.
|
||||
// // 'community' — blessed by reddit.
|
||||
// // 'user' — user-defined (not here)
|
||||
// override: <true|false> // override user settings for this site on update
|
||||
// }
|
||||
//
|
||||
// Veljavne vrednosti za možnosti
|
||||
// Valid values for options:
|
||||
//
|
||||
// status, arStatus, statusEmbedded:
|
||||
//
|
||||
// * enabled — always allow
|
||||
// * default — allow if default is to allow, block if default is to block
|
||||
// * disabled — never allow
|
||||
//
|
||||
sites: {
|
||||
"www.youtube.com" : {
|
||||
status: "enabled", // should extension work on this site?
|
||||
arStatus: "default", // should we enable autodetection
|
||||
statusEmbedded: "enabled", // should extension work for this site when embedded on other sites?
|
||||
override: false, // ignore value localStorage in favour of this
|
||||
type: 'official' // is officially supported? (Alternatives are 'community' and 'user-defined')
|
||||
},
|
||||
"www.netflix.com" : {
|
||||
status: "enabled",
|
||||
arStatus: BrowserDetect.firefox ? "default" : "disabled",
|
||||
statusEmbedded: "enabled",
|
||||
override: false,
|
||||
type: 'official'
|
||||
},
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
if(Debug.debug)
|
||||
console.log("Loading: Keybinds.js");
|
||||
|
||||
class Keybinds {
|
||||
constructor(pageInfo){
|
||||
this.pageInfo = pageInfo;
|
||||
this.settings = pageInfo.settings;
|
||||
this.inputs = ['input','select','button','textarea'];
|
||||
}
|
||||
|
||||
setup(){
|
||||
var ths = this;
|
||||
document.addEventListener('keydown', (event) => ths.handleKeypress(event) );
|
||||
document.addEventListener('keyup', (event) => ths.handleKeypress(event,true) );
|
||||
}
|
||||
|
||||
handleKeypress(event, isKeyUp) { // Tukaj ugotovimo, katero tipko smo pritisnili
|
||||
|
||||
if(Debug.debug && Debug.keyboard ){
|
||||
console.log("%c[Keybinds::_kbd_process] we pressed a key: ", "color: #ff0", event.key , " | keydown: ", event.keydown, "event:", event);
|
||||
}
|
||||
|
||||
// Tipke upoštevamo samo, če smo v celozaslonskem načinu oz. če ne pišemo komentarja
|
||||
// v nasprotnem primeru ne naredimo nič.
|
||||
// We only take actions if we're in full screen or not writing a comment
|
||||
var activeElement = document.activeElement;
|
||||
|
||||
if( (! PlayerData.isFullScreen()) && (
|
||||
(this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1) ||
|
||||
(activeElement.getAttribute("role") === "textbox") ||
|
||||
(activeElement.getAttribute("type") === "text")
|
||||
)){
|
||||
if(Debug.debug && Debug.keyboard)
|
||||
console.log("[Keybinds::_kbd_process] We're writing a comment or something. Doing nothing");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// building modifiers list:
|
||||
var modlist = "";
|
||||
for(var mod of this.settings.active.keyboard.modKeys){
|
||||
if(event[mod])
|
||||
modlist += (mod + "_")
|
||||
}
|
||||
|
||||
if(Debug.debug && Debug.keyboard ){
|
||||
if(modlist)
|
||||
console.log("[Keybinds::_kbd_process] there's been modifier keys. Modlist:", modlist);
|
||||
}
|
||||
|
||||
var keypress = modlist + event.key.toLowerCase();
|
||||
|
||||
if(Debug.debug && Debug.keyboard )
|
||||
console.log("[Keybinds::_kbd_process] our full keypress is this", keypress );
|
||||
|
||||
|
||||
if(this.settings.active.keyboard.shortcuts[keypress]){
|
||||
var conf = this.settings.active.keyboard.shortcuts[keypress];
|
||||
|
||||
if (isKeyUp) {
|
||||
if (conf.keyup) {
|
||||
conf = conf.keyup;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(Debug.debug && Debug.keyboard) {
|
||||
console.log("[Keybinds::_kbd_process] there's an action associated with this keypress. conf:", conf, "conf.arg:", conf.arg);
|
||||
}
|
||||
|
||||
if (conf.action === "crop"){
|
||||
this.pageInfo.stopArDetection();
|
||||
this.pageInfo.setAr(conf.arg);
|
||||
} else if (conf.action === "zoom"){
|
||||
this.pageInfo.stopArDetection();
|
||||
this.pageInfo.zoomStep(conf.arg);
|
||||
} else if (conf.action === "auto-ar"){
|
||||
this.pageInfo.startArDetection();
|
||||
} else if (conf.action === "pan") {
|
||||
this.pageInfo.setPanMode(conf.arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
var _bd_usebrowser = "firefox";
|
||||
|
||||
var _bd_isFirefox = true;
|
||||
var _bd_isChrome = false;
|
||||
var _bd_isEdge = false; // we'll see if FF
|
||||
|
||||
try{
|
||||
// todo: find something that works in firefox but not in edge (or vice-versa)
|
||||
// note that this function returns a promise! and is broken for some reason
|
||||
var browserinfo = browser.runtime.getBrowserInfo();
|
||||
|
||||
// we don't need to actually check because only firefox supports that.
|
||||
// if we're not on firefox, the above call will probably throw an exception anyway.
|
||||
// if browsers other than firefox start supporting that, well ... we'll also need to actually await for promise
|
||||
// that getBrowserInfo() returns to resolve.
|
||||
|
||||
// if (Browser.name.toLowerCase().indexOf(firefox) !== -1 || Browser.vendor.toLowerCase().indexOf(mozilla) !== -1) {
|
||||
_bd_isFirefox = true;
|
||||
_bd_isEdge = false;
|
||||
// }
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
if(Debug.debug) {
|
||||
console.info("[BrowserDetect] browser.runtime.getBrowserInfo() probably failed. This means we're probably not using firefox.", e)
|
||||
}
|
||||
};
|
||||
|
||||
if(typeof browser === "undefined"){ // This is a good sign we're in chrome or chromium-based browsers
|
||||
if(chrome){
|
||||
browser = chrome;
|
||||
_bd_usebrowser = "chrome";
|
||||
_bd_isChrome = true;
|
||||
_bd_isEdge = false;
|
||||
_bd_isFirefox = false;
|
||||
}
|
||||
}
|
||||
|
||||
var BrowserDetect = {
|
||||
usebrowser: _bd_usebrowser,
|
||||
firefox: _bd_isFirefox,
|
||||
chrome: _bd_isChrome,
|
||||
edge: _bd_isEdge
|
||||
}
|
||||
|
||||
if(Debug.debug){
|
||||
console.log("BrowserDetect loaded! Here's BrowserDetect object:", BrowserDetect)
|
||||
}
|
409
js/lib/Comms.js
409
js/lib/Comms.js
@ -1,409 +0,0 @@
|
||||
if(Debug.debug){
|
||||
console.log("Loading Comms.js");
|
||||
}
|
||||
|
||||
class CommsClient {
|
||||
constructor(name, settings) {
|
||||
if (BrowserDetect.firefox) {
|
||||
this.port = browser.runtime.connect({name: name});
|
||||
} else if (BrowserDetect.chrome) {
|
||||
this.port = chrome.runtime.connect({name: name});
|
||||
} else if (BrowserDetect.edge) {
|
||||
this.port = browser.runtime.connect({name: name})
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
this._listener = m => ths.processReceivedMessage(m);
|
||||
this.port.onMessage.addListener(this._listener);
|
||||
|
||||
this.settings = settings;
|
||||
this.pageInfo = undefined;
|
||||
this.commsId = (Math.random() * 20).toFixed(0);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.pageInfo = null;
|
||||
this.settings = null;
|
||||
this.port.onMessage.removeListener(this._listener);
|
||||
}
|
||||
|
||||
setPageInfo(pageInfo){
|
||||
|
||||
this.pageInfo = pageInfo;
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log(`[CommsClient::setPageInfo] <${this.commsId}>`, "SETTING PAGEINFO —", this.pageInfo, this)
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
this._listener = m => ths.processReceivedMessage(m);
|
||||
this.port.onMessage.removeListener(this._listener);
|
||||
this.port.onMessage.addListener(this._listener);
|
||||
|
||||
}
|
||||
|
||||
processReceivedMessage(message){
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log(`[CommsClient.js::processMessage] <${this.commsId}> Received message from background script!`, message);
|
||||
}
|
||||
|
||||
if (!this.pageInfo || !this.settings.active) {
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log(`[CommsClient.js::processMessage] <${this.commsId}> this.pageInfo (or settings) not defined. Extension is probably disabled for this site.\npageInfo:`, this.pageInfo,
|
||||
"\nsettings.active:", this.settings.active,
|
||||
"\nnobj:", this
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.cmd === 'get-current-zoom') {
|
||||
this.pageInfo.requestCurrentZoom();
|
||||
}
|
||||
|
||||
if (message.cmd === "set-ar") {
|
||||
this.pageInfo.setAr(message.ratio);
|
||||
} else if (message.cmd === 'set-alignment') {
|
||||
this.pageInfo.setVideoFloat(message.mode);
|
||||
this.pageInfo.restoreAr();
|
||||
} else if (message.cmd === "set-stretch") {
|
||||
this.pageInfo.setStretchMode(StretchMode[message.mode]);
|
||||
} else if (message.cmd === "autoar-start") {
|
||||
if (message.enabled !== false) {
|
||||
this.pageInfo.initArDetection();
|
||||
this.pageInfo.startArDetection();
|
||||
} else {
|
||||
this.pageInfo.stopArDetection();
|
||||
}
|
||||
} else if (message.cmd === "pause-processing") {
|
||||
this.pageInfo.pauseProcessing();
|
||||
} else if (message.cmd === "resume-processing") {
|
||||
// todo: autoArStatus
|
||||
this.pageInfo.resumeProcessing(message.autoArStatus);
|
||||
} else if (message.cmd === 'set-zoom') {
|
||||
this.pageInfo.setZoom(message.zoom, true);
|
||||
}
|
||||
}
|
||||
|
||||
async sleep(n){
|
||||
return new Promise( (resolve, reject) => setTimeout(resolve, n) );
|
||||
}
|
||||
|
||||
async sendMessage_nonpersistent(message){
|
||||
if(BrowserDetect.firefox){
|
||||
return browser.runtime.sendMessage(message)
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
try{
|
||||
if(BrowserDetect.edge){
|
||||
browser.runtime.sendMessage(message, function(response){
|
||||
var r = response;
|
||||
resolve(r);
|
||||
});
|
||||
} else {
|
||||
chrome.runtime.sendMessage(message, function(response){
|
||||
// Chrome/js shittiness mitigation — remove this line and an empty array will be returned
|
||||
var r = response;
|
||||
resolve(r);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(e){
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async requestSettings(){
|
||||
if(Debug.debug){
|
||||
console.log("%c[CommsClient::requestSettings] sending request for congif!", "background: #11D; color: #aad");
|
||||
}
|
||||
var response = await this.sendMessage_nonpersistent({cmd: 'get-config'});
|
||||
if(Debug.debug){
|
||||
console.log("%c[CommsClient::requestSettings] received settings response!", "background: #11D; color: #aad", response);
|
||||
}
|
||||
|
||||
if(! response || response.extensionConf){
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
this.settings.active = JSON.parse(response.extensionConf);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
registerVideo(){
|
||||
this.port.postMessage({cmd: "has-video"});
|
||||
}
|
||||
|
||||
announceZoom(scale){
|
||||
this.port.postMessage({cmd: "announce-zoom", zoom: scale});
|
||||
}
|
||||
|
||||
unregisterVideo(){
|
||||
this.port.postMessage({cmd: "noVideo"}); // ayymd
|
||||
}
|
||||
}
|
||||
|
||||
class CommsServer {
|
||||
constructor(server) {
|
||||
this.server = server;
|
||||
this.settings = server.settings;
|
||||
this.ports = [];
|
||||
|
||||
var ths = this;
|
||||
|
||||
|
||||
if (BrowserDetect.firefox) {
|
||||
browser.runtime.onConnect.addListener(p => ths.onConnect(p));
|
||||
browser.runtime.onMessage.addListener(m => ths.processReceivedMessage_nonpersistent_ff(m));
|
||||
} else {
|
||||
chrome.runtime.onConnect.addListener(p => ths.onConnect(p));
|
||||
chrome.runtime.onMessage.addListener((msg, sender, callback) => ths.processReceivedMessage_nonpersistent_chrome(m, sender, callback));
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentTabHostname() {
|
||||
const activeTab = await this._getActiveTab();
|
||||
|
||||
const url = activeTab[0].url;
|
||||
|
||||
var hostname;
|
||||
|
||||
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
|
||||
hostname = url.split('/')[2];
|
||||
}
|
||||
else {
|
||||
hostname = url.split('/')[0];
|
||||
}
|
||||
|
||||
hostname = hostname.split(':')[0]; //find & remove port number
|
||||
hostname = hostname.split('?')[0]; //find & remove "?"
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
sendToAll(message){
|
||||
for(var p of this.ports){
|
||||
for(var frame in p){
|
||||
p[frame].postMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _getActiveTab() {
|
||||
if (BrowserDetect.firefox) {
|
||||
return await browser.tabs.query({currentWindow: true, active: true});
|
||||
} else {
|
||||
return await new Promise( (resolve, reject) => {
|
||||
chrome.tabs.query({lastFocusedWindow: true, active: true}, function (res) {
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async sendToActive(message) {
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log("%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
|
||||
}
|
||||
|
||||
var tabs = await this._getActiveTab();
|
||||
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log("[CommsServer::_sendToActive_ff] currently active tab(s)?", tabs);
|
||||
for (var key in this.ports[tabs[0].id]) {
|
||||
console.log("key?", key, this.ports[tabs[0].id]);
|
||||
// this.ports[tabs[0].id][key].postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
for (var key in this.ports[tabs[0].id]) {
|
||||
this.ports[tabs[0].id][key].postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
onConnect(port){
|
||||
var ths = this;
|
||||
|
||||
// poseben primer | special case
|
||||
if (port.name === 'popup-port') {
|
||||
this.popupPort = port;
|
||||
this.popupPort.onMessage.addListener( (m,p) => ths.processReceivedMessage(m,p));
|
||||
return;
|
||||
}
|
||||
|
||||
var tabId = port.sender.tab.id;
|
||||
var frameId = port.sender.frameId;
|
||||
if(! this.ports[tabId]){
|
||||
this.ports[tabId] = {};
|
||||
}
|
||||
this.ports[tabId][frameId] = port;
|
||||
this.ports[tabId][frameId].onMessage.addListener( (m,p) => ths.processReceivedMessage(m, p));
|
||||
this.ports[tabId][frameId].onDisconnect.addListener( (p) => {
|
||||
delete ths.ports[p.sender.tab.id][p.sender.frameId];
|
||||
if(Object.keys(ths.ports[p.sender.tab.id]).length === 0){
|
||||
ths.ports[tabId] = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async processReceivedMessage(message, port){
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[CommsServer.js::processMessage] Received message from background script!", message, "port", port, "\nsettings and server:", this.settings,this.server);
|
||||
}
|
||||
|
||||
if (message.cmd === 'announce-zoom') {
|
||||
// forward off to the popup, no use for this here
|
||||
try {
|
||||
this.popupPort.postMessage({cmd: 'set-current-zoom', zoom: message.zoom});
|
||||
} catch (e) {
|
||||
// can't forward stuff to popup if it isn't open
|
||||
}
|
||||
} else if (message.cmd === 'get-current-zoom') {
|
||||
this.sendToActive(message);
|
||||
}
|
||||
|
||||
if (message.cmd === 'get-current-site') {
|
||||
port.postMessage({cmd: 'set-current-site', site: await this.getCurrentTabHostname()});
|
||||
}
|
||||
|
||||
if (message.cmd === 'get-config') {
|
||||
if(Debug.debug) {
|
||||
console.log("CommsServer: received get-config. Active settings?", this.settings.active, "\n(settings:", this.settings, ")")
|
||||
}
|
||||
port.postMessage({cmd: "set-config", conf: this.settings.active, site: this.server.currentSite})
|
||||
} else if (message.cmd === 'set-stretch') {
|
||||
this.sendToActive(message);
|
||||
} else if (message.cmd === 'set-ar') {
|
||||
this.sendToActive(message);
|
||||
} else if (message.cmd === 'set-custom-ar') {
|
||||
this.settings.active.keyboard.shortcuts.q.arg = message.ratio;
|
||||
this.settings.save();
|
||||
} else if (message.cmd === 'set-alignment') {
|
||||
this.sendToActive(message);
|
||||
} else if (message.cmd === 'autoar-start') {
|
||||
this.sendToActive(message);
|
||||
} else if (message.cmd === "autoar-disable") { // LEGACY - can be removed prolly
|
||||
this.settings.active.arDetect.mode = "disabled";
|
||||
if(message.reason){
|
||||
this.settings.active.arDetect.disabledReason = message.reason;
|
||||
} else {
|
||||
this.settings.active.arDetect.disabledReason = 'User disabled';
|
||||
}
|
||||
this.settings.save();
|
||||
} else if (message.cmd === 'set-zoom') {
|
||||
this.sendToActive(message);
|
||||
}
|
||||
}
|
||||
|
||||
processReceivedMessage_nonpersistent_ff(message, sender){
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("%c[CommsServer.js::processMessage_nonpersistent_ff] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
|
||||
}
|
||||
|
||||
if (message.cmd === 'get-config') {
|
||||
var ret = {extensionConf: JSON.stringify(this.settings.active)};
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("%c[CommsServer.js::processMessage_nonpersistent_ff] Returning this:", "background-color: #11D; color: #aad", ret);
|
||||
}
|
||||
Promise.resolve(ret);
|
||||
} else if (message.cmd === "autoar-enable") {
|
||||
this.settings.active.arDetect.mode = "blacklist";
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: "reload-settings", sender: "uwbg"})
|
||||
if(Debug.debug){
|
||||
console.log("[uw-bg] autoar set to enabled (blacklist). evidenz:", this.settings.active);
|
||||
}
|
||||
} else if (message.cmd === "autoar-disable") {
|
||||
this.settings.active.arDetect.mode = "disabled";
|
||||
if(message.reason){
|
||||
this.settings.active.arDetect.disabledReason = message.reason;
|
||||
} else {
|
||||
this.settings.active.arDetect.disabledReason = 'User disabled';
|
||||
}
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: 'reload-settings', newConf: this.settings.active});
|
||||
if(Debug.debug){
|
||||
console.log("[uw-bg] autoar set to disabled. evidenz:", this.settings.active);
|
||||
}
|
||||
} else if (message.cmd === "autoar-set-interval") {
|
||||
if(Debug.debug)
|
||||
console.log("[uw-bg] trying to set new interval for autoAr. New interval is",message.timeout,"ms");
|
||||
|
||||
// set fairly liberal limit
|
||||
var timeout = message.timeout < 4 ? 4 : message.timeout;
|
||||
this.settings.active.arDetect.timer_playing = timeout;
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: 'reload-settings', newConf: this.settings.active});
|
||||
}
|
||||
}
|
||||
|
||||
processReceivedMessage_nonpersistent_chrome(message, sender, sendResponse){
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[CommsServer.js::processMessage_nonpersistent_chrome] Received message from background script!", message);
|
||||
}
|
||||
|
||||
if(message.cmd === 'get-config') {
|
||||
sendResponse({extensionConf: JSON.stringify(this.settings.active), site: this.getCurrentTabHostname()});
|
||||
// return true;
|
||||
} else if (message.cmd === "autoar-enable") {
|
||||
this.settings.active.arDetect.mode = "blacklist";
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: "reload-settings", sender: "uwbg"})
|
||||
if(Debug.debug){
|
||||
console.log("[uw-bg] autoar set to enabled (blacklist). evidenz:", this.settings.active);
|
||||
}
|
||||
} else if (message.cmd === "autoar-disable") {
|
||||
this.settings.active.arDetect.mode = "disabled";
|
||||
if(message.reason){
|
||||
this.settings.active.arDetect.disabledReason = message.reason;
|
||||
} else {
|
||||
this.settings.active.arDetect.disabledReason = 'User disabled';
|
||||
}
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: 'reload-settings', newConf: this.settings.active});
|
||||
if(Debug.debug){
|
||||
console.log("[uw-bg] autoar set to disabled. evidenz:", this.settings.active);
|
||||
}
|
||||
} else if (message.cmd === "autoar-set-interval") {
|
||||
if(Debug.debug)
|
||||
console.log("[uw-bg] trying to set new interval for autoAr. New interval is",message.timeout,"ms");
|
||||
|
||||
// set fairly liberal limit
|
||||
var timeout = message.timeout < 4 ? 4 : message.timeout;
|
||||
this.settings.active.arDetect.timer_playing = timeout;
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: 'reload-settings', newConf: this.settings.active});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Comms {
|
||||
static async sendMessage(message){
|
||||
if(BrowserDetect.firefox){
|
||||
return browser.runtime.sendMessage(message)
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
try{
|
||||
if(BrowserDetect.edge){
|
||||
browser.runtime.sendMessage(message, function(response){
|
||||
var r = response;
|
||||
resolve(r);
|
||||
});
|
||||
} else {
|
||||
chrome.runtime.sendMessage(message, function(response){
|
||||
// Chrome/js shittiness mitigation — remove this line and an empty array will be returned
|
||||
var r = response;
|
||||
resolve(r);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(e){
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,649 +0,0 @@
|
||||
var EdgeDetectPrimaryDirection = {
|
||||
VERTICAL: 0,
|
||||
HORIZONTAL: 1
|
||||
}
|
||||
|
||||
var EdgeDetectQuality = {
|
||||
FAST: 0,
|
||||
IMPROVED: 1
|
||||
}
|
||||
|
||||
class EdgeDetect{
|
||||
|
||||
constructor(ardConf){
|
||||
this.conf = ardConf;
|
||||
this.settings = ardConf.settings;
|
||||
|
||||
this.sampleWidthBase = this.settings.active.arDetect.edgeDetection.sampleWidth << 2; // corrected so we can work on imageData
|
||||
this.halfSample = this.sampleWidthBase >> 1;
|
||||
|
||||
this.detectionTreshold = this.settings.active.arDetect.edgeDetection.detectionTreshold;
|
||||
|
||||
this.init(); // initiate things that can change
|
||||
}
|
||||
|
||||
// initiates things that we may have to change later down the line
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
findBars(image, sampleCols, direction = EdgeDetectPrimaryDirection.VERTICAL, quality = EdgeDetectQuality.IMPROVED, guardLineOut){
|
||||
var fastCandidates, edgeCandidates, bars;
|
||||
if (direction == EdgeDetectPrimaryDirection.VERTICAL) {
|
||||
fastCandidates = this.findCandidates(image, sampleCols, guardLineOut);
|
||||
|
||||
// if(quality == EdgeDetectQuality.FAST){
|
||||
// edges = fastCandidates; // todo: processing
|
||||
// } else {
|
||||
edgeCandidates = this.edgeDetect(image, fastCandidates);
|
||||
bars = this.edgePostprocess(edgeCandidates, this.conf.canvas.height);
|
||||
|
||||
// }
|
||||
} else {
|
||||
bars = this.pillarTest(image) ? {status: 'ar_known'} : {status: 'ar_unknown'};
|
||||
}
|
||||
|
||||
return bars;
|
||||
}
|
||||
|
||||
findCandidates(image, sampleCols, guardLineOut){
|
||||
var upper_top, upper_bottom, lower_top, lower_bottom;
|
||||
var blackbarTreshold;
|
||||
|
||||
var cols_a = sampleCols.slice(0);
|
||||
var cols_b = cols_a.slice(0);
|
||||
|
||||
// todo: cloning can be done better. check array.splice or whatever
|
||||
for(var i in sampleCols){
|
||||
cols_b[i] = cols_a[i] + 0;
|
||||
}
|
||||
|
||||
var res_top = [];
|
||||
var res_bottom = [];
|
||||
|
||||
this.colsTreshold = sampleCols.length * this.settings.active.arDetect.edgeDetection.minColsForSearch;
|
||||
if(this.colsTreshold == 0)
|
||||
this.colsTreshold = 1;
|
||||
|
||||
this.blackbarTreshold = this.conf.blackLevel + this.settings.active.arDetect.blackbarTreshold;
|
||||
|
||||
|
||||
// if guardline didn't fail and imageDetect did, we don't have to check the upper few pixels
|
||||
// but only if upper and lower edge are defined. If they're not, we need to check full height
|
||||
if(guardLineOut){
|
||||
if(guardLineOut.imageFail && !guardLineOut.blackbarFail && this.conf.guardLine.blackbar.top) {
|
||||
upper_top = this.conf.guardLine.blackbar.top;
|
||||
upper_bottom = this.conf.canvas.height >> 1;
|
||||
lower_top = upper_bottom;
|
||||
lower_bottom = this.conf.guardLine.blackbar.bottom;
|
||||
} else if (! guardLineOut.imageFail && !guardLineOut.blackbarFail && this.conf.guardLine.blackbar.top) {
|
||||
// ta primer se lahko zgodi tudi zaradi kakšnega logotipa. Ker nočemo, da nam en jeben
|
||||
// logotip vsili reset razmerja stranic, se naredimo hrvata in vzamemo nekaj varnostnega
|
||||
// pasu preko točke, ki jo označuje guardLine.blackbar. Recimo 1/8 višine platna na vsaki strani.
|
||||
// a logo could falsely trigger this case, so we need to add some extra margins past
|
||||
// the point marked by guardLine.blackbar. Let's say 1/8 of canvas height on either side.
|
||||
upper_top = 0;
|
||||
upper_bottom = this.conf.guardLine.blackbar.top + (this.conf.canvas.height >> 3);
|
||||
lower_top = this.conf.guardLine.blackbar.bottom - (this.conf.canvas.height >> 3);
|
||||
lower_bottom = this.conf.canvas.height - 1;
|
||||
} else {
|
||||
upper_top = 0;
|
||||
upper_bottom = (this.conf.canvas.height >> 1) /*- parseInt(this.conf.canvas.height * this.settings.active.arDetect.edgeDetection.middleIgnoredArea);*/
|
||||
lower_top = (this.conf.canvas.height >> 1) /*+ parseInt(this.conf.canvas.height * this.settings.active.arDetect.edgeDetection.middleIgnoredArea);*/
|
||||
lower_bottom = this.conf.canvas.height - 1;
|
||||
}
|
||||
} else{
|
||||
upper_top = 0;
|
||||
upper_bottom = (this.conf.canvas.height >> 1) /*- parseInt(this.conf.canvas.height * this.settings.active.arDetect.edgeDetection.middleIgnoredArea);*/
|
||||
lower_top = (this.conf.canvas.height >> 1) /*+ parseInt(this.conf.canvas.height * this.settings.active.arDetect.edgeDetection.middleIgnoredArea);*/
|
||||
lower_bottom = this.conf.canvas.height - 1;
|
||||
}
|
||||
|
||||
if(Debug.debug){
|
||||
console.log("[EdgeDetect::findCandidates] searching for candidates on ranges", upper_top, "<->", upper_bottom, ";", lower_top, "<->", lower_bottom)
|
||||
}
|
||||
|
||||
var upper_top_corrected = upper_top * this.conf.canvasImageDataRowLength;
|
||||
var upper_bottom_corrected = upper_bottom * this.conf.canvasImageDataRowLength;
|
||||
var lower_top_corrected = lower_top * this.conf.canvasImageDataRowLength;
|
||||
var lower_bottom_corrected = lower_bottom * this.conf.canvasImageDataRowLength;
|
||||
|
||||
if(Debug.debugCanvas.enabled){
|
||||
this._columnTest_dbgc(image, upper_top_corrected, upper_bottom_corrected, cols_a, res_top, false);
|
||||
this._columnTest_dbgc(image, lower_top_corrected, lower_bottom_corrected, cols_b, res_bottom, true);
|
||||
} else {
|
||||
this._columnTest(image, upper_top_corrected, upper_bottom_corrected, cols_a, res_top, false);
|
||||
this._columnTest(image, lower_top_corrected, lower_bottom_corrected, cols_b, res_bottom, true);
|
||||
}
|
||||
|
||||
return {res_top: res_top, res_bottom: res_bottom};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// dont call the following outside of this class
|
||||
|
||||
edgeDetect(image, samples){
|
||||
var edgeCandidatesTop = {count: 0};
|
||||
var edgeCandidatesBottom = {count: 0};
|
||||
|
||||
var detections;
|
||||
var canvasWidth = this.conf.canvas.width;
|
||||
var canvasHeight = this.conf.canvas.height;
|
||||
|
||||
var sampleStart, sampleEnd, loopEnd;
|
||||
var sampleRow_black, sampleRow_color;
|
||||
|
||||
var blackEdgeViolation = false;
|
||||
|
||||
var topEdgeCount = 0;
|
||||
var bottomEdgeCount = 0;
|
||||
|
||||
var sample;
|
||||
|
||||
for(sample of samples.res_top){
|
||||
blackEdgeViolation = false; // reset this
|
||||
|
||||
// determine our bounds. Note that sample.col is _not_ corrected for imageData, but halfSample is
|
||||
sampleStart = (sample.col << 2) - this.halfSample;
|
||||
|
||||
if(sampleStart < 0)
|
||||
sampleStart = 0;
|
||||
|
||||
sampleEnd = sampleStart + this.sampleWidthBase;
|
||||
if(sampleEnd > this.conf.canvasImageDataRowLength)
|
||||
sampleEnd = this.conf.canvasImageDataRowLength;
|
||||
|
||||
// calculate row offsets for imageData array
|
||||
sampleRow_black = (sample.top - this.settings.active.arDetect.edgeDetection.edgeTolerancePx) * this.conf.canvasImageDataRowLength;
|
||||
sampleRow_color = (sample.top + 1 + this.settings.active.arDetect.edgeDetection.edgeTolerancePx) * this.conf.canvasImageDataRowLength;
|
||||
|
||||
// že ena kršitev črnega roba pomeni, da kandidat ni primeren
|
||||
// even a single black edge violation means the candidate is not an edge
|
||||
loopEnd = sampleRow_black + sampleEnd;
|
||||
|
||||
if(Debug.debugCanvas.enabled){
|
||||
blackEdgeViolation = this._blackbarTest_dbg(image, sampleRow_black + sampleStart, loopEnd);
|
||||
} else {
|
||||
blackEdgeViolation = this._blackbarTest(image, sampleRow_black + sampleStart, loopEnd);
|
||||
}
|
||||
|
||||
// če je bila črna črta skrunjena, preverimo naslednjega kandidata
|
||||
// if we failed, we continue our search with the next candidate
|
||||
if(blackEdgeViolation)
|
||||
continue;
|
||||
|
||||
detections = 0;
|
||||
loopEnd = sampleRow_color + sampleEnd;
|
||||
|
||||
if(Debug.debugCanvas.enabled) {
|
||||
this._imageTest_dbg(image, sampleRow_color + sampleStart, loopEnd, sample.top, edgeCandidatesTop)
|
||||
} else {
|
||||
this._imageTest(image, sampleRow_color + sampleStart, loopEnd, sample.top, edgeCandidatesTop)
|
||||
}
|
||||
}
|
||||
|
||||
for(sample of samples.res_bottom){
|
||||
blackEdgeViolation = false; // reset this
|
||||
|
||||
// determine our bounds. Note that sample.col is _not_ corrected for imageData, but this.halfSample is
|
||||
sampleStart = (sample.col << 2) - this.halfSample;
|
||||
|
||||
if(sampleStart < 0)
|
||||
sampleStart = 0;
|
||||
|
||||
sampleEnd = sampleStart + this.sampleWidthBase;
|
||||
if(sampleEnd > this.conf.canvasImageDataRowLength)
|
||||
sampleEnd = this.conf.canvasImageDataRowLength;
|
||||
|
||||
// calculate row offsets for imageData array
|
||||
sampleRow_black = (sample.bottom + this.settings.active.arDetect.edgeDetection.edgeTolerancePx) * this.conf.canvasImageDataRowLength;
|
||||
sampleRow_color = (sample.bottom - 1 - this.settings.active.arDetect.edgeDetection.edgeTolerancePx) * this.conf.canvasImageDataRowLength;
|
||||
|
||||
// že ena kršitev črnega roba pomeni, da kandidat ni primeren
|
||||
// even a single black edge violation means the candidate is not an edge
|
||||
loopEnd = sampleRow_black + sampleEnd;
|
||||
|
||||
if(Debug.debugCanvas.enabled){
|
||||
blackEdgeViolation = this._blackbarTest_dbg(image, sampleRow_black + sampleStart, loopEnd);
|
||||
} else {
|
||||
blackEdgeViolation = this._blackbarTest(image, sampleRow_black + sampleStart, loopEnd);
|
||||
}
|
||||
|
||||
// če je bila črna črta skrunjena, preverimo naslednjega kandidata
|
||||
// if we failed, we continue our search with the next candidate
|
||||
if(blackEdgeViolation)
|
||||
continue;
|
||||
|
||||
detections = 0;
|
||||
loopEnd = sampleRow_color + sampleEnd;
|
||||
|
||||
if(Debug.debugCanvas.enabled) {
|
||||
this._imageTest_dbg(image, sampleRow_color + sampleStart, loopEnd, sample.bottom, edgeCandidatesBottom);
|
||||
} else {
|
||||
this._imageTest(image, sampleRow_color + sampleStart, loopEnd, sample.bottom, edgeCandidatesBottom);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
edgeCandidatesTop: edgeCandidatesTop,
|
||||
edgeCandidatesTopCount: edgeCandidatesTop.count,
|
||||
edgeCandidatesBottom: edgeCandidatesBottom,
|
||||
edgeCandidatesBottomCount: edgeCandidatesBottom.count
|
||||
};
|
||||
}
|
||||
|
||||
edgePostprocess(edges){
|
||||
var edgesTop = [];
|
||||
var edgesBottom = [];
|
||||
var alignMargin = this.conf.canvas.height * this.settings.active.arDetect.allowedMisaligned;
|
||||
|
||||
var missingEdge = edges.edgeCandidatesTopCount == 0 || edges.edgeCandidatesBottomCount == 0;
|
||||
|
||||
// pretvorimo objekt v tabelo
|
||||
// convert objects to array
|
||||
|
||||
delete(edges.edgeCandidatesTop.count);
|
||||
delete(edges.edgeCandidatesBottom.count);
|
||||
|
||||
if( edges.edgeCandidatesTopCount > 0){
|
||||
for(var e in edges.edgeCandidatesTop){
|
||||
var edge = edges.edgeCandidatesTop[e];
|
||||
edgesTop.push({
|
||||
distance: edge.offset,
|
||||
absolute: edge.offset,
|
||||
count: edge.count
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if( edges.edgeCandidatesBottomCount > 0){
|
||||
for(var e in edges.edgeCandidatesBottom){
|
||||
var edge = edges.edgeCandidatesBottom[e];
|
||||
edgesBottom.push({
|
||||
distance: this.conf.canvas.height - edge.offset,
|
||||
absolute: edge.offset,
|
||||
count: edge.count
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// sort by distance
|
||||
edgesTop = edgesTop.sort((a,b) => {return a.distance - b.distance});
|
||||
edgesBottom = edgesBottom.sort((a,b) => {return a.distance - b.distance});
|
||||
|
||||
// če za vsako stran (zgoraj in spodaj) poznamo vsaj enega kandidata, potem lahko preverimo nekaj
|
||||
// stvari
|
||||
|
||||
if(! missingEdge ){
|
||||
// predvidevamo, da je logo zgoraj ali spodaj, nikakor pa ne na obeh straneh hkrati.
|
||||
// če kanal logotipa/watermarka ni vključil v video, potem si bosta razdaliji (edge.distance) prvih ključev
|
||||
// zgornjega in spodnjega roba približno enaki
|
||||
//
|
||||
// we'll assume that no youtube channel is rude enough to put channel logo/watermark both on top and the bottom
|
||||
// of the video. If logo's not included in the video, distances (edge.distance) of the first two keys should be
|
||||
// roughly equal. Let's check for that.
|
||||
if( edgesTop[0].distance >= edgesBottom[0].distance - alignMargin &&
|
||||
edgesTop[0].distance <= edgesBottom[0].distance + alignMargin ){
|
||||
|
||||
var blackbarWidth = edgesTop[0].distance > edgesBottom[0].distance ?
|
||||
edgesTop[0].distance : edgesBottom[0].distance;
|
||||
|
||||
return {
|
||||
status: "ar_known",
|
||||
blackbarWidth: blackbarWidth,
|
||||
guardLineTop: edgesTop[0].distance,
|
||||
guardLineBottom: edgesBottom[0].absolute,
|
||||
|
||||
top: edgesTop[0].distance,
|
||||
bottom: edgesBottom[0].distance
|
||||
};
|
||||
}
|
||||
|
||||
// torej, lahko da je na sliki watermark. Lahko, da je slika samo ornh črna. Najprej preverimo za watermark
|
||||
// it could be watermark. It could be a dark frame. Let's check for watermark first.
|
||||
if( edgesTop[0].distance < edgesBottom[0].distance &&
|
||||
edgesTop[0].count < edgesBottom[0].count &&
|
||||
edgesTop[0].count < this.conf.sampleCols.length * this.settings.active.arDetect.edgeDetection.logoTreshold){
|
||||
// možno, da je watermark zgoraj. Preverimo, če se kateri od drugih potencialnih robov na zgornjem robu
|
||||
// ujema s prvim spodnjim (+/- variance). Če je temu tako, potem bo verjetno watermark. Logo mora imeti
|
||||
// manj vzorcev kot navaden rob.
|
||||
|
||||
if(edgesTop[0].length > 1){
|
||||
var lowMargin = edgesBottom[0].distance - alignMargin;
|
||||
var highMargin = edgesBottom[0].distance + alignMargin;
|
||||
|
||||
for(var i = 1; i < edgesTop.length; i++){
|
||||
if(edgesTop[i].distance >= lowMargin && edgesTop[i].distance <= highMargin){
|
||||
// dobili smo dejanski rob. vrnimo ga
|
||||
// we found the actual edge. let's return that.
|
||||
var blackbarWidth = edgesTop[i].distance > edgesBottom[0].distance ?
|
||||
edgesTop[i].distance : edgesBottom[0].distance;
|
||||
|
||||
return {
|
||||
status: "ar_known",
|
||||
blackbarWidth: blackbarWidth,
|
||||
guardLineTop: edgesTop[i].distance,
|
||||
guardLineBottom: edgesBottom[0].absolute,
|
||||
|
||||
top: edgesTop[i].distance,
|
||||
bottom: edgesBottom[0].distance
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if( edgesBottom[0].distance < edgesTop[0].distance &&
|
||||
edgesBottom[0].count < edgesTop[0].count &&
|
||||
edgesBottom[0].count <this.conf.sampleCols.length * this.settings.active.arDetect.edgeDetection.logoTreshold){
|
||||
|
||||
if(edgesBottom[0].length > 1){
|
||||
var lowMargin = edgesTop[0].distance - alignMargin;
|
||||
var highMargin = edgesTop[0].distance + alignMargin;
|
||||
|
||||
for(var i = 1; i < edgesBottom.length; i++){
|
||||
if(edgesBottom[i].distance >= lowMargin && edgesTop[i].distance <= highMargin){
|
||||
// dobili smo dejanski rob. vrnimo ga
|
||||
// we found the actual edge. let's return that.
|
||||
var blackbarWidth = edgesBottom[i].distance > edgesTop[0].distance ?
|
||||
edgesBottom[i].distance : edgesTop[0].distance;
|
||||
|
||||
return {
|
||||
status: "ar_known",
|
||||
blackbarWidth: blackbarWidth,
|
||||
guardLineTop: edgesTop[0].distance,
|
||||
guardLineBottom: edgesBottom[0].absolute,
|
||||
|
||||
top: edgesTop[0].distance,
|
||||
bottom: edgesBottom[i].distance
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
// zgornjega ali spodnjega roba nismo zaznali. Imamo še en trik, s katerim lahko poskusimo
|
||||
// določiti razmerje stranic
|
||||
// either the top or the bottom edge remains undetected, but we have one more trick that we
|
||||
// can try. It also tries to work around logos.
|
||||
|
||||
var edgeDetectionTreshold = this.conf.sampleCols.length * this.settings.active.arDetect.edgeDetection.singleSideConfirmationTreshold;
|
||||
|
||||
if(edges.edgeCandidatesTopCount == 0 && edges.edgeCandidatesBottomCount != 0){
|
||||
for(var edge of edgesBottom){
|
||||
if(edge.count >= edgeDetectionTreshold)
|
||||
return {
|
||||
status: "ar_known",
|
||||
blackbarWidth: edge.distance,
|
||||
guardLineTop: null,
|
||||
guardLineBottom: edge.bottom,
|
||||
|
||||
top: edge.distance,
|
||||
bottom: edge.distance
|
||||
}
|
||||
}
|
||||
}
|
||||
if(edges.edgeCandidatesTopCount != 0 && edges.edgeCandidatesBottomCount == 0){
|
||||
for(var edge of edgesTop){
|
||||
if(edge.count >= edgeDetectionTreshold)
|
||||
return {
|
||||
status: "ar_known",
|
||||
blackbarWidth: edge.distance,
|
||||
guardLineTop: edge.top,
|
||||
guardLineBottom: null,
|
||||
|
||||
top: edge.distance,
|
||||
bottom: edge.distance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// če pridemo do sem, nam ni uspelo nič. Razmerje stranic ni znano
|
||||
// if we reach this bit, we have failed in determining aspect ratio. It remains unknown.
|
||||
return {status: "ar_unknown"}
|
||||
}
|
||||
|
||||
pillarTest(image){
|
||||
// preverimo, če na sliki obstajajo navpične črne obrobe. Vrne 'true' če so zaznane (in če so približno enako debele), 'false' sicer.
|
||||
// true vrne tudi, če zaznamo preveč črnine.
|
||||
// <==XX(::::}----{::::)XX==>
|
||||
// checks the image for presence of vertical pillars. Less accurate than 'find blackbar limits'. If we find a non-black object that's
|
||||
// roughly centered, we return true. Otherwise we return false.
|
||||
// we also return true if we detect too much black
|
||||
|
||||
var blackbarTreshold, upper, lower;
|
||||
blackbarTreshold = this.conf.blackLevel + this.settings.active.arDetect.blackbarTreshold;
|
||||
|
||||
|
||||
var middleRowStart = (this.conf.canvas.height >> 1) * this.conf.canvas.width;
|
||||
var middleRowEnd = middleRowStart + this.conf.canvas.width - 1;
|
||||
|
||||
var rowStart = middleRowStart << 2;
|
||||
var midpoint = (middleRowStart + (this.conf.canvas.width >> 1)) << 2
|
||||
var rowEnd = middleRowEnd << 2;
|
||||
|
||||
var edge_left = -1, edge_right = -1;
|
||||
|
||||
// preverimo na levi strani
|
||||
// let's check for edge on the left side
|
||||
for(var i = rowStart; i < midpoint; i+=4){
|
||||
if(image[i] > blackbarTreshold || image[i+1] > blackbarTreshold || image[i+2] > blackbarTreshold){
|
||||
edge_left = (i - rowStart) >> 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// preverimo na desni strani
|
||||
// check on the right
|
||||
for(var i = rowEnd; i > midpoint; i-= 4){
|
||||
if(image[i] > blackbarTreshold || image[i+1] > blackbarTreshold || image[i+2] > blackbarTreshold){
|
||||
edge_right = this.conf.canvas.width - ((i - rowStart) >> 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// če je katerikoli -1, potem imamo preveč črnine
|
||||
// we probably have too much black if either of those two is -1
|
||||
if(edge_left == -1 || edge_right == -1){
|
||||
return true;
|
||||
}
|
||||
|
||||
// če sta oba robova v mejah merske napake, potem vrnemo 'false'
|
||||
// if both edges resemble rounding error, we retunr 'false'
|
||||
if(edge_left < this.settings.active.arDetect.pillarTest.ignoreThinPillarsPx && edge_right < this.settings.active.arDetect.pillarTest.ignoreThinPillarsPx){
|
||||
return false;
|
||||
}
|
||||
|
||||
var edgeError = this.settings.active.arDetect.pillarTest.allowMisaligned;
|
||||
var error_low = 1 - edgeError;
|
||||
var error_hi = 1 + edgeError;
|
||||
|
||||
// če sta 'edge_left' in 'edge_right' podobna/v mejah merske napake, potem vrnemo true — lahko da smo našli logo na sredini zaslona
|
||||
// if 'edge_left' and 'edge_right' are similar enough to each other, we return true. If we found a logo in a black frame, we could
|
||||
// crop too eagerly
|
||||
if( (edge_left * error_low) < edge_right &&
|
||||
(edge_left * error_hi) > edge_right ){
|
||||
return true;
|
||||
}
|
||||
|
||||
// če se ne zgodi nič od neštetega, potem nismo našli problemov
|
||||
// if none of the above, we haven't found a problem
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// pomožne funkcije
|
||||
// helper functions
|
||||
|
||||
_columnTest(image, top, bottom, colsIn, colsOut, reverseSearchDirection){
|
||||
var tmpI;
|
||||
if(reverseSearchDirection){
|
||||
for(var i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
for(var col of colsIn){
|
||||
tmpI = i + (col << 2);
|
||||
|
||||
if( image[tmpI] > this.blackbarTreshold ||
|
||||
image[tmpI + 1] > this.blackbarTreshold ||
|
||||
image[tmpI + 2] > this.blackbarTreshold ){
|
||||
|
||||
var bottom = (i / this.conf.canvasImageDataRowLength) + 1;
|
||||
colsOut.push({
|
||||
col: col,
|
||||
bottom: bottom
|
||||
});
|
||||
colsIn.splice(colsIn.indexOf(col), 1);
|
||||
}
|
||||
}
|
||||
if(colsIn.length < this.colsTreshold)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for(var i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
|
||||
for(var col of colsIn){
|
||||
tmpI = i + (col << 2);
|
||||
|
||||
if( image[tmpI] > this.blackbarTreshold ||
|
||||
image[tmpI + 1] > this.blackbarTreshold ||
|
||||
image[tmpI + 2] > this.blackbarTreshold ){
|
||||
|
||||
colsOut.push({
|
||||
col: col,
|
||||
top: (i / this.conf.canvasImageDataRowLength) - 1
|
||||
});
|
||||
colsIn.splice(colsIn.indexOf(col), 1);
|
||||
}
|
||||
}
|
||||
if(colsIn.length < this.colsTreshold)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_columnTest_dbgc(image, top, bottom, colsIn, colsOut, reverseSearchDirection){
|
||||
var tmpI;
|
||||
if(reverseSearchDirection){
|
||||
for(var i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
for(var col of colsIn){
|
||||
tmpI = i + (col << 2);
|
||||
|
||||
if( image[tmpI] > this.blackbarTreshold ||
|
||||
image[tmpI + 1] > this.blackbarTreshold ||
|
||||
image[tmpI + 2] > this.blackbarTreshold ){
|
||||
|
||||
var bottom = (i / this.conf.canvasImageDataRowLength) + 1;
|
||||
colsOut.push({
|
||||
col: col,
|
||||
bottom: bottom
|
||||
});
|
||||
colsIn.splice(colsIn.indexOf(col), 1);
|
||||
this.conf.debugCanvas.trace(tmpI,DebugCanvasClasses.EDGEDETECT_CANDIDATE);
|
||||
}
|
||||
else{
|
||||
this.conf.debugCanvas.trace(tmpI, DebugCanvasClasses.EDGEDETECT_ONBLACK);
|
||||
}
|
||||
}
|
||||
if(colsIn.length < this.colsTreshold)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for(var i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
|
||||
for(var col of colsIn){
|
||||
tmpI = i + (col << 2);
|
||||
|
||||
if( image[tmpI] > this.blackbarTreshold ||
|
||||
image[tmpI + 1] > this.blackbarTreshold ||
|
||||
image[tmpI + 2] > this.blackbarTreshold ){
|
||||
|
||||
colsOut.push({
|
||||
col: col,
|
||||
top: (i / this.conf.canvasImageDataRowLength) - 1
|
||||
});
|
||||
colsIn.splice(colsIn.indexOf(col), 1);
|
||||
this.conf.debugCanvas.trace(tmpI, DebugCanvasClasses.EDGEDETECT_CANDIDATE);
|
||||
if(tmpI-1 > 0){
|
||||
this.conf.debugCanvas.trace(tmpI - 1, DebugCanvasClasses.EDGEDETECT_CANDIDATE_SECONDARY);
|
||||
}
|
||||
if(tmpI+1 < image.length){
|
||||
this.conf.debugCanvas.trace(tmpI + 1, DebugCanvasClasses.EDGEDETECT_CANDIDATE_SECONDARY);
|
||||
}
|
||||
} else {
|
||||
this.conf.debugCanvas.trace(tmpI, DebugCanvasClasses.EDGEDETECT_ONBLACK);
|
||||
}
|
||||
}
|
||||
if(colsIn.length < this.colsTreshold)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_blackbarTest(image, start, end){
|
||||
for(var i = start; i < end; i += 4){
|
||||
if( image[i ] > this.blackbarTreshold ||
|
||||
image[i+1] > this.blackbarTreshold ||
|
||||
image[i+2] > this.blackbarTreshold ){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // no violation
|
||||
}
|
||||
|
||||
_blackbarTest_dbg(image, start, end){
|
||||
for(var i = start; i < end; i += 4){
|
||||
if( image[i ] > this.blackbarTreshold ||
|
||||
image[i+1] > this.blackbarTreshold ||
|
||||
image[i+2] > this.blackbarTreshold ){
|
||||
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.VIOLATION)
|
||||
return true;
|
||||
} else {
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.EDGEDETECT_BLACKBAR)
|
||||
}
|
||||
}
|
||||
return false; // no violation
|
||||
}
|
||||
|
||||
_imageTest(image, start, end, sampleOffset, edgeCandidates){
|
||||
var detections = 0;
|
||||
|
||||
for(var i = start; i < end; i += 4){
|
||||
if( image[i ] > this.blackbarTreshold ||
|
||||
image[i+1] > this.blackbarTreshold ||
|
||||
image[i+2] > this.blackbarTreshold ){
|
||||
++detections;
|
||||
}
|
||||
}
|
||||
if(detections >= this.detectionTreshold){
|
||||
if(edgeCandidates[sampleOffset] != undefined)
|
||||
edgeCandidates[sampleOffset].count++;
|
||||
else{
|
||||
edgeCandidates[sampleOffset] = {offset: sampleOffset, count: 1};
|
||||
edgeCandidates.count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_imageTest_dbg(image, start, end, sampleOffset, edgeCandidates){
|
||||
var detections = 0;
|
||||
|
||||
for(var i = start; i < end; i += 4){
|
||||
if( image[i ] > this.blackbarTreshold ||
|
||||
image[i+1] > this.blackbarTreshold ||
|
||||
image[i+2] > this.blackbarTreshold ){
|
||||
++detections;
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.EDGEDETECT_IMAGE);
|
||||
} else {
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.WARN);
|
||||
}
|
||||
}
|
||||
if(detections >= this.detectionTreshold){
|
||||
if(edgeCandidates[sampleOffset] != undefined)
|
||||
edgeCandidates[sampleOffset].count++;
|
||||
else{
|
||||
edgeCandidates[sampleOffset] = {offset: sampleOffset, count: 1};
|
||||
edgeCandidates.count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
class Settings {
|
||||
|
||||
constructor(activeSettings, updateCallback) {
|
||||
this.active = activeSettings ? activeSettings : undefined;
|
||||
this.default = ExtensionConf;
|
||||
this.useSync = false;
|
||||
this.version = undefined;
|
||||
this.updateCallback = updateCallback;
|
||||
|
||||
const ths = this;
|
||||
|
||||
if(BrowserDetect.firefox) {
|
||||
browser.storage.onChanged.addListener( (changes, area) => {
|
||||
if (Debug.debug && Debug.debugStorage) {
|
||||
console.log("[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
||||
if (changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
console.log("[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
|
||||
}
|
||||
}
|
||||
if(changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
ths.setActive(JSON.parse(changes.uwSettings.newValue));
|
||||
}
|
||||
|
||||
if(this.updateCallback) {
|
||||
try {
|
||||
updateCallback();
|
||||
} catch (e) {
|
||||
console.log("[Settings] CALLING UPDATE CALLBACK FAILED.")
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (BrowserDetect.chrome) {
|
||||
chrome.storage.onChanged.addListener( (changes, area) => {
|
||||
if (Debug.debug && Debug.debugStorage) {
|
||||
console.log("[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
||||
if (changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
console.log("[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
|
||||
}
|
||||
}
|
||||
if(changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
ths.setActive(JSON.parse(changes.uwSettings.newValue));
|
||||
}
|
||||
|
||||
if(this.updateCallback) {
|
||||
try {
|
||||
updateCallback();
|
||||
} catch (e) {
|
||||
console.log("[Settings] CALLING UPDATE CALLBACK FAILED.")
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
const settings = await this.get();
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[Settings::init] Configuration fetched from storage:", settings);
|
||||
}
|
||||
|
||||
// if there's no settings saved, return default settings.
|
||||
if(! settings || (Object.keys(settings).length === 0 && settings.constructor === Object)) {
|
||||
this.setDefaultSettings();
|
||||
this.active = this.getDefaultSettings();
|
||||
return this.active;
|
||||
}
|
||||
|
||||
// if there's settings, set saved object as active settings
|
||||
this.active = settings;
|
||||
|
||||
// check if extension has been updated. If not, return settings as they were retreived
|
||||
if (BrowserDetect.firefox) {
|
||||
this.version = browser.runtime.getManifest().version;
|
||||
} else if (BrowserDetect.chrome) {
|
||||
this.version = chrome.runtime.getManifest().version;
|
||||
} else if (BrowserDetect.edge) {
|
||||
this.version = browser.runtime.getManifest().version;
|
||||
}
|
||||
|
||||
if(settings.version === this.version) {
|
||||
if(Debug.debug) {
|
||||
console.log("[Settings::init] extension was saved with current version of ultrawidify (", this.version, "). Returning object as-is.");
|
||||
}
|
||||
return this.active;
|
||||
}
|
||||
|
||||
// if extension has been updated, update existing settings with any options added in the
|
||||
// new version. In addition to that, we remove old keys that are no longer used.
|
||||
const patched = ObjectCopy.addNew(settings, this.default);
|
||||
if(Debug.debug) {
|
||||
console.log("[Settings.init] Results from ObjectCopy.addNew()?", patched, "\n\nSettings from storage", settings, "\ndefault?", this.default,);
|
||||
}
|
||||
|
||||
if(patched){
|
||||
this.active = patched;
|
||||
} else {
|
||||
this.active = JSON.parse(JSON.stringify(this.default));
|
||||
}
|
||||
|
||||
this.set(this.active);
|
||||
return this.active;
|
||||
}
|
||||
|
||||
async get() {
|
||||
if (BrowserDetect.firefox || BrowserDetect.edge) {
|
||||
const ret = this.useSync ? await browser.storage.sync.get('uwSettings') : await browser.storage.local.get('uwSettings');
|
||||
try {
|
||||
return JSON.parse(ret.uwSettings);
|
||||
} catch(e) {
|
||||
return undefined;
|
||||
}
|
||||
} else if (BrowserDetect.chrome) {
|
||||
const ret = new Promise( (resolve, reject) => {
|
||||
chrome.storage.sync.get('uwSettings', (res) => resolve(res));
|
||||
});
|
||||
return ret['uwSettings'];
|
||||
}
|
||||
}
|
||||
|
||||
async set(extensionConf) {
|
||||
if (Debug.debug) {
|
||||
console.log("[Settings::set] setting new settings:", extensionConf)
|
||||
}
|
||||
|
||||
if (BrowserDetect.firefox || BrowserDetect.edge) {
|
||||
extensionConf.version = this.version;
|
||||
return this.useSync ? browser.storage.sync.set( {'uwSettings': JSON.stringify(extensionConf)}): browser.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
||||
} else if (BrowserDetect.chrome) {
|
||||
return chrome.storage.sync.set( {'uwSettings': JSON.stringify(extensionConf)});
|
||||
}
|
||||
}
|
||||
|
||||
async setActive(activeSettings) {
|
||||
this.active = activeSettings;
|
||||
}
|
||||
|
||||
async setProp(prop, value) {
|
||||
this.active[prop] = value;
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (Debug.debug) {
|
||||
console.log("[Settings::save] Saving active settings:", this.active);
|
||||
}
|
||||
|
||||
this.set(this.active);
|
||||
}
|
||||
|
||||
getDefaultSettings() {
|
||||
return JSON.parse(JSON.stringify(this.default));
|
||||
}
|
||||
|
||||
setDefaultSettings() {
|
||||
this.set(this.default);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------
|
||||
// Nastavitve za posamezno stran
|
||||
// Config for a given page:
|
||||
//
|
||||
// <hostname> : {
|
||||
// status: <option> // should extension work on this site?
|
||||
// arStatus: <option> // should we do autodetection on this site?
|
||||
// statusEmbedded: <option> // reserved for future... maybe
|
||||
// }
|
||||
//
|
||||
// Veljavne vrednosti za možnosti
|
||||
// Valid values for options:
|
||||
//
|
||||
// status, arStatus, statusEmbedded:
|
||||
//
|
||||
// * enabled — always allow
|
||||
// * default — allow if default is to allow, block if default is to block
|
||||
// * disabled — never allow
|
||||
|
||||
getSiteSettings(site) {
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
}
|
||||
if (!site || !this.active.sites[site]) {
|
||||
return {};
|
||||
}
|
||||
return this.active.sites[site];
|
||||
}
|
||||
|
||||
canStartExtension(site) {
|
||||
// returns 'true' if extension can be started on a given site. Returns false if we shouldn't run.
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
|
||||
if (!site) {
|
||||
console.log("[Settings::canStartExtension] window.location.hostname is null or undefined:", window.location.hostname)
|
||||
console.log("active settings:", this.active)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Debug.debug) {
|
||||
// let's just temporarily disable debugging while recursively calling
|
||||
// this function to get extension status on current site without duplo
|
||||
// console logs (and without endless recursion)
|
||||
Debug.debug = false;
|
||||
const cse = this.canStartExtension(site);
|
||||
Debug.debug = true;
|
||||
}
|
||||
try{
|
||||
// if site is not defined, we use default mode:
|
||||
if (! this.active.sites[site]) {
|
||||
return this.active.extensionMode === "blacklist";
|
||||
}
|
||||
|
||||
if(this.active.extensionMode === "blacklist") {
|
||||
return this.active.sites[site].status !== "disabled";
|
||||
} else if (this.active.extensionMode === "whitelist") {
|
||||
return this.active.sites[site].status === "enabled";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}catch(e){
|
||||
if(Debug.debug){
|
||||
console.log("[Settings.js::canStartExtension] Something went wrong — are settings defined/has init() been called?\nSettings object:", this)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
extensionEnabled(){
|
||||
return this.active.extensionMode !== 'disabled'
|
||||
}
|
||||
|
||||
extensionEnabledForSite(site) {
|
||||
return this.canStartExtension(site);
|
||||
}
|
||||
|
||||
canStartAutoAr(site) {
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
|
||||
if (!site) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Debug.debug) {
|
||||
// let's just temporarily disable debugging while recursively calling
|
||||
// this function to get extension status on current site without duplo
|
||||
// console logs (and without endless recursion)
|
||||
Debug.debug = false;
|
||||
const csar = this.canStartAutoAr(site);
|
||||
Debug.debug = true;
|
||||
|
||||
console.log("[Settings::canStartAutoAr] ----------------\nCAN WE START THIS EXTENSION ON SITE", site,
|
||||
"?\n\nsettings.active.sites[site]=", this.active.sites[site],
|
||||
"\nExtension mode?", this.active.arDetect.mode,
|
||||
"\nCan extension be started?", csar
|
||||
);
|
||||
}
|
||||
|
||||
// if site is not defined, we use default mode:
|
||||
if (! this.active.sites[site]) {
|
||||
return this.active.arDetect.mode === "blacklist";
|
||||
}
|
||||
|
||||
if (this.active.arDetect.mode === "blacklist") {
|
||||
return this.active.sites[site].arStatus !== "disabled";
|
||||
} else if (this.active.arDetect.mode === "whitelist") {
|
||||
return this.active.sites[site].arStatus === "enabled";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultAr(site) {
|
||||
site = this.getSiteSettings(site);
|
||||
|
||||
if (site.defaultAr) {
|
||||
return site.defaultAr;
|
||||
}
|
||||
return this.active.miscFullscreenSettings.defaultAr;
|
||||
}
|
||||
|
||||
getDefaultStretchMode(site) {
|
||||
site = this.getSiteSettings(site);
|
||||
|
||||
if (site.stretch) {
|
||||
return site.stretch;
|
||||
}
|
||||
return this.active.stretch.initialMode;
|
||||
}
|
||||
|
||||
getDefaultVideoAlignment(site) {
|
||||
site = this.getSiteSettings(site);
|
||||
|
||||
if (site.videoAlignment) {
|
||||
return site.videoAlignment;
|
||||
}
|
||||
return this.active.miscFullscreenSettings.videoFloat;
|
||||
}
|
||||
}
|
@ -1,885 +0,0 @@
|
||||
class ArDetector {
|
||||
|
||||
constructor(videoData){
|
||||
this.conf = videoData;
|
||||
this.video = videoData.video;
|
||||
this.settings = videoData.settings;
|
||||
|
||||
this.setupTimer = null;
|
||||
this.timer = null;
|
||||
|
||||
this.sampleCols = [];
|
||||
|
||||
// todo: dynamically detect the following two
|
||||
this.canFallback = true;
|
||||
this.fallbackMode = false;
|
||||
|
||||
this.blackLevel = this.settings.active.arDetect.blackLevel_default;
|
||||
|
||||
this.arid = (Math.random()*100).toFixed();
|
||||
|
||||
if (Debug.init) {
|
||||
console.log("[ArDetector::ctor] creating new ArDetector. arid:", this.arid);
|
||||
}
|
||||
}
|
||||
|
||||
init(){
|
||||
if (Debug.debug || Debug.init) {
|
||||
console.log("[ArDetect::init] Initializing autodetection. arid:", this.arid);
|
||||
}
|
||||
this.setup(this.settings.active.arDetect.hSamples, this.settings.active.arDetect.vSamples);
|
||||
}
|
||||
|
||||
destroy(){
|
||||
if(Debug.debug || Debug.init) {
|
||||
console.log(`[ArDetect::destroy] <arid:${this.arid}>`)
|
||||
}
|
||||
this.debugCanvas.destroy();
|
||||
this.stop();
|
||||
}
|
||||
|
||||
setup(cwidth, cheight, forceStart){
|
||||
|
||||
this.guardLine = new GuardLine(this);
|
||||
this.edgeDetector = new EdgeDetect(this);
|
||||
this.debugCanvas = new DebugCanvas(this);
|
||||
|
||||
if(Debug.debug || Debug.init) {
|
||||
console.log("[ArDetect::setup] Starting autodetection setup. arid:", this.arid);
|
||||
}
|
||||
|
||||
if (this.fallbackMode || cheight !== this.settings.active.arDetect.hSamples) {
|
||||
if(Debug.debug) {
|
||||
console.log("%c[ArDetect::setup] WARNING: CANVAS RESET DETECTED - recalculating guardLine", "background: #000; color: #ff2" )
|
||||
}
|
||||
// blackbar, imagebar
|
||||
this.guardLine.reset();
|
||||
}
|
||||
|
||||
if(!cwidth){
|
||||
cwidth = this.settings.active.arDetect.hSamples;
|
||||
cheight = this.settings.active.arDetect.vSamples;
|
||||
}
|
||||
|
||||
|
||||
try{
|
||||
if(Debug.debug){
|
||||
console.log("[ArDetect::setup] Trying to setup automatic aspect ratio detector. Choice config bits:\ncanvas dimensions:",cwidth, "×", cheight, "\nvideoData:", this.conf);
|
||||
}
|
||||
|
||||
this._halted = false;
|
||||
this.detectionTimeoutEventCount = 0;
|
||||
|
||||
// // vstavimo začetne stolpce v this.sampleCols. - NE!
|
||||
// // let's insert initial columns to this.sampleCols - NO!!! do it later dow
|
||||
// this.sampleCols = [];
|
||||
|
||||
// var samplingIntervalPx = parseInt(cheight / this.settings.active.arDetect.samplingInterval)
|
||||
// for(var i = 1; i < this.settings.active.arDetect.samplingInterval; i++){
|
||||
// this.sampleCols.push(i * samplingIntervalPx);
|
||||
// }
|
||||
|
||||
if(this.canvas){
|
||||
if(Debug.debug)
|
||||
console.log("[ArDetect::setup] existing canvas found. REMOVING KEBAB removing kebab\n\n\n\n(im hungry and you're not authorized to have it)");
|
||||
|
||||
this.canvas.remove();
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("[ArDetect::setup] canvas removed");
|
||||
}
|
||||
|
||||
// imamo video, pa tudi problem. Ta problem bo verjetno kmalu popravljen, zato setup začnemo hitreje kot prej
|
||||
// we have a video, but also a problem. This problem will prolly be fixed very soon, so setup is called with
|
||||
// less delay than before
|
||||
|
||||
if(this.video.videoWidth === 0 || this.video.videoHeight === 0 ){
|
||||
|
||||
if(Debug.debug){
|
||||
console.log("[ArDetector::setup] video has no width or height!", this.video.videoWidth,"×", this.video.videoHeight)
|
||||
}
|
||||
this.scheduleInitRestart();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// things to note: we'll be keeping canvas in memory only.
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.canvas.width = cwidth;
|
||||
this.canvas.height = cheight;
|
||||
|
||||
this.context = this.canvas.getContext("2d");
|
||||
|
||||
// do setup once
|
||||
// tho we could do it for every frame
|
||||
this.canvasScaleFactor = cheight / this.video.videoHeight;
|
||||
|
||||
try{
|
||||
// determine where to sample
|
||||
var ncol = this.settings.active.arDetect.staticSampleCols;
|
||||
var nrow = this.settings.active.arDetect.staticSampleRows;
|
||||
|
||||
var colSpacing = this.canvas.width / ncol;
|
||||
var rowSpacing = (this.canvas.height << 2) / nrow;
|
||||
|
||||
this.sampleLines = [];
|
||||
this.sampleCols = [];
|
||||
|
||||
for(var i = 0; i < ncol; i++){
|
||||
if(i < ncol - 1)
|
||||
this.sampleCols.push(Math.round(colSpacing * i));
|
||||
else{
|
||||
this.sampleCols.push(Math.round(colSpacing * i) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < nrow; i++){
|
||||
if(i < ncol - 5)
|
||||
this.sampleLines.push(Math.round(rowSpacing * i));
|
||||
else{
|
||||
this.sampleLines.push(Math.round(rowSpacing * i) - 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(ex){
|
||||
console.log("%c[ArDetect::_arSetup] something went terribly wrong when calcuating sample colums.", this.settings.active.colors.criticalFail);
|
||||
console.log("settings object:", Settings);
|
||||
console.log("error:", ex);
|
||||
}
|
||||
|
||||
// we're also gonna reset this
|
||||
this.guardLine.top = null;
|
||||
this.guardLine.bottom = null;
|
||||
|
||||
this.resetBlackLevel();
|
||||
this._forcehalt = false;
|
||||
// if we're restarting ArDetect, we need to do this in order to force-recalculate aspect ratio
|
||||
|
||||
this.conf.resizer.setLastAr({type: "auto", ar: null});
|
||||
|
||||
this.canvasImageDataRowLength = cwidth << 2;
|
||||
this.noLetterboxCanvasReset = false;
|
||||
|
||||
if(forceStart || this.settings.canStartAutoAr() ) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
catch(ex){
|
||||
console.log(ex);
|
||||
}
|
||||
|
||||
if(Debug.debugCanvas.enabled){
|
||||
this.debugCanvas.init({width: cwidth, height: cheight});
|
||||
// DebugCanvas.draw("test marker","test","rect", {x:5, y:5}, {width: 5, height: 5});
|
||||
}
|
||||
|
||||
this.conf.arSetupComplete = true;
|
||||
}
|
||||
|
||||
start(){
|
||||
if (Debug.debug) {
|
||||
console.log("%c[ArDetect::setup] Starting automatic aspect ratio detection.", _ard_console_start);
|
||||
}
|
||||
this._halted = false;
|
||||
this.conf.resizer.resetLastAr();
|
||||
this.scheduleFrameCheck(0, true);
|
||||
}
|
||||
|
||||
unpause() {
|
||||
if(this._paused){ // resume only if we explicitly paused
|
||||
this._paused = false;
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
pause() {
|
||||
// pause only if we were running before. Don't pause if we aren't running
|
||||
// (we are running when _halted is neither true nor undefined)
|
||||
if (this._halted === false) {
|
||||
this._paused = true;
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
stop(){
|
||||
if(Debug.debug){
|
||||
console.log("%c[ArDetect::_ard_stop] Stopping automatic aspect ratio detection", _ard_console_stop);
|
||||
}
|
||||
this._forcehalt = true;
|
||||
this._halted = true;
|
||||
clearTimeout(this.setupTimer);
|
||||
clearTimeout(this.timer);
|
||||
this.conf.resizer.resetLastAr();
|
||||
}
|
||||
|
||||
isRunning(){
|
||||
return ! this._halted && ! this._paused;
|
||||
}
|
||||
|
||||
|
||||
scheduleInitRestart(timeout, force_reset){
|
||||
if(! timeout){
|
||||
timeout = 100;
|
||||
}
|
||||
// don't allow more than 1 instance
|
||||
if(this.setupTimer){
|
||||
clearTimeout(this.setupTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
this.setupTimer = setTimeout(function(){
|
||||
ths.setupTimer = null;
|
||||
try{
|
||||
ths.init();
|
||||
}catch(e){console.log("[ArDetector::scheduleInitRestart] Failed to start init(). Error:",e)}
|
||||
ths = null;
|
||||
},
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
async scheduleFrameCheck(timeout, force_reset){
|
||||
if(! timeout){
|
||||
this.frameCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
var e = await this.settings.get();
|
||||
// run anything that needs to be run after frame check
|
||||
this.postFrameCheck();
|
||||
|
||||
// don't allow more than 1 instance
|
||||
if(this.timer){
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
|
||||
// console.log(this.video, "this.video | ths.video", ths.video)
|
||||
// console.log(this.conf.video, "this.conf | ths.conf", ths.conf.video)
|
||||
// console.log("resizer conf&vid",
|
||||
// this.conf.resizer, this.conf.resizer.conf, this.conf.resizer.video, this.conf.resizer.conf.video )
|
||||
|
||||
// debugger;
|
||||
|
||||
this.timer = setTimeout(function(){
|
||||
ths.timer = null;
|
||||
try{
|
||||
ths.frameCheck();
|
||||
}catch(e){console.log("Frame check failed. Error:",e)}
|
||||
ths = null;
|
||||
},
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
postFrameCheck(){
|
||||
if(Debug.debugCanvas.enabled){
|
||||
this.debugCanvas.update();
|
||||
}
|
||||
}
|
||||
|
||||
//#region helper functions (general)
|
||||
attachCanvas(canvas){
|
||||
if(this.attachedCanvas)
|
||||
this.attachedCanvas.remove();
|
||||
|
||||
// todo: place canvas on top of the video instead of random location
|
||||
canvas.style.position = "absolute";
|
||||
canvas.style.left = "200px";
|
||||
canvas.style.top = "1200px";
|
||||
canvas.style.zIndex = 10000;
|
||||
|
||||
document.getElementsByTagName("body")[0]
|
||||
.appendChild(canvas);
|
||||
}
|
||||
|
||||
canvasReadyForDrawWindow(){
|
||||
if(Debug.debug)
|
||||
console.log("%c[ArDetect::_ard_canvasReadyForDrawWindow] (?)", "color: #44f", this.canvas.height == window.innerHeight, "(ard_height:", this.canvas.height, "| window height:", window.innerHeight, ")");
|
||||
|
||||
return this.canvas.height == window.innerHeight
|
||||
}
|
||||
|
||||
getTimeout(baseTimeout, startTime){
|
||||
var execTime = (performance.now() - startTime);
|
||||
|
||||
if( execTime > this.settings.active.arDetect.autoDisable.maxExecutionTime ){
|
||||
// this.detectionTimeoutEventCount++;
|
||||
|
||||
if(Debug.debug){
|
||||
console.log("[ArDetect::getTimeout] Exec time exceeded maximum allowed execution time. This has now happened " + this.detectionTimeoutEventCount + " times in a row.");
|
||||
}
|
||||
|
||||
// if( this.detectionTimeoutEventCount >= this.settings.active.arDetect.autoDisable.consecutiveTimeoutCount ){
|
||||
// if (Debug.debug){
|
||||
// console.log("[ArDetect::getTimeout] Maximum execution time was exceeded too many times. Automatic aspect ratio detection has been disabled.");
|
||||
// }
|
||||
|
||||
// Comms.sendToBackgroundScript({cmd: 'disable-autoar', reason: 'Automatic aspect ratio detection was taking too much time and has been automatically disabled in order to avoid lag.'});
|
||||
// _ard_stop();
|
||||
// return 999999;
|
||||
// }
|
||||
|
||||
} else {
|
||||
this.detectionTimeoutEventCount = 0;
|
||||
}
|
||||
// return baseTimeout > this.settings.active.arDetect.minimumTimeout ? baseTimeout : this.settings.active.arDetect.minimumTimeout;
|
||||
|
||||
return baseTimeout;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
calculateArFromEdges(edges) {
|
||||
// if we don't specify these things, they'll have some default values.
|
||||
if(edges.top === undefined){
|
||||
edges.top = 0;
|
||||
edges.bottom = 0;
|
||||
edge.left = 0;
|
||||
edges.right = 0;
|
||||
}
|
||||
|
||||
let zoomFactor = 1;
|
||||
var letterbox = edges.top + edges.bottom;
|
||||
|
||||
if (this.fallbackMode) {
|
||||
// there's stuff missing from the canvas. We need to assume canvas' actual height is bigger by a factor x, where
|
||||
// x = [video.zoomedHeight] / [video.unzoomedHeight]
|
||||
//
|
||||
// letterbox also needs to be corrected:
|
||||
// letterbox += [video.zoomedHeight] - [video.unzoomedHeight]
|
||||
|
||||
var vbr = this.video.getBoundingClientRect();
|
||||
|
||||
zoomFactor = vbr.height / this.video.clientHeight;
|
||||
letterbox += vbr.height - this.video.clientHeight;
|
||||
}
|
||||
|
||||
var trueHeight = this.canvas.height * zoomFactor - letterbox;
|
||||
|
||||
if(this.fallbackMode){
|
||||
if(edges.top > 1 && edges.top <= this.settings.active.arDetect.fallbackMode.noTriggerZonePx ){
|
||||
if(Debug.debug && Debug.debugArDetect) {
|
||||
console.log("Edge is in the no-trigger zone. Aspect ratio change is not triggered.")
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// varnostno območje, ki naj ostane črno (da lahko v fallback načinu odkrijemo ožanje razmerja stranic).
|
||||
// x2, ker je safetyBorderPx definiran za eno stran.
|
||||
// safety border so we can detect aspect ratio narrowing (21:9 -> 16:9).
|
||||
// x2 because safetyBorderPx is for one side.
|
||||
trueHeight += (this.settings.active.arDetect.fallbackMode.safetyBorderPx << 1);
|
||||
}
|
||||
|
||||
|
||||
return this.canvas.width * zoomFactor / trueHeight;
|
||||
}
|
||||
|
||||
processAr(trueAr){
|
||||
let actualHeight = 0; // purely for fallback mode
|
||||
this.detectedAr = trueAr;
|
||||
|
||||
// poglejmo, če se je razmerje stranic spremenilo
|
||||
// check if aspect ratio is changed:
|
||||
var lastAr = this.conf.resizer.getLastAr();
|
||||
if( lastAr.type == "auto" && lastAr.ar != null){
|
||||
// spremembo lahko zavrnemo samo, če uporabljamo avtomatski način delovanja in če smo razmerje stranic
|
||||
// že nastavili.
|
||||
//
|
||||
// we can only deny aspect ratio changes if we use automatic mode and if aspect ratio was set from here.
|
||||
|
||||
var arDiff = trueAr - lastAr.ar;
|
||||
|
||||
if (arDiff < 0)
|
||||
arDiff = -arDiff;
|
||||
|
||||
var arDiff_percent = arDiff / trueAr;
|
||||
|
||||
// ali je sprememba v mejah dovoljenega? Če da -> fertik
|
||||
// is ar variance within acceptable levels? If yes -> we done
|
||||
if(Debug.debug && Debug.debugArDetect)
|
||||
console.log("%c[ArDetect::_ard_processAr] new aspect ratio varies from the old one by this much:\n","color: #aaf","old Ar", lastAr.ar, "current ar", trueAr, "arDiff (absolute):",arDiff,"ar diff (relative to new ar)", arDiff_percent);
|
||||
|
||||
if (arDiff < trueAr * this.settings.active.arDetect.allowedArVariance){
|
||||
if(Debug.debug && Debug.debugArDetect)
|
||||
console.log("%c[ArDetect::_ard_processAr] aspect ratio change denied — diff %:", "background: #740; color: #fa2", arDiff_percent)
|
||||
|
||||
return;
|
||||
}
|
||||
else if(Debug.debug && Debug.debugArDetect){
|
||||
console.log("%c[ArDetect::_ard_processAr] aspect ratio change accepted — diff %:", "background: #153; color: #4f9", arDiff_percent)
|
||||
}
|
||||
}
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("[ArDetect::_ard_processAr] attempting to fix aspect ratio. New aspect ratio: ", trueAr);
|
||||
|
||||
this.conf.resizer.setAr(trueAr, {type: "auto", ar: trueAr});
|
||||
}
|
||||
|
||||
frameCheck(){
|
||||
|
||||
// console.log("this.video:", this.video, this.conf.video);
|
||||
// debugger;
|
||||
|
||||
if(this._halted)
|
||||
return;
|
||||
|
||||
if(! this.video){
|
||||
if(Debug.debug || Debug.warnings_critical)
|
||||
console.log("[ArDetect::_ard_vdraw] Video went missing. Destroying current instance of videoData.")
|
||||
this.conf.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
var fallbackMode = false;
|
||||
var startTime = performance.now();
|
||||
var baseTimeout = this.settings.active.arDetect.timer_playing;
|
||||
var triggerTimeout;
|
||||
|
||||
var guardLineResult = true; // true if success, false if fail. true by default
|
||||
var imageDetectResult = false; // true if we detect image along the way. false by default
|
||||
|
||||
|
||||
var sampleCols = this.sampleCols.slice(0);
|
||||
|
||||
var how_far_treshold = 8; // how much can the edge pixel vary (*4)
|
||||
|
||||
if(this.video.ended ){
|
||||
// we slow down if ended. Detecting is pointless.
|
||||
|
||||
this.scheduleFrameCheck(this.settings.active.arDetect.timer_paused);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this.video.paused){
|
||||
// če je video pavziran, še vedno skušamo zaznati razmerje stranic - ampak bolj poredko.
|
||||
// if the video is paused, we still do autodetection. We just do it less often.
|
||||
baseTimeout = this.settings.active.arDetect.timer_paused;
|
||||
}
|
||||
|
||||
try{
|
||||
this.context.drawImage(this.video, 0,0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
catch(ex){
|
||||
if(Debug.debug) {
|
||||
console.log("%c[ArDetect::_ard_vdraw] can't draw image on canvas. Trying canvas.drawWindow instead", "color:#000; backgroud:#f51;", ex);
|
||||
}
|
||||
|
||||
try{
|
||||
if(! this.settings.active.arDetect.fallbackMode.enabled)
|
||||
throw "fallbackMode is disabled.";
|
||||
|
||||
if(this.canvasReadyForDrawWindow()){
|
||||
this.context.drawWindow(window, this.canvasDrawWindowHOffset, 0, this.canvas.width, this.canvas.height, "rgba(0,0,128,1)");
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("%c[ArDetect::_ard_vdraw] canvas.drawImage seems to have worked", "color:#000; backgroud:#2f5;");
|
||||
this.fallbackMode = true;
|
||||
}
|
||||
else{
|
||||
// canvas needs to be resized, so let's change setup
|
||||
this.stop();
|
||||
|
||||
var newCanvasWidth = window.innerHeight * (this.video.videoWidth / this.video.videoHeight);
|
||||
var newCanvasHeight = window.innerHeight;
|
||||
|
||||
if (this.conf.resizer.videoFloat === "center") {
|
||||
this.canvasDrawWindowHOffset = Math.round((window.innerWidth - newCanvasWidth) * 0.5);
|
||||
} else if (this.conf.resizer.videoFloat == "left") {
|
||||
this.canvasDrawWindowHOffset = 0;
|
||||
} else {
|
||||
this.canvasDrawWindowHOffset = window.innerWidth - newCanvasWidth;
|
||||
}
|
||||
|
||||
this.setup(newCanvasWidth, newCanvasHeight);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
catch(ex){
|
||||
if(Debug.debug)
|
||||
console.log("%c[ArDetect::_ard_vdraw] okay this didnt work either", "color:#000; backgroud:#f51;", ex);
|
||||
|
||||
this.scheduleFrameCheck( this.settings.active.arDetect.timer_error );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (! this.blackLevel) {
|
||||
if(Debug.debugArDetect)
|
||||
console.log("[ArDetect::_ard_vdraw] black level undefined, resetting");
|
||||
|
||||
this.resetBlackLevel();
|
||||
}
|
||||
|
||||
// we get the entire frame so there's less references for garbage collection to catch
|
||||
var image = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height).data;
|
||||
|
||||
if(Debug.debugCanvas.enabled){
|
||||
this.debugCanvas.setBuffer(image);
|
||||
}
|
||||
|
||||
//#region black level detection
|
||||
|
||||
// fast test to see if aspect ratio is correct. If we detect anything darker than blackLevel, we modify
|
||||
// blackLevel to the new lowest value
|
||||
var isLetter=true;
|
||||
var currentMaxVal = 0;
|
||||
var currentMax_a;
|
||||
var currentMinVal = 48; // not 255 cos safety, even this is prolly too high
|
||||
var currentMin_a;
|
||||
|
||||
var rowOffset = 0;
|
||||
var colOffset_r, colOffset_g, colOffset_b;
|
||||
|
||||
// detect black level. if currentMax and currentMin vary too much, we automatically know that
|
||||
// black level is bogus and that we aren't letterboxed. We still save the darkest value as black level,
|
||||
// though — as black bars will never be brighter than that.
|
||||
|
||||
for(var i = 0; i < sampleCols.length; ++i){
|
||||
colOffset_r = sampleCols[i] << 2;
|
||||
colOffset_g = colOffset_r + 1;
|
||||
colOffset_b = colOffset_r + 2;
|
||||
|
||||
currentMax_a = image[colOffset_r] > image[colOffset_g] ? image[colOffset_r] : image[colOffset_g];
|
||||
currentMax_a = currentMax_a > image[colOffset_b] ? currentMax_a : image[colOffset_b];
|
||||
|
||||
currentMaxVal = currentMaxVal > currentMax_a ? currentMaxVal : currentMax_a;
|
||||
|
||||
currentMin_a = image[colOffset_r] < image[colOffset_g] ? image[colOffset_r] : image[colOffset_g];
|
||||
currentMin_a = currentMin_a < image[colOffset_b] ? currentMin_a : image[colOffset_b];
|
||||
|
||||
currentMinVal = currentMinVal < currentMin_a ? currentMinVal : currentMin_a;
|
||||
}
|
||||
|
||||
// we'll shift the sum. math says we can do this
|
||||
rowOffset = this.canvas.width * (this.canvas.height - 1);
|
||||
|
||||
for(var i = 0; i < sampleCols.length; ++i){
|
||||
colOffset_r = (rowOffset + sampleCols[i]) << 2;
|
||||
colOffset_g = colOffset_r + 1;
|
||||
colOffset_b = colOffset_r + 2;
|
||||
|
||||
currentMax_a = image[colOffset_r] > image[colOffset_g] ? image[colOffset_r] : image[colOffset_g];
|
||||
currentMax_a = currentMax_a > image[colOffset_b] ? currentMax_a : image[colOffset_b];
|
||||
|
||||
currentMaxVal = currentMaxVal > currentMax_a ? currentMaxVal : currentMax_a;
|
||||
|
||||
currentMin_a = image[colOffset_r] < image[colOffset_g] ? image[colOffset_r] : image[colOffset_g];
|
||||
currentMin_a = currentMin_a < image[colOffset_b] ? currentMin_a : image[colOffset_b];
|
||||
|
||||
if(currentMinVal == undefined && currenMinVal != undefined)
|
||||
currentMinVal = currentMin_a;
|
||||
else if(currentMin_a != undefined)
|
||||
currentMinVal = currentMinVal < currentMin_a ? currentMinVal : currentMin_a;
|
||||
|
||||
}
|
||||
|
||||
// save black level only if defined
|
||||
if(currentMinVal)
|
||||
this.blackLevel = this.blackLevel < currentMinVal ? this.blackLevel : currentMinVal;
|
||||
|
||||
//#endregion
|
||||
|
||||
// this means we don't have letterbox
|
||||
if ( currentMaxVal > (this.blackLevel + this.settings.active.arDetect.blackbarTreshold) || (currentMaxVal - currentMinVal) > this.settings.active.arDetect.blackbarTreshold*4 ){
|
||||
|
||||
// Če ne zaznamo letterboxa, kličemo reset. Lahko, da je bilo razmerje stranic popravljeno na roke. Možno je tudi,
|
||||
// da je letterbox izginil.
|
||||
// If we don't detect letterbox, we reset aspect ratio to aspect ratio of the video file. The aspect ratio could
|
||||
// have been corrected manually. It's also possible that letterbox (that was there before) disappeared.
|
||||
if(Debug.debug && Debug.debugArDetect){
|
||||
console.log(`%c[ArDetect::_ard_vdraw] ---- NO EDGE DETECTED! — canvas has no edge. ----\ncurrentMaxVal: ${currentMaxVal}\nBlack level (+ treshold):${this.blackLevel} (${this.blackLevel + this.settings.active.arDetect.blackbarTreshold})\n---diff test---\nmaxVal-minVal: ${ (currentMaxVal - currentMinVal)}\ntreshold: ${this.settings.active.arDetect.blackbarTreshold}`, "color: #aaf");
|
||||
}
|
||||
|
||||
// Pogledamo, ali smo že kdaj ponastavili CSS. Če še nismo, potem to storimo. Če smo že, potem ne.
|
||||
// Ponastavimo tudi guardline (na null).
|
||||
// let's chec if we ever reset CSS. If we haven't, then we do so. If we did, then we don't.
|
||||
// while resetting the CSS, we also reset guardline top and bottom back to null.
|
||||
|
||||
if(! this.noLetterboxCanvasReset){
|
||||
this.conf.resizer.reset({type: "auto", ar: null});
|
||||
this.guardLine.reset();
|
||||
this.noLetterboxCanvasReset = true;
|
||||
}
|
||||
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout); //no letterbox, no problem
|
||||
return;
|
||||
}
|
||||
|
||||
if(Debug.debug && Debug.debugArDetect){
|
||||
console.log(`%c[ArDetect::_ard_vdraw] edge was detected. Here are stats:\ncurrentMaxVal: ${currentMaxVal}\nBlack level (+ treshold):${this.blackLevel} (${this.blackLevel + this.settings.active.arDetect.blackbarTreshold})\n---diff test---\nmaxVal-minVal: ${ (currentMaxVal - currentMinVal)}\ntreshold: ${this.settings.active.arDetect.blackbarTreshold}`, "color: #afa");
|
||||
}
|
||||
|
||||
// Če preverjamo naprej, potem moramo postaviti to vrednost nazaj na 'false'. V nasprotnem primeru se bo
|
||||
// css resetiral enkrat na video/pageload namesto vsakič, ko so za nekaj časa obrobe odstranejene
|
||||
// if we look further we need to reset this value back to false. Otherwise we'll only get CSS reset once
|
||||
// per video/pageload instead of every time letterbox goes away (this can happen more than once per vid)
|
||||
this.noLetterboxCanvasReset = false;
|
||||
|
||||
// let's do a quick test to see if we're on a black frame
|
||||
// TODO: reimplement but with less bullshit
|
||||
|
||||
// poglejmo, če obrežemo preveč.
|
||||
// let's check if we're cropping too much (or whatever)
|
||||
var guardLineOut;
|
||||
|
||||
guardLineOut = this.guardLine.check(image, this.fallbackMode);
|
||||
|
||||
if (guardLineOut.blackbarFail) { // add new ssamples to our sample columns
|
||||
for(var col of guardLineOut.offenders){
|
||||
sampleCols.push(col)
|
||||
}
|
||||
}
|
||||
|
||||
// če ni padla nobena izmed funkcij, potem se razmerje stranic ni spremenilo
|
||||
// if both succeed, then aspect ratio hasn't changed.
|
||||
|
||||
// if we're in fallback mode and blackbar test failed, we restore CSS
|
||||
if (this.fallbackMode && guardLineOut.blackbarFail) {
|
||||
this.conf.resizer.reset({type: "auto", ar: null});
|
||||
this.guardLine.reset();
|
||||
this.noLetterboxCanvasReset = true;
|
||||
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout); //no letterbox, no problem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!guardLineOut.imageFail && !guardLineOut.blackbarFail) {
|
||||
if(Debug.debug && Debug.debugArDetect){
|
||||
console.log(`%c[ArDetect::_ard_vdraw] guardLine tests were successful. (no imagefail and no blackbarfail)\n`, "color: #afa", guardLineOut);
|
||||
}
|
||||
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout); //no letterbox, no problem
|
||||
return;
|
||||
}
|
||||
|
||||
// će se razmerje stranic spreminja iz ožjega na širšega, potem najprej poglejmo za prisotnostjo navpičnih črnih obrob.
|
||||
// če so prisotne navpične obrobe tudi na levi in desni strani, potlej obstaja možnost, da gre za logo na črnem ozadju.
|
||||
// v tem primeru obstaja nevarnost, da porežemo preveč. Ker obstaja dovolj velika možnost, da bi porezali preveč, rajši
|
||||
// ne naredimo ničesar.
|
||||
//
|
||||
// če je pillarbox zaznan v primeru spremembe iz ožjega na širše razmerje stranice, razmerje povrnemo na privzeto vrednost.
|
||||
//
|
||||
// If aspect ratio changes from narrower to wider, we first check for presence of pillarbox. Presence of pillarbox indicates
|
||||
// a chance of a logo on black background. We could cut easily cut too much. Because there's a somewhat significant chance
|
||||
// that we will cut too much, we rather avoid doing anything at all. There's gonna be a next chance.
|
||||
try{
|
||||
if(guardLineOut.blackbarFail || guardLineOut.imageFail){
|
||||
if(this.edgeDetector.findBars(image, null, EdgeDetectPrimaryDirection.HORIZONTAL).status === 'ar_known'){
|
||||
|
||||
if(Debug.debug && guardLineOut.blackbarFail){
|
||||
console.log("[ArDetect::_ard_vdraw] Detected blackbar violation and pillarbox. Resetting to default aspect ratio.");
|
||||
}
|
||||
|
||||
if(guardLineOut.blackbarFail){
|
||||
this.conf.resizer.reset({type: "auto", ar: null});
|
||||
this.guardLine.reset();
|
||||
}
|
||||
|
||||
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
if(Debug.debug) {
|
||||
console.log("[ArDetect.js::frameCheck] something went wrong when checking for pillarbox. Error:\n", e)
|
||||
}
|
||||
}
|
||||
|
||||
// pa poglejmo, kje se končajo črne letvice na vrhu in na dnu videa.
|
||||
// let's see where black bars end.
|
||||
this.sampleCols_current = sampleCols.length;
|
||||
|
||||
// blackSamples -> {res_top, res_bottom}
|
||||
|
||||
var edgePost = this.edgeDetector.findBars(image, sampleCols, EdgeDetectPrimaryDirection.VERTICAL, EdgeDetectQuality.IMPROVED, guardLineOut);
|
||||
|
||||
if(Debug.debug && Debug.debugArDetect){
|
||||
console.log(`%c[ArDetect::_ard_vdraw] edgeDetector returned this\n`, "color: #aaf", edgePost);
|
||||
}
|
||||
// console.log("SAMPLES:", blackbarSamples, "candidates:", edgeCandidates, "post:", edgePost,"\n\nblack level:", this.blackLevel, "tresh:", this.blackLevel + this.settings.active.arDetect.blackbarTreshold);
|
||||
|
||||
if(edgePost.status == "ar_known"){
|
||||
|
||||
// zaznali smo rob — vendar pa moramo pred obdelavo še preveriti, ali ni "rob" slučajno besedilo. Če smo kot rob pofočkali
|
||||
// besedilo, potem to ni veljaven rob. Razmerja stranic se zato ne bomo pipali.
|
||||
// we detected an edge — but before we process it, we need to check if the "edge" isn't actually some text. If the detected
|
||||
// edge is actually some text on black background, we shouldn't touch the aspect ratio. Whatever we detected is invalid.
|
||||
// var textEdge = false;;
|
||||
|
||||
// if(edgePost.guardLineTop != null){
|
||||
// var row = edgePost.guardLineTop + ~~(this.canvas.height * this.settings.active.arDetect.textLineTest.testRowOffset);
|
||||
// textEdge |= textLineTest(image, row);
|
||||
// }
|
||||
// if(edgePost.guardLineTop != null){
|
||||
// var row = edgePost.guardLineTop - ~~(this.canvas.height * this.settings.active.arDetect.textLineTest.testRowOffset);
|
||||
// textEdge |= textLineTest(image, row);
|
||||
// }
|
||||
|
||||
// v nekaterih common-sense izjemah ne storimo ničesar
|
||||
|
||||
var newAr = this.calculateArFromEdges(edgePost);
|
||||
|
||||
if (this.fallbackMode
|
||||
&& (!guardLineOut.blackbarFail && guardLineOut.imageFail)
|
||||
&& newAr < this.conf.resizer.getLastAr().ar
|
||||
) {
|
||||
// V primeru nesmiselnih rezultatov tudi ne naredimo ničesar.
|
||||
// v fallback mode se lahko naredi, da je novo razmerje stranice ožje kot staro, kljub temu da je šel
|
||||
// blackbar test skozi. Spremembe v tem primeru ne dovolimo.
|
||||
//
|
||||
// (Pravilen fix? Popraviti je treba računanje robov. V fallback mode je treba upoštevati, da obrobe,
|
||||
// ki smo jih obrezali, izginejo is canvasa)
|
||||
//
|
||||
// NOTE: pravilen fix je bil implementiran
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout);
|
||||
return;
|
||||
}
|
||||
// if(!textEdge){
|
||||
if(Debug.debug && Debug.debugArDetect){
|
||||
console.log(`%c[ArDetect::_ard_vdraw] Triggering aspect ration change! new ar: ${newAr}`, "color: #aaf");
|
||||
}
|
||||
this.processAr(newAr);
|
||||
|
||||
// we also know edges for guardline, so set them.
|
||||
// we need to be mindful of fallbackMode though
|
||||
if (!this.fallbackMode) {
|
||||
this.guardLine.setBlackbar({top: edgePost.guardLineTop, bottom: edgePost.guardLineBottom});
|
||||
} else {
|
||||
if (this.conf.player.dimensions){
|
||||
this.guardLine.setBlackbarManual({
|
||||
top: this.settings.active.arDetect.fallbackMode.noTriggerZonePx,
|
||||
bottom: this.conf.player.dimensions.height - this.settings.active.arDetect.fallbackMode.noTriggerZonePx - 1
|
||||
},{
|
||||
top: edgePost.guardLineTop + this.settings.active.arDetect.guardLine.edgeTolerancePx,
|
||||
bottom: edgePost.guardLineBottom - this.settings.active.arDetect.guardLine.edgeTolerancePx
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// }
|
||||
// else{
|
||||
// console.log("detected text on edges, dooing nothing")
|
||||
// }
|
||||
|
||||
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout);
|
||||
return;
|
||||
} else {
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout); //no letterbox, no problem
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
resetBlackLevel(){
|
||||
this.blackLevel = this.settings.active.arDetect.blackLevel_default;
|
||||
}
|
||||
|
||||
}
|
||||
if(Debug.debug)
|
||||
console.log("Loading: ArDetect");
|
||||
|
||||
var _ard_console_stop = "background: #000; color: #f41";
|
||||
var _ard_console_start = "background: #000; color: #00c399";
|
||||
|
||||
|
||||
var textLineTest = function(image, row){
|
||||
// preverimo, če vrstica vsebuje besedilo na črnem ozadju. Če ob pregledu vrstice naletimo na veliko sprememb
|
||||
// iz črnega v ne-črno, potem obstaja možnost, da gledamo besedilo. Prisotnost take vrstice je lahko znak, da
|
||||
// zaznano razmerje stranic ni veljavno
|
||||
//
|
||||
// vrne 'true' če zazna text, 'false' drugače.
|
||||
//
|
||||
//
|
||||
// check if line contains any text. If line scan reveals a lot of changes from black to non-black there's a
|
||||
// chance we're looking at text on a black background. If we detect text near what we think is an edge of the
|
||||
// video, there's a good chance we're about to incorrectly adjust the aspect ratio.
|
||||
//
|
||||
// returns 'true' if text is detected, 'false' otherwise
|
||||
|
||||
var blackbarTreshold = this.blackLevel + this.settings.active.arDetect.blackbarTreshold;
|
||||
var nontextTreshold = this.canvas.width * this.settings.active.arDetect.textLineTest.nonTextPulse;
|
||||
|
||||
var rowStart = (row * this.canvas.width) << 2;
|
||||
var rowEnd = rowStart + (this.canvas.width << 2);
|
||||
|
||||
var pulse = false;
|
||||
var currentPulseLength = 0, pulseCount = 0;
|
||||
var pulses = [];
|
||||
var longestBlack = 0;
|
||||
|
||||
// preglejmo vrstico
|
||||
// analyse the row
|
||||
for(var i = rowStart; i < rowEnd; i+= 4){
|
||||
if(pulse){
|
||||
if(image[i] < blackbarTreshold || image[i+1] < blackbarTreshold || image[i+2] < blackbarTreshold){
|
||||
// pulses.push(currentPulseLength);
|
||||
pulseCount++;
|
||||
pulse = false;
|
||||
currentPulseLength = 0;
|
||||
}
|
||||
else{
|
||||
currentPulseLength++;
|
||||
|
||||
// če najdemo dovolj dolgo zaporedje ne-črnih točk, potem vrnemo 'false' — dobili smo legitimen rob
|
||||
// if we find long enough uninterrupted line of non-black point, we fail the test. We found a legit edge.
|
||||
if(currentPulseLength > nontextTreshold){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(image[i] > blackbarTreshold || image[i+1] > blackbarTreshold || image[i+2] > blackbarTreshold){
|
||||
if(currentPulseLength > longestBlack){
|
||||
longestBlack = currentPulseLength;
|
||||
}
|
||||
pulse = true;
|
||||
currentPulseLength = 0;
|
||||
}
|
||||
else{
|
||||
currentPulseLength++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(pulse){
|
||||
pulseCount++;
|
||||
// pulses.push(currentPulseLength);
|
||||
}
|
||||
|
||||
// pregledamo rezultate:
|
||||
// analyse the results
|
||||
// console.log("pulse test:\n\npulses:", pulseCount, "longest black:", longestBlack);
|
||||
|
||||
// če smo zaznali dovolj pulzov, potem vrnemo res
|
||||
// if we detected enough pulses, we return true
|
||||
if(pulseCount > this.settings.active.arDetect.textLineTest.pulsesToConfirm){
|
||||
return true;
|
||||
}
|
||||
|
||||
// če je najdaljša neprekinjena črta črnih pikslov širša od polovice širine je merilo za zaznavanje
|
||||
// besedila rahlo milejše
|
||||
// if the longest uninterrupted line of black pixels is wider than half the width, we use a more
|
||||
// forgiving standard for determining if we found text
|
||||
if( longestBlack > (this.canvas.width >> 1) &&
|
||||
pulseCount > this.settings.active.arDetect.textLineTest.pulsesToConfirmIfHalfBlack ){
|
||||
return true;
|
||||
}
|
||||
|
||||
// če pridemo do sem, potem besedilo ni bilo zaznano
|
||||
// if we're here, no text was detected
|
||||
return false;
|
||||
}
|
||||
|
@ -1,294 +0,0 @@
|
||||
if(Debug.debug)
|
||||
console.log("Loading: PageInfo.js");
|
||||
|
||||
class PageInfo {
|
||||
constructor(comms, settings){
|
||||
this.hasVideos = false;
|
||||
this.siteDisabled = false;
|
||||
this.videos = [];
|
||||
this.settings = settings;
|
||||
|
||||
this.lastUrl = window.location.href;
|
||||
|
||||
this.rescan(RescanReason.PERIODIC);
|
||||
this.scheduleUrlCheck();
|
||||
|
||||
if(comms){
|
||||
this.comms = comms;
|
||||
}
|
||||
|
||||
if(this.videos.length > 0){
|
||||
console.log("registering video")
|
||||
comms.registerVideo();
|
||||
}
|
||||
|
||||
this.currentZoomScale = 1;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if(Debug.debug || Debug.init){
|
||||
console.log("[PageInfo::destroy] destroying all videos!")
|
||||
}
|
||||
if(this.rescanTimer){
|
||||
clearTimeout(this.rescanTimer);
|
||||
}
|
||||
for (var video of this.videos) {
|
||||
video.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
for(var video of this.videos) {
|
||||
video.destroy();
|
||||
}
|
||||
this.rescan(RescanReason.MANUAL);
|
||||
}
|
||||
|
||||
rescan(rescanReason){
|
||||
try{
|
||||
var vids = document.getElementsByTagName('video');
|
||||
|
||||
if(!vids || vids.length == 0){
|
||||
this.hasVideos = false;
|
||||
|
||||
if(rescanReason == RescanReason.PERIODIC){
|
||||
this.scheduleRescan(RescanReason.PERIODIC);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// add new videos
|
||||
this.hasVideos = false;
|
||||
var videoExists = false;
|
||||
var video, v;
|
||||
|
||||
for (video of vids) {
|
||||
// če najdemo samo en video z višino in širino, to pomeni, da imamo na strani veljavne videe
|
||||
// če trenutni video nima definiranih teh vrednostih, preskočimo vse nadaljnja preverjanja
|
||||
// <===[:::::::]===>
|
||||
// if we find even a single video with width and height, that means the page has valid videos
|
||||
// if video lacks either of the two properties, we skip all further checks cos pointless
|
||||
if(video.offsetWidth && video.offsetHeight){
|
||||
this.hasVideos = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
videoExists = false;
|
||||
|
||||
for (v of this.videos) {
|
||||
if (v.destroyed) {
|
||||
continue; //TODO: if destroyed video is same as current video, copy aspect ratio settings to current video
|
||||
}
|
||||
|
||||
if (v.video == video) {
|
||||
videoExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (videoExists) {
|
||||
continue;
|
||||
} else {
|
||||
if(Debug.debug && Debug.periodic && Debug.videoRescan){
|
||||
console.log("[PageInfo::rescan] found new video candidate:", video)
|
||||
}
|
||||
v = new VideoData(video, this.settings, this);
|
||||
// console.log("[PageInfo::rescan] v is:", v)
|
||||
// debugger;
|
||||
v.initArDetection();
|
||||
this.videos.push(v);
|
||||
|
||||
if(Debug.debug && Debug.periodic && Debug.videoRescan){
|
||||
console.log("[PageInfo::rescan] — videos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.removeDestroyed();
|
||||
|
||||
// console.log("Rescan complete. Total videos?", this.videos.length)
|
||||
}catch(e){
|
||||
console.log("rescan error:",e)
|
||||
}
|
||||
|
||||
if(rescanReason == RescanReason.PERIODIC){
|
||||
this.scheduleRescan(RescanReason.PERIODIC);
|
||||
}
|
||||
}
|
||||
|
||||
removeDestroyed(){
|
||||
this.videos = this.videos.filter( vid => vid.destroyed === false);
|
||||
}
|
||||
|
||||
scheduleRescan(rescanReason){
|
||||
if(rescanReason != RescanReason.PERIODIC){
|
||||
this.rescan(rescanReason);
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
if(this.rescanTimer){
|
||||
clearTimeout(this.rescanTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
|
||||
|
||||
this.rescanTimer = setTimeout(function(rr){
|
||||
ths.rescanTimer = null;
|
||||
ths.rescan(rr);
|
||||
ths = null;
|
||||
}, rescanReason === this.settings.active.pageInfo.timeouts.rescan, RescanReason.PERIODIC)
|
||||
} catch(e) {
|
||||
if(Debug.debug){
|
||||
console.log("[PageInfo::scheduleRescan] scheduling rescan failed. Here's why:",e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scheduleUrlCheck() {
|
||||
try{
|
||||
if(this.urlCheckTimer){
|
||||
clearTimeout(this.urlCheckTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
|
||||
this.rescanTimer = setTimeout(function(){
|
||||
ths.rescanTimer = null;
|
||||
ths.ghettoUrlCheck();
|
||||
ths = null;
|
||||
}, this.settings.active.pageInfo.timeouts.urlCheck)
|
||||
}catch(e){
|
||||
if(Debug.debug){
|
||||
console.log("[PageInfo::scheduleUrlCheck] scheduling URL check failed. Here's why:",e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ghettoUrlCheck() {
|
||||
if (this.lastUrl != window.location.href){
|
||||
if(Debug.debug){
|
||||
console.log("[PageInfo::ghettoUrlCheck] URL has changed. Triggering a rescan!");
|
||||
}
|
||||
|
||||
this.rescan(RescanReason.URL_CHANGE);
|
||||
this.lastUrl = window.location.href;
|
||||
}
|
||||
|
||||
this.scheduleUrlCheck();
|
||||
}
|
||||
|
||||
initArDetection(){
|
||||
for(var vd of this.videos){
|
||||
vd.initArDetection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// to je treba klicat ob menjavi zavihkov
|
||||
// these need to be called on tab switch
|
||||
pauseProcessing(){
|
||||
for(var vd of this.videos){
|
||||
vd.pause();
|
||||
}
|
||||
}
|
||||
|
||||
resumeProcessing(resumeAutoar = false){
|
||||
for(var vd of this.videos){
|
||||
vd.resume();
|
||||
if(resumeAutoar){
|
||||
vd.resumeAutoAr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
startArDetection(){
|
||||
for(var vd of this.videos){
|
||||
vd.startArDetection();
|
||||
}
|
||||
}
|
||||
|
||||
stopArDetection(){
|
||||
for(var vd of this.videos){
|
||||
vd.stopArDetection();
|
||||
}
|
||||
}
|
||||
|
||||
setAr(ar){
|
||||
if(ar !== 'auto') {
|
||||
this.stopArDetection();
|
||||
}
|
||||
|
||||
// TODO: find a way to only change aspect ratio for one video
|
||||
if (ar === 'reset') {
|
||||
for (var vd of this.videos) {
|
||||
vd.resetAr();
|
||||
}
|
||||
} else {
|
||||
for (var vd of this.videos) {
|
||||
vd.setAr(ar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setVideoFloat(videoFloat) {
|
||||
for(var vd of this.videos) {
|
||||
vd.setVideoFloat(videoFloat)
|
||||
}
|
||||
}
|
||||
|
||||
setPanMode(mode) {
|
||||
for(var vd of this.videos) {
|
||||
vd.setPanMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
restoreAr() {
|
||||
for(var vd of this.videos){
|
||||
vd.restoreAr()
|
||||
}
|
||||
}
|
||||
|
||||
setStretchMode(sm){
|
||||
// TODO: find a way to only change aspect ratio for one video
|
||||
|
||||
for(var vd of this.videos){
|
||||
vd.setStretchMode(sm)
|
||||
}
|
||||
}
|
||||
|
||||
setZoom(zoomLevel, no_announce) {
|
||||
for(var vd of this.videos) {
|
||||
vd.setZoom(zoomLevel, no_announce);
|
||||
}
|
||||
}
|
||||
|
||||
zoomStep(step){
|
||||
for(var vd of this.videos){
|
||||
vd.zoomStep(step);
|
||||
}
|
||||
}
|
||||
|
||||
announceZoom(scale) {
|
||||
if (this.announceZoomTimeout) {
|
||||
clearTimeout(this.announceZoom);
|
||||
}
|
||||
this.currentZoomScale = scale;
|
||||
const ths = this;
|
||||
this.announceZoomTimeout = setTimeout(() => ths.comms.announceZoom(scale), this.settings.active.zoom.announceDebounce);
|
||||
}
|
||||
|
||||
requestCurrentZoom() {
|
||||
this.comms.announceZoom(this.currentZoomScale);
|
||||
}
|
||||
}
|
||||
|
||||
var RescanReason = {
|
||||
PERIODIC: 0,
|
||||
URL_CHANGE: 1,
|
||||
MANUAL: 2
|
||||
}
|
@ -1,470 +0,0 @@
|
||||
if(Debug.debug)
|
||||
console.log("Loading: Resizer.js");
|
||||
|
||||
var StretchMode = {
|
||||
NO_STRETCH: 0,
|
||||
BASIC: 1,
|
||||
HYBRID: 2,
|
||||
CONDITIONAL: 3
|
||||
}
|
||||
|
||||
class Resizer {
|
||||
|
||||
constructor(videoData){
|
||||
this.conf = videoData;
|
||||
this.video = videoData.video;
|
||||
this.settings = videoData.settings;
|
||||
|
||||
this.scaler = new Scaler(this.conf);
|
||||
this.stretcher = new Stretcher(this.conf);
|
||||
this.zoom = new Zoom(this.conf);
|
||||
|
||||
// load up default values
|
||||
this.correctedVideoDimensions = {};
|
||||
this.currentCss = {};
|
||||
this.currentStyleString = "";
|
||||
this.currentCssValidFor = {};
|
||||
|
||||
// restore watchdog. While true, applyCss() tries to re-apply new css until this value becomes false again
|
||||
// value becomes false when width and height of <video> tag match with what we want to set. Only necessary when
|
||||
// calling _res_restore() for some weird reason.
|
||||
this.restore_wd = false;
|
||||
|
||||
// CSS watcher will trigger _very_ often for this many iterations
|
||||
this.cssWatcherIncreasedFrequencyCounter = 0;
|
||||
|
||||
|
||||
this.lastAr = this.settings.getDefaultAr(); // this is the aspect ratio we start with
|
||||
this.videoFloat = this.settings.getDefaultVideoAlignment(); // this is initial video alignment
|
||||
this.destroyed = false;
|
||||
|
||||
this.resizerId = (Math.random(99)*100).toFixed(0);
|
||||
|
||||
if (this.settings.active.pan) {
|
||||
console.log("can pan:", this.settings.active.miscFullscreenSettings.mousePan.enabled, "(default:", this.settings.active.miscFullscreenSettings.mousePan.enabled, ")")
|
||||
this.canPan = this.settings.active.miscFullscreenSettings.mousePan.enabled;
|
||||
} else {
|
||||
this.canPan = false;
|
||||
}
|
||||
}
|
||||
|
||||
start(){
|
||||
if(!this.destroyed) {
|
||||
this.startCssWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
stop(){
|
||||
this.stopCssWatcher();
|
||||
}
|
||||
|
||||
destroy(){
|
||||
if(Debug.debug || Debug.init){
|
||||
console.log(`[Resizer::destroy] <rid:${this.resizerId}> received destroy command.`);
|
||||
}
|
||||
this.destroyed = true;
|
||||
this.stopCssWatcher();
|
||||
}
|
||||
|
||||
|
||||
setAr(ar, lastAr){
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
this.startCssWatcher();
|
||||
this.cssWatcherIncreasedFrequencyCounter = 20;
|
||||
|
||||
if(Debug.debug){
|
||||
console.log('[Resizer::setAr] <rid:'+this.resizerId+'> trying to set ar. New ar:', ar)
|
||||
}
|
||||
|
||||
if(lastAr) {
|
||||
this.lastAr = lastAr;
|
||||
} else {
|
||||
if(isNaN(ar)){
|
||||
this.lastAr = {type: 'legacy', ar: ar}
|
||||
} else {
|
||||
this.lastAr = {type: 'static', ar: ar};
|
||||
}
|
||||
}
|
||||
|
||||
if (! this.video) {
|
||||
// console.log("No video detected.")
|
||||
this.videoData.destroy();
|
||||
}
|
||||
|
||||
// // pause AR on basic stretch, unpause when using other mdoes
|
||||
// fir sine reason unpause doesn't unpause. investigate that later
|
||||
// if (this.stretcher.mode === StretchMode.BASIC) {
|
||||
// this.conf.arDetector.pause();
|
||||
// } else {
|
||||
// this.conf.arDetector.unpause();
|
||||
// }
|
||||
|
||||
// do stretch thingy
|
||||
if (this.stretcher.mode === StretchMode.NO_STRETCH || this.stretcher.mode === StretchMode.CONDITIONAL){
|
||||
var stretchFactors = this.scaler.calculateCrop(ar);
|
||||
|
||||
if(! stretchFactors || stretchFactors.error){
|
||||
if(Debug.debug){
|
||||
console.log("[Resizer::setAr] <rid:"+this.resizerId+"> failed to set AR due to problem with calculating crop. Error:", (stretchFactors ? stretchFactors.error : stretchFactors));
|
||||
}
|
||||
if(stretchFactors.error === 'no_video'){
|
||||
this.conf.destroy();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(this.stretcher.mode === StretchMode.CONDITIONAL){
|
||||
this.stretcher.applyConditionalStretch(stretchFactors, ar);
|
||||
}
|
||||
} else if (this.stretcher.mode === StretchMode.HYBRID) {
|
||||
var stretchFactors = this.stretcher.calculateStretch(ar);
|
||||
} else if (this.stretcher.mode === StretchMode.BASIC) {
|
||||
var stretchFactors = this.stretcher.calculateBasicStretch();
|
||||
}
|
||||
|
||||
this.zoom.applyZoom(stretchFactors);
|
||||
|
||||
//TODO: correct these two
|
||||
var translate = this.computeOffsets(stretchFactors);
|
||||
this.applyCss(stretchFactors, translate);
|
||||
|
||||
}
|
||||
|
||||
resetLastAr() {
|
||||
this.lastAr = {type: 'original'};
|
||||
}
|
||||
|
||||
setLastAr(override){
|
||||
this.lastAr = override;
|
||||
}
|
||||
|
||||
getLastAr(){
|
||||
return this.lastAr;
|
||||
}
|
||||
|
||||
setStretchMode(stretchMode){
|
||||
this.stretcher.mode = stretchMode;
|
||||
this.restore();
|
||||
}
|
||||
|
||||
panHandler(event) {
|
||||
// console.log("this.conf.canPan:", this.conf.canPan)
|
||||
if (this.canPan) {
|
||||
// console.log("event?", event)
|
||||
// console.log("this?", this)
|
||||
|
||||
if(!this.conf.player || !this.conf.player.element) {
|
||||
return;
|
||||
}
|
||||
const player = this.conf.player.element;
|
||||
|
||||
const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth;
|
||||
const relativeY = (event.pageY - player.offsetTop) / player.offsetHeight;
|
||||
|
||||
this.setPan(relativeX, relativeY);
|
||||
}
|
||||
}
|
||||
|
||||
setPan(relativeMousePosX, relativeMousePosY){
|
||||
// relativeMousePos[X|Y] - on scale from 0 to 1, how close is the mouse to player edges.
|
||||
// use these values: top, left: 0, bottom, right: 1
|
||||
if(! this.pan){
|
||||
this.pan = {};
|
||||
}
|
||||
|
||||
this.pan.relativeOffsetX = -(relativeMousePosX * 1.1) + 0.55;
|
||||
this.pan.relativeOffsetY = -(relativeMousePosY * 1.1) + 0.55;
|
||||
|
||||
// if(Debug.debug){
|
||||
// console.log("[Resizer::setPan] relative cursor pos:", relativeMousePosX, ",",relativeMousePosY, " | new pan obj:", this.pan)
|
||||
// }
|
||||
this.restore();
|
||||
}
|
||||
|
||||
setVideoFloat(videoFloat) {
|
||||
this.videoFloat = videoFloat;
|
||||
this.restore();
|
||||
}
|
||||
|
||||
startCssWatcher(){
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer.js::startCssWatcher] starting css watcher. Is resizer destroyed?", this.destroyed);
|
||||
}
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this.haltCssWatcher = false;
|
||||
if(!this.cssWatcherTimer){
|
||||
this.scheduleCssWatcher(1);
|
||||
} else {
|
||||
clearTimeout(this.cssWatcherTimer);
|
||||
this.scheduleCssWatcher(1);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleCssWatcher(timeout, force_reset) {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(timeout === undefined) {
|
||||
console.log("?")
|
||||
this.cssCheck(); // no timeout = one-off
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.cssWatcherTimeout) {
|
||||
clearTimeout(this.cssWatcherTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
this.cssWatcherTimer = setTimeout(function () {
|
||||
ths.cssWatcherTimer = null;
|
||||
try {
|
||||
ths.cssCheck();
|
||||
} catch (e) {
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer.js::scheduleCssWatcher] Css check failed. Error:", e);
|
||||
}
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
stopCssWatcher() {
|
||||
if(Debug.debug) console.log("[Resizer.js] STOPPING CSS WATCHER!")
|
||||
|
||||
clearInterval(this.cssWatcherTimeout);
|
||||
}
|
||||
|
||||
restore() {
|
||||
if(Debug.debug){
|
||||
console.log("[Resizer::restore] <rid:"+this.resizerId+"> attempting to restore aspect ratio. this & settings:", {'this': this, "settings": this.settings} );
|
||||
}
|
||||
|
||||
// this is true until we verify that css has actually been applied
|
||||
this.restore_wd = true;
|
||||
|
||||
if(this.lastAr.type === 'original'){
|
||||
this.setAr('reset');
|
||||
}
|
||||
else {
|
||||
this.setAr(this.lastAr.ar, this.lastAr)
|
||||
}
|
||||
}
|
||||
|
||||
reset(){
|
||||
this.setStretchMode(StretchMode.NO_STRETCH);
|
||||
this.zoom.setZoom(1);
|
||||
this.resetPan();
|
||||
this.setAr('reset');
|
||||
}
|
||||
|
||||
setPanMode(mode) {
|
||||
if (mode === 'enable') {
|
||||
this.canPan = true;
|
||||
} else if (mode === 'disable') {
|
||||
this.canPan = false;
|
||||
} else if (mode === 'toggle') {
|
||||
this.canPan = !this.canPan;
|
||||
}
|
||||
}
|
||||
|
||||
resetPan(){
|
||||
this.pan = undefined;
|
||||
}
|
||||
|
||||
setZoom(zoomLevel, no_announce) {
|
||||
this.zoom.setZoom(zoomLevel, no_announce);
|
||||
}
|
||||
|
||||
zoomStep(step){
|
||||
this.zoom.zoomStep(step);
|
||||
}
|
||||
|
||||
resetZoom(){
|
||||
this.zoom.setZoom(1);
|
||||
this.restore();
|
||||
}
|
||||
|
||||
resetCrop(){
|
||||
this.setAr('reset');
|
||||
}
|
||||
|
||||
resetStretch(){
|
||||
this.stretcher.mode = StretchMode.NO_STRETCH;
|
||||
this.restore();
|
||||
}
|
||||
|
||||
|
||||
// mostly internal stuff
|
||||
|
||||
computeOffsets(stretchFactors){
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> video will be aligned to ", this.settings.active.miscFullscreenSettings.videoFloat);
|
||||
}
|
||||
|
||||
var actualWidth = this.conf.video.offsetWidth * stretchFactors.xFactor;
|
||||
var actualHeight = this.conf.video.offsetHeight * stretchFactors.yFactor;
|
||||
|
||||
var wdiff = actualWidth - this.conf.player.dimensions.width;
|
||||
var hdiff = actualHeight - this.conf.player.dimensions.height;
|
||||
|
||||
var translate = {x: 0, y: 0};
|
||||
|
||||
if (this.pan) {
|
||||
// don't offset when video is smaller than player
|
||||
if(wdiff < 0 && hdiff < 0) {
|
||||
return translate;
|
||||
}
|
||||
translate.x = wdiff * this.pan.relativeOffsetX / this.zoom.scale;
|
||||
translate.y = hdiff * this.pan.relativeOffsetY / this.zoom.scale;
|
||||
} else {
|
||||
if (this.videoFloat == "left") {
|
||||
translate.x = wdiff * 0.5;
|
||||
}
|
||||
else if (this.videoFloat == "right") {
|
||||
translate.x = wdiff * -0.5;
|
||||
}
|
||||
}
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:", translate);
|
||||
}
|
||||
|
||||
return translate;
|
||||
}
|
||||
|
||||
applyCss(stretchFactors, translate){
|
||||
|
||||
if (! this.video) {
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer::_res_applyCss] <rid:"+this.resizerId+"> Video went missing, doing nothing.");
|
||||
}
|
||||
|
||||
this.conf.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// save stuff for quick tests (before we turn numbers into css values):
|
||||
this.currentVideoSettings = {
|
||||
validFor: this.conf.player.dimensions,
|
||||
// videoWidth: dimensions.width,
|
||||
// videoHeight: dimensions.height
|
||||
}
|
||||
|
||||
var styleArrayStr = this.video.getAttribute('style');
|
||||
|
||||
if (styleArrayStr) {
|
||||
|
||||
var styleArray = styleArrayStr.split(";");
|
||||
for(var i in styleArray){
|
||||
|
||||
styleArray[i] = styleArray[i].trim();
|
||||
|
||||
if (styleArray[i].startsWith("transform:")){
|
||||
delete styleArray[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
var styleArray = [];
|
||||
}
|
||||
|
||||
// add remaining elements
|
||||
|
||||
if(stretchFactors){
|
||||
styleArray.push(`transform: scale(${stretchFactors.xFactor}, ${stretchFactors.yFactor}) translate(${translate.x}px, ${translate.y}px)`);
|
||||
}
|
||||
|
||||
// build style string back
|
||||
var styleString = "";
|
||||
for(var i in styleArray)
|
||||
if(styleArray[i])
|
||||
styleString += styleArray[i] + "; ";
|
||||
|
||||
this.setStyleString(styleString);
|
||||
}
|
||||
|
||||
setStyleString (styleString, count = 0) {
|
||||
this.video.setAttribute("style", styleString);
|
||||
|
||||
this.currentStyleString = this.video.getAttribute('style');
|
||||
this.currentCssValidFor = this.conf.player.dimensions;
|
||||
|
||||
if(this.restore_wd){
|
||||
|
||||
if(! this.video){
|
||||
if(Debug.debug)
|
||||
console.log("[Resizer::_res_setStyleString] <rid:"+this.resizerId+"> Video element went missing, nothing to do here.")
|
||||
return;
|
||||
}
|
||||
|
||||
// if(
|
||||
// styleString.indexOf("width: " + this.video.style.width) == -1 ||
|
||||
// styleString.indexOf("height: " + this.video.style.height) == -1) {
|
||||
// // css ni nastavljen?
|
||||
// // css not set?
|
||||
// if(Debug.debug)
|
||||
// console.log("[Resizer::_res_setStyleString] Style string not set ???");
|
||||
|
||||
// if(count < settings.active.resizer.setStyleString.maxRetries){
|
||||
// setTimeout( this.setStyleString, settings.active.resizer.setStyleString.retryTimeout, count + 1);
|
||||
// }
|
||||
// else if(Debug.debug){
|
||||
// console.log("[Resizer::_res_setStyleString] we give up. css string won't be set");
|
||||
// }
|
||||
// }
|
||||
// else{
|
||||
this.restore_wd = false;
|
||||
// }
|
||||
}
|
||||
else{
|
||||
if(Debug.debug)
|
||||
console.log("[Resizer::_res_setStyleString] <rid:"+this.resizerId+"> css applied. Style string:", styleString);
|
||||
}
|
||||
}
|
||||
|
||||
cssCheck(){
|
||||
// this means we haven't set our CSS yet, or that we changed video.
|
||||
// if(! this.currentCss.tranform) {
|
||||
// this.scheduleCssWatcher(200);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this means video went missing. videoData will be re-initialized when the next video is found
|
||||
if(! this.video){
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer::cssCheck] <rid:"+this.resizerId+"> no video detecting, issuing destroy command");
|
||||
}
|
||||
this.conf.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.destroyed) {
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer::cssCheck] <rid:"+this.resizerId+"> destroyed flag is set, we shouldnt be running");
|
||||
}
|
||||
this.stopCssWatcher();
|
||||
return;
|
||||
}
|
||||
|
||||
var styleString = this.video.getAttribute('style');
|
||||
|
||||
// first, a quick test:
|
||||
// if (this.currentVideoSettings.validFor == this.conf.player.dimensions ){
|
||||
if (this.currentStyleString !== styleString){
|
||||
this.restore();
|
||||
this.scheduleCssWatcher(10);
|
||||
return;
|
||||
}
|
||||
if (this.cssWatcherIncreasedFrequencyCounter > 0) {
|
||||
--this.cssWatcherIncreasedFrequencyCounter;
|
||||
this.scheduleCssWatcher(20);
|
||||
} else {
|
||||
this.scheduleCssWatcher(1000);
|
||||
}
|
||||
}
|
||||
}
|
84
js/uw-bg.js
84
js/uw-bg.js
@ -1,84 +0,0 @@
|
||||
var BgVars = {
|
||||
arIsActive: true,
|
||||
hasVideos: false,
|
||||
currentSite: ""
|
||||
}
|
||||
|
||||
class UWServer {
|
||||
|
||||
constructor() {
|
||||
this.ports = [];
|
||||
this.arIsActive = true;
|
||||
this.hasVideos = false;
|
||||
this.currentSite = "";
|
||||
this.setup();
|
||||
}
|
||||
|
||||
async setup() {
|
||||
this.settings = new Settings();
|
||||
|
||||
await this.settings.init();
|
||||
this.comms = new CommsServer(this);
|
||||
|
||||
|
||||
var ths = this;
|
||||
if(BrowserDetect.firefox) {
|
||||
browser.tabs.onActivated.addListener(function(m) {ths.onTabSwitched(m)});
|
||||
} else if (BrowserDetect.chrome) {
|
||||
chrome.tabs.onActivated.addListener(function(m) {ths.onTabSwitched(m)});
|
||||
}
|
||||
}
|
||||
|
||||
async _promisifyTabsGet(browserObj, tabId){
|
||||
return new Promise( (resolve, reject) => {
|
||||
browserObj.tabs.get(tabId, (tab) => resolve(tab));
|
||||
});
|
||||
}
|
||||
|
||||
extractHostname(url){
|
||||
var hostname;
|
||||
|
||||
// extract hostname
|
||||
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
|
||||
hostname = url.split('/')[2];
|
||||
}
|
||||
else {
|
||||
hostname = url.split('/')[0];
|
||||
}
|
||||
|
||||
hostname = hostname.split(':')[0]; //find & remove port number
|
||||
hostname = hostname.split('?')[0]; //find & remove "?"
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
async onTabSwitched(activeInfo){
|
||||
this.hasVideos = false;
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("[uw-bg::onTabSwitched] TAB CHANGED, GETTING INFO FROM MAIN TAB");
|
||||
|
||||
try {
|
||||
var tabId = activeInfo.tabId; // just for readability
|
||||
|
||||
var tab;
|
||||
if (BrowserDetect.firefox) {
|
||||
var tab = await browser.tabs.get(tabId);
|
||||
} else if (BrowserDetect.chrome) {
|
||||
var tab = await this._promisifyTabsGet(chrome, tabId);
|
||||
}
|
||||
|
||||
this.currentSite = this.extractHostname(tab.url);
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("TAB SWITCHED!", this.currentSite)
|
||||
}
|
||||
//TODO: change extension icon based on whether there's any videos on current page
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var server = new UWServer();
|
@ -1,95 +0,0 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Ultrawidify",
|
||||
"version": "3.2.2",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "{cf02b1a7-a01a-4e37-a609-516a283f1ed3}"
|
||||
}
|
||||
},
|
||||
|
||||
"icons": {
|
||||
"32":"res/icons/uw-32.png",
|
||||
"64":"res/icons/uw-64.png"
|
||||
},
|
||||
|
||||
"description": "Aspect ratio fixer for youtube that works around some people's disability to properly encode 21:9 (and sometimes, 16:9) videos.",
|
||||
|
||||
"content_scripts": [{
|
||||
"matches": ["*://*/*"],
|
||||
"js": [
|
||||
"js/conf/Debug.js",
|
||||
|
||||
"js/lib/BrowserDetect.js",
|
||||
"js/conf/ExtensionConf.js",
|
||||
|
||||
"js/lib/ObjectCopy.js",
|
||||
"js/lib/Settings.js",
|
||||
|
||||
"js/lib/Comms.js",
|
||||
|
||||
"js/lib/EdgeDetect.js",
|
||||
"js/lib/GuardLine.js",
|
||||
|
||||
|
||||
|
||||
"js/modules/PageInfo.js",
|
||||
"js/modules/DebugCanvas.js",
|
||||
"js/modules/ArDetect.js",
|
||||
"js/modules/Zoom.js",
|
||||
"js/modules/Scaler.js",
|
||||
"js/modules/Stretcher.js",
|
||||
|
||||
"js/modules/Resizer.js",
|
||||
"js/lib/PlayerData.js",
|
||||
"js/lib/VideoData.js",
|
||||
|
||||
"js/conf/Keybinds.js",
|
||||
|
||||
"js/uw.js" ],
|
||||
"all_frames": true
|
||||
}],
|
||||
|
||||
"background": {
|
||||
"scripts": [
|
||||
"js/conf/Debug.js",
|
||||
"js/lib/BrowserDetect.js",
|
||||
|
||||
"js/conf/ExtensionConf.js",
|
||||
|
||||
"js/lib/Comms.js",
|
||||
|
||||
"js/lib/ObjectCopy.js",
|
||||
"js/lib/Settings.js",
|
||||
|
||||
"js/conf/Keybinds.js",
|
||||
|
||||
"js/uw-bg.js"
|
||||
]
|
||||
},
|
||||
|
||||
"permissions": [
|
||||
"tabs", "storage", "activeTab", "<all_urls>", "webNavigation"
|
||||
],
|
||||
|
||||
"browser_action": {
|
||||
"default_icon": "res/icons/uw-32.png",
|
||||
"default_popup": "res/popup/popup.html",
|
||||
"default_title": "Ultrawidify"
|
||||
},
|
||||
|
||||
"web_accessible_resources": [
|
||||
"js/*",
|
||||
"res/fonts/*",
|
||||
"res/css/*",
|
||||
|
||||
"res/img/ytplayer-icons/zoom.png",
|
||||
"res/img/ytplayer-icons/unzoom.png",
|
||||
"res/img/ytplayer-icons/fitw.png",
|
||||
"res/img/ytplayer-icons/fith.png",
|
||||
"res/img/ytplayer-icons/reset.png",
|
||||
"res/img/ytplayer-icons/settings.png",
|
||||
|
||||
"res/img/settings/about-bg.png"
|
||||
]
|
||||
}
|
51
package.json
Normal file
51
package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "ultravidify-git",
|
||||
"version": "4.0.0-a1",
|
||||
"description": "Aspect ratio fixer for youtube that works around some people's disability to properly encode 21:9 (and sometimes, 16:9) videos.",
|
||||
"author": "Tamius Han <tamius.han@gmail.com>",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production BROWSER=firefox webpack --hide-modules",
|
||||
"build-chrome": "cross-env NODE_ENV=production BROWSER=chrome webpack --hide-modules",
|
||||
"build-edge": "cross-env NODE_ENV=production BROWSER=edge webpack --hide-modules",
|
||||
"build:dev": "cross-env NODE_ENV=development BROWSER=firefox webpack --hide-modules",
|
||||
"build-chrome:dev": "cross-env NODE_ENV=development BROWSER=chrome webpack --hide-modules",
|
||||
"build-edge:dev": "cross-env NODE_ENV=development BROWSER=edge webpack --hide-modules",
|
||||
"build-zip": "node scripts/build-zip.js",
|
||||
"watch": "npm run build -- --watch",
|
||||
"watch-chrome": "npm run build-chrome-- --watch",
|
||||
"watch-edge": "npm run build-edge -- --watch",
|
||||
"watch:dev": "cross-env HMR=true npm run build:dev -- --watch",
|
||||
"watch-chrome:dev": "cross-env HMR=true npm run build-chrome:dev -- --watch",
|
||||
"watch-edge:dev": "cross-env HMR=true npm run build-edge:dev -- --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/core-js": "^2.5.0",
|
||||
"@types/es6-promise": "^3.3.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"vue": "^2.5.17",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-webextensions": "^1.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"archiver": "^3.0.0",
|
||||
"babel-loader": "^8.0.2",
|
||||
"copy-webpack-plugin": "^4.5.3",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"ejs": "^2.6.1",
|
||||
"file-loader": "^1.1.11",
|
||||
"mini-css-extract-plugin": "^0.4.4",
|
||||
"node-sass": "^4.9.3",
|
||||
"sass-loader": "^7.1.0",
|
||||
"vue-loader": "^15.4.2",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"web-ext-types": "^2.1.0",
|
||||
"webpack": "^4.20.2",
|
||||
"webpack-chrome-extension-reloader": "^0.8.3",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-shell-plugin": "^0.5.0"
|
||||
}
|
||||
}
|
BIN
releases/edge/ultrawidify_edge_v3.2.2.zip
Normal file
BIN
releases/edge/ultrawidify_edge_v3.2.2.zip
Normal file
Binary file not shown.
@ -1,214 +0,0 @@
|
||||
html, body {
|
||||
color: #f8f8f8;
|
||||
background-color: #1f1f1f;
|
||||
font-family: "overpass";
|
||||
font-size: 1em;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* STANDARD WIDTHS AND HEIGHTS */
|
||||
|
||||
.w100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w24 {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.m-t-0-33em {
|
||||
margin-top: 0.33em;
|
||||
}
|
||||
|
||||
.x-pad-1em {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
/* ELEMENT POSITIONING */
|
||||
|
||||
.row {
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.float-left {
|
||||
float: left;
|
||||
}
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* TEXT FORMATTING (no colors) */
|
||||
|
||||
small {
|
||||
font-size: 0.75em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 0.75em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.medium-small {
|
||||
font-size: 0.85em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.smallcaps{
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
.center{
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* COLORS */
|
||||
|
||||
a {
|
||||
color: #af7f37;
|
||||
}
|
||||
a:hover {
|
||||
color: #c0924e;
|
||||
}
|
||||
|
||||
.color_warn {
|
||||
color: #d6ba4a;
|
||||
}
|
||||
|
||||
strike {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.darker {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.label{
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
color: #ffe;
|
||||
}
|
||||
|
||||
.selected{
|
||||
color: #ffddaa;
|
||||
background-color: #433221;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
/* color: #666; */
|
||||
filter: contrast(50%) brightness(40%) grayscale(100%);
|
||||
}
|
||||
|
||||
.disabled-button {
|
||||
color: #666 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
.disabled-button:hover {
|
||||
color: #777 !important;
|
||||
background-color: #222 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* BUTTONS AND INPUTS */
|
||||
|
||||
input {
|
||||
border: 1px solid #322;
|
||||
padding: 0.5em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.invalid-input {
|
||||
border: 1px solid #720 !important;
|
||||
background-color: #410 !important;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 3px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
border: 1px solid #444;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
color: #dbb;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
color: #fff;
|
||||
background-color: #433221;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** misc **/
|
||||
|
||||
.warning {
|
||||
color: #d6ba4a;
|
||||
padding-left: 35px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.warning::before {
|
||||
content: "⚠ ";
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
font-size: 2.5em;
|
||||
margin-left: -35px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.experimental::after {
|
||||
content: "Experimental";
|
||||
background-color: #ffde12;
|
||||
color: #1f1f1f;
|
||||
display: inline-block;
|
||||
font-size: .75em;
|
||||
font-variant: small-caps;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
html, body {
|
||||
width: 780px !important;
|
||||
max-width: 800px !important;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #7f1416;
|
||||
color: #fff;
|
||||
margin: 0px;
|
||||
margin-top: 0px;
|
||||
padding-top: 8px;
|
||||
padding-left: 15px;
|
||||
padding-bottom: 1px;
|
||||
font-size: 2.7em;
|
||||
}
|
||||
|
||||
.menu-item-inline-desc{
|
||||
font-size: 0.60em;
|
||||
font-weight: 300;
|
||||
font-variant: normal;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
display: inline-block;
|
||||
width: 39%;
|
||||
float: left;
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
.right-side {
|
||||
display: inline-block;
|
||||
width: 60%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding-left: 15px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
.suboption {
|
||||
display: block;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 20px;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
|
||||
#no-videos-display {
|
||||
height: 100%;
|
||||
padding-top: 50px;
|
||||
/* text-align: center; */
|
||||
}
|
@ -1,844 +0,0 @@
|
||||
if(Debug.debug)
|
||||
console.log("[popup.js] loading popup script!");
|
||||
|
||||
document.getElementById("uw-version").textContent = browser.runtime.getManifest().version;
|
||||
|
||||
var Menu = {};
|
||||
// Menu.noVideo = document.getElementById("no-videos-display");
|
||||
Menu.extensionSettings = document.getElementById('_menu_settings_ext');
|
||||
Menu.siteSettings = document.getElementById('_menu_settings_site');
|
||||
Menu.videoSettings = document.getElementById('_menu_settings_video');
|
||||
Menu.about = document.getElementById('_menu_about')
|
||||
|
||||
var MenuTab = {};
|
||||
MenuTab.extensionSettings = document.getElementById('_menu_tab_settings_ext');
|
||||
MenuTab.siteSettings = document.getElementById('_menu_tab_settings_site');
|
||||
MenuTab.videoSettings = document.getElementById('_menu_tab_settings_video');
|
||||
MenuTab.about = document.getElementById('_menu_tab_about')
|
||||
|
||||
//#region ExtPanel
|
||||
var ExtPanel = {};
|
||||
ExtPanel.extOptions = {};
|
||||
ExtPanel.extOptions.blacklist = document.getElementById("_ext_global_options_blacklist");
|
||||
ExtPanel.extOptions.whitelist = document.getElementById("_ext_global_options_whitelist");
|
||||
ExtPanel.extOptions.disabled = document.getElementById("_ext_global_options_disabled");
|
||||
ExtPanel.arOptions = {};
|
||||
ExtPanel.arOptions.blacklist = document.getElementById("_ar_global_options_blacklist");
|
||||
ExtPanel.arOptions.whitelist = document.getElementById("_ar_global_options_whitelist");
|
||||
ExtPanel.arOptions.disabled = document.getElementById("_ar_global_options_disabled");
|
||||
ExtPanel.alignment = {};
|
||||
ExtPanel.alignment.left = document.getElementById("_align_ext_left");
|
||||
ExtPanel.alignment.center = document.getElementById("_align_ext_center");
|
||||
ExtPanel.alignment.right = document.getElementById("_align_ext_right");
|
||||
ExtPanel.stretch = {};
|
||||
ExtPanel.stretch['0'] = document.getElementById("_stretch_global_none");
|
||||
ExtPanel.stretch['1'] = document.getElementById("_stretch_global_basic");
|
||||
ExtPanel.stretch['2'] = document.getElementById("_stretch_global_hybrid");
|
||||
ExtPanel.stretch['3'] = document.getElementById("_stretch_global_conditional");
|
||||
//#endregion
|
||||
//#region SitePanel
|
||||
var SitePanel = {};
|
||||
SitePanel.extOptions = {};
|
||||
SitePanel.extOptions.enabled = document.getElementById("_ext_site_options_whitelist");
|
||||
SitePanel.extOptions.default = document.getElementById("_ext_site_options_default");
|
||||
SitePanel.extOptions.disabled = document.getElementById("_ext_site_options_blacklist");
|
||||
SitePanel.arOptions = {};
|
||||
SitePanel.arOptions.disabled = document.getElementById("_ar_site_options_disabled");
|
||||
SitePanel.arOptions.enabled = document.getElementById("_ar_site_options_enabled");
|
||||
SitePanel.arOptions.default = document.getElementById("_ar_site_options_default");
|
||||
SitePanel.alignment = {};
|
||||
SitePanel.alignment.left = document.getElementById("_align_site_left");
|
||||
SitePanel.alignment.center = document.getElementById("_align_site_center");
|
||||
SitePanel.alignment.right = document.getElementById("_align_site_right");
|
||||
SitePanel.alignment.default = document.getElementById("_align_site_default");
|
||||
SitePanel.stretch = {};
|
||||
SitePanel.stretch['-1'] = document.getElementById("_stretch_site_default")
|
||||
SitePanel.stretch['0'] = document.getElementById("_stretch_site_none")
|
||||
SitePanel.stretch['1'] = document.getElementById("_stretch_site_basic")
|
||||
SitePanel.stretch['2'] = document.getElementById("_stretch_site_hybrid")
|
||||
SitePanel.stretch['3'] = document.getElementById("_stretch_site_conditional")
|
||||
//#endregion
|
||||
//#region VideoPanel
|
||||
var VideoPanel = {};
|
||||
VideoPanel.alignment = {};
|
||||
VideoPanel.alignment.left = document.getElementById("_align_video_left");
|
||||
VideoPanel.alignment.center = document.getElementById("_align_video_center");
|
||||
VideoPanel.alignment.right = document.getElementById("_align_video_right");
|
||||
|
||||
// labels on buttons
|
||||
VideoPanel.buttonLabels = {};
|
||||
VideoPanel.buttonLabels.crop = {};
|
||||
VideoPanel.buttonLabels.crop['auto-ar'] = document.getElementById("_b_changeAr_auto-ar_key");
|
||||
VideoPanel.buttonLabels.crop.reset = document.getElementById("_b_changeAr_reset_key");
|
||||
VideoPanel.buttonLabels.crop['219'] = document.getElementById("_b_changeAr_219_key");
|
||||
VideoPanel.buttonLabels.crop['189'] = document.getElementById("_b_changeAr_189_key");
|
||||
VideoPanel.buttonLabels.crop['169'] = document.getElementById("_b_changeAr_169_key");
|
||||
VideoPanel.buttonLabels.crop.custom = document.getElementById("_b_changeAr_custom_key");
|
||||
VideoPanel.buttonLabels.zoom = {};
|
||||
|
||||
// buttons: for toggle, select
|
||||
VideoPanel.buttons = {};
|
||||
VideoPanel.buttons.zoom = {};
|
||||
VideoPanel.buttons.zoom.showShortcuts = document.getElementById("_zoom_b_show_shortcuts");
|
||||
VideoPanel.buttons.zoom.hideShortcuts = document.getElementById("_zoom_b_hide_shortcuts");
|
||||
VideoPanel.buttons.changeAr = {};
|
||||
VideoPanel.buttons.changeAr.showCustomAr = document.getElementById("_changeAr_b_show_customAr");
|
||||
VideoPanel.buttons.changeAr.hideCustomAr = document.getElementById("_changeAr_b_hide_customAr");
|
||||
|
||||
// inputs (getting values)
|
||||
VideoPanel.inputs = {};
|
||||
VideoPanel.inputs.customCrop = document.getElementById("_input_custom_ar");
|
||||
VideoPanel.inputs.zoomSlider = document.getElementById("_input_zoom_slider");
|
||||
VideoPanel.inputs.allowPan = document.getElementById("_input_zoom_site_allow_pan");
|
||||
|
||||
// various labels
|
||||
VideoPanel.labels = {};
|
||||
VideoPanel.labels.zoomLevel = document.getElementById("_label_zoom_level");
|
||||
|
||||
// misc stuff
|
||||
VideoPanel.misc = {};
|
||||
VideoPanel.misc.zoomShortcuts = document.getElementById("_zoom_shortcuts");
|
||||
VideoPanel.misc.customArChanger = document.getElementById("_changeAr_customAr");
|
||||
|
||||
//#endregion
|
||||
|
||||
var selectedMenu = "";
|
||||
var hasVideos = false;
|
||||
|
||||
var zoom_videoScale = 1;
|
||||
|
||||
var _config;
|
||||
var _changeAr_button_shortcuts = { "autoar":"none", "reset":"none", "219":"none", "189":"none", "169":"none", "custom":"none" }
|
||||
|
||||
var comms = new Comms();
|
||||
var settings = new Settings(undefined, () => updateConfig());
|
||||
|
||||
var site = undefined;
|
||||
|
||||
var port = browser.runtime.connect({name: 'popup-port'});
|
||||
port.onMessage.addListener( (m,p) => processReceivedMessage(m,p));
|
||||
|
||||
|
||||
async function processReceivedMessage(message, port){
|
||||
if (Debug.debug) {
|
||||
console.log("[popup.js] received message", message)
|
||||
}
|
||||
|
||||
if(message.cmd === 'set-current-site'){
|
||||
if (site !== message.site) {
|
||||
port.postMessage({cmd: 'get-current-zoom'});
|
||||
}
|
||||
site = message.site;
|
||||
loadConfig(message.site);
|
||||
} else if (message.cmd === 'set-current-zoom') {
|
||||
setCurrentZoom(message.zoom);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateConfig() {
|
||||
if (Debug.debug) {
|
||||
console.log("[popup.js] settings changed. updating popup if site exists. Site:", site);
|
||||
}
|
||||
|
||||
if (site) {
|
||||
loadConfig(site);
|
||||
}
|
||||
}
|
||||
|
||||
async function setCurrentZoom(scale) {
|
||||
zoom_videoScale = scale;
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[popup.js::setCurrentZoom] we're setting zoom:", zoom_videoScale);
|
||||
}
|
||||
|
||||
VideoPanel.inputs.zoomSlider.value = Math.log2(zoom_videoScale);
|
||||
VideoPanel.labels.zoomLevel.textContent = (zoom_videoScale * 100).toFixed();
|
||||
}
|
||||
|
||||
function hideWarning(warn){
|
||||
document.getElementById(warn).classList.add("hidden");
|
||||
}
|
||||
|
||||
function stringToKeyCombo(key_in){
|
||||
var keys_in = key_in.split("_");
|
||||
var keys_out = "";
|
||||
|
||||
for(key of keys_in){
|
||||
if(key == "ctrlKey")
|
||||
keys_out += "ctrl + ";
|
||||
else if(key == "shiftKey")
|
||||
keys_out += "shift + ";
|
||||
else if(key == "altKey")
|
||||
keys_out += "alt + ";
|
||||
else
|
||||
keys_out += key;
|
||||
}
|
||||
|
||||
return keys_out;
|
||||
}
|
||||
|
||||
function configurePopupTabs(site) {
|
||||
// Determine which tabs can we touch.
|
||||
// If extension is disabled, we can't touch 'site settings' and 'video settings'
|
||||
// If extension is enabled, but site is disabled, we can't touch 'video settings'
|
||||
var extensionEnabled = settings.extensionEnabled();
|
||||
var extensionEnabledForSite = settings.extensionEnabledForSite(site);
|
||||
|
||||
MenuTab.siteSettings.classList.add('disabled');
|
||||
|
||||
if (extensionEnabledForSite) {
|
||||
MenuTab.videoSettings.classList.remove('disabled');
|
||||
}
|
||||
if (extensionEnabled) {
|
||||
MenuTab.videoSettings.classList.remove('disabled');
|
||||
}
|
||||
|
||||
// we assume that these two can be shown. If extension or site are disabled, we'll
|
||||
// add 'disabled' class later down the line:
|
||||
document.getElementById("_site_only_when_site_enabled").classList.remove("disabled");
|
||||
document.getElementById("_ext_only_when_ext_enabled").classList.remove("disabled");
|
||||
|
||||
if (! extensionEnabledForSite) {
|
||||
MenuTab.videoSettings.classList.add('disabled');
|
||||
|
||||
// also disable extra settings for site
|
||||
document.getElementById("_site_only_when_site_enabled").classList.add("disabled");
|
||||
|
||||
if (! extensionEnabled) {
|
||||
MenuTab.siteSettings.classList.add('disabled');
|
||||
|
||||
// also disable extra settings for extension
|
||||
document.getElementById("_ext_only_when_ext_enabled").classList.add("disabled");
|
||||
if (!selectedMenu) {
|
||||
openMenu('extensionSettings');
|
||||
}
|
||||
} else {
|
||||
MenuTab.siteSettings.classList.remove('disabled');
|
||||
if (!selectedMenu) {
|
||||
openMenu('siteSettings');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MenuTab.videoSettings.classList.remove('disabled');
|
||||
MenuTab.siteSettings.classList.remove('disabled');
|
||||
|
||||
// if popup isn't being opened for the first time, there's no reason to switch
|
||||
// we're already in this tab
|
||||
if (!selectedMenu) {
|
||||
openMenu('videoSettings');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function configureGlobalTab() {
|
||||
if (Debug.debug) {
|
||||
console.log("[popup.js] Configuring global tab (ExtPanel).",
|
||||
"\nextension mode: ", settings.active.extensionMode,
|
||||
"\narDetect mode: ", settings.active.arDetect.mode,
|
||||
"\nvideo float mode:", settings.active.miscFullscreenSettings.videoFloat,
|
||||
"\nstretch mode: ", settings.active.stretch.initialMode,
|
||||
"\n..")
|
||||
}
|
||||
|
||||
|
||||
for(var button in ExtPanel.extOptions) {
|
||||
ExtPanel.extOptions[button].classList.remove("selected");
|
||||
}
|
||||
for(var button in ExtPanel.arOptions) {
|
||||
ExtPanel.arOptions[button].classList.remove("selected");
|
||||
}
|
||||
for(var button in ExtPanel.alignment) {
|
||||
ExtPanel.alignment[button].classList.remove("selected");
|
||||
}
|
||||
for(var button in ExtPanel.stretch) {
|
||||
ExtPanel.stretch[button].classList.remove("selected");
|
||||
}
|
||||
|
||||
ExtPanel.extOptions[settings.active.extensionMode].classList.add("selected");
|
||||
ExtPanel.arOptions[settings.active.arDetect.mode].classList.add("selected");
|
||||
ExtPanel.alignment[settings.active.miscFullscreenSettings.videoFloat].classList.add("selected");
|
||||
ExtPanel.stretch[settings.active.stretch.initialMode].classList.add("selected");
|
||||
}
|
||||
|
||||
function configureSitesTab(site) {
|
||||
if (Debug.debug) {
|
||||
console.log("[popup.js] Configuring sites tab (SitePanel).",
|
||||
"\nsite: ", site,
|
||||
"\nextension mode: ", settings.active.sites[site] ? settings.active.sites[site].status : 'no site-special settings for this site',
|
||||
"\narDetect mode: ", settings.active.sites[site] ? settings.active.sites[site].arStatus : 'no site-special settings for this site',
|
||||
"\nvideo float mode:", settings.active.sites[site] ? settings.active.sites[site].videoFloat : 'no site-special settings for this site',
|
||||
"\ndefault ar: ", settings.active.sites[site] ? settings.active.sites[site].ar : 'no site-special settings for this site',
|
||||
"\nstretch mode: ", settings.active.sites[site] ? settings.active.sites[site].stretch : 'no site-special settings for this site',
|
||||
"\n...")
|
||||
}
|
||||
|
||||
for(const button in SitePanel.extOptions) {
|
||||
SitePanel.extOptions[button].classList.remove("selected");
|
||||
}
|
||||
for(const button in SitePanel.arOptions) {
|
||||
SitePanel.arOptions[button].classList.remove("selected");
|
||||
}
|
||||
for(const button in SitePanel.alignment) {
|
||||
SitePanel.alignment[button].classList.remove("selected");
|
||||
}
|
||||
for(const button in SitePanel.stretch) {
|
||||
SitePanel.stretch[button].classList.remove("selected");
|
||||
}
|
||||
|
||||
if (settings.active.sites[site] && settings.active.sites[site]) {
|
||||
console.log("settings for", site, "exist!")
|
||||
SitePanel.extOptions[settings.active.sites[site].status].classList.add("selected");
|
||||
SitePanel.arOptions[settings.active.sites[site].arStatus].classList.add("selected");
|
||||
} else {
|
||||
SitePanel.extOptions.default.classList.add("selected");
|
||||
SitePanel.arOptions.default.classList.add("selected");
|
||||
}
|
||||
|
||||
// optional settings:
|
||||
if (settings.active.sites[site] && settings.active.sites[site].videoAlignment) {
|
||||
SitePanel.alignment[settings.active.sites[site].videoAlignment].classList.add("selected");
|
||||
} else {
|
||||
SitePanel.alignment.default.classList.add('selected');
|
||||
}
|
||||
|
||||
if(settings.active.sites[site] && settings.active.sites[site].stretch !== undefined) { // can be 0
|
||||
SitePanel.stretch[settings.active.sites[site].stretch].classList.add("selected");
|
||||
} else {
|
||||
SitePanel.stretch['-1'].classList.add("selected");
|
||||
}
|
||||
}
|
||||
|
||||
function configureVideoTab() {
|
||||
// process keyboard shortcuts for crop settings
|
||||
if(settings.active.keyboard.shortcuts){
|
||||
for(var key in settings.active.keyboard.shortcuts){
|
||||
var shortcut = settings.active.keyboard.shortcuts[key];
|
||||
var keypress = stringToKeyCombo(key);
|
||||
|
||||
try{
|
||||
if(shortcut.action == "crop"){
|
||||
if (key == 'q') {
|
||||
_changeAr_button_shortcuts["custom"] = keypress;
|
||||
}
|
||||
else if(shortcut.arg == 2.0){
|
||||
_changeAr_button_shortcuts["189"] = keypress;
|
||||
}
|
||||
else if(shortcut.arg == 2.39){
|
||||
_changeAr_button_shortcuts["219"] = keypress;
|
||||
}
|
||||
else if(shortcut.arg == 1.78){
|
||||
_changeAr_button_shortcuts["169"] = keypress;
|
||||
}
|
||||
else if(shortcut.arg == "fitw") {
|
||||
_changeAr_button_shortcuts["fitw"] = keypress;
|
||||
}
|
||||
else if(shortcut.arg == "fith") {
|
||||
_changeAr_button_shortcuts["fith"] = keypress;
|
||||
}
|
||||
else if(shortcut.arg == "reset") {
|
||||
_changeAr_button_shortcuts["reset"] = keypress;
|
||||
}
|
||||
}
|
||||
else if(shortcut.action == "auto-ar") {
|
||||
_changeAr_button_shortcuts["auto-ar"] = keypress;
|
||||
}
|
||||
}
|
||||
catch(Ex){
|
||||
//do nothing if key doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
// fill in custom aspect ratio
|
||||
if (settings.active.keyboard.shortcuts.q) {
|
||||
document.getElementById("_input_custom_ar").value = settings.active.keyboard.shortcuts.q.arg;
|
||||
}
|
||||
|
||||
for(var key in _changeAr_button_shortcuts){
|
||||
try{
|
||||
document.getElementById(`_b_changeAr_${key}_key`).textContent = `(${_changeAr_button_shortcuts[key]})`;
|
||||
}
|
||||
catch(ex){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: get min, max from settings
|
||||
VideoPanel.inputs.zoomSlider.min = Math.log2(0.5);
|
||||
VideoPanel.inputs.zoomSlider.max = Math.log2(8);
|
||||
VideoPanel.inputs.zoomSlider.value = Math.log2(zoom_videoScale);
|
||||
|
||||
VideoPanel.inputs.zoomSlider.addEventListener('input', (event) => {
|
||||
var newZoom = Math.pow(2, VideoPanel.inputs.zoomSlider.value);
|
||||
|
||||
// save value so it doesn't get reset next time the popup updates
|
||||
zoom_videoScale = newZoom;
|
||||
|
||||
// update zoom% label
|
||||
VideoPanel.labels.zoomLevel.textContent = (newZoom * 100).toFixed();
|
||||
|
||||
// send the command to bg script
|
||||
var command = {
|
||||
cmd: 'set-zoom',
|
||||
zoom: newZoom
|
||||
};
|
||||
|
||||
port.postMessage(command);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadConfig(site){
|
||||
|
||||
console.log("NEW CONFIG!")
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("\n\n-------------------------------------\n[popup.js::loadConfig] loading config. conf object:", settings.active);
|
||||
}
|
||||
|
||||
document.getElementById("_menu_tab_settings_site_site_label").textContent = site;
|
||||
|
||||
|
||||
configurePopupTabs(site);
|
||||
configureGlobalTab();
|
||||
configureSitesTab(site);
|
||||
configureVideoTab();
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("[popup.js::loadConfig] config loaded\n-----------------------\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
async function getSite(){
|
||||
if (Debug.debug) {
|
||||
console.log("[popup.js] requesting current site");
|
||||
}
|
||||
|
||||
port.postMessage({cmd: 'get-current-site'});
|
||||
}
|
||||
|
||||
function openMenu(menu){
|
||||
if(Debug.debug){
|
||||
console.log("[popup.js::openMenu] trying to open menu", menu, "\n element: ", Menu[menu]);
|
||||
}
|
||||
|
||||
for(var m in Menu){
|
||||
if(Menu[m])
|
||||
Menu[m].classList.add("hidden");
|
||||
}
|
||||
for(var m in MenuTab){
|
||||
if(MenuTab[m])
|
||||
MenuTab[m].classList.remove("selected");
|
||||
}
|
||||
|
||||
Menu[menu].classList.remove("hidden");
|
||||
MenuTab[menu].classList.add("selected");
|
||||
|
||||
selectedMenu = menu;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getCustomAspectRatio() {
|
||||
var textBox_value = document.getElementById("_input_custom_ar").value.trim();
|
||||
// validate value - this spaghett will match the following stuff
|
||||
// [int]/[int]
|
||||
// 1:[float]
|
||||
// [float]
|
||||
if (! /(^[0-9]+\/[0-9]+$|^(1:)?[0-9]+\.?[0-9]*$)/.test(textBox_value)) {
|
||||
return false; // validation failed!
|
||||
}
|
||||
|
||||
if (! isNaN(parseFloat(textBox_value))) {
|
||||
return parseFloat(textBox_value);
|
||||
}
|
||||
if (/\//.test(textBox_value)) {
|
||||
const vars = textBox_value.split('/');
|
||||
return parseInt(vars[0])/parseInt(vars[1]); // non-ints shouldn't make it past regex
|
||||
}
|
||||
if (/:/.test(textBox_value)) {
|
||||
const vars = textBox_value.split(':');
|
||||
return parseFloat(vars[1]);
|
||||
}
|
||||
|
||||
// we should never come this far.
|
||||
// If we do, then there's something wrong with the input and our regex
|
||||
return false;
|
||||
}
|
||||
|
||||
function validateCustomAr(){
|
||||
const valid = getCustomAspectRatio() !== false;
|
||||
const inputField = document.getElementById("_input_custom_ar");
|
||||
const valueSaveButton = document.getElementById("_b_changeAr_save_custom_ar");
|
||||
|
||||
if (valid) {
|
||||
inputField.classList.remove("invalid-input");
|
||||
valueSaveButton.classList.remove("disabled-button");
|
||||
} else {
|
||||
inputField.classList.add("invalid-input");
|
||||
valueSaveButton.classList.add("disabled-button");
|
||||
}
|
||||
}
|
||||
|
||||
function validateAutoArTimeout(){
|
||||
const inputField = document.getElementById("_input_autoAr_timer");
|
||||
const valueSaveButton = document.getElementById("_b_autoar_save_autoar_timer");
|
||||
|
||||
if (! isNaN(parseInt(inputField.value.trim().value()))) {
|
||||
inputField.classList.remove("invalid-input");
|
||||
valueSaveButton.classList.remove("disabled-button");
|
||||
} else {
|
||||
inputField.classList.add("invalid-input");
|
||||
valueSaveButton.classList.add("disabled-button");
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
|
||||
|
||||
function getcmd(e){
|
||||
var command = {};
|
||||
command.sender = "popup";
|
||||
command.receiver = "uwbg";
|
||||
|
||||
if(e.target.classList.contains("disabled"))
|
||||
return;
|
||||
|
||||
if(e.target.classList.contains("menu-item")){
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[popup.js::eventListener] clicked on a tab. Class list:", e.target.classList);
|
||||
}
|
||||
|
||||
if(e.target.classList.contains("_menu_tab_settings_ext")){
|
||||
openMenu("extensionSettings");
|
||||
} else if(e.target.classList.contains("_menu_tab_settings_site")){
|
||||
openMenu("siteSettings");
|
||||
} else if(e.target.classList.contains("_menu_tab_settings_video")){
|
||||
openMenu("videoSettings");
|
||||
} else if(e.target.classList.contains("_menu_tab_about")){
|
||||
openMenu("about");
|
||||
}
|
||||
|
||||
// don't send commands
|
||||
return;
|
||||
}
|
||||
if(e.target.classList.contains("_ext")) {
|
||||
var command = {};
|
||||
if(e.target.classList.contains("_ext_global_options")){
|
||||
if (e.target.classList.contains("_blacklist")) {
|
||||
settings.active.extensionMode = "blacklist";
|
||||
} else if (e.target.classList.contains("_whitelist")) {
|
||||
settings.active.extensionMode = "whitelist";
|
||||
} else {
|
||||
settings.active.extensionMode = "disabled";
|
||||
}
|
||||
settings.save();
|
||||
return;
|
||||
} else if (e.target.classList.contains("_ext_site_options")) {
|
||||
var mode;
|
||||
if(e.target.classList.contains("_blacklist")){
|
||||
mode = "disabled";
|
||||
} else if(e.target.classList.contains("_whitelist")) {
|
||||
mode = "enabled";
|
||||
} else {
|
||||
mode = "default";
|
||||
}
|
||||
|
||||
if(settings.active.sites[site]) {
|
||||
settings.active.sites[site].status = mode;
|
||||
settings.active.sites[site].statusEmbedded = mode;
|
||||
} else {
|
||||
settings.active.sites[site] = {
|
||||
status: mode,
|
||||
statusEmbedded: mode,
|
||||
arStatus: 'default',
|
||||
type: 'user-defined'
|
||||
}
|
||||
}
|
||||
settings.save();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(e.target.classList.contains("_changeAr")){
|
||||
if(e.target.classList.contains("_ar_auto")){
|
||||
command.cmd = "autoar-start";
|
||||
command.enabled = true;
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_reset")){
|
||||
command.cmd = "set-ar";
|
||||
command.ratio = "reset";
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_fitw")){
|
||||
command.cmd = "set-ar";
|
||||
command.ratio = "fitw";
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_fitw")){
|
||||
command.cmd = "set-ar";
|
||||
command.ratio = "fith";
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_219")){
|
||||
command.cmd = "set-ar";
|
||||
command.ratio = 2.39;
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_189")){
|
||||
command.cmd = "set-ar";
|
||||
command.ratio = 2.0;
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_169")){
|
||||
command.cmd = "set-ar";
|
||||
command.ratio = 1.78;
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_1610")){
|
||||
command.cmd = "set-ar";
|
||||
command.ratio = 1.6;
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_custom")){
|
||||
ratio = getCustomAspectRatio();
|
||||
command.cmd = "set-ar";
|
||||
command.ratio = ratio;
|
||||
return ratio !== false ? command : null;
|
||||
}
|
||||
if(e.target.classList.contains("_ar_save_custom_ar")){
|
||||
ratio = getCustomAspectRatio();
|
||||
command.cmd = "set-custom-ar";
|
||||
command.ratio = ratio;
|
||||
return ratio !== false ? command : null; // this validates input
|
||||
}
|
||||
}
|
||||
if(e.target.classList.contains("_stretch")){
|
||||
// stretch, global
|
||||
if (e.target.classList.contains("_ar_stretch_global")) {
|
||||
if (e.target.classList.contains("_none")) {
|
||||
settings.active.stretch.initialMode = 0;
|
||||
} else if (e.target.classList.contains("_basic")) {
|
||||
settings.active.stretch.initialMode = 1;
|
||||
} else if (e.target.classList.contains("_hybrid")) {
|
||||
settings.active.stretch.initialMode = 2;
|
||||
} else if (e.target.classList.contains("_conditional")) {
|
||||
settings.active.stretch.initialMode = 3;
|
||||
}
|
||||
settings.save();
|
||||
return;
|
||||
}
|
||||
|
||||
// stretch, site
|
||||
if (e.target.classList.contains("_ar_stretch_site")) {
|
||||
if (e.target.classList.contains("_none")) {
|
||||
settings.active.sites[site].stretch = 0;
|
||||
} else if (e.target.classList.contains("_basic")) {
|
||||
settings.active.sites[site].stretch = 1;
|
||||
} else if (e.target.classList.contains("_hybrid")) {
|
||||
settings.active.sites[site].stretch = 2;
|
||||
} else if (e.target.classList.contains("_conditional")) {
|
||||
settings.active.sites[site].stretch = 3;
|
||||
} else {
|
||||
delete(settings.active.sites[site].stretch);
|
||||
}
|
||||
settings.save();
|
||||
return;
|
||||
}
|
||||
|
||||
if(e.target.classList.contains("_ar_stretch_none")) {
|
||||
command.cmd = "set-stretch";
|
||||
command.mode = "NO_STRETCH";
|
||||
} else if(e.target.classList.contains("_ar_stretch_basic")) {
|
||||
command.cmd = "set-stretch";
|
||||
command.mode = "BASIC";
|
||||
} else if(e.target.classList.contains("_ar_stretch_hybrid")) {
|
||||
command.cmd = "set-stretch";
|
||||
command.mode = "HYBRID";
|
||||
} else if(e.target.classList.contains("_ar_stretch_conditional")) {
|
||||
command.cmd = "set-stretch";
|
||||
command.mode = "CONDITIONAL";
|
||||
}
|
||||
return command;
|
||||
}
|
||||
if(e.target.classList.contains("_autoAr")){
|
||||
if(e.target.classList.contains("_ar_global_options")){
|
||||
if (e.target.classList.contains("_blacklist")) {
|
||||
settings.active.arDetect.mode = "blacklist";
|
||||
} else if (e.target.classList.contains("_whitelist")) {
|
||||
settings.active.arDetect.mode = "whitelist";
|
||||
} else {
|
||||
settings.active.arDetect.mode = "disabled";
|
||||
}
|
||||
settings.save();
|
||||
return;
|
||||
} else if (e.target.classList.contains("_save_autoAr_timer")) {
|
||||
var value = parseInt(document.getElementById("_input_autoAr_timer").value.trim());
|
||||
|
||||
if(! isNaN(value)){
|
||||
var timeout = parseInt(value);
|
||||
settings.active.arDetect.timer_playing = timeout;
|
||||
settings.save();
|
||||
}
|
||||
return;
|
||||
} else if (e.target.classList.contains("_ar_site_options")) {
|
||||
var mode;
|
||||
if(e.target.classList.contains("_disabled")){
|
||||
mode = "disabled";
|
||||
} else if(e.target.classList.contains("_enabled")) {
|
||||
mode = "enabled";
|
||||
} else {
|
||||
mode = "default";
|
||||
}
|
||||
|
||||
if(settings.active.sites[site]) {
|
||||
settings.active.sites[site].arStatus = mode;
|
||||
} else {
|
||||
settings.active.sites[site] = {
|
||||
status: settings.active.extensionMode,
|
||||
statusEmbedded: settings.active.extensionMode,
|
||||
arStatus: mode,
|
||||
type: 'user-defined'
|
||||
}
|
||||
}
|
||||
settings.save();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (e.target.classList.contains("_align_ext")) {
|
||||
if (e.target.classList.contains("_align_ext_left")) {
|
||||
settings.active.miscFullscreenSettings.videoFloat = 'left';
|
||||
} else if (e.target.classList.contains("_align_ext_center")) {
|
||||
settings.active.miscFullscreenSettings.videoFloat = 'center';
|
||||
} else if (e.target.classList.contains("_align_ext_right")) {
|
||||
settings.active.miscFullscreenSettings.videoFloat = 'right';
|
||||
}
|
||||
|
||||
settings.save();
|
||||
return;
|
||||
}
|
||||
if (e.target.classList.contains("_align_site")) {
|
||||
if (!site) {
|
||||
return;
|
||||
}
|
||||
if (e.target.classList.contains("_align_site_left")) {
|
||||
settings.active.sites[site].videoAlignment = 'left';
|
||||
} else if (e.target.classList.contains("_align_site_center")) {
|
||||
settings.active.sites[site].videoAlignment = 'center';
|
||||
} else if (e.target.classList.contains("_align_site_right")) {
|
||||
settings.active.sites[site].videoAlignment = 'right';
|
||||
} else {
|
||||
// default case — remove this object
|
||||
delete(settings.active.sites[site].videoAlignment);
|
||||
}
|
||||
|
||||
settings.save();
|
||||
return;
|
||||
}
|
||||
if (e.target.classList.contains("_align")) {
|
||||
command.cmd = "set-alignment";
|
||||
|
||||
if (e.target.classList.contains("_align_video_left")) {
|
||||
command.mode = 'left';
|
||||
} else if (e.target.classList.contains("_align_video_center")) {
|
||||
command.mode = 'center';
|
||||
} else if (e.target.classList.contains("_align_video_right")) {
|
||||
command.mode = 'right';
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
//#region zoom buttons
|
||||
if (e.target.classList.contains("_zoom_show_shortcuts")) {
|
||||
VideoPanel.misc.zoomShortcuts.classList.remove("hidden");
|
||||
VideoPanel.buttons.zoom.hideShortcuts.classList.remove("hidden");
|
||||
VideoPanel.buttons.zoom.showShortcuts.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
if (e.target.classList.contains("_zoom_hide_shortcuts")) {
|
||||
VideoPanel.misc.zoomShortcuts.classList.add("hidden");
|
||||
VideoPanel.buttons.zoom.hideShortcuts.classList.add("hidden");
|
||||
VideoPanel.buttons.zoom.showShortcuts.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
if (e.target.classList.contains("_zoom_reset")) {
|
||||
zoom_videoScale = scale;
|
||||
VideoPanel.labels.zoomLevel.textContent = 100;
|
||||
VideoPanel.inputs.zoomSlider.value = 0; // log₂(1)
|
||||
|
||||
command.cmd = 'set-zoom';
|
||||
command.zoom = 1;
|
||||
return command;
|
||||
}
|
||||
//#endregion
|
||||
//#region show/hide custom ar
|
||||
if (e.target.classList.contains("_changeAr_show_customAr")) {
|
||||
VideoPanel.misc.customArChanger.classList.remove("hidden");
|
||||
VideoPanel.buttons.changeAr.showCustomAr.classList.add("hidden");
|
||||
VideoPanel.buttons.changeAr.hideCustomAr.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
if (e.target.classList.contains("_changeAr_hide_customAr")) {
|
||||
VideoPanel.misc.customArChanger.classList.add("hidden");
|
||||
VideoPanel.buttons.changeAr.showCustomAr.classList.remove("hidden");
|
||||
VideoPanel.buttons.changeAr.hideCustomAr.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
var command = getcmd(e);
|
||||
if(command)
|
||||
port.postMessage(command);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
async function sleep(t) {
|
||||
return new Promise( (resolve,reject) => {
|
||||
setTimeout(() => resolve(), t);
|
||||
});
|
||||
}
|
||||
|
||||
async function popup_init() {
|
||||
// let's init settings and check if they're loaded
|
||||
await settings.init();
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("[popup] Are settings loaded?", settings)
|
||||
}
|
||||
|
||||
|
||||
const customArInputField = document.getElementById("_input_custom_ar");
|
||||
const autoarFrequencyInputField = document.getElementById("_input_autoAr_timer");
|
||||
|
||||
customArInputField.addEventListener("blur", (event) => {
|
||||
validateCustomAr();
|
||||
});
|
||||
customArInputField.addEventListener("mouseleave", (event) => {
|
||||
validateCustomAr();
|
||||
});
|
||||
|
||||
// autoarFrequencyInputField.addEventListener("blur", (event) => {
|
||||
// validateAutoArTimeout();
|
||||
// });
|
||||
// autoarFrequencyInputField.addEventListener("mouseleave", (event) => {
|
||||
// validateAutoArTimeout();
|
||||
// });
|
||||
|
||||
hideWarning("script-not-running-warning");
|
||||
while (true) {
|
||||
console.log("GETTING SITE")
|
||||
getSite();
|
||||
await sleep(5000);
|
||||
}
|
||||
}
|
||||
|
||||
popup_init();
|
@ -1,259 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- shit like this really makes you appreciate angular and similar frameworks, doesn't it? -->
|
||||
<title></title>
|
||||
<meta charset="utf-8">
|
||||
<link rel='stylesheet' type='text/css' href='../css/font/overpass.css'>
|
||||
<link rel='stylesheet' type='text/css' href='../css/font/overpass-mono.css'>
|
||||
<link rel='stylesheet' type='text/css' href='../css/common.css'>
|
||||
<link rel='stylesheet' type='text/css' href='./css/popup.css'>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
U<span class="smallcaps">ltrawidify</span>: <small>Quick settings</small>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- TABS/SIDEBAR -->
|
||||
<div class="left-side">
|
||||
|
||||
<div id="_menu_tab_settings_ext" class="menu-item _menu_tab_settings_ext">
|
||||
Extension settings
|
||||
</div>
|
||||
<div id="_menu_tab_settings_site" class="menu-item _menu_tab_settings_site">
|
||||
Site settings
|
||||
<span class="menu-item-inline-desc"><br/>Settings for <span id="_menu_tab_settings_site_site_label"></span></span>
|
||||
</div>
|
||||
<div id="_menu_tab_settings_video" class="menu-item _menu_tab_settings_video">
|
||||
Video settings
|
||||
<span class="menu-item-inline-desc"><br/>Crop & stretch options for current video</span>
|
||||
</div>
|
||||
<div id="_menu_tab_about" class="menu-item _menu_tab_about">
|
||||
About Ultrawidify<span class="menu-item-inline-desc"><br/>See for bug reports</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- PANELS/CONTENT -->
|
||||
<div class="right-side">
|
||||
<div id="script-not-running-warning" class="warning">If you see this line, then your browser didn't start the popup script yet. This is a problem caused by the browser. Please wait a few seconds.</div>
|
||||
|
||||
|
||||
<!-- EXTENSION SETTINGS -->
|
||||
<div id="_menu_settings_ext" class="suboption hidden">
|
||||
<p>These settings can be overriden on per-site basis.</p>
|
||||
<div class="row">
|
||||
<span class="label">Enable this extension:</span>
|
||||
<div class="button-row">
|
||||
<a id="_ext_global_options_blacklist" class="button _ext _ext_global_options _blacklist">Always</a>
|
||||
<a id="_ext_global_options_whitelist" class="button _ext _ext_global_options _whitelist">On whitelisted sites</a>
|
||||
<a id="_ext_global_options_disabled" class="button _ext _ext_global_options _disabled" >Never</a>
|
||||
</div>
|
||||
|
||||
<div id="_ext_only_when_ext_enabled">
|
||||
<div class="row">
|
||||
<span class="label">Enable autodetection:</span>
|
||||
<div class="button-row">
|
||||
<a id="_ar_global_options_blacklist" class="button _autoAr _ar_global_options _blacklist">Always</a>
|
||||
<a id="_ar_global_options_whitelist" class="button _autoAr _ar_global_options _whitelist">On whitelisted sites</a>
|
||||
<a id="_ar_global_options_disabled" class="button _autoAr _ar_global_options _disabled" >Never</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: default aspect ratio settings -->
|
||||
|
||||
<div class="row">
|
||||
<span class="label experimental">Default stretching mode</span>
|
||||
<div class="button-row">
|
||||
<a id="_stretch_global_none" class="button _stretch _ar_stretch_global _none w24">Never<br/><span id="_b_stretch_default_none_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_global_basic" class="button _stretch _ar_stretch_global _basic w24">Basic<br/><span id="_b_stretch_default_basic_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_global_hybrid" class="button _stretch _ar_stretch_global _hybrid w24">Hybrid<br/><span id="_b_stretch_default_hybrid_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_global_conditional" class="button _stretch _ar_stretch_global _conditional w24">Thin borders<br/><span id="_b_stretch_default_conditional_key" class="smallcaps small darker"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VIDEO ALIGNMENT -->
|
||||
<div class="row">
|
||||
<span class="label">Video alignment:</span>
|
||||
<div class="button-row">
|
||||
<a id="_align_ext_left" class="button _align_ext _align_ext_left">Left</a>
|
||||
<a id="_align_ext_center" class="button _align_ext _align_ext_center">Center</a>
|
||||
<a id="_align_ext_right" class="button _align_ext _align_ext_right">Right</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SITE SETTINGS -->
|
||||
<div id="_menu_settings_site" class="suboption hidden">
|
||||
<div class="row">
|
||||
<span class="label">Options for this site:</span>
|
||||
<div class="button-row">
|
||||
<a id="_ext_site_options_whitelist" class="button _ext _ext_site_options _whitelist">Whitelist</a>
|
||||
<a id="_ext_site_options_default" class="button _ext _ext_site_options _default" >Default</a>
|
||||
<a id="_ext_site_options_blacklist" class="button _ext _ext_site_options _blacklist">Blacklist</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- things that are disabled if site is disabled -->
|
||||
<div id="_site_only_when_site_enabled">
|
||||
<div class="row">
|
||||
<span class="label">Automatic detection:</span>
|
||||
<div class="button-row">
|
||||
<a id="_ar_site_options_enabled" class="button _autoAr _ar_site_options _enabled" >Whitelist</a>
|
||||
<a id="_ar_site_options_default" class="button _autoAr _ar_site_options _default" >Default</a>
|
||||
<a id="_ar_site_options_disabled" class="button _autoAr _ar_site_options _disabled">Blacklist</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: default aspect ratio, default settings -->
|
||||
|
||||
<!-- STRETCH MODE -->
|
||||
<div class="row">
|
||||
<span class="label experimental">Default stretching mode</span>
|
||||
<div class="button-row">
|
||||
<a id="_stretch_site_none" class="button _stretch _ar_stretch_site _none w24">Never<br/><span id="_b_stretch_default_none_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_site_basic" class="button _stretch _ar_stretch_site _basic w24">Basic<br/><span id="_b_stretch_default_basic_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_site_hybrid" class="button _stretch _ar_stretch_site _hybrid w24">Hybrid<br/><span id="_b_stretch_default_hybrid_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_site_conditional" class="button _stretch _ar_stretch_site _conditional w24">Thin borders<br/><span id="_b_stretch_default_conditional_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_site_default" class="button _stretch _ar_stretch_site _default w24">Default<br/><span id="_b_stretch_default_conditional_key" class="smallcaps small darker"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VIDEO ALIGNMENT -->
|
||||
<div class="row">
|
||||
<span class="label">Video alignment:</span>
|
||||
<div class="button-row">
|
||||
<a id="_align_site_left" class="button _align_site _align_site_left">Left</a>
|
||||
<a id="_align_site_center" class="button _align_site _align_site_center">Center</a>
|
||||
<a id="_align_site_right" class="button _align_site _align_site_right">Right</a>
|
||||
<a id="_align_site_default"class="button _align_site _align_site_default">Default</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- VIDEO SETTINGS -->
|
||||
<div id="_menu_settings_video" class="suboption">
|
||||
|
||||
<!-- CROP STUFF -->
|
||||
<div class="row">
|
||||
<span class="label">Cropping mode</span>
|
||||
<div class="button-row">
|
||||
<a class="button _changeAr _ar_auto w24">Auto-detect<br/><span id="_b_changeAr_auto-ar_key" class="smallcaps small darker"></span> </a>
|
||||
<a class="button _changeAr _ar_reset w24">Reset<br/><span id="_b_changeAr_reset_key" class="smallcaps small darker"> </span></a>
|
||||
<a class="button _changeAr _ar_219 w24">21:9<br/><span id="_b_changeAr_219_key" class="smallcaps small darker"> </span></a>
|
||||
<a class="button _changeAr _ar_189 w24">2:1 (18:9)<br/><span id="_b_changeAr_189_key" class="smallcaps small darker"> </span></a>
|
||||
<a class="button _changeAr _ar_169 w24">16:9<br/><span id="_b_changeAr_169_key" class="smallcaps small darker"> </span></a>
|
||||
<a class="button _changeAr _ar_custom w24">Custom<br/><span id="_b_changeAr_custom_key" class="smallcaps small darker"> </span></a>
|
||||
</div>
|
||||
<div style="overflow: auto">
|
||||
<div class="float-right medium-small">
|
||||
<a id="_changeAr_b_show_customAr" class="_changeAr_show_customAr x-pad-1em">Set custom aspect ratio</a>
|
||||
<a id="_changeAr_b_hide_customAr" class="_changeAr_hide_customAr hidden x-pad-1em">Hide custom aspect ratio settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="_changeAr_customAr" class="row hidden">
|
||||
<span class="label">Custom aspect ratio</span>
|
||||
<div>
|
||||
<input id="_input_custom_ar" class="_changeAr _custom" onblur="validateCustomAr()"> <span id="_b_changeAr_save_custom_ar" class="button _ar_save_custom_ar _changeAr">Save</span>
|
||||
<br/><small>Aspect ratio can be entered in any of these forms (without quotes):
|
||||
<ul style="display: block; padding-top: 0px; margin-top: 0px">
|
||||
<li>fraction (<i>width/height</i>, e.g. <i>16/10</i>, <i>2560/1080</i>, etc.)</li>
|
||||
<li>ratio (e.g. <i>1:2.35</i> or simply <i>2.35</i> — <i>1:</i> bit can be left out)</li>
|
||||
</ul></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ZOOM STUFF -->
|
||||
<span class="label experimental">Manual zooming and panning</span>
|
||||
<div class="row">
|
||||
<!--
|
||||
min, max and value need to be implemented in js as this slider
|
||||
should use logarithmic scale
|
||||
-->
|
||||
<input id="_input_zoom_slider" class="w100"
|
||||
type="range"
|
||||
step="any"
|
||||
/>
|
||||
<div style="overflow: auto">
|
||||
<div class="inline-block float-left medium-small x-pad-1em">
|
||||
Zoom: <span id="_label_zoom_level">100</span>%
|
||||
</div>
|
||||
<div class="inline-block float-right medium-small">
|
||||
<a id="_zoom_b_show_shortcuts" class="_zoom_show_shortcuts x-pad-1em">show shortcuts</a>
|
||||
<a id="_zoom_b_hide_shortcuts" class="_zoom_hide_shortcuts hidden x-pad-1em">hide shortcuts</a>
|
||||
<a class="_zoom_reset x-pad-1em">reset</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="m-t-0-33em w100 display-block">
|
||||
<input id="_input_zoom_site_allow_pan"
|
||||
type="checkbox"
|
||||
/>
|
||||
Pan with mouse
|
||||
</div> -->
|
||||
<div id="_zoom_shortcuts" class="row hidden">
|
||||
<small>Keyboard shortcuts:<ul>
|
||||
<li>Z: zoom</li>
|
||||
<li>U: unzoom</li>
|
||||
<li>P: toggle pan</li>
|
||||
<li>shift: hold to enable/prevent pan</li>
|
||||
</ul></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STRETCH STUFF -->
|
||||
<span class="label experimental">Stretching mode</span>
|
||||
<div class="button-row">
|
||||
<a class="button _stretch _ar_stretch_none w24">Normal<br/><span id="_b_stretch_none_key" class="smallcaps small darker"></span></a>
|
||||
<a class="button _stretch _ar_stretch_basic w24">Basic<br/><span id="_b_stretch_basic_key" class="smallcaps small darker"></span></a>
|
||||
<a class="button _stretch _ar_stretch_hybrid w24">Hybrid<br/><span id="_b_stretch_hybrid_key" class="smallcaps small darker"></span></a>
|
||||
<a class="button _stretch _ar_stretch_conditional w24">Thin borders<br/><span id="_b_stretch_conditional_key" class="smallcaps small darker"></span></a>
|
||||
</div>
|
||||
|
||||
<!-- VIDEO ALIGNMENT -->
|
||||
<div class="row">
|
||||
<span class="label">Video alignment:</span>
|
||||
<div class="button-row">
|
||||
<a id="_align_video_left" class="button _align _align_video_left">Left</a>
|
||||
<a id="_align_video_center" class="button _align _align_video_center">Center</a>
|
||||
<a id="_align_video_right" class="button _align _align_video_right">Right</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ABOUTR -->
|
||||
<div id="_menu_about" class="suboption hidden">
|
||||
<div class="row">
|
||||
<span class="label">Ultrawidify version:</span><br/> <span id="uw-version"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">Having an issue?</span><br/> Report <strike>undocumented features</strike> bugs using one of the following options:<ul><li> <a target="_blank" href="https://github.com/xternal7/ultrawidify/issues"><b>Github (strongly preferred)</b></a><br/></li>
|
||||
<li>PM me on <a target="_blank" href="https://www.reddit.com/message/compose?to=xternal7&subject=[Ultrawidify]%20ENTER%20SUMMARY%20OF%20YOUR%20PROBLEM%20HERE&message=Describe+your+issue+in+more+detail.+Don%27t+forget+to+include%3A%0D%0A%2A+Extension+version%0D%0A%2A+Browser%0D%0A%2A+Operating+system%0D%0A%2A+Other+extensions+that+could+possibly+interfere%0D%0A%0D%0AIf+you%27re+reporting+an+issue+with+automatic+aspect+ratio+detection%2C+please+also+include+the+following+%28if+possible%29%3A%0D%0A%2A+model+and+make+of+your+CPU%0D%0A%2A+amount+of+RAM">reddit</a><br/></li>
|
||||
<li>Email: <a target="_blank" href="mailto:tamius.han@gmail.com?subject=%5BUltrawidify%5D+ENTER+SUMMARY+OF+YOUR+ISSUE+HERE&body=Describe+your+issue+in+more+detail.+Don%27t+forget+to+include%3A%0D%0A%2A+Extension+version%0D%0A%2A+Browser%0D%0A%2A+Operating+system%0D%0A%2A+Other+extensions+that+could+possibly+interfere%0D%0A%0D%0AIf+you%27re+reporting+an+issue+with+automatic+aspect+ratio+detection%2C+please+also+include+the+following+%28if+possible%29%3A%0D%0A%2A+model+and+make+of+your+CPU%0D%0A%2A+amount+of+RAM">tamius.han@gmail.com</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- load all scripts. ordering is important! -->
|
||||
|
||||
<script src="../../js/conf/Debug.js"></script>
|
||||
|
||||
<script src="../../js/lib/BrowserDetect.js"></script>
|
||||
<script src="../../js/lib/Comms.js"></script>
|
||||
|
||||
<script src="../../js/conf/ExtensionConf.js"></script>
|
||||
<script src="../../js/lib/Settings.js"></script>
|
||||
|
||||
<script src="./js/popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -1,202 +0,0 @@
|
||||
@import url("../css/font/overpass.css");
|
||||
@import url("../css/font/overpass-mono.css");
|
||||
|
||||
body {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoYRxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg==);
|
||||
background-size: 75px;
|
||||
background-color: #000000;
|
||||
color: #ddd;
|
||||
font-family: 'overpass', serif;
|
||||
font-size: 1.2em;
|
||||
width: 100%;
|
||||
border: 0px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
text-align: center;
|
||||
z-index: -1000;
|
||||
}
|
||||
|
||||
a, a:visited{
|
||||
color: #fa6607;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover{
|
||||
color: #fa6;
|
||||
}
|
||||
|
||||
.show{
|
||||
display: block !important;
|
||||
}
|
||||
.hide{
|
||||
display: none !important;
|
||||
}
|
||||
head{
|
||||
width: 100%;
|
||||
border: 0px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
h1,h2{
|
||||
color: #ff9;
|
||||
font-weight: 300;
|
||||
}
|
||||
h1{
|
||||
font-size: 3.3em;
|
||||
}
|
||||
h2{
|
||||
font-size: 2.2em;
|
||||
}
|
||||
.sites_header{
|
||||
font-size: 1.6em;
|
||||
color: #ff9;
|
||||
}
|
||||
.content{
|
||||
display: inline-block;
|
||||
width: 52em;
|
||||
}
|
||||
.center{
|
||||
margin: 0 auto;
|
||||
}
|
||||
.basecolor {
|
||||
color: #ddd !important;
|
||||
}
|
||||
.red{
|
||||
color: #ff3a00;
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.69;
|
||||
}
|
||||
.left {
|
||||
text-align: left;
|
||||
}
|
||||
.block {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.tabline {
|
||||
background-color: #000;
|
||||
width: 100%;
|
||||
margin-bottom: 1.5em;
|
||||
padding-top: 0.3em;
|
||||
padding-bottom: 0.4em;
|
||||
z-index: -200;
|
||||
}
|
||||
.tab {
|
||||
color: #fa6607;
|
||||
}
|
||||
.tab:hover{
|
||||
text-shadow: #aa5 0px 0px 0.02em,#aa5 0px 0px 0.02em;
|
||||
}
|
||||
.tab-selected {
|
||||
color: #ff9 !important;
|
||||
}
|
||||
#all{
|
||||
min-width: 100%;
|
||||
min-height: 100vh;
|
||||
background-image: url('img/settings/about-bg.png');
|
||||
background-position: calc(50vw + 20em) 10vh;
|
||||
background-attachment: fixed;
|
||||
background-size: auto 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.dup_keybinds{
|
||||
background-color: #720 !important;
|
||||
}
|
||||
input[type=text]{
|
||||
font-size: 1em;
|
||||
padding-left: 0.6em;
|
||||
margin-left: 1em;
|
||||
width: 2em;
|
||||
background-color: #000;
|
||||
border: 1px #442 solid;
|
||||
}
|
||||
.uw_shortcuts_line{
|
||||
padding-top: 0.25em;
|
||||
padding-left: 5em;
|
||||
}
|
||||
.uw_shortcuts_label{
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
width: 17.5em;
|
||||
}
|
||||
.uw_options_line{
|
||||
margin-top: 0.75em;
|
||||
font-size: 1.1em;
|
||||
width: 80%;
|
||||
margin-left: 5%;
|
||||
}
|
||||
.uw_options_option{
|
||||
margin-left: 5%;
|
||||
}
|
||||
.uw_suboption{
|
||||
margin-left: 5em;
|
||||
margin-top: 0.75em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.uw_options_desc, .uw_suboption_desc{
|
||||
margin-top: 0.33em;
|
||||
font-size: 0.69em;
|
||||
color: #aaa;
|
||||
}
|
||||
.uw_suboption_desc{
|
||||
margin-left: 5em;
|
||||
}
|
||||
|
||||
.buttonbar{
|
||||
/* width: 100%; */
|
||||
padding-left: 20em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
.button{
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
padding-top: 0.4em;
|
||||
width: 4em;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,0.66);
|
||||
color: #ffc;
|
||||
height: 1.7em;
|
||||
}
|
||||
|
||||
/** site options css **/
|
||||
.site_name {
|
||||
padding-left: 1em;
|
||||
padding-bottom: 0.3em;
|
||||
color: #fff;
|
||||
font-size: 1.1em;
|
||||
height: 13em !important;
|
||||
|
||||
}
|
||||
.site_details {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.site_title_ebox {
|
||||
width: 10em !important;
|
||||
font-size: 1.5em !important;
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
margin-left: 0px !important;
|
||||
border: 0px !important;
|
||||
color: #ffc !important;
|
||||
}
|
||||
.details_ebox {
|
||||
width: 12em !important;
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
border: 0px !important;
|
||||
color: #ffc !important;
|
||||
margin-left: 0em !important;
|
||||
}
|
||||
.details_ebox:disabled {
|
||||
color: #aaa !important;
|
||||
}
|
||||
.inline_button {
|
||||
display: inline-block;
|
||||
margin-top: -1.42em;
|
||||
}
|
||||
.inline_button:hover {
|
||||
color: #fff;
|
||||
}
|
@ -1,292 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Ultrawidify :: settings</title>
|
||||
<meta charset="UTF-8">
|
||||
<base target="_blank">
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="all">
|
||||
<header>
|
||||
<div class="content center">
|
||||
<div class="left">
|
||||
<h1>Ultrawidify :: settings</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabline center">
|
||||
<div class="content left">
|
||||
<div id="tab_general_settings" class="block tab tab-selected">General settings</div>
|
||||
<div id="tab_sites" class="block tab">Site options</div>
|
||||
<div id="tab_shortcuts" class="block tab">Shortcuts</div>
|
||||
<div id="tab_about" class="block tab">About</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
<!-- GENERAL SETTINGS -->
|
||||
|
||||
<div id="general_settings">
|
||||
<div class="content left">
|
||||
|
||||
<div class="row">
|
||||
<span class="label">Enable this extension:</span>
|
||||
<div class="button-row">
|
||||
<a id="_ext_global_options_blacklist" class="button _ext _ext_global_options _blacklist">Always</a>
|
||||
<a id="_ext_global_options_whitelist" class="button _ext _ext_global_options _whitelist">On whitelisted sites</a>
|
||||
<a id="_ext_global_options_disabled" class="button _ext _ext_global_options _disabled" >Never</a>
|
||||
</div>
|
||||
<span class="description">
|
||||
<b>Always</b> enables this extension on every site you visit that you didn't blacklist.
|
||||
<b>On whitelisted sites</b> enables this extension only on sites you explicitly whitelisted.
|
||||
<b>Never</b> disables extension on all sites, even on those you whitelisted.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<span class="label">Enable autodetection:</span>
|
||||
<div class="button-row">
|
||||
<a id="_general_ar_global_options_blacklist" class="button _autoAr _ar_global_options _blacklist">Always</a>
|
||||
<a id="_general_ar_global_options_whitelist" class="button _autoAr _ar_global_options _whitelist">On whitelisted sites</a>
|
||||
<a id="_general_ar_global_options_disabled" class="button _autoAr _ar_global_options _disabled" >Never</a>
|
||||
</div>
|
||||
<span class="description">
|
||||
<b>Always</b> enables autodetection on every site this extension is enabled for, unless blacklisted.
|
||||
<b>On whitelisted sites</b> enables autodetection only for sites that you explicitly enabled.
|
||||
<b>Never</b> disables autodetection on all sites, even on those you whitelisted.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<span class="label">In-player UI</span>
|
||||
<div class="button-row">
|
||||
<a id="_ar_global_options_blacklist" class="button _ui _ui_global_options _blacklist">Always/a>
|
||||
<a id="_ar_global_options_whitelist" class="button _ui _ui_global_options _whitelist">On whitelisted sites</a>
|
||||
<a id="_ar_global_options_disabled" class="button _ui _ui_global_options _disabled" >Never</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<span class="label">Video alignment:</span>
|
||||
<div class="button-row">
|
||||
<a id="_align_left" class="button _align _align_left">left</a>
|
||||
<a id="_align_center" class="button _align _align_center">center</a>
|
||||
<a id="_align_right" class="button _align _align_right">right</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="button-row">
|
||||
<a id="_stretch_global_none" class="button _stretch _ar_stretch_global _none">Never<br/><span id="_b_stretch_default_none_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_global_basic" class="button _stretch _ar_stretch_global _basic">Basic<br/><span id="_b_stretch_default_basic_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_global_hybrid" class="button _stretch _ar_stretch_global _hybrid">Hybrid<br/><span id="_b_stretch_default_hybrid_key" class="smallcaps small darker"></span></a>
|
||||
<a id="_stretch_global_conditional" class="button _stretch _ar_stretch_global _conditional">Thin borders<br/><span id="_b_stretch_default_conditional_key" class="smallcaps small darker"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- AUTOMATIC DETECTION SETTINGS -->
|
||||
|
||||
<div id="autoar_settings">
|
||||
|
||||
<div class="row">
|
||||
<span class="label">Enable autodetection:</span>
|
||||
<div class="button-row">
|
||||
<a id="_ar_global_options_blacklist" class="button _autoAr _ar_global_options _blacklist">Always</a>
|
||||
<a id="_ar_global_options_whitelist" class="button _autoAr _ar_global_options _whitelist">On whitelisted sites</a>
|
||||
<a id="_ar_global_options_disabled" class="button _autoAr _ar_global_options _disabled" >Never</a>
|
||||
</div>
|
||||
<div class="description">
|
||||
<b>Always</b> enables autodetection on every site this extension is enabled for, unless blacklisted.
|
||||
<b>On whitelisted sites</b> enables autodetection only for sites that you explicitly enabled.
|
||||
<b>Never</b> disables autodetection on all sites, even on those you whitelisted.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<span class="label">Checking for aspect ratio changes</span>
|
||||
<div>
|
||||
When video is playing: <input id="_input_autoAr_timer" class="_autoAr _autoAr_timer" type="number" min="5" max="10000"> ms
|
||||
<div>
|
||||
<div>
|
||||
When video is paused: <input id="_input_autoAr_timer_pause" class="_autoAr _autoAr_timer_pause" type="number" min="5" max="10000"> ms
|
||||
<div>
|
||||
<div>
|
||||
On error: <input id="_input_autoAr_timer_error" class="_autoAr _autoAr_timer_error" type="number" min="5" max="10000"> ms
|
||||
<div>
|
||||
|
||||
<div class="description">
|
||||
All values are in milliseconds.
|
||||
The values determine how often the autodetection script will scan video for changes in aspect ratio under different circumstances.
|
||||
Using shorter intervals can cause lag and increases resource usage.
|
||||
Using longer intervals increases delay between aspect ratio changing and said change being detected.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="_autoar_fallback_mode_top" class="row">
|
||||
<label>Enable fallback mode</label>
|
||||
todo: insert checkbox
|
||||
<div class="description">
|
||||
<p>In order for autodetection to work, the extension grabs a frame of the video you're watching and takes a look at it.
|
||||
Some websites use DRM, which prevents you from using the HTML5-blessed way of getting a frame from a video.
|
||||
Some browsers offer some features that can be used to circumvent DRM protection.
|
||||
With this option enabled, the extension will use said features to basically do that.
|
||||
</p>
|
||||
<p><b>Technical details: tl;dr — only works in Firefox and maybe its forks.</b><br/>
|
||||
Fallback mode depends on two things:
|
||||
<ol>
|
||||
<li>Attempting to grab a video frame from DRM protected video stream must return an error (Chrome returns black image)</li>
|
||||
<li>Supports <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawWindow" target="_blank">CanvasRenderingContext2D.drawWindow()</a> method, which pretty much only Firefox does</li>
|
||||
</ol>
|
||||
</p>
|
||||
<p><b>But I thought it's <a href="http://www.dmlp.org/legal-guide/circumventing-copyright-controls" target="_blank">illegal</a> to circumvent DRM?</b><br/>
|
||||
Courts <a href="https://arstechnica.com/information-technology/2010/07/court-breaking-drm-for-a-fair-use-is-legal/" target="_blank">disagree</a> (at least as far as our use-case is concerned).
|
||||
At this point I'd also throw in some choice words about DRM and everyone using it, but AMO gives me "you're breaking terms of conduct" warnings over use of the word for thing you flush down the toilet, prepended by a word for male cow*, in the code comments.<small><br/>*'cow' is term for the female</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- INTERFACE AND SHORTCUTS -->
|
||||
|
||||
<div id="interface_shortcut_settings">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- SITE SETTINGS -->
|
||||
|
||||
<div id="uw_sites" class="hide">
|
||||
<h1>PLEASE NOTE: THIS IS COMPLETELY NON-FUNCTIONAL ATM. YOU CANT SET SITE-SPECIFIC OPTIONS ATM</h1>
|
||||
<div id="uw_sites_body" class="content left">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- REMOVE PLOX -->
|
||||
|
||||
<div id="uw_shortcuts" class="hide">
|
||||
<div class="content left">
|
||||
<h2>Keyboard shortcuts</h2>
|
||||
|
||||
<form>
|
||||
<!-- BEGIN FORM -->
|
||||
|
||||
<!-- try autoar -->
|
||||
<div class="uw_shortcuts_line">
|
||||
<div class="uw_shortcuts_label">Autodetect aspect ratio</div>
|
||||
<input type='checkbox' id="_kbd_autoar_ctrlKey" > Ctrl +
|
||||
<input type='checkbox' id="_kbd_autoar_shiftKey" > Shift +
|
||||
<input type='checkbox' id="_kbd_autoar_altKey" > Alt +
|
||||
<input type='text' id="_kbd_autoar_lettr" maxlength='1' class='shortcut_input'>
|
||||
</div>
|
||||
|
||||
<!-- fit width -->
|
||||
<div class="uw_shortcuts_line">
|
||||
<div class="uw_shortcuts_label">Fit to width</div>
|
||||
<input type='checkbox' id="_kbd_fitw_ctrlKey" > Ctrl +
|
||||
<input type='checkbox' id="_kbd_fitw_shiftKey" > Shift +
|
||||
<input type='checkbox' id="_kbd_fitw_altKey" > Alt +
|
||||
<input type='text' id="_kbd_fitw_lettr" maxlength='1' class='shortcut_input'>
|
||||
</div>
|
||||
|
||||
<!-- fit height -->
|
||||
<div class="uw_shortcuts_line">
|
||||
<div class="uw_shortcuts_label">Fit to height</div>
|
||||
<input type='checkbox' id="_kbd_fith_ctrlKey" > Ctrl +
|
||||
<input type='checkbox' id="_kbd_fith_shiftKey" > Shift +
|
||||
<input type='checkbox' id="_kbd_fith_altKey" > Alt +
|
||||
<input type='text' id="_kbd_fith_lettr" maxlength='1' class='shortcut_input'>
|
||||
</div>
|
||||
|
||||
<!-- reset -->
|
||||
<div class="uw_shortcuts_line">
|
||||
<div class="uw_shortcuts_label">Reset</div>
|
||||
<input type='checkbox' id="_kbd_reset_ctrlKey" > Ctrl +
|
||||
<input type='checkbox' id="_kbd_reset_shiftKey" > Shift +
|
||||
<input type='checkbox' id="_kbd_reset_altKey" > Alt +
|
||||
<input type='text' id="_kbd_reset_lettr" maxlength='1' class='shortcut_input'>
|
||||
</div>
|
||||
|
||||
<!-- force 21:9 -->
|
||||
<div class="uw_shortcuts_line">
|
||||
<div class="uw_shortcuts_label">Force 21:9</div>
|
||||
<input type='checkbox' id="_kbd_char_219_ctrlKey" > Ctrl +
|
||||
<input type='checkbox' id="_kbd_char_219_shiftKey" > Shift +
|
||||
<input type='checkbox' id="_kbd_char_219_altKey" > Alt +
|
||||
<input type='text' id="_kbd_char_219_lettr" maxlength='1' class='shortcut_input'>
|
||||
</div>
|
||||
|
||||
<!-- force 2:1 -->
|
||||
<div class="uw_shortcuts_line">
|
||||
<div class="uw_shortcuts_label">Force 2:1 (18:9)</div>
|
||||
<input type='checkbox' id="_kbd_char_189_ctrlKey" > Ctrl +
|
||||
<input type='checkbox' id="_kbd_char_189_shiftKey" > Shift +
|
||||
<input type='checkbox' id="_kbd_char_189_altKey" > Alt +
|
||||
<input type='text' id="_kbd_char_189_lettr" maxlength='1' class='shortcut_input'>
|
||||
</div>
|
||||
|
||||
<!-- force 16:9 -->
|
||||
<div class="uw_shortcuts_line">
|
||||
<div class="uw_shortcuts_label">Force 16:9</div>
|
||||
<input type='checkbox' id="_kbd_char_169_ctrlKey" > Ctrl +
|
||||
<input type='checkbox' id="_kbd_char_169_shiftKey" > Shift +
|
||||
<input type='checkbox' id="_kbd_char_169_altKey" > Alt +
|
||||
<input type='text' id="_kbd_char_169_lettr" maxlength='1' class='shortcut_input'>
|
||||
</div>
|
||||
|
||||
<!-- END FORM -->
|
||||
</form>
|
||||
<div class="buttonbar"><div class="button" id="kb_cancel">Cancel</div><div class="button" id="kb_save">Save</div></div>
|
||||
<div id="errbox"></div>
|
||||
<div><small><b>Note:</b> keyboard shortcut conflicts aren't handled by this extension. You must know your shortcuts.<br/><b>Pro tip:</b> If you don't want to have a keybind for a shortcut, just uncheck all boxes for the action and remove the letter</small></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ABOUT -->
|
||||
|
||||
<div id="about" class="hide">
|
||||
<div class="content left">
|
||||
<h2>Ultrawidify - an aspect ratio fixer for youtube <small>(and netflix)</small></h2>
|
||||
<p>Created by Tamius Han (me). If something is broken, you can shout at me on <a href="https://github.com/xternal7/ultrawidify">github</a> (shout nicely, though. Open an issue or bug report or something).
|
||||
If you're curious to see the source code, <a href="https://github.com/xternal7/ultrawidify">github's here</a>.
|
||||
If you're looking at this page because you're bored and want to be bored some more, <a href="http://tamius.net">my website's here</a>.</p>
|
||||
<h2>So you want to help?</h2>
|
||||
<p>Alan pls add quick description.</p>
|
||||
<h2>Plans for the future</h2>
|
||||
<h2>Acknowledgements</h2>
|
||||
<p>This extension uses font <a href="http://overpassfont.org/">Overpass</a>.</p>
|
||||
<!-- <p>Special thanks to CD Project Red (The Witcher 2), Cyanide Studios (Styx, Of Orcs and Men), and Valve (CS:GO), which made it possible for me to untrigger myself after seeing so many improperly encoded videos.</p> -->
|
||||
<!-- <small><p>More or less.</p> -->
|
||||
<!-- <p>GW2 is also roughly how the avatar was obtained. Noone tell Anet, though.</p></small> -->
|
||||
<!-- <p>Special one-finger salute to all incompetent people who don't know how to properly encode videos and upload them to youtube (to word it most nicely).</p> -->
|
||||
<p>Special thanks to me for making this extension. You're welcome.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../../js/conf/Debug.js"></script>
|
||||
|
||||
<script src="../../js/lib/BrowserDetect.js"></script>
|
||||
<script src="../../js/lib/StorageManager.js"></script>
|
||||
|
||||
<script src="../../js/conf/Keybinds.js"></script>
|
||||
<script src="../../js/conf/Settings.js"></script>
|
||||
|
||||
<script src="./settings.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,134 +0,0 @@
|
||||
function showAbout(){
|
||||
clearPage();
|
||||
|
||||
document.getElementById("about").classList.remove("hide");
|
||||
document.getElementById("tab_about").classList.add("tab-selected");
|
||||
}
|
||||
function showShortcuts(){
|
||||
clearPage();
|
||||
|
||||
document.getElementById("uw_shortcuts").classList.remove("hide");
|
||||
document.getElementById("tab_shortcuts").classList.add("tab-selected");
|
||||
}
|
||||
function showGeneralSettings(){
|
||||
clearPage();
|
||||
|
||||
document.getElementById("general_settings").classList.remove("hide");
|
||||
document.getElementById("tab_general_settings").classList.add("tab-selected");
|
||||
}
|
||||
|
||||
function showSites(){
|
||||
clearPage();
|
||||
document.getElementById("uw_sites").classList.remove("hide");
|
||||
document.getElementById("tab_sites").classList.add("tab-selected");
|
||||
}
|
||||
|
||||
function clearPage(){
|
||||
// Hide you sections
|
||||
document.getElementById("uw_shortcuts").classList.add("hide");
|
||||
document.getElementById("about").classList.add("hide");
|
||||
document.getElementById("general_settings").classList.add("hide");
|
||||
document.getElementById("uw_sites").classList.add("hide");
|
||||
|
||||
// Hide you tabs
|
||||
document.getElementById("tab_shortcuts").classList.remove("tab-selected");
|
||||
document.getElementById("tab_about").classList.remove("tab-selected");
|
||||
document.getElementById("tab_general_settings").classList.remove("tab-selected");
|
||||
document.getElementById("tab_sites").classList.remove("tab-selected");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function loadKeybinds(){
|
||||
// load showShortcuts
|
||||
var keybinds = await Keybinds.fetch();
|
||||
|
||||
for(var key in keybinds){
|
||||
if(Debug.debug)
|
||||
console.log("[settings.js::loadKeybinds] we're looking at this key:", key, "it splits like this:", key.toLowerCase().split("_"));
|
||||
|
||||
var keypressArr = key.split("_");
|
||||
var opts = keybinds[key];
|
||||
|
||||
var query = "_kbd_" + opts.action + "_";
|
||||
if(opts.action == "char"){
|
||||
if(opts.targetAr == 2.39)
|
||||
query += ("219_");
|
||||
else if(opts.targetAr == 2.0)
|
||||
query += ("189_");
|
||||
else if(opts.targetAr == 1.78)
|
||||
query += ("169_");
|
||||
}
|
||||
|
||||
var q2;
|
||||
for(var modKey of Keybinds.mods){
|
||||
q2 = "#" + query + modKey;
|
||||
document.querySelector(q2).checked = keypressArr.indexOf(modKey) != -1;
|
||||
}
|
||||
q2 = "#" + query + "lettr";
|
||||
document.querySelector(q2).value = keypressArr[keypressArr.length - 1].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
async function saveKeybinds(){
|
||||
|
||||
var actions = [ "autoar", "fitw", "fith", "reset", "char_219", "char_189", "char_169" ];
|
||||
|
||||
var savedShortcuts = {};
|
||||
|
||||
for(var action of actions){
|
||||
var queryBase = "#_kbd_" + action;
|
||||
var letter = document.querySelector(queryBase + "_lettr").value.trim();
|
||||
|
||||
if(letter === "" || letter === undefined)
|
||||
continue; // we don't make a shortcut for this action
|
||||
|
||||
var shortcutKeypress = "";
|
||||
|
||||
for(mod of Keybinds.mods)
|
||||
if(document.querySelector(queryBase + "_" + mod).checked)
|
||||
shortcutKeypress += (mod + "_");
|
||||
|
||||
shortcutKeypress += letter;
|
||||
|
||||
savedShortcuts[shortcutKeypress] = {};
|
||||
|
||||
if(action.startsWith("char_")){
|
||||
savedShortcuts[shortcutKeypress].action = "char";
|
||||
|
||||
if(action == "char_219")
|
||||
savedShortcuts[shortcutKeypress].targetAr = 2.39;
|
||||
else if(action == "char_189")
|
||||
savedShortcuts[shortcutKeypress].targetAr = 2.0;
|
||||
else if(action == "char_169")
|
||||
savedShortcuts[shortcutKeypress].targetAr = 1.78;
|
||||
}
|
||||
else{
|
||||
savedShortcuts[shortcutKeypress].action = action;
|
||||
}
|
||||
}
|
||||
|
||||
// out with the old
|
||||
await StorageManager.delopt("keybinds");
|
||||
//in with the new
|
||||
StorageManager.setopt({"keybinds":savedShortcuts});
|
||||
}
|
||||
|
||||
|
||||
// page init
|
||||
// document.addEventListener("DOMContentLoaded", loadopts);
|
||||
|
||||
document.querySelector("#tab_shortcuts").addEventListener("click", showShortcuts);
|
||||
document.querySelector("#tab_about").addEventListener("click", showAbout);
|
||||
// document.querySelector("#tab_general_settings").addEventListener("click",showGeneralSettings);
|
||||
// document.querySelector("#tab_sites").addEventListener("click", showSites);
|
||||
|
||||
document.querySelector("#kb_save").addEventListener("click", saveKeybinds);
|
||||
document.querySelector("#kb_cancel").addEventListener("click", loadKeybinds);
|
||||
|
||||
// document.querySelector("#enable_autoar").addEventListener("click",saveAutoar);
|
||||
// document.querySelector("#enable_ui").addEventListener("click", saveUI);
|
||||
// document.querySelector("#enable_ui_compact").addEventListener("click", saveUI);
|
||||
|
||||
loadKeybinds();
|
53
scripts/build-zip.js
Normal file
53
scripts/build-zip.js
Normal file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const archiver = require('archiver');
|
||||
|
||||
const DEST_DIR = path.join(__dirname, '../dist');
|
||||
const DEST_ZIP_DIR = path.join(__dirname, '../dist-zip');
|
||||
|
||||
const extractExtensionData = () => {
|
||||
const extPackageJson = require('../package.json');
|
||||
|
||||
return {
|
||||
name: extPackageJson.name,
|
||||
version: extPackageJson.version
|
||||
}
|
||||
};
|
||||
|
||||
const makeDestZipDirIfNotExists = () => {
|
||||
if(!fs.existsSync(DEST_ZIP_DIR)) {
|
||||
fs.mkdirSync(DEST_ZIP_DIR);
|
||||
}
|
||||
}
|
||||
|
||||
const buildZip = (src, dist, zipFilename) => {
|
||||
console.info(`Building ${zipFilename}...`);
|
||||
|
||||
const archive = archiver('zip', { zlib: { level: 9 }});
|
||||
const stream = fs.createWriteStream(path.join(dist, zipFilename));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
archive
|
||||
.directory(src, false)
|
||||
.on('error', err => reject(err))
|
||||
.pipe(stream);
|
||||
|
||||
stream.on('close', () => resolve());
|
||||
archive.finalize();
|
||||
});
|
||||
};
|
||||
|
||||
const main = () => {
|
||||
const {name, version} = extractExtensionData();
|
||||
const zipFilename = `${name}-v${version}.zip`;
|
||||
|
||||
makeDestZipDirIfNotExists();
|
||||
|
||||
buildZip(DEST_DIR, DEST_ZIP_DIR, zipFilename)
|
||||
.then(() => console.info('OK'))
|
||||
.catch(console.err);
|
||||
};
|
||||
|
||||
main();
|
55
scripts/remove-evals.js
Normal file
55
scripts/remove-evals.js
Normal file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const BUNDLE_DIR = path.join(__dirname, '../dist');
|
||||
const bundles = [
|
||||
'background.js',
|
||||
'popup/popup.js',
|
||||
'options/options.js',
|
||||
];
|
||||
|
||||
const evalRegexForProduction = /;([a-z])=function\(\){return this}\(\);try{\1=\1\|\|Function\("return this"\)\(\)\|\|\(0,eval\)\("this"\)}catch\(t\){"object"==typeof window&&\(\1=window\)}/g;
|
||||
const evalRegexForDevelopment = /;\\r\\n\\r\\n\/\/ This works in non-strict mode(?:.){1,304}/g;
|
||||
|
||||
const removeEvals = (file) => {
|
||||
console.info(`Removing eval() from ${file}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(file, 'utf8', (err, data) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const regex = process.env.NODE_ENV === 'production' ? evalRegexForProduction : evalRegexForDevelopment;
|
||||
|
||||
if(!regex.test(data)) {
|
||||
reject(`No CSP specific code found in ${file}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
data = data.replace(regex, '=window;');
|
||||
|
||||
fs.writeFile(file, data, (err) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const main = () => {
|
||||
bundles.forEach(bundle => {
|
||||
removeEvals(path.join(BUNDLE_DIR, bundle))
|
||||
.then(() => console.info(`Bundle ${bundle}: OK`))
|
||||
.catch(console.error);
|
||||
});
|
||||
};
|
||||
|
||||
main();
|
224
src/common/components/ActionAlt.vue
Normal file
224
src/common/components/ActionAlt.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="action flex flex-column">
|
||||
|
||||
<div class="flex flex-row action-name-cmd-container">
|
||||
<div class="">
|
||||
|
||||
</div>
|
||||
<div class="flex action-name">
|
||||
<span class="icon red" @click="removeAction()">🗙</span>
|
||||
<span class="icon" @click="editAction()">🖉</span>
|
||||
{{action.name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row command-details">
|
||||
<div class="flex flex-column cmd-container">
|
||||
<div class="flex bold">
|
||||
Command:
|
||||
</div>
|
||||
<div class="flex cmdlist">
|
||||
{{parseCommand(action.cmd)}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SCOPES -->
|
||||
<div class="flex flex-column scopes-container">
|
||||
<div class="flex bold">Popup scopes:</div>
|
||||
|
||||
<!-- global scope -->
|
||||
<div v-if="action.scopes.global"
|
||||
class="flex flex-row scope-row"
|
||||
>
|
||||
<div class="flex scope-scope">
|
||||
Global:
|
||||
</div>
|
||||
<div class="flex scope-row-label scope-visible">
|
||||
Visible? <span class="scope-row-highlight">{{action.scopes.global.show ? 'Yes' : 'No'}}</span>
|
||||
</div>
|
||||
<div v-if="action.scopes.global.show"
|
||||
class="flex scope-row-label scope-button-label"
|
||||
>
|
||||
Button label?
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.global.label ? action.scopes.global.label : (action.label ? action.label : '')}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex scope-row-label">
|
||||
Keyboard shortcut:
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.global.shortcut ? parseActionShortcut(action.scopes.global.shortcut[0]) : 'None'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- site scope -->
|
||||
<div v-if="action.scopes.site"
|
||||
class="flex flex-row scope-row"
|
||||
>
|
||||
<div class="flex scope-scope">
|
||||
Site:
|
||||
</div>
|
||||
<div class="flex scope-row-label scope-visible">
|
||||
Visible? <span class="scope-row-highlight">{{action.scopes.site.show ? 'Yes' : 'No'}}</span>
|
||||
</div>
|
||||
<div v-if="action.scopes.site.show"
|
||||
class="flex scope-row-label scope-button-label"
|
||||
>
|
||||
Button label?
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.site.label ? action.scopes.site.label : (action.label ? action.label : '')}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex scope-row-label">
|
||||
Keyboard shortcut:
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.site.shortcut ? parseActionShortcut(action.scopes.site.shortcut[0]) : 'None'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- page scope -->
|
||||
<div v-if="action.scopes.page"
|
||||
class="flex flex-row scope-row"
|
||||
>
|
||||
<div class="flex scope-scope">
|
||||
Page:
|
||||
</div>
|
||||
<div class="flex scope-row-label scope-visible">
|
||||
Visible? <span class="scope-row-highlight">{{action.scopes.page.show ? 'Yes' : 'No'}}</span>
|
||||
</div>
|
||||
<div v-if="action.scopes.page.show"
|
||||
class="flex scope-row-label scope-button-label"
|
||||
>
|
||||
Button label?
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.page.label ? action.scopes.page.label : (action.label ? action.label : '')}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex scope-row-label">
|
||||
Keyboard shortcut:
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.page.shortcut ? parseActionShortcut(action.scopes.page.shortcut[0]) : 'None'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Stretch from '../enums/stretch.enum';
|
||||
import KeyboardShortcutParser from '../js/KeyboardShortcutParser';
|
||||
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
props: {
|
||||
action: Object,
|
||||
index: Number,
|
||||
},
|
||||
methods: {
|
||||
parseActionShortcut(shortcut) {
|
||||
return KeyboardShortcutParser.parseShortcut(shortcut);
|
||||
},
|
||||
parseCommand(cmd) {
|
||||
let cmdstring = '';
|
||||
for (const c of cmd) {
|
||||
cmdstring += `${c.action} ${c.arg}${c.persistent ? ' persistent' : ''}; `;
|
||||
}
|
||||
return cmdstring;
|
||||
},
|
||||
editAction() {
|
||||
this.$emit('edit');
|
||||
},
|
||||
removeAction() {
|
||||
this.$emit('remove');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../res/css/colors.scss';
|
||||
|
||||
.action {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action .command-details {
|
||||
height: 0px;
|
||||
max-height: 0px;
|
||||
transition: max-height 0.5s ease;
|
||||
overflow: hidden;
|
||||
transition: height 0.5s ease;
|
||||
}
|
||||
|
||||
.action:hover .command-details {
|
||||
height: auto;
|
||||
max-height: 200px;
|
||||
transition: max-height 0.5s ease;
|
||||
}
|
||||
|
||||
.action-name-cmd-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.action-name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 300;
|
||||
color: $text-normal;
|
||||
}
|
||||
.action-name:hover {
|
||||
color: lighten($primary-color, 20%);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: $primary-color !important;
|
||||
}
|
||||
|
||||
.cmd-container {
|
||||
width: 13.37rem;
|
||||
}
|
||||
|
||||
.cmdlist {
|
||||
font-family: 'Overpass Mono';
|
||||
font-size: 0.9rem;
|
||||
color: $text-dim;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.scope-scope {
|
||||
width: 5rem;
|
||||
text-align: right !important;
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
.scope-visible {
|
||||
width: 7rem;
|
||||
}
|
||||
|
||||
.scope-button-label {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.scope-row-label {
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
.scope-row-highlight {
|
||||
color: $text-normal;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
76
src/common/components/ActionRow.vue
Normal file
76
src/common/components/ActionRow.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<tr>
|
||||
<td class="cmd monospace">{{parseCommand(action.cmd)}}</td>
|
||||
<td class="">{{action.label ? action.label : ""}}</td>
|
||||
<td class="shortcut center-text">{{parseActionShortcut(action)}}</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': (action.shortcut && action.shortcut.length &&
|
||||
(action.shortcut[0].onMouseMove || action.shortcut[0].onClick ||
|
||||
action.shortcut[0].onScroll))}"
|
||||
></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': action.popup_global}"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': action.popup_site}"
|
||||
></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': action.popup}"
|
||||
></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': action.ui}"
|
||||
></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{action.ui_path ? action.ui_path : ""}}</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Stretch from '../enums/stretch.enum';
|
||||
import KeyboardShortcutParser from '../js/KeyboardShortcutParser'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
props: {
|
||||
action: Object
|
||||
},
|
||||
methods: {
|
||||
parseActionShortcut(action) {
|
||||
return KeyboardShortcutParser.parseShortcut(action.shortcut[0]);
|
||||
},
|
||||
parseCommand(cmd) {
|
||||
let cmdstring = '';
|
||||
for(const c of cmd) {
|
||||
cmdstring += `${c.action} ${c.arg}${c.persistent ? ' persistent' : ''}; `;
|
||||
}
|
||||
return cmdstring;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
17
src/common/components/Button.vue
Normal file
17
src/common/components/Button.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="button center-text flex"
|
||||
:class="{'setting-selected': selected, 'w24': fixedWidth, 'flex-auto': !fixedWidth }"
|
||||
>
|
||||
{{label}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
fixedWidth: Boolean,
|
||||
selected: Boolean,
|
||||
label: String,
|
||||
}
|
||||
}
|
||||
</script>
|
30
src/common/components/ShortcutButton.vue
Normal file
30
src/common/components/ShortcutButton.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="flex flex-column flex-center">
|
||||
<div class="flex flex-self-center">
|
||||
{{label}}
|
||||
</div>
|
||||
<div class="flex dark flex-self-center">
|
||||
<small>
|
||||
{{shortcut ? `(${shortcut})` : ''}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
label: String,
|
||||
shortcut: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.center-text {
|
||||
text-align: center;
|
||||
}
|
||||
.dark {
|
||||
opacity: 50%;
|
||||
}
|
||||
</style>
|
7
src/common/enums/anti-gradient-mode.enum.js
Normal file
7
src/common/enums/anti-gradient-mode.enum.js
Normal file
@ -0,0 +1,7 @@
|
||||
var AntiGradientMode = Object.freeze({
|
||||
Disabled: 0,
|
||||
Lax: 1,
|
||||
Strict: 2
|
||||
});
|
||||
|
||||
export default AntiGradientMode;
|
10
src/common/enums/aspect-ratio.enum.js
Normal file
10
src/common/enums/aspect-ratio.enum.js
Normal file
@ -0,0 +1,10 @@
|
||||
var AspectRatio = Object.freeze({
|
||||
Initial: -1,
|
||||
Reset: 0,
|
||||
Automatic: 1,
|
||||
FitWidth: 2,
|
||||
FitHeight: 3,
|
||||
Fixed: 4,
|
||||
});
|
||||
|
||||
export default AspectRatio;
|
10
src/common/enums/extension-mode.enum.js
Normal file
10
src/common/enums/extension-mode.enum.js
Normal file
@ -0,0 +1,10 @@
|
||||
var ExtensionMode = Object.freeze({
|
||||
AutoDisabled: -2,
|
||||
Disabled: -1,
|
||||
Default: 0,
|
||||
Whitelist: 1,
|
||||
Basic: 2,
|
||||
Enabled: 3,
|
||||
});
|
||||
|
||||
export default ExtensionMode;
|
9
src/common/enums/stretch.enum.js
Normal file
9
src/common/enums/stretch.enum.js
Normal file
@ -0,0 +1,9 @@
|
||||
var Stretch = Object.freeze({
|
||||
NoStretch: 0,
|
||||
Basic: 1,
|
||||
Hybrid: 2,
|
||||
Conditional: 3,
|
||||
Default: -1
|
||||
});
|
||||
|
||||
export default Stretch;
|
8
src/common/enums/video-alignment.enum.js
Normal file
8
src/common/enums/video-alignment.enum.js
Normal file
@ -0,0 +1,8 @@
|
||||
var VideoAlignment = Object.freeze({
|
||||
Left: 0,
|
||||
Center: 1,
|
||||
Right: 2,
|
||||
Default: -1
|
||||
});
|
||||
|
||||
export default VideoAlignment;
|
26
src/common/js/KeyboardShortcutParser.js
Normal file
26
src/common/js/KeyboardShortcutParser.js
Normal file
@ -0,0 +1,26 @@
|
||||
class KeyboardShortcutParser {
|
||||
static parseShortcut(keypress) {
|
||||
var shortcutCombo = '';
|
||||
|
||||
if (keypress.ctrlKey) {
|
||||
shortcutCombo += 'Ctrl + ';
|
||||
}
|
||||
if (keypress.shiftKey) {
|
||||
shortcutCombo += 'Shift + ';
|
||||
}
|
||||
if (keypress.metaKey) {
|
||||
shortcutCombo += 'Meta + ';
|
||||
}
|
||||
if (keypress.altKey) {
|
||||
shortcutCombo += 'Alt + ';
|
||||
}
|
||||
if (keypress.key) {
|
||||
shortcutCombo += keypress.key.toUpperCase();
|
||||
} else {
|
||||
shortcutCombo += '<mouse action>'
|
||||
}
|
||||
return shortcutCombo;
|
||||
}
|
||||
}
|
||||
|
||||
export default KeyboardShortcutParser;
|
17
src/common/misc/Donate.vue
Normal file
17
src/common/misc/Donate.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<p><i><a href="https://www.youtube.com/watch?v=Mn3YEJTSYs8&feature=youtu.be&t=770" target='_blank'>...</a> but as is normal, you can't use a free extensions without the developers begging for money. It's the bi-daily beggathon here on ... wherever here is.</i>
|
||||
</p>
|
||||
<p>
|
||||
Jokes and references few will get aside, developing this extension does take a decent amount of time, motivation, carefully calibrated quantities
|
||||
of alcohol and enough coffee to bankrupt a small nation.
|
||||
</p>
|
||||
<p>If you want to buy me a beer or bankroll my caffeine addiction, you can do so by <a href="https://paypal.me/tamius">clicking here</a>. All donations are appreciated.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
28
src/common/mixins/ComputeActionsMixin.js
Normal file
28
src/common/mixins/ComputeActionsMixin.js
Normal file
@ -0,0 +1,28 @@
|
||||
export default {
|
||||
computed: {
|
||||
scopeActions: function() {
|
||||
return this.settings.active.actions.filter(x => x.scopes[this.scope] && x.scopes[this.scope].show) || [];
|
||||
},
|
||||
extensionActions: function(){
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-extension-mode') || [];
|
||||
},
|
||||
aardActions: function(){
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-autoar-mode') || [];
|
||||
},
|
||||
aspectRatioActions: function(){
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-ar') || [];
|
||||
},
|
||||
stretchActions: function(){
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-stretch') || [];
|
||||
},
|
||||
keyboardActions: function() {
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-keyboard') || [];
|
||||
},
|
||||
alignmentActions: function() {
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-alignment') || [];
|
||||
},
|
||||
otherActions: function() {
|
||||
return this.scopeActions.filter(x => x.cmd.length > 1) || [];
|
||||
}
|
||||
}
|
||||
}
|
212
src/ext/conf/ActionList.js
Normal file
212
src/ext/conf/ActionList.js
Normal file
@ -0,0 +1,212 @@
|
||||
import VideoAlignment from '../../common/enums/video-alignment.enum';
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
||||
import AspectRatio from '../../common/enums/aspect-ratio.enum';
|
||||
|
||||
var ActionList = {
|
||||
'set-ar': {
|
||||
name: 'Set aspect ratio',
|
||||
args: [{
|
||||
name: 'Automatic',
|
||||
arg: AspectRatio.Automatic,
|
||||
},{
|
||||
name: 'Fit width',
|
||||
arg: AspectRatio.FitWidth,
|
||||
},{
|
||||
name: 'Fit height',
|
||||
arg: AspectRatio.FitHeight,
|
||||
},{
|
||||
name: 'Reset',
|
||||
arg: AspectRatio.Reset,
|
||||
},{
|
||||
name: 'Manually specify ratio',
|
||||
arg: AspectRatio.Fixed,
|
||||
customArg: true,
|
||||
hintHTML: '',
|
||||
}],
|
||||
scopes: {
|
||||
global: false,
|
||||
site: false,
|
||||
page: true,
|
||||
}
|
||||
},
|
||||
'set-stretch': {
|
||||
name: 'Set stretch',
|
||||
args: [{
|
||||
name: 'Normal',
|
||||
arg: Stretch.NoStretch
|
||||
},{
|
||||
name: 'Basic',
|
||||
arg: Stretch.Basic,
|
||||
},{
|
||||
name: 'Hybrid',
|
||||
arg: Stretch.Hybrid,
|
||||
},{
|
||||
name: 'Thin borders',
|
||||
arg: Stretch.Conditional,
|
||||
},{
|
||||
name: 'Default',
|
||||
arg: Stretch.Default,
|
||||
scopes: {
|
||||
site: true
|
||||
}
|
||||
}],
|
||||
scopes: {
|
||||
global: true,
|
||||
site: true,
|
||||
page: true,
|
||||
}
|
||||
},
|
||||
'set-alignment': {
|
||||
name: 'Set video alignment',
|
||||
args: [{
|
||||
name: 'Left',
|
||||
arg: VideoAlignment.Left,
|
||||
},{
|
||||
name: 'Center',
|
||||
arg: VideoAlignment.Center,
|
||||
},{
|
||||
name: 'Right',
|
||||
arg: VideoAlignment.Right
|
||||
},{
|
||||
name: 'Default',
|
||||
arg: VideoAlignment.Default,
|
||||
scopes: {
|
||||
site: true,
|
||||
}
|
||||
}],
|
||||
scopes: {
|
||||
global: true,
|
||||
site: true,
|
||||
page: true,
|
||||
}
|
||||
},
|
||||
'pan': {
|
||||
name: 'Pan',
|
||||
args: [{
|
||||
name: '',
|
||||
arg: 'toggle'
|
||||
}],
|
||||
scopes: {
|
||||
page: true,
|
||||
}
|
||||
},
|
||||
'toggle-pan': {
|
||||
name: 'Toggle panning mode',
|
||||
args: [{
|
||||
name: 'Toggle',
|
||||
arg: 'toggle',
|
||||
},{
|
||||
name: 'Enable',
|
||||
arg: 'enable',
|
||||
},{
|
||||
name: 'Disable',
|
||||
arg: 'disable'
|
||||
}],
|
||||
scopes: {
|
||||
page: true
|
||||
}
|
||||
},
|
||||
'change-zoom': {
|
||||
name: 'Zoom',
|
||||
args: [{
|
||||
name: 'Zoom level increase/decrease',
|
||||
customArg: true,
|
||||
hintHTML: '<small>Positive values zoom in, negative values zoom out. Increases/decreases are logarithmic: value of \'1\' will double the zoom, value of \'-1\' will halve it.</small>'
|
||||
}],
|
||||
scopes: {
|
||||
page: true,
|
||||
}
|
||||
},
|
||||
'set-zoom': {
|
||||
name: 'Set zoom level',
|
||||
args: [{
|
||||
name: 'Zoom level increase/decrease',
|
||||
customArg: true,
|
||||
hintHTML: '<small>Examples: 0.5 sets zoom to 50%, 1 sets zoom to 100%, 2 sets zoom to 200%. Don\'t use negative values unless you want to experience Australian youtube.</small>'
|
||||
}],
|
||||
scopes: {
|
||||
page: true,
|
||||
}
|
||||
},
|
||||
'set-extension-mode': {
|
||||
name: 'Set extension mode',
|
||||
args: [{
|
||||
name: 'Enable',
|
||||
arg: ExtensionMode.Enabled,
|
||||
},{
|
||||
name: 'On whitelisted only',
|
||||
arg: ExtensionMode.Whitelist,
|
||||
scopes: {
|
||||
global: true,
|
||||
}
|
||||
},{
|
||||
name: 'Default',
|
||||
arg: ExtensionMode.Default,
|
||||
scopes: {
|
||||
page: true,
|
||||
}
|
||||
},{
|
||||
name: 'Disable',
|
||||
arg: ExtensionMode.Default
|
||||
}],
|
||||
scopes: {
|
||||
global: true,
|
||||
site: true,
|
||||
}
|
||||
},
|
||||
'set-autoar-mode': {
|
||||
name: 'Set automatic aspect ratio detection mode',
|
||||
args: [{
|
||||
name: 'Enable',
|
||||
arg: ExtensionMode.Enabled,
|
||||
},{
|
||||
name: 'On whitelisted only',
|
||||
arg: ExtensionMode.Whitelist,
|
||||
scopes: {
|
||||
global: true,
|
||||
}
|
||||
},{
|
||||
name: 'Default',
|
||||
arg: ExtensionMode.Default,
|
||||
scopes: {
|
||||
page: true,
|
||||
}
|
||||
},{
|
||||
name: 'Disable',
|
||||
arg: ExtensionMode.Disabled
|
||||
}],
|
||||
scopes: {
|
||||
global: true,
|
||||
site: true,
|
||||
}
|
||||
},
|
||||
'set-keyboard': {
|
||||
name: 'Keyboard shortcuts',
|
||||
args: [{
|
||||
name: 'Enable',
|
||||
arg: ExtensionMode.Enabled,
|
||||
},{
|
||||
name: 'On whitelisted only',
|
||||
arg: ExtensionMode.Whitelist,
|
||||
scopes: {
|
||||
global: true,
|
||||
}
|
||||
},{
|
||||
name: 'Default',
|
||||
arg: ExtensionMode.Default,
|
||||
scopes: {
|
||||
page: true,
|
||||
}
|
||||
},{
|
||||
name: 'Disable',
|
||||
arg: ExtensionMode.Disabled
|
||||
}],
|
||||
scopes: {
|
||||
global: true,
|
||||
site: true,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default ActionList;
|
13
src/ext/conf/BrowserDetect.js
Normal file
13
src/ext/conf/BrowserDetect.js
Normal file
@ -0,0 +1,13 @@
|
||||
import Debug from './Debug.js';
|
||||
|
||||
const BrowserDetect = {
|
||||
firefox: process.env.BROWSER === 'firefox',
|
||||
chrome: process.env.BROWSER === 'chrome',
|
||||
edge: process.env.BROWSER === 'edge',
|
||||
}
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("Loading: BrowserDetect.js\n\nprocess.env.BROWSER:", process.env.BROWSER, "Exporting BrowserDetect:", BrowserDetect);
|
||||
}
|
||||
|
||||
export default BrowserDetect;
|
61
src/ext/conf/Debug.js
Normal file
61
src/ext/conf/Debug.js
Normal file
@ -0,0 +1,61 @@
|
||||
// Set prod to true when releasing
|
||||
const _prod = true;
|
||||
// const _prod = false;
|
||||
|
||||
var Debug = {
|
||||
// performanceMetrics: true, // should not be affected by debug.debug in order to allow benchmarking of the impact logging in console has
|
||||
// init: true,
|
||||
// debug: true,
|
||||
// debug: false,
|
||||
// keyboard: true,
|
||||
// debugResizer: true,
|
||||
// debugArDetect: true,
|
||||
// scaler: true,
|
||||
// debugStorage: false,
|
||||
// debugStorage: true,
|
||||
// comms: false,
|
||||
// comms: true,
|
||||
// showArDetectCanvas: true,
|
||||
// flushStoredSettings: true,
|
||||
// flushStoredSettings: false,
|
||||
// playerDetectDebug: true,
|
||||
// periodic: true,
|
||||
// videoRescan: true,
|
||||
// mousemove: true,
|
||||
// arDetect: {
|
||||
// edgeDetect: true
|
||||
// },
|
||||
// canvas: {
|
||||
// debugDetection: true
|
||||
// },
|
||||
debugCanvas: {
|
||||
// enabled: true,
|
||||
// guardLine: true
|
||||
// enabled: false,
|
||||
// guardLine: false
|
||||
}
|
||||
}
|
||||
|
||||
if(_prod){
|
||||
__disableAllDebug(Debug);
|
||||
}
|
||||
|
||||
function __disableAllDebug(obj) {
|
||||
for(let key in obj) {
|
||||
if (obj.hasOwnProperty(key) ){
|
||||
if(obj[key] instanceof Object) {
|
||||
__disableAllDebug(obj[key]);
|
||||
}
|
||||
else {
|
||||
obj[key] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("Guess we're debugging ultrawidify then. Debug.js must always load first, and others must follow.\nLoading: Debug.js");
|
||||
|
||||
|
||||
|
||||
export default Debug;
|
925
src/ext/conf/ExtensionConf.js
Normal file
925
src/ext/conf/ExtensionConf.js
Normal file
@ -0,0 +1,925 @@
|
||||
import Debug from './Debug';
|
||||
import currentBrowser from './BrowserDetect';
|
||||
import VideoAlignment from '../../common/enums/video-alignment.enum';
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
||||
import AntiGradientMode from '../../common/enums/anti-gradient-mode.enum';
|
||||
import AspectRatio from '../../common/enums/aspect-ratio.enum';
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("Loading: ExtensionConf.js");
|
||||
|
||||
var ExtensionConf = {
|
||||
arDetect: {
|
||||
disabledReason: "", // if automatic aspect ratio has been disabled, show reason
|
||||
allowedMisaligned: 0.05, // top and bottom letterbox thickness can differ by this much.
|
||||
// Any more and we don't adjust ar.
|
||||
allowedArVariance: 0.075, // amount by which old ar can differ from the new (1 = 100%)
|
||||
timers: { // autodetection frequency
|
||||
playing: 333, // while playing
|
||||
paused: 3000, // while paused
|
||||
error: 3000, // after error
|
||||
minimumTimeout: 5,
|
||||
tickrate: 10, // 1 tick every this many milliseconds
|
||||
},
|
||||
autoDisable: { // settings for automatically disabling the extension
|
||||
maxExecutionTime: 6000, // if execution time of main autodetect loop exceeds this many milliseconds,
|
||||
// we disable it.
|
||||
consecutiveTimeoutCount: 5, // we only do it if it happens this many consecutive times
|
||||
|
||||
// FOR FUTURE USE
|
||||
consecutiveArResets: 5 // if aspect ratio reverts immediately after AR change is applied, we disable everything
|
||||
},
|
||||
canvasDimensions: {
|
||||
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
|
||||
// it's not used to detect aspect ratio by itself, so it can be tiny af
|
||||
width: 16,
|
||||
height: 9,
|
||||
},
|
||||
sampleCanvas: { // size of image sample for detecting aspect ratio. Bigger size means more accurate results,
|
||||
// at the expense of performance
|
||||
width: 640,
|
||||
height: 360,
|
||||
},
|
||||
},
|
||||
|
||||
// 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
|
||||
// cummulative treshold.
|
||||
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, // treshold, 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 neighbouring 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
|
||||
},
|
||||
sampling: {
|
||||
staticCols: 9, // we take a column at [0-n]/n-th parts along the width and sample it
|
||||
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
|
||||
},
|
||||
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
|
||||
},
|
||||
fallbackMode: {
|
||||
enabled: true,
|
||||
safetyBorderPx: 5, // determines the thickness of safety border in fallback mode
|
||||
noTriggerZonePx: 8 // if we detect edge less than this many pixels thick, we don't correct.
|
||||
},
|
||||
arSwitchLimiter: { // to be implemented
|
||||
switches: 2, // we can switch this many times
|
||||
period: 2.0 // per this period
|
||||
},
|
||||
edgeDetection: {
|
||||
sampleWidth: 8, // we take a sample this wide for edge detection
|
||||
detectionThreshold: 4, // sample needs to have this many non-black pixels to be a valid edge
|
||||
confirmationThreshold: 1, //
|
||||
singleSideConfirmationThreshold: 3, // we need this much edges (out of all samples, not just edges) in order
|
||||
// to confirm an edge in case there's no edges on top or bottom (other
|
||||
// than logo, of course)
|
||||
logoThreshold: 0.15, // if edge candidate sits with count greater than this*all_samples, it can't be logo
|
||||
// or watermark.
|
||||
edgeTolerancePx: 1, // we check for black edge violation this far from detection point
|
||||
edgeTolerancePercent: null, // we check for black edge detection this % of height from detection point. unused
|
||||
middleIgnoredArea: 0.2, // we ignore this % of canvas height towards edges while detecting aspect ratios
|
||||
minColsForSearch: 0.5, // if we hit the edge of blackbars for all but this many columns (%-wise), we don't
|
||||
// continue with search. It's pointless, because black edge is higher/lower than we
|
||||
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
|
||||
},
|
||||
pillarTest: {
|
||||
ignoreThinPillarsPx: 5, // ignore pillars that are less than this many pixels thick.
|
||||
allowMisaligned: 0.05 // left and right edge can vary this much (%)
|
||||
},
|
||||
textLineTest: {
|
||||
nonTextPulse: 0.10, // if a single continuous pulse has this many non-black pixels, we aren't dealing
|
||||
// with text. This value is relative to canvas width (%)
|
||||
pulsesToConfirm: 10, // this is a threshold to confirm we're seeing text.
|
||||
pulsesToConfirmIfHalfBlack: 5, // this is the threshold to confirm we're seeing text if longest black pulse
|
||||
// is over 50% of the canvas width
|
||||
testRowOffset: 0.02 // we test this % of height from detected edge
|
||||
}
|
||||
},
|
||||
zoom: {
|
||||
minLogZoom: -1,
|
||||
maxLogZoom: 3,
|
||||
announceDebounce: 200 // we wait this long before announcing new zoom
|
||||
},
|
||||
miscSettings: {
|
||||
mousePan: {
|
||||
enabled: false
|
||||
},
|
||||
mousePanReverseMouse: false,
|
||||
},
|
||||
stretch: {
|
||||
conditionalDifferencePercent: 0.05 // black bars less than this wide will trigger stretch
|
||||
// if mode is set to '1'. 1.0=100%
|
||||
},
|
||||
resizer: {
|
||||
setStyleString: {
|
||||
maxRetries: 3,
|
||||
retryTimeout: 200
|
||||
}
|
||||
},
|
||||
pageInfo: {
|
||||
timeouts: {
|
||||
urlCheck: 200,
|
||||
rescan: 1500
|
||||
}
|
||||
},
|
||||
// -----------------------------------------
|
||||
// ::: ACTIONS :::
|
||||
// -----------------------------------------
|
||||
// Nastavitve za ukaze. Zamenja stare nastavitve za bližnične tipke.
|
||||
//
|
||||
// Polje 'shortcut' je tabela, če se slučajno lotimo kdaj delati choordov.
|
||||
actions: [{
|
||||
name: 'Trigger automatic detection', // name displayed in settings
|
||||
label: 'Automatic', // name displayed in ui (can be overriden in scope/playerUi)
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Automatic,
|
||||
persistent: false, // optional, false by default. If true, change doesn't take effect immediately.
|
||||
// Instead, this action saves stuff to settings
|
||||
}],
|
||||
scopes: {
|
||||
global: { // if 'global' is undefined, 'show' is presumed to be 'false'
|
||||
show: false,
|
||||
},
|
||||
site: {
|
||||
show: false,
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
label: 'Automatic', // example override, takes precedence over default label
|
||||
shortcut: [{
|
||||
key: 'a',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}],
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'crop',
|
||||
},
|
||||
},{
|
||||
name: 'Reset to default',
|
||||
label: 'Reset',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Reset,
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: true,
|
||||
shortcut: [{
|
||||
key: 'r',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}],
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'crop'
|
||||
},
|
||||
},{
|
||||
name: 'Fit to width',
|
||||
label: 'Fit width',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.FitWidth,
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: true,
|
||||
shortcut: [{
|
||||
key: 'w',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}],
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'crop'
|
||||
}
|
||||
},{
|
||||
name: 'Fit to height',
|
||||
label: 'Fit height',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.FitHeight
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: true,
|
||||
shortcut: [{
|
||||
key: 'e',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}]
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'crop'
|
||||
}
|
||||
},{
|
||||
name: 'Set aspect ratio to 16:9',
|
||||
label: '16:9',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Fixed,
|
||||
customArg: 1.78,
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: true,
|
||||
shortcut: [{
|
||||
key: 's',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: false,
|
||||
onKeyDown: true,
|
||||
}],
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'crop'
|
||||
}
|
||||
},{
|
||||
name: 'Set aspect ratio to 21:9 (2.39:1)',
|
||||
label: '21:9',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Fixed,
|
||||
customArg: 2.39
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: true,
|
||||
shortcut: [{
|
||||
key: 'd',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: false,
|
||||
onKeyDown: true,
|
||||
}]
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'crop'
|
||||
}
|
||||
},{
|
||||
name: 'Set aspect ratio to 18:9',
|
||||
label: '18:9',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Fixed,
|
||||
customArg: 2.0,
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: true,
|
||||
shortcut: [{
|
||||
key: 'x',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}]
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'crop',
|
||||
}
|
||||
},{
|
||||
name: 'Zoom in',
|
||||
label: 'Zoom',
|
||||
cmd: [{
|
||||
action: 'change-zoom',
|
||||
arg: 0.1
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: false,
|
||||
shortcut: [{
|
||||
key: 'z',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}],
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: false,
|
||||
}
|
||||
},{
|
||||
name: 'Zoom out',
|
||||
label: 'Unzoom',
|
||||
cmd: [{
|
||||
action: 'change-zoom',
|
||||
arg: -0.1
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: false,
|
||||
shortcut: [{
|
||||
key: 'u',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}],
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: false
|
||||
}
|
||||
},{
|
||||
name: 'Toggle panning mode',
|
||||
label: 'Toggle pan',
|
||||
cmd: [{
|
||||
action: 'toggle-pan',
|
||||
arg: 'toggle'
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: true,
|
||||
shortcut: [{
|
||||
key: 'p',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}]
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'zoom'
|
||||
}
|
||||
},{
|
||||
name: 'Hold to pan',
|
||||
cmd: [{
|
||||
action: 'pan',
|
||||
arg: 'toggle',
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
show: false,
|
||||
shortcut: [{
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
onKeyDown: false,
|
||||
onKeyUp: false,
|
||||
onMouseMove: true,
|
||||
}],
|
||||
}
|
||||
}
|
||||
},
|
||||
//
|
||||
// S T R E T C H I N G
|
||||
//
|
||||
{
|
||||
name: 'Set stretch to "none"',
|
||||
label: 'Don\'t stretch',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.NoStretch,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
label: 'Normal'
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
label: 'Normal'
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
label: 'Normal'
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'stretch'
|
||||
}
|
||||
},{
|
||||
name: 'Set stretch to "basic"',
|
||||
label: 'Basic stretch',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.Basic,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
label: 'Basic'
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
label: 'Basic'
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
label: 'Basic'
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'stretch'
|
||||
}
|
||||
},{
|
||||
name: 'Set stretch to "hybrid"',
|
||||
label: 'Hybrid stretch',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.Hybrid,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
label: 'Hybrid'
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
label: 'Hybrid'
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
label: 'Hybrid'
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'stretch'
|
||||
}
|
||||
},{
|
||||
name: 'Stretch only to hide thin borders',
|
||||
label: 'Thin borders only',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.Conditional,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
label: 'Thin borders'
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
label: 'Thin borders'
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
label: 'Thin borders'
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'stretch'
|
||||
}
|
||||
},{
|
||||
name: 'Set stretch to default value',
|
||||
label: 'Default',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.Default,
|
||||
}],
|
||||
scopes: {
|
||||
site: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
//
|
||||
// A L I G N M E N T
|
||||
//
|
||||
{
|
||||
name: 'Align video to the left',
|
||||
label: 'Left',
|
||||
cmd: [{
|
||||
action: 'set-alignment',
|
||||
arg: VideoAlignment.Left,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'align'
|
||||
}
|
||||
},{
|
||||
name: 'Align video to center',
|
||||
label: 'Center',
|
||||
cmd: [{
|
||||
action: 'set-alignment',
|
||||
arg: VideoAlignment.Center,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'align'
|
||||
}
|
||||
},{
|
||||
name: 'Align video to the right',
|
||||
label: 'Right',
|
||||
cmd: [{
|
||||
action: 'set-alignment',
|
||||
arg: VideoAlignment.Right
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
}
|
||||
},
|
||||
playerUi: {
|
||||
show: true,
|
||||
path: 'align'
|
||||
}
|
||||
},{
|
||||
name: 'Use default alignment',
|
||||
label: 'Default',
|
||||
cmd: [{
|
||||
action: 'set-alignment',
|
||||
arg: VideoAlignment.Default
|
||||
}],
|
||||
scopes: {
|
||||
site: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
//
|
||||
// E N A B L E E X T E N S I O N / A U T O A R
|
||||
// (for sites/extension tab in the popup)
|
||||
//
|
||||
{
|
||||
name: 'Enable extension',
|
||||
label: 'Enable',
|
||||
cmd: [{
|
||||
action: 'set-extension-mode',
|
||||
arg: ExtensionMode.Enabled,
|
||||
persistent: true,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Enable extension on whitelisted sites only',
|
||||
label: 'On whitelist only',
|
||||
cmd: [{
|
||||
action: 'set-extension-mode',
|
||||
arg: ExtensionMode.Whitelist,
|
||||
persistent: true,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Extension mode: use default settings',
|
||||
label: 'Default',
|
||||
cmd: [{
|
||||
action: 'set-extension-mode',
|
||||
arg: ExtensionMode.Default,
|
||||
persistent: true,
|
||||
}],
|
||||
scopes: {
|
||||
site: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Disable extension',
|
||||
label: 'Disable',
|
||||
cmd: [{
|
||||
action: 'set-extension-mode',
|
||||
arg: ExtensionMode.Disabled,
|
||||
persistent: true,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Enable automatic aspect ratio detection',
|
||||
label: 'Enable',
|
||||
cmd: [{
|
||||
action: 'set-autoar-mode',
|
||||
arg: ExtensionMode.Enabled,
|
||||
persistent: true,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true
|
||||
},
|
||||
site: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Enable automatic aspect ratio detection on whitelisted sites only',
|
||||
label: 'On whitelist only',
|
||||
cmd: [{
|
||||
action: 'set-autoar-mode',
|
||||
arg: ExtensionMode.Whitelist,
|
||||
persistent: true,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Use default settings for automatic aspect ratio detection',
|
||||
label: 'Default',
|
||||
cmd: [{
|
||||
action: 'set-autoar-mode',
|
||||
arg: ExtensionMode.Default,
|
||||
persistent: true,
|
||||
}],
|
||||
scopes: {
|
||||
site: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Disable automatic aspect ratio detection',
|
||||
label: 'Disable',
|
||||
cmd: [{
|
||||
action: 'set-autoar-mode',
|
||||
arg: ExtensionMode.Disabled,
|
||||
persistent: true,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
//
|
||||
//
|
||||
// Enable/disable keyboard shortcuts
|
||||
//
|
||||
{
|
||||
name: 'Enable keyboard shortcuts',
|
||||
label: 'Enable',
|
||||
cmd: [{
|
||||
action: 'set-keyboard',
|
||||
arg: ExtensionMode.Enabled,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Enable keyboard shortcuts on whitelisted sites only',
|
||||
label: 'On whitelist only',
|
||||
cmd: [{
|
||||
action: 'set-keyboard',
|
||||
arg: ExtensionMode.Whitelist,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true
|
||||
},
|
||||
}
|
||||
},{
|
||||
name: 'Keyboard shortcuts mode: use default settings',
|
||||
label: 'Default',
|
||||
cmd: [{
|
||||
action: 'set-keyboard',
|
||||
arg: ExtensionMode.Default,
|
||||
}],
|
||||
scopes: {
|
||||
site: {
|
||||
show: true
|
||||
}
|
||||
}
|
||||
},{
|
||||
name: 'Disable keyboard shortcuts',
|
||||
label: 'Disable',
|
||||
cmd: [{
|
||||
action: 'set-keyboard',
|
||||
arg: ExtensionMode.Disabled,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
show: true,
|
||||
},
|
||||
site: {
|
||||
show: true,
|
||||
},
|
||||
page: {
|
||||
show: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
// -----------------------------------------
|
||||
// ::: SITE CONFIGURATION :::
|
||||
// -----------------------------------------
|
||||
// Nastavitve za posamezno stran
|
||||
// Config for a given page:
|
||||
//
|
||||
// <hostname> : {
|
||||
// status: <option> // should extension work on this site?
|
||||
// arStatus: <option> // should we do autodetection on this site?
|
||||
//
|
||||
// defaultAr?: <ratio> // automatically apply this aspect ratio on this side. Use extension defaults if undefined.
|
||||
// stretch? <stretch mode> // automatically stretch video on this site in this manner
|
||||
// videoAlignment? <left|center|right>
|
||||
//
|
||||
// type: <official|community|user> // 'official' — blessed by Tam.
|
||||
// // 'community' — blessed by reddit.
|
||||
// // 'user' — user-defined (not here)
|
||||
// override: <true|false> // override user settings for this site on update
|
||||
// }
|
||||
//
|
||||
// Veljavne vrednosti za možnosti
|
||||
// Valid values for options:
|
||||
//
|
||||
// status, arStatus, statusEmbedded:
|
||||
//
|
||||
// * enabled — always allow, full
|
||||
// * basic — allow, but only the basic version without playerData
|
||||
// * default — allow if default is to allow, block if default is to block
|
||||
// * disabled — never allow
|
||||
//
|
||||
sites: {
|
||||
"@global": { // global defaults. Possible options will state site-only options in order
|
||||
// to avoid writing this multiple times. Tags:
|
||||
// #g — only available in @global
|
||||
// #s — only available for specific site
|
||||
mode: ExtensionMode.Enabled, // How should extension work:
|
||||
// 'enabled' - work everywhere except blacklist
|
||||
// 'whitelist' - only work on whitelisted sites (#g)
|
||||
// 'disabled' - work nowhere
|
||||
// 'basic' - (Possible future use)
|
||||
// 'default' - follow global rules (#s)
|
||||
autoar: ExtensionMode.Enabled, // Should we try to automatically detect aspect ratio?
|
||||
// Options: 'enabled', 'whitelist' (#g), 'default' (#s), 'disabled'
|
||||
autoarFallback: currentBrowser.firefox ? // if autoAr fails, try fallback mode?
|
||||
ExtensionMode.Enabled : // Options same as in autoar.
|
||||
ExtensionMode.Disabled, // if autoar is disabled, this setting is irrelevant
|
||||
stretch: Stretch.NoStretch, // Default stretch mode.
|
||||
videoAlignment: VideoAlignment.Center, // Video alignment
|
||||
keyboardShortcutsEnabled: ExtensionMode.Enabled,
|
||||
},
|
||||
"www.youtube.com" : {
|
||||
mode: ExtensionMode.Enabled,
|
||||
autoar: ExtensionMode.Enabled,
|
||||
autoarFallback: ExtensionMode.Enabled,
|
||||
override: false, // ignore value localStorage in favour of this
|
||||
type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined')
|
||||
actions: null, // overrides global keyboard shortcuts and button configs. Is array, is optional.
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
},
|
||||
"www.netflix.com" : {
|
||||
mode: ExtensionMode.Enabled,
|
||||
autoar: currentBrowser.firefox ? ExtensionMode.Enabled : ExtensionMode.Disabled,
|
||||
override: false,
|
||||
type: 'official',
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
autoarPreventConditions: { // prevents autoar on following conditions
|
||||
videoStyleString: { // if video style string thing does anything of what follows
|
||||
containsProperty: { // if video style string has any of these properties (listed as keys)
|
||||
'height': { // if 'height' property is present in style attribute, we prevent autoar from running
|
||||
allowedValues: [ // unless attribute is equal to anything in here. Optional.
|
||||
'100%'
|
||||
]
|
||||
}
|
||||
// 'width': true // this would prevent aard from runing if <video> had a 'width' property in style, regardless of value
|
||||
// could also be an empty object, in theory.
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default ExtensionConf;
|
285
src/ext/lib/ActionHandler.js
Normal file
285
src/ext/lib/ActionHandler.js
Normal file
@ -0,0 +1,285 @@
|
||||
import Debug from '../conf/Debug';
|
||||
import PlayerData from './video-data/PlayerData';
|
||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
||||
|
||||
class ActionHandler {
|
||||
|
||||
constructor(pageInfo) {
|
||||
this.pageInfo = pageInfo;
|
||||
this.settings = pageInfo.settings;
|
||||
|
||||
this.inputs = ['input', 'select', 'button', 'textarea'];
|
||||
this.keyboardLocalDisabled = false;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (Debug.debug) {
|
||||
console.log("[ActionHandler::init] starting init");
|
||||
}
|
||||
|
||||
this.keyUpActions = [];
|
||||
this.keyDownActions = [];
|
||||
this.mouseMoveActions = [];
|
||||
this.mouseScrollUpActions = [];
|
||||
this.mouseScrollDownActions = [];
|
||||
this.mouseEnterActions = [];
|
||||
this.mouseLeaveActions = [];
|
||||
|
||||
var ths = this;
|
||||
|
||||
var actions;
|
||||
try {
|
||||
if (this.settings.active.sites[window.location.host].actions) {
|
||||
actions = this.settings.active.sites[window.location.host].actions;
|
||||
} else {
|
||||
actions = this.settings.active.actions;
|
||||
}
|
||||
} catch (e) {
|
||||
actions = this.settings.active.actions;
|
||||
}
|
||||
|
||||
|
||||
for (var action of actions) {
|
||||
if (!action.scopes) {
|
||||
continue;
|
||||
}
|
||||
for (var scope in action.scopes) {
|
||||
if (! action.scopes[scope].shortcut) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var shortcut = action.scopes[scope].shortcut[0];
|
||||
if (shortcut.onKeyDown) {
|
||||
this.keyDownActions.push({
|
||||
shortcut: shortcut,
|
||||
cmd: action.cmd,
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
if (shortcut.onKeyUp) {
|
||||
this.keyUpActions.push({
|
||||
shortcut: shortcut,
|
||||
cmd: action.cmd,
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
if (shortcut.onScrollUp) {
|
||||
this.mouseScrollUpActions.push({
|
||||
shortcut: shortcut,
|
||||
cmd: action.cmd,
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
if (shortcut.onScrollDown) {
|
||||
this.mouseScrollDownActions.push({
|
||||
shortcut: shortcut,
|
||||
cmd: action.cmd,
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
if (shortcut.onMouseEnter) {
|
||||
this.mouseEnterActions.push({
|
||||
shortcut: shortcut,
|
||||
cmd: action.cmd,
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
if (shortcut.onMouseLeave) {
|
||||
this.mouseLeaveActions.push({
|
||||
shortcut: shortcut,
|
||||
cmd: action.cmd,
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
if (shortcut.onMouseMove) {
|
||||
this.mouseMoveActions.push({
|
||||
shortcut: shortcut,
|
||||
cmd: action.cmd,
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (event) => ths.handleKeydown(event) );
|
||||
document.addEventListener('keyup', (event) => ths.handleKeyup(event) );
|
||||
|
||||
this.pageInfo.setActionHandler(this);
|
||||
if (Debug.debug) {
|
||||
console.log("[ActionHandler::init] initialization complete");
|
||||
}
|
||||
}
|
||||
|
||||
registerHandleMouse(videoData) {
|
||||
if (Debug.debug && Debug.mousemove) {
|
||||
console.log("[ActionHandler::registerHandleMouse] registering handle mouse for videodata:", videoData)
|
||||
}
|
||||
var ths = this;
|
||||
if (videoData.player && videoData.player.element) {
|
||||
videoData.player.element.addEventListener('mousemove', (event) => ths.handleMouseMove(event, videoData));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setKeyboardLocal(state) {
|
||||
if (state === ExtensionMode.Enabled) {
|
||||
this.keyboardLocalDisabled = false;
|
||||
} else if (state === ExtensionMode.Disabled) {
|
||||
this.keyboardLocalDisabled = true;
|
||||
}
|
||||
// don't do shit on invalid value of state
|
||||
}
|
||||
|
||||
preventAction() {
|
||||
var activeElement = document.activeElement;
|
||||
|
||||
if(Debug.debug && Debug.keyboard) {
|
||||
Debug.debug = false; // temp disable to avoid recursing;
|
||||
|
||||
console.log("[ActionHandler::preventAction] Testing whether we're in a textbox or something. Detailed rundown of conditions:\n" +
|
||||
"is full screen? (yes->allow):", PlayerData.isFullScreen(),
|
||||
"\nis tag one of defined inputs? (yes->prevent):", this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1,
|
||||
"\nis role = textbox? (yes -> prevent):", activeElement.getAttribute("role") === "textbox",
|
||||
"\nis type === 'text'? (yes -> prevent):", activeElement.getAttribute("type") === "text",
|
||||
"\nis keyboard local disabled? (yes -> prevent):", this.keyboardLocalDisabled,
|
||||
"\nis keyboard enabled in settings? (no -> prevent)", this.settings.keyboardShortcutsEnabled(window.location.hostname),
|
||||
"\nwill the action be prevented? (yes -> prevent)", this.preventAction(),
|
||||
"\n-----------------{ extra debug info }-------------------",
|
||||
"\ntag name? (lowercase):", activeElement.tagName, activeElement.tagName.toLocaleLowerCase(),
|
||||
"\nrole:", activeElement.getAttribute('role'),
|
||||
"\ntype:", activeElement.getAttribute('type'),
|
||||
"insta-fail inputs:", this.inputs
|
||||
);
|
||||
|
||||
Debug.debug = true; // undisable
|
||||
}
|
||||
|
||||
// lately youtube has allowed you to read and write comments while watching video in
|
||||
// fullscreen mode. We can no longer do this.
|
||||
// if (PlayerData.isFullScreen()) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (this.keyboardLocalDisabled) {
|
||||
return true;
|
||||
}
|
||||
if (!this.settings.keyboardShortcutsEnabled(window.location.hostname)) {
|
||||
return true;
|
||||
}
|
||||
if (this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1) {
|
||||
return true;
|
||||
}
|
||||
if (activeElement.getAttribute("role") === "textbox") {
|
||||
return true;
|
||||
}
|
||||
if (activeElement.getAttribute("type") === "text") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isActionMatch(shortcut, event) {
|
||||
return shortcut.key === event.key &&
|
||||
shortcut.ctrlKey === event.ctrlKey &&
|
||||
shortcut.metaKey === event.metaKey &&
|
||||
shortcut.altKey === event.altKey &&
|
||||
shortcut.shiftKey === event.shiftKey
|
||||
}
|
||||
|
||||
execAction(actions, event, videoData) {
|
||||
if(Debug.debug && Debug.keyboard ){
|
||||
console.log("%c[ActionHandler::execAction] Trying to find and execute action for event. Actions/event: ", "color: #ff0", actions, event);
|
||||
}
|
||||
|
||||
for (var action of actions) {
|
||||
if (this.isActionMatch(action.shortcut, event)) {
|
||||
if(Debug.debug && Debug.keyboard ){
|
||||
console.log("%c[ActionHandler::execAction] found an action associated with keypress/event: ", "color: #ff0", action);
|
||||
}
|
||||
|
||||
for (var cmd of action.cmd) {
|
||||
if (action.scope === 'page') {
|
||||
if (cmd.action === "set-ar") {
|
||||
this.pageInfo.setAr({type: cmd.arg, ratio: cmd.customArg});
|
||||
} else if (cmd.action === "change-zoom") {
|
||||
this.pageInfo.zoomStep(cmd.arg);
|
||||
} else if (cmd.action === "set-zoom") {
|
||||
this.pageInfo.setZoom(cmd.arg);
|
||||
} else if (cmd.action === "set-stretch") {
|
||||
this.pageInfo.setStretchMode(cmd.arg);
|
||||
} else if (cmd.action === "toggle-pan") {
|
||||
this.pageInfo.setPanMode(cmd.arg)
|
||||
} else if (cmd.action === "pan") {
|
||||
if (videoData) {
|
||||
videoData.panHandler(event, true);
|
||||
}
|
||||
} else if (cmd.action === 'set-keyboard') {
|
||||
this.setKeyboardLocal(cmd.arg);
|
||||
}
|
||||
} else {
|
||||
let site = action.scope === 'site' ? window.location.host : '@global';
|
||||
|
||||
if (cmd.action === "set-stretch") {
|
||||
this.settings.active.sites[site].stretch = cmd.arg;
|
||||
} else if (cmd.action === "set-alignment") {
|
||||
this.settings.active.sites[site].videoAlignment = cmd.arg;
|
||||
} else if (cmd.action === "set-extension-mode") {
|
||||
this.settings.active.sites[site].status = cmd.arg;
|
||||
} else if (cmd.action === "set-autoar-mode") {
|
||||
this.settings.active.sites[site].arStatus = cmd.arg;
|
||||
} else if (cmd.action === 'set-keyboard') {
|
||||
this.settings.active.sites[site].keyboardShortcutsEnabled = cmd.arg;
|
||||
}
|
||||
this.settings.save();
|
||||
}
|
||||
}
|
||||
|
||||
// če smo našli dejanje za to tipko, potem ne preiskujemo naprej
|
||||
// if we found an action for this key, we stop searching for a match
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handleKeyup(event) {
|
||||
if(Debug.debug && Debug.keyboard ){
|
||||
console.log("%c[ActionHandler::handleKeyup] we pressed a key: ", "color: #ff0", event.key , " | keyup: ", event.keyup, "event:", event);
|
||||
}
|
||||
|
||||
if (this.preventAction()) {
|
||||
if (Debug.debug && Debug.keyboard) {
|
||||
console.log("[ActionHandler::handleKeyup] we are in a text box or something. Doing nothing.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.execAction(this.keyUpActions, event);
|
||||
}
|
||||
|
||||
handleKeydown(event) {
|
||||
if(Debug.debug && Debug.keyboard ){
|
||||
console.log("%c[ActionHandler::handleKeydown] we pressed a key: ", "color: #ff0", event.key , " | keydown: ", event.keydown, "event:", event);
|
||||
}
|
||||
|
||||
if (this.preventAction()) {
|
||||
if (Debug.debug && Debug.keyboard) {
|
||||
console.log("[ActionHandler::handleKeydown] we are in a text box or something. Doing nothing.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.execAction(this.keyDownActions, event);
|
||||
}
|
||||
|
||||
handleMouseMove(event, videoData) {
|
||||
if (Debug.debug && Debug.mousemove) {
|
||||
console.log("[ActionHandler::handleMouseMove] mouse move is being handled.\nevent:", event, "\nvideo data:", videoData);
|
||||
}
|
||||
videoData.panHandler(event);
|
||||
this.execAction(this.mouseMoveActions, event, videoData)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ActionHandler;
|
@ -1,3 +1,5 @@
|
||||
import Debug from '../conf/Debug';
|
||||
|
||||
class ObjectCopy {
|
||||
static addNew(existing, target){
|
||||
|
||||
@ -50,4 +52,6 @@ class ObjectCopy {
|
||||
// target: object that we want
|
||||
// ignoreKeys: if key is an object, we don't recursively call this function on that key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ObjectCopy;
|
418
src/ext/lib/Settings.js
Normal file
418
src/ext/lib/Settings.js
Normal file
@ -0,0 +1,418 @@
|
||||
import Debug from '../conf/Debug';
|
||||
import currentBrowser from '../conf/BrowserDetect';
|
||||
import ExtensionConf from '../conf/ExtensionConf';
|
||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
||||
import ObjectCopy from '../lib/ObjectCopy';
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import VideoAlignment from '../../common/enums/video-alignment.enum';
|
||||
|
||||
|
||||
|
||||
class Settings {
|
||||
|
||||
constructor(activeSettings, updateCallback) {
|
||||
this.active = activeSettings ? activeSettings : undefined;
|
||||
this.default = ExtensionConf;
|
||||
this.useSync = false;
|
||||
this.version = undefined;
|
||||
this.updateCallback = updateCallback;
|
||||
|
||||
const ths = this;
|
||||
|
||||
if(currentBrowser.firefox) {
|
||||
browser.storage.onChanged.addListener( (changes, area) => {
|
||||
if (Debug.debug && Debug.debugStorage) {
|
||||
console.log("[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
||||
if (changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
console.log("[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
|
||||
}
|
||||
}
|
||||
if(changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
ths.setActive(JSON.parse(changes.uwSettings.newValue));
|
||||
}
|
||||
|
||||
if(this.updateCallback) {
|
||||
try {
|
||||
updateCallback(ths);
|
||||
} catch (e) {
|
||||
console.log("[Settings] CALLING UPDATE CALLBACK FAILED.")
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (currentBrowser.chrome) {
|
||||
chrome.storage.onChanged.addListener( (changes, area) => {
|
||||
if (Debug.debug && Debug.debugStorage) {
|
||||
console.log("[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
||||
if (changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
console.log("[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
|
||||
}
|
||||
}
|
||||
if(changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
ths.setActive(JSON.parse(changes.uwSettings.newValue));
|
||||
}
|
||||
|
||||
if(this.updateCallback) {
|
||||
try {
|
||||
updateCallback(ths);
|
||||
} catch (e) {
|
||||
console.log("[Settings] CALLING UPDATE CALLBACK FAILED.")
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
const settings = await this.get();
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[Settings::init] Configuration fetched from storage:", settings);
|
||||
|
||||
if (Debug.flushStoredSettings) {
|
||||
console.log("%c[Settings::init] Debug.flushStoredSettings is true. Using default settings", "background: #d00; color: #ffd");
|
||||
Debug.flushStoredSettings = false; // don't do it again this session
|
||||
this.setDefaultSettings();
|
||||
this.active = this.getDefaultSettings();
|
||||
return this.active;
|
||||
}
|
||||
}
|
||||
|
||||
// if there's no settings saved, return default settings.
|
||||
if(! settings || (Object.keys(settings).length === 0 && settings.constructor === Object)) {
|
||||
this.setDefaultSettings();
|
||||
this.active = this.getDefaultSettings();
|
||||
return this.active;
|
||||
}
|
||||
|
||||
// if last saved settings was for version prior to 4.x, we reset settings to default
|
||||
// it's not like people will notice cos that version didn't preserve settings at all
|
||||
if (settings.version && !settings.version.startsWith('4')) {
|
||||
this.setDefaultSettings();
|
||||
this.active = this.getDefaultSettings();
|
||||
return this.active;
|
||||
}
|
||||
|
||||
// if there's settings, set saved object as active settings
|
||||
this.active = settings;
|
||||
|
||||
// check if extension has been updated. If not, return settings as they were retreived
|
||||
if (currentBrowser.firefox) {
|
||||
this.version = browser.runtime.getManifest().version;
|
||||
} else if (currentBrowser.chrome) {
|
||||
this.version = chrome.runtime.getManifest().version;
|
||||
} else if (currentBrowser.edge) {
|
||||
this.version = browser.runtime.getManifest().version;
|
||||
}
|
||||
|
||||
if(settings.version === this.version) {
|
||||
if(Debug.debug) {
|
||||
console.log("[Settings::init] extension was saved with current version of ultrawidify (", this.version, "). Returning object as-is.");
|
||||
}
|
||||
return this.active;
|
||||
}
|
||||
|
||||
// if extension has been updated, update existing settings with any options added in the
|
||||
// new version. In addition to that, we remove old keys that are no longer used.
|
||||
const patched = ObjectCopy.addNew(settings, this.default);
|
||||
if(Debug.debug) {
|
||||
console.log("[Settings.init] Results from ObjectCopy.addNew()?", patched, "\n\nSettings from storage", settings, "\ndefault?", this.default,);
|
||||
}
|
||||
|
||||
if(patched){
|
||||
this.active = patched;
|
||||
} else {
|
||||
this.active = JSON.parse(JSON.stringify(this.default));
|
||||
}
|
||||
|
||||
this.set(this.active);
|
||||
return this.active;
|
||||
}
|
||||
|
||||
async get() {
|
||||
let ret;
|
||||
|
||||
if (currentBrowser.firefox) {
|
||||
ret = await browser.storage.local.get('uwSettings');
|
||||
} else if (currentBrowser.chrome) {
|
||||
ret = await new Promise( (resolve, reject) => {
|
||||
chrome.storage.local.get('uwSettings', (res) => resolve(res));
|
||||
});
|
||||
} else if (currentBrowser.edge) {
|
||||
ret = await new Promise( (resolve, reject) => {
|
||||
browser.storage.local.get('uwSettings', (res) => resolve(res));
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(ret.uwSettings);
|
||||
} catch(e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async set(extensionConf) {
|
||||
if (Debug.debug) {
|
||||
console.log("[Settings::set] setting new settings:", extensionConf)
|
||||
}
|
||||
|
||||
if (currentBrowser.firefox || currentBrowser.edge) {
|
||||
extensionConf.version = this.version;
|
||||
return this.useSync ? browser.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)}): browser.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
||||
} else if (currentBrowser.chrome) {
|
||||
return chrome.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
||||
}
|
||||
}
|
||||
|
||||
async setActive(activeSettings) {
|
||||
this.active = activeSettings;
|
||||
}
|
||||
|
||||
async setProp(prop, value) {
|
||||
this.active[prop] = value;
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (Debug.debug) {
|
||||
console.log("[Settings::save] Saving active settings:", this.active);
|
||||
}
|
||||
|
||||
this.set(this.active);
|
||||
}
|
||||
|
||||
async rollback() {
|
||||
this.active = await this.get();
|
||||
}
|
||||
|
||||
getDefaultSettings() {
|
||||
return JSON.parse(JSON.stringify(this.default));
|
||||
}
|
||||
|
||||
setDefaultSettings() {
|
||||
this.set(this.default);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------
|
||||
// Nastavitve za posamezno stran
|
||||
// Config for a given page:
|
||||
//
|
||||
// <hostname> : {
|
||||
// status: <option> // should extension work on this site?
|
||||
// arStatus: <option> // should we do autodetection on this site?
|
||||
// statusEmbedded: <option> // reserved for future... maybe
|
||||
// }
|
||||
//
|
||||
// Veljavne vrednosti za možnosti
|
||||
// Valid values for options:
|
||||
//
|
||||
// status, arStatus, statusEmbedded:
|
||||
//
|
||||
// * enabled — always allow
|
||||
// * basic — only allow fullscreen
|
||||
// * default — allow if default is to allow, block if default is to block
|
||||
// * disabled — never allow
|
||||
|
||||
|
||||
getActionsForSite(site) {
|
||||
if (!site) {
|
||||
return this.active.actions;
|
||||
}
|
||||
if (this.active.sites[site] && this.active.sites[site].actions && this.active.sites[site].actions.length > 0) {
|
||||
return this.active.sites[site].actions;
|
||||
}
|
||||
return this.active.actions;
|
||||
}
|
||||
|
||||
getExtensionMode(site) {
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
|
||||
if (!site) {
|
||||
console.log("[Settings::canStartExtension] window.location.hostname is null or undefined:", window.location.hostname)
|
||||
console.log("active settings:", this.active)
|
||||
return ExtensionMode.Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// if site-specific settings don't exist for the site, we use default mode:
|
||||
if (! this.active.sites[site]) {
|
||||
return this.getExtensionMode('@global');
|
||||
}
|
||||
|
||||
if (this.active.sites[site].mode === ExtensionMode.Enabled) {
|
||||
return ExtensionMode.Enabled;
|
||||
} else if (this.active.sites[site].mode === ExtensionMode.Basic) {
|
||||
return ExtensionMode.Basic;
|
||||
} else if (this.active.sites[site].mode === ExtensionMode.Default && site !== '@global') {
|
||||
return this.getExtensionMode('@global');
|
||||
} else {
|
||||
return ExtensionMode.Disabled;
|
||||
}
|
||||
|
||||
} catch(e){
|
||||
if(Debug.debug){
|
||||
console.log("[Settings.js::canStartExtension] Something went wrong — are settings defined/has init() been called?\n\nerror:", e, "\n\nSettings object:", this)
|
||||
}
|
||||
return ExtensionMode.Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
canStartExtension(site) {
|
||||
// returns 'true' if extension can be started on a given site. Returns false if we shouldn't run.
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
|
||||
if (!site) {
|
||||
console.log("[Settings::canStartExtension] window.location.hostname is null or undefined:", window.location.hostname)
|
||||
console.log("active settings:", this.active)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if (Debug.debug) {
|
||||
// // let's just temporarily disable debugging while recursively calling
|
||||
// // this function to get extension status on current site without duplo
|
||||
// // console logs (and without endless recursion)
|
||||
// Debug.debug = false;
|
||||
// const cse = this.canStartExtension(site);
|
||||
// Debug.debug = true;
|
||||
// }
|
||||
try{
|
||||
// if site is not defined, we use default mode:
|
||||
if (! this.active.sites[site]) {
|
||||
return this.active.sites['@global'].mode === ExtensionMode.Enabled;
|
||||
}
|
||||
|
||||
if(this.active.sites['@global'].mode === ExtensionMode.Enabled) {
|
||||
return this.active.sites[site].mode !== ExtensionMode.Disabled;
|
||||
} else if (this.active.sites['@global'].mode === ExtensionMode.Whitelist) {
|
||||
return this.active.sites[site].mode === ExtensionMode.Enabled;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch(e){
|
||||
if(Debug.debug){
|
||||
console.log("[Settings.js::canStartExtension] Something went wrong — are settings defined/has init() been called?\nSettings object:", this)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
keyboardShortcutsEnabled(site) {
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
}
|
||||
if (!site) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.active.sites[site]
|
||||
|| this.active.sites[site].keyboardShortcutsEnabled === undefined
|
||||
|| this.active.sites[site].keyboardShortcutsEnabled === ExtensionMode.Default) {
|
||||
return this.keyboardShortcutsEnabled('@global');
|
||||
} else {
|
||||
return this.active.sites[site].keyboardShortcutsEnabled === ExtensionMode.Enabled;
|
||||
}
|
||||
} catch (e) {
|
||||
if (Debug.debug) {
|
||||
console.error("[Settings.js::keyboardDisabled] something went wrong:", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
extensionEnabled(){
|
||||
return this.active.sites['@global'] !== ExtensionMode.Disabled
|
||||
}
|
||||
|
||||
extensionEnabledForSite(site) {
|
||||
return this.canStartExtension(site);
|
||||
}
|
||||
|
||||
canStartAutoAr(site) {
|
||||
// 'site' argument is only ever used when calling this function recursively for debugging
|
||||
if (!site) {
|
||||
site = window.location.host;
|
||||
|
||||
if (!site) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Debug.debug) {
|
||||
// let's just temporarily disable debugging while recursively calling
|
||||
// this function to get extension status on current site without duplo
|
||||
// console logs (and without endless recursion)
|
||||
Debug.debug = false;
|
||||
const csar = this.canStartAutoAr(site);
|
||||
Debug.debug = true;
|
||||
|
||||
console.log("[Settings::canStartAutoAr] ----------------\nCAN WE START AUTOAR ON SITE", site,
|
||||
"?\n\nsettings.active.sites[site]=", this.active.sites[site], "settings.active.sites[@global]=", this.active.sites['@global'],
|
||||
"\nAutoar mode (global)?", this.active.sites['@global'].autoar,
|
||||
`\nAutoar mode (${site})`, this.active.sites[site] ? this.active.sites[site].autoar : '<not defined>',
|
||||
"\nCan autoar be started?", csar
|
||||
);
|
||||
}
|
||||
|
||||
// if site is not defined, we use default mode:
|
||||
if (! this.active.sites[site]) {
|
||||
return this.active.sites['@global'].autoar === ExtensionMode.Enabled;
|
||||
}
|
||||
|
||||
if (this.active.sites['@global'].autoar === ExtensionMode.Enabled) {
|
||||
return this.active.sites[site].autoar !== ExtensionMode.Disabled;
|
||||
} else if (this.active.sites['@global'].autoar === ExtensionMode.Whitelist) {
|
||||
console.log("canStartAutoAr — can(not) start csar because extension is in whitelist mode, and this site is (not) equal to", ExtensionMode.Enabled)
|
||||
return this.active.sites[site].autoar === ExtensionMode.Enabled;
|
||||
} else {
|
||||
console.log("canStartAutoAr — cannot start csar because extension is globally disabled")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultOption(option) {
|
||||
const allDefault = {
|
||||
mode: ExtensionMode.Default,
|
||||
autoar: ExtensionMode.Default,
|
||||
autoarFallback: ExtensionMode.Default,
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
};
|
||||
|
||||
if (!option || allDefault[option] === undefined) {
|
||||
return allDefault;
|
||||
}
|
||||
|
||||
return allDefault[option];
|
||||
}
|
||||
|
||||
getDefaultAr(site) {
|
||||
// site = this.getSiteSettings(site);
|
||||
|
||||
// if (site.defaultAr) {
|
||||
// return site.defaultAr;
|
||||
// }
|
||||
return this.active.miscSettings.defaultAr;
|
||||
}
|
||||
|
||||
getDefaultStretchMode(site) {
|
||||
if (site && this.active.sites[site] && this.active.sites[site].stretch !== Stretch.Default) {
|
||||
return this.active.sites[site].stretch;
|
||||
}
|
||||
|
||||
return this.active.sites['@global'].stretch;
|
||||
}
|
||||
|
||||
getDefaultVideoAlignment(site) {
|
||||
if (site && this.active.sites[site] && this.active.sites[site].videoAlignment !== VideoAlignment.Default) {
|
||||
return this.active.sites[site].videoAlignment;
|
||||
}
|
||||
|
||||
return this.active.sites['@global'].videoAlignment;
|
||||
}
|
||||
}
|
||||
|
||||
export default Settings;
|
981
src/ext/lib/ar-detect/ArDetector.js
Normal file
981
src/ext/lib/ar-detect/ArDetector.js
Normal file
@ -0,0 +1,981 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import EdgeDetect from './edge-detect/EdgeDetect';
|
||||
import EdgeStatus from './edge-detect/enums/EdgeStatusEnum';
|
||||
import EdgeDetectPrimaryDirection from './edge-detect/enums/EdgeDetectPrimaryDirectionEnum';
|
||||
import EdgeDetectQuality from './edge-detect/enums/EdgeDetectQualityEnum';
|
||||
import GuardLine from './GuardLine';
|
||||
import DebugCanvas from './DebugCanvas';
|
||||
import VideoAlignment from '../../../common/enums/video-alignment.enum';
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
|
||||
class ArDetector {
|
||||
|
||||
constructor(videoData){
|
||||
this.conf = videoData;
|
||||
this.video = videoData.video;
|
||||
this.settings = videoData.settings;
|
||||
|
||||
this.setupTimer = null;
|
||||
|
||||
this.sampleCols = [];
|
||||
|
||||
this.canFallback = true;
|
||||
this.fallbackMode = false;
|
||||
|
||||
this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel;
|
||||
|
||||
this.arid = (Math.random()*100).toFixed();
|
||||
|
||||
// ar detector starts in this state. running main() sets both to false
|
||||
this._halted = true;
|
||||
this._exited = true;
|
||||
|
||||
// we can tick manually, for debugging
|
||||
this._manualTicks = false;
|
||||
this._nextTick = false;
|
||||
|
||||
this.canDoFallbackMode = false;
|
||||
if (Debug.init) {
|
||||
console.log("[ArDetector::ctor] creating new ArDetector. arid:", this.arid);
|
||||
}
|
||||
}
|
||||
|
||||
setManualTick(manualTick) {
|
||||
this._manualTicks = manualTick;
|
||||
}
|
||||
|
||||
tick() {
|
||||
this._nextTick = true;
|
||||
}
|
||||
|
||||
init(){
|
||||
if (Debug.debug || Debug.init) {
|
||||
console.log("[ArDetect::init] Initializing autodetection. arid:", this.arid);
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.settings.canStartAutoAr()) {
|
||||
this.setup();
|
||||
} else {
|
||||
throw "Settings prevent autoar from starting"
|
||||
}
|
||||
} catch (e) {
|
||||
if (Debug.debug) {
|
||||
console.log("%c[ArDetect::init] INITIALIZATION FAILED!\n", _ard_console_stop, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy(){
|
||||
if(Debug.debug || Debug.init) {
|
||||
console.log(`[ArDetect::destroy] <arid:${this.arid}>`)
|
||||
}
|
||||
// this.debugCanvas.destroy();
|
||||
this.stop();
|
||||
}
|
||||
|
||||
setup(cwidth, cheight){
|
||||
if(Debug.debug || Debug.init) {
|
||||
console.log("[ArDetect::setup] Starting autodetection setup. arid:", this.arid);
|
||||
}
|
||||
|
||||
//
|
||||
// [-1] check for zero-width and zero-height videos. If we detect this, we kick the proverbial
|
||||
// can some distance down the road. This problem will prolly fix itself soon. We'll also
|
||||
// not do any other setup until this issue is fixed
|
||||
//
|
||||
if(this.video.videoWidth === 0 || this.video.videoHeight === 0 ){
|
||||
if(Debug.debug){
|
||||
console.log("[ArDetector::setup] video has no width or height!", this.video.videoWidth,"×", this.video.videoHeight)
|
||||
}
|
||||
this.scheduleInitRestart();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// [0] initiate "dependencies" first
|
||||
//
|
||||
|
||||
this.guardLine = new GuardLine(this);
|
||||
this.edgeDetector = new EdgeDetect(this);
|
||||
// this.debugCanvas = new DebugCanvas(this);
|
||||
|
||||
|
||||
//
|
||||
// [1] initiate canvases
|
||||
//
|
||||
|
||||
if (!cwidth) {
|
||||
cwidth = this.settings.active.arDetect.canvasDimensions.sampleCanvas.width;
|
||||
cheight = this.settings.active.arDetect.canvasDimensions.sampleCanvas.height;
|
||||
}
|
||||
|
||||
if (this.canvas) {
|
||||
this.canvas.remove();
|
||||
}
|
||||
if (this.blackframeCanvas) {
|
||||
this.blackframeCanvas.remove();
|
||||
}
|
||||
|
||||
// things to note: we'll be keeping canvas in memory only.
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.canvas.width = cwidth;
|
||||
this.canvas.height = cheight;
|
||||
this.blackframeCanvas = document.createElement("canvas");
|
||||
this.blackframeCanvas.width = this.settings.active.arDetect.canvasDimensions.blackframeCanvas.width;
|
||||
this.blackframeCanvas.height = this.settings.active.arDetect.canvasDimensions.blackframeCanvas.height;
|
||||
|
||||
this.context = this.canvas.getContext("2d");
|
||||
this.blackframeContext = this.blackframeCanvas.getContext("2d");
|
||||
|
||||
|
||||
// do setup once
|
||||
// tho we could do it for every frame
|
||||
this.canvasScaleFactor = cheight / this.video.videoHeight;
|
||||
|
||||
|
||||
//
|
||||
// [2] determine places we'll use to sample our main frame
|
||||
//
|
||||
|
||||
var ncol = this.settings.active.arDetect.sampling.staticCols;
|
||||
var nrow = this.settings.active.arDetect.sampling.staticRows;
|
||||
|
||||
var colSpacing = this.canvas.width / ncol;
|
||||
var rowSpacing = (this.canvas.height << 2) / nrow;
|
||||
|
||||
this.sampleLines = [];
|
||||
this.sampleCols = [];
|
||||
|
||||
for(var i = 0; i < ncol; i++){
|
||||
if(i < ncol - 1)
|
||||
this.sampleCols.push(Math.round(colSpacing * i));
|
||||
else{
|
||||
this.sampleCols.push(Math.round(colSpacing * i) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < nrow; i++){
|
||||
if(i < ncol - 5)
|
||||
this.sampleLines.push(Math.round(rowSpacing * i));
|
||||
else{
|
||||
this.sampleLines.push(Math.round(rowSpacing * i) - 4);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// [3] detect if we're in the fallback mode and reset guardline
|
||||
//
|
||||
|
||||
if (this.fallbackMode) {
|
||||
if(Debug.debug) {
|
||||
console.log("%c[ArDetect::setup] WARNING: CANVAS RESET DETECTED - recalculating guardLine", "background: #000; color: #ff2" )
|
||||
}
|
||||
// blackbar, imagebar
|
||||
this.guardLine.reset();
|
||||
}
|
||||
|
||||
//
|
||||
// [4] see if browser supports "fallback mode" by drawing a small portion of our window
|
||||
//
|
||||
|
||||
try {
|
||||
this.blackframeContext.drawWindow(window,0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height, "rgba(0,0,128,1)");
|
||||
this.canDoFallbackMode = true;
|
||||
} catch (e) {
|
||||
this.canDoFallbackMode = false;
|
||||
}
|
||||
|
||||
//
|
||||
// [5] do other things setup needs to do
|
||||
//
|
||||
|
||||
this.detectionTimeoutEventCount = 0;
|
||||
this.resetBlackLevel();
|
||||
|
||||
// if we're restarting ArDetect, we need to do this in order to force-recalculate aspect ratio
|
||||
this.conf.resizer.setLastAr({type: AspectRatio.Automatic, ratio: null});
|
||||
|
||||
this.canvasImageDataRowLength = cwidth << 2;
|
||||
this.noLetterboxCanvasReset = false;
|
||||
|
||||
if (this.settings.canStartAutoAr() ) {
|
||||
this.start();
|
||||
}
|
||||
|
||||
if(Debug.debugCanvas.enabled){
|
||||
// this.debugCanvas.init({width: cwidth, height: cheight});
|
||||
// DebugCanvas.draw("test marker","test","rect", {x:5, y:5}, {width: 5, height: 5});
|
||||
}
|
||||
|
||||
this.conf.arSetupComplete = true;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (Debug.debug) {
|
||||
console.log("%c[ArDetect::setup] Starting automatic aspect ratio detection.", _ard_console_start);
|
||||
}
|
||||
if (this.conf.resizer.lastAr.type === AspectRatio.Automatic) {
|
||||
// ensure first autodetection will run in any case
|
||||
this.conf.resizer.setLastAr({type: AspectRatio.Automatic, ratio: null});
|
||||
}
|
||||
|
||||
// launch main() if it's currently not running:
|
||||
this.main();
|
||||
this._halted = false;
|
||||
this._paused = false;
|
||||
|
||||
}
|
||||
|
||||
unpause() {
|
||||
if(this._paused){ // resume only if we explicitly paused
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
pause() {
|
||||
// pause only if we were running before. Don't pause if we aren't running
|
||||
// (we are running when _halted is neither true nor undefined)
|
||||
if (this._halted === false) {
|
||||
this._paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
stop(){
|
||||
if(Debug.debug){
|
||||
console.log("%c[ArDetect::_ard_stop] Stopping automatic aspect ratio detection", _ard_console_stop);
|
||||
}
|
||||
this._halted = true;
|
||||
// this.conf.resizer.resetLastAr();
|
||||
}
|
||||
|
||||
async main() {
|
||||
if (this.paused) {
|
||||
// unpause if paused
|
||||
this._paused = false;
|
||||
}
|
||||
if (!this._halted) {
|
||||
// we are already running, don't run twice
|
||||
return;
|
||||
}
|
||||
|
||||
let exitedRetries = 10;
|
||||
|
||||
while (!this._exited && exitedRetries --> 0) {
|
||||
if (Debug.debug) {
|
||||
console.log("[ArDetector::main] We are trying to start another instance of autodetection on current video, but the previous instance hasn't exited yet. Waiting for old instance to exit ...")
|
||||
}
|
||||
await this.sleep(this.settings.active.arDetect.timers.tickrate);
|
||||
}
|
||||
if (!this._exited) {
|
||||
if (Debug.debug) {
|
||||
console.log("[ArDetector::main] Previous instance didn't exit in time. Not starting a new one.")
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("%c[ArDetect::main] Main autodetection loop started.", _ard_console_start);
|
||||
}
|
||||
|
||||
// we need to unhalt:
|
||||
this._halted = false;
|
||||
this._exited = false;
|
||||
|
||||
// set initial timestamps so frame check will trigger the first time we run the loop
|
||||
let lastFrameCheckStartTime = Date.now() - (this.settings.active.arDetect.timers.playing << 1);
|
||||
|
||||
const frameCheckTimes = new Array(10).fill(-1);
|
||||
let frameCheckBufferIndex = 0;
|
||||
let fcstart, fctime;
|
||||
|
||||
while (this && !this._halted) {
|
||||
// NOTE: we separated tickrate and inter-check timeouts so that when video switches
|
||||
// state from 'paused' to 'playing', we don't need to wait for the rest of the longer
|
||||
// paused state timeout to finish.
|
||||
|
||||
if ( (!this._manualTicks && this.canTriggerFrameCheck(lastFrameCheckStartTime)) || this._nextTick) {
|
||||
this._nextTick = false;
|
||||
|
||||
lastFrameCheckStartTime = Date.now();
|
||||
fcstart = performance.now();
|
||||
|
||||
try {
|
||||
this.frameCheck();
|
||||
} catch (e) {
|
||||
if (Debug.debug) {
|
||||
console.log("%c[ArDetector::main] Frame check failed:", "color: #000, background: #f00", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (Debug.performanceMetrics) {
|
||||
fctime = performance.now() - fcstart;
|
||||
frameCheckTimes[frameCheckBufferIndex % frameCheckTimes.length] = fctime;
|
||||
this.conf.pageInfo.sendPerformanceUpdate({frameCheckTimes: frameCheckTimes, lastFrameCheckTime: fctime});
|
||||
++frameCheckBufferIndex;
|
||||
}
|
||||
}
|
||||
|
||||
await this.sleep(this.settings.active.arDetect.timers.tickrate);
|
||||
}
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log(`%c[ArDetect::main] Main autodetection loop exited. Halted? ${this._halted}`, _ard_console_stop);
|
||||
}
|
||||
this._exited = true;
|
||||
}
|
||||
|
||||
async sleep(timeout) {
|
||||
return new Promise( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
||||
}
|
||||
|
||||
canTriggerFrameCheck(lastFrameCheckStartTime) {
|
||||
if (this._paused) {
|
||||
return false;
|
||||
}
|
||||
if (this.video.ended || this.video.paused){
|
||||
// we slow down if ended or pausing. Detecting is pointless.
|
||||
// we don't stop outright in case seeking happens during pause/after video was
|
||||
// ended and video gets into 'playing' state again
|
||||
return Date.now() - lastFrameCheckStartTime > this.settings.active.arDetect.timers.paused;
|
||||
}
|
||||
if (this.video.error){
|
||||
// če je video pavziran, še vedno skušamo zaznati razmerje stranic - ampak bolj poredko.
|
||||
// if the video is paused, we still do autodetection. We just do it less often.
|
||||
return Date.now() - lastFrameCheckStartTime > this.settings.active.arDetect.timers.error;
|
||||
}
|
||||
|
||||
return Date.now() - lastFrameCheckStartTime > this.settings.active.arDetect.timers.playing;
|
||||
}
|
||||
|
||||
isRunning(){
|
||||
return ! (this._halted || this._paused || this._exited);
|
||||
}
|
||||
|
||||
|
||||
scheduleInitRestart(timeout, force_reset){
|
||||
if(! timeout){
|
||||
timeout = 100;
|
||||
}
|
||||
// don't allow more than 1 instance
|
||||
if(this.setupTimer){
|
||||
clearTimeout(this.setupTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
this.setupTimer = setTimeout(function(){
|
||||
ths.setupTimer = null;
|
||||
try{
|
||||
ths.main();
|
||||
}catch(e){console.log("[ArDetector::scheduleInitRestart] Failed to start init(). Error:",e)}
|
||||
ths = null;
|
||||
},
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//#region helper functions (general)
|
||||
attachCanvas(canvas){
|
||||
if(this.attachedCanvas)
|
||||
this.attachedCanvas.remove();
|
||||
|
||||
// todo: place canvas on top of the video instead of random location
|
||||
canvas.style.position = "absolute";
|
||||
canvas.style.left = "200px";
|
||||
canvas.style.top = "1200px";
|
||||
canvas.style.zIndex = 10000;
|
||||
|
||||
document.getElementsByTagName("body")[0]
|
||||
.appendChild(canvas);
|
||||
}
|
||||
|
||||
canvasReadyForDrawWindow(){
|
||||
if(Debug.debug)
|
||||
console.log("%c[ArDetect::_ard_canvasReadyForDrawWindow] (?)", "color: #44f", this.canvas.height == window.innerHeight, "(ard_height:", this.canvas.height, "| window height:", window.innerHeight, ")");
|
||||
|
||||
return this.canvas.height == window.innerHeight
|
||||
}
|
||||
|
||||
getTimeout(baseTimeout, startTime){
|
||||
var execTime = (performance.now() - startTime);
|
||||
|
||||
if( execTime > this.settings.active.arDetect.autoDisable.maxExecutionTime ){
|
||||
// this.detectionTimeoutEventCount++;
|
||||
|
||||
if(Debug.debug){
|
||||
console.log("[ArDetect::getTimeout] Exec time exceeded maximum allowed execution time. This has now happened " + this.detectionTimeoutEventCount + " times in a row.");
|
||||
}
|
||||
|
||||
// if( this.detectionTimeoutEventCount >= this.settings.active.arDetect.autoDisable.consecutiveTimeoutCount ){
|
||||
// if (Debug.debug){
|
||||
// console.log("[ArDetect::getTimeout] Maximum execution time was exceeded too many times. Automatic aspect ratio detection has been disabled.");
|
||||
// }
|
||||
|
||||
// Comms.sendToBackgroundScript({cmd: 'disable-autoar', reason: 'Automatic aspect ratio detection was taking too much time and has been automatically disabled in order to avoid lag.'});
|
||||
// _ard_stop();
|
||||
// return 999999;
|
||||
// }
|
||||
|
||||
} else {
|
||||
this.detectionTimeoutEventCount = 0;
|
||||
}
|
||||
// return baseTimeout > this.settings.active.arDetect.minimumTimeout ? baseTimeout : this.settings.active.arDetect.minimumTimeout;
|
||||
|
||||
return baseTimeout;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
calculateArFromEdges(edges) {
|
||||
// if we don't specify these things, they'll have some default values.
|
||||
if(edges.top === undefined){
|
||||
edges.top = 0;
|
||||
edges.bottom = 0;
|
||||
edges.left = 0; // RESERVED FOR FUTURE — CURRENTLY UNUSED
|
||||
edges.right = 0; // THIS FUNCTION CAN PRESENTLY ONLY HANDLE LETTERBOX
|
||||
}
|
||||
|
||||
let letterbox = edges.top + edges.bottom;
|
||||
|
||||
|
||||
if (! this.fallbackMode) {
|
||||
// Since video is stretched to fit the canvas, we need to take that into account when calculating target
|
||||
// aspect ratio and correct our calculations to account for that
|
||||
|
||||
const fileAr = this.video.videoWidth / this.video.videoHeight;
|
||||
const canvasAr = this.canvas.width / this.canvas.height;
|
||||
let widthCorrected;
|
||||
|
||||
if (edges.top && edges.bottom) {
|
||||
// in case of letterbox, we take canvas height as canon and assume width got stretched or squished
|
||||
|
||||
if (fileAr != canvasAr) {
|
||||
widthCorrected = this.canvas.height * fileAr;
|
||||
} else {
|
||||
widthCorrected = this.canvas.width;
|
||||
}
|
||||
|
||||
return widthCorrected / (this.canvas.height - letterbox);
|
||||
}
|
||||
} else {
|
||||
// fallback mode behaves a wee bit differently
|
||||
|
||||
let zoomFactor = 1;
|
||||
|
||||
// there's stuff missing from the canvas. We need to assume canvas' actual height is bigger by a factor x, where
|
||||
// x = [video.zoomedHeight] / [video.unzoomedHeight]
|
||||
//
|
||||
// letterbox also needs to be corrected:
|
||||
// letterbox += [video.zoomedHeight] - [video.unzoomedHeight]
|
||||
|
||||
var vbr = this.video.getBoundingClientRect();
|
||||
|
||||
zoomFactor = vbr.height / this.video.clientHeight;
|
||||
letterbox += vbr.height - this.video.clientHeight;
|
||||
|
||||
var trueHeight = this.canvas.height * zoomFactor - letterbox;
|
||||
|
||||
if(edges.top > 1 && edges.top <= this.settings.active.arDetect.fallbackMode.noTriggerZonePx ){
|
||||
if(Debug.debug && Debug.debugArDetect) {
|
||||
console.log("Edge is in the no-trigger zone. Aspect ratio change is not triggered.")
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// varnostno območje, ki naj ostane črno (da lahko v fallback načinu odkrijemo ožanje razmerja stranic).
|
||||
// x2, ker je safetyBorderPx definiran za eno stran.
|
||||
// safety border so we can detect aspect ratio narrowing (21:9 -> 16:9).
|
||||
// x2 because safetyBorderPx is for one side.
|
||||
trueHeight += (this.settings.active.arDetect.fallbackMode.safetyBorderPx << 1);
|
||||
|
||||
return this.canvas.width * zoomFactor / trueHeight;
|
||||
}
|
||||
}
|
||||
|
||||
processAr(trueAr){
|
||||
let actualHeight = 0; // purely for fallback mode
|
||||
this.detectedAr = trueAr;
|
||||
|
||||
// poglejmo, če se je razmerje stranic spremenilo
|
||||
// check if aspect ratio is changed:
|
||||
var lastAr = this.conf.resizer.getLastAr();
|
||||
if (lastAr.type === AspectRatio.Automatic && lastAr.ratio !== null){
|
||||
// spremembo lahko zavrnemo samo, če uporabljamo avtomatski način delovanja in če smo razmerje stranic
|
||||
// že nastavili.
|
||||
//
|
||||
// we can only deny aspect ratio changes if we use automatic mode and if aspect ratio was set from here.
|
||||
|
||||
var arDiff = trueAr - lastAr.ar;
|
||||
|
||||
if (arDiff < 0)
|
||||
arDiff = -arDiff;
|
||||
|
||||
var arDiff_percent = arDiff / trueAr;
|
||||
|
||||
// ali je sprememba v mejah dovoljenega? Če da -> fertik
|
||||
// is ar variance within acceptable levels? If yes -> we done
|
||||
if(Debug.debug && Debug.debugArDetect)
|
||||
console.log("%c[ArDetect::processAr] new aspect ratio varies from the old one by this much:\n","color: #aaf","old Ar", lastAr.ar, "current ar", trueAr, "arDiff (absolute):",arDiff,"ar diff (relative to new ar)", arDiff_percent);
|
||||
|
||||
if (arDiff < trueAr * this.settings.active.arDetect.allowedArVariance){
|
||||
if(Debug.debug && Debug.debugArDetect)
|
||||
console.log("%c[ArDetect::processAr] aspect ratio change denied — diff %:", "background: #740; color: #fa2", arDiff_percent)
|
||||
|
||||
return;
|
||||
}
|
||||
else if(Debug.debug && Debug.debugArDetect){
|
||||
console.log("%c[ArDetect::processAr] aspect ratio change accepted — diff %:", "background: #153; color: #4f9", arDiff_percent)
|
||||
}
|
||||
}
|
||||
|
||||
if(Debug.debug) {
|
||||
|
||||
console.log("%c[ArDetect::processAr] Triggering aspect ratio change. New aspect ratio: ", _ard_console_change, trueAr);
|
||||
}
|
||||
|
||||
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: trueAr}, {type: AspectRatio.Automatic, ratio: trueAr});
|
||||
}
|
||||
|
||||
frameCheck(){
|
||||
if(! this.video){
|
||||
if(Debug.debug || Debug.warnings_critical) {
|
||||
console.log("[ArDetect::frameCheck] Video went missing. Destroying current instance of videoData.")
|
||||
}
|
||||
this.conf.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.blackframeContext) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
var startTime = performance.now();
|
||||
let sampleCols = this.sampleCols.slice(0);
|
||||
|
||||
//
|
||||
// [0] blackframe tests (they also determine whether we need fallback mode)
|
||||
//
|
||||
try {
|
||||
this.blackframeContext.drawImage(this.video, 0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height);
|
||||
this.fallbackMode = false;
|
||||
} catch (e) {
|
||||
if(Debug.debug && Debug.debugArDetect) {
|
||||
console.log(`%c[ArDetect::frameCheck] can't draw image on canvas. ${this.canDoFallbackMode ? 'Trying canvas.drawWindow instead' : 'Doing nothing as browser doesn\'t support fallback mode.'}`, "color:#000; backgroud:#f51;", e);
|
||||
}
|
||||
// nothing to see here, really, if fallback mode isn't supported by browser
|
||||
if (! this.canDoFallbackMode) {
|
||||
return;
|
||||
}
|
||||
if (! this.canvasReadyForDrawWindow()) {
|
||||
// this means canvas needs to be resized, so we'll just re-run setup with all those new parameters
|
||||
this.stop();
|
||||
|
||||
var newCanvasWidth = window.innerHeight * (this.video.videoWidth / this.video.videoHeight);
|
||||
var newCanvasHeight = window.innerHeight;
|
||||
|
||||
if (this.conf.resizer.videoAlignment === VideoAlignment.Center) {
|
||||
this.canvasDrawWindowHOffset = Math.round((window.innerWidth - newCanvasWidth) * 0.5);
|
||||
} else if (this.conf.resizer.videoAlignment === VideoAlignment.Left) {
|
||||
this.canvasDrawWindowHOffset = 0;
|
||||
} else {
|
||||
this.canvasDrawWindowHOffset = window.innerWidth - newCanvasWidth;
|
||||
}
|
||||
|
||||
this.setup(newCanvasWidth, newCanvasHeight);
|
||||
|
||||
return;
|
||||
}
|
||||
// if this is the case, we'll first draw on canvas, as we'll need intermediate canvas if we want to get a
|
||||
// smaller sample for blackframe check
|
||||
this.fallbackMode = true;
|
||||
|
||||
try {
|
||||
this.context.drawWindow(window, this.canvasDrawWindowHOffset, 0, this.canvas.width, this.canvas.height, "rgba(0,0,128,1)");
|
||||
} catch (e) {
|
||||
console.log(`%c[ArDetect::frameCheck] can't draw image on canvas with fallback mode either. This error is prolly only temporary.`, "color:#000; backgroud:#f51;", e);
|
||||
return; // it's prolly just a fluke, so we do nothing special here
|
||||
}
|
||||
// draw blackframe sample from our main sample:
|
||||
this.blackframeContext.drawImage(this.canvas, this.blackframeCanvas.width, this.blackframeCanvas.height)
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("%c[ArDetect::frameCheck] canvas.drawImage seems to have worked", "color:#000; backgroud:#2f5;");
|
||||
}
|
||||
}
|
||||
|
||||
const bfanalysis = this.blackframeTest();
|
||||
if (bfanalysis.isBlack) {
|
||||
// we don't do any corrections on frames confirmed black
|
||||
if (Debug.debug && Debug.arDetect) {
|
||||
console.log("%c[ArDetect::frameCheck] Black frame analysis suggests this frame is black or too dark. Doing nothing,", "color: #fa3", bfanalysis);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// if (Debug.debug && Debug.arDetect) {
|
||||
// console.log("%c[ArDetect::frameCheck] Black frame analysis suggests this frame is not completely black. Doing further analysis,", "color: #3fa", bfanalysis);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// if we are in fallback mode, then frame has already been drawn to the main canvas.
|
||||
// if we are in normal mode though, the frame has yet to be drawn
|
||||
if (!this.fallbackMode) {
|
||||
this.context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
const imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height).data;
|
||||
|
||||
if (! this.fastLetterboxPresenceTest(imageData, sampleCols) ) {
|
||||
// Če ne zaznamo letterboxa, kličemo reset. Lahko, da je bilo razmerje stranic popravljeno na roke. Možno je tudi,
|
||||
// da je letterbox izginil.
|
||||
// If we don't detect letterbox, we reset aspect ratio to aspect ratio of the video file. The aspect ratio could
|
||||
// have been corrected manually. It's also possible that letterbox (that was there before) disappeared.
|
||||
this.conf.resizer.reset({type: AspectRatio.Automatic, ratio: null});
|
||||
this.guardLine.reset();
|
||||
this.noLetterboxCanvasReset = true;
|
||||
|
||||
|
||||
if (Debug.debug && Debug.arDetect) {
|
||||
console.log("%c[ArDetect::frameCheck] Letterbox not detected in fast test. Letterbox is either gone or we manually corrected aspect ratio. Nothing will be done.", "color: #fa3");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Če preverjamo naprej, potem moramo postaviti to vrednost nazaj na 'false'. V nasprotnem primeru se bo
|
||||
// css resetiral enkrat na video/pageload namesto vsakič, ko so za nekaj časa obrobe odstranejene
|
||||
// if we look further we need to reset this value back to false. Otherwise we'll only get CSS reset once
|
||||
// per video/pageload instead of every time letterbox goes away (this can happen more than once per vid)
|
||||
this.noLetterboxCanvasReset = false;
|
||||
|
||||
// poglejmo, če obrežemo preveč.
|
||||
// let's check if we're cropping too much
|
||||
const guardLineOut = this.guardLine.check(imageData, this.fallbackMode);
|
||||
|
||||
// če ni padla nobena izmed funkcij, potem se razmerje stranic ni spremenilo
|
||||
// if both succeed, then aspect ratio hasn't changed.
|
||||
if (!guardLineOut.imageFail && !guardLineOut.blackbarFail) {
|
||||
if(Debug.debug && Debug.debugArDetect){
|
||||
console.log(`%c[ArDetect::frameCheck] guardLine tests were successful. (no imagefail and no blackbarfail)\n`, "color: #afa", guardLineOut);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// drugače nadaljujemo, našemu vzorcu stolpcev pa dodamo tiste stolpce, ki so
|
||||
// kršili blackbar (če obstajajo) ter jih razvrstimo
|
||||
// otherwise we continue. We add blackbar violations to the list of the cols
|
||||
// we'll sample and sort them
|
||||
if (guardLineOut.blackbarFail) {
|
||||
sampleCols.concat(guardLineOut.offenders).sort((a, b) => a > b);
|
||||
}
|
||||
|
||||
// if we're in fallback mode and blackbar test failed, we restore CSS and quit
|
||||
// (since the new letterbox edge isn't present in our sample due to technical
|
||||
// limitations)
|
||||
if (this.fallbackMode && guardLineOut.blackbarFail) {
|
||||
this.conf.resizer.reset({type: AspectRatio.Automatic, ratio: null});
|
||||
this.guardLine.reset();
|
||||
this.noLetterboxCanvasReset = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// će se razmerje stranic spreminja iz ožjega na širšega, potem najprej poglejmo za prisotnostjo navpičnih črnih obrob.
|
||||
// če so prisotne navpične obrobe tudi na levi in desni strani, potlej obstaja možnost, da gre za logo na črnem ozadju.
|
||||
// v tem primeru obstaja nevarnost, da porežemo preveč. Ker obstaja dovolj velika možnost, da bi porezali preveč, rajši
|
||||
// ne naredimo ničesar.
|
||||
//
|
||||
// če je pillarbox zaznan v primeru spremembe iz ožjega na širše razmerje stranice, razmerje povrnemo na privzeto vrednost.
|
||||
//
|
||||
// If aspect ratio changes from narrower to wider, we first check for presence of pillarbox. Presence of pillarbox indicates
|
||||
// a chance of a logo on black background. We could cut easily cut too much. Because there's a somewhat significant chance
|
||||
// that we will cut too much, we rather avoid doing anything at all. There's gonna be a next chance.
|
||||
try{
|
||||
if(guardLineOut.blackbarFail || guardLineOut.imageFail){
|
||||
if(this.edgeDetector.findBars(imageData, null, EdgeDetectPrimaryDirection.HORIZONTAL).status === 'ar_known'){
|
||||
|
||||
if(Debug.debug && guardLineOut.blackbarFail){
|
||||
console.log("[ArDetect::frameCheck] Detected blackbar violation and pillarbox. Resetting to default aspect ratio.");
|
||||
}
|
||||
|
||||
if(guardLineOut.blackbarFail){
|
||||
this.conf.resizer.reset({type: AspectRatio.Automatic, ratio: null});
|
||||
this.guardLine.reset();
|
||||
}
|
||||
|
||||
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
if(Debug.debug) {
|
||||
console.log("[ArDetect.js::frameCheck] something went wrong when checking for pillarbox. Error:\n", e)
|
||||
}
|
||||
}
|
||||
|
||||
// pa poglejmo, kje se končajo črne letvice na vrhu in na dnu videa.
|
||||
// let's see where black bars end.
|
||||
this.sampleCols_current = sampleCols.length;
|
||||
|
||||
// blackSamples -> {res_top, res_bottom}
|
||||
|
||||
var edgePost = this.edgeDetector.findBars(imageData, sampleCols, EdgeDetectPrimaryDirection.VERTICAL, EdgeDetectQuality.IMPROVED, guardLineOut, bfanalysis);
|
||||
|
||||
if(Debug.debug && Debug.debugArDetect){
|
||||
console.log(`%c[ArDetect::frameCheck] edgeDetector returned this\n`, "color: #aaf", edgePost);
|
||||
}
|
||||
// console.log("SAMPLES:", blackbarSamples, "candidates:", edgeCandidates, "post:", edgePost,"\n\nblack level:", this.blackLevel, "tresh:", this.blackLevel + this.settings.active.arDetect.blackbar.threshold);
|
||||
|
||||
if (edgePost.status !== EdgeStatus.AR_KNOWN){
|
||||
// rob ni bil zaznan, zato ne naredimo ničesar.
|
||||
// no edge was detected. Let's leave things as they were
|
||||
|
||||
if (Debug.debug && Debug.arDetect) {
|
||||
console.log("%c[ArDetect::frameCheck] Edge wasn't detected with findBars", "color: #fa3", edgePost, "EdgeStatus.AR_KNOWN:", EdgeStatus.AR_KNOWN);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var newAr = this.calculateArFromEdges(edgePost);
|
||||
|
||||
// if (this.fallbackMode
|
||||
// && (!guardLineOut.blackbarFail && guardLineOut.imageFail)
|
||||
// && newAr < this.conf.resizer.getLastAr().ar
|
||||
// ) {
|
||||
// // V primeru nesmiselnih rezultatov tudi ne naredimo ničesar.
|
||||
// // v fallback mode se lahko naredi, da je novo razmerje stranice ožje kot staro, kljub temu da je šel
|
||||
// // blackbar test skozi. Spremembe v tem primeru ne dovolimo.
|
||||
// //
|
||||
// // (Pravilen fix? Popraviti je treba računanje robov. V fallback mode je treba upoštevati, da obrobe,
|
||||
// // ki smo jih obrezali, izginejo is canvasa)
|
||||
// //
|
||||
// // NOTE: pravilen fix je bil implementiran
|
||||
// return;
|
||||
// }
|
||||
|
||||
if(Debug.debug && Debug.debugArDetect){
|
||||
console.log(`%c[ArDetect::frameCheck] Triggering aspect ration change! new ar: ${newAr}`, "color: #aaf");
|
||||
}
|
||||
this.processAr(newAr);
|
||||
|
||||
// we also know edges for guardline, so set them.
|
||||
// we need to be mindful of fallbackMode though
|
||||
if (!this.fallbackMode) {
|
||||
this.guardLine.setBlackbar({top: edgePost.guardLineTop, bottom: edgePost.guardLineBottom});
|
||||
} else {
|
||||
if (this.conf.player.dimensions){
|
||||
this.guardLine.setBlackbarManual({
|
||||
top: this.settings.active.arDetect.fallbackMode.noTriggerZonePx,
|
||||
bottom: this.conf.player.dimensions.height - this.settings.active.arDetect.fallbackMode.noTriggerZonePx - 1
|
||||
},{
|
||||
top: edgePost.guardLineTop + this.settings.active.arDetect.guardLine.edgeTolerancePx,
|
||||
bottom: edgePost.guardLineBottom - this.settings.active.arDetect.guardLine.edgeTolerancePx
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// }
|
||||
// else{
|
||||
// console.log("detected text on edges, dooing nothing")
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
resetBlackLevel(){
|
||||
this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel;
|
||||
}
|
||||
|
||||
blackLevelTest_full() {
|
||||
|
||||
}
|
||||
|
||||
blackframeTest() {
|
||||
if (this.blackLevel === undefined) {
|
||||
if (Debug.debug && Debug.debugArDetect) {
|
||||
console.log("[ArDetect::frameCheck] black level undefined, resetting");
|
||||
}
|
||||
|
||||
this.resetBlackLevel();
|
||||
}
|
||||
|
||||
const rows = this.blackframeCanvas.height;
|
||||
const cols = this.blackframeCanvas.width;
|
||||
const pixels = rows * cols;
|
||||
let cumulative_r = 0, cumulative_g = 0, cumulative_b = 0;
|
||||
let max_r = 0, max_g = 0, max_b = 0;
|
||||
let avg_r, avg_g, avg_b;
|
||||
let var_r = 0, var_g = 0, var_b = 0;
|
||||
|
||||
let pixelMax = 0;
|
||||
let cumulativeValue = 0;
|
||||
let blackPixelCount = 0;
|
||||
const bfImageData = this.blackframeContext.getImageData(0, 0, cols, rows).data;
|
||||
const blackTreshold = this.blackLevel + this.settings.active.arDetect.blackbar.frameTreshold;
|
||||
|
||||
|
||||
// we do some recon for letterbox and pillarbox. While this can't determine whether letterbox/pillarbox exists
|
||||
// with sufficient level of certainty due to small sample resolution, it can still give us some hints for later
|
||||
let rowMax = new Array(rows).fill(0);
|
||||
let colMax = new Array(cols).fill(0);
|
||||
|
||||
let r, c;
|
||||
|
||||
|
||||
for (let i = 0; i < bfImageData.length; i+= 4) {
|
||||
pixelMax = Math.max(bfImageData[i], bfImageData[i+1], bfImageData[i+2]);
|
||||
bfImageData[i+3] = pixelMax;
|
||||
|
||||
if (pixelMax < blackTreshold) {
|
||||
if (pixelMax < this.blackLevel) {
|
||||
this.blackLevel = pixelMax;
|
||||
}
|
||||
blackPixelCount++;
|
||||
} else {
|
||||
cumulativeValue += pixelMax;
|
||||
cumulative_r += bfImageData[i];
|
||||
cumulative_g += bfImageData[i+1];
|
||||
cumulative_b += bfImageData[i+2];
|
||||
|
||||
max_r = max_r > bfImageData[i] ? max_r : bfImageData[i];
|
||||
max_g = max_g > bfImageData[i+1] ? max_g : bfImageData[i+1];
|
||||
max_b = max_b > bfImageData[i+2] ? max_b : bfImageData[i+2];
|
||||
}
|
||||
|
||||
r = ~~(i/rows);
|
||||
c = i % cols;
|
||||
|
||||
if (pixelMax > rowMax[r]) {
|
||||
rowMax[r] = pixelMax;
|
||||
}
|
||||
if (pixelMax > colMax[c]) {
|
||||
colMax[c] = colMax;
|
||||
}
|
||||
}
|
||||
|
||||
max_r = 1 / (max_r || 1);
|
||||
max_g = 1 / (max_g || 1);
|
||||
max_b = 1 / (max_b || 1);
|
||||
|
||||
const imagePixels = pixels - blackPixelCount;
|
||||
// calculate averages and normalize them
|
||||
avg_r = (cumulative_r / imagePixels) * max_r;
|
||||
avg_g = (cumulative_g / imagePixels) * max_g;
|
||||
avg_b = (cumulative_b / imagePixels) * max_b;
|
||||
|
||||
// second pass for color variance
|
||||
for (let i = 0; i < bfImageData.length; i+= 4) {
|
||||
if (bfImageData[i+3] >= this.blackLevel) {
|
||||
var_r += Math.abs(avg_r - bfImageData[i] * max_r);
|
||||
var_g += Math.abs(avg_g - bfImageData[i+1] * max_g);
|
||||
var_b += Math.abs(avg_b - bfImageData[i+1] * max_b);
|
||||
}
|
||||
}
|
||||
|
||||
const hasSufficientVariance = Math.abs(var_r - var_g) / Math.max(var_r, var_g, 1) > this.settings.active.arDetect.blackframe.sufficientColorVariance
|
||||
|| Math.abs(var_r - var_b) / Math.max(var_r, var_b, 1) > this.settings.active.arDetect.blackframe.sufficientColorVariance
|
||||
|| Math.abs(var_b - var_g) / Math.max(var_b, var_g, 1) > this.settings.active.arDetect.blackframe.sufficientColorVariance
|
||||
|
||||
let isBlack = (blackPixelCount/(cols * rows) > this.settings.active.arDetect.blackframe.blackPixelsCondition);
|
||||
|
||||
if (! isBlack) {
|
||||
if (hasSufficientVariance) {
|
||||
isBlack = cumulativeValue < this.settings.active.arDetect.blackframe.cumulativeThresholdLax;
|
||||
} else {
|
||||
isBlack = cumulativeValue < this.settings.active.arDetect.blackframe.cumulativeThresholdStrict;
|
||||
}
|
||||
}
|
||||
|
||||
if (Debug.debug) {
|
||||
return {
|
||||
isBlack: isBlack,
|
||||
blackPixelCount: blackPixelCount,
|
||||
blackPixelRatio: (blackPixelCount/(cols * rows)),
|
||||
cumulativeValue: cumulativeValue,
|
||||
hasSufficientVariance: hasSufficientVariance,
|
||||
blackLevel: this.blackLevel,
|
||||
variances: {
|
||||
raw: {
|
||||
r: var_r, g: var_g, b: var_b
|
||||
},
|
||||
relative: {
|
||||
rg: Math.abs(var_r - var_g) / Math.max(var_r, var_g, 1),
|
||||
rb: Math.abs(var_r - var_b) / Math.max(var_r, var_b, 1),
|
||||
gb: Math.abs(var_b - var_g) / Math.max(var_b, var_g, 1),
|
||||
},
|
||||
relativePercent: {
|
||||
rg: Math.abs(var_r - var_g) / Math.max(var_r, var_g, 1) / this.settings.active.arDetect.blackframe.sufficientColorVariance,
|
||||
rb: Math.abs(var_r - var_b) / Math.max(var_r, var_b, 1) / this.settings.active.arDetect.blackframe.sufficientColorVariance,
|
||||
gb: Math.abs(var_b - var_g) / Math.max(var_b, var_g, 1) / this.settings.active.arDetect.blackframe.sufficientColorVariance,
|
||||
},
|
||||
varianceLimit: this.settings.active.arDetect.blackframe.sufficientColorVariance,
|
||||
},
|
||||
cumulativeValuePercent: cumulativeValue / (hasSufficientVariance ? this.settings.active.arDetect.blackframe.cumulativeThresholdLax : this.settings.active.arDetect.blackframe.cumulativeThresholdStrict),
|
||||
rowMax: rowMax,
|
||||
colMax: colMax,
|
||||
};
|
||||
}
|
||||
return {
|
||||
isBlack: isBlack,
|
||||
rowMax: rowMax,
|
||||
colMax: colMax,
|
||||
};
|
||||
}
|
||||
|
||||
fastLetterboxPresenceTest(imageData, sampleCols) {
|
||||
// fast test to see if aspect ratio is correct.
|
||||
// returns 'true' if presence of letterbox is possible.
|
||||
// returns 'false' if we found a non-black edge pixel.
|
||||
|
||||
// If we detect anything darker than blackLevel, we modify blackLevel to the new lowest value
|
||||
const rowOffset = this.canvas.width * (this.canvas.height - 1);
|
||||
let currentMin = 255, currentMax = 0, colOffset_r, colOffset_g, colOffset_b, colOffset_rb, colOffset_gb, colOffset_bb, blthreshold = this.settings.active.arDetect.blackbar.threshold;
|
||||
|
||||
// detect black level. if currentMax comes above blackbar + blackbar threshold, we know we aren't letterboxed
|
||||
|
||||
for (var i = 0; i < sampleCols.length; ++i){
|
||||
colOffset_r = sampleCols[i] << 2;
|
||||
colOffset_g = colOffset_r + 1;
|
||||
colOffset_b = colOffset_r + 2;
|
||||
colOffset_rb = colOffset_r + rowOffset;
|
||||
colOffset_gb = colOffset_g + rowOffset;
|
||||
colOffset_bb = colOffset_b + rowOffset;
|
||||
|
||||
currentMax = Math.max(
|
||||
imageData[colOffset_r], imageData[colOffset_g], imageData[colOffset_b],
|
||||
// imageData[colOffset_rb], imageData[colOffset_gb], imageData[colOffset_bb],
|
||||
currentMax
|
||||
);
|
||||
|
||||
if (currentMax > this.blackLevel + blthreshold) {
|
||||
console.log("CURRENT MAX:", currentMax, "BLACK LEVEL, threshold, bl+t", this.blackLevel, blthreshold, this.blackLevel+blthreshold)
|
||||
// we search no further
|
||||
if (currentMin < this.blackLevel) {
|
||||
this.blackLevel = currentMin;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
currentMin = Math.min(
|
||||
currentMax,
|
||||
currentMin
|
||||
);
|
||||
}
|
||||
|
||||
if (currentMin < this.blackLevel)
|
||||
this.blackLevel = currentMin
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var _ard_console_stop = "background: #000; color: #f41";
|
||||
var _ard_console_start = "background: #000; color: #00c399";
|
||||
var _ard_console_change = "background: #000; color: #ff8";
|
||||
|
||||
export default ArDetector;
|
@ -116,7 +116,7 @@ class DebugCanvas {
|
||||
}
|
||||
|
||||
DebugCanvasClasses = {
|
||||
VIOLATION: {color: '#ff0000', colorRgb: [255, 00, 0], text: 'violation (general)'},
|
||||
VIOLATION: {color: '#ff0000', colorRgb: [255, 0, 0], text: 'violation (general)'},
|
||||
WARN: {color: '#d0d039', colorRgb: [208, 208, 57], text: 'lesser violation (general)'},
|
||||
GUARDLINE_BLACKBAR: {color: '#3333FF', colorRgb: [51, 51, 255], text: 'guardline/blackbar (expected value)'},
|
||||
GUARDLINE_IMAGE: {color: '#000088', colorRgb: [0, 0, 136], text: 'guardline/image (expected value)'},
|
@ -1,3 +1,5 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
|
||||
class GuardLine {
|
||||
|
||||
// ardConf — reference to ArDetector that has current GuardLine instance
|
||||
@ -51,7 +53,8 @@ class GuardLine {
|
||||
check(image, fallbackMode){
|
||||
// izračunaj enkrat in shrani na objekt
|
||||
// calculate once and save object-instance-wide
|
||||
this.blackbarTreshold = this.conf.blackLevel + this.settings.active.arDetect.blackbarTreshold;
|
||||
this.blackbarThreshold = this.conf.blackLevel + this.settings.active.arDetect.blackbar.threshold;
|
||||
this.imageThreshold = this.blackbarThreshold + this.settings.active.arDetect.blackbar.imageThreshold;
|
||||
|
||||
// dejansko testiranje
|
||||
// actual checks
|
||||
@ -98,7 +101,6 @@ class GuardLine {
|
||||
var offset = parseInt(this.conf.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
||||
|
||||
var offenders = [];
|
||||
var firstOffender = -1;
|
||||
var offenderCount = -1; // doing it this way means first offender has offenderCount==0. Ez index.
|
||||
|
||||
// TODO: implement logo check.
|
||||
@ -161,7 +163,7 @@ class GuardLine {
|
||||
if(!this.imageBar.top || !this.imageBar.bottom)
|
||||
return { success: false };
|
||||
|
||||
var offset = parseInt(this.conf.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
||||
var offset = (this.conf.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
||||
|
||||
// TODO: implement logo check.
|
||||
|
||||
@ -182,7 +184,7 @@ class GuardLine {
|
||||
// robu (eden izmed robov je lahko v celoti črn)
|
||||
// how many non-black pixels we need to consider this check a success. We only need to detect enough pixels
|
||||
// on one edge (one of the edges can be black as long as both aren't)
|
||||
var successTreshold = parseInt(this.conf.canvas.width * this.settings.active.arDetect.guardLine.imageTestTreshold);
|
||||
var successThreshold = (this.conf.canvas.width * this.settings.active.arDetect.guardLine.imageTestThreshold);
|
||||
var rowStart, rowEnd;
|
||||
|
||||
|
||||
@ -194,9 +196,9 @@ class GuardLine {
|
||||
var res = false;
|
||||
|
||||
if(Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine){
|
||||
res = this._ti_debugCheckRow(image, rowStart, rowEnd, successTreshold);
|
||||
res = this._ti_debugCheckRow(image, rowStart, rowEnd, successThreshold);
|
||||
} else {
|
||||
res = this._ti_checkRow(image, rowStart, rowEnd,successTreshold);
|
||||
res = this._ti_checkRow(image, rowStart, rowEnd,successThreshold);
|
||||
}
|
||||
|
||||
if(res)
|
||||
@ -210,9 +212,9 @@ class GuardLine {
|
||||
|
||||
|
||||
if(Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine){
|
||||
res = this._ti_debugCheckRow(image, rowStart, rowEnd, successTreshold);
|
||||
res = this._ti_debugCheckRow(image, rowStart, rowEnd, successThreshold);
|
||||
} else {
|
||||
res = this._ti_checkRow(image, rowStart, rowEnd,successTreshold);
|
||||
res = this._ti_checkRow(image, rowStart, rowEnd,successThreshold);
|
||||
}
|
||||
|
||||
return {success: res};
|
||||
@ -228,7 +230,7 @@ class GuardLine {
|
||||
|
||||
// we track sections that go over what's supposed to be a black line, so we can suggest more
|
||||
// columns to sample
|
||||
if(image[i] > this.blackbarTreshold || image[i+1] > this.blackbarTreshold || image[i+2] > this.blackbarTreshold){
|
||||
if(image[i] > this.blackbarThreshold || image[i+1] > this.blackbarThreshold || image[i+2] > this.blackbarThreshold){
|
||||
if(firstOffender < 0){
|
||||
firstOffender = (i - rowStart) >> 2;
|
||||
offenderCount++;
|
||||
@ -253,7 +255,7 @@ class GuardLine {
|
||||
|
||||
// we track sections that go over what's supposed to be a black line, so we can suggest more
|
||||
// columns to sample
|
||||
if(image[i] > this.blackbarTreshold || image[i+1] > this.blackbarTreshold || image[i+2] > this.blackbarTreshold){
|
||||
if(image[i] > this.blackbarThreshold || image[i+1] > this.blackbarThreshold || image[i+2] > this.blackbarThreshold){
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.VIOLATION);
|
||||
if(firstOffender < 0){
|
||||
firstOffender = (i - rowStart) >> 2;
|
||||
@ -275,10 +277,10 @@ class GuardLine {
|
||||
return offenderCount;
|
||||
}
|
||||
|
||||
_ti_checkRow(image, rowStart, rowEnd, successTreshold) {
|
||||
_ti_checkRow(image, rowStart, rowEnd, successThreshold) {
|
||||
for(var i = rowStart; i < rowEnd; i+=4){
|
||||
if(image[i] > this.blackbarTreshold || image[i+1] > this.blackbarTreshold || image[i+2] > this.blackbarTreshold){
|
||||
if(successTreshold --<= 0){
|
||||
if(image[i] > this.imageThreshold || image[i+1] > this.imageThreshold || image[i+2] > this.imageThreshold){
|
||||
if(successThreshold --<= 0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -287,11 +289,11 @@ class GuardLine {
|
||||
return false;
|
||||
}
|
||||
|
||||
_ti_debugCheckRow(image, rowStart, rowEnd, successTreshold) {
|
||||
_ti_debugCheckRow(image, rowStart, rowEnd, successThreshold) {
|
||||
for(var i = rowStart; i < rowEnd; i+=4){
|
||||
if(image[i] > this.blackbarTreshold || image[i+1] > this.blackbarTreshold || image[i+2] > this.blackbarTreshold){
|
||||
if(image[i] > this.imageThreshold || image[i+1] > this.imageThreshold || image[i+2] > this.imageThreshold){
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.GUARDLINE_IMAGE);
|
||||
if(successTreshold --<= 0){
|
||||
if(successThreshold --<= 0){
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
@ -302,3 +304,5 @@ class GuardLine {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default GuardLine;
|
1234
src/ext/lib/ar-detect/edge-detect/EdgeDetect.js
Normal file
1234
src/ext/lib/ar-detect/edge-detect/EdgeDetect.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,6 @@
|
||||
var EdgeDetectPrimaryDirection = Object.freeze({
|
||||
VERTICAL: 0,
|
||||
HORIZONTAL: 1
|
||||
});
|
||||
|
||||
export default EdgeDetectPrimaryDirection;
|
@ -0,0 +1,6 @@
|
||||
var EdgeDetectQuality = Object.freeze({
|
||||
FAST: 0,
|
||||
IMPROVED: 1
|
||||
});
|
||||
|
||||
export default EdgeDetectQuality;
|
@ -0,0 +1,6 @@
|
||||
var EdgeStatus = Object.freeze({
|
||||
AR_UNKNOWN: 0,
|
||||
AR_KNOWN: 1,
|
||||
});
|
||||
|
||||
export default EdgeStatus;
|
35
src/ext/lib/comms/Comms.js
Normal file
35
src/ext/lib/comms/Comms.js
Normal file
@ -0,0 +1,35 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
|
||||
class Comms {
|
||||
static async sendMessage(message){
|
||||
|
||||
if(BrowserDetect.firefox){
|
||||
return browser.runtime.sendMessage(message)
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
try{
|
||||
if(BrowserDetect.edge){
|
||||
browser.runtime.sendMessage(message, function(response){
|
||||
var r = response;
|
||||
resolve(r);
|
||||
});
|
||||
} else {
|
||||
chrome.runtime.sendMessage(message, function(response){
|
||||
// Chrome/js shittiness mitigation — remove this line and an empty array will be returned
|
||||
var r = response;
|
||||
resolve(r);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(e){
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Comms;
|
185
src/ext/lib/comms/CommsClient.js
Normal file
185
src/ext/lib/comms/CommsClient.js
Normal file
@ -0,0 +1,185 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
|
||||
class CommsClient {
|
||||
constructor(name, settings) {
|
||||
if (BrowserDetect.firefox) {
|
||||
this.port = browser.runtime.connect({name: name});
|
||||
} else if (BrowserDetect.chrome) {
|
||||
this.port = chrome.runtime.connect({name: name});
|
||||
} else if (BrowserDetect.edge) {
|
||||
this.port = browser.runtime.connect({name: name})
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
this._listener = m => ths.processReceivedMessage(m);
|
||||
this.port.onMessage.addListener(this._listener);
|
||||
|
||||
this.settings = settings;
|
||||
this.pageInfo = undefined;
|
||||
this.commsId = (Math.random() * 20).toFixed(0);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.pageInfo = null;
|
||||
this.settings = null;
|
||||
if (!BrowserDetect.edge) { // edge is a very special browser made by outright morons.
|
||||
this.port.onMessage.removeListener(this._listener);
|
||||
}
|
||||
}
|
||||
|
||||
setPageInfo(pageInfo){
|
||||
|
||||
this.pageInfo = pageInfo;
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log(`[CommsClient::setPageInfo] <${this.commsId}>`, "SETTING PAGEINFO —", this.pageInfo, this)
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
this._listener = m => ths.processReceivedMessage(m);
|
||||
if (!BrowserDetect.edge) {
|
||||
this.port.onMessage.removeListener(this._listener);
|
||||
}
|
||||
this.port.onMessage.addListener(this._listener);
|
||||
|
||||
}
|
||||
|
||||
processReceivedMessage(message){
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log(`[CommsClient.js::processMessage] <${this.commsId}> Received message from background script!`, message);
|
||||
}
|
||||
|
||||
if (!this.pageInfo || !this.settings.active) {
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log(`[CommsClient.js::processMessage] <${this.commsId}> this.pageInfo (or settings) not defined. Extension is probably disabled for this site.\npageInfo:`, this.pageInfo,
|
||||
"\nsettings.active:", this.settings.active,
|
||||
"\nnobj:", this
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.cmd === 'get-current-zoom') {
|
||||
this.pageInfo.requestCurrentZoom();
|
||||
}
|
||||
|
||||
if (message.cmd === "set-ar") {
|
||||
this.pageInfo.setAr({type: message.arg, ratio: message.customArg}, message.playing);
|
||||
} else if (message.cmd === 'set-alignment') {
|
||||
this.pageInfo.setvideoAlignment(message.arg, message.playing);
|
||||
this.pageInfo.restoreAr();
|
||||
} else if (message.cmd === "set-stretch") {
|
||||
this.pageInfo.setStretchMode(message.arg, message.playing);
|
||||
} else if (message.cmd === 'set-keyboard') {
|
||||
this.pageInfo.setKeyboardShortcutsEnabled(message.arg)
|
||||
} else if (message.cmd === "autoar-start") {
|
||||
if (message.enabled !== false) {
|
||||
this.pageInfo.initArDetection(message.playing);
|
||||
this.pageInfo.startArDetection(message.playing);
|
||||
} else {
|
||||
this.pageInfo.stopArDetection(message.playing);
|
||||
}
|
||||
} else if (message.cmd === "pause-processing") {
|
||||
this.pageInfo.pauseProcessing(message.playing);
|
||||
} else if (message.cmd === "resume-processing") {
|
||||
// todo: autoArStatus
|
||||
this.pageInfo.resumeProcessing(message.autoArStatus, message.playing);
|
||||
} else if (message.cmd === 'set-zoom') {
|
||||
this.pageInfo.setZoom(message.arg, true, message.playing);
|
||||
} else if (message.cmd === 'change-zoom') {
|
||||
this.pageInfo.zoomStep(message.arg, message.playing);
|
||||
} else if (message.cmd === 'mark-player') {
|
||||
this.pageInfo.markPlayer(message.name, message.color);
|
||||
} else if (message.cmd === 'unmark-player') {
|
||||
this.pageInfo.unmarkPlayer();
|
||||
} else if (message.cmd === 'autoar-set-manual-tick') {
|
||||
this.pageInfo.setManualTick(message.arg);
|
||||
} else if (message.cmd === 'autoar-tick') {
|
||||
this.pageInfo.tick();
|
||||
}
|
||||
}
|
||||
|
||||
async sleep(n){
|
||||
return new Promise( (resolve, reject) => setTimeout(resolve, n) );
|
||||
}
|
||||
|
||||
async sendMessage_nonpersistent(message){
|
||||
message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead
|
||||
|
||||
if(BrowserDetect.firefox){
|
||||
return browser.runtime.sendMessage(message)
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
try{
|
||||
if(BrowserDetect.edge){
|
||||
browser.runtime.sendMessage(message, function(response){
|
||||
var r = response;
|
||||
resolve(r);
|
||||
});
|
||||
} else {
|
||||
chrome.runtime.sendMessage(message, function(response){
|
||||
// Chrome/js shittiness mitigation — remove this line and an empty array will be returned
|
||||
var r = response;
|
||||
resolve(r);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(e){
|
||||
reject(e);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async requestSettings(){
|
||||
if(Debug.debug){
|
||||
console.log("%c[CommsClient::requestSettings] sending request for congif!", "background: #11D; color: #aad");
|
||||
}
|
||||
var response = await this.sendMessage_nonpersistent({cmd: 'get-config'});
|
||||
if(Debug.debug){
|
||||
console.log("%c[CommsClient::requestSettings] received settings response!", "background: #11D; color: #aad", response);
|
||||
}
|
||||
|
||||
if(! response || response.extensionConf){
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
this.settings.active = JSON.parse(response.extensionConf);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
registerVideo(){
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log(`[CommsClient::registerVideo] <${this.commsId}>`, "Registering video for current page.");
|
||||
}
|
||||
if (this.pageInfo) {
|
||||
if (this.pageInfo.videos.length) {
|
||||
this.port.postMessage({cmd: "has-video"});
|
||||
}
|
||||
} else {
|
||||
// this.port.postMessage({cmd: "has-video"});
|
||||
}
|
||||
}
|
||||
|
||||
sendPerformanceUpdate(message){
|
||||
this.port.postMessage({cmd: 'performance-update', message: message});
|
||||
}
|
||||
|
||||
unregisterVideo(){
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log(`[CommsClient::unregisterVideo] <${this.commsId}>`, "Unregistering video for current page.");
|
||||
}
|
||||
this.port.postMessage({cmd: "noVideo"}); // ayymd
|
||||
}
|
||||
|
||||
announceZoom(scale){
|
||||
this.port.postMessage({cmd: "announce-zoom", zoom: scale});
|
||||
this.registerVideo()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CommsClient;
|
272
src/ext/lib/comms/CommsServer.js
Normal file
272
src/ext/lib/comms/CommsServer.js
Normal file
@ -0,0 +1,272 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
|
||||
class CommsServer {
|
||||
constructor(server) {
|
||||
this.server = server;
|
||||
this.settings = server.settings;
|
||||
this.ports = [];
|
||||
|
||||
var ths = this;
|
||||
|
||||
// console.log("[CommsServer::ctor] INIT! are we in ff?", BrowserDetect.firefox, "BrowserDetect says ...", BrowserDetect)
|
||||
|
||||
if (BrowserDetect.firefox) {
|
||||
browser.runtime.onConnect.addListener(p => ths.onConnect(p));
|
||||
browser.runtime.onMessage.addListener(m => ths.processReceivedMessage_nonpersistent(m));
|
||||
} else {
|
||||
chrome.runtime.onConnect.addListener(p => ths.onConnect(p));
|
||||
chrome.runtime.onMessage.addListener((m, sender, callback) => ths.processReceivedMessage_nonpersistent(m, sender, callback));
|
||||
}
|
||||
}
|
||||
|
||||
async toObject(obj) {
|
||||
// console.log("(not actually) CLONING OBJECT", obj);
|
||||
// try {
|
||||
// const r = JSON.parse(JSON.stringify(obj));
|
||||
// return r;
|
||||
// } catch (e) {
|
||||
// console.log("ERROR WHILE CLONING", obj);
|
||||
return obj;
|
||||
// }
|
||||
}
|
||||
|
||||
async getCurrentTabHostname() {
|
||||
const activeTab = await this._getActiveTab();
|
||||
|
||||
const url = activeTab[0].url;
|
||||
|
||||
var hostname;
|
||||
|
||||
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
|
||||
hostname = url.split('/')[2];
|
||||
}
|
||||
else {
|
||||
hostname = url.split('/')[0];
|
||||
}
|
||||
|
||||
hostname = hostname.split(':')[0]; //find & remove port number
|
||||
hostname = hostname.split('?')[0]; //find & remove "?"
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
sendToAll(message){
|
||||
message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead
|
||||
for(var p of this.ports){
|
||||
for(var frame in p){
|
||||
p[frame].postMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _getActiveTab() {
|
||||
if (BrowserDetect.firefox) {
|
||||
return await browser.tabs.query({currentWindow: true, active: true});
|
||||
} else {
|
||||
return await new Promise( (resolve, reject) => {
|
||||
chrome.tabs.query({lastFocusedWindow: true, active: true}, function (res) {
|
||||
resolve(res);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async sendToFrame(message, tab, frame) {
|
||||
// message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log(`%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
||||
}
|
||||
|
||||
if (isNaN(tab)) {
|
||||
if (tab === '__playing') {
|
||||
message['playing'] = true;
|
||||
this.sendToAll(message);
|
||||
return;
|
||||
} else if (tab === '__all') {
|
||||
this.sendToAll(message);
|
||||
return;
|
||||
}
|
||||
[tab, frame] = tab.split('-')
|
||||
}
|
||||
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log(`%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
||||
}
|
||||
|
||||
try {
|
||||
this.ports[tab][frame].postMessage(message);
|
||||
} catch (e) {
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log(`%c[CommsServer::sendToFrame] Sending message failed. Reason:`, "background: #dda; color: #11D", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async sendToActive(message) {
|
||||
message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead
|
||||
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log("%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
|
||||
}
|
||||
|
||||
var tabs = await this._getActiveTab();
|
||||
|
||||
if(Debug.debug && Debug.comms){
|
||||
console.log("[CommsServer::_sendToActive] currently active tab(s)?", tabs);
|
||||
for (var key in this.ports[tabs[0].id]) {
|
||||
console.log("key?", key, this.ports[tabs[0].id]);
|
||||
}
|
||||
}
|
||||
|
||||
for (var key in this.ports[tabs[0].id]) {
|
||||
this.ports[tabs[0].id][key].postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
onConnect(port){
|
||||
var ths = this;
|
||||
|
||||
// poseben primer | special case
|
||||
if (port.name === 'popup-port') {
|
||||
this.popupPort = port;
|
||||
this.popupPort.onMessage.addListener( (m,p) => ths.processReceivedMessage(m,p));
|
||||
return;
|
||||
}
|
||||
|
||||
var tabId = port.sender.tab.id;
|
||||
var frameId = port.sender.frameId;
|
||||
if(! this.ports[tabId]){
|
||||
this.ports[tabId] = {};
|
||||
}
|
||||
this.ports[tabId][frameId] = port;
|
||||
this.ports[tabId][frameId].onMessage.addListener( (m,p) => ths.processReceivedMessage(m, p));
|
||||
this.ports[tabId][frameId].onDisconnect.addListener( (p) => {
|
||||
delete ths.ports[p.sender.tab.id][p.sender.frameId];
|
||||
if(Object.keys(ths.ports[p.sender.tab.id]).length === 0){
|
||||
ths.ports[tabId] = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async processReceivedMessage(message, port){
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[CommsServer.js::processReceivedMessage] Received message from popup/content script!", message, "port", port, "\nsettings and server:", this.settings,this.server);
|
||||
}
|
||||
|
||||
if (message.forwardToContentScript) {
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[CommsServer.js::processReceivedMessage] Message has 'forward to content script' flag set. Forwarding message as is. Message:", message);
|
||||
}
|
||||
|
||||
this.sendToFrame(message, message.targetTab, message.targetFrame);
|
||||
}
|
||||
if (message.forwardToAll) {
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[CommsServer.js::processReceivedMessage] Message has 'forward to all' flag set. Forwarding message as is. Message:", message);
|
||||
}
|
||||
this.sendToAll(message);
|
||||
}
|
||||
if (message.forwardToActive) {
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[CommsServer.js::processReceivedMessage] Message has 'forward to active' flag set. Forwarding message as is. Message:", message);
|
||||
}
|
||||
this.sendToActive(message)
|
||||
}
|
||||
|
||||
if (message.cmd === 'announce-zoom') {
|
||||
// forward off to the popup, no use for this here
|
||||
try {
|
||||
this.popupPort.postMessage({cmd: 'set-current-zoom', zoom: message.zoom});
|
||||
} catch (e) {
|
||||
// can't forward stuff to popup if it isn't open
|
||||
}
|
||||
} else if (message.cmd === 'get-current-zoom') {
|
||||
this.sendToActive(message);
|
||||
}
|
||||
|
||||
if (message.cmd === 'get-current-site') {
|
||||
port.postMessage({
|
||||
cmd: 'set-current-site',
|
||||
site: this.server.getVideoTab(),
|
||||
tabHostname: await this.getCurrentTabHostname()
|
||||
});
|
||||
}
|
||||
if (message.cmd === 'popup-set-selected-tab') {
|
||||
this.server.setSelectedTab(message.selectedMenu, message.selectedSubitem);
|
||||
}
|
||||
|
||||
if (message.cmd === 'get-config') {
|
||||
if(Debug.debug) {
|
||||
console.log("CommsServer: received get-config. Active settings?", this.settings.active, "\n(settings:", this.settings, ")")
|
||||
}
|
||||
port.postMessage(
|
||||
{cmd: "set-config", conf: this.settings.active, site: this.server.currentSite}
|
||||
);
|
||||
} else if (message.cmd === 'has-video') {
|
||||
this.server.registerVideo(port.sender);
|
||||
} else if (message.cmd === 'noVideo') {
|
||||
this.server.unregisterVideo(port.sender);
|
||||
}
|
||||
}
|
||||
|
||||
processReceivedMessage_nonpersistent(message, sender, sendResponse){
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("%c[CommsServer.js::processMessage_nonpersistent] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
|
||||
}
|
||||
|
||||
if (message.forwardToContentScript) {
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[CommsServer.js::processMessage_nonpersistent] Message has 'forward to content script' flag set. Forwarding message as is. Message:", message);
|
||||
console.log("[CommsServer.js::processMessage_nonpersistent] (btw we probably shouldn't be seeing this. This should prolly happen in persistent connection?");
|
||||
}
|
||||
|
||||
this.sendToFrame(message, message.targetFrame);
|
||||
}
|
||||
|
||||
if (message.cmd === 'get-config') {
|
||||
if (BrowserDetect.firefox) {
|
||||
var ret = {extensionConf: JSON.stringify(this.settings.active)};
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("%c[CommsServer.js::processMessage_nonpersistent] Returning this:", "background-color: #11D; color: #aad", ret);
|
||||
}
|
||||
Promise.resolve(ret);
|
||||
} else {
|
||||
sendResponse({extensionConf: JSON.stringify(this.settings.active)});
|
||||
return true;
|
||||
}
|
||||
} else if (message.cmd === "autoar-enable") {
|
||||
this.settings.active.sites['@global'].autoar = "blacklist";
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: "reload-settings", sender: "uwbg"})
|
||||
if(Debug.debug){
|
||||
console.log("[uw-bg] autoar set to enabled (blacklist). evidenz:", this.settings.active);
|
||||
}
|
||||
} else if (message.cmd === "autoar-disable") {
|
||||
this.settings.active.sites['@global'].autoar = "disabled";
|
||||
if(message.reason){
|
||||
this.settings.active.arDetect.disabledReason = message.reason;
|
||||
} else {
|
||||
this.settings.active.arDetect.disabledReason = 'User disabled';
|
||||
}
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: 'reload-settings', newConf: this.settings.active});
|
||||
if(Debug.debug){
|
||||
console.log("[uw-bg] autoar set to disabled. evidenz:", this.settings.active);
|
||||
}
|
||||
} else if (message.cmd === "autoar-set-interval") {
|
||||
if (Debug.debug) {
|
||||
console.log("[uw-bg] trying to set new interval for autoAr. New interval is",message.timeout,"ms");
|
||||
}
|
||||
// set fairly liberal limit
|
||||
var timeout = message.timeout < 4 ? 4 : message.timeout;
|
||||
this.settings.active.arDetect.timer_playing = timeout;
|
||||
this.settings.save();
|
||||
this.sendToAll({cmd: 'reload-settings', newConf: this.settings.active});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CommsServer;
|
488
src/ext/lib/video-data/PageInfo.js
Normal file
488
src/ext/lib/video-data/PageInfo.js
Normal file
@ -0,0 +1,488 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import VideoData from './VideoData';
|
||||
import RescanReason from './enums/RescanReason';
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("Loading: PageInfo.js");
|
||||
|
||||
|
||||
|
||||
class PageInfo {
|
||||
constructor(comms, settings, extensionMode){
|
||||
this.hasVideos = false;
|
||||
this.siteDisabled = false;
|
||||
this.videos = [];
|
||||
this.settings = settings;
|
||||
this.actionHandlerInitQueue = [];
|
||||
|
||||
this.lastUrl = window.location.href;
|
||||
this.extensionMode = extensionMode;
|
||||
|
||||
if(comms){
|
||||
this.comms = comms;
|
||||
}
|
||||
|
||||
this.rescan(RescanReason.PERIODIC);
|
||||
this.scheduleUrlCheck();
|
||||
|
||||
this.currentZoomScale = 1;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if(Debug.debug || Debug.init){
|
||||
console.log("[PageInfo::destroy] destroying all videos!")
|
||||
}
|
||||
if(this.rescanTimer){
|
||||
clearTimeout(this.rescanTimer);
|
||||
}
|
||||
for (var video of this.videos) {
|
||||
this.comms.unregisterVideo(video.id)
|
||||
video.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
for(var video of this.videos) {
|
||||
video.destroy();
|
||||
}
|
||||
this.rescan(RescanReason.MANUAL);
|
||||
}
|
||||
|
||||
initMouseActionHandler(videoData) {
|
||||
if (this.actionHandler) {
|
||||
this.actionHandler.registerHandleMouse(videoData);
|
||||
} else {
|
||||
this.actionHandlerInitQueue.push(videoData);
|
||||
}
|
||||
}
|
||||
|
||||
setActionHandler(actionHandler) {
|
||||
this.actionHandler = actionHandler;
|
||||
for (var item of this.actionHandlerInitQueue) {
|
||||
this.actionHandler.registerHandleMouse(item);
|
||||
}
|
||||
this.actionHandlerInitQueue = [];
|
||||
}
|
||||
|
||||
rescan(rescanReason){
|
||||
const oldVideoCount = this.videos.length;
|
||||
|
||||
try{
|
||||
var vids = document.getElementsByTagName('video');
|
||||
|
||||
if(!vids || vids.length == 0){
|
||||
this.hasVideos = false;
|
||||
|
||||
if(rescanReason == RescanReason.PERIODIC){
|
||||
this.scheduleRescan(RescanReason.PERIODIC);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// add new videos
|
||||
this.hasVideos = false;
|
||||
var videoExists = false;
|
||||
var video, v;
|
||||
|
||||
for (video of vids) {
|
||||
// če najdemo samo en video z višino in širino, to pomeni, da imamo na strani veljavne videe
|
||||
// če trenutni video nima definiranih teh vrednostih, preskočimo vse nadaljnja preverjanja
|
||||
// <===[:::::::]===>
|
||||
// if we find even a single video with width and height, that means the page has valid videos
|
||||
// if video lacks either of the two properties, we skip all further checks cos pointless
|
||||
if(video.offsetWidth && video.offsetHeight){
|
||||
this.hasVideos = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
videoExists = false;
|
||||
|
||||
for (v of this.videos) {
|
||||
if (v.destroyed) {
|
||||
continue; //TODO: if destroyed video is same as current video, copy aspect ratio settings to current video
|
||||
}
|
||||
|
||||
if (v.video == video) {
|
||||
videoExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (videoExists) {
|
||||
continue;
|
||||
} else {
|
||||
if (Debug.debug && Debug.periodic && Debug.videoRescan) {
|
||||
console.log("[PageInfo::rescan] found new video candidate:", video, "NOTE:: Video initialization starts here:\n--------------------------------\n")
|
||||
}
|
||||
v = new VideoData(video, this.settings, this);
|
||||
// console.log("[PageInfo::rescan] v is:", v)
|
||||
v.initArDetection();
|
||||
this.videos.push(v);
|
||||
|
||||
if(Debug.debug && Debug.periodic && Debug.videoRescan){
|
||||
console.log("[PageInfo::rescan] END VIDEO INITIALIZATION\n\n\n-------------------------------------\nvideos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.removeDestroyed();
|
||||
|
||||
// če smo ostali brez videev, potem odregistriraj stran.
|
||||
// če nismo ostali brez videev, potem registriraj stran.
|
||||
//
|
||||
// if we're left withotu videos on the current page, we unregister the page.
|
||||
// if we have videos, we call register.
|
||||
// if(Debug.debug) {
|
||||
// console.log("[PageInfo::rescan] Comms:", this.comms, "\nvideos.length:", this.videos.length, "\nold video count:", oldVideoCount)
|
||||
// }
|
||||
if (this.comms) {
|
||||
if (this.videos.length != oldVideoCount) { // only if number of videos changed, tho
|
||||
if (this.videos.length > 0) {
|
||||
this.comms.registerVideo({host: window.location.host, location: window.location});
|
||||
} else {
|
||||
this.comms.unregisterVideo({host: window.location.host, location: window.location});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
// če pride do zajeba, potem lahko domnevamo da na strani ni nobenega videa. Uničimo vse objekte videoData
|
||||
// da preprečimo večkratno inicializacijo. Če smo se z našim ugibom zmotili, potem se bodo vsi videi ponovno
|
||||
// našli ob naslednjem preiskovanju
|
||||
//
|
||||
// if we encounter a fuckup, we can assume that no videos were found on the page. We destroy all videoData
|
||||
// objects to prevent multiple initalization (which happened, but I don't know why). No biggie if we destroyed
|
||||
// videoData objects in error — they'll be back in the next rescan
|
||||
if (Debug.debug) {
|
||||
console.log("rescan error: — destroying all videoData objects",e);
|
||||
}
|
||||
for (const v of this.videos) {
|
||||
v.destroy();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(rescanReason == RescanReason.PERIODIC){
|
||||
this.scheduleRescan(RescanReason.PERIODIC);
|
||||
}
|
||||
}
|
||||
|
||||
removeDestroyed(){
|
||||
this.videos = this.videos.filter( vid => vid.destroyed === false);
|
||||
}
|
||||
|
||||
scheduleRescan(rescanReason){
|
||||
if(rescanReason != RescanReason.PERIODIC){
|
||||
this.rescan(rescanReason);
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
if(this.rescanTimer){
|
||||
clearTimeout(this.rescanTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
|
||||
|
||||
this.rescanTimer = setTimeout(function(rr){
|
||||
ths.rescanTimer = null;
|
||||
ths.rescan(rr);
|
||||
ths = null;
|
||||
}, rescanReason === this.settings.active.pageInfo.timeouts.rescan, RescanReason.PERIODIC)
|
||||
} catch(e) {
|
||||
if(Debug.debug){
|
||||
console.log("[PageInfo::scheduleRescan] scheduling rescan failed. Here's why:",e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scheduleUrlCheck() {
|
||||
try{
|
||||
if(this.urlCheckTimer){
|
||||
clearTimeout(this.urlCheckTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
|
||||
this.rescanTimer = setTimeout(function(){
|
||||
ths.rescanTimer = null;
|
||||
ths.ghettoUrlCheck();
|
||||
ths = null;
|
||||
}, this.settings.active.pageInfo.timeouts.urlCheck)
|
||||
}catch(e){
|
||||
if(Debug.debug){
|
||||
console.log("[PageInfo::scheduleUrlCheck] scheduling URL check failed. Here's why:",e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ghettoUrlCheck() {
|
||||
if (this.lastUrl != window.location.href){
|
||||
if(Debug.debug){
|
||||
console.log("[PageInfo::ghettoUrlCheck] URL has changed. Triggering a rescan!");
|
||||
}
|
||||
|
||||
this.rescan(RescanReason.URL_CHANGE);
|
||||
this.lastUrl = window.location.href;
|
||||
}
|
||||
|
||||
this.scheduleUrlCheck();
|
||||
}
|
||||
|
||||
initArDetection(playingOnly){
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if(vd.isPlaying()) {
|
||||
vd.initArDetection();
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
vd.initArDetection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// to je treba klicat ob menjavi zavihkov
|
||||
// these need to be called on tab switch
|
||||
pauseProcessing(playingOnly){
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.pause();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
vd.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resumeProcessing(resumeAutoar = false, playingOnly = false){
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.resume();
|
||||
if(resumeAutoar){
|
||||
vd.resumeAutoAr();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
vd.resume();
|
||||
if(resumeAutoar){
|
||||
vd.resumeAutoAr();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
startArDetection(playingOnly){
|
||||
if (Debug.debug) {
|
||||
console.log('[PageInfo::startArDetection()] starting automatic ar detection!')
|
||||
}
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if (video.isPlaying()) {
|
||||
vd.startArDetection();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
vd.startArDetection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopArDetection(playingOnly){
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.stopArDetection();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
vd.stopArDetection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAr(ar, playingOnly){
|
||||
if (Debug.debug) {
|
||||
console.log('[PageInfo::setAr] aspect ratio:', ar, "playing only?", playingOnly)
|
||||
}
|
||||
|
||||
if (ar.type !== AspectRatio.Automatic) {
|
||||
this.stopArDetection(playingOnly);
|
||||
} else {
|
||||
if (Debug.debug) {
|
||||
console.log('[PageInfo::setAr] aspect ratio is auto');
|
||||
}
|
||||
|
||||
try {
|
||||
for (var vd of this.videos) {
|
||||
if (!playingOnly || vd.isPlaying()) {
|
||||
vd.resetLastAr();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("???", e);
|
||||
}
|
||||
this.initArDetection(playingOnly);
|
||||
this.startArDetection(playingOnly);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: find a way to only change aspect ratio for one video
|
||||
if (ar === AspectRatio.Reset) {
|
||||
for (var vd of this.videos) {
|
||||
if (!playingOnly || vd.isPlaying()) {
|
||||
vd.resetAr();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var vd of this.videos) {
|
||||
if (!playingOnly || vd.isPlaying()) {
|
||||
vd.setAr(ar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setvideoAlignment(videoAlignment, playingOnly) {
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos) {
|
||||
if (vd.isPlaying()) {
|
||||
vd.setvideoAlignment(videoAlignment)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos) {
|
||||
vd.setvideoAlignment(videoAlignment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setPanMode(mode, playingOnly) {
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos) {
|
||||
if (vd.isPlaying()) {
|
||||
vd.setPanMode(mode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos) {
|
||||
vd.setPanMode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
restoreAr(playingOnly) {
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.restoreAr();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
vd.restoreAr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setStretchMode(sm, playingOnly){
|
||||
// TODO: find a way to only change aspect ratio for one video
|
||||
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.setStretchMode(sm)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
vd.setStretchMode(sm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setZoom(zoomLevel, no_announce, playingOnly) {
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos) {
|
||||
if (vd.isPlaying()) {
|
||||
vd.setZoom(zoomLevel, no_announce);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos) {
|
||||
vd.setZoom(zoomLevel, no_announce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoomStep(step, playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if (!playingOnly || vd.isPlaying()) {
|
||||
vd.zoomStep(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
markPlayer(name, color) {
|
||||
for (var vd of this.videos) {
|
||||
vd.markPlayer(name,color);
|
||||
}
|
||||
}
|
||||
unmarkPlayer() {
|
||||
for (var vd of this.videos) {
|
||||
vd.unmarkPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
announceZoom(scale) {
|
||||
if (this.announceZoomTimeout) {
|
||||
clearTimeout(this.announceZoom);
|
||||
}
|
||||
this.currentZoomScale = scale;
|
||||
const ths = this;
|
||||
this.announceZoomTimeout = setTimeout(() => ths.comms.announceZoom(scale), this.settings.active.zoom.announceDebounce);
|
||||
}
|
||||
|
||||
setManualTick(manualTick) {
|
||||
for(var vd of this.videos) {
|
||||
vd.setManualTick();
|
||||
}
|
||||
}
|
||||
|
||||
tick() {
|
||||
for(var vd of this.videos) {
|
||||
vd.tick();
|
||||
}
|
||||
}
|
||||
|
||||
sendPerformanceUpdate(performanceUpdate) {
|
||||
if(this.comms) {
|
||||
this.comms.sendPerformanceUpdate(performanceUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
requestCurrentZoom() {
|
||||
this.comms.announceZoom(this.currentZoomScale);
|
||||
}
|
||||
|
||||
setKeyboardShortcutsEnabled(state) {
|
||||
this.actionHandler.setKeybordLocal(state);
|
||||
}
|
||||
}
|
||||
|
||||
export default PageInfo;
|
@ -1,3 +1,7 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import ExtensionMode from '../../../common/enums/extension-mode.enum'
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("Loading: PlayerData.js");
|
||||
|
||||
@ -33,11 +37,14 @@ class PlayerData {
|
||||
this.videoData = videoData;
|
||||
this.video = videoData.video;
|
||||
this.settings = videoData.settings;
|
||||
this.extensionMode = videoData.extensionMode;
|
||||
this.element = undefined;
|
||||
this.dimensions = undefined;
|
||||
this.overlayNode = undefined;
|
||||
|
||||
|
||||
this.getPlayerDimensions();
|
||||
if (this.extensionMode === ExtensionMode.Enabled) {
|
||||
this.getPlayerDimensions();
|
||||
}
|
||||
this.startChangeDetection();
|
||||
}
|
||||
|
||||
@ -45,9 +52,6 @@ class PlayerData {
|
||||
return ( window.innerHeight == window.screen.height && window.innerWidth == window.screen.width);
|
||||
}
|
||||
|
||||
panListener(event) {
|
||||
this.panHandler(event);
|
||||
}
|
||||
|
||||
start(){
|
||||
this.startChangeDetection();
|
||||
@ -59,8 +63,8 @@ class PlayerData {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.element.removeEventListener('mousemove', this.panListener);
|
||||
this.stopChangeDetection();
|
||||
this.destroyOverlay();
|
||||
}
|
||||
|
||||
startChangeDetection(){
|
||||
@ -70,6 +74,58 @@ class PlayerData {
|
||||
clearTimeout(this.watchTimeout);
|
||||
}
|
||||
|
||||
makeOverlay() {
|
||||
if (!this.overlayNode) {
|
||||
this.destroyOverlay();
|
||||
}
|
||||
|
||||
var overlay = document.createElement('div');
|
||||
overlay.style.width = '100%';
|
||||
overlay.style.height = '100%';
|
||||
overlay.style.position = 'absolute';
|
||||
overlay.style.top = '0';
|
||||
overlay.style.left = '0';
|
||||
overlay.style.zIndex = '1000000000';
|
||||
overlay.style.pointerEvents = 'none';
|
||||
|
||||
this.overlayNode = overlay;
|
||||
this.element.appendChild(overlay);
|
||||
}
|
||||
|
||||
destroyOverlay() {
|
||||
if(this.playerIdElement) {
|
||||
this.playerIdElement.remove();
|
||||
this.playerIdElement = undefined;
|
||||
}
|
||||
if (this.overlayNode) {
|
||||
this.overlayNode.remove();
|
||||
this.overlayNode = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
markPlayer(name, color) {
|
||||
if (!this.overlayNode) {
|
||||
this.makeOverlay();
|
||||
}
|
||||
if (this.playerIdElement) {
|
||||
this.playerIdElement.remove();
|
||||
}
|
||||
this.playerIdElement = document.createElement('div');
|
||||
this.playerIdElement.innerHTML = `<div style="background-color: ${color}; color: #fff; position: absolute; top: 0; left: 0">${name}</div>`;
|
||||
|
||||
this.overlayNode.appendChild(this.playerIdElement);
|
||||
}
|
||||
|
||||
unmarkPlayer() {
|
||||
if (Debug.debug) {
|
||||
console.log("[PlayerData::unmarkPlayer] unmarking player!")
|
||||
}
|
||||
if (this.playerIdElement) {
|
||||
this.playerIdElement.remove();
|
||||
}
|
||||
this.playerIdElement = undefined;
|
||||
}
|
||||
|
||||
scheduleGhettoWatcher(timeout, force_reset) {
|
||||
if(! timeout){
|
||||
timeout = 100;
|
||||
@ -102,8 +158,7 @@ class PlayerData {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
ghettoWatcher(){
|
||||
ghettoWatcherFull() {
|
||||
if(this.checkPlayerSizeChange()){
|
||||
if(Debug.debug){
|
||||
console.log("[uw::ghettoOnChange] change detected");
|
||||
@ -111,13 +166,10 @@ class PlayerData {
|
||||
|
||||
this.getPlayerDimensions();
|
||||
if(! this.element ){
|
||||
this.scheduleGhettoWatcher();
|
||||
return;
|
||||
}
|
||||
|
||||
this.videoData.resizer.restore(); // note: this returns true if change goes through, false otherwise.
|
||||
|
||||
this.scheduleGhettoWatcher();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -134,20 +186,45 @@ class PlayerData {
|
||||
this.getPlayerDimensions();
|
||||
|
||||
if(! this.element ){
|
||||
this.scheduleGhettoWatcher();
|
||||
return;
|
||||
}
|
||||
|
||||
this.videoData.resizer.restore();
|
||||
}
|
||||
|
||||
this.scheduleGhettoWatcher();
|
||||
}
|
||||
|
||||
panHandler(event) {
|
||||
this.videoData.panHandler(event);
|
||||
ghettoWatcherBasic() {
|
||||
if (this.checkFullscreenChange()) {
|
||||
if (PlayerData.isFullScreen()) {
|
||||
const lastAr = this.videoData.resizer.getLastAr(); // save last ar for restore later
|
||||
|
||||
this.videoData.resizer.restore();
|
||||
|
||||
if (lastAr.type === 'original' || lastAr.type === AspectRatio.Automatic) {
|
||||
this.videoData.rebootArDetection();
|
||||
}
|
||||
} else {
|
||||
const lastAr = this.videoData.resizer.getLastAr(); // save last ar for restore later
|
||||
this.videoData.resizer.reset();
|
||||
this.videoData.resizer.stop();
|
||||
this.videoData.stopArDetection();
|
||||
this.videoData.resizer.setLastAr(lastAr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ghettoWatcher(){
|
||||
if (this.extensionMode === ExtensionMode.Enabled) {
|
||||
this.ghettoWatcherFull();
|
||||
this.scheduleGhettoWatcher();
|
||||
} else if (this.extensionMode === ExtensionMode.Basic) {
|
||||
this.ghettoWatcherBasic();
|
||||
this.scheduleGhettoWatcher();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
getPlayerDimensions(elementNames){
|
||||
// element names — reserved for future use. If element names are provided, this function should return first element that
|
||||
// has classname or id that matches at least one in the elementNames array.
|
||||
@ -159,7 +236,6 @@ class PlayerData {
|
||||
|
||||
if(this.element) {
|
||||
const ths = this;
|
||||
this.element.removeEventListener('mousemove', this.panListener);
|
||||
}
|
||||
this.element = undefined;
|
||||
this.dimensions = undefined;
|
||||
@ -226,11 +302,11 @@ class PlayerData {
|
||||
fullscreen: true
|
||||
}
|
||||
const ths = this;
|
||||
if(this.element) {
|
||||
this.element.removeEventListener('mousemove', (event) => ths.panListener);
|
||||
|
||||
if (this.element != element) {
|
||||
this.element = element;
|
||||
this.makeOverlay()
|
||||
}
|
||||
this.element = element;
|
||||
this.element.addEventListener('mousemove', ths.panListener);
|
||||
} else {
|
||||
this.dimensions = {
|
||||
width: candidate_width,
|
||||
@ -238,11 +314,10 @@ class PlayerData {
|
||||
fullscreen: isFullScreen
|
||||
};
|
||||
const ths = this;
|
||||
if(this.element) {
|
||||
this.element.removeEventListener('mousemove', (event) => ths.panListener);
|
||||
if(this.element != playerCandidateNode) {
|
||||
this.element = playerCandidateNode;
|
||||
this.makeOverlay();
|
||||
}
|
||||
this.element = playerCandidateNode;
|
||||
this.element.addEventListener('mousemove', (event) => ths.panListener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,5 +355,34 @@ class PlayerData {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
checkFullscreenChange() {
|
||||
const isFs = PlayerData.isFullScreen();
|
||||
|
||||
if (this.dimensions) {
|
||||
if (this.dimensions.fullscreen != isFs) {
|
||||
this.dimensions = {
|
||||
fullscreen: isFs,
|
||||
width: isFs ? screen.width : this.video.offsetWidth,
|
||||
height: isFs ? screen.height : this.video.offsetHeight
|
||||
};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[PlayerData::checkFullscreenChange] this.dimensions is not defined. Assuming fs change happened and setting default values.")
|
||||
}
|
||||
|
||||
this.dimensions = {
|
||||
fullscreen: isFs,
|
||||
width: isFs ? screen.width : this.video.offsetWidth,
|
||||
height: isFs ? screen.height : this.video.offsetHeight
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default PlayerData;
|
@ -1,3 +1,8 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import PlayerData from './PlayerData';
|
||||
import Resizer from '../video-transform/Resizer';
|
||||
import ArDetector from '../ar-detect/ArDetector';
|
||||
|
||||
class VideoData {
|
||||
|
||||
constructor(video, settings, pageInfo){
|
||||
@ -5,6 +10,10 @@ class VideoData {
|
||||
this.video = video;
|
||||
this.destroyed = false;
|
||||
this.settings = settings;
|
||||
this.pageInfo = pageInfo;
|
||||
this.extensionMode = pageInfo.extensionMode;
|
||||
|
||||
|
||||
// POZOR: VRSTNI RED JE POMEMBEN (arDetect mora bit zadnji)
|
||||
// NOTE: ORDERING OF OBJ INITIALIZATIONS IS IMPORTANT (arDetect needs to go last)
|
||||
this.player = new PlayerData(this);
|
||||
@ -13,11 +22,20 @@ class VideoData {
|
||||
this.arDetector = new ArDetector(this); // this starts Ar detection. needs optional parameter that prevets ardetdctor from starting
|
||||
// player dimensions need to be in:
|
||||
// this.player.dimensions
|
||||
this.pageInfo = pageInfo;
|
||||
|
||||
// apply default align and stretch
|
||||
if (Debug.init) {
|
||||
console.log("%c[VideoData::ctor] Initial resizer reset!", {background: '#afd', color: '#132'});
|
||||
}
|
||||
this.resizer.reset();
|
||||
|
||||
|
||||
this.vdid = (Math.random()*100).toFixed();
|
||||
if (Debug.init) {
|
||||
console.log("[VideoData::ctor] Created videoData with vdid", this.vdid);
|
||||
console.log("[VideoData::ctor] Created videoData with vdid", this.vdid,"\nextension mode:", this.extensionMode);
|
||||
}
|
||||
|
||||
this.pageInfo.initMouseActionHandler(this);
|
||||
}
|
||||
|
||||
firstTimeArdInit(){
|
||||
@ -43,6 +61,9 @@ class VideoData {
|
||||
}
|
||||
|
||||
startArDetection() {
|
||||
if (Debug.debug) {
|
||||
console.log("[VideoData::startArDetection] starting AR detection")
|
||||
}
|
||||
if(this.destroyed) {
|
||||
throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
||||
}
|
||||
@ -52,6 +73,13 @@ class VideoData {
|
||||
this.arDetector.start();
|
||||
}
|
||||
|
||||
rebootArDetection() {
|
||||
if(this.destroyed) {
|
||||
throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
||||
}
|
||||
this.arDetector.init();
|
||||
}
|
||||
|
||||
stopArDetection() {
|
||||
if (this.arDetector) {
|
||||
this.arDetector.stop();
|
||||
@ -101,7 +129,9 @@ class VideoData {
|
||||
this.paused = false;
|
||||
try {
|
||||
this.resizer.start();
|
||||
this.player.start();
|
||||
if (this.player) {
|
||||
this.player.start();
|
||||
}
|
||||
} catch (e) {
|
||||
if(Debug.debug){
|
||||
console.log("[VideoData.js::resume] cannot resume for reasons. Will destroy videoData. Error here:", e);
|
||||
@ -116,6 +146,18 @@ class VideoData {
|
||||
}
|
||||
}
|
||||
|
||||
setManualTick(manualTick) {
|
||||
if(this.arDetector){
|
||||
this.arDetector.setManualTick(manualTick);
|
||||
}
|
||||
}
|
||||
|
||||
tick() {
|
||||
if(this.arDetector){
|
||||
this.arDetector.tick();
|
||||
}
|
||||
}
|
||||
|
||||
setLastAr(lastAr){
|
||||
this.resizer.setLastAr(lastAr);
|
||||
}
|
||||
@ -128,7 +170,11 @@ class VideoData {
|
||||
this.resizer.reset();
|
||||
}
|
||||
|
||||
panHandler(event) {
|
||||
resetLastAr() {
|
||||
this.resizer.setLastAr('original');
|
||||
}
|
||||
|
||||
panHandler(event, forcePan) {
|
||||
if(this.destroyed) {
|
||||
throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
||||
}
|
||||
@ -136,15 +182,15 @@ class VideoData {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
this.resizer.panHandler(event);
|
||||
this.resizer.panHandler(event, forcePan);
|
||||
}
|
||||
|
||||
setPanMode(mode) {
|
||||
this.resizer.setPanMode(mode);
|
||||
}
|
||||
|
||||
setVideoFloat(videoFloat) {
|
||||
this.resizer.setVideoFloat(videoFloat);
|
||||
setvideoAlignment(videoAlignment) {
|
||||
this.resizer.setvideoAlignment(videoAlignment);
|
||||
}
|
||||
|
||||
restoreAr(){
|
||||
@ -166,4 +212,24 @@ class VideoData {
|
||||
announceZoom(scale){
|
||||
this.pageInfo.announceZoom(scale);
|
||||
}
|
||||
}
|
||||
|
||||
markPlayer(name, color) {
|
||||
if (this.player) {
|
||||
this.player.markPlayer(name, color)
|
||||
}
|
||||
}
|
||||
unmarkPlayer() {
|
||||
this.player.unmarkPlayer();
|
||||
}
|
||||
|
||||
isPlaying() {
|
||||
|
||||
console.log("is playing? video:", this.video, "ctime:", this.video.currentTime,
|
||||
"paused/ended:", this.video.paused, this.video.ended,
|
||||
"is playing?", this.video && this.video.currentTime > 0 && !this.video.paused && !this.video.ended);
|
||||
|
||||
return this.video && this.video.currentTime > 0 && !this.video.paused && !this.video.ended;
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoData;
|
7
src/ext/lib/video-data/enums/RescanReason.js
Normal file
7
src/ext/lib/video-data/enums/RescanReason.js
Normal file
@ -0,0 +1,7 @@
|
||||
var RescanReason = Object.freeze({
|
||||
PERIODIC: 0,
|
||||
URL_CHANGE: 1,
|
||||
MANUAL: 2
|
||||
});
|
||||
|
||||
export default RescanReason;
|
676
src/ext/lib/video-transform/Resizer.js
Normal file
676
src/ext/lib/video-transform/Resizer.js
Normal file
@ -0,0 +1,676 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import Scaler from './Scaler';
|
||||
import Stretcher from './Stretcher';
|
||||
import Zoom from './Zoom';
|
||||
import PlayerData from '../video-data/PlayerData';
|
||||
import ExtensionMode from '../../../common/enums/extension-mode.enum';
|
||||
import Stretch from '../../../common/enums/stretch.enum';
|
||||
import VideoAlignment from '../../../common/enums/video-alignment.enum';
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("Loading: Resizer.js");
|
||||
}
|
||||
|
||||
class Resizer {
|
||||
|
||||
constructor(videoData) {
|
||||
this.conf = videoData;
|
||||
this.video = videoData.video;
|
||||
this.settings = videoData.settings;
|
||||
this.extensionMode = videoData.extensionMode;
|
||||
|
||||
this.scaler = new Scaler(this.conf);
|
||||
this.stretcher = new Stretcher(this.conf);
|
||||
this.zoom = new Zoom(this.conf);
|
||||
|
||||
this.cssCheckDisabled = false;
|
||||
|
||||
// load up default values
|
||||
this.correctedVideoDimensions = {};
|
||||
this.currentCss = {};
|
||||
this.currentStyleString = "";
|
||||
this.currentCssValidFor = {};
|
||||
|
||||
// restore watchdog. While true, applyCss() tries to re-apply new css until this value becomes false again
|
||||
// value becomes false when width and height of <video> tag match with what we want to set. Only necessary when
|
||||
// calling _res_restore() for some weird reason.
|
||||
this.restore_wd = false;
|
||||
|
||||
// CSS watcher will trigger _very_ often for this many iterations
|
||||
this.cssWatcherIncreasedFrequencyCounter = 0;
|
||||
|
||||
|
||||
// this.lastAr = this.settings.getDefaultAr(); // this is the aspect ratio we start with
|
||||
this.lastAr = {type: AspectRatio.Initial};
|
||||
this.videoAlignment = this.settings.getDefaultVideoAlignment(window.location.hostname); // this is initial video alignment
|
||||
this.destroyed = false;
|
||||
|
||||
this.resizerId = (Math.random(99)*100).toFixed(0);
|
||||
|
||||
if (this.settings.active.pan) {
|
||||
console.log("can pan:", this.settings.active.miscSettings.mousePan.enabled, "(default:", this.settings.active.miscSettings.mousePan.enabled, ")")
|
||||
this.canPan = this.settings.active.miscSettings.mousePan.enabled;
|
||||
} else {
|
||||
this.canPan = false;
|
||||
}
|
||||
}
|
||||
|
||||
start(){
|
||||
if(!this.destroyed) {
|
||||
this.startCssWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
stop(){
|
||||
this.stopCssWatcher();
|
||||
}
|
||||
|
||||
destroy(){
|
||||
if(Debug.debug || Debug.init){
|
||||
console.log(`[Resizer::destroy] <rid:${this.resizerId}> received destroy command.`);
|
||||
}
|
||||
this.destroyed = true;
|
||||
this.stopCssWatcher();
|
||||
}
|
||||
|
||||
calculateRatioForLegacyOptions(ar){
|
||||
// also present as modeToAr in Scaler.js
|
||||
if (ar.type !== AspectRatio.FitWidth && ar.type !== AspectRatio.FitHeight && ar.ratio) {
|
||||
return ar;
|
||||
}
|
||||
// Skrbi za "stare" možnosti, kot na primer "na širino zaslona", "na višino zaslona" in "ponastavi".
|
||||
// Približevanje opuščeno.
|
||||
// handles "legacy" options, such as 'fit to widht', 'fit to height' and AspectRatio.Reset. No zoom tho
|
||||
var ratioOut;
|
||||
|
||||
if (!this.conf.video) {
|
||||
if (Debug.debug) {
|
||||
console.log("[Scaler.js::modeToAr] No video??",this.conf.video, "killing videoData");
|
||||
}
|
||||
this.conf.destroy();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (! this.conf.player.dimensions) {
|
||||
ratioOut = screen.width / screen.height;
|
||||
} else {
|
||||
if (Debug.debug && Debug.debugResizer) {
|
||||
console.log(`[Resizer::calculateRatioForLegacyOptions] <rid:${this.resizerId}> Player dimensions:`, this.conf.player.dimensions.width ,'x', this.conf.player.dimensions.height,'aspect ratio:', this.conf.player.dimensions.width / this.conf.player.dimensions.height)
|
||||
}
|
||||
ratioOut = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
}
|
||||
|
||||
// POMEMBNO: lastAr je potrebno nastaviti šele po tem, ko kličemo _res_setAr(). _res_setAr() predvideva,
|
||||
// da želimo nastaviti statično (type: 'static') razmerje stranic — tudi, če funkcijo kličemo tu oz. v ArDetect.
|
||||
//
|
||||
// IMPORTANT NOTE: lastAr needs to be set after _res_setAr() is called, as _res_setAr() assumes we're
|
||||
// setting a static aspect ratio (even if the function is called from here or ArDetect).
|
||||
|
||||
var fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
|
||||
if (ar.type === AspectRatio.FitWidth){
|
||||
ar.ratio = ratioOut > fileAr ? ratioOut : fileAr;
|
||||
}
|
||||
else if(ar.type === AspectRatio.FitHeight){
|
||||
ar.ratio = ratioOut < fileAr ? ratioOut : fileAr;
|
||||
}
|
||||
else if(ar.type === AspectRatio.Reset){
|
||||
if(Debug.debug){
|
||||
console.log("[Scaler.js::modeToAr] Using original aspect ratio -", fileAr);
|
||||
}
|
||||
ar.ratio = fileAr;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ar;
|
||||
}
|
||||
|
||||
|
||||
setAr(ar, lastAr){
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(Debug.debug){
|
||||
console.log('[Resizer::setAr] <rid:'+this.resizerId+'> trying to set ar. New ar:', ar)
|
||||
}
|
||||
|
||||
if (ar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ar.type === AspectRatio.Automatic ||
|
||||
ar.type === AspectRatio.Reset && this.lastAr.type === AspectRatio.Initial) {
|
||||
// some sites do things that interfere with our site (and aspect ratio setting in general)
|
||||
// first, we check whether video contains anything we don't like
|
||||
|
||||
const siteSettings = this.settings.active.sites[window.location.host];
|
||||
if (siteSettings && siteSettings.autoarPreventConditions) {
|
||||
if (siteSettings.autoarPreventConditions.videoStyleString) {
|
||||
const styleString = (this.video.getAttribute('style') || '').split(';');
|
||||
|
||||
if (siteSettings.autoarPreventConditions.videoStyleString.containsProperty) {
|
||||
const bannedProperties = siteSettings.autoarPreventConditions.videoStyleString.containsProperty;
|
||||
for (const prop in bannedProperties) {
|
||||
for (const s of styleString) {
|
||||
if (s.trim().startsWith(prop)) {
|
||||
|
||||
// check if css property has a list of allowed values:
|
||||
if (bannedProperties[prop].allowedValues) {
|
||||
const styleValue = s.split(':')[1].trim();
|
||||
|
||||
// check if property value is on the list of allowed values
|
||||
// if it's not, we aren't allowed to start aard
|
||||
if (bannedProperties[prop].allowedValues.indexOf(styleValue) === -1) {
|
||||
if (Debug.debug) {
|
||||
console.log("%c[Resizer::setAr] video style contains forbidden css property/value combo: ", "color: #900, background: #100", prop, " — we aren't allowed to start autoar.")
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// no allowed values, no problem. We have forbidden property
|
||||
// and this means aard can't start.
|
||||
if (Debug.debug) {
|
||||
console.log("%c[Resizer::setAr] video style contains forbidden css property: ", "color: #900, background: #100", prop, " — we aren't allowed to start autoar.")
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastAr) {
|
||||
this.lastAr = this.calculateRatioForLegacyOptions(lastAr);
|
||||
ar = this.calculateRatioForLegacyOptions(ar);
|
||||
} else {
|
||||
// NOTE: "fitw" "fith" and "reset" should ignore ar.ratio bit, but
|
||||
// I'm not sure whether they do. Check that.
|
||||
ar = this.calculateRatioForLegacyOptions(ar);
|
||||
if (! ar) {
|
||||
if (Debug.debug && Debug.debugResizer) {
|
||||
console.log(`[Resizer::setAr] <${this.resizerId}> Something wrong with ar or the player. Doing nothing.`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.lastAr = {type: ar.type, ratio: ar.ratio}
|
||||
}
|
||||
|
||||
if (this.extensionMode === ExtensionMode.Basic && !PlayerData.isFullScreen() && ar.type !== AspectRatio.Reset) {
|
||||
// don't actually apply or calculate css when using basic mode if not in fullscreen
|
||||
// ... unless we're resetting the aspect ratio to original
|
||||
return;
|
||||
}
|
||||
|
||||
if (! this.video) {
|
||||
// console.log("No video detected.")
|
||||
this.videoData.destroy();
|
||||
}
|
||||
|
||||
if (this.extensionMode === ExtensionMode.Enabled || PlayerData.isFullScreen()) {
|
||||
this.startCssWatcher();
|
||||
}
|
||||
this.cssWatcherIncreasedFrequencyCounter = 20;
|
||||
|
||||
// // pause AR on basic stretch, unpause when using other mdoes
|
||||
// fir sine reason unpause doesn't unpause. investigate that later
|
||||
try {
|
||||
if (this.stretcher.mode === Stretch.Basic) {
|
||||
this.conf.arDetector.pause();
|
||||
} else {
|
||||
if (this.lastAr.type === AspectRatio.Automatic) {
|
||||
this.conf.arDetector.unpause();
|
||||
}
|
||||
}
|
||||
} catch (e) { // resizer starts before arDetector. this will do nothing but fail if arDetector isn't setup
|
||||
|
||||
}
|
||||
|
||||
// do stretch thingy
|
||||
if (this.stretcher.mode === Stretch.NoStretch || this.stretcher.mode === Stretch.Conditional){
|
||||
var stretchFactors = this.scaler.calculateCrop(ar);
|
||||
|
||||
if(! stretchFactors || stretchFactors.error){
|
||||
if(Debug.debug){
|
||||
console.log("[Resizer::setAr] <rid:"+this.resizerId+"> failed to set AR due to problem with calculating crop. Error:", (stretchFactors ? stretchFactors.error : stretchFactors));
|
||||
}
|
||||
if (stretchFactors.error === 'no_video'){
|
||||
this.conf.destroy();
|
||||
}
|
||||
if (stretchFactors.error === 'illegal_video_dimensions') {
|
||||
if(Debug.debug){
|
||||
console.log("[Resizer::setAr] <rid:"+this.resizerId+"> Illegal video dimensions found. We will pause everything.");
|
||||
}
|
||||
// if we get illegal video dimensions, cssWatcher goes nuts. This is harmful,
|
||||
// so we stop it until that sorts itself out
|
||||
this.stopCssWatcher();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
this.startCssWatcher();
|
||||
}
|
||||
if(this.stretcher.mode === Stretch.Conditional){
|
||||
this.stretcher.applyConditionalStretch(stretchFactors, ar.ratio);
|
||||
}
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("[Resizer::setAr] Processed stretch factors for ", this.stretcher.mode === Stretch.NoStretch ? 'stretch-free crop.' : 'crop with conditional stretch.', 'Stretch factors are:', stretchFactors);
|
||||
}
|
||||
|
||||
} else if (this.stretcher.mode === Stretch.Hybrid) {
|
||||
var stretchFactors = this.stretcher.calculateStretch(ar.ratio);
|
||||
if (Debug.debug) {
|
||||
console.log('[Resizer::setAr] Processed stretch factors for hybrid stretch/crop. Stretch factors are:', stretchFactors);
|
||||
}
|
||||
} else if (this.stretcher.mode === Stretch.Basic) {
|
||||
var stretchFactors = this.stretcher.calculateBasicStretch();
|
||||
if (Debug.debug) {
|
||||
console.log('[Resizer::setAr] Processed stretch factors for basic stretch. Stretch factors are:', stretchFactors);
|
||||
}
|
||||
} else {
|
||||
var stretchFactors = {xFactor: 1, yFactor: 1}
|
||||
if (Debug.debug) {
|
||||
console.log('[Resizer::setAr] Okay wtf happened? If you see this, something has gone wrong', stretchFactors,"\n------[ i n f o d u m p ]------\nstretcher:", this.stretcher);
|
||||
}
|
||||
}
|
||||
|
||||
this.zoom.applyZoom(stretchFactors);
|
||||
|
||||
//TODO: correct these two
|
||||
var translate = this.computeOffsets(stretchFactors);
|
||||
this.applyCss(stretchFactors, translate);
|
||||
|
||||
}
|
||||
|
||||
resetLastAr() {
|
||||
this.lastAr = {type: AspectRatio.Initial};
|
||||
}
|
||||
|
||||
setLastAr(override){
|
||||
this.lastAr = override;
|
||||
}
|
||||
|
||||
getLastAr(){
|
||||
return this.lastAr;
|
||||
}
|
||||
|
||||
setStretchMode(stretchMode){
|
||||
this.stretcher.setStretchMode(stretchMode);
|
||||
this.restore();
|
||||
}
|
||||
|
||||
panHandler(event, forcePan) {
|
||||
// console.log("this.conf.canPan:", this.conf.canPan)
|
||||
if (this.canPan || forcePan) {
|
||||
if(!this.conf.player || !this.conf.player.element) {
|
||||
return;
|
||||
}
|
||||
// dont allow weird floats
|
||||
this.videoAlignment = VideoAlignment.Center;
|
||||
|
||||
const player = this.conf.player.element;
|
||||
|
||||
const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth;
|
||||
const relativeY = (event.pageY - player.offsetTop) / player.offsetHeight;
|
||||
|
||||
if (Debug.debug && Debug.mousemove) {
|
||||
console.log("[Resizer::panHandler] mousemove.pageX, pageY:", event.pageX, event.pageY,
|
||||
"\nrelativeX/Y:", relativeX, relativeY)
|
||||
}
|
||||
|
||||
this.setPan(relativeX, relativeY);
|
||||
}
|
||||
}
|
||||
|
||||
setPan(relativeMousePosX, relativeMousePosY){
|
||||
// relativeMousePos[X|Y] - on scale from 0 to 1, how close is the mouse to player edges.
|
||||
// use these values: top, left: 0, bottom, right: 1
|
||||
if(! this.pan){
|
||||
this.pan = {};
|
||||
}
|
||||
|
||||
if (this.settings.active.miscSettings.mousePanReverseMouse) {
|
||||
this.pan.relativeOffsetX = (relativeMousePosX * 1.1) - 0.55;
|
||||
this.pan.relativeOffsetY = (relativeMousePosY * 1.1) - 0.55;
|
||||
} else {
|
||||
this.pan.relativeOffsetX = -(relativeMousePosX * 1.1) + 0.55;
|
||||
this.pan.relativeOffsetY = -(relativeMousePosY * 1.1) + 0.55;
|
||||
}
|
||||
// if(Debug.debug){
|
||||
// console.log("[Resizer::setPan] relative cursor pos:", relativeMousePosX, ",",relativeMousePosY, " | new pan obj:", this.pan)
|
||||
// }
|
||||
this.restore();
|
||||
}
|
||||
|
||||
setvideoAlignment(videoAlignment) {
|
||||
this.videoAlignment = videoAlignment;
|
||||
this.restore();
|
||||
}
|
||||
|
||||
startCssWatcher(){
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer.js::startCssWatcher] starting css watcher. Is resizer destroyed?", this.destroyed);
|
||||
}
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cssCheckDisabled = false;
|
||||
|
||||
if(!this.cssWatcherTimer){
|
||||
this.scheduleCssWatcher(1);
|
||||
} else {
|
||||
clearTimeout(this.cssWatcherTimer);
|
||||
this.scheduleCssWatcher(1);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleCssWatcher(timeout, force_reset) {
|
||||
if (this.destroyed || (this.extensionMode !== ExtensionMode.Enabled && !PlayerData.isFullScreen())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(timeout === undefined) {
|
||||
this.cssCheck(); // no timeout = one-off
|
||||
return;
|
||||
}
|
||||
|
||||
// one extra check to ensure we don't run css checks when we aren't supposed to
|
||||
if (this.cssCheckDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.cssWatcherTimeout) {
|
||||
clearTimeout(this.cssWatcherTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
this.cssWatcherTimer = setTimeout(function () {
|
||||
ths.cssWatcherTimer = null;
|
||||
try {
|
||||
if (! ths.cssCheckDisabled) {
|
||||
ths.cssCheck();
|
||||
}
|
||||
} catch (e) {
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer.js::scheduleCssWatcher] Css check failed. Error:", e);
|
||||
}
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
stopCssWatcher() {
|
||||
if (Debug.debug) {
|
||||
console.log(`[Resizer.js] <${this.resizerId}> STOPPING CSS WATCHER!`)
|
||||
}
|
||||
this.cssCheckDisabled = true;
|
||||
clearInterval(this.cssWatcherTimeout);
|
||||
}
|
||||
|
||||
restore() {
|
||||
if(Debug.debug){
|
||||
console.log("[Resizer::restore] <rid:"+this.resizerId+"> attempting to restore aspect ratio. this & settings:", {'a_lastAr': this.lastAr, 'this': this, "settings": this.settings} );
|
||||
}
|
||||
|
||||
// this is true until we verify that css has actually been applied
|
||||
this.restore_wd = true;
|
||||
|
||||
if(this.lastAr.type === AspectRatio.Initial){
|
||||
this.setAr({type: AspectRatio.Reset});
|
||||
}
|
||||
else {
|
||||
if (this.lastAr && this.lastAr.ratio === null) {
|
||||
console.log("[Resizer::restore] LAST AR IS NULL")
|
||||
throw "Last ar is null!"
|
||||
}
|
||||
this.setAr(this.lastAr, this.lastAr)
|
||||
}
|
||||
}
|
||||
|
||||
reset(){
|
||||
this.setStretchMode(this.settings.active.sites[window.location.hostname] ? this.settings.active.sites[window.location.hostname].stretch : this.settings.active.sites['@global'].stretch);
|
||||
this.zoom.setZoom(1);
|
||||
this.resetPan();
|
||||
this.setAr({type: AspectRatio.Reset});
|
||||
}
|
||||
|
||||
setPanMode(mode) {
|
||||
if (mode === 'enable') {
|
||||
this.canPan = true;
|
||||
} else if (mode === 'disable') {
|
||||
this.canPan = false;
|
||||
} else if (mode === 'toggle') {
|
||||
this.canPan = !this.canPan;
|
||||
}
|
||||
}
|
||||
|
||||
resetPan(){
|
||||
this.pan = undefined;
|
||||
}
|
||||
|
||||
setZoom(zoomLevel, no_announce) {
|
||||
this.zoom.setZoom(zoomLevel, no_announce);
|
||||
}
|
||||
|
||||
zoomStep(step){
|
||||
this.zoom.zoomStep(step);
|
||||
}
|
||||
|
||||
resetZoom(){
|
||||
this.zoom.setZoom(1);
|
||||
this.restore();
|
||||
}
|
||||
|
||||
resetCrop(){
|
||||
this.setAr({type: AspectRatio.Reset});
|
||||
}
|
||||
|
||||
resetStretch(){
|
||||
this.stretcher.setStretchMode(Stretch.NoStretch);
|
||||
this.restore();
|
||||
}
|
||||
|
||||
|
||||
// mostly internal stuff
|
||||
|
||||
computeOffsets(stretchFactors){
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("[Resizer::computeOffsets] <rid:"+this.resizerId+"> video will be aligned to ", this.settings.active.sites['@global'].videoAlignment);
|
||||
}
|
||||
|
||||
const wdiff = this.conf.player.dimensions.width - this.conf.video.offsetWidth;
|
||||
const hdiff = this.conf.player.dimensions.height - this.conf.video.offsetHeight;
|
||||
|
||||
const wdiffAfterZoom = this.conf.video.offsetWidth * stretchFactors.xFactor - this.conf.player.dimensions.width;
|
||||
const hdiffAfterZoom = this.conf.video.offsetHeight * stretchFactors.yFactor - this.conf.player.dimensions.height;
|
||||
|
||||
var translate = {
|
||||
x: wdiff * 0.5,
|
||||
y: hdiff * 0.5,
|
||||
};
|
||||
|
||||
if (this.pan) {
|
||||
// don't offset when video is smaller than player
|
||||
if(wdiffAfterZoom < 0 && hdiffAfterZoom < 0) {
|
||||
return translate;
|
||||
}
|
||||
translate.x += wdiffAfterZoom * this.pan.relativeOffsetX * this.zoom.scale;
|
||||
translate.y += hdiffAfterZoom * this.pan.relativeOffsetY * this.zoom.scale;
|
||||
} else {
|
||||
if (this.videoAlignment == VideoAlignment.Left) {
|
||||
translate.x += wdiffAfterZoom * 0.5;
|
||||
}
|
||||
else if (this.videoAlignment == VideoAlignment.Right) {
|
||||
translate.x -= wdiffAfterZoom * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:\n\n",
|
||||
'---- data in ----\n',
|
||||
'player dimensions:', {w: this.conf.player.dimensions.width, h: this.conf.player.dimensions.height},
|
||||
'video dimensions: ', {w: this.conf.video.offsetWidth, h: this.conf.video.offsetHeight},
|
||||
'stretch factors: ', stretchFactors,
|
||||
'pan & zoom: ', this.pan, this.zoom,
|
||||
'\n\n---- data out ----\n',
|
||||
'translate:', translate);
|
||||
}
|
||||
|
||||
return translate;
|
||||
}
|
||||
|
||||
applyCss(stretchFactors, translate){
|
||||
|
||||
if (! this.video) {
|
||||
if(Debug.debug) {
|
||||
console.log("[Resizer::applyCss] <rid:"+this.resizerId+"> Video went missing, doing nothing.");
|
||||
|
||||
}
|
||||
|
||||
this.conf.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// save stuff for quick tests (before we turn numbers into css values):
|
||||
this.currentVideoSettings = {
|
||||
validFor: this.conf.player.dimensions,
|
||||
// videoWidth: dimensions.width,
|
||||
// videoHeight: dimensions.height
|
||||
}
|
||||
|
||||
var styleArrayStr = this.video.getAttribute('style');
|
||||
var styleArrayObj = window.getComputedStyle(this.video);
|
||||
|
||||
|
||||
if (styleArrayStr) {
|
||||
var styleArray = styleArrayStr.split(";");
|
||||
for(var i in styleArray){
|
||||
|
||||
styleArray[i] = styleArray[i].trim();
|
||||
|
||||
// some sites do 'top: 50%; left: 50%; transform: <transform>' to center videos.
|
||||
// we dont wanna, because we already center videos on our own
|
||||
if (styleArray[i].startsWith("transform:") ||
|
||||
styleArray[i].startsWith("top:") ||
|
||||
styleArray[i].startsWith("left:") ||
|
||||
styleArray[i].startsWith("right:") ||
|
||||
styleArray[i].startsWith("bottom:") ){
|
||||
delete styleArray[i];
|
||||
}
|
||||
}
|
||||
}else {
|
||||
var styleArray = [];
|
||||
}
|
||||
|
||||
|
||||
// add remaining elements
|
||||
|
||||
if(stretchFactors){
|
||||
styleArray.push(`transform: translate(${translate.x}px, ${translate.y}px) scale(${stretchFactors.xFactor}, ${stretchFactors.yFactor})`);
|
||||
styleArray.push("top: 0px; left: 0px; bottom: 0px; right: 0px");
|
||||
}
|
||||
|
||||
// build style string back
|
||||
var styleString = "";
|
||||
for(var i in styleArray)
|
||||
if(styleArray[i])
|
||||
styleString += styleArray[i] + "; ";
|
||||
|
||||
this.setStyleString(styleString);
|
||||
}
|
||||
|
||||
setStyleString (styleString, count = 0) {
|
||||
this.video.setAttribute("style", styleString);
|
||||
|
||||
this.currentStyleString = this.video.getAttribute('style');
|
||||
this.currentCssValidFor = this.conf.player.dimensions;
|
||||
|
||||
if(this.restore_wd){
|
||||
|
||||
if(! this.video){
|
||||
if(Debug.debug)
|
||||
console.log("[Resizer::_res_setStyleString] <rid:"+this.resizerId+"> Video element went missing, nothing to do here.")
|
||||
return;
|
||||
}
|
||||
|
||||
// if(
|
||||
// styleString.indexOf("width: " + this.video.style.width) == -1 ||
|
||||
// styleString.indexOf("height: " + this.video.style.height) == -1) {
|
||||
// // css ni nastavljen?
|
||||
// // css not set?
|
||||
// if(Debug.debug)
|
||||
// console.log("[Resizer::_res_setStyleString] Style string not set ???");
|
||||
|
||||
// if(count < settings.active.resizer.setStyleString.maxRetries){
|
||||
// setTimeout( this.setStyleString, settings.active.resizer.setStyleString.retryTimeout, count + 1);
|
||||
// }
|
||||
// else if(Debug.debug){
|
||||
// console.log("[Resizer::_res_setStyleString] we give up. css string won't be set");
|
||||
// }
|
||||
// }
|
||||
// else{
|
||||
this.restore_wd = false;
|
||||
// }
|
||||
}
|
||||
else{
|
||||
if(Debug.debug)
|
||||
console.log("[Resizer::_res_setStyleString] <rid:"+this.resizerId+"> css applied. Style string:", styleString);
|
||||
}
|
||||
}
|
||||
|
||||
cssCheck(){
|
||||
if (this.cssCheckDisabled) {
|
||||
throw "fucking dont"
|
||||
return;
|
||||
}
|
||||
// this means we haven't set our CSS yet, or that we changed video.
|
||||
// if(! this.currentCss.tranform) {
|
||||
// this.scheduleCssWatcher(200);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this means video went missing. videoData will be re-initialized when the next video is found
|
||||
if(! this.video){
|
||||
if(Debug.debug && Debug.debugResizer) {
|
||||
console.log("[Resizer::cssCheck] <rid:"+this.resizerId+"> no video detecting, issuing destroy command");
|
||||
}
|
||||
this.conf.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.destroyed) {
|
||||
if(Debug.debug && Debug.debugResizer) {
|
||||
console.log("[Resizer::cssCheck] <rid:"+this.resizerId+"> destroyed flag is set, we shouldnt be running");
|
||||
}
|
||||
this.stopCssWatcher();
|
||||
return;
|
||||
}
|
||||
|
||||
var styleString = this.video.getAttribute('style');
|
||||
|
||||
// first, a quick test:
|
||||
// if (this.currentVideoSettings.validFor == this.conf.player.dimensions ){
|
||||
if (this.currentStyleString !== styleString){
|
||||
if(Debug.debug && Debug.debugResizer) {
|
||||
console.log(`%c[Resizer::cssCheck] <rid:${this.resizerId}> something touched our style string. We need to re-apply the style.`, {background: '#ddf', color: '#007'});
|
||||
}
|
||||
this.restore();
|
||||
this.scheduleCssWatcher(10);
|
||||
}
|
||||
if (this.cssWatcherIncreasedFrequencyCounter > 0) {
|
||||
--this.cssWatcherIncreasedFrequencyCounter;
|
||||
this.scheduleCssWatcher(20);
|
||||
} else {
|
||||
this.scheduleCssWatcher(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Resizer;
|
@ -1,3 +1,6 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
|
||||
// računa velikost videa za približevanje/oddaljevanje
|
||||
// does video size calculations for zooming/cropping
|
||||
|
||||
@ -9,12 +12,17 @@ class Scaler {
|
||||
constructor(videoData) {
|
||||
this.conf = videoData;
|
||||
}
|
||||
|
||||
|
||||
modeToAr(mode){
|
||||
// Skrbi za "stare" možnosti, kot na primer "na širino zaslona", "na višino zaslona" in "ponastavi".
|
||||
// Približevanje opuščeno.
|
||||
// handles "legacy" options, such as 'fit to widht', 'fit to height' and 'reset'. No zoom tho
|
||||
var ar;
|
||||
// Skrbi za "stare" možnosti, kot na primer "na širino zaslona", "na višino zaslona" in "ponastavi".
|
||||
// Približevanje opuščeno.
|
||||
// handles "legacy" options, such as 'fit to widht', 'fit to height' and AspectRatio.Reset. No zoom tho
|
||||
modeToAr (ar) {
|
||||
if (ar.type !== AspectRatio.FitWidth && ar.type !== AspectRatio.FitHeight && ar.ratio) {
|
||||
return ar.ratio;
|
||||
}
|
||||
|
||||
var ratioOut;
|
||||
|
||||
if (!this.conf.video) {
|
||||
if(Debug.debug){
|
||||
@ -26,10 +34,10 @@ class Scaler {
|
||||
|
||||
|
||||
if(! this.conf.player.dimensions ){
|
||||
ar = screen.width / screen.height;
|
||||
ratioOut = screen.width / screen.height;
|
||||
}
|
||||
else {
|
||||
ar = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
ratioOut = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
}
|
||||
|
||||
// POMEMBNO: lastAr je potrebno nastaviti šele po tem, ko kličemo _res_setAr(). _res_setAr() predvideva,
|
||||
@ -40,81 +48,85 @@ class Scaler {
|
||||
|
||||
var fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
|
||||
if (mode == "fitw"){
|
||||
return ar > fileAr ? ar : fileAr;
|
||||
if (ar.type === AspectRatio.FitWidth){
|
||||
ratioOut > fileAr ? ratioOut : fileAr
|
||||
ar.ratio = ratioOut;
|
||||
return ratioOut;
|
||||
}
|
||||
else if(mode == "fith"){
|
||||
return ar < fileAr ? ar : fileAr;
|
||||
else if(ar.type === AspectRatio.FitHeight){
|
||||
ratioOut < fileAr ? ratioOut : fileAr
|
||||
ar.ratio = ratioOut;
|
||||
return ratioOut;
|
||||
}
|
||||
else if(mode == "reset"){
|
||||
else if(ar.type === AspectRatio.Reset){
|
||||
if(Debug.debug){
|
||||
console.log("[Scaler.js::modeToAr] Using original aspect ratio -", fileAr)
|
||||
}
|
||||
|
||||
ar.ar = fileAr;
|
||||
return fileAr;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
calculateCrop(mode) {
|
||||
|
||||
|
||||
if(!this.conf.video || this.conf.video.videoWidth == 0 || this.conf.video.videoHeight == 0){
|
||||
if(Debug.debug)
|
||||
console.log("[Scaler::calculateCrop] ERROR — no video detected.");
|
||||
calculateCrop(ar) {
|
||||
if(!this.conf.video){
|
||||
if (Debug.debug) {
|
||||
console.log("[Scaler::calculateCrop] ERROR — no video detected. Conf:", this.conf, "video:", this.conf.video, "video dimensions:", this.conf.video && this.conf.video.videoWidth, '×', this.conf.video && this.conf.video.videoHeight);
|
||||
}
|
||||
|
||||
this.conf.destroy();
|
||||
return {error: "no_video"};
|
||||
}
|
||||
if (this.conf.video.videoWidth == 0 || this.conf.video.videoHeight == 0) {
|
||||
// that's illegal, but not illegal enough to just blast our shit to high hell
|
||||
// mr officer will let you go with a warning this time around
|
||||
if (Debug.debug) {
|
||||
console.log("[Scaler::calculateCrop] Video has illegal dimensions. Video dimensions:", this.conf.video && this.conf.video.videoWidth, '×', this.conf.video && this.conf.video.videoHeight);
|
||||
}
|
||||
|
||||
return {error: "illegal_video_dimensions"};
|
||||
}
|
||||
|
||||
// če je 'ar' string, potem bomo z njim opravili v legacy wrapperju. Seveda obstaja izjema
|
||||
// if 'ar' is string, we'll handle that in legacy wrapper, with one exception
|
||||
|
||||
if(mode === 'reset'){
|
||||
if (ar.type === AspectRatio.Reset){
|
||||
return {xFactor: 1, yFactor: 1}
|
||||
}
|
||||
|
||||
|
||||
var ar = 0;
|
||||
if(isNaN(mode)){
|
||||
ar = this.modeToAr(mode);
|
||||
} else {
|
||||
ar = mode;
|
||||
}
|
||||
|
||||
// handle fuckie-wuckies
|
||||
if (! ar){
|
||||
if(Debug.debug)
|
||||
console.log("[Scaler::calculateCrop] no ar?", ar, " -- we were given this mode:", mode);
|
||||
return {error: "no_ar", ar: ar};
|
||||
if (!ar.ratio){
|
||||
if (Debug.debug && Debug.scaler) {
|
||||
console.log("[Scaler::calculateCrop] no ar?", ar.ratio, " -- we were given this mode:", ar);
|
||||
}
|
||||
return {error: "no_ar", ratio: ar.ratio};
|
||||
}
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("[Scaler::calculateCrop] trying to set ar. args are: ar->",ar,"; this.conf.player.dimensions->",this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
if (Debug.debug && Debug.scaler) {
|
||||
console.log("[Scaler::calculateCrop] trying to set ar. args are: ar->",ar.ratio,"; this.conf.player.dimensions->",this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
}
|
||||
|
||||
if( (! this.conf.player.dimensions) || this.conf.player.dimensions.width === 0 || this.conf.player.dimensions.height === 0 ){
|
||||
if(Debug.debug)
|
||||
if (Debug.debug && Debug.scaler) {
|
||||
console.log("[Scaler::calculateCrop] ERROR — no (or invalid) this.conf.player.dimensions:",this.conf.player.dimensions);
|
||||
}
|
||||
return {error: "this.conf.player.dimensions_error"};
|
||||
}
|
||||
|
||||
// zdaj lahko končno začnemo računati novo velikost videa
|
||||
// we can finally start computing required video dimensions now:
|
||||
|
||||
|
||||
// Dejansko razmerje stranic datoteke/<video> značke
|
||||
// Actual aspect ratio of the file/<video> tag
|
||||
var fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
var playerAr = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
|
||||
if(mode == "default" || !ar)
|
||||
ar = fileAr;
|
||||
if (ar.type === AspectRatio.Initial || !ar.ratio) {
|
||||
ar.ratio = fileAr;
|
||||
}
|
||||
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("[Scaler::calculateCrop] ar is " ,ar, ", file ar is", fileAr, ", this.conf.player.dimensions are ", this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
if (Debug.debug && Debug.scaler) {
|
||||
console.log("[Scaler::calculateCrop] ar is " ,ar.ratio, ", file ar is", fileAr, ", this.conf.player.dimensions are ", this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
}
|
||||
|
||||
var videoDimensions = {
|
||||
xFactor: 1,
|
||||
@ -127,22 +139,25 @@ class Scaler {
|
||||
// console.log("[Scaler::calculateCrop] Player dimensions?", this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
// }
|
||||
|
||||
if( fileAr < ar ){
|
||||
if (fileAr < ar.ratio){
|
||||
// imamo letterbox zgoraj in spodaj -> spremenimo velikost videa (a nikoli širše od ekrana)
|
||||
// letterbox -> change video size (but never to wider than monitor width)
|
||||
|
||||
videoDimensions.xFactor = Math.min(ar, playerAr) / fileAr;
|
||||
// if (Debug.debug && Debug.scaler) {
|
||||
// console.log(`%c[Scaler::calculateCrop] Trying to determine scaling factors. Aspect ratios:\n file: ${fileAr.toFixed(3)}\n player: ${playerAr.toFixed(3)}\n target: ${ar.ratio.toFixed(3)}\n-----------------------`, "color: #2ba");
|
||||
// }
|
||||
videoDimensions.xFactor = Math.min(ar.ratio, playerAr) / fileAr;
|
||||
videoDimensions.yFactor = videoDimensions.xFactor;
|
||||
}
|
||||
else {
|
||||
videoDimensions.xFactor = fileAr / Math.min(ar, playerAr);
|
||||
} else {
|
||||
videoDimensions.xFactor = fileAr / Math.min(ar.ratio, playerAr);
|
||||
videoDimensions.yFactor = videoDimensions.xFactor;
|
||||
}
|
||||
|
||||
if(Debug.debug){
|
||||
if (Debug.debug && Debug.scaler) {
|
||||
console.log("[Scaler::calculateCrop] Crop factor calculated — ", videoDimensions.xFactor);
|
||||
}
|
||||
|
||||
return videoDimensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Scaler;
|
@ -1,9 +1,11 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import Stretch from '../../../common/enums/stretch.enum';
|
||||
|
||||
// računa vrednosti za transform-scale (x, y)
|
||||
// transform: scale(x,y) se uporablja za raztegovanje videa, ne pa za približevanje
|
||||
// calculates values for transform scale(x, y)
|
||||
// transform: scale(x,y) is used for stretching, not zooming.
|
||||
|
||||
|
||||
class Stretcher {
|
||||
// internal variables
|
||||
|
||||
@ -12,7 +14,15 @@ class Stretcher {
|
||||
constructor(videoData) {
|
||||
this.conf = videoData;
|
||||
this.settings = videoData.settings;
|
||||
this.mode = this.settings.getDefaultStretchMode();
|
||||
this.mode = this.settings.getDefaultStretchMode(window.location.hostname);
|
||||
}
|
||||
|
||||
setStretchMode(stretchMode) {
|
||||
if (stretchMode === Stretch.Default) {
|
||||
this.mode = this.settings.getDefaultStretchMode(window.location.hostname);
|
||||
} else {
|
||||
this.mode = stretchMode;
|
||||
}
|
||||
}
|
||||
|
||||
applyConditionalStretch(stretchFactors, actualAr){
|
||||
@ -177,4 +187,6 @@ class Stretcher {
|
||||
|
||||
return stretchFactors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Stretcher;
|
@ -1,10 +1,9 @@
|
||||
// računa približevanje ter računa/popravlja odmike videa
|
||||
import Debug from '../../conf/Debug';
|
||||
|
||||
// računa približevanje ter računa/popravlja odmike videa
|
||||
// calculates zooming and video offsets/panning
|
||||
|
||||
class Zoom {
|
||||
// internal variables
|
||||
|
||||
|
||||
// functions
|
||||
constructor(videoData) {
|
||||
this.scale = 1;
|
||||
@ -40,10 +39,14 @@ class Zoom {
|
||||
}
|
||||
|
||||
setZoom(scale, no_announce){
|
||||
if (Debug.debug) {
|
||||
console.log("[Zoom::setZoom] Setting zoom to", scale, "!");
|
||||
}
|
||||
|
||||
// NOTE: SCALE IS NOT LOGARITHMIC
|
||||
if(scale < Math.pow(this.minScale)) {
|
||||
if(scale < Math.pow(2, this.minScale)) {
|
||||
scale = this.minScale;
|
||||
} else if (scale > Math.pow(this.maxScale)) {
|
||||
} else if (scale > Math.pow(2, this.maxScale)) {
|
||||
scale = this.maxScale;
|
||||
}
|
||||
|
||||
@ -55,8 +58,21 @@ class Zoom {
|
||||
}
|
||||
}
|
||||
|
||||
applyZoom(videoDimensions){
|
||||
videoDimensions.xFactor *= this.scale;
|
||||
videoDimensions.yFactor *= this.scale;
|
||||
applyZoom(stretchFactors){
|
||||
if (!stretchFactors) {
|
||||
return;
|
||||
}
|
||||
if (Debug.debug) {
|
||||
console.log("[Zoom::setZoom] Applying zoom. Stretch factors pre:", stretchFactors, " —> scale:", this.scale);
|
||||
}
|
||||
|
||||
stretchFactors.xFactor *= this.scale;
|
||||
stretchFactors.yFactor *= this.scale;
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("[Zoom::setZoom] Applying zoom. Stretch factors post:", stretchFactors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Zoom;
|
240
src/ext/uw-bg.js
Normal file
240
src/ext/uw-bg.js
Normal file
@ -0,0 +1,240 @@
|
||||
import Debug from './conf/Debug.js';
|
||||
import BrowserDetect from './conf/BrowserDetect';
|
||||
import CommsServer from './lib/comms/CommsServer';
|
||||
import Settings from './lib/Settings';
|
||||
|
||||
|
||||
var BgVars = {
|
||||
arIsActive: true,
|
||||
hasVideos: false,
|
||||
currentSite: ""
|
||||
}
|
||||
|
||||
class UWServer {
|
||||
|
||||
constructor() {
|
||||
this.ports = [];
|
||||
this.arIsActive = true;
|
||||
this.hasVideos = false;
|
||||
this.currentSite = "";
|
||||
this.setup();
|
||||
|
||||
this.videoTabs = {};
|
||||
this.currentTabId = 0;
|
||||
this._gctimeout = undefined;
|
||||
|
||||
this.selectedSubitem = {
|
||||
'siteSettings': undefined,
|
||||
'videoSettings': undefined,
|
||||
}
|
||||
}
|
||||
|
||||
async setup() {
|
||||
this.settings = new Settings();
|
||||
|
||||
await this.settings.init();
|
||||
this.comms = new CommsServer(this);
|
||||
|
||||
var ths = this;
|
||||
if(BrowserDetect.firefox) {
|
||||
browser.tabs.onActivated.addListener(function(m) {ths.onTabSwitched(m)});
|
||||
} else if (BrowserDetect.chrome) {
|
||||
chrome.tabs.onActivated.addListener(function(m) {ths.onTabSwitched(m)});
|
||||
}
|
||||
|
||||
console.log("will schedule gcframe")
|
||||
this.scheduleGc();
|
||||
}
|
||||
|
||||
async _promisifyTabsGet(browserObj, tabId){
|
||||
return new Promise( (resolve, reject) => {
|
||||
browserObj.tabs.get(tabId, (tab) => resolve(tab));
|
||||
});
|
||||
}
|
||||
|
||||
scheduleGc(timeout) {
|
||||
if (this._gctimeout) {
|
||||
return;
|
||||
}
|
||||
if (!timeout) {
|
||||
timeout = 0;
|
||||
}
|
||||
|
||||
const ths = this;
|
||||
setTimeout( () => {
|
||||
clearTimeout(ths._gctimeout);
|
||||
ths.gcFrames();
|
||||
|
||||
|
||||
ths._gctimeoutgcTimeout = ths.scheduleGc(5000);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
extractHostname(url){
|
||||
var hostname;
|
||||
|
||||
// extract hostname
|
||||
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
|
||||
hostname = url.split('/')[2];
|
||||
}
|
||||
else {
|
||||
hostname = url.split('/')[0];
|
||||
}
|
||||
|
||||
hostname = hostname.split(':')[0]; //find & remove port number
|
||||
hostname = hostname.split('?')[0]; //find & remove "?"
|
||||
|
||||
return hostname;
|
||||
}
|
||||
|
||||
async onTabSwitched(activeInfo){
|
||||
this.hasVideos = false;
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("[uw-bg::onTabSwitched] TAB CHANGED, GETTING INFO FROM MAIN TAB");
|
||||
|
||||
try {
|
||||
this.currentTabId = activeInfo.tabId; // just for readability
|
||||
|
||||
var tab;
|
||||
if (BrowserDetect.firefox) {
|
||||
var tab = await browser.tabs.get(this.currentTabId);
|
||||
} else if (BrowserDetect.chrome) {
|
||||
var tab = await this._promisifyTabsGet(chrome, this.currentTabId);
|
||||
}
|
||||
|
||||
this.currentSite = this.extractHostname(tab.url);
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("TAB SWITCHED!", this.currentSite)
|
||||
}
|
||||
|
||||
this.selectedSubitem = {
|
||||
'siteSettings': undefined,
|
||||
'videoSettings': undefined,
|
||||
}
|
||||
//TODO: change extension icon based on whether there's any videos on current page
|
||||
}
|
||||
|
||||
async gcFrames() {
|
||||
// does "garbage collection" on frames
|
||||
|
||||
let frames;
|
||||
|
||||
if (BrowserDetect.firefox) {
|
||||
frames = await browser.webNavigation.getAllFrames({tabId: this.currentTabId});
|
||||
} else if (BrowserDetect.chrome) {
|
||||
frames = await new Promise( (resolve, reject) => {
|
||||
chrome.webNavigation.getAllFrames({tabId: this.currentTabId}, (data) => {resolve(data); return true});
|
||||
});
|
||||
}
|
||||
|
||||
if (this.videoTabs[this.currentTabId]) {
|
||||
for (let key in this.videoTabs[this.currentTabId].frames) {
|
||||
if (! frames.find(x => x.frameId == key)) {
|
||||
delete this.videoTabs[this.currentTabId].frames[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerVideo(sender) {
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[UWServer::registerVideo] registering video.\nsender:", sender);
|
||||
}
|
||||
|
||||
const tabHostname = this.extractHostname(sender.tab.url);
|
||||
const frameHostname = this.extractHostname(sender.url);
|
||||
|
||||
// preveri za osirotele/zastarele vrednosti ter jih po potrebi izbriši
|
||||
// check for orphaned/outdated values and remove them if neccessary
|
||||
if (this.videoTabs[sender.tab.id]) {
|
||||
if (this.videoTabs[sender.tab.id].host != tabHostname) {
|
||||
delete this.videoTabs[sender.tab.id]
|
||||
} else {
|
||||
if(this.videoTabs[sender.tab.id].frames[sender.frameId]) {
|
||||
if (this.videoTabs[sender.tab.id].frames[sender.frameId].host != frameHostname) {
|
||||
delete this.videoTabs[sender.tab.id].frames[sender.frameId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.videoTabs[sender.tab.id]) {
|
||||
if (this.videoTabs[sender.tab.id].frames[sender.frameId]) {
|
||||
return; // existing value is fine, no need to act
|
||||
} else {
|
||||
this.videoTabs[sender.tab.id].frames[sender.frameId] = {
|
||||
id: sender.frameId,
|
||||
host: frameHostname,
|
||||
url: sender.url
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.videoTabs[sender.tab.id] = {
|
||||
id: sender.tab.id,
|
||||
host: tabHostname,
|
||||
url: sender.tab.url,
|
||||
frames: {}
|
||||
};
|
||||
this.videoTabs[sender.tab.id].frames[sender.frameId] = {
|
||||
id: sender.frameId,
|
||||
host: frameHostname,
|
||||
url: sender.url
|
||||
}
|
||||
}
|
||||
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[UWServer::registerVideo] video registered. current videoTabs:", this.videoTabs);
|
||||
}
|
||||
}
|
||||
|
||||
unregisterVideo(sender) {
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[UWServer::unregisterVideo] unregistering video.\nsender:", sender);
|
||||
}
|
||||
if (this.videoTabs[sender.tab.id]) {
|
||||
if ( Object.keys(this.videoTabs[sender.tab.id].frames).length <= 1) {
|
||||
delete this.videoTabs[sender.tab.id]
|
||||
} else {
|
||||
if(this.videoTabs[sender.tab.id].frames[sender.frameId]) {
|
||||
delete this.videoTabs[sender.tab.id].frames[sender.frameId];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[UWServer::ungisterVideo] video unregistered. current videoTabs:", this.videoTabs);
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedTab(menu, subitem) {
|
||||
if (Debug.debug && Debug.comms) {
|
||||
console.log("[uw-bg::setSelectedTab] saving selected tab for", menu, ":", subitem)
|
||||
}
|
||||
this.selectedSubitem[menu] = subitem;
|
||||
}
|
||||
|
||||
getVideoTab() {
|
||||
// friendly reminder: if current tab doesn't have a video,
|
||||
// there won't be anything in this.videoTabs[this.currentTabId]
|
||||
if (this.videoTabs[this.currentTabId]) {
|
||||
return {
|
||||
...this.videoTabs[this.currentTabId],
|
||||
selected: this.selectedSubitem
|
||||
};
|
||||
}
|
||||
|
||||
// return something more or less empty if this tab doesn't have
|
||||
// a video registered for it
|
||||
return {
|
||||
host: this.currentSite,
|
||||
frames: [],
|
||||
selected: this.selectedSubitem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var server = new UWServer();
|
@ -1,3 +1,11 @@
|
||||
import Debug from './conf/Debug';
|
||||
import BrowserDetect from './conf/BrowserDetect';
|
||||
import ExtensionMode from '../common/enums/extension-mode.enum'
|
||||
import Settings from './lib/Settings';
|
||||
import ActionHandler from './lib/ActionHandler';
|
||||
import CommsClient from './lib/comms/CommsClient';
|
||||
import PageInfo from './lib/video-data/PageInfo';
|
||||
|
||||
if(Debug.debug){
|
||||
console.log("\n\n\n\n\n\n ——— Sᴛλʀᴛɪɴɢ Uʟᴛʀᴀᴡɪᴅɪꜰʏ ———\n << ʟᴏᴀᴅɪɴɢ ᴍᴀɪɴ ꜰɪʟᴇ >>\n\n\n\n");
|
||||
try {
|
||||
@ -12,13 +20,16 @@ if(Debug.debug){
|
||||
}
|
||||
}
|
||||
|
||||
if (BrowserDetect.edge) {
|
||||
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
|
||||
}
|
||||
|
||||
class UW {
|
||||
constructor(){
|
||||
this.pageInfo = undefined;
|
||||
this.comms = undefined;
|
||||
this.settings = undefined;
|
||||
this.keybinds = undefined;
|
||||
this.actionHandler = undefined;
|
||||
}
|
||||
|
||||
async init(){
|
||||
@ -47,7 +58,14 @@ class UW {
|
||||
|
||||
// če smo razširitev onemogočili v nastavitvah, ne naredimo ničesar
|
||||
// If extension is soft-disabled, don't do shit
|
||||
if(! this.settings.canStartExtension()){
|
||||
|
||||
var extensionMode = this.settings.getExtensionMode();
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[uw::init] Extension mode:" + (extensionMode < 0 ? "disabled" : extensionMode == '1' ? 'basic' : 'full'));
|
||||
}
|
||||
|
||||
if(extensionMode === ExtensionMode.Disabled){
|
||||
if(Debug.debug) {
|
||||
console.log("[uw::init] EXTENSION DISABLED, THEREFORE WONT BE STARTED")
|
||||
}
|
||||
@ -55,15 +73,23 @@ class UW {
|
||||
}
|
||||
|
||||
try {
|
||||
this.pageInfo = new PageInfo(this.comms, this.settings);
|
||||
this.pageInfo = new PageInfo(this.comms, this.settings, extensionMode);
|
||||
if(Debug.debug){
|
||||
console.log("[uw.js::setup] pageInfo initialized. Here's the object:", this.pageInfo);
|
||||
}
|
||||
this.comms.setPageInfo(this.pageInfo);
|
||||
|
||||
this.keybinds = new Keybinds(this.pageInfo);
|
||||
this.keybinds.setup();
|
||||
if(Debug.debug) {
|
||||
console.log("[uw.js::setup] will try to initate ActionHandler. Settings are:", this.settings, this.settings.active)
|
||||
}
|
||||
|
||||
this.actionHandler = new ActionHandler(this.pageInfo);
|
||||
this.actionHandler.init();
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("[uw.js::setup] ActionHandler initiated:", this.actionHandler);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.log("[uw::init] FAILED TO START EXTENSION. Error:", e);
|
||||
}
|
||||
@ -72,5 +98,5 @@ class UW {
|
||||
}
|
||||
}
|
||||
|
||||
var uw = new UW();
|
||||
uw.init();
|
||||
var main = new UW();
|
||||
main.init();
|
BIN
src/icons/icon.xcf
Normal file
BIN
src/icons/icon.xcf
Normal file
Binary file not shown.
BIN
src/icons/icon_128.png
Normal file
BIN
src/icons/icon_128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.8 KiB |
BIN
src/icons/icon_48.png
Normal file
BIN
src/icons/icon_48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
59
src/manifest.json
Normal file
59
src/manifest.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Ultrawidify",
|
||||
"description": "Aspect ratio fixer for Youtube, Netflix and possibly more. Supports automatic aspect ratio detection on certain sites.",
|
||||
"version": "4.0.1a1",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "{cf02b1a7-a01a-4e37-a609-516a283f1ed3}"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"32":"res/icons/uw-32.png",
|
||||
"64":"res/icons/uw-64.png"
|
||||
},
|
||||
"browser_action": {
|
||||
"default_title": "Ultrawidify",
|
||||
"default_popup": "popup/popup.html"
|
||||
},
|
||||
|
||||
"content_scripts": [{
|
||||
"matches": ["*://*/*"],
|
||||
"js": [
|
||||
"ext/uw.js"
|
||||
],
|
||||
"all_frames": true
|
||||
}],
|
||||
|
||||
"background": {
|
||||
"scripts": [
|
||||
"ext/uw-bg.js"
|
||||
]
|
||||
},
|
||||
|
||||
"options_ui": {
|
||||
"page": "options/options.html",
|
||||
"browser_style": false,
|
||||
"open_in_tab": true
|
||||
},
|
||||
|
||||
|
||||
"web_accessible_resources": [
|
||||
"./*",
|
||||
"ext/*",
|
||||
"res/fonts/*",
|
||||
"res/css/*",
|
||||
|
||||
"res/img/ytplayer-icons/zoom.png",
|
||||
"res/img/ytplayer-icons/unzoom.png",
|
||||
"res/img/ytplayer-icons/fitw.png",
|
||||
"res/img/ytplayer-icons/fith.png",
|
||||
"res/img/ytplayer-icons/reset.png",
|
||||
"res/img/ytplayer-icons/settings.png",
|
||||
|
||||
"res/img/settings/about-bg.png"
|
||||
],
|
||||
"permissions": [
|
||||
"tabs", "storage", "activeTab", "<all_urls>", "webNavigation"
|
||||
]
|
||||
}
|
234
src/options/App.vue
Normal file
234
src/options/App.vue
Normal file
@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<!-- POPUPS -->
|
||||
<div v-if="anyOpenedPopups"
|
||||
class="popup-container"
|
||||
>
|
||||
<AddEditActionPopup v-if="editActionPopupVisible"
|
||||
:settings="settings"
|
||||
:actionIndex="editActionIndex"
|
||||
@close="closePopups()"
|
||||
>
|
||||
</AddEditActionPopup>
|
||||
<ConfirmPopup v-if="confirmationDialogText"
|
||||
:dialogText="confirmationDialogText"
|
||||
@close="closePopups()"
|
||||
@confirm="confirm()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ACTUAL PAGE -->
|
||||
<div class="flex flex-row"
|
||||
:class="{'blur': anyOpenedPopups}"
|
||||
>
|
||||
|
||||
<div class="header flex flex-column">
|
||||
<div class="flex extension-name text-sink-anchor">
|
||||
<div class="text-sink title-sink-pad w100 text-center">
|
||||
Ultrawidify
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MENU ITEMS HERE -->
|
||||
<div class="flex flex-column menu">
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'general'}"
|
||||
@click="setSelectedTab('general')"
|
||||
>
|
||||
General settings
|
||||
</div>
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'autoar'}"
|
||||
@click="setSelectedTab('autoar')"
|
||||
>
|
||||
Autodetection
|
||||
</div>
|
||||
<div class="menu-item experimental"
|
||||
:class="{'selected-tab': selectedTab === 'controls'}"
|
||||
@click="setSelectedTab('controls')"
|
||||
>
|
||||
Actions
|
||||
</div>
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'txtconf'}"
|
||||
@click="setSelectedTab('txtconf')"
|
||||
>
|
||||
Super advanced settings
|
||||
</div>
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'about'}"
|
||||
@click="setSelectedTab('about')"
|
||||
>
|
||||
About
|
||||
</div>
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'donate'}"
|
||||
@click="setSelectedTab('donate')"
|
||||
>
|
||||
Donate
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex content-area flex-column">
|
||||
<div class="flex content-title text-sink-anchor">
|
||||
<div class="text-sink title-sink-pad">
|
||||
{{selectedTabTitle}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MAIN PAGE HERE -->
|
||||
<div class="content-content">
|
||||
<GeneralSettings v-if="settingsInitialized && selectedTab === 'general'"
|
||||
:settings="settings"
|
||||
>
|
||||
</GeneralSettings>
|
||||
<AutodetectionSettings v-if="settingsInitialized && selectedTab === 'autoar'"
|
||||
:settings="settings"
|
||||
>
|
||||
</AutodetectionSettings>
|
||||
<ControlsSettings v-if="selectedTab === 'controls'"
|
||||
:settings="settings"
|
||||
@edit-event="showEditActionPopup($event)"
|
||||
@remove-event="showRemoveActionPopup($event)"
|
||||
>
|
||||
</ControlsSettings>
|
||||
<SuperAdvancedSettings v-if="selectedTab === 'txtconf'"
|
||||
:settings="settings"
|
||||
></SuperAdvancedSettings>
|
||||
<About v-if="selectedTab === 'about'">
|
||||
</About>
|
||||
<Donate v-if="selectedTab === 'donate'" />
|
||||
<!-- Vice City/beggathon reference: https://youtu.be/Mn3YEJTSYs8?t=770 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Donate from '../common/misc/Donate.vue';
|
||||
import SuperAdvancedSettings from './SuperAdvancedSettings.vue';
|
||||
import Debug from '../ext/conf/Debug.js';
|
||||
import BrowserDetect from '../ext/conf/BrowserDetect.js';
|
||||
|
||||
import ExtensionConf from '../ext/conf/ExtensionConf.js';
|
||||
|
||||
import ObjectCopy from '../ext/lib/ObjectCopy.js';
|
||||
|
||||
import Settings from '../ext/lib/Settings.js';
|
||||
|
||||
import GeneralSettings from './GeneralSettings';
|
||||
import ControlsSettings from './controls-settings/ControlsSettings';
|
||||
import AddEditActionPopup from './controls-settings/AddEditActionPopup';
|
||||
import ConfirmPopup from './common/ConfirmationPopup';
|
||||
import About from './about'
|
||||
|
||||
import AutodetectionSettings from './AutodetectionSettings';
|
||||
// import SuperAdvancedSettings from './'
|
||||
|
||||
export default {
|
||||
name: "Ultrawidify",
|
||||
data () {
|
||||
return {
|
||||
selectedTab: "general",
|
||||
selectedTabTitle: "General settings",
|
||||
settings: {},
|
||||
settingsInitialized: false,
|
||||
editActionPopupVisible: false,
|
||||
editActionIndex: -1,
|
||||
anyOpenedPopups: false,
|
||||
removeConfirmationDialog: '',
|
||||
confirmationDialogText: '',
|
||||
messages: {
|
||||
removeAction: "Are you sure you want to remove this action?"
|
||||
},
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
this.settings = new Settings(undefined, this.updateSettings);
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
},
|
||||
components: {
|
||||
GeneralSettings,
|
||||
ControlsSettings,
|
||||
AddEditActionPopup,
|
||||
About,
|
||||
AutodetectionSettings,
|
||||
ConfirmPopup,
|
||||
SuperAdvancedSettings,
|
||||
Donate,
|
||||
},
|
||||
methods: {
|
||||
setSelectedTab(newTab) {
|
||||
this.selectedTab = newTab;
|
||||
if (newTab === 'general') {
|
||||
this.selectedTabTitle = 'General settings';
|
||||
} else if (newTab === 'autoar') {
|
||||
this.selectedTabTitle = 'Advanced autodetection settings';
|
||||
} else if (newTab === 'controls') {
|
||||
this.selectedTabTitle = 'Actions';
|
||||
} else if (newTab === 'txtconf') {
|
||||
this.selectedTabTitle = 'Super advanced settings';
|
||||
} else if (newTab === 'about') {
|
||||
this.selectedTabTitle = 'About';
|
||||
} else if (newTab === 'donate') {
|
||||
this.selectedTabTitle = 'Beggathon';
|
||||
}
|
||||
},
|
||||
updateSettings(newSettings) {
|
||||
this.settings = newSettings;
|
||||
this.settingsInitialized = true;
|
||||
},
|
||||
showEditActionPopup(event) {
|
||||
this.editActionPopupVisible = true;
|
||||
this.editActionIndex = event;
|
||||
this.anyOpenedPopups = true;
|
||||
},
|
||||
showRemoveActionPopup(indexToRemove) {
|
||||
this.editActionIndex = indexToRemove;
|
||||
this.anyOpenedPopups = true;
|
||||
this.confirmationDialogText = this.messages.removeAction;
|
||||
},
|
||||
closePopups(){
|
||||
this.anyOpenedPopups = false;
|
||||
this.editActionPopupVisible = false;
|
||||
this.confirmationDialogText = '';
|
||||
},
|
||||
confirm(){
|
||||
if (this.confirmationDialogText === this.messages.removeAction) {
|
||||
this.settings.active.actions.splice(this.editActionIndex, 1);
|
||||
this.settings.save();
|
||||
}
|
||||
|
||||
|
||||
this.closePopups();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style src="../res/css/font/overpass.css"></style>
|
||||
<style src="../res/css/font/overpass-mono.css"></style>
|
||||
<style src="../res/css/flex.css"></style>
|
||||
<style src="../res/css/common.scss"></style>
|
||||
<style src="./options.scss"></style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.blur {
|
||||
filter: blur(2px);
|
||||
}
|
||||
</style>
|
680
src/options/AutodetectionSettings.vue
Normal file
680
src/options/AutodetectionSettings.vue
Normal file
@ -0,0 +1,680 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Quick settings</h2>
|
||||
|
||||
<div class="flex label-secondary">
|
||||
How often should autodetection check for changes?
|
||||
</div>
|
||||
<div class="description">
|
||||
Shorter intervals (left side of the slider) are more responsive to changes in aspect ratio detections,
|
||||
but requires more system resources. Slider is logarithmic.
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex flex-input">
|
||||
More often <small>(~60/s)</small>
|
||||
<input type="range"
|
||||
:value="Math.log(settings.active.arDetect.timers.playing)"
|
||||
@change="setArCheckFrequency($event.target.value)"
|
||||
min="2.3"
|
||||
max="9.3"
|
||||
step="any"
|
||||
/>
|
||||
Less often <small>(~1/10s)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex label-secondary">
|
||||
How sensitive should aspect ratio detection be?
|
||||
</div>
|
||||
<div class="description">
|
||||
Sensitive preset will allow the extension to correct aspect ratio even when letterbox isn't clearly defined. This
|
||||
can result in aspect ratio detection correcting aspect ratio when it shouldn't.
|
||||
Accurate preset will take a more conservative approach to determining aspect ratio, correcting aspect ratio only when
|
||||
it's absolutely sure that the aspect ratio needs changing. This option results in fewer incorrect aspect ratio corrections,
|
||||
but can also result in extension not correcting aspect ratio when it should.
|
||||
Strict preset is 'accurate' on stereoids.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row row-padding">
|
||||
<Button label="Sensitive"
|
||||
:selected="sensitivity === 'sensitive'"
|
||||
@click.native="setConfirmationThresholds('sensitive')"
|
||||
/>
|
||||
<Button label="Balanced"
|
||||
:selected="sensitivity === 'balanced'"
|
||||
@click.native="setConfirmationThresholds('balanced')"
|
||||
/>
|
||||
<Button label="Accurate"
|
||||
:selected="getSensitivity() === 'accurate'"
|
||||
@click.native="setConfirmationThresholds('accurate')"
|
||||
/>
|
||||
<Button label="Strict"
|
||||
:selected="getSensitivity() === 'strict'"
|
||||
@click.native="setConfirmationThresholds('strict')"
|
||||
/>
|
||||
<Button label="Custom (changed below)"
|
||||
:selected="getSensitivity() === 'user-defined'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 style="padding-top: 150px">Autodetection settings in detail</h2>
|
||||
<!-- <div>
|
||||
<input type="checkbox"
|
||||
v-model="showAdvancedOptions"
|
||||
/>
|
||||
Show advanced options
|
||||
</div> -->
|
||||
|
||||
<div class="label">Autodetection frequency</div>
|
||||
<div class="description">
|
||||
Determines how often we check for aspect ratio changes.
|
||||
More frequent aspect ratio checks can result in insane RAM usage.
|
||||
Less frequent aspect ratio checks result in bigger delay between aspect ratio change and correction.
|
||||
Delays are given in milliseconds.
|
||||
</div>
|
||||
|
||||
<div class="info">Note that tick rate must be smaller than check frequency.
|
||||
<!-- <a v-if="!showAdvancedOptions"
|
||||
href="#"
|
||||
@click="showAdvancedOptions = true"
|
||||
>Show advanced options</a> -->
|
||||
</div>
|
||||
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Frequency while playing:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.timers.playing"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Frequency while paused:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.timers.paused"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Error timeout:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.timers.error"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding" v-if="showAdvancedOptions">
|
||||
<div class="flex label-secondary form-label">
|
||||
Tick rate:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.timers.tickrate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="label">Fallback mode</div>
|
||||
<div class="description">
|
||||
Some streaming sites implement stuff (DRM) that prevents us from detecting aspect ratio using our favourite way.
|
||||
Fortunately for us, some browsers (most notably: Firefox) allow us to work around that limitation using an alternative.
|
||||
Said alternative is not without downsides, though:<br />
|
||||
<ul><li> it's less accurate and leaves a thin black edge all around the video</li>
|
||||
<li> Uses bigger image sample, which translates into slightly to moderately increased RAM and CPU usage <small>(increase in resource usage depends on your monitor resolution)</small></li>
|
||||
</ul>
|
||||
This is why fallback mode can be toggled off separately from the main thing.
|
||||
<span v-if="!fallbackModeAvailable">Unfortunately for you, <b>this option doesn't seem to be available in the browser you're using.</b></span>
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary">
|
||||
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="checkbox"
|
||||
v-model="settings.active.arDetect.fallbackMode.enabled"
|
||||
/> Enable fallback mode <span v-if="!fallbackModeAvailable">because I'm super duper sure I'm using firefox right now.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Safety border thickness (in px)
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.fallbackMode.safetyBorderPx"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Don't react if detected edge is less than this many pixels thick:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.fallbackMode.noTriggerZonePx"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label">Letterbox misallignment threshold</div>
|
||||
<div class="description">
|
||||
If top and bottom bar differ by more than this (0 — 0%, 1 — 100%), we do not correct aspect ratio.
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Letterbox misalignment threshold
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.allowedMisaligned"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label">Sampling options</div>
|
||||
<div class="description">
|
||||
Various sampling related options.<br/>
|
||||
<b>Static columns:</b> Image is sampled in this many columns, spaced at regular intervals between both edges.<br/>
|
||||
<b>Random columns:</b> (ADVANCED; NOT IMPLEMENTED/PLANNED) In addition to static colums, sample image at this many random columns.<br/>
|
||||
<b>Static rows:</b> (ADVANCED) Image is sampled in this many rows, spaced at regular intervals between both edges.<br/>
|
||||
<b>Sample width, height:</b> (ADVANCED) size of the sample. Bigger -> more accurate aspect ratio detection, but uses more resources.<br/>
|
||||
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Static sample columns:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.sampling.staticCols"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Random sample columns:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.sampling.randomCols"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Static rows:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.sampling.staticRows"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Sample width:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.canvasDimensions.sampleCanvas.width"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Static rows:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.canvasDimensions.sampleCanvas.height"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label">Blackbar</div>
|
||||
<div class="description">
|
||||
These settings determine what's considered black and what's considered non-black.<br/>
|
||||
<b>Black level:</b> 0-255, where 0 is black. Anything below this number across all RGB components is considered black.
|
||||
Black level can decrease if we detect darker blacks in the video. Lower values —> more accurate edge detection;
|
||||
higher values —> detection is more forgiving to videos with less-than-ideal contrast ratios.<br/>
|
||||
<b>Threshold:</b> If pixel is darker than the sum of black level and this value, it's considered black. In theory, lower -> better.
|
||||
In practice, this value needs to be kept surprisingly high (8 might not be high enough), otherwise compression artifacts in videos
|
||||
start having an adverse effect on quality of automatic detection.<br/>
|
||||
<b>Gradient detection:</b> Attempt to discriminate between hard edges and gradients. 'Strict' and 'Lax' prevent aspect ratio
|
||||
changes if we detected gradients instead of a legit edge. This results in fewer false positives, but may cause aspect ratio
|
||||
detection to not work on darker frames.<br/>
|
||||
<b>Image threshold:</b> When gradient detection is enabled, everything that's brighter than the sum of black level, threshold and
|
||||
ths is considered to be non-black.<br/>
|
||||
<b>Gradient threshold:</b> If the distance between last black pixel and the first non-black pixel in a given column is more than this value,
|
||||
we're looking at a gradient. If this happens while gradient detection is on, we don't change aspect ratio.<br/>
|
||||
<b>Gradient sample size:</b> This option is really only relevant when using 'lax' gradient detection. If we don't find a non-black pixel
|
||||
within this distance after last known black pixel when scanning a column, we presume we're not on a gradient.
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Black level:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.blackbar.blackLevel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Threshold:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.blackbar.threshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Gradient detection:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<select v-model="settings.active.arDetect.blackbar.antiGradientMode">
|
||||
<option :value="0">Disabled</option>
|
||||
<option :value="1">Lax</option>
|
||||
<option :value="2">Strict</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Image threshold
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.blackbar.imageThreshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Gradient threshold:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.blackbar.gradientThreshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showAdvancedOptions" class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Gradient sample size:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.blackbar.gradientSampleSize"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showAdvancedOptions">
|
||||
<div class="label">Black frame detection</div>
|
||||
<div class="description">
|
||||
Black frame detection is a quick test that tries to determine whether we're looking at a black frame. This test prevents
|
||||
us from wasting precious time trying to detect aspect ratio on frames that are too dark for reliable aspect ratio detection.<br/>
|
||||
<b>Sample width, height:</b> Sample size. Since we're checking <i>every</i> pixel in this sample, dimensions should be kept small.<br/>
|
||||
<b>Color variance treshold:</b> In videos (such as movie trailers), any text that appears on the black background is usually of
|
||||
a roughly the same color, while dark movie footage is not. This allows us to trigger autodetection on dark movie footage and
|
||||
to not trigger autodetection when movie trailer flashes some text on black background. If color variance is greater than this value,
|
||||
blackframe detection will use 'lax' (lower) cummulative threshold to determine whether the frame is black or not. If color variance
|
||||
is less than this value, 'strict' (higher) cummulative threshold will be used to determine whether the frame is black or not instead.<br/>
|
||||
<b>Cumulative threshold:</b> If we add the maximum of red, green, blue values of every pixel in the sample and they total more than this, the frame is bright enough.
|
||||
Comes in 'lax' and 'strict' versions. See 'color variance threshold' description for details about 'lax' and 'strict.'<br/>
|
||||
<b>Black pixel threshold:</b> If more than this fraction of pixels from the sample are "black", we consider the frame black. This overrules cumulative threshold.
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Blackframe sample width:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.canvasDimensions.blackframeCanvas.width"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Blackframe sample height:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.canvasDimensions.blackframeCanvas.height"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Color variance treshold:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.blackframe.sufficientColorVariance"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Cumulative threshold (lax mode):
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.blackframe.cumulativeThresholdLax"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Cumulative threshold (strict mode):
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.blackframe.cumulativeThresholdStrict"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Black pixel threshold:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type=""
|
||||
v-model="settings.active.arDetect.blackframe.blackPixelsCondition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label">Edge detection</div>
|
||||
<div class="description">
|
||||
Options in this section govern edge detection.<br/>
|
||||
<b>Sample width</b> — In a bid to detect "false" edges, we take two samples this many pixels wide near the point of our potential edge. One sample must be completely black, the other must contain a set
|
||||
amount of non-black pixels.<br/>
|
||||
<b>Detection threshold</b> — non-black sample mentioned above needs to contain at least this many non-black pixels.<br/>
|
||||
<b>Thickness quorum (per edge)</b> — amount of samples that agree on the thincknes of the blackbar that we need in order to establish aspect ratio. Every edge needs to have at least this many. Values higher than {{~~(settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold / 2)}} (quorum (total)/2) are pointless.<br/>
|
||||
<b>Thickness quorum (total)</b> — amount of samples that agree on the thinckess of the blackbar that we need in order to establish aspect ratio in case one of the edges doesn't contain enough samples to achieve quorum.<br/>
|
||||
<b>Logo threshold</b> — if edge candidate sits with count greater than this*all_samples, it can't be a logo or a watermark.<br/>
|
||||
<b>Ignore middle area</b> — When trying to detect area, ignore area between middle and canvasHeight * {this value} pixels towards the edge.<br/>
|
||||
<b>Detect limit</b> — stop search after finding a potential candidate in this many sample columns<br/>
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Sample width:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.edgeDetection.sampleWidth"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Detection threshold (px):
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.edgeDetection.detectionThreshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Thickness quorum (per edge):
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input
|
||||
v-model="settings.active.arDetect.edgeDetection.confirmationThreshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Thicnkess quorum (total):
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input
|
||||
v-model="settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding" v-if="showAdvancedOptions">
|
||||
<div class="flex label-secondary form-label">
|
||||
Logo threshold:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input
|
||||
v-model="settings.active.arDetect.edgeDetection.logoThreshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding" v-if="showAdvancedOptions">
|
||||
<div class="flex label-secondary form-label">
|
||||
Ignore middle area:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input
|
||||
v-model="settings.active.arDetect.edgeDetection.middleIgnoredArea"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding" v-if="showAdvancedOptions">
|
||||
<div class="flex label-secondary form-label">
|
||||
Detect limit:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input
|
||||
v-model="settings.active.arDetect.edgeDetection.minColsForSearch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label">Guard line</div>
|
||||
<div class="description">
|
||||
Quick test to determine whether aspect ratio hasn't changed. Test is performed by taking two samples on each edge of the image —
|
||||
one in the last row of the letterbox (blackbar), and one in the first row of the video (image).<br/>
|
||||
<b>Ignore edge margin:</b> We don't take blackbar and image samples {width * this} many pixels from left and right edge.<br/>
|
||||
<b>Image threshold:</b> If all pixels in blackbar are black and this fraction (0-1) of pixels in image are non-black, we presume that aspect ratio hasn't changed.<br/>
|
||||
<b>Edge tolerance (px):</b> I lied. Blackbar test happens this many pixels away from the last row of the letterbox.
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary ">
|
||||
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="checkbox"
|
||||
v-model="settings.active.arDetect.guardLine.enabled"
|
||||
/> Enable guardline</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Ignore edge margin:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.guardLine.ignoreEdgeMargin"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Image threshold:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.guardLine.imageTestThreshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Edge tolerance (px):
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.guardLine.edgeTolerancePx"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showAdvancedOptions">
|
||||
<div class="label">Aspect ratio change threshold</div>
|
||||
<div class="description">
|
||||
New and old aspect ratio must differ by at least this much (%, 1=100%) before we trigger aspect ratio correction.
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary form-label">
|
||||
Aspect ratio change threshold.
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type=""
|
||||
v-model="settings.active.arDetect.allowedMisaligned"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row button-box sticky-bottom">
|
||||
<Button label="Cancel"
|
||||
@click.native="cancel()"
|
||||
>
|
||||
</Button>
|
||||
<Button label="Save settings"
|
||||
@click.native="saveManual()"
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '../common/components/Button.vue';
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
},
|
||||
|
||||
props: ['settings'],
|
||||
data() {
|
||||
return {
|
||||
showAdvancedOptions: true,
|
||||
fallbackModeAvailable: false,
|
||||
sensitivity: 'sensitive',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.sensitivity = this.getSensitivity();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 10;
|
||||
canvas.height = 10;
|
||||
const ctx = canvas.getContext('2d');
|
||||
try {
|
||||
ctx.drawWindow(window,0, 0, 10, 10, "rgba(0,0,0,0)");
|
||||
this.fallbackModeAvailable = true;
|
||||
} catch (e) {
|
||||
// console.log("DrawWindow failed:", e)
|
||||
this.fallbackModeAvailable = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setArCheckFrequency(event) {
|
||||
this.settings.active.arDetect.timers.playing = Math.floor(Math.pow(Math.E, event));
|
||||
},
|
||||
getSensitivity() {
|
||||
if (this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold === 3 && this.settings.active.arDetect.edgeDetection.confirmationThreshold === 1) {
|
||||
return 'sensitive';
|
||||
}
|
||||
if (this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold === 5 && this.settings.active.arDetect.edgeDetection.confirmationThreshold === 2) {
|
||||
return 'balanced';
|
||||
}
|
||||
if (this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold === 7 && this.settings.active.arDetect.edgeDetection.confirmationThreshold === 3) {
|
||||
return 'accurate';
|
||||
}
|
||||
if (this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold === 16 && this.settings.active.arDetect.edgeDetection.confirmationThreshold === 8) {
|
||||
return 'strict';
|
||||
}
|
||||
return 'user-defined';
|
||||
},
|
||||
setConfirmationThresholds(sens) {
|
||||
console.log("setting conf treshold", sens)
|
||||
if (sens === 'sensitive') {
|
||||
this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold = 3;
|
||||
this.settings.active.arDetect.edgeDetection.confirmationThreshold = 1;
|
||||
} else if (sens === 'balanced') {
|
||||
this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold = 5;
|
||||
this.settings.active.arDetect.edgeDetection.confirmationThreshold = 2;
|
||||
} else if (sens === 'accurate') {
|
||||
this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold = 7;
|
||||
this.settings.active.arDetect.edgeDetection.confirmationThreshold = 3;
|
||||
} else if (sens === 'strict') {
|
||||
this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold = 16;
|
||||
this.settings.active.arDetect.edgeDetection.confirmationThreshold = 8;
|
||||
}
|
||||
|
||||
this.sensitivity = this.getSensitivity();
|
||||
},
|
||||
saveManual(){
|
||||
this.settings.save();
|
||||
// this.parsedSettings = JSON.stringify(this.settings.active, null, 2);
|
||||
// this.lastSettings = JSON.parse(JSON.stringify(this.settings.active));
|
||||
const ths = this;
|
||||
this.$nextTick( () => {
|
||||
ths.parsedSettings = JSON.stringify(ths.lastSettings, null, 2)
|
||||
ths.lastSettings = JSON.parse(JSON.stringify(ths.settings.active))
|
||||
});
|
||||
},
|
||||
cancel(){
|
||||
this.parsedSettings = '';
|
||||
this.settings.rollback();
|
||||
const ths = this;
|
||||
this.$nextTick( () => ths.parsedSettings = JSON.stringify(ths.lastSettings, null, 2) );
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-label {
|
||||
width: 20rem;
|
||||
text-align: right;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
</style>
|
185
src/options/GeneralSettings.vue
Normal file
185
src/options/GeneralSettings.vue
Normal file
@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="label">
|
||||
Enable this extension:
|
||||
</div>
|
||||
<div class="flex flex-row button-box">
|
||||
<Button label="Always"
|
||||
:selected="settings.active.sites['@global'].mode === ExtensionMode.Enabled"
|
||||
@click.native="setDefaultExtensionMode(ExtensionMode.Enabled)"
|
||||
>
|
||||
</Button>
|
||||
<Button label="On whitelisted sites"
|
||||
:selected="settings.active.sites['@global'].mode === ExtensionMode.Whitelist"
|
||||
@click.native="setDefaultExtensionMode(ExtensionMode.Whitelist)"
|
||||
>
|
||||
</Button>
|
||||
<Button label="Never"
|
||||
:selected="settings.active.sites['@global'].mode === ExtensionMode.Disabled"
|
||||
@click.native="setDefaultExtensionMode(ExtensionMode.Disabled)"
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="description">
|
||||
<b>Always</b> enables this extension on every site you visit that you didn't blacklist.<br/>
|
||||
<b>On whitelisted sites</b> enables this extension only on sites you explicitly whitelisted.<br/>
|
||||
<b>Never</b> disables extension on all sites, even on those you whitelisted.
|
||||
</div>
|
||||
|
||||
<div class="label">
|
||||
Enable autodetection:
|
||||
</div>
|
||||
<div class="flex flex-row button-box">
|
||||
<Button label="Always"
|
||||
:selected="settings.active.sites['@global'].autoar === ExtensionMode.Enabled"
|
||||
@click.native="setDefaultAutodetectionMode(ExtensionMode.Enabled)">
|
||||
</Button>
|
||||
<Button label="On whitelisted sites"
|
||||
:selected="settings.active.sites['@global'].autoar === ExtensionMode.Whitelist"
|
||||
@click.native="setDefaultAutodetectionMode(ExtensionMode.Whitelist)">
|
||||
</Button>
|
||||
<Button label="Never"
|
||||
:selected="settings.active.sites['@global'].autoar === ExtensionMode.Disabled"
|
||||
@click.native="setDefaultAutodetectionMode(ExtensionMode.Disabled)">
|
||||
</Button>
|
||||
</div>
|
||||
<div class="description">
|
||||
<b>Always</b> enables autodetection on every site this extension is enabled for, unless blacklisted.<br/>
|
||||
<b>On whitelisted sites</b> enables autodetection only for sites that you explicitly enabled.<br/>
|
||||
<b>Never</b> disables autodetection on all sites, even on those you whitelisted.<br/>
|
||||
<!-- <br/> -->
|
||||
<!-- For more settings related to autodetection, please check the 'Autodetection' tab. -->
|
||||
</div>
|
||||
|
||||
|
||||
<div class="label">
|
||||
Default video alignment:
|
||||
</div>
|
||||
<div class="flex flex-row button-box">
|
||||
<Button label="Left"
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignment.Left"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignment.Left)">
|
||||
</Button>
|
||||
<Button label="Center"
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignment.Center"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignment.Center)">
|
||||
</Button>
|
||||
<Button label="Right"
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignment.Right"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignment.Right)">
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="label">
|
||||
Default stretch mode:
|
||||
</div>
|
||||
<div class="flex flex-row button-box">
|
||||
<Button label="Don't stretch"
|
||||
:selected="settings.active.sites['@global'].stretch === Stretch.NoStretch"
|
||||
@click.native="setDefaultStretchingMode(Stretch.NoStretch)">
|
||||
</Button>
|
||||
<Button label="Basic stretch"
|
||||
:selected="settings.active.sites['@global'].stretch === Stretch.Basic"
|
||||
@click.native="setDefaultStretchingMode(Stretch.Basic)">
|
||||
</Button>
|
||||
<Button label="Hybrid stretch"
|
||||
:selected="settings.active.sites['@global'].stretch === Stretch.Hybrid"
|
||||
@click.native="setDefaultStretchingMode(Stretch.Hybrid)">
|
||||
</Button>
|
||||
<Button label="Thin borders only"
|
||||
:selected="settings.active.sites['@global'].stretch === Stretch.Conditional"
|
||||
@click.native="setDefaultStretchingMode(Stretch.Conditional)"
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="description">
|
||||
<b>None:</b> do not stretch the video at all. This is the default option, for men of culture.<br/>
|
||||
<b>Basic:</b> stretches video to fit the player or screen unconditionally. If video has letterbox encoded, this option <i>will not</i> try to remove letterbox before stretching. You probably shouldn't be using this option.<br/>
|
||||
<b>Hybrid:</b> stretches the video to fit the player, but only if cropping didn't completely remove the black bars.<br/>
|
||||
<b>Thin borders:</b> stretches only if the width of black borders after cropping is thin.
|
||||
<br/>
|
||||
Threshold for thin borders can be defined below.
|
||||
</div>
|
||||
<div class="indent">
|
||||
<div class="flex flex-row row-padding">
|
||||
<div class="flex label-secondary">
|
||||
Thin border threshold:
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="number"
|
||||
step="any"
|
||||
:value="settings.active.stretch.conditionalDifferencePercent"
|
||||
@input="updateStretchThreshold($event.target.value)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label">
|
||||
Reset settings
|
||||
</div>
|
||||
<div class="flex flex-row button-box">
|
||||
<Button label="Reset settings"
|
||||
@click.native="resetSettings()"
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '../common/components/Button';
|
||||
import Stretch from '../common/enums/stretch.enum';
|
||||
import ExtensionMode from '../common/enums/extension-mode.enum';
|
||||
import VideoAlignment from '../common/enums/video-alignment.enum';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
},
|
||||
props: {
|
||||
settings: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
ExtensionMode: ExtensionMode,
|
||||
VideoAlignment: VideoAlignment,
|
||||
stretchThreshold: 0,
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
methods: {
|
||||
setDefaultAutodetectionMode(mode) {
|
||||
this.settings.active.sites['@global'].autoar = mode;
|
||||
this.settings.save();
|
||||
},
|
||||
setDefaultExtensionMode(mode) {
|
||||
this.settings.active.sites['@global'].mode = mode;
|
||||
this.settings.save();
|
||||
|
||||
},
|
||||
setDefaultvideoAlignment(mode) {
|
||||
this.settings.active.sites['@global'].videoAlignment = mode;
|
||||
this.settings.save();
|
||||
},
|
||||
setDefaultStretchingMode(mode) {
|
||||
this.settings.active.sites['@global'].stretch = mode;
|
||||
this.settings.save();
|
||||
},
|
||||
updateStretchThreshold(newThreshold) {
|
||||
if (!newThreshold || isNaN(newThreshold)) {
|
||||
return;
|
||||
}
|
||||
this.settings.active.stretch.conditionalDifferencePercent = newThreshold;
|
||||
this.settings.save();
|
||||
},
|
||||
resetSettings() {
|
||||
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
|
||||
this.settings.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
100
src/options/SuperAdvancedSettings.vue
Normal file
100
src/options/SuperAdvancedSettings.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div style="padding-bottom: 100px;">
|
||||
<p>If you feel like you need an adventure, you can edit the settings the real manly way.</p>
|
||||
<p>Features of editing settings via text:
|
||||
<ul>
|
||||
<li>Even less validation than in gui way</li>
|
||||
<li>Includes settings that Tam hasn't (or won't) put into the GUI part of settings.</li>
|
||||
<li>Absolutely NO hand holding!</li>
|
||||
<li>Even less documentation (unless you go and crawl through source code on github)! Variable names are self-documenting (tm) enough</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>Save button is all the way to the bottom.</p>
|
||||
<div ref="settingsEditArea"
|
||||
style="white-space: pre-wrap; border: 1px solid orange; padding: 10px;"
|
||||
:class="{'jsonbg': !hasError, 'jsonbg-error': hasError}"
|
||||
contenteditable="true"
|
||||
@input="updateSettings"
|
||||
>{{parsedSettings}}</div>
|
||||
|
||||
|
||||
<div class="flex flex-row button-box sticky-bottom">
|
||||
<Button label="Cancel"
|
||||
@click.native="cancel()"
|
||||
>
|
||||
</Button>
|
||||
<Button label="Save settings"
|
||||
:disabled="hasError"
|
||||
@click.native="saveManual()"
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '../common/components/Button.vue';
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasError: false,
|
||||
parsedSettings: '',
|
||||
lastSettings: {},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parsedSettings = JSON.stringify(this.settings.active, null, 2);
|
||||
this.lastSettings = JSON.parse(JSON.stringify(this.settings.active));
|
||||
},
|
||||
props: {
|
||||
settings: Object,
|
||||
},
|
||||
computed: {
|
||||
// parsedSettings() {
|
||||
// return
|
||||
// }
|
||||
},
|
||||
methods: {
|
||||
updateSettings(val) {
|
||||
try {
|
||||
this.settings.active = JSON.parse(val.target.textContent);
|
||||
this.hasError = false;
|
||||
} catch (e) {
|
||||
this.hasError = true;
|
||||
}
|
||||
},
|
||||
saveManual(){
|
||||
if (this.hasError) {
|
||||
return;
|
||||
}
|
||||
this.settings.save();
|
||||
// this.parsedSettings = JSON.stringify(this.settings.active, null, 2);
|
||||
// this.lastSettings = JSON.parse(JSON.stringify(this.settings.active));
|
||||
const ths = this;
|
||||
this.$nextTick( () => {
|
||||
ths.parsedSettings = JSON.stringify(ths.lastSettings, null, 2)
|
||||
ths.lastSettings = JSON.parse(JSON.stringify(ths.settings.active))
|
||||
});
|
||||
},
|
||||
cancel(){
|
||||
this.parsedSettings = '';
|
||||
this.settings.rollback();
|
||||
const ths = this;
|
||||
this.$nextTick( () => ths.parsedSettings = JSON.stringify(ths.lastSettings, null, 2) );
|
||||
this.hasError = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.jsonbg {
|
||||
background-color: #131313;
|
||||
}
|
||||
.jsonbg-error {
|
||||
background-color: #884420;
|
||||
}
|
||||
</style>
|
42
src/options/about.vue
Normal file
42
src/options/about.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Ultrawidify - an aspect ratio fixer for youtube <small>(and netflix and various other sites)</small></h2>
|
||||
<p>Ultrawidify version: {{addonVersion}}. Created by Tamius Han (me).</p>
|
||||
<p><b>Having an issue?</b> Report <strike>undocumented features</strike> bugs using one of the following options:
|
||||
<ul>
|
||||
<li> <a target="_blank" href="https://github.com/xternal7/ultrawidify/issues"><b>Github</b></a> <b>(strongly preferred)</b><br/></li>
|
||||
<li>PM me on <a target="_blank" href="https://www.reddit.com/message/compose?to=xternal7&subject=[Ultrawidify]%20ENTER%20SUMMARY%20OF%20YOUR%20PROBLEM%20HERE&message=Describe+your+issue+in+more+detail.+Don%27t+forget+to+include%3A%0D%0A%2A+Extension+version%0D%0A%2A+Browser%0D%0A%2A+Operating+system%0D%0A%2A+Other+extensions+that+could+possibly+interfere%0D%0A%0D%0AIf+you%27re+reporting+an+issue+with+automatic+aspect+ratio+detection%2C+please+also+include+the+following+%28if+possible%29%3A%0D%0A%2A+model+and+make+of+your+CPU%0D%0A%2A+amount+of+RAM">reddit</a><br/></li>
|
||||
<li>Email: <a target="_blank" href="mailto:tamius.han@gmail.com?subject=%5BUltrawidify%5D+ENTER+SUMMARY+OF+YOUR+ISSUE+HERE&body=Describe+your+issue+in+more+detail.+Don%27t+forget+to+include%3A%0D%0A%2A+Extension+version%0D%0A%2A+Browser%0D%0A%2A+Operating+system%0D%0A%2A+Other+extensions+that+could+possibly+interfere%0D%0A%0D%0AIf+you%27re+reporting+an+issue+with+automatic+aspect+ratio+detection%2C+please+also+include+the+following+%28if+possible%29%3A%0D%0A%2A+model+and+make+of+your+CPU%0D%0A%2A+amount+of+RAM">tamius.han@gmail.com</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p> </p>
|
||||
|
||||
<p>If you're curious about the source code, <a href="https://github.com/xternal7/ultrawidify">github's here</a>. It's available under sorta-you-can-look-but-you-can't-touch licence, meaning: you can look, I won't mind if you shoot me a pull request, but I will mind if you're just gonna reupload this extension to the AMO/Chrome store.</p>
|
||||
<p>If you're looking at this page because you're bored and want to be bored some more, <a href="https://tamius.net">my website's here</a> and <a href="https://stuff.tamius.net/sacred-texts/">my blog is here</a>.</p>
|
||||
<p>I already have a 'donation' tab, but if you want to buy me a beer, <a href="https://paypal.me/tamius">my paypal's here</a>.</p>
|
||||
<h2>Plans for the future</h2>
|
||||
<p>Improving automatic detection, trying to re-implement in-player user interface (confirmed!), fixing bugs. Maybe even give the settings page a facelift.</p>
|
||||
<h2>Acknowledgements</h2>
|
||||
<p>This extension uses font <a href="http://overpassfont.org/">Overpass</a> and everything <a href="https://github.com/Kocal/vue-web-extension">this Vue template brings along</a>.</p>
|
||||
|
||||
<h2>Special thanks to</h2>
|
||||
<ul>
|
||||
<li>Everyone who reports bugs</li>
|
||||
<li>Everyone who waited for about 6-8 months for the 4.0 release. It was supposed to be released by the end of December 2018, we're in April. It was a major rewrite, though.</li>
|
||||
<li>Everyone who donated at any point in time.</li>
|
||||
<li>me for making this extension. You're welcome.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
addonVersion: browser.runtime.getManifest().version || chrome.runtime.getManifest().version,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
66
src/options/common/ConfirmationPopup.vue
Normal file
66
src/options/common/ConfirmationPopup.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="full-screen"
|
||||
@click="cancel()"
|
||||
>
|
||||
<div class="dialog-box flex flex-column" @click="$event.stopPropagation()">
|
||||
<div>
|
||||
{{dialogText}}
|
||||
</div>
|
||||
<div class="flex flex-row flex-end">
|
||||
<ShortcutButton class="flex b3 button"
|
||||
label="Ok"
|
||||
@click.native="confirm()"
|
||||
/>
|
||||
<ShortcutButton class="flex b3 button"
|
||||
label="Cancel"
|
||||
@click.native="cancel()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ShortcutButton from '../../common/components/ShortcutButton.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
dialogText: String,
|
||||
},
|
||||
components: {
|
||||
ShortcutButton
|
||||
},
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$emit('close')
|
||||
},
|
||||
confirm() {
|
||||
this.$emit('confirm')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.full-screen {
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
background-color: rgba(0,0,0,0.9);
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
max-width: 130rem;
|
||||
max-height: 42rem;
|
||||
padding-top: 2rem;
|
||||
padding-left: 3rem;
|
||||
padding-right: 3rem;
|
||||
}
|
||||
|
||||
</style>
|
329
src/options/controls-settings/AddEditActionPopup.vue
Normal file
329
src/options/controls-settings/AddEditActionPopup.vue
Normal file
@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<div class="full-screen"
|
||||
@click="cancel()"
|
||||
>
|
||||
<div class="dialog-box flex flex-column" @click="$event.stopPropagation()">
|
||||
<div class="window-title">
|
||||
{{actionIndex < 0 ? 'Add new action' : 'Edit action'}}
|
||||
</div>
|
||||
|
||||
<div class="command-chain-border">
|
||||
<CommandChain class="w100"
|
||||
:command="action.cmd"
|
||||
@new-command="addNewCommand()"
|
||||
@edit="editCommand"
|
||||
@delete="deleteCommand"
|
||||
>
|
||||
</CommandChain>
|
||||
<CommandAddEdit class="w100 cae-margin"
|
||||
v-if="addEditCommand"
|
||||
:action="currentCmd"
|
||||
@set-command="updateCommand"
|
||||
@close-popup="addEditCommand=false"
|
||||
>
|
||||
</CommandAddEdit>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column section-pad">
|
||||
<div class="flex flex-row">
|
||||
<div class="flex label-secondary form-label">
|
||||
<span class="w100">
|
||||
Action name:
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-input flex-grow">
|
||||
<input type="text"
|
||||
class="w100"
|
||||
:value="actionIndex < 0 ? action.name : settings.active.actions[actionIndex].name"
|
||||
@input="updateActionName($event.target.value)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="flex label-secondary form-label">
|
||||
<span class="w100">
|
||||
Default label:
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-input flex-grow">
|
||||
<input type="text"
|
||||
class="w100"
|
||||
:value="actionIndex < 0 ? action.label : settings.active.actions[actionIndex].label"
|
||||
@input="updateActionLabel($event.target.value)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row section-pad">
|
||||
<b>Show this action in the following tabs:</b>
|
||||
</div>
|
||||
|
||||
<template v-if="action && action.cmd[0] && action.cmd[0].action !== 'set-ar'">
|
||||
<div class="tab-title">Extension settings (global)</div>
|
||||
<ScopeSettings :scopeOptions="globalScopeOptions"
|
||||
@show="updateScopes('global', 'show', $event)"
|
||||
@set-label="updateScopes('global', 'label', $event)"
|
||||
@set-shortcut="updateScopes('global', 'shortcut', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="action && action.cmd[0] && action.cmd[0].action !== 'set-ar'">
|
||||
<div class="tab-title">Site settings (site)</div>
|
||||
<ScopeSettings :scopeOptions="siteScopeOptions"
|
||||
@show="updateScopes('site', 'show', $event)"
|
||||
@set-label="updateScopes('site', 'label', $event)"
|
||||
@set-shortcut="updateScopes('site', 'shortcut', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div class="tab-title">Video settings (page)</div>
|
||||
<ScopeSettings :scopeOptions="pageScopeOptions"
|
||||
@show="updateScopes('page', 'show', $event)"
|
||||
@set-label="updateScopes('page', 'label', $event)"
|
||||
@set-shortcut="updateScopes('page', 'shortcut', $event)"
|
||||
/>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
<ShortcutButton class="flex b3 button"
|
||||
label="Save"
|
||||
@click.native="saveSettings()"
|
||||
/>
|
||||
<ShortcutButton class="flex b3 button"
|
||||
label="Cancel"
|
||||
@click.native="cancel()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ShortcutButton from '../../common/components/ShortcutButton.vue'
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import KeyboardShortcutParser from '../../common/js/KeyboardShortcutParser';
|
||||
import CommandChain from './command-builder/CommandChain';
|
||||
import CommandAddEdit from './command-builder/CommandAddEdit';
|
||||
import ScopeSettings from './scope-settings-component/ScopeSettings';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
settings: Object,
|
||||
actionIndex: Number,
|
||||
},
|
||||
components: {
|
||||
CommandChain: CommandChain,
|
||||
CommandAddEdit: CommandAddEdit,
|
||||
ScopeSettings,
|
||||
ShortcutButton
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
action: {
|
||||
name: 'New action',
|
||||
label: 'New action',
|
||||
cmd: [],
|
||||
scopes: {}
|
||||
},
|
||||
addEditCommand: false,
|
||||
currentCmdIndex: -1,
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.actionIndex >= 0) {
|
||||
// this.currentCmdIndex = this.actionIndex;
|
||||
this.action = this.settings.active.actions[this.actionIndex];
|
||||
this.currentCmdIndex = this.actionIndex;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
globalScopeOptions: function() {
|
||||
return {
|
||||
show: this.action.scopes.global && this.action.scopes.global.show || false,
|
||||
label: (this.action.scopes.global && this.action.scopes.global.label) ? this.action.scopes.global.label : '',
|
||||
shortcut: this.action.scopes.global && this.action.scopes.global.shortcut
|
||||
}
|
||||
},
|
||||
siteScopeOptions: function() {
|
||||
return {
|
||||
show: this.action.scopes.site && this.action.scopes.site.show || false,
|
||||
label: (this.action.scopes.site && this.action.scopes.site.label) ? this.action.scopes.site.label : '',
|
||||
shortcut: this.action.scopes.site && this.action.scopes.site.shortcut
|
||||
}
|
||||
},
|
||||
pageScopeOptions: function() {
|
||||
return {
|
||||
show: this.action.scopes.page && this.action.scopes.page.show || false,
|
||||
label: (this.action.scopes.page && this.action.scopes.page.label) ? this.action.scopes.page.label : '',
|
||||
shortcut: this.action.scopes.page && this.action.scopes.page.shortcut
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
action: {
|
||||
deep: true,
|
||||
handler: function(val) {
|
||||
this.action = val;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseActionShortcut(action) {
|
||||
return KeyboardShortcutParser.parseShortcut(action.shortcut[0]);
|
||||
},
|
||||
parseCommand(cmd) {
|
||||
let cmdstring = '';
|
||||
for(const c of cmd) {
|
||||
cmdstring += `${c.action} ${c.arg} ${c.customArg ? '' : c.customArg} | ${c.persistent ? ' persistent' : ''}; `;
|
||||
}
|
||||
return cmdstring;
|
||||
},
|
||||
updateActionName(newName) {
|
||||
this.action.name = newName;
|
||||
},
|
||||
updateActionLabel(newLabel) {
|
||||
this.action.label = newLabel;
|
||||
},
|
||||
updateScopes(scope, prop, value) {
|
||||
if(this.action.scopes[scope] === undefined) {
|
||||
this.action.scopes[scope] = {};
|
||||
}
|
||||
this.action.scopes[scope][prop] = value;
|
||||
|
||||
// TODO: remove for release
|
||||
// this.action = JSON.parse(JSON.stringify(this.action))
|
||||
},
|
||||
addNewCommand() {
|
||||
this.currentCmdIndex = -1;
|
||||
this.currentCmd = undefined;
|
||||
this.addEditCommand = true;
|
||||
},
|
||||
editCommand(index) {
|
||||
this.currentCmdIndex = index;
|
||||
this.currentCmd = this.action.cmd[index];
|
||||
this.addEditCommand = true;
|
||||
},
|
||||
deleteCommand(index) {
|
||||
this.action.cmd.splice(index,1);
|
||||
},
|
||||
updateCommand(action, arg, customArg) {
|
||||
this.addEditCommand = false;
|
||||
|
||||
if (this.currentCmdIndex < 0) {
|
||||
this.action.cmd.push({
|
||||
action: action,
|
||||
arg: arg,
|
||||
customArg: customArg,
|
||||
});
|
||||
} else {
|
||||
this.action.cmd[this.currentCmdIndex] = {
|
||||
action: action,
|
||||
arg: arg,
|
||||
customArg: customArg,
|
||||
};
|
||||
}
|
||||
this.action = JSON.parse(JSON.stringify(this.action));
|
||||
},
|
||||
saveSettings() {
|
||||
if (this.currentCmdIndex < 0) {
|
||||
this.settings.active.actions.push(this.action);
|
||||
}
|
||||
this.settings.save();
|
||||
this.close();
|
||||
},
|
||||
cancel() {
|
||||
this.settings.rollback();
|
||||
this.close();
|
||||
},
|
||||
close() {
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../res/css/colors';
|
||||
|
||||
.cae-margin {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.full-screen {
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
background-color: rgba(0,0,0,0.9);
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
max-width: 130rem;
|
||||
max-height: 42rem;
|
||||
padding-top: 2rem;
|
||||
padding-left: 3rem;
|
||||
padding-right: 3rem;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
font-size: 2.4rem;
|
||||
font-variant: small-caps;
|
||||
margin-bottom: 1.69rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
width: 16rem;
|
||||
text-align: left;
|
||||
// text-align: right;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-size: 1.2em;
|
||||
color: lighten($primary-color, 25%);
|
||||
padding-top: 20px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
opacity: 50% !important;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.w100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
padding-top: 0.4em;
|
||||
width: 4em;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,0.66);
|
||||
color: #ffc;
|
||||
height: 1.7em;
|
||||
}
|
||||
|
||||
.command-chain-border {
|
||||
border: 1px dotted lighten($selected-color, 10%);
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-pad {
|
||||
padding-top: 35px;
|
||||
}
|
||||
|
||||
</style>
|
164
src/options/controls-settings/ControlsSettings.vue
Normal file
164
src/options/controls-settings/ControlsSettings.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div>
|
||||
<p>Here you can create, edit or remove actions. This includes adding keyboard shortcuts that trigger them, or toggling visibility of each action in the extension popup.</p>
|
||||
<p><b>Note that this section is highly experimental.</b> If there's extension-breaking bugs, please file a bug report on github or e-mail me.
|
||||
However, there's some bugs that I'm aware of, but have little intentions to fix any time soon: <ul>
|
||||
<li><b>Crop actions</b> (and maybe some others) are limited to the <i>page</i> scope (‘Video settings’ in popup) by design.
|
||||
Yes, 'global' and 'site' checkboxes are still there, but they won't do anything. I probably won't remove them any time soon,
|
||||
because I'd honestly rather spend my finite time on other things.</li>
|
||||
<li>UI looks absolutely atrocious, and it's prolly gonna stay that way for a while. I am open to specific design suggestions, though.</li>
|
||||
<li><b>For shortcuts:</b> you can use modifier keys (ctrl/alt/meta/shift), but remember to release modifier keys last. For example,
|
||||
if you want to do use shift-S for anything: press both keys, release S, release shift. If you release shift first, extension
|
||||
will think you were holding only shift key. You're prolly used to doing things that way already. If you're not — bad news,
|
||||
reworking how keyboard shortcuts work isn't going to happen for a while.</li>
|
||||
<li>I don't think reordering (in a user-friendly way at least) is gonna happen any time soon.</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p> </p>
|
||||
|
||||
<div class="flex flex-row button-box">
|
||||
<Button label="Add new action"
|
||||
@click.native="addAction()"
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column">
|
||||
<div class="action-item-category-header">
|
||||
Crop actions
|
||||
</div>
|
||||
<template v-for="(action, index) of settings.active.actions">
|
||||
<ActionAlt v-if="action.cmd.length === 1 && action.cmd[0].action === 'set-ar'"
|
||||
:key="index"
|
||||
:action="action"
|
||||
@edit="changeShortcut(index)"
|
||||
@remove="removeAction(index)"
|
||||
>
|
||||
</ActionAlt>
|
||||
</template>
|
||||
<div class="action-item-category-header">
|
||||
Stretch actions
|
||||
</div>
|
||||
<template v-for="(action, index) of settings.active.actions">
|
||||
<ActionAlt v-if="action.cmd.length === 1 && action.cmd[0].action === 'set-stretch'"
|
||||
:key="index"
|
||||
:action="action"
|
||||
@edit="changeShortcut(index)"
|
||||
@remove="removeAction(index)"
|
||||
>
|
||||
</ActionAlt>
|
||||
</template>
|
||||
|
||||
|
||||
<div class="action-item-category-header">
|
||||
Alignment actions
|
||||
</div>
|
||||
<template v-for="(action, index) of settings.active.actions">
|
||||
<ActionAlt v-if="action.cmd.length === 1 && action.cmd[0].action === 'set-alignment'"
|
||||
:key="index"
|
||||
:action="action"
|
||||
@edit="changeShortcut(index)"
|
||||
@remove="removeAction(index)"
|
||||
>
|
||||
</ActionAlt>
|
||||
</template>
|
||||
|
||||
<div class="action-item-category-header">
|
||||
Zoom / panning actions
|
||||
</div>
|
||||
<template v-for="(action, index) of settings.active.actions">
|
||||
<ActionAlt v-if="action.cmd.length === 1 && (
|
||||
action.cmd[0].action === 'change-zoom' ||
|
||||
action.cmd[0].action === 'set-zoom' ||
|
||||
action.cmd[0].action === 'set-pan' ||
|
||||
action.cmd[0].action === 'pan' ||
|
||||
action.cmd[0].action === 'set-pan'
|
||||
)"
|
||||
:key="index"
|
||||
:action="action"
|
||||
@edit="changeShortcut(index)"
|
||||
@remove="removeAction(index)"
|
||||
>
|
||||
</ActionAlt>
|
||||
</template>
|
||||
|
||||
<div class="action-item-category-header">
|
||||
Other actions
|
||||
</div>
|
||||
<template v-for="(action, index) of settings.active.actions">
|
||||
<ActionAlt v-if="action.cmd.length > 1 || (
|
||||
action.cmd[0].action !== 'change-zoom' &&
|
||||
action.cmd[0].action !== 'set-zoom' &&
|
||||
action.cmd[0].action !== 'set-pan' &&
|
||||
action.cmd[0].action !== 'pan' &&
|
||||
action.cmd[0].action !== 'set-pan' &&
|
||||
action.cmd[0].action !== 'set-alignment' &&
|
||||
action.cmd[0].action !== 'set-stretch' &&
|
||||
action.cmd[0].action !== 'set-ar'
|
||||
)"
|
||||
:key="index"
|
||||
:action="action"
|
||||
@edit="changeShortcut(index)"
|
||||
@remove="removeAction(index)"
|
||||
>
|
||||
</ActionAlt>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '../../common/components/Button';
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import ActionAlt from '../../common/components/ActionAlt';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
ActionAlt,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
tableVisibility: {
|
||||
crop: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
props: {
|
||||
settings: Object
|
||||
},
|
||||
watch: {
|
||||
settings: (newVal, oldVal) => {
|
||||
this.settings = newVal;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeAction(index) {
|
||||
this.$emit('remove-event', index)
|
||||
},
|
||||
addAction() {
|
||||
this.$emit('edit-event', -1);
|
||||
},
|
||||
changeShortcut(index) {
|
||||
this.$emit('edit-event', index);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../res/css/colors.scss';
|
||||
|
||||
.action-item-category-header {
|
||||
margin-top: 3rem;
|
||||
color: $primary-color;
|
||||
font-size: 2.4rem;
|
||||
font-variant: small-caps;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
154
src/options/controls-settings/command-builder/CommandAddEdit.vue
Normal file
154
src/options/controls-settings/command-builder/CommandAddEdit.vue
Normal file
@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="window-content">
|
||||
<!-- ACTION SELECT -->
|
||||
<div class="flex flex-row">
|
||||
<div class="flex label-secondary form-label">
|
||||
<span class="w100">
|
||||
Select action:
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-grow flex-input">
|
||||
<select class=""
|
||||
:value="selectedAction"
|
||||
@change="setAction($event.target.value)"
|
||||
>
|
||||
<option :value="undefined" selected disabled>Select ...</option>
|
||||
<option v-for="(action, key) in ActionList"
|
||||
:value="key"
|
||||
:key="key"
|
||||
>
|
||||
{{action.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ARGUMENT SELECT -->
|
||||
<div v-if="selectedAction && ActionList[selectedAction]"
|
||||
class="flex flex-row">
|
||||
<div class="flex label-secondary form-label">
|
||||
<span class="w100">
|
||||
Select action parameter:
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-grow flex-input">
|
||||
<select class=""
|
||||
:value="selectedArgument ? selectedArgument.arg : undefined"
|
||||
@change="setArgument($event.target.value)"
|
||||
>
|
||||
<option :value="undefined" selected disabled>Select ...</option>
|
||||
<option v-for="arg of ActionList[selectedAction].args"
|
||||
:key="arg.arg"
|
||||
:value="arg.arg"
|
||||
>
|
||||
{{arg.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CUSTOM ARGUMENT INPUT -->
|
||||
<div v-if="selectedArgument && selectedArgument.customArg"
|
||||
class="flex flex-row">
|
||||
<div class="flex label-secondary form-label">
|
||||
<span class="w100">
|
||||
{{selectedArgument.name}}:
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-grow flex-input">
|
||||
<input type="text"
|
||||
class="w100"
|
||||
v-model="customArgumentValue"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-end close-save-button-margin">
|
||||
<div class="button"
|
||||
@click="$emit('close-popup')"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div class="button"
|
||||
@click="emitCommand()"
|
||||
>
|
||||
Save
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionList from '../../../ext/conf/ActionList';
|
||||
import Stretch from '../../../common/enums/stretch.enum';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
ActionList: ActionList,
|
||||
selectedAction: undefined,
|
||||
selectedArgument: undefined,
|
||||
customArgumentValue: undefined
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.action) {
|
||||
this.selectedAction = this.action.action;
|
||||
this.selectedArgument = {
|
||||
name: ActionList[this.action.action].args.find(x => x.arg === this.action.arg) || ActionList[this.action.action].args.find(x => x.customArg),
|
||||
arg: this.action.arg
|
||||
}
|
||||
this.customArgumentValue = this.action.customArg;
|
||||
}
|
||||
|
||||
// console.log("this.actionList", ActionList, this.ActionList)
|
||||
// for(const a in ActionList) {
|
||||
// console.log(a);
|
||||
// }
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
props: {
|
||||
action: Object,
|
||||
scope: String,
|
||||
},
|
||||
methods: {
|
||||
setAction(cmd) {
|
||||
this.selectedAction = cmd;
|
||||
this.selectedArgument = undefined;
|
||||
this.customArgumentValue = undefined;
|
||||
},
|
||||
setArgument(arg) {
|
||||
this.selectedArgument = ActionList[this.selectedAction].args.find(x => x.arg == arg);
|
||||
this.customArgumentValue = undefined;
|
||||
},
|
||||
emitCommand() {
|
||||
this.$emit(
|
||||
'set-command',
|
||||
this.selectedAction,
|
||||
this.selectedArgument.arg,
|
||||
this.customArgumentValue
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.form-label {
|
||||
width: 15rem;
|
||||
text-align: left;
|
||||
/* text-align: right; */
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.close-save-button-margin {
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
</style>
|
103
src/options/controls-settings/command-builder/CommandBlock.vue
Normal file
103
src/options/controls-settings/command-builder/CommandBlock.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="block-main">
|
||||
<div class="button-main">
|
||||
<div class="button-action-display">
|
||||
{{ActionList[action.action].name}}: {{
|
||||
(ActionList[action.action].args.find(x => x.arg === action.arg) || action).arg
|
||||
}}
|
||||
</div>
|
||||
<div class="actions flex flex-row flex-center flex-cross-center">
|
||||
<div v-if="!first || true"
|
||||
class="flex order-button"
|
||||
@click="$emit('move-left')"
|
||||
>
|
||||
<
|
||||
</div>
|
||||
<div class="flex flex-grow command-action"
|
||||
@click="$emit('delete')"
|
||||
>
|
||||
🗙
|
||||
</div>
|
||||
<div class="flex flex-grow command-action"
|
||||
@click="$emit('edit')"
|
||||
>
|
||||
🖉
|
||||
</div>
|
||||
<div v-if="!last || true"
|
||||
class="flex order-button"
|
||||
@click="$emit('move-right')"
|
||||
>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import ActionList from '../../../ext/conf/ActionList';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
ActionList: ActionList,
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
props: {
|
||||
action: Object,
|
||||
first: Boolean,
|
||||
last: Boolean
|
||||
},
|
||||
methods: {
|
||||
parseActionShortcut(action) {
|
||||
if (action.shortcut) {
|
||||
return KeyboardShortcutParser.parseShortcut(action.shortcut[0]);
|
||||
}
|
||||
},
|
||||
setAction(cmd) {
|
||||
this.selectedAction = cmd;
|
||||
this.selectedArgument = undefined;
|
||||
this.customArgumentValue = undefined;
|
||||
},
|
||||
setArgument(arg) {
|
||||
this.selectedArgument = arg;
|
||||
this.customArgumentValue = undefined;
|
||||
},
|
||||
// emitCommand() {
|
||||
// this.$emit(
|
||||
// 'set-command',
|
||||
// this.selectedAction,
|
||||
// this.customArgumentValue ? this.customArgumentValue : this.selectedArgument.arg
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './../../../res/css/colors';
|
||||
|
||||
.block-main {
|
||||
border: 1px solid $primary-color;
|
||||
min-width: 10rem;
|
||||
padding-top: 0.1rem;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.button-action-display, .command-action, .order-button {
|
||||
display: block;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.button-action-display {
|
||||
padding-top: 0.1rem;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="command-chain flex flex-row flex-center flex-cross-center">
|
||||
<!-- {{command}} -->
|
||||
<div class="flex flex-row flex-center flex-cross-center">
|
||||
<CommandBlock v-for="(cmd, index) of command"
|
||||
:key="index"
|
||||
:action="cmd"
|
||||
@edit="$emit('edit', index)"
|
||||
@delete="$emit('delete', index)"
|
||||
@move-left="$emit('move-left', index)"
|
||||
@move-right="$emit('move-left', (index + 1))"
|
||||
>
|
||||
</CommandBlock>
|
||||
<div class="new-command h100 flex flex-column flex-center flex-cross-center"
|
||||
@click="$emit('new-command')"
|
||||
>
|
||||
<div><b>+</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommandBlock from './CommandBlock';
|
||||
import CommandAddEdit from './CommandAddEdit';
|
||||
|
||||
export default {
|
||||
created () {
|
||||
},
|
||||
components: {
|
||||
CommandBlock,
|
||||
CommandAddEdit,
|
||||
},
|
||||
props: [
|
||||
'command'
|
||||
],
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../res/css/colors';
|
||||
|
||||
.new-command {
|
||||
color: $primary-color;
|
||||
height: 100%;
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-row">
|
||||
<div class="flex label-secondary form-label">
|
||||
Show in popup:
|
||||
</div>
|
||||
<div class="flex flex-input flex-grow">
|
||||
<input type="checkbox"
|
||||
:checked="scopeOptions.show"
|
||||
@input="$emit('show', $event.target.checked)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="flex label-secondary form-label">
|
||||
Custom label <small>(optional)</small>:
|
||||
</div>
|
||||
<div class="flex flex-input flex-grow">
|
||||
<input type="text"
|
||||
v-model="scopeOptions.label"
|
||||
@input="setLabel($event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="flex label-secondary form-label">
|
||||
<span class="w100">
|
||||
Shortcut:
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-input flex-grow">
|
||||
<SetShortcutButton
|
||||
:shortcut="scopeOptions.shortcut"
|
||||
@set-shortcut="$emit('set-shortcut', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SetShortcutButton from './SetShortcutButton.vue';
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SetShortcutButton
|
||||
},
|
||||
props: {
|
||||
scopeOptions: Object,
|
||||
},
|
||||
methods: {
|
||||
setLabel(label) {
|
||||
if (label.trim() === '') {
|
||||
label = undefined;
|
||||
}
|
||||
this.$emit('set-label', label.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="w100">
|
||||
<input type="text"
|
||||
class="shortcut-change-input"
|
||||
ref="input"
|
||||
:value="shortcutText"
|
||||
@focus="initiateKeypress"
|
||||
@keyup="processKeyup"
|
||||
/>
|
||||
<span v-if="shortcut" @click="$emit('set-shortcut')">(Clear shortcut)</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import KeyboardShortcutParser from '../../../common/js/KeyboardShortcutParser'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
shortcut: Array, // note: array in unlikely case we ever try to implement choords
|
||||
waitingForPress: false,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
shortcutText: '[click to add shortcut]'
|
||||
}
|
||||
},
|
||||
created(){
|
||||
if (this.shortcut && this.shortcut.length) {
|
||||
this.shortcutText = KeyboardShortcutParser.parseShortcut(this.shortcut[0]);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initiateKeypress() {
|
||||
this.shortcutText = '[Press a key or key combination]';
|
||||
this.waitingForPress = true;
|
||||
},
|
||||
processKeyup(event) {
|
||||
if (this.waitingForPress) {
|
||||
const shortcut = {
|
||||
key: event.key,
|
||||
ctrlKey: event.ctrlKey,
|
||||
metaKey: event.metaKey,
|
||||
altKey: event.altKey,
|
||||
shiftKey: event.shiftKey,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
};
|
||||
this.$emit('set-shortcut', [shortcut])
|
||||
this.$refs.input.blur();
|
||||
this.shortcutText = KeyboardShortcutParser.parseShortcut(shortcut);
|
||||
}
|
||||
this.waitingForPress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style style="scss" scoped>
|
||||
.shortcut-change-input {
|
||||
background-color: transparent;
|
||||
border: 0px solid transparent;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
15
src/options/options.html
Normal file
15
src/options/options.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ultravidify - Options</title>
|
||||
<link rel="stylesheet" href="options.css">
|
||||
<% if (NODE_ENV === 'development') { %>
|
||||
<!-- Load some resources only in development environment -->
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
11
src/options/options.js
Normal file
11
src/options/options.js
Normal file
@ -0,0 +1,11 @@
|
||||
// console.log("global browser", browser, global.browser)
|
||||
// global.browser = require('webextension-polyfill')
|
||||
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
})
|
249
src/options/options.scss
Normal file
249
src/options/options.scss
Normal file
@ -0,0 +1,249 @@
|
||||
@import "../res/css/font/overpass.css";
|
||||
@import "../res/css/font/overpass-mono.css";
|
||||
@import "../res/css/colors.scss";
|
||||
@import "../res/css/common.scss";
|
||||
|
||||
body {
|
||||
background-image: url('/res/img/settings/bg_random2.png');
|
||||
background-size: 25em;
|
||||
|
||||
text-align: center;
|
||||
z-index: -1000;
|
||||
}
|
||||
|
||||
/*
|
||||
+
|
||||
+ BASIC LAYOUT
|
||||
+
|
||||
*/
|
||||
|
||||
.header {
|
||||
width: 42rem;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
width: 100%;
|
||||
padding-left: 42rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.text-sink-anchor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.text-sink {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
}
|
||||
.title-sink-pad {
|
||||
padding-bottom: 1.69rem;
|
||||
}
|
||||
|
||||
/*
|
||||
+
|
||||
+ SIDE MENU
|
||||
+
|
||||
*/
|
||||
|
||||
.extension-name {
|
||||
font-size: 4.20rem;
|
||||
height: 7.7rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu {
|
||||
font-size: 1.9rem;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
/*
|
||||
+
|
||||
+ TAB PANEL
|
||||
+
|
||||
*/
|
||||
|
||||
.content-title {
|
||||
font-size: 3.3rem;
|
||||
height: 7.7rem;
|
||||
}
|
||||
|
||||
.content-content {
|
||||
width: 100%;
|
||||
max-width: 96rem;
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.block {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.tabline {
|
||||
background-color: #000;
|
||||
width: 100%;
|
||||
margin-bottom: 1.5em;
|
||||
padding-top: 0.3em;
|
||||
padding-bottom: 0.4em;
|
||||
z-index: -200;
|
||||
}
|
||||
|
||||
.dup_keybinds{
|
||||
background-color: #720 !important;
|
||||
}
|
||||
|
||||
.uw_shortcuts_line{
|
||||
padding-top: 0.25em;
|
||||
padding-left: 5em;
|
||||
}
|
||||
.uw_shortcuts_label{
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
width: 17.5em;
|
||||
}
|
||||
.uw_options_line{
|
||||
margin-top: 0.75em;
|
||||
font-size: 1.1em;
|
||||
width: 80%;
|
||||
margin-left: 5%;
|
||||
}
|
||||
.uw_options_option{
|
||||
margin-left: 5%;
|
||||
}
|
||||
.uw_suboption{
|
||||
margin-left: 5em;
|
||||
margin-top: 0.75em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.uw_options_desc, .uw_suboption_desc{
|
||||
margin-top: 0.33em;
|
||||
font-size: 0.69em;
|
||||
color: #aaa;
|
||||
}
|
||||
.uw_suboption_desc{
|
||||
margin-left: 5em;
|
||||
}
|
||||
|
||||
.buttonbar{
|
||||
/* width: 100%; */
|
||||
padding-left: 20em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
padding-top: 0.4em;
|
||||
width: 4em;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,0.66);
|
||||
color: #ffc;
|
||||
height: 1.7em;
|
||||
}
|
||||
|
||||
.monospace {
|
||||
font-family: 'Overpass Mono', monospace
|
||||
}
|
||||
|
||||
.center-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/** site options css **/
|
||||
.site_name {
|
||||
padding-left: 1em;
|
||||
padding-bottom: 0.3em;
|
||||
color: #fff;
|
||||
font-size: 1.1em;
|
||||
height: 13em !important;
|
||||
|
||||
}
|
||||
.site_details {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.site_title_ebox {
|
||||
width: 10em !important;
|
||||
font-size: 1.5em !important;
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
margin-left: 0px !important;
|
||||
border: 0px !important;
|
||||
color: #ffc !important;
|
||||
}
|
||||
.details_ebox {
|
||||
width: 12em !important;
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
border: 0px !important;
|
||||
color: #ffc !important;
|
||||
margin-left: 0em !important;
|
||||
}
|
||||
.details_ebox:disabled {
|
||||
color: #aaa !important;
|
||||
}
|
||||
|
||||
.checkbox-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.checkbox-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding-left: 35px;
|
||||
margin-bottom: 12px;
|
||||
/* cursor: pointer; */
|
||||
font-size: 22px;
|
||||
/* -webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; */
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.checkbox-checked {
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
top: 5px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid rgb(245, 145, 63);
|
||||
border-width: 0 3px 3px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* ACTION TABLE STYLES */
|
||||
|
||||
.action-item-category-label {
|
||||
color: rgb(245, 145, 63);
|
||||
font-size: 2em;
|
||||
font-weight: 200;
|
||||
margin-bottom: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.action-item-table-header {
|
||||
color: rgb(245, 145, 63);
|
||||
}
|
||||
|
||||
.action-item-table-header-small {
|
||||
color: rgb(245, 145, 63);
|
||||
font-size: 0.85em !important;
|
||||
font-weight: 300 !important;
|
||||
}
|
||||
|
||||
.action-list-item:hover {
|
||||
background-color: rgba(254, 146, 63, 0.15);
|
||||
}
|
377
src/popup/App.vue
Normal file
377
src/popup/App.vue
Normal file
@ -0,0 +1,377 @@
|
||||
<template>
|
||||
<div class="popup">
|
||||
<div class="header">
|
||||
<span class="smallcaps">Ultrawidify</span>: <small>Quick settings</small>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<!-- TABS/SIDEBAR -->
|
||||
<div id="tablist" class="flex flex-column flex-nogrow flex-noshrink">
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'global'}"
|
||||
@click="selectTab('global')"
|
||||
>
|
||||
<div class="">
|
||||
Extension settings
|
||||
</div>
|
||||
<div class="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'site'}"
|
||||
@click="selectTab('site')"
|
||||
>
|
||||
<div class="">
|
||||
Site settings
|
||||
</div>
|
||||
<div class="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'video'}"
|
||||
@click="selectTab('video')"
|
||||
>
|
||||
<div class="">
|
||||
Video settings
|
||||
</div>
|
||||
<div v-if="selectedTab === 'video' && this.activeFrames.length > 0"
|
||||
class=""
|
||||
>
|
||||
<small>Select embedded frame to control:</small>
|
||||
<div class="">
|
||||
<div v-for="frame of activeFrames"
|
||||
class="tabitem"
|
||||
:class="{'tabitem-selected': selectedFrame === frame.id}"
|
||||
:key="frame.id"
|
||||
@click="selectFrame(frame.id)"
|
||||
>
|
||||
{{frame.label}} <span v-if="frame.name !== undefined && frame.color" :style="{'background-color': frame.color}">[{{frame.name}}]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'about'}"
|
||||
@click="selectTab('about')"
|
||||
>
|
||||
<div class="">
|
||||
About
|
||||
</div>
|
||||
<div class="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item"
|
||||
:class="{'selected-tab': selectedTab === 'donate'}"
|
||||
@click="selectTab('donate')"
|
||||
>
|
||||
<div class="">
|
||||
Donate
|
||||
</div>
|
||||
<div class="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PANELS/CONTENT -->
|
||||
<div id="tab-content" class="flex-grow" style="max-width: 480px !important;">
|
||||
<VideoPanel v-if="settings && settings.active && selectedTab === 'video'"
|
||||
class=""
|
||||
:settings="settings"
|
||||
:frame="selectedFrame"
|
||||
:zoom="currentZoom"
|
||||
@zoom-change="updateZoom($event)"
|
||||
/>
|
||||
<DefaultSettingsPanel v-if="settings && settings.active && (selectedTab === 'site' || selectedTab === 'global')"
|
||||
class=""
|
||||
:settings="settings"
|
||||
:scope="selectedTab"
|
||||
:site="site && site.host"
|
||||
/>
|
||||
<PerformancePanel v-if="selectedTab === 'performance-metrics'"
|
||||
:performance="performance" />
|
||||
<AboutPanel v-if="selectedTab === 'about'" />
|
||||
<Donate v-if="selectedTab === 'donate'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Donate from '../common/misc/Donate.vue';
|
||||
import Debug from '../ext/conf/Debug';
|
||||
import BrowserDetect from '../ext/conf/BrowserDetect';
|
||||
import Comms from '../ext/lib/comms/Comms';
|
||||
import VideoPanel from './panels/VideoPanel';
|
||||
import PerformancePanel from './panels/PerformancePanel';
|
||||
import Settings from '../ext/lib/Settings';
|
||||
import ExecAction from './js/ExecAction.js';
|
||||
import DefaultSettingsPanel from './panels/DefaultSettingsPanel';
|
||||
import AboutPanel from './panels/AboutPanel';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
selectedTab: 'video',
|
||||
selectedFrame: '__all',
|
||||
activeFrames: [],
|
||||
port: BrowserDetect.firefox ? browser.runtime.connect({name: 'popup-port'}) : chrome.runtime.connect({name: 'popup-port'}),
|
||||
comms: new Comms(),
|
||||
frameStore: {},
|
||||
frameStoreCount: 0,
|
||||
performance: {},
|
||||
site: null,
|
||||
currentZoom: 1,
|
||||
execAction: new ExecAction(),
|
||||
settings: new Settings(undefined, () => this.updateConfig()),
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.settings.init();
|
||||
this.port.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
|
||||
this.execAction.setSettings(this.settings);
|
||||
|
||||
// ensure we'll clean player markings on popup close
|
||||
window.addEventListener("unload", () => {
|
||||
this.port.postMessage({
|
||||
cmd: 'unmark-player',
|
||||
forwardToAll: true,
|
||||
});
|
||||
});
|
||||
|
||||
// get info about current site from background script
|
||||
while (true) {
|
||||
try {
|
||||
this.getSite();
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
await this.sleep(5000);
|
||||
}
|
||||
},
|
||||
components: {
|
||||
VideoPanel,
|
||||
DefaultSettingsPanel,
|
||||
PerformancePanel,
|
||||
Debug,
|
||||
AboutPanel,
|
||||
Donate,
|
||||
},
|
||||
methods: {
|
||||
async sleep(t) {
|
||||
return new Promise( (resolve,reject) => {
|
||||
setTimeout(() => resolve(), t);
|
||||
});
|
||||
},
|
||||
toObject(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
},
|
||||
getSite() {
|
||||
try {
|
||||
if (Debug.debug) {
|
||||
console.log("[popup.js] requesting current site");
|
||||
}
|
||||
this.port.postMessage({cmd: 'get-current-site'});
|
||||
} catch (e) {
|
||||
if (Debug.debug) {
|
||||
console.log("[popup::getSite] sending get-current-site failed for some reason. Reason:", e)
|
||||
}
|
||||
}
|
||||
},
|
||||
getRandomColor() {
|
||||
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
|
||||
},
|
||||
selectTab(tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
selectFrame(frame) {
|
||||
this.selectedFrame = frame;
|
||||
},
|
||||
async updateConfig() {
|
||||
|
||||
},
|
||||
processReceivedMessage(message, port) {
|
||||
if (Debug.debug) {
|
||||
console.log("[popup.js] received message set-c", message);
|
||||
console.log("[popup.js] message cloned set-c", JSON.parse(JSON.stringify(message)));
|
||||
}
|
||||
|
||||
if(message.cmd === 'set-current-site'){
|
||||
if (this.site) {
|
||||
if (!this.site.host) {
|
||||
// dunno why this fix is needed, but sometimes it is
|
||||
this.site.host = site.tabHostname;
|
||||
}
|
||||
}
|
||||
if (!this.site || this.site.host !== message.site.host) {
|
||||
this.port.postMessage({cmd: 'get-current-zoom'});
|
||||
}
|
||||
this.site = message.site;
|
||||
// loadConfig(site.host); TODO
|
||||
this.loadFrames(this.site);
|
||||
} else if (message.cmd === 'set-current-zoom') {
|
||||
this.setCurrentZoom(message.zoom);
|
||||
} else if (message.cmd === 'performance-update') {
|
||||
for (let key in message.message) {
|
||||
this.performance[key] = message.message[key];
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
loadFrames(videoTab) {
|
||||
if (videoTab.selected) {
|
||||
this.selectedSubitem = videoTab.selected;
|
||||
// selectedSubitemLoaded = true;
|
||||
}
|
||||
|
||||
this.activeFrames = [];
|
||||
|
||||
if (videoTab.frames.length < 2 || Object.keys(videoTab.frames).length < 2) {
|
||||
this.selectedFrame = '__all';
|
||||
return;
|
||||
}
|
||||
for (const frame in videoTab.frames) {
|
||||
|
||||
if (frame && !this.frameStore[frame]) {
|
||||
const fs = {
|
||||
name: this.frameStoreCount++,
|
||||
color: this.getRandomColor()
|
||||
}
|
||||
|
||||
this.frameStore[frame] = fs;
|
||||
|
||||
this.port.postMessage({
|
||||
cmd: 'mark-player',
|
||||
forwardToContentScript: true,
|
||||
targetTab: videoTab.id,
|
||||
targetFrame: frame,
|
||||
name: fs.name,
|
||||
color: fs.color
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.activeFrames = [{id: '__all', label: 'All'},{id: '__playing', label: 'Currently playing'}];
|
||||
|
||||
for (const frame in videoTab.frames) {
|
||||
this.activeFrames.push({
|
||||
id: `${this.site.id}-${frame}`,
|
||||
label: videoTab.frames[frame].host,
|
||||
...this.frameStore[frame],
|
||||
})
|
||||
}
|
||||
},
|
||||
getRandomColor() {
|
||||
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
|
||||
},
|
||||
setCurrentZoom(nz) {
|
||||
this.currentZoom = nz;
|
||||
},
|
||||
updateZoom(nz){
|
||||
this.currentZoom = nz;
|
||||
},
|
||||
selectFrame(id){
|
||||
this.selectedFrame = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style src="../res/css/font/overpass.css"></style>
|
||||
<style src="../res/css/font/overpass-mono.css"></style>
|
||||
<style src="../res/css/flex.css"></style>
|
||||
<style src="../res/css/common.scss"></style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
html, body {
|
||||
width: 800px !important;
|
||||
max-width: 800px !important;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#tablist {
|
||||
min-width: 275px;
|
||||
}
|
||||
|
||||
.header {
|
||||
overflow: hidden;
|
||||
background-color: #7f1416;
|
||||
color: #fff;
|
||||
margin: 0px;
|
||||
margin-top: 0px;
|
||||
padding-top: 8px;
|
||||
padding-left: 15px;
|
||||
padding-bottom: 1px;
|
||||
font-size: 2.7em;
|
||||
}
|
||||
|
||||
.menu-item-inline-desc{
|
||||
font-size: 0.60em;
|
||||
font-weight: 300;
|
||||
font-variant: normal;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding-left: 15px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
font-variant: small-caps;
|
||||
border-left: transparent 5px solid;
|
||||
}
|
||||
|
||||
.suboption {
|
||||
display: block;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 20px;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
|
||||
#no-videos-display {
|
||||
height: 100%;
|
||||
padding-top: 50px;
|
||||
/* text-align: center; */
|
||||
}
|
||||
|
||||
.tabitem-container {
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.selected-tab {
|
||||
background-color: initial;
|
||||
border-left: #f18810 5px solid;
|
||||
}
|
||||
|
||||
.tabitem {
|
||||
font-variant: normal;
|
||||
// font-size: 0.69em;
|
||||
margin-left: 1em;
|
||||
border-left: transparent 3px solid;
|
||||
padding-left: 12px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.tabitem-selected {
|
||||
color: #fff !important;
|
||||
background-color: initial;
|
||||
border-left: #f0c089 3px solid !important;
|
||||
}
|
||||
.tabitem-selected::before {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
.tabitem-iframe::after {
|
||||
content: "</>";
|
||||
padding-left: 0.33em;
|
||||
}
|
||||
|
||||
.popup {
|
||||
max-width: 780px;
|
||||
// width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
58
src/popup/js/ExecAction.js
Normal file
58
src/popup/js/ExecAction.js
Normal file
@ -0,0 +1,58 @@
|
||||
import Comms from '../../ext/lib/comms/Comms';
|
||||
|
||||
class ExecAction {
|
||||
constructor(settings, site) {
|
||||
this.settings = settings;
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
setSettings(settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
setSite(site) {
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
exec(action, scope, frame) {
|
||||
for (var cmd of action.cmd) {
|
||||
if (scope === 'page') {
|
||||
const message = {
|
||||
forwardToContentScript: true,
|
||||
targetFrame: frame,
|
||||
frame: frame,
|
||||
cmd: cmd.action,
|
||||
arg: cmd.arg,
|
||||
customArg: cmd.customArg
|
||||
}
|
||||
Comms.sendMessage(message);
|
||||
} else {
|
||||
|
||||
let site = this.site;
|
||||
if (scope === 'global') {
|
||||
site = '@global';
|
||||
} else if (!this.site) {
|
||||
site = window.location.host;
|
||||
}
|
||||
|
||||
if (scope === 'site' && !this.settings.active.sites[site]) {
|
||||
this.settings.active.sites[site] = this.settings.getDefaultOption();
|
||||
}
|
||||
|
||||
if (cmd.action === "set-stretch") {
|
||||
this.settings.active.sites[site].stretch = cmd.arg;
|
||||
} else if (cmd.action === "set-alignment") {
|
||||
this.settings.active.sites[site].videoAlignment = cmd.arg;
|
||||
} else if (cmd.action === "set-extension-mode") {
|
||||
this.settings.active.sites[site].mode = cmd.arg;
|
||||
} else if (cmd.action === "set-autoar-mode") {
|
||||
this.settings.active.sites[site].autoar = cmd.arg;
|
||||
} else if (cmd.action === 'set-keyboard') {
|
||||
this.settings.active.sites[site].keyboardShortcutsEnabled = cmd.arg;
|
||||
}
|
||||
this.settings.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ExecAction;
|
25
src/popup/panels/AboutPanel.vue
Normal file
25
src/popup/panels/AboutPanel.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row">
|
||||
<span class="label">Ultrawidify version:</span><br/> {{addonVersion}}
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">Having an issue?</span><br/> Report <strike>undocumented features</strike> bugs using one of the following options:
|
||||
<ul>
|
||||
<li> <a target="_blank" href="https://github.com/xternal7/ultrawidify/issues"><b>Github (strongly preferred)</b></a><br/></li>
|
||||
<li>PM me on <a target="_blank" href="https://www.reddit.com/message/compose?to=xternal7&subject=[Ultrawidify]%20ENTER%20SUMMARY%20OF%20YOUR%20PROBLEM%20HERE&message=Describe+your+issue+in+more+detail.+Don%27t+forget+to+include%3A%0D%0A%2A+Extension+version%0D%0A%2A+Browser%0D%0A%2A+Operating+system%0D%0A%2A+Other+extensions+that+could+possibly+interfere%0D%0A%0D%0AIf+you%27re+reporting+an+issue+with+automatic+aspect+ratio+detection%2C+please+also+include+the+following+%28if+possible%29%3A%0D%0A%2A+model+and+make+of+your+CPU%0D%0A%2A+amount+of+RAM">reddit</a><br/></li>
|
||||
<li>Email: <a target="_blank" href="mailto:tamius.han@gmail.com?subject=%5BUltrawidify%5D+ENTER+SUMMARY+OF+YOUR+ISSUE+HERE&body=Describe+your+issue+in+more+detail.+Don%27t+forget+to+include%3A%0D%0A%2A+Extension+version%0D%0A%2A+Browser%0D%0A%2A+Operating+system%0D%0A%2A+Other+extensions+that+could+possibly+interfere%0D%0A%0D%0AIf+you%27re+reporting+an+issue+with+automatic+aspect+ratio+detection%2C+please+also+include+the+following+%28if+possible%29%3A%0D%0A%2A+model+and+make+of+your+CPU%0D%0A%2A+amount+of+RAM">tamius.han@gmail.com</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
addonVersion: browser.runtime.getManifest().version || chrome.runtime.getManifest().version,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
185
src/popup/panels/DefaultSettingsPanel.vue
Normal file
185
src/popup/panels/DefaultSettingsPanel.vue
Normal file
@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div class="w100 flex flex-column" style="padding-bottom: 20px">
|
||||
<!-- <ShortcutButton class="button"
|
||||
@click.native="wipeSettings()"
|
||||
label="Wipe settings"
|
||||
/> -->
|
||||
|
||||
<!-- ENABLE EXTENSION -->
|
||||
<div v-if="settings && extensionActions.length"
|
||||
class="w100"
|
||||
>
|
||||
<div class="label">Enable extension {{scope === 'site' ? 'for this site' : ''}}:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of extensionActions"
|
||||
class="flex flex-grow button"
|
||||
:key="index"
|
||||
:class="{'setting-selected': getCurrent('mode') === action.cmd[0].arg }"
|
||||
:label="(action.scopes[scope] && action.scopes[scope].label) ? action.scopes[scope].label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ENABLE AUTODETECTION -->
|
||||
<div v-if="aardActions.length"
|
||||
class="w100"
|
||||
>
|
||||
<div class="label">Enable autodetection {{scope === 'site' ? 'for this site' : ''}}:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of aardActions"
|
||||
class="flex flex-grow button"
|
||||
:class="{'setting-selected': getCurrent('autoar') === action.cmd[0].arg}"
|
||||
:key="index"
|
||||
:label="(action.scopes[scope] && action.scopes[scope].label) ? action.scopes[scope].label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
<div class="info"><small>Note: some sites implement restrictions that make autodetection a fair bit less reliable in Firefox and outright impossible in anything else.</small></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DEFAULT SETTINGS -->
|
||||
<div v-if="stretchActions.length">
|
||||
<div class="label">Default stretching mode:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of stretchActions"
|
||||
class="flex b3 flex-grow button"
|
||||
:class="{'setting-selected': getCurrent('stretch') === action.cmd[0].arg}"
|
||||
:key="index"
|
||||
:label="(action.scopes[scope] && action.scopes[scope].label) ? action.scopes[scope].label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="keyboardActions.length">
|
||||
<div class="label experimental">Enable/disable keyboard shortcuts</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of keyboardActions"
|
||||
class="flex b3 flex-grow button"
|
||||
:class="{'setting-selected': getCurrent('keyboardShortcutsEnabled') === action.cmd[0].arg}"
|
||||
:key="index"
|
||||
:label="(action.scopes[scope] && action.scopes[scope].label) ? action.scopes[scope].label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="alignmentActions.length">
|
||||
<div class="label">Video alignment:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of alignmentActions"
|
||||
class="flex b3 flex-grow button"
|
||||
:class="{'setting-selected': getCurrent('videoAlignment') === action.cmd[0].arg}"
|
||||
:key="index"
|
||||
:label="(action.scopes[scope] && action.scopes[scope].label) ? action.scopes[scope].label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="otherActions.length">
|
||||
<div class="label">Other actions:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of otherActions"
|
||||
class="flex b3 flex-grow button"
|
||||
:key="index"
|
||||
:label="(action.scopes[scope] && action.scopes[scope].label) ? action.scopes[scope].label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ExecAction from '../js/ExecAction';
|
||||
import KeyboardShortcutParser from '../../common/js/KeyboardShortcutParser';
|
||||
import ShortcutButton from '../../common/components/ShortcutButton';
|
||||
import ComputeActionsMixin from '../../common/mixins/ComputeActionsMixin';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
ComputeActionsMixin
|
||||
],
|
||||
props: {
|
||||
settings: Object,
|
||||
scope: String,
|
||||
site: String,
|
||||
},
|
||||
created() {
|
||||
this.exec = new ExecAction(this.settings, this.site);
|
||||
},
|
||||
components: {
|
||||
ShortcutButton,
|
||||
},
|
||||
|
||||
watch: {
|
||||
settings: {
|
||||
deep: true,
|
||||
handler: function(val) {
|
||||
this.$forceUpdate();
|
||||
this.exec.setSettings(val);
|
||||
}
|
||||
},
|
||||
site: function(val){
|
||||
this.exec.setSite(val);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
execAction(action) {
|
||||
this.exec.exec(action, this.scope);
|
||||
},
|
||||
getCurrent(option) {
|
||||
if (!this.settings) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let site;
|
||||
if (this.scope === 'global') {
|
||||
site = '@global'
|
||||
this.site = site;
|
||||
} else {
|
||||
if (!this.site) {
|
||||
return '';
|
||||
}
|
||||
site = this.site;
|
||||
}
|
||||
// console.log("SETTINGS FOR SITE", site, "option", option, JSON.parse(JSON.stringify(this.settings.active.sites)))
|
||||
if (this.settings.active.sites[site]) {
|
||||
return this.settings.active.sites[site][option];
|
||||
} else {
|
||||
return this.settings.getDefaultOption(option);
|
||||
}
|
||||
},
|
||||
parseShortcut(action) {
|
||||
if (! action.scopes[this.scope].shortcut) {
|
||||
return '';
|
||||
}
|
||||
return KeyboardShortcutParser.parseShortcut(action.scopes[this.scope].shortcut[0]);
|
||||
},
|
||||
wipeSettings() {
|
||||
settings.setDefaultSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
5
src/popup/panels/DonatePanel.vue
Normal file
5
src/popup/panels/DonatePanel.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
TODO: beggathon
|
||||
</div>
|
||||
</template>
|
26
src/popup/panels/PerformancePanel.vue
Normal file
26
src/popup/panels/PerformancePanel.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
PERFORMANCE PANEL :: This is here for debugging purposes.
|
||||
|
||||
<pre>{{performance}}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
performance: Object,
|
||||
},
|
||||
watch: {
|
||||
performance: {
|
||||
deep: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
177
src/popup/panels/VideoPanel.vue
Normal file
177
src/popup/panels/VideoPanel.vue
Normal file
@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="w100" style="padding-bottom: 20px">
|
||||
<div v-if="aspectRatioActions.length"
|
||||
class="w100"
|
||||
>
|
||||
<div class="label">Cropping mode:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of aspectRatioActions"
|
||||
class="flex b3 flex-grow button"
|
||||
:key="index"
|
||||
:label="(action.scopes.page && action.scopes.page.label) ? action.scopes.page.label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="true"
|
||||
class="w100">
|
||||
<div class="label experimental">Zooming and panning</div>
|
||||
<div class="row w100"
|
||||
>
|
||||
<!--
|
||||
min, max and value need to be implemented in js as this slider
|
||||
should use logarithmic scale
|
||||
-->
|
||||
<input id="_input_zoom_slider"
|
||||
class="w100"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="4"
|
||||
:value="logarithmicZoom"
|
||||
@input="changeZoom($event.target.value)"
|
||||
/>
|
||||
<div style="overflow: auto" class="flex flex-row">
|
||||
<div class="flex flex-grow medium-small x-pad-1em">
|
||||
Zoom: {{(zoom * 100).toFixed()}}%
|
||||
</div>
|
||||
<div class="flex flex-nogrow flex-noshrink medium-small">
|
||||
<a class="_zoom_reset x-pad-1em" @click="resetZoom()">reset</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-t-0-33em w100 display-block">
|
||||
<input id="_input_zoom_site_allow_pan"
|
||||
type="checkbox"
|
||||
/>
|
||||
Pan with mouse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="stretchActions.length">
|
||||
<div class="label">Stretching mode:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of stretchActions"
|
||||
class="flex b3 flex-grow button"
|
||||
:key="index"
|
||||
:label="(action.scopes.page && action.scopes.page.label) ? action.scopes.page.label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div v-if="keyboardActions.length">
|
||||
<div class="label">Keyboard shortcuts:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of keyboardActions"
|
||||
class="flex b3 button"
|
||||
:key="index"
|
||||
:label="(action.scopes.page && action.scopes.page.label) ? action.scopes.page.label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div v-if="alignmentActions.length">
|
||||
<div class="label">Video alignment:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of alignmentActions"
|
||||
class="flex b3 button"
|
||||
:key="index"
|
||||
:label="(action.scopes.page && action.scopes.page.label) ? action.scopes.page.label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="otherActions.length">
|
||||
<div class="label">Other actions:</div>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
<ShortcutButton v-for="(action, index) of otherActions"
|
||||
class="flex b3 button"
|
||||
:key="index"
|
||||
:label="(action.scopes.page && action.scopes.page.label) ? action.scopes.page.label : action.label"
|
||||
:shortcut="parseShortcut(action)"
|
||||
@click.native="execAction(action)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ExecAction from '../js/ExecAction';
|
||||
import KeyboardShortcutParser from '../../common/js/KeyboardShortcutParser';
|
||||
import ShortcutButton from '../../common/components/ShortcutButton';
|
||||
import ComputeActionsMixin from '../../common/mixins/ComputeActionsMixin';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
scope: 'page',
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
ComputeActionsMixin
|
||||
],
|
||||
props: [
|
||||
'settings',
|
||||
'frame',
|
||||
'zoom'
|
||||
],
|
||||
created() {
|
||||
this.exec = new ExecAction(this.settings);
|
||||
},
|
||||
components: {
|
||||
ShortcutButton,
|
||||
},
|
||||
computed: {
|
||||
logarithmicZoom: function(){
|
||||
return Math.log2(this.zoom);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
execAction(action) {
|
||||
this.exec.exec(action, 'page', this.frame);
|
||||
},
|
||||
parseShortcut(action) {
|
||||
if (! action.scopes.page.shortcut) {
|
||||
return '';
|
||||
}
|
||||
return KeyboardShortcutParser.parseShortcut(action.scopes.page.shortcut[0]);
|
||||
},
|
||||
resetZoom() {
|
||||
this.zoom = 1;
|
||||
},
|
||||
changeZoom(nz) {
|
||||
nz = Math.pow(2, nz);
|
||||
this.$emit('zoom-change', nz);
|
||||
this.exec.exec(
|
||||
{cmd: [{action: 'set-zoom', arg: nz}]},
|
||||
'page',
|
||||
this.frame
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.b3 {
|
||||
width: 9rem;
|
||||
padding-left: 0.33rem;
|
||||
padding-right: 0.33rem;
|
||||
}
|
||||
</style>
|
18
src/popup/popup.html
Normal file
18
src/popup/popup.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
|
||||
<% if (NODE_ENV === 'development') { %>
|
||||
<!-- Load some resources only in development environment -->
|
||||
<% } %>
|
||||
</head>
|
||||
<body style="width: 800px; height: 600px; overflow-x: hidden">
|
||||
<div id="app">
|
||||
|
||||
</div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
11
src/popup/popup.js
Normal file
11
src/popup/popup.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
|
||||
// global.browser = require('webextension-polyfill')
|
||||
// Vue.prototype.$browser = global.browser
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
})
|
16
src/res/css/colors.scss
Normal file
16
src/res/css/colors.scss
Normal file
@ -0,0 +1,16 @@
|
||||
$text-normal: #ddd;
|
||||
$text-dim: #999;
|
||||
$text-dark: #666;
|
||||
$primary-color: #fb772a;
|
||||
$secondary-color: #e70c0c;
|
||||
|
||||
$input-background: #141414;
|
||||
$input-border: #4e3527;
|
||||
$page-background: #101010;
|
||||
|
||||
$background-primary: #101010;
|
||||
$selected-color: #f5cbaf;
|
||||
$background-selected: #412d20;
|
||||
|
||||
|
||||
$info-color: #bda9f3;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user