Merge branch 'master' into stable
This commit is contained in:
commit
79fa726b19
6
.babelrc
6
.babelrc
@ -6,9 +6,9 @@
|
||||
["@babel/preset-env", {
|
||||
"useBuiltIns": false,
|
||||
"targets": {
|
||||
"esmodules": true,
|
||||
},
|
||||
}],
|
||||
"esmodules": true
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
// {
|
||||
|
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"hollowtree.vue-pack",
|
||||
"msjsdiag.debugger-for-chrome",
|
||||
"firefox-devtools.vscode-firefox-debug",
|
||||
"msjsdiag.debugger-for-edge"
|
||||
]
|
||||
}
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -17,6 +17,8 @@
|
||||
"decycle",
|
||||
"disneyplus",
|
||||
"equalish",
|
||||
"fith",
|
||||
"fitw",
|
||||
"fuckup",
|
||||
"gfycat",
|
||||
"gmail",
|
||||
@ -28,6 +30,7 @@
|
||||
"letterboxed",
|
||||
"manjaro",
|
||||
"minification",
|
||||
"mitigations",
|
||||
"nogrow",
|
||||
"noshrink",
|
||||
"outro",
|
||||
|
50
CHANGELOG.md
50
CHANGELOG.md
@ -8,14 +8,59 @@
|
||||
* Settings page looks ugly af right now. Maybe fix it some time later
|
||||
* other bug fixes
|
||||
|
||||
## v5.0 (planned major)
|
||||
|
||||
## v7.0 (planned major)
|
||||
* WebGL autodetection
|
||||
|
||||
## v6.0 (next major)
|
||||
|
||||
* in-player GUI
|
||||
* Fix UI logger
|
||||
|
||||
## v5.x (current major)
|
||||
|
||||
### v5.0.4
|
||||
* Attempt to fix disney+ again, courtesy of [@jwannebo](https://github.com/tamius-han/ultrawidify/issues/84#issuecomment-846334005) on github.
|
||||
|
||||
|
||||
### v5.0.3
|
||||
|
||||
* Fixed the issue where the videos were sometimes offset up and left. Again.
|
||||
* Fix the issue where correcting source stretch was squished incorrectly ([#153](https://github.com/tamius-han/ultrawidify/issues/153))
|
||||
|
||||
### v5.0.2
|
||||
|
||||
* When in full screen, the extension will assume player element dimensions are the same as the screen resolution. This should help with sites where ultrawidify doesn't correctly identify the player, as cropping generally doesn't work if player element is not identified. Old behaviour can be restored in advanced extension settings by toggling the "use player aspect ratio in fullscreen" checkbox under 'player detection settings'.
|
||||
* Extension should now respect 'disable extension' option for real.
|
||||
* Fixed the issue where player wouldn't get detected if video was wider than the player.
|
||||
|
||||
|
||||
### v5.0.1
|
||||
* Added an option for users to turn off (and/or configure) Chrome/Edge's zoom limiter.
|
||||
|
||||
### v5.0.0
|
||||
|
||||
There's been some big-ish changes under the hood:
|
||||
|
||||
* Migrate main scripts to typescript (vue is currently not included).
|
||||
* webextension-polyfill is now used everywhere (if only because typescript throws a hissy fit with `browser` and `chrome` otherwise) ([#114](https://github.com/tamius-han/ultrawidify/issues/114))
|
||||
* Fix some bugs that I didn't even know I had, but typescript kinda shone some light on them
|
||||
* Manual zoom (Z/U unless sites override the two) should now work again (without automatic AR constantly overriding it). Same goes for panning. ([#135](https://github.com/tamius-han/ultrawidify/issues/135) & [#138](https://github.com/tamius-han/ultrawidify/issues/138))
|
||||
* Fix issue when video would be scaled incorrectly if video element uses `height:auto`.
|
||||
* **[5.0.0.1]** Fixed the issue where settings were reset on page load.
|
||||
* **[5.0.0.1]** Fixed the issue where settings page wouldn't load.
|
||||
## v4.x (current major)
|
||||
|
||||
### v4.5.3
|
||||
|
||||
* Provides workaround for the fullscreen stretching bug Chrome 88 (or a recent Windows 10 update) introduced for nVidia users using hardware acceleration on Windows 10. In order to mitigate this bug, Ultrawidify needs to keep a 5-10 px wide black border while watching videos in full screen. This bug is also present in Edge.
|
||||
* **[4.5.3.1]** Fixed letterbox misalignment binding in settings (#134)
|
||||
* **[4.5.3.2]** Fixed false 'autodetection not supported' notifications.
|
||||
|
||||
### v4.5.2
|
||||
|
||||
* Fixed the issue where videos would sometimes get misaligned while using hybrid stretch, except for real this time. ([#125](https://github.com/tamius-han/ultrawidify/issues/125))
|
||||
* Improved DRM detection (the 'autodetection cannot work on this site' popup should now no longer show up on the sites where autodetection _can_ work)
|
||||
|
||||
### v4.5.1
|
||||
|
||||
* Fixed the misalignment issue on netflix ... hopefully.
|
||||
@ -28,6 +73,7 @@
|
||||
* **[4.5.1.3]** Added fix for disney plus
|
||||
* **[4.5.1.3]** Microsoft Edge has fixed the bugs that prevented the extension from working properly. Popup should no longer be shown.
|
||||
|
||||
|
||||
### v4.5.0 (Current)
|
||||
|
||||
* Under the hood: migrated from vue2 to vue3, because optional chaining in templates is too OP.
|
||||
|
14
README.md
14
README.md
@ -1,10 +1,8 @@
|
||||
# Ultrawidify — aspect ratio fixer for youtube and netflix
|
||||
# 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).
|
||||
|
||||
**Microsoft Edge is not supported at this time, as Edge features some bugs that make it impossible for extension to work.** [Read more](https://github.com/tamius-han/ultrawidify/issues/117#issuecomment-747109695).
|
||||
[Firefox](https://addons.mozilla.org/en/firefox/addon/ultrawidify/), [Chrome](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi), [Edge](https://microsoftedge.microsoft.com/addons/detail/ultrawidify/lmpgpgechmkkkehkihpiddbcbgibokbi).
|
||||
|
||||
There's also [nightly "builds"](https://stuff.lionsarch.tamius.net/ultrawidify/nightly/).
|
||||
|
||||
@ -51,13 +49,13 @@ If extension doesn't work for a site I'm not testing on out of the box, follow [
|
||||
|
||||
### Installing this extension
|
||||
|
||||
You can download this extension from Firefox' and Chrome's extension stores:
|
||||
You can download this extension from the relevant extension stores:
|
||||
|
||||
* [Firefox](https://addons.mozilla.org/en/firefox/addon/ultrawidify/)
|
||||
* [Chrome, Opera](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
||||
* [Chromium Edge](https://microsoftedge.microsoft.com/addons/detail/lmpgpgechmkkkehkihpiddbcbgibokbi)
|
||||
* [Chrome](https://chrome.google.com/webstore/detail/ultrawidify/dndehlekllfkaijdlokmmicgnlanfjbi)
|
||||
* [Edge](https://microsoftedge.microsoft.com/addons/detail/ultrawidify/lmpgpgechmkkkehkihpiddbcbgibokbi)
|
||||
|
||||
Users of beta and developer branches of Opera can find Ultrawidify on Opera store as well, but given Opera's review process is unacceptably slow (awaiting moderator review since 2020-03-15) not only am I not going to maintain Opera store presence, Opera users are advised to use Chrome version of the extension in order to avoid waiting years for new features to be approved by Opera moderators.
|
||||
Other browsers are not officially supported. If you're using a different Chromium-based browser, you can try installing the addon from the Chrome Web Store — but if things don't work, you're on your own.
|
||||
|
||||
### Nightly builds
|
||||
|
||||
|
26526
package-lock.json
generated
26526
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "ultrawidify",
|
||||
"version": "4.5.1",
|
||||
"version": "5.0.4",
|
||||
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
|
||||
"author": "Tamius Han <tamius.han@gmail.com>",
|
||||
"scripts": {
|
||||
"build": "npm run pre-build; cross-env NODE_ENV=production BROWSER=firefox CHANNEL=stable webpack --hide-modules",
|
||||
"build-all": "mkdir -p ./build/old; npm run pre-build; rm ./dist-zip/uw-amo-source.zip; mv -f ./dist-zip/*.zip ./build/old; npm run build; node scripts/build-zip.js ff; npm run build-chrome; node scripts/build-zip.js chrome; npm run build-edge; node scripts/build-zip.js edge; ./scripts/prepare-amo-source.sh",
|
||||
"build-all": "bash ./scripts/build-all.sh",
|
||||
"build-chrome": "cross-env NODE_ENV=production BROWSER=chrome CHANNEL=stable webpack --hide-modules",
|
||||
"build-chrome:dev": "cross-env NODE_ENV=development BROWSER=chrome webpack --hide-modules",
|
||||
"build-edge": "cross-env NODE_ENV=production BROWSER=edge CHANNEL=stable webpack --hide-modules",
|
||||
@ -15,15 +15,19 @@
|
||||
"build-testing-chrome": "cross-env NODE_ENV=development BROWSER=chrome CHANNEL=testing webpack --hide-modules",
|
||||
"build-zip": "node scripts/build-zip.js",
|
||||
"build:dev": "webpack --hide-modules",
|
||||
"dev": "cross-env NODE_ENV=development CHANNEL=dev concurrently \"cross-env BROWSER=firefox npm run build:dev -- --watch\" \"cross-env BROWSER=chrome npm run build:dev -- --watch\"",
|
||||
"dev": "cross-env NODE_ENV=development CHANNEL=dev concurrently \"cross-env BROWSER=firefox npm run build:dev -- --watch\" \"cross-env BROWSER=chrome npm run build:dev -- --watch\" \"cross-env BROWSER=edge npm run build:dev -- --watch\"",
|
||||
"pre-build": "rm -rf ./dist-ff; rm -rf ./dist-chrome; rm -rf ./node_modules; npm ci",
|
||||
"start": "npm run dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@types/chrome": "0.0.129",
|
||||
"@types/core-js": "^2.5.3",
|
||||
"@types/es6-promise": "^3.3.0",
|
||||
"@types/firefox": "0.0.30",
|
||||
"@types/resize-observer-browser": "^0.1.5",
|
||||
"@vue/cli": "^4.5.9",
|
||||
"@vue/cli-plugin-typescript": "^4.5.11",
|
||||
"bootstrap": "^4.5.3",
|
||||
"bootstrap-icons": "^1.1.0",
|
||||
"bootstrap-icons-vue": "^0.3.0",
|
||||
@ -31,17 +35,23 @@
|
||||
"concurrently": "^5.2.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"json-cyclic": "0.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
"typescript": "^4.2.3",
|
||||
"vue": "^3.0.0-beta.1",
|
||||
"vuex": "^4.0.0-alpha.1",
|
||||
"vuex-webextensions": "^1.3.0"
|
||||
"vuex-webextensions": "^1.3.0",
|
||||
"webextension-polyfill-ts": "^0.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.5",
|
||||
"@babel/core": "^7.12.13",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@babel/preset-env": "^7.12.13",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/node": "^14.14.25",
|
||||
"@vue/compiler-sfc": "^3.0.3",
|
||||
"archiver": "^3.0.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-preset-es2020": "^1.0.2",
|
||||
"copy-webpack-plugin": "^4.5.3",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
@ -50,9 +60,10 @@
|
||||
"mini-css-extract-plugin": "^0.4.4",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"ts-loader": "^8.0.16",
|
||||
"vue-cli-plugin-vue-next": "~0.1.4",
|
||||
"vue-loader": "^16.0.0",
|
||||
"web-ext-types": "^2.1.0",
|
||||
"web-ext-types": "^2.3.0",
|
||||
"webextension-polyfill": "^0.6.0",
|
||||
"webpack": "^4.44.0",
|
||||
"webpack-chrome-extension-reloader": "^0.8.3",
|
||||
|
42
scripts/build-all.sh
Normal file
42
scripts/build-all.sh
Normal file
@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NOTE: this script needs to be run with the npm run build-all
|
||||
# command from the root directory of the project. Running it in
|
||||
# any other way probably isn't going to work.
|
||||
|
||||
# pre-build steps:
|
||||
mkdir -p ./build/old
|
||||
npm run pre-build
|
||||
rm ./dist-zip/uw-amo-source.zip
|
||||
mv -f ./dist-zip/*.zip ./build/old
|
||||
|
||||
# lets force raise ram limit, but the improper way
|
||||
# export NODE_OPTIONS=--max_old_space_size=4096
|
||||
|
||||
# build the version for each browser and create a zip afterwards
|
||||
# step 1: define build functions
|
||||
#function buildFF {
|
||||
npm run build
|
||||
node scripts/build-zip.js ff
|
||||
#}
|
||||
#function buildChrome {
|
||||
npm run build-chrome
|
||||
node scripts/build-zip.js chrome
|
||||
#}
|
||||
#function buildEdge {
|
||||
npm run build-edge
|
||||
node scripts/build-zip.js edge
|
||||
#}
|
||||
|
||||
# step 2: execute them all at once
|
||||
# buildFF &
|
||||
# buildChrome &
|
||||
# buildEdge &
|
||||
|
||||
# wait < <(jobs -p)
|
||||
|
||||
# prepare AMO source
|
||||
# source code needs to be prepared AFTER
|
||||
# the code has been built, to ensure that
|
||||
# package-lock.json remains unchanged
|
||||
./scripts/prepare-amo-source.sh
|
@ -37,6 +37,9 @@ if [ ! -z "$GIT_COMMIT" ] ; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# let's raise RAM limit for npm command globally
|
||||
NODE_OPTIONS=--max_old_space_size=4096
|
||||
|
||||
npm ci
|
||||
|
||||
rm -rf ./dist-zip || true # no big deal if ./dist-zip doesn't exist
|
||||
@ -46,7 +49,7 @@ mkdir dist-zip # create it back
|
||||
# build firefox
|
||||
#
|
||||
npm run "${BUILD_SCRIPT}"
|
||||
node scripts/build-zip.js ff nightly
|
||||
node --max-old-space-size=2048 scripts/build-zip.js ff nightly
|
||||
# if [ ! -z "${AMO_API_KEY}" ] ; then
|
||||
# if [ ! -z "${AMO_API_SECRET}" ] ; then
|
||||
# web-ext sign --source-dir ./dist --api-key "${AMO_API_KEY}" --api-secret "${AMO_API_SECRET}"
|
||||
@ -57,12 +60,18 @@ node scripts/build-zip.js ff nightly
|
||||
# build chrome
|
||||
#
|
||||
npm run "${BUILD_SCRIPT}-chrome"
|
||||
node scripts/build-zip.js chrome nightly
|
||||
|
||||
node --max-old-space-size=2048 scripts/build-zip.js chrome nightly
|
||||
#
|
||||
#./scripts/build-crx.sh
|
||||
#
|
||||
|
||||
#
|
||||
# build edge
|
||||
#
|
||||
npm run "${BUILD_SCRIPT}-edge"
|
||||
node --max-old-space-size=2048 scripts/build-zip.js chrome nightly
|
||||
|
||||
|
||||
######################################
|
||||
# UPLOAD TO WEB SERVER
|
||||
######################################
|
||||
@ -76,7 +85,6 @@ echo "Uploading to server ..."
|
||||
scp -i ~/.ssh/id_rsa -r ./dist-zip/* "ultrawidify-uploader@${RELEASE_SERVER}:${RELEASE_DIRECTORY}${BUILD_CHANNEL_DIRECTORY}"
|
||||
|
||||
|
||||
|
||||
######################################
|
||||
# Build finished message
|
||||
######################################
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
</div>
|
||||
<div class="flex action-name">
|
||||
<span v-if="action.cmd && action.cmd.length > 1 || action.cmd[0].action === 'set-ar' && action.userAdded || (action.cmd[0].arg === AspectRatio.Fixed)" class="icon red" @click="removeAction()">🗙</span>
|
||||
<span v-if="action.cmd && action.cmd.length > 1 || action.cmd[0].action === 'set-ar' && action.userAdded || (action.cmd[0].arg === AspectRatioType.Fixed)" class="icon red" @click="removeAction()">🗙</span>
|
||||
<span v-else class="icon transparent">🗙</span>
|
||||
<span class="icon" @click="editAction()">🖉</span>
|
||||
{{action.name}}
|
||||
@ -108,16 +108,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Stretch from '../enums/stretch.enum';
|
||||
import AspectRatio from '../enums/aspect-ratio.enum';
|
||||
import StretchType from '../enums/StretchType.enum';
|
||||
import AspectRatioType from '../enums/AspectRatioType.enum';
|
||||
import KeyboardShortcutParser from '../js/KeyboardShortcutParser';
|
||||
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
AspectRatio: AspectRatio,
|
||||
StretchType: StretchType,
|
||||
AspectRatioType: AspectRatioType,
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -46,13 +46,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Stretch from '../enums/stretch.enum';
|
||||
import StretchType from '../enums/StretchType.enum';
|
||||
import KeyboardShortcutParser from '../js/KeyboardShortcutParser'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch
|
||||
StretchType: StretchType
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -6,7 +6,7 @@ let Notifications = Object.freeze({
|
||||
},
|
||||
'AARD_DRM': {
|
||||
icon: 'exclamation-triangle',
|
||||
text: '<b>Autodetection cannot run on this video.</b> This usually happens when sites use DRM. You will have to set aspect ratio manually.',
|
||||
text: '<b>Autodetection may not be able to run on this video.</b> On sites that utilize DRM you will have to set aspect ratio manually.',
|
||||
timeout: 5000,
|
||||
}
|
||||
});
|
||||
|
7
src/common/enums/AntiGradientMode.enum.ts
Normal file
7
src/common/enums/AntiGradientMode.enum.ts
Normal file
@ -0,0 +1,7 @@
|
||||
enum AntiGradientMode {
|
||||
Disabled = 0,
|
||||
Lax = 1,
|
||||
Strict = 2
|
||||
}
|
||||
|
||||
export default AntiGradientMode;
|
11
src/common/enums/AspectRatioType.enum.ts
Normal file
11
src/common/enums/AspectRatioType.enum.ts
Normal file
@ -0,0 +1,11 @@
|
||||
enum AspectRatioType {
|
||||
Initial = -1, // page default
|
||||
Reset = 0, // reset to initial
|
||||
Automatic = 1, // set by Aard
|
||||
FitWidth = 2, // legacy/dynamic = fit to width
|
||||
FitHeight = 3, // legacy/dynamic = fit to height
|
||||
Fixed = 4, // pre-determined aspect ratio
|
||||
Manual = 5, // ratio achieved by zooming in/zooming out
|
||||
}
|
||||
|
||||
export default AspectRatioType;
|
9
src/common/enums/CropModePersistence.enum.ts
Normal file
9
src/common/enums/CropModePersistence.enum.ts
Normal file
@ -0,0 +1,9 @@
|
||||
enum CropModePersistence {
|
||||
Default = -1,
|
||||
Disabled = 0,
|
||||
UntilPageReload = 1,
|
||||
CurrentSession = 2,
|
||||
Forever = 3,
|
||||
}
|
||||
|
||||
export default CropModePersistence;
|
10
src/common/enums/ExtensionMode.enum.ts
Normal file
10
src/common/enums/ExtensionMode.enum.ts
Normal file
@ -0,0 +1,10 @@
|
||||
enum ExtensionMode {
|
||||
AutoDisabled = -2,
|
||||
Disabled = -1,
|
||||
Default = 0,
|
||||
Whitelist = 1,
|
||||
Basic = 2,
|
||||
Enabled = 3,
|
||||
};
|
||||
|
||||
export default ExtensionMode;
|
11
src/common/enums/StretchType.enum.ts
Normal file
11
src/common/enums/StretchType.enum.ts
Normal file
@ -0,0 +1,11 @@
|
||||
enum StretchType {
|
||||
NoStretch = 0,
|
||||
Basic = 1,
|
||||
Hybrid = 2,
|
||||
Conditional = 3,
|
||||
Fixed = 4,
|
||||
FixedSource = 5,
|
||||
Default = -1
|
||||
};
|
||||
|
||||
export default StretchType;
|
8
src/common/enums/VideoAlignmentType.enum.ts
Normal file
8
src/common/enums/VideoAlignmentType.enum.ts
Normal file
@ -0,0 +1,8 @@
|
||||
enum VideoAlignmentType {
|
||||
Left = 0,
|
||||
Center = 1,
|
||||
Right = 2,
|
||||
Default = -1
|
||||
};
|
||||
|
||||
export default VideoAlignmentType;
|
@ -1,7 +0,0 @@
|
||||
var AntiGradientMode = Object.freeze({
|
||||
Disabled: 0,
|
||||
Lax: 1,
|
||||
Strict: 2
|
||||
});
|
||||
|
||||
export default AntiGradientMode;
|
@ -1,11 +0,0 @@
|
||||
var AspectRatio = Object.freeze({
|
||||
Initial: -1, // page default
|
||||
Reset: 0, // reset to initial
|
||||
Automatic: 1, // set by Aard
|
||||
FitWidth: 2, // legacy/dynamic: fit to width
|
||||
FitHeight: 3, // legacy/dynamic: fit to height
|
||||
Fixed: 4, // pre-determined aspect ratio
|
||||
Manual: 5, // ratio achieved by zooming in/zooming out
|
||||
});
|
||||
|
||||
export default AspectRatio;
|
@ -1,9 +0,0 @@
|
||||
var CropModePersistence = Object.freeze({
|
||||
Default: -1,
|
||||
Disabled: 0,
|
||||
UntilPageReload: 1,
|
||||
CurrentSession: 2,
|
||||
Forever: 3,
|
||||
});
|
||||
|
||||
export default CropModePersistence;
|
@ -1,19 +0,0 @@
|
||||
if (process.env.CHANNEL !== 'stable') {
|
||||
console.info('Loading ExtensionMode');
|
||||
}
|
||||
|
||||
|
||||
var ExtensionMode = Object.freeze({
|
||||
AutoDisabled: -2,
|
||||
Disabled: -1,
|
||||
Default: 0,
|
||||
Whitelist: 1,
|
||||
Basic: 2,
|
||||
Enabled: 3,
|
||||
});
|
||||
|
||||
if (process.env.CHANNEL !== 'stable') {
|
||||
console.info('Loaded ExtensionMode');
|
||||
}
|
||||
|
||||
export default ExtensionMode;
|
@ -1,11 +0,0 @@
|
||||
var Stretch = Object.freeze({
|
||||
NoStretch: 0,
|
||||
Basic: 1,
|
||||
Hybrid: 2,
|
||||
Conditional: 3,
|
||||
Fixed: 4,
|
||||
FixedSource: 5,
|
||||
Default: -1
|
||||
});
|
||||
|
||||
export default Stretch;
|
@ -1,8 +0,0 @@
|
||||
var VideoAlignment = Object.freeze({
|
||||
Left: 0,
|
||||
Center: 1,
|
||||
Right: 2,
|
||||
Default: -1
|
||||
});
|
||||
|
||||
export default VideoAlignment;
|
293
src/common/interfaces/SettingsInterface.ts
Normal file
293
src/common/interfaces/SettingsInterface.ts
Normal file
@ -0,0 +1,293 @@
|
||||
import { Action } from '../../../node_modules/vuex/types/index'
|
||||
import AntiGradientMode from '../enums/AntiGradientMode.enum'
|
||||
import AspectRatioType from '../enums/AspectRatioType.enum'
|
||||
import CropModePersistence from '../enums/CropModePersistence.enum'
|
||||
import ExtensionMode from '../enums/ExtensionMode.enum'
|
||||
import StretchType from '../enums/StretchType.enum'
|
||||
import VideoAlignmentType from '../enums/VideoAlignmentType.enum'
|
||||
|
||||
interface ActionScopeInterface {
|
||||
show: boolean,
|
||||
label?: string, // example override, takes precedence over default label
|
||||
shortcut?: {
|
||||
key?: string,
|
||||
code?: string,
|
||||
ctrlKey?: boolean,
|
||||
metaKey?: boolean,
|
||||
altKey?: boolean,
|
||||
shiftKey?: boolean,
|
||||
onKeyUp?: boolean,
|
||||
onKeyDown?: boolean,
|
||||
onMouseMove?: boolean,
|
||||
}[],
|
||||
}
|
||||
|
||||
interface SettingsInterface {
|
||||
arDetect: {
|
||||
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
|
||||
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
|
||||
// Any more and we don't adjust ar.
|
||||
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
|
||||
timers: { // autodetection frequency
|
||||
playing: number, // while playing
|
||||
paused: number, // while paused
|
||||
error: number, // after error
|
||||
minimumTimeout: number,
|
||||
tickrate: number, // 1 tick every this many milliseconds
|
||||
},
|
||||
autoDisable: { // settings for automatically disabling the extension
|
||||
maxExecutionTime: number, // if execution time of main autodetect loop exceeds this many milliseconds,
|
||||
// we disable it.
|
||||
consecutiveTimeoutCount: number, // we only do it if it happens this many consecutive times
|
||||
|
||||
// FOR FUTURE USE
|
||||
consecutiveArResets: number // 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: number,
|
||||
height: number,
|
||||
},
|
||||
sampleCanvas: { // size of image sample for detecting aspect ratio. Bigger size means more accurate results,
|
||||
// at the expense of performance
|
||||
width: number,
|
||||
height: number,
|
||||
},
|
||||
},
|
||||
|
||||
// samplingInterval: 10, // we sample at columns at (width/this) * [ 1 .. this - 1]
|
||||
blackframe: {
|
||||
sufficientColorVariance: number, // calculate difference between average intensity and pixel, for every pixel for every color
|
||||
// component. Average intensity is normalized to where 0 is black and 1 is biggest value for
|
||||
// that component. If sum of differences between normalized average intensity and normalized
|
||||
// component varies more than this % between color components, we can afford to use less strict
|
||||
// cumulative threshold.
|
||||
cumulativeThresholdLax: number,
|
||||
cumulativeThresholdStrict: number,// if we add values of all pixels together and get more than this, the frame is bright enough.
|
||||
// (note: blackframe is 16x9 px -> 144px total. cumulative threshold can be reached fast)
|
||||
blackPixelsCondition: number, // How much pixels must be black (1 all, 0 none) before we consider frame as black. Takes
|
||||
// precedence over cumulative threshold: if blackPixelsCondition is met, the frame is dark
|
||||
// regardless of whether cumulative threshold has been reached.
|
||||
},
|
||||
blackbar: {
|
||||
blackLevel: number, // everything darker than 10/255 across all RGB components is considered black by
|
||||
// default. blackLevel can decrease if we detect darker black.
|
||||
threshold: number, // if pixel is darker than the sum of black level and this value, we count it as black
|
||||
// on 0-255. Needs to be fairly high (8 might not cut it) due to compression
|
||||
// artifacts in the video itself
|
||||
frameThreshold: number, // threshold, but when doing blackframe test
|
||||
imageThreshold: number, // in order to detect pixel as "not black", the pixel must be brighter than
|
||||
// the sum of black level, threshold and this value.
|
||||
gradientThreshold: number, // When trying to determine thickness of the black bars, we take 2 values: position of
|
||||
// the last pixel that's darker than our threshold, and position of the first pixel that's
|
||||
// brighter than our image threshold. If positions are more than this many pixels apart,
|
||||
// we assume we aren't looking at letterbox and thus don't correct the aspect ratio.
|
||||
gradientSampleSize: number, // How far do we look to find the gradient
|
||||
maxGradient: number, // if two neighboring pixels in gradientSampleSize differ by more than this, then we aren't
|
||||
// looking at a gradient
|
||||
gradientNegativeTreshold: number,
|
||||
gradientMaxSD: number, // reserved for future use
|
||||
antiGradientMode: AntiGradientMode
|
||||
},
|
||||
variableBlackbarThresholdOptions: { // In case of poor bitrate videos, jpeg artifacts may cause us issues
|
||||
// FOR FUTURE USE
|
||||
enabled: boolean, // allow increasing blackbar threshold
|
||||
disableArDetectOnMax: boolean, // disable autodetection when threshold goes over max blackbar threshold
|
||||
maxBlackbarThreshold: number, // max threshold (don't increase past this)
|
||||
thresholdStep: number, // when failing to set aspect ratio, increase threshold by this much
|
||||
increaseAfterConsecutiveResets: number // increase if AR resets this many times in a row
|
||||
},
|
||||
sampling: {
|
||||
staticCols: number, // we take a column at [0-n]/n-th parts along the width and sample it
|
||||
randomCols: number, // we add this many randomly selected columns to the static columns
|
||||
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks
|
||||
},
|
||||
guardLine: { // all pixels on the guardline need to be black, or else we trigger AR recalculation
|
||||
// (if AR fails to be recalculated, we reset AR)
|
||||
enabled: boolean,
|
||||
ignoreEdgeMargin: number, // we ignore anything that pokes over the black line this close to the edge
|
||||
// (relative to width of the sample)
|
||||
imageTestThreshold: number, // when testing for image, this much pixels must be over blackbarThreshold
|
||||
edgeTolerancePx: number, // black edge violation is performed this far from reported 'last black pixel'
|
||||
edgeTolerancePercent: null // unused. same as above, except use % of canvas height instead of pixels
|
||||
},
|
||||
fallbackMode: {
|
||||
enabled: boolean,
|
||||
safetyBorderPx: number, // determines the thickness of safety border in fallback mode
|
||||
noTriggerZonePx: number // if we detect edge less than this many pixels thick, we don't correct.
|
||||
},
|
||||
arSwitchLimiter: { // to be implemented
|
||||
switches: number, // we can switch this many times
|
||||
period: number // per this period
|
||||
},
|
||||
edgeDetection: {
|
||||
sampleWidth: number, // we take a sample this wide for edge detection
|
||||
detectionThreshold: number, // sample needs to have this many non-black pixels to be a valid edge
|
||||
confirmationThreshold: number, //
|
||||
singleSideConfirmationThreshold: number, // we need this much edges (out of all samples, not just edges) in order
|
||||
// to confirm an edge in case there's no edges on top or bottom (other
|
||||
// than logo, of course)
|
||||
logoThreshold: number, // if edge candidate sits with count greater than this*all_samples, it can't be logo
|
||||
// or watermark.
|
||||
edgeTolerancePx?: number, // we check for black edge violation this far from detection point
|
||||
edgeTolerancePercent?: number, // we check for black edge detection this % of height from detection point. unused
|
||||
middleIgnoredArea: number, // we ignore this % of canvas height towards edges while detecting aspect ratios
|
||||
minColsForSearch: number, // 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: number, // ignore pillars that are less than this many pixels thick.
|
||||
allowMisaligned: number // left and right edge can vary this much (%)
|
||||
},
|
||||
textLineTest: {
|
||||
nonTextPulse: number, // if a single continuous pulse has this many non-black pixels, we aren't dealing
|
||||
// with text. This value is relative to canvas width (%)
|
||||
pulsesToConfirm: number, // this is a threshold to confirm we're seeing text.
|
||||
pulsesToConfirmIfHalfBlack: number, // this is the threshold to confirm we're seeing text if longest black pulse
|
||||
// is over 50% of the canvas width
|
||||
testRowOffset: number // we test this % of height from detected edge
|
||||
}
|
||||
},
|
||||
zoom: {
|
||||
minLogZoom: number,
|
||||
maxLogZoom: number,
|
||||
announceDebounce: number // we wait this long before announcing new zoom
|
||||
},
|
||||
miscSettings: {
|
||||
mousePan: {
|
||||
enabled: boolean
|
||||
},
|
||||
mousePanReverseMouse: boolean,
|
||||
defaultAr?: any
|
||||
},
|
||||
stretch: {
|
||||
conditionalDifferencePercent: number // black bars less than this wide will trigger stretch
|
||||
// if mode is set to '1'. 1.0=100%
|
||||
},
|
||||
resizer: {
|
||||
setStyleString: {
|
||||
maxRetries: number,
|
||||
retryTimeout: number
|
||||
}
|
||||
},
|
||||
pageInfo: {
|
||||
timeouts: {
|
||||
urlCheck: number,
|
||||
rescan: number
|
||||
}
|
||||
},
|
||||
pan?: any,
|
||||
version?: string,
|
||||
preventReload?: boolean,
|
||||
|
||||
// -----------------------------------------
|
||||
// ::: MITIGATIONS :::
|
||||
// -----------------------------------------
|
||||
// Settings for browser bug workarounds.
|
||||
mitigations?: {
|
||||
zoomLimit?: {
|
||||
enabled?: boolean,
|
||||
fullscreenOnly?: boolean,
|
||||
limit?: number,
|
||||
}
|
||||
}
|
||||
// -----------------------------------------
|
||||
// ::: 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?: string, // name displayed in settings
|
||||
label?: string, // name displayed in ui (can be overridden in scope/playerUi)
|
||||
cmd?: {
|
||||
action: string,
|
||||
arg: any,
|
||||
customArg?: any,
|
||||
persistent?: boolean, // optional, false by default. If true, change doesn't take effect immediately.
|
||||
// Instead, this action saves stuff to settings
|
||||
}[],
|
||||
scopes?: {
|
||||
global?: ActionScopeInterface,
|
||||
site?: ActionScopeInterface,
|
||||
page?: ActionScopeInterface
|
||||
},
|
||||
playerUi?: {
|
||||
show: boolean,
|
||||
path?: string,
|
||||
},
|
||||
userAdded?: boolean,
|
||||
}[],
|
||||
whatsNewChecked: boolean,
|
||||
// -----------------------------------------
|
||||
// ::: 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: {
|
||||
[x: string]: {
|
||||
mode?: ExtensionMode,
|
||||
autoar?: ExtensionMode,
|
||||
autoarFallback?: ExtensionMode,
|
||||
stretch?: StretchType,
|
||||
videoAlignment?: VideoAlignmentType,
|
||||
keyboardShortcutsEnabled?: ExtensionMode,
|
||||
type?: string,
|
||||
override?: boolean,
|
||||
arPersistence?: boolean,
|
||||
actions?: any;
|
||||
|
||||
cropModePersistence?: CropModePersistence;
|
||||
|
||||
DOM?: {
|
||||
player?: {
|
||||
manual?: boolean,
|
||||
querySelectors?: string,
|
||||
additionalCss?: string,
|
||||
useRelativeAncestor?: boolean,
|
||||
videoAncestor?: any,
|
||||
playerNodeCss?: string,
|
||||
periodicallyRefreshPlayerElement?: boolean
|
||||
},
|
||||
video?: {
|
||||
manual?: boolean,
|
||||
querySelectors?: string,
|
||||
additionalCss?: string,
|
||||
useRelativeAncestor?: boolean,
|
||||
playerNodeCss?: string
|
||||
}
|
||||
},
|
||||
css?: string;
|
||||
usePlayerArInFullscreen?: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsInterface;
|
3
src/common/js/utils.ts
Normal file
3
src/common/js/utils.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export async function sleep(timeout) {
|
||||
return new Promise<void>( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
||||
}
|
@ -10,7 +10,7 @@ export default {
|
||||
}) || [];
|
||||
},
|
||||
extensionActions: function(){
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-extension-mode') || [];
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-ExtensionMode') || [];
|
||||
},
|
||||
aardActions: function(){
|
||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-autoar-mode') || [];
|
||||
|
@ -1,299 +1,176 @@
|
||||
<template>
|
||||
<div v-if="uiVisible" class="uw-hover uv-hover-trigger-region">
|
||||
<div class="flex flex-column flex-center">
|
||||
<div class="text panel">
|
||||
<h1>Your browser is incompatible with this extension on this site</h1>
|
||||
<!-- <p><sup>2020-12-22</sup></p> -->
|
||||
<p>
|
||||
In October 2020, Microsoft Edge received an update that breaks video playback on sites that utilize DRM in certain cases.
|
||||
As a result of this bug, cropped videos will be displayed incorrectly. This issue cannot be fixed by any extension, and
|
||||
<b>using this extension (and its alternatives) on this site may make things worse</b> — even if you only set aspect ratio manually.
|
||||
</p>
|
||||
<p>
|
||||
I have attempted all possible workarounds and none of them work.
|
||||
</p>
|
||||
<p>
|
||||
I would like to ask a couple of favours:
|
||||
</p>
|
||||
<p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Report this issue to Microsoft.</b>
|
||||
I have already done so, however such issues get noticed quicker if more people report them.
|
||||
<div class="vspc"></div>
|
||||
I have prepared a description of the problem — you can find it below (you may need to scroll).
|
||||
</li>
|
||||
<li>
|
||||
<b>Please notify me when Microsoft fixes this issue.</b>
|
||||
I use windows only sporadically and cannot check whether Edge developers have fixed the issue or not.
|
||||
You can contact me by opening an issue on <a href="https://github.com/tamius-han/ultrawidify/issues" target="_blank">github</a> or
|
||||
sending me <a href="mailto:tamius.han@gmail.com" target="_blank">an email</a>. You can also <a href="https://www.reddit.com/message/compose?to=xternal7" target="_blank">PM me on reddit</a>.
|
||||
<div class="vspc"></div>
|
||||
Please include the following text with your message:
|
||||
<div class="vspc"></div>
|
||||
<i>{{userAgent}}</i>
|
||||
<div class="vspc"></div>
|
||||
You can determine whether the issue is fixed by attempting to watch 21:9 movie on netflix and compare what you see to the screenshots I included at the bottom.
|
||||
</li>
|
||||
<li>
|
||||
<small>
|
||||
<b>Avoid leaving one-star reviews on Chrome Web Store.</b> I don't usually whine about one star
|
||||
reviews, but Chrome Web Store is for Google Chrome users. As such, I cannot optimize the extension
|
||||
for Edge in my Chrome Web Store submission. Furthermore, people reading reviews in Chrome
|
||||
Web Store are interested in knowing how extension behaves in Google Chrome. The experience you
|
||||
get in Microsoft Edge is irrelevant to them.
|
||||
<div class="vspc"></div>
|
||||
If you wish the best experience, you should install this addon from Edge Addons store (once I make
|
||||
it available on that store again).
|
||||
<div class="vspc"></div>
|
||||
Thanks for being understanding.
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<b>Thanks for your help in advance. It's much appreciated.</b>
|
||||
</p>
|
||||
<p> </p>
|
||||
<p>
|
||||
If you're interested in more details about why this happens, you can find more details in <a href="https://stuff.tamius.net/sacred-texts/2020/12/22/ultrawidify-and-edge-2020-edition/" target="_blank">this blogpost</a>.
|
||||
</p>
|
||||
<p>
|
||||
Finished reading? <b><a @click="uiVisible=false">Hide this popup</a></b>.
|
||||
</p>
|
||||
<p>
|
||||
In order to disable this popup forever, open the ultrawidify popup, click on 'site settings' and disable automatic aspect ratio detection for this site.
|
||||
You should probably even disable the extension for this site altogether for the time being.
|
||||
</p>
|
||||
<p>
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
<h3>Help by reporting this issue to Microsoft</h3>
|
||||
<p><b>Go to the settings menu</b> <small>(upper right corner of the window, three dots)</small> <b>→ Help and feedback</b> <small>(second option from the bottom)</small> <b>→ Send feedback.</b> (Alternatively, press Alt + Shift + I)</p>
|
||||
<p>Enter this in the first box:</p>
|
||||
<p>
|
||||
<br/>
|
||||
<i>
|
||||
Videos on sites that utilize DRM protection schemes are not being scaled correctly. If a part of a DRM-protected video is being displayed outside the boundaries of the browser window,
|
||||
Edge will scale the video to only fit the portion of the video tag that is currently being displayed on the screen, rather than filling the entire video tag. This causes videos appear
|
||||
differently than website developers intended at best, and breaking certain websites at worst.
|
||||
</i>
|
||||
<br/>
|
||||
</p>
|
||||
<p>Or something along these lines. Click 'send' when you're done.</p>
|
||||
<p>It's the squeaky wheel that gets the grease: developers tend to prioritize issues that affect more people. The more people report this issue, the more likely it is for developers to notice it.</p>
|
||||
</div>
|
||||
<div class="image-examples panel">
|
||||
<h3>How can one tell when the Edge bug is fixed?</h3>
|
||||
<p>
|
||||
When 21:9 movies on netflix look like this:
|
||||
</p>
|
||||
<p>
|
||||
<img :src="getUrl('res/img/git-ignore/edge-demo-working.jpg')" />
|
||||
</p>
|
||||
<p>
|
||||
And not like this:
|
||||
</p>
|
||||
<p>
|
||||
<img :src="getUrl('res/img/git-ignore/edge-demo.jpg')" />
|
||||
</p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<div class="uw-hover uv-hover-trigger-region">
|
||||
TEST CONTENT
|
||||
</div>
|
||||
<div class="popup-panel">
|
||||
<div class="tab-row flex flex-row">
|
||||
<div class="tab">
|
||||
todo: icon<br/>
|
||||
Video options
|
||||
</div>
|
||||
</div>
|
||||
<div>sudpo
|
||||
<!-- Panel section -->
|
||||
<template v-if="settingsInitialized">
|
||||
<VideoSettings
|
||||
:settings="settings"
|
||||
></VideoSettings>
|
||||
<ResizerDebugPanel :debugData="debugData">
|
||||
</ResizerDebugPanel>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VideoSettings from './PlayerUiPanels/VideoSettings.vue'
|
||||
import { mapState } from 'vuex';
|
||||
import Icon from '../common/components/Icon';
|
||||
import ResizerDebugPanel from './PlayerUiPanels/ResizerDebugPanelComponent';
|
||||
import BrowserDetect from '../ext/conf/BrowserDetect';
|
||||
import ExecAction from './ui-libs/ExecAction';
|
||||
import Logger from '../ext/lib/Logger';
|
||||
import Settings from '../ext/lib/Settings';
|
||||
import FontLoader from '../ext/lib/uwui/FontLoader';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
ResizerDebugPanel, VideoSettings
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// component properties
|
||||
settings: {},
|
||||
settingsInitialized: false,
|
||||
execAction: new ExecAction(),
|
||||
logger: null,
|
||||
|
||||
uiVisible: true,
|
||||
userAgent: window.navigator.userAgent
|
||||
debugData: {
|
||||
resizer: {},
|
||||
player: {},
|
||||
},
|
||||
debugDataPrettified: ''
|
||||
};
|
||||
},
|
||||
created() {
|
||||
FontLoader.loadFonts();
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'showUi',
|
||||
'resizerDebugData',
|
||||
'playerDebugData'
|
||||
]),
|
||||
windowWidth: () => {
|
||||
return window.innerWidth;
|
||||
},
|
||||
windowHeight: () => {
|
||||
return window.innerHeight;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
showUi(visible) {
|
||||
if (visible !== undefined) {
|
||||
this.uiVisible = visible;
|
||||
}
|
||||
},
|
||||
resizerDebugData(newData) {
|
||||
this.debugData.resizer = newData;
|
||||
this.debugDataPrettified = JSON.stringify(this.debugData, null, 2);
|
||||
},
|
||||
playerDebugData(newData) {
|
||||
this.debugData.player = newData;
|
||||
this.debugDataPrettified = JSON.stringify(this.debugData, null, 2);
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
this.logger = new Logger();
|
||||
await this.logger.init({
|
||||
allowLogging: true,
|
||||
});
|
||||
|
||||
this.settings = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger});
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
|
||||
this.execAction.setSettings(this.settings);
|
||||
} catch (e) {
|
||||
console.error('Failed to initiate ultrawidify player ui.', e);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUrl(url) {
|
||||
return BrowserDetect.getURL(url);
|
||||
},
|
||||
async hidePopupForever() {
|
||||
const settings = new Settings();
|
||||
await settings.init();
|
||||
|
||||
if (!settings.active.mutedNotifications) {
|
||||
settings.active.mutedNotifications = {};
|
||||
}
|
||||
if (!settings.active.mutedNotifications?.browserSpecific) {
|
||||
settings.active.mutedNotifications.browserSpecific = {
|
||||
edge: {
|
||||
brokenDrm: {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
settings.active.mutedNotifications.browserSpecific.edge.brokenDrm[window.location.hostname] = true;
|
||||
|
||||
await settings.saveWithoutReload();
|
||||
this.uiVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="../res/css/uwui-base.scss"></style>
|
||||
<style lang="scss" src="../res/css/uwui-base.scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
@import '../res/css/uwui-base.scss';
|
||||
@import '../res/css/colors.scss';
|
||||
// @import '../res/css/font/overpass.css';
|
||||
// @import '../res/css/font/overpass-mono.css';
|
||||
@import '../res/css/font/overpass.css';
|
||||
@import '../res/css/font/overpass-mono.css';
|
||||
@import '../res/css/common.scss';
|
||||
|
||||
.uw-ultrawidify-container-root {
|
||||
|
||||
* {
|
||||
font-family: 'Overpass', 'Segoe UI';
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
pointer-events: auto;
|
||||
|
||||
|
||||
.vspc {
|
||||
height: 0.5em;
|
||||
display: block;
|
||||
}
|
||||
// .relative-wrapper {
|
||||
// position: relative;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// }
|
||||
|
||||
.uw-hover {
|
||||
position: absolute;
|
||||
top: 5%;
|
||||
left: 5%;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
color: #ddd;
|
||||
background-color: rgba(84, 8, 8, 0.786);
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
|
||||
pointer-events: auto;
|
||||
overflow-y: scroll;
|
||||
z-index: 999999999999999999;
|
||||
}
|
||||
.uw-hover:hover {
|
||||
background-color: #f00;
|
||||
}
|
||||
|
||||
p, h1, h2, h3 {
|
||||
margin: 0.75em;
|
||||
display: block;
|
||||
// display: inline-block;
|
||||
}
|
||||
.popup-panel {
|
||||
position: absolute;
|
||||
|
||||
h1 {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
|
||||
h2, h3 {
|
||||
color: #fa6;
|
||||
text-align: center;
|
||||
}
|
||||
z-index: 999999999999999999;
|
||||
|
||||
i {
|
||||
display: block !important;
|
||||
padding: 4px 8px;
|
||||
font-family: 'Overpass Mono', monospace;
|
||||
background-color: rgba(11,11,11,0.75);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
width: 2500px;
|
||||
height: 1200px;
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
|
||||
b {
|
||||
color: #fff;
|
||||
}
|
||||
pointer-events: all !important;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
background-color: rgba(0,0,0,0.69);
|
||||
color: #fff;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
sup {
|
||||
vertical-align: super;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
max-width: 960px;
|
||||
min-width: 420px;
|
||||
}
|
||||
|
||||
.image-examples {
|
||||
flex-grow: auto;
|
||||
flex-shrink: 1;
|
||||
min-width: 720px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
list-style-position: inside;
|
||||
}
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
list-style-position: inside;
|
||||
}
|
||||
ul ul, ol ul {
|
||||
list-style-type: circle;
|
||||
list-style-position: inside;
|
||||
margin-left: 15px;
|
||||
}
|
||||
ol ol, ul ol {
|
||||
list-style-type: lower-latin;
|
||||
list-style-position: inside;
|
||||
margin-left: 15px;
|
||||
}
|
||||
li {
|
||||
margin-top: 16px;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.pad {
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
// padding-bottom: 10%;
|
||||
overflow-y: auto;
|
||||
|
||||
.tab {
|
||||
display: block;
|
||||
height: 42px;
|
||||
font-size: 2.5rem;
|
||||
background: rgb(87, 54, 26);
|
||||
}
|
||||
.tab:hover {
|
||||
background-color: #f00;
|
||||
}
|
||||
}
|
||||
.flex-row {
|
||||
justify-content: space-around;
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
143
src/ext/UWContent.ts
Normal file
143
src/ext/UWContent.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import Debug from './conf/Debug';
|
||||
import ExtensionMode from '../common/enums/ExtensionMode.enum';
|
||||
import Settings from './lib/Settings';
|
||||
import ActionHandler from './lib/ActionHandler';
|
||||
import Comms from './lib/comms/Comms';
|
||||
import CommsClient from './lib/comms/CommsClient';
|
||||
import PageInfo from './lib/video-data/PageInfo';
|
||||
import Logger, { baseLoggingOptions } from './lib/Logger';
|
||||
|
||||
export default class UWContent {
|
||||
pageInfo: PageInfo;
|
||||
comms: CommsClient;
|
||||
settings: Settings;
|
||||
actionHandler: ActionHandler;
|
||||
logger: Logger;
|
||||
|
||||
commsHandlers: {
|
||||
[x: string]: ((a: any, b?: any) => void | Promise<void>)[]
|
||||
} = {
|
||||
'get-current-zoom': [() => this.pageInfo.requestCurrentZoom()],
|
||||
'set-ar': [(message) => this.pageInfo.setAr({type: message.arg, ratio: message.customArg}, message.playing)],
|
||||
'set-alignment': [(message) => {
|
||||
this.pageInfo.setVideoAlignment(message.arg, message.playing);
|
||||
this.pageInfo.restoreAr();
|
||||
}],
|
||||
'set-stretch': [(message) => this.pageInfo.setStretchMode(message.arg, message.playing, message.customArg)],
|
||||
'set-keyboard': [(message) => this.pageInfo.setKeyboardShortcutsEnabled(message.arg)],
|
||||
'autoar-start': [(message) => {
|
||||
if (message.enabled !== false) {
|
||||
this.pageInfo.initArDetection(message.playing);
|
||||
this.pageInfo.startArDetection(message.playing);
|
||||
} else {
|
||||
this.pageInfo.stopArDetection(message.playing);
|
||||
}
|
||||
}],
|
||||
'pause-processing': [(message) => this.pageInfo.pauseProcessing(message.playing)],
|
||||
'resume-processing': [(message) => this.pageInfo.resumeProcessing(message.autoArStatus, message.playing)],
|
||||
'set-zoom': [(message) => this.pageInfo.setZoom(message.arg, true, message.playing)],
|
||||
'change-zoom': [(message) => this.pageInfo.zoomStep(message.arg, message.playing)],
|
||||
'mark-player': [(message) => this.pageInfo.markPlayer(message.name, message.color)],
|
||||
'unmark-player': [() => this.pageInfo.unmarkPlayer()],
|
||||
'autoar-set-manual-tick': [(message) => this.pageInfo.setManualTick(message.arg)],
|
||||
'autoar-tick': [() => this.pageInfo.tick()],
|
||||
'set-ar-persistence': [(message) => this.pageInfo.setArPersistence(message.arg)],
|
||||
}
|
||||
|
||||
constructor(){
|
||||
}
|
||||
|
||||
reloadSettings() {
|
||||
try {
|
||||
this.logger.log('info', 'debug', 'Things happened in the popup. Will reload extension settings.');
|
||||
this.init();
|
||||
} catch (e) {
|
||||
console.warn('Ultrawidify: settings reload failed. This probably shouldn\'t outright kill the extension, but page reload is recommended.');
|
||||
}
|
||||
}
|
||||
|
||||
async init(){
|
||||
try {
|
||||
if (Debug.debug) {
|
||||
console.log("[uw::main] loading configuration ...");
|
||||
}
|
||||
|
||||
// logger init is the first thing that needs to run
|
||||
try {
|
||||
if (!this.logger) {
|
||||
|
||||
this.logger = new Logger();
|
||||
await this.logger.init(baseLoggingOptions);
|
||||
|
||||
// show popup if logging to file is enabled
|
||||
if (this.logger.isLoggingAllowed() && this.logger.isLoggingToFile()) {
|
||||
console.info("[uw::init] Logging is allowed! Initalizing vue and UI!");
|
||||
|
||||
// CommsClient is not initiated yet, so we use static comms to send the command
|
||||
Comms.sendMessage({cmd: 'show-logger'});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("logger init failed!", e)
|
||||
}
|
||||
|
||||
// init() is re-run any time settings change
|
||||
if (this.comms) {
|
||||
this.comms.destroy();
|
||||
}
|
||||
if (!this.settings) {
|
||||
this.settings = new Settings({
|
||||
onSettingsChanged: () => this.reloadSettings(),
|
||||
logger: this.logger
|
||||
});
|
||||
await this.settings.init();
|
||||
}
|
||||
|
||||
this.comms = new CommsClient('content-main-port', this.logger, this.commsHandlers);
|
||||
|
||||
// če smo razširitev onemogočili v nastavitvah, ne naredimo ničesar
|
||||
// If extension is soft-disabled, don't do shit
|
||||
|
||||
var extensionMode = this.settings.getExtensionMode();
|
||||
|
||||
this.logger.log('info', 'debug', "[uw::init] Extension mode:" + (extensionMode < 0 ? "disabled" : extensionMode == '1' ? 'basic' : 'full'));
|
||||
|
||||
const isSiteDisabled = extensionMode === ExtensionMode.Disabled
|
||||
|
||||
if (isSiteDisabled) {
|
||||
if (this.settings.getExtensionMode('@global') === ExtensionMode.Disabled) {
|
||||
this.logger.log('info', 'debug', "[uw::init] EXTENSION DISABLED, THEREFORE WONT BE STARTED")
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.pageInfo) {
|
||||
this.logger.log('info', 'debug', '[uw.js::setup] An instance of pageInfo already exists and will be destroyed.');
|
||||
this.pageInfo.destroy();
|
||||
}
|
||||
this.pageInfo = new PageInfo(this.comms, this.settings, this.logger, extensionMode, isSiteDisabled);
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] pageInfo initialized.");
|
||||
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] will try to initate ActionHandler.");
|
||||
|
||||
// start action handler only if extension is enabled for this site
|
||||
if (!isSiteDisabled) {
|
||||
if (this.actionHandler) {
|
||||
this.actionHandler.destroy();
|
||||
}
|
||||
this.actionHandler = new ActionHandler(this.pageInfo);
|
||||
this.actionHandler.init();
|
||||
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] ActionHandler initiated.");
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error('Ultrawidify: failed to start extension. Error:', e)
|
||||
this.logger.log('error', 'debug', "[uw::init] FAILED TO START EXTENSION. Error:", e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Ultrawidify initalization failed for some reason:', e);
|
||||
}
|
||||
}
|
||||
}
|
315
src/ext/UWServer.ts
Normal file
315
src/ext/UWServer.ts
Normal file
@ -0,0 +1,315 @@
|
||||
import Debug from './conf/Debug.js';
|
||||
import BrowserDetect from './conf/BrowserDetect';
|
||||
import CommsServer from './lib/comms/CommsServer';
|
||||
import Settings from './lib/Settings';
|
||||
import Logger, { baseLoggingOptions } from './lib/Logger';
|
||||
|
||||
import { sleep } from '../common/js/utils';
|
||||
|
||||
import { browser } from 'webextension-polyfill-ts';
|
||||
|
||||
export default class UWServer {
|
||||
settings: Settings;
|
||||
logger: Logger;
|
||||
comms: CommsServer;
|
||||
|
||||
ports: any[] = [];
|
||||
hasVideos: boolean;
|
||||
currentSite: string = '';
|
||||
videoTabs: any = {};
|
||||
currentTabId: number = 0;
|
||||
|
||||
selectedSubitem: any = {
|
||||
'siteSettings': undefined,
|
||||
'videoSettings': undefined,
|
||||
}
|
||||
|
||||
|
||||
private gcTimeout: any;
|
||||
uiLoggerInitialized: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.setup();
|
||||
}
|
||||
|
||||
async setup() {
|
||||
try {
|
||||
// logger is the first thing that goes up
|
||||
const loggingOptions = {
|
||||
isBackgroundScript: true,
|
||||
allowLogging: false,
|
||||
useConfFromStorage: true,
|
||||
logAll: true,
|
||||
fileOptions: {
|
||||
enabled: false,
|
||||
},
|
||||
consoleOptions: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
this.logger = new Logger();
|
||||
await this.logger.init(loggingOptions);
|
||||
|
||||
this.settings = new Settings({logger: this.logger});
|
||||
await this.settings.init();
|
||||
this.comms = new CommsServer(this);
|
||||
this.comms.subscribe('show-logger', async () => await this.initUiAndShowLogger());
|
||||
this.comms.subscribe('init-vue', async () => await this.initUi());
|
||||
this.comms.subscribe('uwui-vue-initialized', () => this.uiLoggerInitialized = true);
|
||||
this.comms.subscribe('emit-logs', () => {}); // we don't need to do anything, this gets forwarded to UI content script as is
|
||||
|
||||
browser.tabs.onActivated.addListener((m) => {this.onTabSwitched(m)});
|
||||
} catch (e) {
|
||||
console.error(`Ultrawidify [server]: failed to start. Reason:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
async _promisifyTabsGet(browserObj, tabId){
|
||||
return new Promise( (resolve, reject) => {
|
||||
browserObj.tabs.get(tabId, (tab) => resolve(tab));
|
||||
});
|
||||
}
|
||||
|
||||
async injectCss(css, sender) {
|
||||
try {
|
||||
if (BrowserDetect.firefox || BrowserDetect.edge) {
|
||||
browser.tabs.insertCSS(sender.tab.id, {code: css, cssOrigin: 'user', frameId: sender.frameId});
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
chrome.tabs.insertCSS(sender.tab.id, {code: css, cssOrigin: 'user', frameId: sender.frameId});
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.log('error','debug', '[UwServer::injectCss] Error while injecting css:', {error: e, css, sender});
|
||||
}
|
||||
}
|
||||
async removeCss(css, sender) {
|
||||
try {
|
||||
browser.tabs.removeCSS(sender.tab.id, {code: css, cssOrigin: 'user', frameId: sender.frameId});
|
||||
} catch (e) {
|
||||
this.logger.log('error','debug', '[UwServer::injectCss] Error while removing css:', {error: e, css, sender});
|
||||
}
|
||||
}
|
||||
|
||||
async replaceCss(oldCss, newCss, sender) {
|
||||
if (oldCss !== newCss) {
|
||||
this.injectCss(newCss, sender);
|
||||
this.removeCss(oldCss, sender);
|
||||
}
|
||||
}
|
||||
|
||||
extractHostname(url){
|
||||
var hostname;
|
||||
|
||||
if (!url) {
|
||||
return "<no url>";
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
try {
|
||||
this.currentTabId = activeInfo.tabId; // just for readability
|
||||
|
||||
let tab;
|
||||
if (BrowserDetect.firefox) {
|
||||
tab = await browser.tabs.get(this.currentTabId);
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
tab = await this._promisifyTabsGet(chrome, this.currentTabId);
|
||||
}
|
||||
|
||||
this.currentSite = this.extractHostname(tab.url);
|
||||
this.logger.log('info', 'debug', '[UwServer::onTabSwitched] user switched tab. New site:', this.currentSite);
|
||||
} catch(e) {
|
||||
this.logger.log('error', 'debug', '[UwServer::onTabSwitched] there was a problem getting currnet site:', e)
|
||||
}
|
||||
|
||||
this.selectedSubitem = {
|
||||
'siteSettings': undefined,
|
||||
'videoSettings': undefined,
|
||||
}
|
||||
//TODO: change extension icon based on whether there's any videos on current page
|
||||
}
|
||||
|
||||
registerVideo(sender) {
|
||||
this.logger.log('info', 'comms', '[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]?.host != tabHostname) {
|
||||
delete this.videoTabs[sender.tab.id]
|
||||
} else 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]) {
|
||||
this.videoTabs[sender.tab.id].frames[sender.frameId] = {
|
||||
id: sender.frameId,
|
||||
host: frameHostname,
|
||||
url: sender.url,
|
||||
registerTime: Date.now(),
|
||||
}
|
||||
} 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,
|
||||
registerTime: Date.now(),
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log('info', 'comms', '[UWServer::registerVideo] Video registered. current videoTabs:', this.videoTabs);
|
||||
}
|
||||
|
||||
unregisterVideo(sender) {
|
||||
this.logger.log('info', 'comms', '[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];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.log('info', 'comms', '[UwServer::unregisterVideo] Video has been unregistered. Current videoTabs:', this.videoTabs);
|
||||
}
|
||||
|
||||
setSelectedTab(menu, subitem) {
|
||||
this.logger.log('info', 'comms', '[UwServer::setSelectedTab] saving selected tab for', menu, ':', subitem);
|
||||
this.selectedSubitem[menu] = subitem;
|
||||
}
|
||||
|
||||
async initUi() {
|
||||
try {
|
||||
if (BrowserDetect.firefox) {
|
||||
await browser.tabs.executeScript({
|
||||
file: '/ext/uw-ui.js',
|
||||
allFrames: true,
|
||||
});
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
await new Promise<void>( resolve =>
|
||||
chrome.tabs.executeScript({
|
||||
file: '/ext/uw-ui.js',
|
||||
allFrames: true,
|
||||
}, () => resolve())
|
||||
);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.warn('Ultrawidify [server]: UI setup failed. While problematic, this problem shouldn\'t completely crash the extension.');
|
||||
this.logger.log('ERROR', 'uwbg', 'UI initialization failed. Reason:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async initUiAndShowLogger() {
|
||||
try {
|
||||
// this implementation is less than optimal and very hacky, but it should work
|
||||
// just fine for our use case.
|
||||
this.uiLoggerInitialized = false;
|
||||
|
||||
await this.initUi();
|
||||
|
||||
await new Promise<void>( async (resolve, reject) => {
|
||||
// if content script doesn't give us a response within 5 seconds, something is
|
||||
// obviously wrong and we stop waiting,
|
||||
|
||||
// oh and btw, resolve/reject do not break the loops, so we need to do that
|
||||
// ourselves:
|
||||
// https://stackoverflow.com/questions/55207256/will-resolve-in-promise-loop-break-loop-iteration
|
||||
let isRejected = false;
|
||||
setTimeout( async () => {isRejected = true; reject()}, 5000);
|
||||
|
||||
// check whether UI has been initiated on the FE. If it was, we resolve the
|
||||
// promise and off we go
|
||||
while (!isRejected) {
|
||||
if (this.uiLoggerInitialized) {
|
||||
resolve();
|
||||
return; // remember the bit about resolve() not breaking the loop?
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Ultrawidify [server]: failed to set up logger UI. While problematic, this problem shouldn\'t completely crash the extension.');
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentTab() {
|
||||
return (await browser.tabs.query({active: true, currentWindow: true}))[0];
|
||||
}
|
||||
|
||||
async getVideoTab() {
|
||||
// friendly reminder: if current tab doesn't have a video,
|
||||
// there won't be anything in this.videoTabs[this.currentTabId]
|
||||
|
||||
const ctab = await this.getCurrentTab();
|
||||
|
||||
if (!ctab || !ctab.id) {
|
||||
return {
|
||||
host: 'INVALID SITE',
|
||||
frames: [],
|
||||
}
|
||||
}
|
||||
|
||||
if (this.videoTabs[ctab.id]) {
|
||||
// if video is older than PageInfo's video rescan period (+ 4000ms of grace),
|
||||
// we clean it up from videoTabs[tabId].frames array.
|
||||
const ageLimit = Date.now() - this.settings.active.pageInfo.timeouts.rescan - 4000;
|
||||
try {
|
||||
for (const key in this.videoTabs[ctab.id].frames) {
|
||||
if (this.videoTabs[ctab.id].frames[key].registerTime < ageLimit) {
|
||||
delete this.videoTabs[ctab.id].frames[key];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// something went wrong. There's prolly no frames.
|
||||
return {
|
||||
host: this.extractHostname(ctab.url),
|
||||
frames: [],
|
||||
selected: this.selectedSubitem
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...this.videoTabs[ctab.id],
|
||||
host: this.extractHostname(ctab.url),
|
||||
selected: this.selectedSubitem
|
||||
};
|
||||
}
|
||||
|
||||
// return something more or less empty if this tab doesn't have
|
||||
// a video registered for it
|
||||
return {
|
||||
host: this.extractHostname(ctab.url),
|
||||
frames: [],
|
||||
selected: this.selectedSubitem
|
||||
}
|
||||
}
|
||||
|
||||
// chrome shitiness mitigation
|
||||
sendUnmarkPlayer(message) {
|
||||
this.comms.sendUnmarkPlayer(message);
|
||||
}
|
||||
}
|
@ -1,27 +1,27 @@
|
||||
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';
|
||||
import CropModePersistence from '../../common/enums/crop-mode-persistence.enum';
|
||||
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
||||
import StretchType from '../../common/enums/StretchType.enum';
|
||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
||||
import AspectRatioType from '../../common/enums/AspectRatioType.enum';
|
||||
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
||||
|
||||
var ActionList = {
|
||||
'set-ar': {
|
||||
name: 'Set aspect ratio',
|
||||
args: [{
|
||||
name: 'Automatic',
|
||||
arg: AspectRatio.Automatic,
|
||||
arg: AspectRatioType.Automatic,
|
||||
},{
|
||||
name: 'Fit width',
|
||||
arg: AspectRatio.FitWidth,
|
||||
arg: AspectRatioType.FitWidth,
|
||||
},{
|
||||
name: 'Fit height',
|
||||
arg: AspectRatio.FitHeight,
|
||||
arg: AspectRatioType.FitHeight,
|
||||
},{
|
||||
name: 'Reset',
|
||||
arg: AspectRatio.Reset,
|
||||
arg: AspectRatioType.Reset,
|
||||
},{
|
||||
name: 'Manually specify ratio',
|
||||
arg: AspectRatio.Fixed,
|
||||
arg: AspectRatioType.Fixed,
|
||||
customArg: true,
|
||||
customSetter: (value) => {
|
||||
const [width, height] = value.split(':');
|
||||
@ -70,33 +70,33 @@ var ActionList = {
|
||||
name: 'Set stretch',
|
||||
args: [{
|
||||
name: 'Normal',
|
||||
arg: Stretch.NoStretch
|
||||
arg: StretchType.NoStretch
|
||||
},{
|
||||
name: 'Basic',
|
||||
arg: Stretch.Basic,
|
||||
arg: StretchType.Basic,
|
||||
},{
|
||||
name: 'Hybrid',
|
||||
arg: Stretch.Hybrid,
|
||||
arg: StretchType.Hybrid,
|
||||
},{
|
||||
name: 'Thin borders',
|
||||
arg: Stretch.Conditional,
|
||||
arg: StretchType.Conditional,
|
||||
},{
|
||||
name: 'Fixed (source)',
|
||||
arg: Stretch.FixedSource,
|
||||
arg: StretchType.FixedSource,
|
||||
customArg: true,
|
||||
scopes: {
|
||||
page: true,
|
||||
}
|
||||
},{
|
||||
name: 'Fixed (displayed)',
|
||||
arg: Stretch.Fixed,
|
||||
arg: StretchType.Fixed,
|
||||
customArg: true,
|
||||
scopes: {
|
||||
page: true,
|
||||
}
|
||||
},{
|
||||
name: 'Default',
|
||||
arg: Stretch.Default,
|
||||
arg: StretchType.Default,
|
||||
scopes: {
|
||||
site: true
|
||||
}
|
||||
@ -111,16 +111,16 @@ var ActionList = {
|
||||
name: 'Set video alignment',
|
||||
args: [{
|
||||
name: 'Left',
|
||||
arg: VideoAlignment.Left,
|
||||
arg: VideoAlignmentType.Left,
|
||||
},{
|
||||
name: 'Center',
|
||||
arg: VideoAlignment.Center,
|
||||
arg: VideoAlignmentType.Center,
|
||||
},{
|
||||
name: 'Right',
|
||||
arg: VideoAlignment.Right
|
||||
arg: VideoAlignmentType.Right
|
||||
},{
|
||||
name: 'Default',
|
||||
arg: VideoAlignment.Default,
|
||||
arg: VideoAlignmentType.Default,
|
||||
scopes: {
|
||||
site: true,
|
||||
}
|
||||
@ -179,7 +179,7 @@ var ActionList = {
|
||||
page: true,
|
||||
}
|
||||
},
|
||||
'set-extension-mode': {
|
||||
'set-ExtensionMode': {
|
||||
name: 'Set extension mode',
|
||||
args: [{
|
||||
name: 'Enable',
|
||||
|
@ -1,8 +1,9 @@
|
||||
// How to use:
|
||||
// version: {ExtensionConf object, but only properties that get overwritten}
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
||||
import VideoAlignment from '../../common/enums/video-alignment.enum';
|
||||
import StretchType from '../../common/enums/StretchType.enum';
|
||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
||||
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
||||
import BrowserDetect from './BrowserDetect';
|
||||
|
||||
const ExtensionConfPatch = [
|
||||
{
|
||||
@ -267,7 +268,7 @@ const ExtensionConfPatch = [
|
||||
label: '4:3 stretch (src)',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.FixedSource,
|
||||
arg: StretchType.FixedSource,
|
||||
customArg: 1.33,
|
||||
}],
|
||||
scopes: {
|
||||
@ -284,7 +285,7 @@ const ExtensionConfPatch = [
|
||||
label: '16:9 stretch (src)',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.FixedSource,
|
||||
arg: StretchType.FixedSource,
|
||||
customArg: 1.77,
|
||||
}],
|
||||
scopes: {
|
||||
@ -309,8 +310,8 @@ const ExtensionConfPatch = [
|
||||
autoar: ExtensionMode.Enabled,
|
||||
autoarFallback: ExtensionMode.Enabled,
|
||||
override: true, // ignore value localStorage in favour of this
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
DOM: {
|
||||
player: {
|
||||
@ -339,8 +340,8 @@ const ExtensionConfPatch = [
|
||||
autoar: ExtensionMode.Enabled,
|
||||
override: false,
|
||||
type: 'community',
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
arPersistence: true, // persist aspect ratio between different videos
|
||||
autoarPreventConditions: { // prevents autoar on following conditions
|
||||
@ -445,6 +446,71 @@ const ExtensionConfPatch = [
|
||||
// do nothing if disney+ is missing
|
||||
}
|
||||
}
|
||||
}, {
|
||||
forVersion: '5.0.1',
|
||||
updateFn: (userOptions, defaultOptions) => {
|
||||
try {
|
||||
userOptions.mitigations = {
|
||||
zoomLimit: {
|
||||
enabled: BrowserDetect.edge || BrowserDetect.isEdgeUA,
|
||||
limit: 0.997,
|
||||
fullscreenOnly: true
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}, {
|
||||
forVersion: '5.0.1.1',
|
||||
updateFn: (userOptions, defaultOptions) => {
|
||||
try {
|
||||
userOptions.mitigations = {
|
||||
zoomLimit: {
|
||||
enabled: true,
|
||||
limit: 0.997,
|
||||
fullscreenOnly: true
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}, {
|
||||
forVersion: '5.0.2',
|
||||
updateFn: (userOptions, defaultOptions) => {
|
||||
try {
|
||||
if (! userOptions.mitigations) {
|
||||
userOptions.mitigations = {
|
||||
zoomLimit: {
|
||||
enabled: true,
|
||||
limit: 0.997,
|
||||
fullscreenOnly: true
|
||||
}
|
||||
}
|
||||
} else if (BrowserDetect.chrome) {
|
||||
userOptions.mitigations = {
|
||||
zoomLimit: {
|
||||
enabled: true,
|
||||
limit: 0.997,
|
||||
fullscreenOnly: true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}, {
|
||||
forVersion: '5.0.4',
|
||||
updateFn: (userOptions, defaultOptions) => {
|
||||
userOptions.sites['www.disneyplus.com'].DOM.player = {
|
||||
... userOptions.sites['www.disneyplus.com'].DOM.player,
|
||||
querySelectors: ".btm-media-client-element",
|
||||
useRelativeAncestor: true,
|
||||
videoAncestor: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -1,16 +1,18 @@
|
||||
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';
|
||||
import CropModePersistence from '../../common/enums/crop-mode-persistence.enum';
|
||||
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
||||
import StretchType from '../../common/enums/StretchType.enum';
|
||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
||||
import AntiGradientMode from '../../common/enums/AntiGradientMode.enum';
|
||||
import AspectRatioType from '../../common/enums/AspectRatioType.enum';
|
||||
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
||||
import SettingsInterface from '../../common/interfaces/SettingsInterface';
|
||||
import BrowserDetect from './BrowserDetect';
|
||||
|
||||
if(Debug.debug)
|
||||
console.log("Loading: ExtensionConf.js");
|
||||
|
||||
var ExtensionConf = {
|
||||
const ExtensionConf: SettingsInterface = {
|
||||
arDetect: {
|
||||
disabledReason: "", // if automatic aspect ratio has been disabled, show reason
|
||||
allowedMisaligned: 0.05, // top and bottom letterbox thickness can differ by this much.
|
||||
@ -176,7 +178,7 @@ var ExtensionConf = {
|
||||
label: 'Automatic', // name displayed in ui (can be overridden in scope/playerUi)
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Automatic,
|
||||
arg: AspectRatioType.Automatic,
|
||||
persistent: false, // optional, false by default. If true, change doesn't take effect immediately.
|
||||
// Instead, this action saves stuff to settings
|
||||
}],
|
||||
@ -211,7 +213,7 @@ var ExtensionConf = {
|
||||
label: 'Reset',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Reset,
|
||||
arg: AspectRatioType.Reset,
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
@ -237,7 +239,7 @@ var ExtensionConf = {
|
||||
label: 'Fit width',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.FitWidth,
|
||||
arg: AspectRatioType.FitWidth,
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
@ -263,7 +265,7 @@ var ExtensionConf = {
|
||||
label: 'Fit height',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.FitHeight
|
||||
arg: AspectRatioType.FitHeight
|
||||
}],
|
||||
scopes: {
|
||||
page: {
|
||||
@ -290,7 +292,7 @@ var ExtensionConf = {
|
||||
label: '16:9',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Fixed,
|
||||
arg: AspectRatioType.Fixed,
|
||||
customArg: 1.78,
|
||||
}],
|
||||
scopes: {
|
||||
@ -318,7 +320,7 @@ var ExtensionConf = {
|
||||
label: '21:9',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Fixed,
|
||||
arg: AspectRatioType.Fixed,
|
||||
customArg: 2.39
|
||||
}],
|
||||
scopes: {
|
||||
@ -346,7 +348,7 @@ var ExtensionConf = {
|
||||
label: '18:9',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatio.Fixed,
|
||||
arg: AspectRatioType.Fixed,
|
||||
customArg: 2.0,
|
||||
}],
|
||||
scopes: {
|
||||
@ -373,7 +375,7 @@ var ExtensionConf = {
|
||||
label: 'Never persist',
|
||||
cmd: [{
|
||||
action: 'set-ar-persistence',
|
||||
arg: CropModePersistence.Never,
|
||||
arg: CropModePersistence.Disabled,
|
||||
}],
|
||||
scopes: {
|
||||
site: {
|
||||
@ -547,7 +549,7 @@ var ExtensionConf = {
|
||||
label: 'Don\'t stretch',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.NoStretch,
|
||||
arg: StretchType.NoStretch,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
@ -572,7 +574,7 @@ var ExtensionConf = {
|
||||
label: 'Basic stretch',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.Basic,
|
||||
arg: StretchType.Basic,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
@ -597,7 +599,7 @@ var ExtensionConf = {
|
||||
label: 'Hybrid stretch',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.Hybrid,
|
||||
arg: StretchType.Hybrid,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
@ -622,7 +624,7 @@ var ExtensionConf = {
|
||||
label: 'Thin borders only',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.Conditional,
|
||||
arg: StretchType.Conditional,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
@ -647,7 +649,7 @@ var ExtensionConf = {
|
||||
label: 'Default',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.Default,
|
||||
arg: StretchType.Default,
|
||||
}],
|
||||
scopes: {
|
||||
site: {
|
||||
@ -660,7 +662,7 @@ var ExtensionConf = {
|
||||
label: '4:3 stretch (src)',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.FixedSource,
|
||||
arg: StretchType.FixedSource,
|
||||
customArg: 1.33,
|
||||
}],
|
||||
scopes: {
|
||||
@ -677,7 +679,7 @@ var ExtensionConf = {
|
||||
label: '16:9 stretch (src)',
|
||||
cmd: [{
|
||||
action: 'set-stretch',
|
||||
arg: Stretch.FixedSource,
|
||||
arg: StretchType.FixedSource,
|
||||
customArg: 1.77,
|
||||
}],
|
||||
scopes: {
|
||||
@ -698,7 +700,7 @@ var ExtensionConf = {
|
||||
label: 'Left',
|
||||
cmd: [{
|
||||
action: 'set-alignment',
|
||||
arg: VideoAlignment.Left,
|
||||
arg: VideoAlignmentType.Left,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
@ -720,7 +722,7 @@ var ExtensionConf = {
|
||||
label: 'Center',
|
||||
cmd: [{
|
||||
action: 'set-alignment',
|
||||
arg: VideoAlignment.Center,
|
||||
arg: VideoAlignmentType.Center,
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
@ -742,7 +744,7 @@ var ExtensionConf = {
|
||||
label: 'Right',
|
||||
cmd: [{
|
||||
action: 'set-alignment',
|
||||
arg: VideoAlignment.Right
|
||||
arg: VideoAlignmentType.Right
|
||||
}],
|
||||
scopes: {
|
||||
global: {
|
||||
@ -764,7 +766,7 @@ var ExtensionConf = {
|
||||
label: 'Default',
|
||||
cmd: [{
|
||||
action: 'set-alignment',
|
||||
arg: VideoAlignment.Default
|
||||
arg: VideoAlignmentType.Default
|
||||
}],
|
||||
scopes: {
|
||||
site: {
|
||||
@ -780,7 +782,7 @@ var ExtensionConf = {
|
||||
name: 'Enable extension',
|
||||
label: 'Enable',
|
||||
cmd: [{
|
||||
action: 'set-extension-mode',
|
||||
action: 'set-ExtensionMode',
|
||||
arg: ExtensionMode.Enabled,
|
||||
persistent: true,
|
||||
}],
|
||||
@ -796,7 +798,7 @@ var ExtensionConf = {
|
||||
name: 'Enable extension on whitelisted sites only',
|
||||
label: 'On whitelist only',
|
||||
cmd: [{
|
||||
action: 'set-extension-mode',
|
||||
action: 'set-ExtensionMode',
|
||||
arg: ExtensionMode.Whitelist,
|
||||
persistent: true,
|
||||
}],
|
||||
@ -809,7 +811,7 @@ var ExtensionConf = {
|
||||
name: 'Extension mode: use default settings',
|
||||
label: 'Default',
|
||||
cmd: [{
|
||||
action: 'set-extension-mode',
|
||||
action: 'set-ExtensionMode',
|
||||
arg: ExtensionMode.Default,
|
||||
persistent: true,
|
||||
}],
|
||||
@ -822,7 +824,7 @@ var ExtensionConf = {
|
||||
name: 'Disable extension',
|
||||
label: 'Disable',
|
||||
cmd: [{
|
||||
action: 'set-extension-mode',
|
||||
action: 'set-ExtensionMode',
|
||||
arg: ExtensionMode.Disabled,
|
||||
persistent: true,
|
||||
}],
|
||||
@ -959,6 +961,13 @@ var ExtensionConf = {
|
||||
}
|
||||
},
|
||||
],
|
||||
mitigations: {
|
||||
zoomLimit: {
|
||||
enabled: true,
|
||||
limit: 0.997,
|
||||
fullscreenOnly: true
|
||||
}
|
||||
},
|
||||
whatsNewChecked: true,
|
||||
// -----------------------------------------
|
||||
// ::: SITE CONFIGURATION :::
|
||||
@ -1006,8 +1015,8 @@ var ExtensionConf = {
|
||||
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
|
||||
stretch: StretchType.NoStretch, // Default stretch mode.
|
||||
videoAlignment: VideoAlignmentType.Center, // Video alignment
|
||||
keyboardShortcutsEnabled: ExtensionMode.Enabled,
|
||||
},
|
||||
"www.youtube.com" : {
|
||||
@ -1017,8 +1026,8 @@ var ExtensionConf = {
|
||||
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,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
DOM: {
|
||||
player: {
|
||||
@ -1035,8 +1044,8 @@ var ExtensionConf = {
|
||||
autoar: ExtensionMode.Enabled,
|
||||
override: false,
|
||||
type: 'official',
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
arPersistence: true, // persist aspect ratio between different videos
|
||||
"DOM": {
|
||||
@ -1054,16 +1063,17 @@ var ExtensionConf = {
|
||||
autoar: ExtensionMode.Enabled,
|
||||
override: false,
|
||||
type: 'community',
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
arPersistence: true, // persist aspect ratio between different videos
|
||||
DOM: {
|
||||
"player": {
|
||||
"manual": true,
|
||||
"querySelectors": ".btn-media-clients",
|
||||
"querySelectors": ".btm-media-client-element",
|
||||
"additionalCss": "",
|
||||
"useRelativeAncestor": false,
|
||||
"videoAncestor": 1,
|
||||
"playerNodeCss": ""
|
||||
}
|
||||
},
|
||||
@ -1074,8 +1084,8 @@ var ExtensionConf = {
|
||||
autoar: ExtensionMode.Enabled,
|
||||
override: true,
|
||||
type: 'official',
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
DOM: {
|
||||
player: {
|
||||
@ -1117,8 +1127,8 @@ var ExtensionConf = {
|
||||
autoar:ExtensionMode.Enabled,
|
||||
override: false,
|
||||
type: 'testing',
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
DOM: {
|
||||
player: {
|
||||
@ -1134,8 +1144,8 @@ var ExtensionConf = {
|
||||
autoar: ExtensionMode.Enabled,
|
||||
override: false,
|
||||
type: 'testing',
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
DOM: {
|
||||
player: {
|
@ -1,20 +1,37 @@
|
||||
import Debug from '../conf/Debug';
|
||||
import PlayerData from './video-data/PlayerData';
|
||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
||||
import Logger from './Logger';
|
||||
import PageInfo from './video-data/PageInfo';
|
||||
import Settings from './Settings';
|
||||
import VideoData from './video-data/VideoData';
|
||||
|
||||
if(process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading ActionHandler");
|
||||
}
|
||||
|
||||
class ActionHandler {
|
||||
logger: Logger;
|
||||
pageInfo: PageInfo;
|
||||
settings: Settings;
|
||||
|
||||
|
||||
inputs: string[] = ['input', 'select', 'button', 'textarea'];
|
||||
keyboardLocalDisabled: boolean = false;
|
||||
|
||||
keyUpActions: any[] = [];
|
||||
keyDownActions: any[] = [];
|
||||
mouseMoveActions: any[] = [];
|
||||
mouseScrollUpActions: any[] = [];
|
||||
mouseScrollDownActions: any[] = [];
|
||||
mouseEnterActions: any[] = [];
|
||||
mouseLeaveActions: any[] = [];
|
||||
|
||||
|
||||
constructor(pageInfo) {
|
||||
this.logger = pageInfo.logger;
|
||||
this.pageInfo = pageInfo;
|
||||
this.settings = pageInfo.settings;
|
||||
|
||||
this.inputs = ['input', 'select', 'button', 'textarea'];
|
||||
this.keyboardLocalDisabled = false;
|
||||
}
|
||||
|
||||
init() {
|
||||
@ -105,7 +122,7 @@ class ActionHandler {
|
||||
}
|
||||
|
||||
// events should be handled in handleEvent function. We need to do things this
|
||||
// way, otherwise we can't remove event listenerđ
|
||||
// way, otherwise we can't remove event listener
|
||||
// https://stackoverflow.com/a/19507086
|
||||
document.addEventListener('keydown', this );
|
||||
document.addEventListener('keyup', this );
|
||||
@ -168,7 +185,6 @@ class ActionHandler {
|
||||
this.logger.resume(); // undisable
|
||||
|
||||
this.logger.log('info', 'keyboard', "[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",
|
||||
@ -186,12 +202,6 @@ class ActionHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
@ -240,7 +250,7 @@ class ActionHandler {
|
||||
this.isActionMatchStandard(shortcut, event) || this.isActionMatchKeyCode(shortcut, event);
|
||||
}
|
||||
|
||||
execAction(actions, event, videoData) {
|
||||
execAction(actions, event, videoData?: VideoData) {
|
||||
this.logger.log('info', 'keyboard', "%c[ActionHandler::execAction] Trying to find and execute action for event. Actions/event: ", "color: #ff0", actions, event);
|
||||
|
||||
const isLatin = event.key ? this.isLatin(event.key) : true;
|
||||
@ -276,9 +286,9 @@ class ActionHandler {
|
||||
} 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;
|
||||
this.settings.active.sites[site].mode = cmd.arg;
|
||||
} else if (cmd.action === "set-autoar-mode") {
|
||||
this.settings.active.sites[site].arStatus = cmd.arg;
|
||||
this.settings.active.sites[site].autoar = cmd.arg;
|
||||
} else if (cmd.action === 'set-keyboard') {
|
||||
this.settings.active.sites[site].keyboardShortcutsEnabled = cmd.arg;
|
||||
} else if (cmd.action === 'set-ar-persistence') {
|
||||
@ -323,9 +333,9 @@ class ActionHandler {
|
||||
this.execAction(this.keyDownActions, event);
|
||||
}
|
||||
|
||||
handleMouseMove(event, videoData) {
|
||||
handleMouseMove(event, videoData?: VideoData) {
|
||||
this.logger.log('info', 'keyboard', "[ActionHandler::handleMouseMove] mouse move is being handled.\nevent:", event, "\nvideo data:", videoData);
|
||||
videoData.panHandler(event);
|
||||
videoData?.panHandler(event);
|
||||
this.execAction(this.mouseMoveActions, event, videoData)
|
||||
}
|
||||
|
@ -1,42 +1,112 @@
|
||||
import currentBrowser from '../conf/BrowserDetect';
|
||||
import { decycle } from 'json-cyclic';
|
||||
import Comms from './comms/Comms';
|
||||
import BrowserDetect from '../conf/BrowserDetect';
|
||||
import { browser } from 'webextension-polyfill-ts';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info('Loading Logger');
|
||||
}
|
||||
|
||||
class Logger {
|
||||
constructor(options) {
|
||||
this.onLogEndCallbacks = [];
|
||||
this.history = [];
|
||||
this.globalHistory = {};
|
||||
this.isContentScript = false;
|
||||
this.isBackgroundScript = true;
|
||||
|
||||
export const baseLoggingOptions: LoggerConfig = {
|
||||
isContentScript: true,
|
||||
allowLogging: false,
|
||||
useConfFromStorage: true,
|
||||
fileOptions: {
|
||||
enabled: false
|
||||
},
|
||||
consoleOptions: {
|
||||
"enabled": false,
|
||||
"debug": true,
|
||||
"init": true,
|
||||
"settings": true,
|
||||
"keyboard": true,
|
||||
"mousemove": false,
|
||||
"actionHandler": true,
|
||||
"comms": true,
|
||||
"playerDetect": true,
|
||||
"resizer": true,
|
||||
"scaler": true,
|
||||
"stretcher": true,
|
||||
// "videoRescan": true,
|
||||
// "playerRescan": true,
|
||||
"arDetect": true,
|
||||
"arDetect_verbose": true
|
||||
},
|
||||
allowBlacklistedOrigins: {
|
||||
'periodicPlayerCheck': false,
|
||||
'periodicVideoStyleChangeCheck': false,
|
||||
'handleMouseMove': false
|
||||
}
|
||||
};
|
||||
|
||||
export interface LoggingOptions {
|
||||
enabled?: boolean;
|
||||
debug?: boolean;
|
||||
init?: boolean;
|
||||
settings?: boolean;
|
||||
keyboard?: boolean;
|
||||
mousemove?: boolean;
|
||||
actionHandler?: boolean;
|
||||
comms?: boolean;
|
||||
playerDetect?: boolean;
|
||||
resizer?: boolean;
|
||||
scaler?: boolean;
|
||||
stretcher?: boolean;
|
||||
videoRescan?: boolean;
|
||||
playerRescan?: boolean;
|
||||
arDetect?: boolean;
|
||||
arDetect_verbose?: boolean;
|
||||
}
|
||||
|
||||
export interface LoggerBlacklistedOrigins {
|
||||
periodicPlayerCheck?: boolean;
|
||||
periodicVideoStyleChangeCheck?: boolean;
|
||||
handleMouseMove?: boolean;
|
||||
}
|
||||
|
||||
export interface LoggerConfig {
|
||||
isContentScript?: boolean;
|
||||
isBackgroundScript?: boolean;
|
||||
allowLogging?: boolean;
|
||||
useConfFromStorage?: boolean;
|
||||
fileOptions?: LoggingOptions;
|
||||
consoleOptions?: LoggingOptions;
|
||||
allowBlacklistedOrigins?: LoggerBlacklistedOrigins;
|
||||
}
|
||||
|
||||
class Logger {
|
||||
temp_disable: boolean = false;
|
||||
onLogEndCallbacks: any[] = [];
|
||||
history: any[] = [];
|
||||
globalHistory: any = {};
|
||||
isContentScript: boolean = false;
|
||||
isBackgroundScript: boolean = true;
|
||||
vuexStore: any;
|
||||
uwInstance: any;
|
||||
conf: any;
|
||||
startTime: number;
|
||||
stopTime: number;
|
||||
|
||||
constructor(options?: {vuexStore?: any, uwInstance?: any}) {
|
||||
this.vuexStore = options?.vuexStore;
|
||||
this.uwInstance = options?.uwInstance;
|
||||
}
|
||||
|
||||
static saveConfig(conf) {
|
||||
static saveConfig(conf: LoggerConfig) {
|
||||
if (process.env.CHANNEL === 'dev') {
|
||||
console.info('Saving logger conf:', conf)
|
||||
}
|
||||
|
||||
if (currentBrowser.firefox || currentBrowser.edge) {
|
||||
return browser.storage.local.set( {'uwLogger': JSON.stringify(conf)});
|
||||
} else if (currentBrowser.chrome) {
|
||||
return chrome.storage.local.set( {'uwLogger': JSON.stringify(conf)});
|
||||
}
|
||||
browser.storage.local.set( {'uwLogger': JSON.stringify(conf)});
|
||||
}
|
||||
|
||||
static syncConfig(callback) {
|
||||
const br = currentBrowser.firefox ? browser : chrome;
|
||||
br.storage.onChanged.addListener( (changes, area) => {
|
||||
static syncConfig(callback: (x) => void) {
|
||||
browser.storage.onChanged.addListener( (changes, area) => {
|
||||
if (changes.uwLogger) {
|
||||
const newLoggerConf = JSON.parse(changes.uwLogger.newValue)
|
||||
if (process.env.CHANNEL === 'dev') {
|
||||
console.info('Logger settings reloaded. New conf:', conf);
|
||||
console.info('Logger settings reloaded. New conf:', newLoggerConf);
|
||||
}
|
||||
callback(newLoggerConf);
|
||||
}
|
||||
@ -46,17 +116,13 @@ class Logger {
|
||||
static async getConfig() {
|
||||
let ret;
|
||||
|
||||
if (currentBrowser.firefox) {
|
||||
// if (BrowserDetect.firefox) {
|
||||
ret = await browser.storage.local.get('uwLogger');
|
||||
} else if (currentBrowser.chrome) {
|
||||
ret = await new Promise( (resolve, reject) => {
|
||||
chrome.storage.local.get('uwLogger', (res) => resolve(res));
|
||||
});
|
||||
} else if (currentBrowser.edge) {
|
||||
ret = await new Promise( (resolve, reject) => {
|
||||
browser.storage.local.get('uwLogger', (res) => resolve(res));
|
||||
});
|
||||
}
|
||||
// } else if (BrowserDetect.anyChromium) {
|
||||
// ret = await new Promise( (resolve, reject) => {
|
||||
// browser.storage.local.get('uwLogger', (res) => resolve(res));
|
||||
// });
|
||||
// }
|
||||
|
||||
if (process.env.CHANNEL === 'dev') {
|
||||
try {
|
||||
@ -73,7 +139,7 @@ class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
async init(conf) {
|
||||
async init(conf: LoggerConfig) {
|
||||
// this is the only property that always gets passed via conf
|
||||
// and doesn't get ignored even if the rest of the conf gets
|
||||
// loaded from browser storage
|
||||
@ -101,9 +167,7 @@ class Logger {
|
||||
this.temp_disable = false;
|
||||
this.stopTime = this.conf.timeout ? performance.now() + (this.conf.timeout * 1000) : undefined;
|
||||
|
||||
const br = currentBrowser.firefox ? browser : chrome;
|
||||
|
||||
br.storage.onChanged.addListener( (changes, area) => {
|
||||
browser.storage.onChanged.addListener( (changes, area) => {
|
||||
if (process.env.CHANNEL === 'dev') {
|
||||
if (!changes.uwLogger) {
|
||||
// console.info('[Logger::<storage/on change> No new logger settings!');
|
||||
@ -131,7 +195,7 @@ class Logger {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.log = [];
|
||||
this.history = [];
|
||||
this.startTime = performance.now();
|
||||
this.stopTime = this.conf.timeout ? performance.now() + (this.conf.timeout * 1000) : undefined;
|
||||
}
|
||||
@ -142,9 +206,9 @@ class Logger {
|
||||
Logger.saveConfig(conf);
|
||||
}
|
||||
|
||||
async getSaved() {
|
||||
return Logger.getSaved();
|
||||
}
|
||||
// async getSaved() {
|
||||
// return Logger.getSaved();
|
||||
// }
|
||||
|
||||
|
||||
// allow syncing of start times between bg and page scripts.
|
||||
@ -174,7 +238,7 @@ class Logger {
|
||||
getFileLogJSONString() {
|
||||
return {
|
||||
site: window && window.location,
|
||||
log: JSON.toString(this.history),
|
||||
log: JSON.stringify(this.history),
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,7 +277,7 @@ class Logger {
|
||||
parseStack() {
|
||||
const trace = (new Error()).stack;
|
||||
|
||||
const stackInfo = {};
|
||||
const stackInfo: any = {};
|
||||
// we turn our stack into array and remove the "file::line" part of the trace,
|
||||
// since that is useless because minification/webpack
|
||||
stackInfo['stack'] = {trace: trace.split('\n').map(a => a.split('@')[0])};
|
||||
@ -336,7 +400,7 @@ class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
canLogConsole(component, stackInfo) {
|
||||
canLogConsole(component, stackInfo?) {
|
||||
if (!this.conf.consoleOptions?.enabled || this.temp_disable) {
|
||||
return false;
|
||||
}
|
||||
@ -437,7 +501,7 @@ class Logger {
|
||||
let ts = performance.now();
|
||||
let secondMark = ts - 1000;
|
||||
let halfSecondMark = ts - 500;
|
||||
let i = this.history.length();
|
||||
let i = this.history.length;
|
||||
|
||||
// correct ts _after_ secondMark and halfSecondMark were determined
|
||||
if (ts <= this.history[this.history.length - 1]) {
|
@ -4,7 +4,7 @@ class ObjectCopy {
|
||||
static addNew(current, newValues){
|
||||
|
||||
// clone target
|
||||
var out = JSON.parse(JSON.stringify(newValues));
|
||||
let out = JSON.parse(JSON.stringify(newValues));
|
||||
|
||||
if(! current) {
|
||||
if(Debug.debug) {
|
||||
@ -14,7 +14,7 @@ class ObjectCopy {
|
||||
return out;
|
||||
}
|
||||
|
||||
for(var k in out) {
|
||||
for(let k in out) {
|
||||
// if current key exist, replace it with existing value. Take no action otherwise.
|
||||
if(current[k]) {
|
||||
|
||||
@ -37,7 +37,7 @@ class ObjectCopy {
|
||||
|
||||
// add the values that would otherwise be deleted back to our object. (We need that so user-defined
|
||||
// sites don't get forgotten)
|
||||
for(var k in current) {
|
||||
for(let k in current) {
|
||||
if (! out[k]) {
|
||||
out[k] = current[k];
|
||||
}
|
||||
@ -47,7 +47,7 @@ class ObjectCopy {
|
||||
}
|
||||
|
||||
static overwrite(current, newValues){
|
||||
for(var k in newValues) {
|
||||
for(let k in newValues) {
|
||||
// if current key exist, replace it with existing value. Take no action otherwise.
|
||||
if (current[k] !== undefined) {
|
||||
// Types and constructors of objects must match. If they don't, we always use the new value.
|
@ -1,19 +1,41 @@
|
||||
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';
|
||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
||||
import ObjectCopy from './ObjectCopy';
|
||||
import StretchType from '../../common/enums/StretchType.enum';
|
||||
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
||||
import ExtensionConfPatch from '../conf/ExtConfPatches';
|
||||
import CropModePersistence from '../../common/enums/crop-mode-persistence.enum';
|
||||
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
||||
import BrowserDetect from '../conf/BrowserDetect';
|
||||
import Logger from './Logger';
|
||||
import SettingsInterface from '../../common/interfaces/SettingsInterface';
|
||||
import { browser } from 'webextension-polyfill-ts';
|
||||
|
||||
if(process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading Settings");
|
||||
}
|
||||
|
||||
|
||||
class Settings {
|
||||
//#region flags
|
||||
useSync: boolean = false;
|
||||
version: string;
|
||||
//#endregion
|
||||
|
||||
//#region helper classes
|
||||
logger: Logger;
|
||||
//#endregion
|
||||
|
||||
//#region data
|
||||
default: SettingsInterface; // default settings
|
||||
active: SettingsInterface; // currently active settings
|
||||
//#endregion
|
||||
|
||||
//#region callbacks
|
||||
onSettingsChanged: any;
|
||||
afterSettingsSaved: any;
|
||||
//#endregion
|
||||
|
||||
constructor(options) {
|
||||
// Options: activeSettings, updateCallback, logger
|
||||
@ -23,14 +45,8 @@ class Settings {
|
||||
this.active = options?.activeSettings ?? undefined;
|
||||
this.default = ExtensionConf;
|
||||
this.default['version'] = this.getExtensionVersion();
|
||||
this.useSync = false;
|
||||
this.version = undefined;
|
||||
|
||||
if (currentBrowser.firefox) {
|
||||
browser.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
|
||||
} else if (currentBrowser.chrome) {
|
||||
chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
|
||||
}
|
||||
browser.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
|
||||
}
|
||||
|
||||
storageChangeListener(changes, area) {
|
||||
@ -60,14 +76,10 @@ class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
static getExtensionVersion() {
|
||||
if (currentBrowser.firefox) {
|
||||
return browser.runtime.getManifest().version;
|
||||
} else if (currentBrowser.chrome) {
|
||||
return chrome.runtime.getManifest().version;
|
||||
}
|
||||
static getExtensionVersion(): string {
|
||||
return browser.runtime.getManifest().version;
|
||||
}
|
||||
getExtensionVersion() {
|
||||
getExtensionVersion(): string {
|
||||
return Settings.getExtensionVersion();
|
||||
}
|
||||
|
||||
@ -96,7 +108,10 @@ class Settings {
|
||||
// also, the fourth digit can start with a letter.
|
||||
// versions that start with a letter are ranked lower than
|
||||
// versions x.x.x.0
|
||||
if (isNaN(+aa[3]) ^ isNaN(+bb[3])) {
|
||||
if (
|
||||
(isNaN(+aa[3]) && !isNaN(+bb[3]))
|
||||
|| (!isNaN(+aa[3]) && isNaN(+bb[3]))
|
||||
) {
|
||||
return isNaN(+aa[3]) ? -1 : 1;
|
||||
}
|
||||
|
||||
@ -140,8 +155,6 @@ class Settings {
|
||||
const sorted = this.sortConfPatches(extconfPatches);
|
||||
return sorted.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
|
||||
}
|
||||
|
||||
|
||||
applySettingsPatches(oldVersion, patches) {
|
||||
let index = this.findFirstNecessaryPatch(oldVersion, patches);
|
||||
if (index === -1) {
|
||||
@ -216,16 +229,6 @@ class Settings {
|
||||
// if there's settings, set saved object as active settings
|
||||
this.active = settings;
|
||||
|
||||
// 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 (this.active.version && !settings.version.startsWith('4')) {
|
||||
this.active = this.getDefaultSettings();
|
||||
this.active.version = this.version;
|
||||
await this.save();
|
||||
return this.active;
|
||||
}
|
||||
|
||||
|
||||
// if version number is undefined, we make it defined
|
||||
// this should only happen on first extension initialization
|
||||
if (!this.active.version) {
|
||||
@ -268,17 +271,7 @@ class Settings {
|
||||
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));
|
||||
});
|
||||
}
|
||||
ret = await browser.storage.local.get('uwSettings');
|
||||
|
||||
this.logger?.log('info', 'settings', 'Got settings:', ret && ret.uwSettings && JSON.parse(ret.uwSettings));
|
||||
|
||||
@ -301,10 +294,10 @@ class Settings {
|
||||
sites[site].autoar = ExtensionMode.Default;
|
||||
}
|
||||
if (sites[site].stretch === undefined) {
|
||||
sites[site].stretch = Stretch.Default;
|
||||
sites[site].stretch = StretchType.Default;
|
||||
}
|
||||
if (sites[site].videoAlignment === undefined) {
|
||||
sites[site].videoAlignment = VideoAlignment.Default;
|
||||
sites[site].videoAlignment = VideoAlignmentType.Default;
|
||||
}
|
||||
if (sites[site].keyboardShortcutsEnabled === undefined) {
|
||||
sites[site].keyboardShortcutsEnabled = ExtensionMode.Default;
|
||||
@ -312,7 +305,7 @@ class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
async set(extensionConf, options) {
|
||||
async set(extensionConf, options?) {
|
||||
if (!options || !options.forcePreserveVersion) {
|
||||
extensionConf.version = this.version;
|
||||
}
|
||||
@ -321,11 +314,7 @@ class Settings {
|
||||
|
||||
this.logger?.log('info', 'settings', "[Settings::set] setting new settings:", extensionConf)
|
||||
|
||||
if (BrowserDetect.firefox) {
|
||||
return browser.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
||||
} else {
|
||||
return chrome.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
||||
}
|
||||
return browser.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
||||
}
|
||||
|
||||
async setActive(activeSettings) {
|
||||
@ -336,7 +325,7 @@ class Settings {
|
||||
this.active[prop] = value;
|
||||
}
|
||||
|
||||
async save(options) {
|
||||
async save(options?) {
|
||||
if (Debug.debug && Debug.storage) {
|
||||
console.log("[Settings::save] Saving active settings:", this.active);
|
||||
}
|
||||
@ -389,7 +378,15 @@ class Settings {
|
||||
return this.active.actions;
|
||||
}
|
||||
|
||||
getExtensionMode(site) {
|
||||
getSettingsForSite(site?) {
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
}
|
||||
|
||||
return this.active.sites[site];
|
||||
}
|
||||
|
||||
getExtensionMode(site?: string) {
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
|
||||
@ -487,14 +484,14 @@ class Settings {
|
||||
}
|
||||
|
||||
extensionEnabled(){
|
||||
return this.active.sites['@global'] !== ExtensionMode.Disabled
|
||||
return this.active.sites['@global'].mode !== ExtensionMode.Disabled
|
||||
}
|
||||
|
||||
extensionEnabledForSite(site) {
|
||||
return this.canStartExtension(site);
|
||||
}
|
||||
|
||||
canStartAutoAr(site) {
|
||||
canStartAutoAr(site?: string) {
|
||||
// 'site' argument is only ever used when calling this function recursively for debugging
|
||||
if (!site) {
|
||||
site = window.location.hostname;
|
||||
@ -539,13 +536,13 @@ class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultOption(option) {
|
||||
getDefaultOption(option?) {
|
||||
const allDefault = {
|
||||
mode: ExtensionMode.Default,
|
||||
autoar: ExtensionMode.Default,
|
||||
autoarFallback: ExtensionMode.Default,
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
};
|
||||
|
||||
if (!option || allDefault[option] === undefined) {
|
||||
@ -565,7 +562,7 @@ class Settings {
|
||||
}
|
||||
|
||||
getDefaultStretchMode(site) {
|
||||
if (site && (this.active.sites[site]?.stretch ?? Stretch.Default) !== Stretch.Default) {
|
||||
if (site && (this.active.sites[site]?.stretch ?? StretchType.Default) !== StretchType.Default) {
|
||||
return this.active.sites[site].stretch;
|
||||
}
|
||||
|
||||
@ -573,7 +570,7 @@ class Settings {
|
||||
}
|
||||
|
||||
getDefaultCropPersistenceMode(site) {
|
||||
if (site && (this.active.sites[site]?.cropModePersistence ?? Stretch.Default) !== Stretch.Default) {
|
||||
if (site && (this.active.sites[site]?.cropModePersistence ?? StretchType.Default) !== StretchType.Default) {
|
||||
return this.active.sites[site].cropModePersistence;
|
||||
}
|
||||
|
||||
@ -582,7 +579,7 @@ class Settings {
|
||||
}
|
||||
|
||||
getDefaultVideoAlignment(site) {
|
||||
if ( (this.active.sites[site]?.videoAlignment ?? VideoAlignment.Default) !== VideoAlignment.Default) {
|
||||
if ( (this.active.sites[site]?.videoAlignment ?? VideoAlignmentType.Default) !== VideoAlignmentType.Default) {
|
||||
return this.active.sites[site].videoAlignment;
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
export async function sleep(timeout) {
|
||||
return new Promise( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
export async function sleep(timeout) {
|
||||
return new Promise( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
||||
return new Promise( (resolve, reject) => setTimeout(() => resolve(null), timeout));
|
||||
}
|
@ -6,12 +6,56 @@ import EdgeDetectPrimaryDirection from './edge-detect/enums/EdgeDetectPrimaryDir
|
||||
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';
|
||||
import {sleep} from '../../lib/Util';
|
||||
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import {sleep} from '../Util';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import Logger from '../Logger';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import Settings from '../Settings';
|
||||
|
||||
class ArDetector {
|
||||
logger: Logger;
|
||||
conf: VideoData;
|
||||
video: HTMLVideoElement;
|
||||
settings: Settings;
|
||||
|
||||
guardLine: GuardLine;
|
||||
edgeDetector: EdgeDetect;
|
||||
|
||||
setupTimer: any;
|
||||
sampleCols: any[];
|
||||
sampleLines
|
||||
|
||||
canFallback: boolean = true;
|
||||
fallbackMode: boolean = false;
|
||||
|
||||
blackLevel: number;
|
||||
|
||||
arid: string;
|
||||
|
||||
// ar detector starts in this state. running main() sets both to false
|
||||
_paused: boolean;
|
||||
_halted: boolean = true;
|
||||
_exited: boolean = true;
|
||||
|
||||
private manualTickEnabled: boolean;
|
||||
_nextTick: boolean;
|
||||
canDoFallbackMode: boolean = false;
|
||||
|
||||
// helper objects
|
||||
private attachedCanvas: HTMLCanvasElement;
|
||||
canvas: HTMLCanvasElement;
|
||||
private blackframeCanvas: HTMLCanvasElement;
|
||||
private context: CanvasRenderingContext2D;
|
||||
private blackframeContext: CanvasRenderingContext2D;
|
||||
private canvasScaleFactor: number;
|
||||
private detectionTimeoutEventCount: number;
|
||||
canvasImageDataRowLength: number;
|
||||
private noLetterboxCanvasReset: boolean;
|
||||
private detectedAr: any;
|
||||
private canvasDrawWindowHOffset: number;
|
||||
private sampleCols_current: number;
|
||||
|
||||
constructor(videoData){
|
||||
this.logger = videoData.logger;
|
||||
@ -19,34 +63,18 @@ class ArDetector {
|
||||
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;
|
||||
|
||||
this.drmNotificationShown = false;
|
||||
|
||||
this.logger.log('info', 'init', `[ArDetector::ctor] creating new ArDetector. arid: ${this.arid}`);
|
||||
}
|
||||
|
||||
setManualTick(manualTick) {
|
||||
this._manualTicks = manualTick;
|
||||
this.manualTickEnabled = manualTick;
|
||||
}
|
||||
|
||||
tick() {
|
||||
@ -68,19 +96,19 @@ class ArDetector {
|
||||
}
|
||||
|
||||
destroy(){
|
||||
this.logger.log('info', 'init', `%c[ArDetect::destroy] <@${this.arid}> Destroying aard.`, _ard_console_stop, e);
|
||||
this.logger.log('info', 'init', `%c[ArDetect::destroy] <@${this.arid}> Destroying aard.`, _ard_console_stop);
|
||||
// this.debugCanvas.destroy();
|
||||
this.stop();
|
||||
}
|
||||
|
||||
setup(cwidth, cheight){
|
||||
setup(cwidth?: number, cheight?: number){
|
||||
this.logger.log('info', 'init', `[ArDetect::setup] <@${this.arid}> Starting autodetection setup.`);
|
||||
//
|
||||
// [-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 (this.video.videoWidth === 0 || this.video.videoHeight === 0 ){
|
||||
this.logger.log('warn', 'debug', `[ArDetect::setup] <@${this.arid}> This video has zero width or zero height. Dimensions: ${this.video.videoWidth} × ${this.video.videoHeight}`);
|
||||
|
||||
this.scheduleInitRestart();
|
||||
@ -133,16 +161,16 @@ class ArDetector {
|
||||
// [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;
|
||||
let ncol = this.settings.active.arDetect.sampling.staticCols;
|
||||
let nrow = this.settings.active.arDetect.sampling.staticRows;
|
||||
|
||||
var colSpacing = this.canvas.width / ncol;
|
||||
var rowSpacing = (this.canvas.height << 2) / nrow;
|
||||
let colSpacing = this.canvas.width / ncol;
|
||||
let rowSpacing = (this.canvas.height << 2) / nrow;
|
||||
|
||||
this.sampleLines = [];
|
||||
this.sampleCols = [];
|
||||
|
||||
for(var i = 0; i < ncol; i++){
|
||||
for(let i = 0; i < ncol; i++){
|
||||
if(i < ncol - 1)
|
||||
this.sampleCols.push(Math.round(colSpacing * i));
|
||||
else{
|
||||
@ -150,7 +178,7 @@ class ArDetector {
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < nrow; i++){
|
||||
for(let i = 0; i < nrow; i++){
|
||||
if(i < ncol - 5)
|
||||
this.sampleLines.push(Math.round(rowSpacing * i));
|
||||
else{
|
||||
@ -173,7 +201,7 @@ class ArDetector {
|
||||
//
|
||||
|
||||
try {
|
||||
this.blackframeContext.drawWindow(window,0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height, "rgba(0,0,128,1)");
|
||||
(this.blackframeContext as any).drawWindow(window,0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height, "rgba(0,0,128,1)");
|
||||
this.canDoFallbackMode = true;
|
||||
} catch (e) {
|
||||
this.canDoFallbackMode = false;
|
||||
@ -187,7 +215,7 @@ class ArDetector {
|
||||
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: this.getDefaultAr()});
|
||||
this.conf.resizer.setLastAr({type: AspectRatioType.Automatic, ratio: this.getDefaultAr()});
|
||||
|
||||
this.canvasImageDataRowLength = cwidth << 2;
|
||||
this.noLetterboxCanvasReset = false;
|
||||
@ -212,9 +240,9 @@ class ArDetector {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.conf.resizer.lastAr.type === AspectRatio.Automatic) {
|
||||
if (this.conf.resizer.lastAr.type === AspectRatioType.Automatic) {
|
||||
// ensure first autodetection will run in any case
|
||||
this.conf.resizer.setLastAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
||||
this.conf.resizer.setLastAr({type: AspectRatioType.Automatic, ratio: this.getDefaultAr()});
|
||||
}
|
||||
|
||||
|
||||
@ -292,7 +320,7 @@ class ArDetector {
|
||||
// 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) {
|
||||
if ( (!this.manualTickEnabled && this.canTriggerFrameCheck(lastFrameCheckStartTime)) || this._nextTick) {
|
||||
this._nextTick = false;
|
||||
|
||||
lastFrameCheckStartTime = Date.now();
|
||||
@ -344,7 +372,7 @@ class ArDetector {
|
||||
}
|
||||
|
||||
|
||||
scheduleInitRestart(timeout, force_reset){
|
||||
scheduleInitRestart(timeout?: number, force_reset?: boolean){
|
||||
if(! timeout){
|
||||
timeout = 100;
|
||||
}
|
||||
@ -353,7 +381,7 @@ class ArDetector {
|
||||
clearTimeout(this.setupTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
let ths = this;
|
||||
this.setupTimer = setTimeout(function(){
|
||||
ths.setupTimer = null;
|
||||
try{
|
||||
@ -391,7 +419,7 @@ class ArDetector {
|
||||
}
|
||||
|
||||
getTimeout(baseTimeout, startTime){
|
||||
var execTime = (performance.now() - startTime);
|
||||
let execTime = (performance.now() - startTime);
|
||||
|
||||
return baseTimeout;
|
||||
}
|
||||
@ -447,12 +475,12 @@ class ArDetector {
|
||||
// letterbox also needs to be corrected:
|
||||
// letterbox += [video.zoomedHeight] - [video.unzoomedHeight]
|
||||
|
||||
var vbr = this.video.getBoundingClientRect();
|
||||
let vbr = this.video.getBoundingClientRect();
|
||||
|
||||
zoomFactor = vbr.height / this.video.clientHeight;
|
||||
letterbox += vbr.height - this.video.clientHeight;
|
||||
|
||||
var trueHeight = this.canvas.height * zoomFactor - letterbox;
|
||||
let trueHeight = this.canvas.height * zoomFactor - letterbox;
|
||||
|
||||
if(edges.top > 1 && edges.top <= this.settings.active.arDetect.fallbackMode.noTriggerZonePx ){
|
||||
this.logger.log('info', 'arDetect', `%c[ArDetect::calculateArFromEdges] <@${this.arid}> Edge is in the no-trigger zone. Aspect ratio change is not triggered.`)
|
||||
@ -474,23 +502,23 @@ class ArDetector {
|
||||
|
||||
// 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 && lastAr.ratio !== undefined){
|
||||
let lastAr = this.conf.resizer.getLastAr();
|
||||
if (lastAr.type === AspectRatioType.Automatic && lastAr.ratio !== null && lastAr.ratio !== undefined){
|
||||
// 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;
|
||||
let arDiff = trueAr - lastAr.ratio;
|
||||
|
||||
if (arDiff < 0)
|
||||
arDiff = -arDiff;
|
||||
|
||||
var arDiff_percent = arDiff / trueAr;
|
||||
const arDiff_percent = arDiff / trueAr;
|
||||
|
||||
// ali je sprememba v mejah dovoljenega? Če da -> fertik
|
||||
// is ar variance within acceptable levels? If yes -> we done
|
||||
this.logger.log('info', 'arDetect', `%c[ArDetect::processAr] <@${this.arid}> 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);
|
||||
this.logger.log('info', 'arDetect', `%c[ArDetect::processAr] <@${this.arid}> New aspect ratio varies from the old one by this much:\n`,"color: #aaf","old Ar", lastAr.ratio, "current ar", trueAr, "arDiff (absolute):",arDiff,"ar diff (relative to new ar)", arDiff_percent);
|
||||
|
||||
if (arDiff < trueAr * this.settings.active.arDetect.allowedArVariance){
|
||||
this.logger.log('info', 'arDetect', `%c[ArDetect::processAr] <@${this.arid}> Aspect ratio change denied — diff %: ${arDiff_percent}`, "background: #740; color: #fa2");
|
||||
@ -500,35 +528,16 @@ class ArDetector {
|
||||
}
|
||||
this.logger.log('info', 'debug', `%c[ArDetect::processAr] <@${this.arid}> Triggering aspect ratio change. New aspect ratio: ${trueAr}`, _ard_console_change);
|
||||
|
||||
this.conf.resizer.updateAr({type: AspectRatio.Automatic, ratio: trueAr}, {type: AspectRatio.Automatic, ratio: trueAr});
|
||||
this.conf.resizer.updateAr({type: AspectRatioType.Automatic, ratio: trueAr});
|
||||
}
|
||||
|
||||
clearImageData(id) {
|
||||
if (ArrayBuffer.transfer) {
|
||||
ArrayBuffer.transfer(id, 0);
|
||||
if ((ArrayBuffer as any).transfer) {
|
||||
(ArrayBuffer as any).transfer(id, 0);
|
||||
}
|
||||
id = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether video we're trying to play is protected by DRM.
|
||||
*
|
||||
* When trying to get an image frame of a DRM-protected video in
|
||||
* firefox, the method canvas.drawImage(video) will throw an exception.
|
||||
*
|
||||
* This doesn't happen in Chrome. As opposed to Firefox, chrome will
|
||||
* simply draw a transparent black image and not tell anyone that
|
||||
* anything is amiss. However, since the image is (according to my testing
|
||||
* on netflix) completely transparent, this means we can determine whether
|
||||
* the video is DRM-protected by looking at the alpha byte of the image.
|
||||
*
|
||||
* (Videos don't tend to have an alpha channel, so they're always
|
||||
* completely opaque (i.e. have value of 255))
|
||||
*/
|
||||
hasDRM() {
|
||||
return this.blackframeContext.getImageData(0,0,1,1).data[3] === 0;
|
||||
}
|
||||
|
||||
frameCheck(){
|
||||
if(! this.video){
|
||||
this.logger.log('error', 'debug', `%c[ArDetect::frameCheck] <@${this.arid}> Video went missing. Destroying current instance of videoData.`);
|
||||
@ -540,7 +549,7 @@ class ArDetector {
|
||||
this.init();
|
||||
}
|
||||
|
||||
var startTime = performance.now();
|
||||
let startTime = performance.now();
|
||||
let sampleCols = this.sampleCols.slice(0);
|
||||
|
||||
//
|
||||
@ -550,31 +559,29 @@ class ArDetector {
|
||||
this.blackframeContext.drawImage(this.video, 0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height);
|
||||
|
||||
// special browsers require special tests
|
||||
if (this.hasDRM()) {
|
||||
this.fallbackMode = false;
|
||||
throw 'Video is protected by DRM. Autodetection cannot run here.';
|
||||
}
|
||||
// if (this.hasDRM()) {
|
||||
// this.fallbackMode = false;
|
||||
// throw 'VIDEO_DRM_PROTECTED';
|
||||
// }
|
||||
this.fallbackMode = false;
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'arDetect', `%c[ArDetect::frameCheck] <@${this.arid}> %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.drmNotificationShown) {
|
||||
this.drmNotificationShown = true;
|
||||
|
||||
// if we detect Edge, we'll throw the aggressive popup
|
||||
this.conf.player.showEdgeNotification();
|
||||
this.conf.player.showNotification('AARD_DRM');
|
||||
|
||||
this.conf.resizer.setAr({type: AspectRatio.Reset});
|
||||
return;
|
||||
}
|
||||
|
||||
// in case of DRM errors, we need to prevent the execution to reach the aspec
|
||||
// ratio setting part of this function
|
||||
if (e === 'Video is protected by DRM. Autodetection cannot run here.') {
|
||||
return;
|
||||
}
|
||||
// ratio setting part of this function. For the time being, we're only stopping
|
||||
// in case we encounter DRM error in Chrome. Firefox has fallback mode and generates
|
||||
// different error, so that goes.
|
||||
// if (e === 'VIDEO_DRM_PROTECTED') {
|
||||
// // nothing to see here, really, if fallback mode isn't supported by browser
|
||||
// if (!this.drmNotificationShown) {
|
||||
// this.drmNotificationShown = true;
|
||||
|
||||
// this.conf.player.showNotification('AARD_DRM');
|
||||
// this.conf.resizer.setAr({type: AspectRatio.Reset});
|
||||
// }
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (! this.canvasReadyForDrawWindow()) {
|
||||
// this means canvas needs to be resized, so we'll just re-run setup with all those new parameters
|
||||
@ -583,9 +590,9 @@ class ArDetector {
|
||||
let newCanvasWidth = window.innerHeight * (this.video.videoWidth / this.video.videoHeight);
|
||||
let newCanvasHeight = window.innerHeight;
|
||||
|
||||
if (this.conf.resizer.videoAlignment === VideoAlignment.Center) {
|
||||
if (this.conf.resizer.videoAlignment === VideoAlignmentType.Center) {
|
||||
this.canvasDrawWindowHOffset = Math.round((window.innerWidth - newCanvasWidth) * 0.5);
|
||||
} else if (this.conf.resizer.videoAlignment === VideoAlignment.Left) {
|
||||
} else if (this.conf.resizer.videoAlignment === VideoAlignmentType.Left) {
|
||||
this.canvasDrawWindowHOffset = 0;
|
||||
} else {
|
||||
this.canvasDrawWindowHOffset = window.innerWidth - newCanvasWidth;
|
||||
@ -600,7 +607,7 @@ class ArDetector {
|
||||
this.fallbackMode = true;
|
||||
|
||||
try {
|
||||
this.context.drawWindow(window, this.canvasDrawWindowHOffset, 0, this.canvas.width, this.canvas.height, "rgba(0,0,128,1)");
|
||||
(this.context as any).drawWindow(window, this.canvasDrawWindowHOffset, 0, this.canvas.width, this.canvas.height, "rgba(0,0,128,1)");
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'arDetect', `%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
|
||||
@ -632,7 +639,7 @@ class ArDetector {
|
||||
// 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.updateAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
||||
this.conf.resizer.updateAr({type: AspectRatioType.Automatic, ratio: this.getDefaultAr()});
|
||||
this.guardLine.reset();
|
||||
this.noLetterboxCanvasReset = true;
|
||||
|
||||
@ -665,14 +672,16 @@ class ArDetector {
|
||||
// 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);
|
||||
sampleCols.concat(guardLineOut.offenders).sort(
|
||||
(a: number, b: number) => 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.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
||||
this.conf.resizer.setAr({type: AspectRatioType.Automatic, ratio: this.getDefaultAr()});
|
||||
this.guardLine.reset();
|
||||
this.noLetterboxCanvasReset = true;
|
||||
|
||||
@ -692,17 +701,15 @@ class ArDetector {
|
||||
// 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(this.edgeDetector.findBars(imageData, null, EdgeDetectPrimaryDirection.Horizontal).status === 'ar_known'){
|
||||
|
||||
|
||||
if(guardLineOut.blackbarFail){
|
||||
this.logger.log('info', 'arDetect', `[ArDetect::frameCheck] Detected blackbar violation and pillarbox. Resetting to default aspect ratio.`);
|
||||
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
||||
this.conf.resizer.setAr({type: AspectRatioType.Automatic, ratio: this.getDefaultAr()});
|
||||
this.guardLine.reset();
|
||||
}
|
||||
|
||||
triggerTimeout = this.getTimeout(baseTimeout, startTime);
|
||||
this.scheduleFrameCheck(triggerTimeout);
|
||||
this.clearImageData(imageData);
|
||||
return;
|
||||
}
|
||||
@ -717,20 +724,20 @@ class ArDetector {
|
||||
|
||||
// blackSamples -> {res_top, res_bottom}
|
||||
|
||||
var edgePost = this.edgeDetector.findBars(imageData, sampleCols, EdgeDetectPrimaryDirection.VERTICAL, EdgeDetectQuality.IMPROVED, guardLineOut, bfanalysis);
|
||||
let edgePost = this.edgeDetector.findBars(imageData, sampleCols, EdgeDetectPrimaryDirection.Vertical, EdgeDetectQuality.Improved, guardLineOut, bfanalysis);
|
||||
|
||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] edgeDetector returned this\n`, "color: #aaf", edgePost);
|
||||
|
||||
if (edgePost.status !== EdgeStatus.AR_KNOWN){
|
||||
if (edgePost.status !== EdgeStatus.ARKnown){
|
||||
// rob ni bil zaznan, zato ne naredimo ničesar.
|
||||
// no edge was detected. Let's leave things as they were
|
||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Edge wasn't detected with findBars`, "color: #fa3", edgePost, "EdgeStatus.AR_KNOWN:", EdgeStatus.AR_KNOWN);
|
||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Edge wasn't detected with findBars`, "color: #fa3", edgePost, "EdgeStatus.AR_KNOWN:", EdgeStatus.ARKnown);
|
||||
|
||||
this.clearImageData(imageData);
|
||||
return;
|
||||
}
|
||||
|
||||
var newAr = this.calculateArFromEdges(edgePost);
|
||||
let newAr = this.calculateArFromEdges(edgePost);
|
||||
|
||||
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Triggering aspect ration change! new ar: ${newAr}`, "color: #aaf");
|
||||
|
||||
@ -762,14 +769,14 @@ class ArDetector {
|
||||
this.logger.log('error', 'arDetect', `%c[ArDetect::frameCheck] There was a problem setting blackbar. Doing nothing. Error:`, e);
|
||||
|
||||
try {
|
||||
this.guardline.reset();
|
||||
this.guardLine.reset();
|
||||
} catch (e) {
|
||||
// no guardline, no bigge
|
||||
}
|
||||
// WE DO NOT RESET ASPECT RATIO HERE IN CASE OF PROBLEMS, CAUSES UNWARRANTED RESETS:
|
||||
// (eg. here: https://www.youtube.com/watch?v=nw5Z93Yt-UQ&t=410)
|
||||
//
|
||||
// this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
|
||||
// this.conf.resizer.setAr({type: AspectRatioType.Automatic, ratio: this.getDefaultAr()});
|
||||
}
|
||||
|
||||
this.clearImageData(imageData);
|
||||
@ -801,7 +808,7 @@ class ArDetector {
|
||||
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;
|
||||
const blackTreshold = this.blackLevel + this.settings.active.arDetect.blackbar.frameThreshold;
|
||||
|
||||
|
||||
// we do some recon for letterbox and pillarbox. While this can't determine whether letterbox/pillarbox exists
|
||||
@ -809,7 +816,7 @@ class ArDetector {
|
||||
let rowMax = new Array(rows).fill(0);
|
||||
let colMax = new Array(cols).fill(0);
|
||||
|
||||
let r, c;
|
||||
let r: number, c: number;
|
||||
|
||||
|
||||
for (let i = 0; i < bfImageData.length; i+= 4) {
|
||||
@ -923,7 +930,7 @@ class ArDetector {
|
||||
|
||||
// detect black level. if currentMax comes above blackbar + blackbar threshold, we know we aren't letterboxed
|
||||
|
||||
for (var i = 0; i < sampleCols.length; ++i){
|
||||
for (let i = 0; i < sampleCols.length; ++i){
|
||||
colOffset_r = sampleCols[i] << 2;
|
||||
colOffset_g = colOffset_r + 1;
|
||||
colOffset_b = colOffset_r + 2;
|
||||
@ -959,8 +966,8 @@ class ArDetector {
|
||||
|
||||
}
|
||||
|
||||
var _ard_console_stop = "background: #000; color: #f41";
|
||||
var _ard_console_start = "background: #000; color: #00c399";
|
||||
var _ard_console_change = "background: #000; color: #ff8";
|
||||
let _ard_console_stop = "background: #000; color: #f41";
|
||||
let _ard_console_start = "background: #000; color: #00c399";
|
||||
let _ard_console_change = "background: #000; color: #ff8";
|
||||
|
||||
export default ArDetector;
|
12
src/ext/lib/ar-detect/DrmDetecor.ts
Normal file
12
src/ext/lib/ar-detect/DrmDetecor.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Checks whether video we're trying to play is protected by DRM.
|
||||
* @param {*} video video we're trying to check
|
||||
*/
|
||||
export function hasDrm(video) {
|
||||
// if video is not playing, we cannot know whether autodetection will work or not
|
||||
if (!video || !(video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return video.mediaKeys instanceof MediaKeys;
|
||||
}
|
@ -1,13 +1,33 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import Settings from '../Settings';
|
||||
import ArDetector from './ArDetector';
|
||||
|
||||
export type GuardLineBar = {
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
}
|
||||
|
||||
export type ImageCheckResult = {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
class GuardLine {
|
||||
|
||||
blackbar: GuardLineBar;
|
||||
imageBar: GuardLineBar;
|
||||
|
||||
aard: ArDetector;
|
||||
settings: Settings;
|
||||
|
||||
blackbarThreshold: number;
|
||||
imageThreshold: number;
|
||||
|
||||
|
||||
// ardConf — reference to ArDetector that has current GuardLine instance
|
||||
constructor(ardConf){
|
||||
constructor (ardConf){
|
||||
this.blackbar = {top: undefined, bottom: undefined};
|
||||
this.imageBar = {top: undefined, bottom: undefined};
|
||||
|
||||
this.conf = ardConf;
|
||||
this.aard = ardConf;
|
||||
this.settings = ardConf.settings;
|
||||
}
|
||||
|
||||
@ -28,12 +48,12 @@ class GuardLine {
|
||||
}
|
||||
|
||||
setBlackbar(bbconf){
|
||||
var bbTop = bbconf.top - this.settings.active.arDetect.guardLine.edgeTolerancePx;
|
||||
var bbBottom = bbconf.bottom + this.settings.active.arDetect.guardLine.edgeTolerancePx;
|
||||
let bbTop = bbconf.top - this.settings.active.arDetect.guardLine.edgeTolerancePx;
|
||||
let bbBottom = bbconf.bottom + this.settings.active.arDetect.guardLine.edgeTolerancePx;
|
||||
|
||||
// to odstrani vse neveljavne nastavitve in vse možnosti, ki niso smiselne
|
||||
// this removes any configs with invalid values or values that dont make sense
|
||||
if (bbTop < 0 || bbBottom >= this.conf.canvas.height ){
|
||||
if (bbTop < 0 || bbBottom >= this.aard.canvas.height ){
|
||||
throw {error: "INVALID_SETTINGS_IN_GUARDLINE", bbTop, bbBottom}
|
||||
}
|
||||
|
||||
@ -51,12 +71,12 @@ class GuardLine {
|
||||
check(image, fallbackMode){
|
||||
// izračunaj enkrat in shrani na objekt
|
||||
// calculate once and save object-instance-wide
|
||||
this.blackbarThreshold = this.conf.blackLevel + this.settings.active.arDetect.blackbar.threshold;
|
||||
this.blackbarThreshold = this.aard.blackLevel + this.settings.active.arDetect.blackbar.threshold;
|
||||
this.imageThreshold = this.blackbarThreshold + this.settings.active.arDetect.blackbar.imageThreshold;
|
||||
|
||||
// dejansko testiranje
|
||||
// actual checks
|
||||
var guardLineResult = this.guardLineCheck(image, fallbackMode);
|
||||
let guardLineResult = this.guardLineCheck(image, fallbackMode);
|
||||
|
||||
// Zaznali smo kršitev črnega dela, zato nam ni treba preveriti, ali je slika
|
||||
// prisotna. Vemo namreč, da se je razmerje stranic zmanjšalo.
|
||||
@ -71,7 +91,7 @@ class GuardLine {
|
||||
}
|
||||
}
|
||||
|
||||
var imageCheckResult = this.imageCheck(image, fallbackMode);
|
||||
let imageCheckResult = this.imageCheck(image, fallbackMode);
|
||||
|
||||
return {
|
||||
blackbarFail: false,
|
||||
@ -95,45 +115,46 @@ class GuardLine {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
var offset = parseInt(this.conf.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
||||
let offset = (this.aard.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
||||
|
||||
var offenders = [];
|
||||
var offenderCount = -1; // doing it this way means first offender has offenderCount==0. Ez index.
|
||||
let offenders = [];
|
||||
let offenderCount = -1; // doing it this way means first offender has offenderCount==0. Ez index.
|
||||
|
||||
// TODO: implement logo check.
|
||||
|
||||
// preglejmo obe vrstici
|
||||
// check both rows
|
||||
|
||||
let edge_lower, edge_upper;
|
||||
|
||||
if(! fallbackMode){
|
||||
var edge_upper = this.blackbar.top;
|
||||
var edge_lower = this.blackbar.bottom;
|
||||
edge_upper = this.blackbar.top;
|
||||
edge_lower = this.blackbar.bottom;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
// fallback mode is a bit different
|
||||
edge_upper = 0;
|
||||
edge_lower = this.conf.canvas.height - 1;
|
||||
edge_lower = this.aard.canvas.height - 1;
|
||||
}
|
||||
|
||||
var rowStart, rowEnd;
|
||||
let rowStart, rowEnd;
|
||||
|
||||
// <<<=======| checking upper row |========>>>
|
||||
|
||||
rowStart = ((edge_upper * this.conf.canvas.width) << 2) + offset;
|
||||
rowEnd = rowStart + ( this.conf.canvas.width << 2 ) - (offset * 2);
|
||||
rowStart = ((edge_upper * this.aard.canvas.width) << 2) + offset;
|
||||
rowEnd = rowStart + ( this.aard.canvas.width << 2 ) - (offset * 2);
|
||||
|
||||
if (Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine) {
|
||||
offenderCount = this._gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
||||
// offenderCount = this._gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
||||
} else {
|
||||
offenderCount = this._gl_rowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
||||
}
|
||||
// <<<=======| checking lower row |========>>>
|
||||
|
||||
rowStart = ((edge_lower * this.conf.canvas.width) << 2) + offset;
|
||||
rowEnd = rowStart + ( this.conf.canvas.width << 2 ) - (offset * 2);
|
||||
rowStart = ((edge_lower * this.aard.canvas.width) << 2) + offset;
|
||||
rowEnd = rowStart + ( this.aard.canvas.width << 2 ) - (offset * 2);
|
||||
|
||||
if (Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine) {
|
||||
offenderCount = this._gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
||||
// offenderCount = this._gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
||||
} else {
|
||||
offenderCount = this._gl_rowCheck(image, rowStart, rowEnd, offenders, offenderCount);
|
||||
}
|
||||
@ -148,19 +169,19 @@ class GuardLine {
|
||||
return {success: true};
|
||||
}
|
||||
|
||||
var ret = new Array(offenders.length);
|
||||
for(var o in offenders){
|
||||
let ret = new Array(offenders.length);
|
||||
for(let o in offenders){
|
||||
ret[o] = offenders[o].x + (offenders[o].width * 0.25);
|
||||
}
|
||||
|
||||
return {success: false, offenders: ret};
|
||||
}
|
||||
|
||||
imageCheck(image){
|
||||
imageCheck(image, fallbackMode?: boolean): ImageCheckResult {
|
||||
if(!this.imageBar.top || !this.imageBar.bottom)
|
||||
return { success: false };
|
||||
|
||||
var offset = (this.conf.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
||||
let offset = (this.aard.canvas.width * this.settings.active.arDetect.guardLine.ignoreEdgeMargin) << 2;
|
||||
|
||||
// TODO: implement logo check.
|
||||
|
||||
@ -169,47 +190,47 @@ class GuardLine {
|
||||
// check both rows - by the rules and definitions, we shouldn't go out of bounds here. no need to check, then
|
||||
|
||||
// if(fallbackMode){
|
||||
// var edge_upper = this.settings.active.arDetect.fallbackMode.noTriggerZonePx;
|
||||
// var edge_lower = this.conf.canvas.height - this.settings.active.arDetect.fallbackMode.noTriggerZonePx - 1;
|
||||
// let edge_upper = this.settings.active.arDetect.fallbackMode.noTriggerZonePx;
|
||||
// let edge_lower = this.conf.canvas.height - this.settings.active.arDetect.fallbackMode.noTriggerZonePx - 1;
|
||||
// }
|
||||
// else{
|
||||
var edge_upper = this.imageBar.top;
|
||||
var edge_lower = this.imageBar.bottom;
|
||||
let edge_upper = this.imageBar.top;
|
||||
let edge_lower = this.imageBar.bottom;
|
||||
// }
|
||||
|
||||
// koliko pikslov rabimo zaznati, da je ta funkcija uspe. Tu dovoljujemo tudi, da so vsi piksli na enem
|
||||
// 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 successThreshold = (this.conf.canvas.width * this.settings.active.arDetect.guardLine.imageTestThreshold);
|
||||
var rowStart, rowEnd;
|
||||
let successThreshold = (this.aard.canvas.width * this.settings.active.arDetect.guardLine.imageTestThreshold);
|
||||
let rowStart, rowEnd;
|
||||
|
||||
|
||||
// <<<=======| checking upper row |========>>>
|
||||
|
||||
rowStart = ((edge_upper * this.conf.canvas.width) << 2) + offset;
|
||||
rowEnd = rowStart + ( this.conf.canvas.width << 2 ) - (offset * 2);
|
||||
rowStart = ((edge_upper * this.aard.canvas.width) << 2) + offset;
|
||||
rowEnd = rowStart + ( this.aard.canvas.width << 2 ) - (offset * 2);
|
||||
|
||||
var res = false;
|
||||
let res = false;
|
||||
|
||||
if(Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine){
|
||||
res = this._ti_debugCheckRow(image, rowStart, rowEnd, successThreshold);
|
||||
// res = this._ti_debugCheckRow(image, rowStart, rowEnd, successThreshold);
|
||||
} else {
|
||||
res = this._ti_checkRow(image, rowStart, rowEnd,successThreshold);
|
||||
}
|
||||
|
||||
if(res)
|
||||
if (res) {
|
||||
return {success: true};
|
||||
|
||||
}
|
||||
|
||||
// <<<=======| checking lower row |========>>>
|
||||
|
||||
rowStart = ((edge_lower * this.conf.canvas.width) << 2) + offset;
|
||||
rowStart = ((edge_lower * this.aard.canvas.width) << 2) + offset;
|
||||
// rowEnd = rowStart + ( this.conf.canvas.width << 2 ) - (offset * 2);
|
||||
|
||||
|
||||
if(Debug.debugCanvas.enabled && Debug.debugCanvas.guardLine){
|
||||
res = this._ti_debugCheckRow(image, rowStart, rowEnd, successThreshold);
|
||||
// res = this._ti_debugCheckRow(image, rowStart, rowEnd, successThreshold);
|
||||
} else {
|
||||
res = this._ti_checkRow(image, rowStart, rowEnd,successThreshold);
|
||||
}
|
||||
@ -222,8 +243,8 @@ class GuardLine {
|
||||
|
||||
|
||||
_gl_rowCheck(image, rowStart, rowEnd, offenders, offenderCount){
|
||||
var firstOffender = -1;
|
||||
for(var i = rowStart; i < rowEnd; i+=4){
|
||||
let firstOffender = -1;
|
||||
for(let i = rowStart; i < rowEnd; i+=4){
|
||||
|
||||
// we track sections that go over what's supposed to be a black line, so we can suggest more
|
||||
// columns to sample
|
||||
@ -246,36 +267,36 @@ class GuardLine {
|
||||
return offenderCount;
|
||||
}
|
||||
|
||||
_gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount){
|
||||
var firstOffender = -1;
|
||||
for(var i = rowStart; i < rowEnd; i+=4){
|
||||
// _gl_debugRowCheck(image, rowStart, rowEnd, offenders, offenderCount){
|
||||
// let firstOffender = -1;
|
||||
// for(let i = rowStart; i < rowEnd; i+=4){
|
||||
|
||||
// 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.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;
|
||||
offenderCount++;
|
||||
offenders.push({x: firstOffender, width: 1});
|
||||
}
|
||||
else{
|
||||
offenders[offenderCount].width++
|
||||
}
|
||||
}
|
||||
else{
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.GUARDLINE_BLACKBAR);
|
||||
// is that a black pixel again? Let's reset the 'first offender'
|
||||
firstOffender = -1;
|
||||
}
|
||||
// // 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.blackbarThreshold || image[i+1] > this.blackbarThreshold || image[i+2] > this.blackbarThreshold){
|
||||
// this.aard.debugCanvas.trace(i, DebugCanvasClasses.VIOLATION);
|
||||
// if(firstOffender < 0){
|
||||
// firstOffender = (i - rowStart) >> 2;
|
||||
// offenderCount++;
|
||||
// offenders.push({x: firstOffender, width: 1});
|
||||
// }
|
||||
// else{
|
||||
// offenders[offenderCount].width++
|
||||
// }
|
||||
// }
|
||||
// else{
|
||||
// this.aard.debugCanvas.trace(i, DebugCanvasClasses.GUARDLINE_BLACKBAR);
|
||||
// // is that a black pixel again? Let's reset the 'first offender'
|
||||
// firstOffender = -1;
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
return offenderCount;
|
||||
}
|
||||
// return offenderCount;
|
||||
// }
|
||||
|
||||
_ti_checkRow(image, rowStart, rowEnd, successThreshold) {
|
||||
for(var i = rowStart; i < rowEnd; i+=4){
|
||||
_ti_checkRow(image, rowStart, rowEnd, successThreshold): boolean {
|
||||
for(let i = rowStart; i < rowEnd; i+=4){
|
||||
if(image[i] > this.imageThreshold || image[i+1] > this.imageThreshold || image[i+2] > this.imageThreshold){
|
||||
if(successThreshold --<= 0){
|
||||
return true;
|
||||
@ -286,20 +307,20 @@ class GuardLine {
|
||||
return false;
|
||||
}
|
||||
|
||||
_ti_debugCheckRow(image, rowStart, rowEnd, successThreshold) {
|
||||
for(var i = rowStart; i < rowEnd; i+=4){
|
||||
if(image[i] > this.imageThreshold || image[i+1] > this.imageThreshold || image[i+2] > this.imageThreshold){
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.GUARDLINE_IMAGE);
|
||||
if(successThreshold --<= 0){
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.WARN);
|
||||
}
|
||||
}
|
||||
// _ti_debugCheckRow(image, rowStart, rowEnd, successThreshold) {
|
||||
// for(let i = rowStart; i < rowEnd; i+=4){
|
||||
// if(image[i] > this.imageThreshold || image[i+1] > this.imageThreshold || image[i+2] > this.imageThreshold){
|
||||
// this.aard.debugCanvas.trace(i, DebugCanvasClasses.GUARDLINE_IMAGE);
|
||||
// if(successThreshold --<= 0){
|
||||
// return true;
|
||||
// }
|
||||
// } else {
|
||||
// this.aard.debugCanvas.trace(i, DebugCanvasClasses.WARN);
|
||||
// }
|
||||
// }
|
||||
|
||||
return false;
|
||||
}
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
export default GuardLine;
|
@ -2,13 +2,28 @@ import Debug from '../../../conf/Debug';
|
||||
import EdgeStatus from './enums/EdgeStatusEnum';
|
||||
import EdgeDetectQuality from './enums/EdgeDetectQualityEnum';
|
||||
import EdgeDetectPrimaryDirection from './enums/EdgeDetectPrimaryDirectionEnum';
|
||||
import AntiGradientMode from '../../../../common/enums/anti-gradient-mode.enum';
|
||||
import AntiGradientMode from '../../../../common/enums/AntiGradientMode.enum';
|
||||
import ArDetector from '../ArDetector';
|
||||
import Logger from '../../Logger';
|
||||
import Settings from '../../Settings';
|
||||
|
||||
if (Debug.debug) {
|
||||
console.log("Loading EdgeDetect.js");
|
||||
}
|
||||
|
||||
class EdgeDetect{
|
||||
conf: ArDetector;
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
|
||||
|
||||
// helper stuff
|
||||
private sampleWidthBase: number;
|
||||
private halfSample: number;
|
||||
private detectionThreshold: number;
|
||||
private colsThreshold: number;
|
||||
private blackbarThreshold: number;
|
||||
private imageThreshold: number;
|
||||
|
||||
constructor(ardConf){
|
||||
this.conf = ardConf;
|
||||
@ -28,27 +43,27 @@ class EdgeDetect{
|
||||
|
||||
}
|
||||
|
||||
findBars(image, sampleCols, direction = EdgeDetectPrimaryDirection.VERTICAL, quality = EdgeDetectQuality.IMPROVED, guardLineOut, blackFrameAnalysis){
|
||||
findBars(image, sampleCols, direction = EdgeDetectPrimaryDirection.Vertical, quality = EdgeDetectQuality.Improved, guardLineOut?, blackFrameAnalysis?){
|
||||
let fastCandidates, edgeCandidates, bars;
|
||||
if (direction == EdgeDetectPrimaryDirection.VERTICAL) {
|
||||
if (direction == EdgeDetectPrimaryDirection.Vertical) {
|
||||
try {
|
||||
fastCandidates = this.findCandidates(image, sampleCols, guardLineOut);
|
||||
|
||||
if (! this.isValidSample(fastCandidates)) {
|
||||
return {status: EdgeStatus.AR_UNKNOWN};
|
||||
return {status: EdgeStatus.ARUnknown};
|
||||
}
|
||||
// if(quality == EdgeDetectQuality.FAST){
|
||||
// edges = fastCandidates; // todo: processing
|
||||
// } else {
|
||||
edgeCandidates = this.edgeDetect(image, fastCandidates);
|
||||
bars = this.edgePostprocess(edgeCandidates, this.conf.canvas.height);
|
||||
bars = this.edgePostprocess(edgeCandidates);
|
||||
// }
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'arDetect', '%c[EdgeDetect::findBars] find bars failed.', 'background: #f00, color: #000', e);
|
||||
return {status: EdgeStatus.AR_UNKNOWN}
|
||||
return {status: EdgeStatus.ARUnknown}
|
||||
}
|
||||
} else {
|
||||
bars = this.pillarTest(image) ? {status: EdgeStatus.AR_KNOWN} : {status: EdgeStatus.AR_UNKNOWN};
|
||||
bars = this.pillarTest(image) ? {status: EdgeStatus.ARKnown} : {status: EdgeStatus.ARUnknown};
|
||||
}
|
||||
|
||||
return bars;
|
||||
@ -113,10 +128,10 @@ class EdgeDetect{
|
||||
|
||||
this.logger.log('info', 'arDetect', '[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;
|
||||
let upper_top_corrected = upper_top * this.conf.canvasImageDataRowLength;
|
||||
let upper_bottom_corrected = upper_bottom * this.conf.canvasImageDataRowLength;
|
||||
let lower_top_corrected = lower_top * this.conf.canvasImageDataRowLength;
|
||||
let 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);
|
||||
@ -221,20 +236,20 @@ class EdgeDetect{
|
||||
}
|
||||
|
||||
edgeDetect(image, samples){
|
||||
var edgeCandidatesTop = {count: 0};
|
||||
var edgeCandidatesBottom = {count: 0};
|
||||
let edgeCandidatesTop = {count: 0};
|
||||
let edgeCandidatesBottom = {count: 0};
|
||||
|
||||
var detections;
|
||||
var canvasWidth = this.conf.canvas.width;
|
||||
var canvasHeight = this.conf.canvas.height;
|
||||
let detections;
|
||||
let canvasWidth = this.conf.canvas.width;
|
||||
let canvasHeight = this.conf.canvas.height;
|
||||
|
||||
var sampleStart, sampleEnd, loopEnd;
|
||||
var sampleRow_black, sampleRow_color;
|
||||
let sampleStart, sampleEnd, loopEnd;
|
||||
let sampleRow_black, sampleRow_color;
|
||||
|
||||
var blackEdgeViolation = false;
|
||||
let blackEdgeViolation = false;
|
||||
|
||||
var topEdgeCount = 0;
|
||||
var bottomEdgeCount = 0;
|
||||
let topEdgeCount = 0;
|
||||
let bottomEdgeCount = 0;
|
||||
|
||||
try {
|
||||
for (const sample of samples.res_top){
|
||||
@ -259,7 +274,7 @@ class EdgeDetect{
|
||||
loopEnd = sampleRow_black + sampleEnd;
|
||||
|
||||
if (Debug.debugCanvas.enabled){
|
||||
blackEdgeViolation = this._blackbarTest_dbg(image, sampleRow_black + sampleStart, loopEnd);
|
||||
// blackEdgeViolation = this._blackbarTest_dbg(image, sampleRow_black + sampleStart, loopEnd);
|
||||
} else {
|
||||
blackEdgeViolation = this._blackbarTest(image, sampleRow_black + sampleStart, loopEnd);
|
||||
}
|
||||
@ -274,7 +289,7 @@ class EdgeDetect{
|
||||
loopEnd = sampleRow_color + sampleEnd;
|
||||
|
||||
if (Debug.debugCanvas.enabled) {
|
||||
this._imageTest_dbg(image, sampleRow_color + sampleStart, loopEnd, sample.black, edgeCandidatesTop)
|
||||
// this._imageTest_dbg(image, sampleRow_color + sampleStart, loopEnd, sample.black, edgeCandidatesTop)
|
||||
} else {
|
||||
this._imageTest(image, sampleRow_color + sampleStart, loopEnd, sample.black, edgeCandidatesTop);
|
||||
}
|
||||
@ -302,7 +317,7 @@ class EdgeDetect{
|
||||
loopEnd = sampleRow_black + sampleEnd;
|
||||
|
||||
if(Debug.debugCanvas.enabled){
|
||||
blackEdgeViolation = this._blackbarTest_dbg(image, sampleRow_black + sampleStart, loopEnd);
|
||||
// blackEdgeViolation = this._blackbarTest_dbg(image, sampleRow_black + sampleStart, loopEnd);
|
||||
} else {
|
||||
blackEdgeViolation = this._blackbarTest(image, sampleRow_black + sampleStart, loopEnd);
|
||||
}
|
||||
@ -317,7 +332,7 @@ class EdgeDetect{
|
||||
loopEnd = sampleRow_color + sampleEnd;
|
||||
|
||||
if(Debug.debugCanvas.enabled) {
|
||||
this._imageTest_dbg(image, sampleRow_color + sampleStart, loopEnd, sample.black, edgeCandidatesBottom);
|
||||
// this._imageTest_dbg(image, sampleRow_color + sampleStart, loopEnd, sample.black, edgeCandidatesBottom);
|
||||
} else {
|
||||
this._imageTest(image, sampleRow_color + sampleStart, loopEnd, sample.black, edgeCandidatesBottom);
|
||||
}
|
||||
@ -335,11 +350,11 @@ class EdgeDetect{
|
||||
}
|
||||
|
||||
edgePostprocess(edges){
|
||||
var edgesTop = [];
|
||||
var edgesBottom = [];
|
||||
var alignMargin = this.conf.canvas.height * this.settings.active.arDetect.allowedMisaligned;
|
||||
let edgesTop = [];
|
||||
let edgesBottom = [];
|
||||
let alignMargin = this.conf.canvas.height * this.settings.active.arDetect.allowedMisaligned;
|
||||
|
||||
var missingEdge = edges.edgeCandidatesTopCount == 0 || edges.edgeCandidatesBottomCount == 0;
|
||||
let missingEdge = edges.edgeCandidatesTopCount == 0 || edges.edgeCandidatesBottomCount == 0;
|
||||
|
||||
// pretvorimo objekt v tabelo
|
||||
// convert objects to array
|
||||
@ -348,8 +363,8 @@ class EdgeDetect{
|
||||
delete(edges.edgeCandidatesBottom.count);
|
||||
|
||||
if( edges.edgeCandidatesTopCount > 0){
|
||||
for(var e in edges.edgeCandidatesTop){
|
||||
var edge = edges.edgeCandidatesTop[e];
|
||||
for(let e in edges.edgeCandidatesTop){
|
||||
let edge = edges.edgeCandidatesTop[e];
|
||||
edgesTop.push({
|
||||
distance: edge.offset,
|
||||
absolute: edge.offset,
|
||||
@ -359,8 +374,8 @@ class EdgeDetect{
|
||||
}
|
||||
|
||||
if( edges.edgeCandidatesBottomCount > 0){
|
||||
for(var e in edges.edgeCandidatesBottom){
|
||||
var edge = edges.edgeCandidatesBottom[e];
|
||||
for(let e in edges.edgeCandidatesBottom){
|
||||
let edge = edges.edgeCandidatesBottom[e];
|
||||
edgesBottom.push({
|
||||
distance: this.conf.canvas.height - edge.offset,
|
||||
absolute: edge.offset,
|
||||
@ -388,13 +403,13 @@ class EdgeDetect{
|
||||
if( edgesTop[0].distance >= edgesBottom[0].distance - alignMargin &&
|
||||
edgesTop[0].distance <= edgesBottom[0].distance + alignMargin ){
|
||||
|
||||
var blackbarWidth = edgesTop[0].distance > edgesBottom[0].distance ?
|
||||
let blackbarWidth = edgesTop[0].distance > edgesBottom[0].distance ?
|
||||
edgesTop[0].distance : edgesBottom[0].distance;
|
||||
|
||||
if (edgesTop[0].count + edgesBottom[0].count > this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold
|
||||
|| ( edgesTop[0].count > this.settings.active.arDetect.edgeDetection.confirmationThreshold && edgesBottom[0].count > this.settings.active.arDetect.edgeDetection.confirmationThreshold) ){
|
||||
return {
|
||||
status: EdgeStatus.AR_KNOWN,
|
||||
status: EdgeStatus.ARKnown,
|
||||
blackbarWidth: blackbarWidth,
|
||||
guardLineTop: edgesTop[0].distance,
|
||||
guardLineBottom: edgesBottom[0].absolute,
|
||||
@ -415,20 +430,20 @@ class EdgeDetect{
|
||||
// manj vzorcev kot navaden rob.
|
||||
|
||||
if (edgesTop[0].length > 1){
|
||||
var lowMargin = edgesBottom[0].distance - alignMargin;
|
||||
var highMargin = edgesBottom[0].distance + alignMargin;
|
||||
let lowMargin = edgesBottom[0].distance - alignMargin;
|
||||
let highMargin = edgesBottom[0].distance + alignMargin;
|
||||
|
||||
for (var i = 1; i < edgesTop.length; i++){
|
||||
for (let 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 ?
|
||||
let blackbarWidth = edgesTop[i].distance > edgesBottom[0].distance ?
|
||||
edgesTop[i].distance : edgesBottom[0].distance;
|
||||
|
||||
if (edgesTop[i].count + edgesBottom[0].count > this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold
|
||||
|| (edgesTop[i].count > this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold && edgesBottom[0].count > this.settings.active.arDetect.edgeDetection.confirmationThreshold) ) {
|
||||
return {
|
||||
status: EdgeStatus.AR_KNOWN,
|
||||
status: EdgeStatus.ARKnown,
|
||||
blackbarWidth: blackbarWidth,
|
||||
guardLineTop: edgesTop[i].distance,
|
||||
guardLineBottom: edgesBottom[0].absolute,
|
||||
@ -446,20 +461,20 @@ class EdgeDetect{
|
||||
edgesBottom[0].count < this.conf.sampleCols.length * this.settings.active.arDetect.edgeDetection.logoThreshold){
|
||||
|
||||
if(edgesBottom[0].length > 1){
|
||||
var lowMargin = edgesTop[0].distance - alignMargin;
|
||||
var highMargin = edgesTop[0].distance + alignMargin;
|
||||
let lowMargin = edgesTop[0].distance - alignMargin;
|
||||
let highMargin = edgesTop[0].distance + alignMargin;
|
||||
|
||||
for(var i = 1; i < edgesBottom.length; i++){
|
||||
for(let 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 ?
|
||||
let blackbarWidth = edgesBottom[i].distance > edgesTop[0].distance ?
|
||||
edgesBottom[i].distance : edgesTop[0].distance;
|
||||
|
||||
if (edgesTop[0].count + edgesBottom[i].count > this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold
|
||||
|| (edgesTop[0].count > this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold && edgesBottom[i].count > this.settings.active.arDetect.edgeDetection.confirmationThreshold)) {
|
||||
return {
|
||||
status: EdgeStatus.AR_KNOWN,
|
||||
status: EdgeStatus.ARKnown,
|
||||
blackbarWidth: blackbarWidth,
|
||||
guardLineTop: edgesTop[0].distance,
|
||||
guardLineBottom: edgesBottom[i].absolute,
|
||||
@ -482,10 +497,10 @@ class EdgeDetect{
|
||||
const edgeDetectionThreshold = this.settings.active.arDetect.edgeDetection.singleSideConfirmationThreshold;
|
||||
|
||||
if (edges.edgeCandidatesTopCount == 0 && edges.edgeCandidatesBottomCount != 0){
|
||||
for(var edge of edgesBottom){
|
||||
for(let edge of edgesBottom){
|
||||
if(edge.count >= edgeDetectionThreshold)
|
||||
return {
|
||||
status: EdgeStatus.AR_KNOWN,
|
||||
status: EdgeStatus.ARKnown,
|
||||
blackbarWidth: edge.distance,
|
||||
guardLineTop: null,
|
||||
guardLineBottom: edge.bottom,
|
||||
@ -496,10 +511,10 @@ class EdgeDetect{
|
||||
}
|
||||
}
|
||||
if (edges.edgeCandidatesTopCount != 0 && edges.edgeCandidatesBottomCount == 0){
|
||||
for(var edge of edgesTop){
|
||||
for(let edge of edgesTop){
|
||||
if(edge.count >= edgeDetectionThreshold)
|
||||
return {
|
||||
status: EdgeStatus.AR_KNOWN,
|
||||
status: EdgeStatus.ARKnown,
|
||||
blackbarWidth: edge.distance,
|
||||
guardLineTop: edge.top,
|
||||
guardLineBottom: null,
|
||||
@ -512,7 +527,7 @@ class EdgeDetect{
|
||||
}
|
||||
// č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: EdgeStatus.AR_UNKNOWN}
|
||||
return {status: EdgeStatus.ARUnknown}
|
||||
}
|
||||
|
||||
pillarTest(image){
|
||||
@ -523,22 +538,22 @@ class EdgeDetect{
|
||||
// roughly centered, we return true. Otherwise we return false.
|
||||
// we also return true if we detect too much black
|
||||
|
||||
var blackbarThreshold, upper, lower;
|
||||
let blackbarThreshold, upper, lower;
|
||||
blackbarThreshold = this.conf.blackLevel + this.settings.active.arDetect.blackbar.threshold;
|
||||
|
||||
|
||||
var middleRowStart = (this.conf.canvas.height >> 1) * this.conf.canvas.width;
|
||||
var middleRowEnd = middleRowStart + this.conf.canvas.width - 1;
|
||||
let middleRowStart = (this.conf.canvas.height >> 1) * this.conf.canvas.width;
|
||||
let middleRowEnd = middleRowStart + this.conf.canvas.width - 1;
|
||||
|
||||
var rowStart = middleRowStart << 2;
|
||||
var midpoint = (middleRowStart + (this.conf.canvas.width >> 1)) << 2
|
||||
var rowEnd = middleRowEnd << 2;
|
||||
let rowStart = middleRowStart << 2;
|
||||
let midpoint = (middleRowStart + (this.conf.canvas.width >> 1)) << 2
|
||||
let rowEnd = middleRowEnd << 2;
|
||||
|
||||
var edge_left = -1, edge_right = -1;
|
||||
let 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){
|
||||
for(let i = rowStart; i < midpoint; i+=4){
|
||||
if(image[i] > blackbarThreshold || image[i+1] > blackbarThreshold || image[i+2] > blackbarThreshold){
|
||||
edge_left = (i - rowStart) >> 2;
|
||||
break;
|
||||
@ -547,7 +562,7 @@ class EdgeDetect{
|
||||
|
||||
// preverimo na desni strani
|
||||
// check on the right
|
||||
for(var i = rowEnd; i > midpoint; i-= 4){
|
||||
for(let i = rowEnd; i > midpoint; i-= 4){
|
||||
if(image[i] > blackbarThreshold || image[i+1] > blackbarThreshold || image[i+2] > blackbarThreshold){
|
||||
edge_right = this.conf.canvas.width - ((i - rowStart) >> 2);
|
||||
break;
|
||||
@ -566,9 +581,9 @@ class EdgeDetect{
|
||||
return false;
|
||||
}
|
||||
|
||||
var edgeError = this.settings.active.arDetect.pillarTest.allowMisaligned;
|
||||
var error_low = 1 - edgeError;
|
||||
var error_hi = 1 + edgeError;
|
||||
let edgeError = this.settings.active.arDetect.pillarTest.allowMisaligned;
|
||||
let error_low = 1 - edgeError;
|
||||
let 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
|
||||
@ -848,13 +863,13 @@ class EdgeDetect{
|
||||
let tmpI;
|
||||
let lastTmpI = 0;
|
||||
let edgeDetectCount = 0;
|
||||
for(const c in colsOut) {
|
||||
for(const c of colsOut) {
|
||||
c.diffs = [];
|
||||
}
|
||||
if (reverseSearchDirection) {
|
||||
if (this.settings.active.arDetect.blackbar.antiGradientMode === AntiGradientMode.Disabled) {
|
||||
// todo: remove gradient detection code from this branch
|
||||
for(var i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
for(let i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
for(let c = 0; c < colsIn.length; c++){
|
||||
if (colsIn[c].blackFound && colsIn[c].imageFound) {
|
||||
// če smo našli obe točki, potem ne pregledujemo več.
|
||||
@ -908,7 +923,7 @@ class EdgeDetect{
|
||||
}
|
||||
} else {
|
||||
// anti-gradient detection
|
||||
for(var i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
for(let i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
for(let c = 0; c < colsIn.length; c++){
|
||||
if (colsIn[c].blackFound && colsIn[c].imageFound) {
|
||||
// če smo našli obe točki, potem ne pregledujemo več.
|
||||
@ -995,7 +1010,7 @@ class EdgeDetect{
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
|
||||
for(let i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
|
||||
for(let c = 0; c < colsIn.length; c++){
|
||||
if (colsIn[c].blackFound && colsIn[c].imageFound) {
|
||||
// če smo našli obe točki, potem ne pregledujemo več.
|
||||
@ -1051,18 +1066,18 @@ class EdgeDetect{
|
||||
|
||||
}
|
||||
|
||||
_columnTest(image, top, bottom, colsIn, colsOut, reverseSearchDirection){
|
||||
var tmpI;
|
||||
_columnTest(image, top: number, bottom: number, colsIn, colsOut, reverseSearchDirection){
|
||||
let tmpI;
|
||||
if(reverseSearchDirection){
|
||||
for(var i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
for(var col of colsIn){
|
||||
for(let i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
for(const col of colsIn){
|
||||
tmpI = i + (col << 2);
|
||||
|
||||
if( image[tmpI] > this.blackbarThreshold ||
|
||||
image[tmpI + 1] > this.blackbarThreshold ||
|
||||
image[tmpI + 2] > this.blackbarThreshold ){
|
||||
|
||||
var bottom = (i / this.conf.canvasImageDataRowLength) + 1;
|
||||
const bottom = (i / this.conf.canvasImageDataRowLength) + 1;
|
||||
colsOut.push({
|
||||
col: col,
|
||||
bottom: bottom
|
||||
@ -1074,8 +1089,8 @@ class EdgeDetect{
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for(var i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
|
||||
for(var col of colsIn){
|
||||
for(let i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
|
||||
for(const col of colsIn){
|
||||
tmpI = i + (col << 2);
|
||||
|
||||
if( image[tmpI] > this.blackbarThreshold ||
|
||||
@ -1095,65 +1110,65 @@ class EdgeDetect{
|
||||
}
|
||||
}
|
||||
|
||||
_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);
|
||||
// _columnTest_dbgc(image, top, bottom, colsIn, colsOut, reverseSearchDirection){
|
||||
// let tmpI;
|
||||
// if(reverseSearchDirection){
|
||||
// for(let i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
|
||||
// for(let col of colsIn){
|
||||
// tmpI = i + (col << 2);
|
||||
|
||||
if( image[tmpI] > this.blackbarThreshold ||
|
||||
image[tmpI + 1] > this.blackbarThreshold ||
|
||||
image[tmpI + 2] > this.blackbarThreshold ){
|
||||
// if( image[tmpI] > this.blackbarThreshold ||
|
||||
// image[tmpI + 1] > this.blackbarThreshold ||
|
||||
// image[tmpI + 2] > this.blackbarThreshold ){
|
||||
|
||||
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.colsThreshold)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for(var i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
|
||||
for(var col of colsIn){
|
||||
tmpI = i + (col << 2);
|
||||
// let 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.colsThreshold)
|
||||
// break;
|
||||
// }
|
||||
// } else {
|
||||
// for(let i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
|
||||
// for(let col of colsIn){
|
||||
// tmpI = i + (col << 2);
|
||||
|
||||
if( image[tmpI] > this.blackbarThreshold ||
|
||||
image[tmpI + 1] > this.blackbarThreshold ||
|
||||
image[tmpI + 2] > this.blackbarThreshold ){
|
||||
// if( image[tmpI] > this.blackbarThreshold ||
|
||||
// image[tmpI + 1] > this.blackbarThreshold ||
|
||||
// image[tmpI + 2] > this.blackbarThreshold ){
|
||||
|
||||
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.colsThreshold)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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.colsThreshold)
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
_blackbarTest(image, start, end){
|
||||
for(var i = start; i < end; i += 4){
|
||||
for(let i = start; i < end; i += 4){
|
||||
if( image[i ] > this.blackbarThreshold ||
|
||||
image[i+1] > this.blackbarThreshold ||
|
||||
image[i+2] > this.blackbarThreshold ){
|
||||
@ -1163,25 +1178,25 @@ class EdgeDetect{
|
||||
return false; // no violation
|
||||
}
|
||||
|
||||
_blackbarTest_dbg(image, start, end){
|
||||
for(var i = start; i < end; i += 4){
|
||||
if( image[i ] > this.blackbarThreshold ||
|
||||
image[i+1] > this.blackbarThreshold ||
|
||||
image[i+2] > this.blackbarThreshold ){
|
||||
// _blackbarTest_dbg(image, start, end){
|
||||
// for(let i = start; i < end; i += 4){
|
||||
// if( image[i ] > this.blackbarThreshold ||
|
||||
// image[i+1] > this.blackbarThreshold ||
|
||||
// image[i+2] > this.blackbarThreshold ){
|
||||
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.VIOLATION)
|
||||
return true;
|
||||
} else {
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.EDGEDETECT_BLACKBAR)
|
||||
}
|
||||
}
|
||||
return false; // no violation
|
||||
}
|
||||
// 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;
|
||||
let detections = 0;
|
||||
|
||||
for (var i = start; i < end; i += 4){
|
||||
for (let i = start; i < end; i += 4){
|
||||
if (image[i ] > this.blackbarThreshold ||
|
||||
image[i+1] > this.blackbarThreshold ||
|
||||
image[i+2] > this.blackbarThreshold ){
|
||||
@ -1198,28 +1213,28 @@ class EdgeDetect{
|
||||
}
|
||||
}
|
||||
|
||||
_imageTest_dbg(image, start, end, sampleOffset, edgeCandidates){
|
||||
var detections = 0;
|
||||
// _imageTest_dbg(image, start, end, sampleOffset, edgeCandidates){
|
||||
// let detections = 0;
|
||||
|
||||
for(var i = start; i < end; i += 4){
|
||||
if( image[i ] > this.blackbarThreshold ||
|
||||
image[i+1] > this.blackbarThreshold ||
|
||||
image[i+2] > this.blackbarThreshold ){
|
||||
++detections;
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.EDGEDETECT_IMAGE);
|
||||
} else {
|
||||
this.conf.debugCanvas.trace(i, DebugCanvasClasses.WARN);
|
||||
}
|
||||
}
|
||||
if(detections >= this.detectionThreshold){
|
||||
if(edgeCandidates[sampleOffset] != undefined)
|
||||
edgeCandidates[sampleOffset].count++;
|
||||
else{
|
||||
edgeCandidates[sampleOffset] = {offset: sampleOffset, count: 1};
|
||||
edgeCandidates.count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// for(let i = start; i < end; i += 4){
|
||||
// if( image[i ] > this.blackbarThreshold ||
|
||||
// image[i+1] > this.blackbarThreshold ||
|
||||
// image[i+2] > this.blackbarThreshold ){
|
||||
// ++detections;
|
||||
// this.conf.debugCanvas.trace(i, DebugCanvasClasses.EDGEDETECT_IMAGE);
|
||||
// } else {
|
||||
// this.conf.debugCanvas.trace(i, DebugCanvasClasses.WARN);
|
||||
// }
|
||||
// }
|
||||
// if(detections >= this.detectionThreshold){
|
||||
// if(edgeCandidates[sampleOffset] != undefined)
|
||||
// edgeCandidates[sampleOffset].count++;
|
||||
// else{
|
||||
// edgeCandidates[sampleOffset] = {offset: sampleOffset, count: 1};
|
||||
// edgeCandidates.count++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
var EdgeDetectPrimaryDirection = Object.freeze({
|
||||
VERTICAL: 0,
|
||||
HORIZONTAL: 1
|
||||
});
|
||||
|
||||
export default EdgeDetectPrimaryDirection;
|
@ -0,0 +1,6 @@
|
||||
enum EdgeDetectPrimaryDirection {
|
||||
Vertical = 0,
|
||||
Horizontal = 1
|
||||
};
|
||||
|
||||
export default EdgeDetectPrimaryDirection;
|
@ -1,6 +0,0 @@
|
||||
var EdgeDetectQuality = Object.freeze({
|
||||
FAST: 0,
|
||||
IMPROVED: 1
|
||||
});
|
||||
|
||||
export default EdgeDetectQuality;
|
@ -0,0 +1,6 @@
|
||||
enum EdgeDetectQuality {
|
||||
Fast = 0,
|
||||
Improved = 1
|
||||
};
|
||||
|
||||
export default EdgeDetectQuality;
|
@ -1,6 +0,0 @@
|
||||
var EdgeStatus = Object.freeze({
|
||||
AR_UNKNOWN: 0,
|
||||
AR_KNOWN: 1,
|
||||
});
|
||||
|
||||
export default EdgeStatus;
|
@ -0,0 +1,6 @@
|
||||
enum EdgeStatus {
|
||||
ARUnknown = 0,
|
||||
ARKnown = 1,
|
||||
};
|
||||
|
||||
export default EdgeStatus;
|
@ -1,43 +0,0 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading Comms");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Comms loaded");
|
||||
}
|
||||
|
||||
export default Comms;
|
19
src/ext/lib/comms/Comms.ts
Normal file
19
src/ext/lib/comms/Comms.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import { browser } from 'webextension-polyfill-ts';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading Comms");
|
||||
}
|
||||
|
||||
class Comms {
|
||||
static async sendMessage(message){
|
||||
browser.runtime.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Comms loaded");
|
||||
}
|
||||
|
||||
export default Comms;
|
@ -1,19 +1,33 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import Logger from '../Logger';
|
||||
import { browser } from 'webextension-polyfill-ts';
|
||||
import Settings from '../Settings';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading CommsClient");
|
||||
}
|
||||
|
||||
class CommsClient {
|
||||
commsId: string;
|
||||
|
||||
logger: Logger;
|
||||
settings: any; // sus?
|
||||
|
||||
|
||||
commands: {[x: string]: any[]};
|
||||
|
||||
|
||||
|
||||
_listener: (m: any) => void;
|
||||
port: any;
|
||||
|
||||
|
||||
constructor(name, logger, commands) {
|
||||
try {
|
||||
this.logger = logger;
|
||||
|
||||
if (BrowserDetect.firefox) {
|
||||
this.port = browser.runtime.connect({name: name});
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
this.port = chrome.runtime.connect({name: name});
|
||||
}
|
||||
this.port = browser.runtime.connect(null, {name: name});
|
||||
|
||||
this.logger.onLogEnd(
|
||||
(history) => {
|
||||
@ -32,6 +46,9 @@ class CommsClient {
|
||||
this.commsId = (Math.random() * 20).toFixed(0);
|
||||
|
||||
this.commands = commands;
|
||||
} catch (e) {
|
||||
console.error("CONSTRUCOTR FAILED:", e)
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -60,34 +77,11 @@ class CommsClient {
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
return browser.runtime.sendMessage(null, message, null);
|
||||
}
|
||||
|
||||
|
||||
// TODO: sus function — does it get any use?
|
||||
async requestSettings(){
|
||||
this.logger.log('info', 'comms', "%c[CommsClient::requestSettings] sending request for congif!", "background: #11D; color: #aad");
|
||||
|
||||
@ -99,12 +93,12 @@ class CommsClient {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
this.settings.active = JSON.parse(response.extensionConf);
|
||||
this.settings = {active: JSON.parse(response.extensionConf)};
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async sendMessage(message) {
|
||||
await this.sendMessage_nonpersistent(message);
|
||||
return this.sendMessage_nonpersistent(message);
|
||||
}
|
||||
|
||||
registerVideo(){
|
@ -1,339 +0,0 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
|
||||
class CommsServer {
|
||||
constructor(server) {
|
||||
this.server = server;
|
||||
this.logger = server.logger;
|
||||
this.settings = server.settings;
|
||||
this.ports = [];
|
||||
this.popupPort = null;
|
||||
|
||||
if (BrowserDetect.firefox) {
|
||||
browser.runtime.onConnect.addListener(p => this.onConnect(p));
|
||||
browser.runtime.onMessage.addListener((m, sender) => this.processReceivedMessage_nonpersistent(m, sender));
|
||||
} else {
|
||||
chrome.runtime.onConnect.addListener(p => this.onConnect(p));
|
||||
chrome.runtime.onMessage.addListener((m, sender, callback) => this.processReceivedMessage_nonpersistent(m, sender, callback));
|
||||
}
|
||||
|
||||
// commands — functions that handle incoming messages
|
||||
// functions can have the following arguments, which are,
|
||||
// in this order:
|
||||
// message — the message we received
|
||||
// port|sender — on persistent channels, second argument is port on which the server
|
||||
// listens. If the message was sent in non-persistent way, this is the
|
||||
// sender script/frame/whatever of the message
|
||||
// sendResponse — callback function on messages received via non-persistent channel
|
||||
this.commands = {
|
||||
'announce-zoom': [
|
||||
(message) => {
|
||||
try {
|
||||
// forward message to the popup
|
||||
this.popupPort.postMessage({cmd: 'set-current-zoom', zoom: message.zoom});
|
||||
} catch (e) {
|
||||
// if popup is closed, this will/may fail. This is okay, so we just ignore this error
|
||||
}
|
||||
},
|
||||
],
|
||||
'get-current-zoom': [
|
||||
(message) => this.sendToActive(message),
|
||||
],
|
||||
'get-current-site': [
|
||||
async (message, port) => {
|
||||
port.postMessage({
|
||||
cmd: 'set-current-site',
|
||||
site: await this.server.getVideoTab(),
|
||||
tabHostname: await this.getCurrentTabHostname()
|
||||
});
|
||||
},
|
||||
],
|
||||
'popup-set-selected-tab': [
|
||||
(message) => this.server.setSelectedTab(message.selectedMenu, message.selectedSubitem),
|
||||
],
|
||||
'get-config': [
|
||||
(message, port) => {
|
||||
this.logger.log('info', 'comms', "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}
|
||||
);
|
||||
},
|
||||
],
|
||||
'has-video': [
|
||||
(message, port) => this.server.registerVideo(port.sender),
|
||||
],
|
||||
'noVideo': [
|
||||
(message, port) => this.server.unregisterVideo(port.sender),
|
||||
],
|
||||
'inject-css': [
|
||||
(message, sender) => this.server.injectCss(message.cssString, sender),
|
||||
],
|
||||
'eject-css': [
|
||||
(message, sender) => this.server.removeCss(message.cssString, sender),
|
||||
],
|
||||
'replace-css': [
|
||||
(message, sender) => this.server.replaceCss(message.oldCssString, message.newCssString, sender),
|
||||
],
|
||||
'get-config': [
|
||||
(message, sender, sendResponse) => {
|
||||
if (BrowserDetect.firefox) {
|
||||
var ret = {extensionConf: JSON.stringify(this.settings.active)};
|
||||
this.logger.log('info', 'comms', "%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;
|
||||
}
|
||||
}
|
||||
],
|
||||
'autoar-enable': [
|
||||
() => {
|
||||
this.settings.active.sites['@global'].autoar = "blacklist";
|
||||
this.settings.save();
|
||||
this.logger.log('info', 'comms', "[uw-bg] autoar set to enabled (blacklist). evidenz:", this.settings.active);
|
||||
}
|
||||
],
|
||||
'autoar-disable': [
|
||||
(message) => {
|
||||
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.logger.log('info', 'comms', "[uw-bg] autoar set to disabled. evidenz:", this.settings.active);
|
||||
}
|
||||
],
|
||||
'autoar-set-interval': [
|
||||
(message) => {
|
||||
this.logger.log('info', 'comms', `[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();
|
||||
}
|
||||
],
|
||||
'logging-stop-and-save': [ // TODO: possibly never used/superseded — check
|
||||
(message, sender) => {
|
||||
this.logger.log('info', 'comms', "Received command to stop logging and export the received input");
|
||||
this.logger.addToGlobalHistory(`${message.host}::${sender?.tab?.id ?? '×'}-${sender.frameId ?? '×'}`, JSON.parse(message.history));
|
||||
this.logger.finish();
|
||||
}
|
||||
],
|
||||
'logging-save': [
|
||||
(message, sender) => {
|
||||
this.logger.log('info', 'comms', `Received command to save log for site ${message.host} (tabId ${sender.tab.id}, frameId ${sender.frameId}`);
|
||||
this.logger.addToGlobalHistory(`${message?.host}::${sender?.tab?.id ?? '×'}-${sender?.frameId ?? '×'}`, JSON.parse(message.history));
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
subscribe(command, callback) {
|
||||
if (!this.commands[command]) {
|
||||
this.commands[command] = [callback];
|
||||
} else {
|
||||
this.commands[command].push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentTabHostname() {
|
||||
const activeTab = await this._getActiveTab();
|
||||
|
||||
if (!activeTab || activeTab.length < 1) {
|
||||
this.logger.log('warn', 'comms', 'There is no active tab for some reason. activeTab:', activeTab);
|
||||
}
|
||||
|
||||
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(const tab of this.ports){
|
||||
for(const frame in tab){
|
||||
for (const port in tab[frame]) {
|
||||
tab[frame][port].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;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if port is NOT defined, send to all content scripts of a given frame
|
||||
// if port is defined, send just to that particular script of a given frame
|
||||
async sendToFrameContentScripts(message, tab, frame, port) {
|
||||
if (port !== undefined) {
|
||||
// note: 'port' is _not_ shadowed here.
|
||||
this.ports[tab][frame][port].postMessage(message);
|
||||
return;
|
||||
}
|
||||
for (const port in this.ports[tab][frame]) {
|
||||
// note: 'port' is shadowed here!
|
||||
this.ports[tab][frame][port].postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
async sendToFrame(message, tab, frame, port) {
|
||||
this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
||||
|
||||
if (isNaN(tab)) {
|
||||
if (frame === '__playing') {
|
||||
message['playing'] = true;
|
||||
this.sendToAll(message);
|
||||
return;
|
||||
} else if (frame === '__all') {
|
||||
this.sendToAll(message);
|
||||
return;
|
||||
}
|
||||
[tab, frame] = frame.split('-');
|
||||
}
|
||||
|
||||
this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
||||
|
||||
try {
|
||||
this.sendToFrameContentScripts(message, tab, frame, port);
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'comms', `%c[CommsServer::sendToFrame] Sending message failed. Reason:`, "background: #dda; color: #11D", e);
|
||||
}
|
||||
}
|
||||
|
||||
async sendToAllFrames(message, tab, port) {
|
||||
for (const frame in this.ports[tab]) {
|
||||
this.sendToFrameContentScripts(message, tab, frame, port);
|
||||
}
|
||||
}
|
||||
|
||||
async sendToActive(message) {
|
||||
this.logger.log('info', 'comms', "%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
|
||||
|
||||
const tabs = await this._getActiveTab();
|
||||
|
||||
this.logger.log('info', 'comms', "[CommsServer::_sendToActive] currently active tab(s)?", tabs);
|
||||
for (const frame in this.ports[tabs[0].id]) {
|
||||
this.logger.log('info', 'comms', "key?", frame, this.ports[tabs[0].id]);
|
||||
}
|
||||
|
||||
for (const frame in this.ports[tabs[0].id]) {
|
||||
this.sendToFrameContentScripts(message, tabs[0].id, frame);
|
||||
}
|
||||
}
|
||||
|
||||
onConnect(port){
|
||||
// poseben primer | special case
|
||||
if (port.name === 'popup-port') {
|
||||
this.popupPort = port;
|
||||
this.popupPort.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
|
||||
return;
|
||||
}
|
||||
|
||||
var tabId = port.sender.tab.id;
|
||||
var frameId = port.sender.frameId;
|
||||
if (! this.ports[tabId]){
|
||||
this.ports[tabId] = {};
|
||||
}
|
||||
if (! this.ports[tabId][frameId]) {
|
||||
this.ports[tabId][frameId] = {};
|
||||
}
|
||||
this.ports[tabId][frameId][port.name] = port;
|
||||
this.ports[tabId][frameId][port.name].onMessage.addListener( (m,p) => this.processReceivedMessage(m, p));
|
||||
|
||||
this.ports[tabId][frameId][port.name].onDisconnect.addListener( (p) => {
|
||||
try {
|
||||
delete this.ports[p.sender.tab.id][p.sender.frameId][port.name];
|
||||
} catch (e) {
|
||||
// no biggie if the thing above doesn't exist.
|
||||
}
|
||||
if (Object.keys(this.ports[tabId][frameId].length === 0)) {
|
||||
delete this.ports[tabId][frameId];
|
||||
if(Object.keys(this.ports[p.sender.tab.id]).length === 0) {
|
||||
delete this.ports[tabId];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async execCmd(message, portOrSender, sendResponse) {
|
||||
this.logger.log(
|
||||
'info', 'comms', '[CommsServer.js::execCmd] Received message', message,
|
||||
". Port/sender:", portOrSender, "sendResponse:", sendResponse, "\nThere is ", this.commands[message.cmd]?.length ?? 0,
|
||||
" command(s) for action", message.cmd
|
||||
);
|
||||
if (this.commands[message.cmd]) {
|
||||
for (const c of this.commands[message.cmd]) {
|
||||
try {
|
||||
await c(message, portOrSender, sendResponse);
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'debug', "[CommsServer.js::execCmd] failed to execute command.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleMessage(message, portOrSender, sendResponse) {
|
||||
await this.execCmd(message, portOrSender, sendResponse);
|
||||
|
||||
if (message.forwardToSameFramePort) {
|
||||
this.sendToFrameContentScripts(message, portOrSender.tab.id, portOrSender.frameId, message.port)
|
||||
}
|
||||
if (message.forwardToContentScript) {
|
||||
this.logger.log('info', 'comms', "[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) {
|
||||
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Message has 'forward to all' flag set. Forwarding message as is. Message:", message);
|
||||
this.sendToAll(message);
|
||||
}
|
||||
if (message.forwardToActive) {
|
||||
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Message has 'forward to active' flag set. Forwarding message as is. Message:", message);
|
||||
this.sendToActive(message);
|
||||
}
|
||||
}
|
||||
|
||||
async processReceivedMessage(message, port){
|
||||
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Received message from popup/content script!", message, "port", port);
|
||||
|
||||
this.handleMessage(message, port)
|
||||
}
|
||||
|
||||
processReceivedMessage_nonpersistent(message, sender, sendResponse){
|
||||
this.logger.log('info', 'comms', "%c[CommsServer.js::processMessage_nonpersistent] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
|
||||
|
||||
this.handleMessage(message, sender, sendResponse);
|
||||
}
|
||||
|
||||
// chrome shitiness mitigation
|
||||
sendUnmarkPlayer(message) {
|
||||
this.logger.log('info', 'comms', '[CommsServer.js::sendUnmarkPlayer] Chrome is a shit browser that doesn\'t do port.postMessage() in unload events, so we have to resort to inelegant hacks. If you see this, then the workaround method works.');
|
||||
this.processReceivedMessage(message, this.popupPort);
|
||||
}
|
||||
}
|
||||
|
||||
export default CommsServer;
|
347
src/ext/lib/comms/CommsServer.ts
Normal file
347
src/ext/lib/comms/CommsServer.ts
Normal file
@ -0,0 +1,347 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import { browser } from 'webextension-polyfill-ts';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
|
||||
|
||||
class CommsServer {
|
||||
server: any;
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
|
||||
|
||||
ports: {
|
||||
[frame: string] : {
|
||||
[port: string]: any
|
||||
}
|
||||
}[] = [];
|
||||
popupPort: any;
|
||||
|
||||
/**
|
||||
* commands — functions that handle incoming messages
|
||||
* functions can have the following arguments, which are,
|
||||
* in this order:
|
||||
* message — the message we received
|
||||
* port|sender — on persistent channels, second argument is port on which the server
|
||||
* listens. If the message was sent in non-persistent way, this is the
|
||||
* sender script/frame/whatever of the message
|
||||
* sendResponse — callback function on messages received via non-persistent channel
|
||||
*/
|
||||
commands: {[x: string]: ((a: any, b: any) => void | Promise<void>)[]} = {
|
||||
'announce-zoom': [
|
||||
(message) => {
|
||||
try {
|
||||
// forward message to the popup
|
||||
this.popupPort.postMessage({cmd: 'set-current-zoom', zoom: message.zoom});
|
||||
} catch (e) {
|
||||
// if popup is closed, this will/may fail. This is okay, so we just ignore this error
|
||||
}
|
||||
},
|
||||
],
|
||||
'get-current-zoom': [
|
||||
(message) => this.sendToActive(message),
|
||||
],
|
||||
'get-current-site': [
|
||||
async (message, port) => {
|
||||
port.postMessage({
|
||||
cmd: 'set-current-site',
|
||||
site: await this.server.getVideoTab(),
|
||||
tabHostname: await this.getCurrentTabHostname()
|
||||
});
|
||||
},
|
||||
],
|
||||
'popup-set-selected-tab': [
|
||||
(message) => this.server.setSelectedTab(message.selectedMenu, message.selectedSubitem),
|
||||
],
|
||||
'has-video': [
|
||||
(message, port) => this.server.registerVideo(port.sender),
|
||||
],
|
||||
'noVideo': [
|
||||
(message, port) => this.server.unregisterVideo(port.sender),
|
||||
],
|
||||
'inject-css': [
|
||||
(message, sender) => this.server.injectCss(message.cssString, sender),
|
||||
],
|
||||
'eject-css': [
|
||||
(message, sender) => this.server.removeCss(message.cssString, sender),
|
||||
],
|
||||
'replace-css': [
|
||||
(message, sender) => this.server.replaceCss(message.oldCssString, message.newCssString, sender),
|
||||
],
|
||||
// 'get-config': [
|
||||
// (message, port) => {
|
||||
// this.logger.log('info', 'comms', "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}
|
||||
// );
|
||||
// },
|
||||
// ],
|
||||
'get-config': [
|
||||
(message, sender) => {
|
||||
var ret = {extensionConf: JSON.stringify(this.settings.active)};
|
||||
this.logger.log('info', 'comms', "%c[CommsServer.js::processMessage_nonpersistent] Returning this:", "background-color: #11D; color: #aad", ret);
|
||||
Promise.resolve(ret);
|
||||
}
|
||||
],
|
||||
'autoar-enable': [
|
||||
() => {
|
||||
this.settings.active.sites['@global'].autoar = ExtensionMode.Enabled;
|
||||
this.settings.save();
|
||||
this.logger.log('info', 'comms', "[uw-bg] autoar set to enabled (blacklist). evidenz:", this.settings.active);
|
||||
}
|
||||
],
|
||||
'autoar-disable': [
|
||||
(message) => {
|
||||
this.settings.active.sites['@global'].autoar = ExtensionMode.Disabled;
|
||||
if (message.reason){
|
||||
this.settings.active.arDetect.disabledReason = message.reason;
|
||||
} else {
|
||||
this.settings.active.arDetect.disabledReason = 'User disabled';
|
||||
}
|
||||
this.settings.save();
|
||||
this.logger.log('info', 'comms', "[uw-bg] autoar set to disabled. evidenz:", this.settings.active);
|
||||
}
|
||||
],
|
||||
'autoar-set-interval': [
|
||||
(message) => {
|
||||
this.logger.log('info', 'comms', `[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.timers.playing = timeout;
|
||||
this.settings.save();
|
||||
}
|
||||
],
|
||||
'logging-stop-and-save': [ // TODO: possibly never used/superseded — check
|
||||
(message, sender) => {
|
||||
this.logger.log('info', 'comms', "Received command to stop logging and export the received input");
|
||||
this.logger.addToGlobalHistory(`${message.host}::${sender?.tab?.id ?? '×'}-${sender.frameId ?? '×'}`, JSON.parse(message.history));
|
||||
this.logger.finish();
|
||||
}
|
||||
],
|
||||
'logging-save': [
|
||||
(message, sender) => {
|
||||
this.logger.log('info', 'comms', `Received command to save log for site ${message.host} (tabId ${sender.tab.id}, frameId ${sender.frameId}`);
|
||||
this.logger.addToGlobalHistory(`${message?.host}::${sender?.tab?.id ?? '×'}-${sender?.frameId ?? '×'}`, JSON.parse(message.history));
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
//#region getters
|
||||
get activeTab() {
|
||||
return browser.tabs.query({currentWindow: true, active: true});
|
||||
}
|
||||
//#endregion
|
||||
|
||||
constructor(server) {
|
||||
this.server = server;
|
||||
this.logger = server.logger;
|
||||
this.settings = server.settings;
|
||||
|
||||
browser.runtime.onConnect.addListener(p => this.onConnect(p));
|
||||
browser.runtime.onMessage.addListener((m, sender) => this.processReceivedMessage_nonpersistent(m, sender));
|
||||
}
|
||||
|
||||
subscribe(command, callback) {
|
||||
if (!this.commands[command]) {
|
||||
this.commands[command] = [callback];
|
||||
} else {
|
||||
this.commands[command].push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentTabHostname() {
|
||||
const activeTab = await this.activeTab;
|
||||
|
||||
if (!activeTab || activeTab.length < 1) {
|
||||
this.logger.log('warn', 'comms', 'There is no active tab for some reason. activeTab:', activeTab);
|
||||
}
|
||||
|
||||
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(const tab of this.ports){
|
||||
for(const frame in tab){
|
||||
for (const port in tab[frame]) {
|
||||
tab[frame][port].postMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message to addon content scripts.
|
||||
* @param message message
|
||||
* @param tab the tab we want to send the message to
|
||||
* @param frame the frame within that tab that we want to send the message to
|
||||
* @param port if defined, message will only be sent to that specific script, otherwise it gets sent to all scripts of a given frame
|
||||
*/
|
||||
async sendToFrameContentScripts(message, tab, frame, port?) {
|
||||
if (port !== undefined) {
|
||||
this.ports[tab][frame][port].postMessage(message);
|
||||
return;
|
||||
}
|
||||
for (const framePort in this.ports[tab][frame]) {
|
||||
this.ports[tab][frame][framePort].postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
async sendToFrame(message, tab, frame, port?) {
|
||||
this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
||||
|
||||
if (isNaN(tab)) {
|
||||
if (frame === '__playing') {
|
||||
message['playing'] = true;
|
||||
this.sendToAll(message);
|
||||
return;
|
||||
} else if (frame === '__all') {
|
||||
this.sendToAll(message);
|
||||
return;
|
||||
}
|
||||
[tab, frame] = frame.split('-');
|
||||
}
|
||||
|
||||
this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
||||
|
||||
try {
|
||||
this.sendToFrameContentScripts(message, tab, frame, port);
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'comms', `%c[CommsServer::sendToFrame] Sending message failed. Reason:`, "background: #dda; color: #11D", e);
|
||||
}
|
||||
}
|
||||
|
||||
async sendToAllFrames(message, tab, port) {
|
||||
for (const frame in this.ports[tab]) {
|
||||
this.sendToFrameContentScripts(message, tab, frame, port);
|
||||
}
|
||||
}
|
||||
|
||||
async sendToActive(message) {
|
||||
this.logger.log('info', 'comms', "%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
|
||||
|
||||
const tabs = await this.activeTab;
|
||||
|
||||
this.logger.log('info', 'comms', "[CommsServer::_sendToActive] currently active tab(s)?", tabs);
|
||||
for (const frame in this.ports[tabs[0].id]) {
|
||||
this.logger.log('info', 'comms', "key?", frame, this.ports[tabs[0].id]);
|
||||
}
|
||||
|
||||
for (const frame in this.ports[tabs[0].id]) {
|
||||
this.sendToFrameContentScripts(message, tabs[0].id, frame);
|
||||
}
|
||||
}
|
||||
|
||||
onConnect(port){
|
||||
// poseben primer | special case
|
||||
if (port.name === 'popup-port') {
|
||||
this.popupPort = port;
|
||||
this.popupPort.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
|
||||
return;
|
||||
}
|
||||
|
||||
var tabId = port.sender.tab.id;
|
||||
var frameId = port.sender.frameId;
|
||||
if (! this.ports[tabId]){
|
||||
this.ports[tabId] = {};
|
||||
}
|
||||
if (! this.ports[tabId][frameId]) {
|
||||
this.ports[tabId][frameId] = {};
|
||||
}
|
||||
this.ports[tabId][frameId][port.name] = port;
|
||||
this.ports[tabId][frameId][port.name].onMessage.addListener( (m,p) => this.processReceivedMessage(m, p));
|
||||
|
||||
this.ports[tabId][frameId][port.name].onDisconnect.addListener( (p) => {
|
||||
try {
|
||||
delete this.ports[p.sender.tab.id][p.sender.frameId][port.name];
|
||||
} catch (e) {
|
||||
// no biggie if the thing above doesn't exist.
|
||||
}
|
||||
if (Object.keys(this.ports[tabId][frameId].length === 0)) {
|
||||
delete this.ports[tabId][frameId];
|
||||
if(Object.keys(this.ports[p.sender.tab.id]).length === 0) {
|
||||
delete this.ports[tabId];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// TODO: sendResponse seems redundant — it used to be a callback for
|
||||
// chrome-based browsers, but browser polyfill doesn't do callback. Just
|
||||
// awaits.
|
||||
async execCmd(message, portOrSender, sendResponse?) {
|
||||
this.logger.log(
|
||||
'info', 'comms', '[CommsServer.js::execCmd] Received message', message,
|
||||
". Port/sender:", portOrSender, "sendResponse:", sendResponse, "\nThere is ", this.commands[message.cmd]?.length ?? 0,
|
||||
" command(s) for action", message.cmd
|
||||
);
|
||||
if (this.commands[message.cmd]) {
|
||||
for (const c of this.commands[message.cmd]) {
|
||||
try {
|
||||
await c(message, portOrSender);
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'debug', "[CommsServer.js::execCmd] failed to execute command.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleMessage(message, portOrSender) {
|
||||
await this.execCmd(message, portOrSender);
|
||||
|
||||
if (message.forwardToSameFramePort) {
|
||||
this.sendToFrameContentScripts(message, portOrSender.tab.id, portOrSender.frameId, message.port);
|
||||
}
|
||||
if (message.forwardToContentScript) {
|
||||
this.logger.log('info', 'comms', "[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) {
|
||||
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Message has 'forward to all' flag set. Forwarding message as is. Message:", message);
|
||||
this.sendToAll(message);
|
||||
}
|
||||
if (message.forwardToActive) {
|
||||
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Message has 'forward to active' flag set. Forwarding message as is. Message:", message);
|
||||
this.sendToActive(message);
|
||||
}
|
||||
}
|
||||
|
||||
async processReceivedMessage(message, port){
|
||||
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Received message from popup/content script!", message, "port", port);
|
||||
|
||||
this.handleMessage(message, port)
|
||||
}
|
||||
|
||||
processReceivedMessage_nonpersistent(message, sender){
|
||||
this.logger.log('info', 'comms', "%c[CommsServer.js::processMessage_nonpersistent] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
|
||||
|
||||
this.handleMessage(message, sender);
|
||||
}
|
||||
|
||||
// chrome shitiness mitigation
|
||||
sendUnmarkPlayer(message) {
|
||||
this.logger.log('info', 'comms', '[CommsServer.js::sendUnmarkPlayer] Chrome is a shit browser that doesn\'t do port.postMessage() in unload events, so we have to resort to inelegant hacks. If you see this, then the workaround method works.');
|
||||
this.processReceivedMessage(message, this.popupPort);
|
||||
}
|
||||
}
|
||||
|
||||
export default CommsServer;
|
@ -86,7 +86,11 @@ class PlayerUi extends UI {
|
||||
|
||||
//#region lifecycle
|
||||
replace(playerElement) {
|
||||
super.replace(this.getUiConfig(playerElement));
|
||||
try {
|
||||
super.replace(this.getUiConfig(playerElement));
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'Couldn\'t replace player element for ui. Error:', e);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class UI {
|
||||
) {
|
||||
this.interfaceId = interfaceId;
|
||||
this.commsConfig = commsConfig;
|
||||
this.storeConfig = storeConfig,
|
||||
this.storeConfig = storeConfig;
|
||||
this.uiConfig = uiConfig;
|
||||
|
||||
this.init();
|
||||
|
@ -1,27 +1,55 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import VideoData from './VideoData';
|
||||
import RescanReason from './enums/RescanReason';
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
import CropModePersistence from '../../../common/enums/crop-mode-persistence.enum';
|
||||
import RescanReason from './enums/RescanReason.enum';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
import CommsClient from '../comms/CommsClient';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading PageInfo");
|
||||
}
|
||||
|
||||
class PageInfo {
|
||||
//#region flags
|
||||
readOnly: boolean = false;
|
||||
hasVideos: boolean = false;
|
||||
siteDisabled: boolean = false;
|
||||
//#endregion
|
||||
|
||||
//#region timers and timeouts
|
||||
rescanTimer: any;
|
||||
urlCheckTimer: any;
|
||||
announceZoomTimeout: any;
|
||||
//#endregion
|
||||
|
||||
//#region helper objects
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
comms: CommsClient;
|
||||
videos: VideoData[] = [];
|
||||
//#endregion
|
||||
|
||||
//#region misc stuff
|
||||
lastUrl: string;
|
||||
extensionMode: ExtensionMode;
|
||||
defaultCrop: any;
|
||||
currentCrop: any;
|
||||
actionHandlerInitQueue: any[] = [];
|
||||
currentZoomScale: number = 1;
|
||||
|
||||
actionHandler: any;
|
||||
//#endregion
|
||||
|
||||
constructor(comms, settings, logger, extensionMode, readOnly = false){
|
||||
this.logger = logger;
|
||||
this.hasVideos = false;
|
||||
this.siteDisabled = false;
|
||||
this.videos = [];
|
||||
this.settings = settings;
|
||||
this.actionHandlerInitQueue = [];
|
||||
|
||||
this.lastUrl = window.location.href;
|
||||
this.extensionMode = extensionMode;
|
||||
this.readOnly = readOnly;
|
||||
this.defaultCrop = undefined;
|
||||
this.currentCrop = undefined;
|
||||
|
||||
if (comms){
|
||||
this.comms = comms;
|
||||
@ -54,8 +82,6 @@ class PageInfo {
|
||||
|
||||
this.rescan(RescanReason.PERIODIC);
|
||||
this.scheduleUrlCheck();
|
||||
|
||||
this.currentZoomScale = 1;
|
||||
}
|
||||
|
||||
async injectCss(cssString) {
|
||||
@ -85,9 +111,9 @@ class PageInfo {
|
||||
if(this.rescanTimer){
|
||||
clearTimeout(this.rescanTimer);
|
||||
}
|
||||
for (var video of this.videos) {
|
||||
for (let video of this.videos) {
|
||||
try {
|
||||
this.comms.unregisterVideo(video.id)
|
||||
(this.comms.unregisterVideo as any)(video.vdid)
|
||||
video.destroy();
|
||||
} catch (e) {
|
||||
this.logger.log('error', ['debug', 'init'], '[PageInfo::destroy] unable to destroy video! Error:', e);
|
||||
@ -95,7 +121,7 @@ class PageInfo {
|
||||
}
|
||||
|
||||
try {
|
||||
playerStyleString = this.settings.active.sites[window.location.hostname].css;
|
||||
const playerStyleString = this.settings.active.sites[window.location.hostname].css;
|
||||
if (playerStyleString) {
|
||||
this.comms.sendMessage({
|
||||
cmd: 'eject-css',
|
||||
@ -108,7 +134,7 @@ class PageInfo {
|
||||
}
|
||||
|
||||
reset() {
|
||||
for(var video of this.videos) {
|
||||
for(let video of this.videos) {
|
||||
video.destroy();
|
||||
}
|
||||
this.rescan(RescanReason.MANUAL);
|
||||
@ -124,7 +150,7 @@ class PageInfo {
|
||||
|
||||
setActionHandler(actionHandler) {
|
||||
this.actionHandler = actionHandler;
|
||||
for (var item of this.actionHandlerInitQueue) {
|
||||
for (let item of this.actionHandlerInitQueue) {
|
||||
this.actionHandler.registerHandleMouse(item);
|
||||
}
|
||||
this.actionHandlerInitQueue = [];
|
||||
@ -132,8 +158,8 @@ class PageInfo {
|
||||
|
||||
getVideos(host) {
|
||||
if (this.settings.active.sites[host]?.DOM?.video?.manual
|
||||
&& this.settings.active.sites[host]?.DOM?.video?.querySelector){
|
||||
const videos = document.querySelectorAll(this.settings.active.sites[host].DOM.video.querySelector);
|
||||
&& this.settings.active.sites[host]?.DOM?.video?.querySelectors){
|
||||
const videos = document.querySelectorAll(this.settings.active.sites[host].DOM.video.querySelectors);
|
||||
|
||||
if (videos.length) {
|
||||
return videos;
|
||||
@ -150,7 +176,7 @@ class PageInfo {
|
||||
const oldVideoCount = this.videos.length;
|
||||
|
||||
try{
|
||||
var vids = this.getVideos(window.location.hostname);
|
||||
let vids = this.getVideos(window.location.hostname);
|
||||
|
||||
if(!vids || vids.length == 0){
|
||||
this.hasVideos = false;
|
||||
@ -164,8 +190,8 @@ class PageInfo {
|
||||
|
||||
// add new videos
|
||||
this.hasVideos = false;
|
||||
var videoExists = false;
|
||||
var video, v;
|
||||
let videoExists = false;
|
||||
let video, v;
|
||||
|
||||
for (video of vids) {
|
||||
// če najdemo samo en video z višino in širino, to pomeni, da imamo na strani veljavne videe
|
||||
@ -245,9 +271,11 @@ class PageInfo {
|
||||
// }
|
||||
|
||||
if (this.videos.length > 0) {
|
||||
this.comms.registerVideo({host: window.location.hostname, location: window.location});
|
||||
// this.comms.registerVideo({host: window.location.hostname, location: window.location});
|
||||
this.comms.registerVideo();
|
||||
} else {
|
||||
this.comms.unregisterVideo({host: window.location.hostname, location: window.location});
|
||||
// this.comms.unregisterVideo({host: window.location.hostname, location: window.location});
|
||||
this.comms.unregisterVideo();
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,7 +314,7 @@ class PageInfo {
|
||||
clearTimeout(this.rescanTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
let ths = this;
|
||||
|
||||
this.rescanTimer = setTimeout(function(rescanReason){
|
||||
ths.rescanTimer = null;
|
||||
@ -304,7 +332,7 @@ class PageInfo {
|
||||
clearTimeout(this.urlCheckTimer);
|
||||
}
|
||||
|
||||
var ths = this;
|
||||
let ths = this;
|
||||
|
||||
this.urlCheckTimer = setTimeout(function(){
|
||||
ths.urlCheckTimer = null;
|
||||
@ -329,14 +357,14 @@ class PageInfo {
|
||||
|
||||
initArDetection(playingOnly){
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
if(vd.isPlaying()) {
|
||||
vd.initArDetection();
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
vd.initArDetection();
|
||||
}
|
||||
}
|
||||
@ -347,13 +375,13 @@ class PageInfo {
|
||||
// these need to be called on tab switch
|
||||
pauseProcessing(playingOnly){
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.pause();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
vd.pause();
|
||||
}
|
||||
}
|
||||
@ -361,7 +389,7 @@ class PageInfo {
|
||||
|
||||
resumeProcessing(resumeAutoar = false, playingOnly = false){
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.resume();
|
||||
if(resumeAutoar){
|
||||
@ -370,7 +398,7 @@ class PageInfo {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
vd.resume();
|
||||
if(resumeAutoar){
|
||||
vd.resumeAutoAr();
|
||||
@ -385,13 +413,13 @@ class PageInfo {
|
||||
this.logger.log('info', 'debug', '[PageInfo::startArDetection()] starting automatic ar detection!')
|
||||
}
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
if (video.isPlaying()) {
|
||||
for(let vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.startArDetection();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
vd.startArDetection();
|
||||
}
|
||||
}
|
||||
@ -399,28 +427,28 @@ class PageInfo {
|
||||
|
||||
stopArDetection(playingOnly){
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.stopArDetection();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
vd.stopArDetection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAr(ar, playingOnly){
|
||||
setAr(ar, playingOnly?: boolean){
|
||||
this.logger.log('info', 'debug', '[PageInfo::setAr] aspect ratio:', ar, "playing only?", playingOnly)
|
||||
|
||||
if (ar.type !== AspectRatio.Automatic) {
|
||||
if (ar.type !== AspectRatioType.Automatic) {
|
||||
this.stopArDetection(playingOnly);
|
||||
} else {
|
||||
this.logger.log('info', 'debug', '[PageInfo::setAr] aspect ratio is auto');
|
||||
|
||||
try {
|
||||
for (var vd of this.videos) {
|
||||
for (let vd of this.videos) {
|
||||
if (!playingOnly || vd.isPlaying()) {
|
||||
vd.resetLastAr();
|
||||
}
|
||||
@ -434,14 +462,14 @@ class PageInfo {
|
||||
}
|
||||
|
||||
// TODO: find a way to only change aspect ratio for one video
|
||||
if (ar === AspectRatio.Reset) {
|
||||
for (var vd of this.videos) {
|
||||
if (ar === AspectRatioType.Reset) {
|
||||
for (let vd of this.videos) {
|
||||
if (!playingOnly || vd.isPlaying()) {
|
||||
vd.resetAr();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var vd of this.videos) {
|
||||
for (let vd of this.videos) {
|
||||
if (!playingOnly || vd.isPlaying()) {
|
||||
vd.setAr(ar)
|
||||
}
|
||||
@ -451,78 +479,78 @@ class PageInfo {
|
||||
|
||||
setVideoAlignment(videoAlignment, playingOnly) {
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos) {
|
||||
for(let vd of this.videos) {
|
||||
if (vd.isPlaying()) {
|
||||
vd.setVideoAlignment(videoAlignment)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos) {
|
||||
for(let vd of this.videos) {
|
||||
vd.setVideoAlignment(videoAlignment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setPanMode(mode, playingOnly) {
|
||||
setPanMode(mode, playingOnly?: boolean) {
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos) {
|
||||
for(let vd of this.videos) {
|
||||
if (vd.isPlaying()) {
|
||||
vd.setPanMode(mode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos) {
|
||||
for(let vd of this.videos) {
|
||||
vd.setPanMode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
restoreAr(playingOnly) {
|
||||
restoreAr(playingOnly?: boolean) {
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.restoreAr();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
vd.restoreAr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setStretchMode(stretchMode, playingOnly, fixedStretchRatio){
|
||||
setStretchMode(stretchMode, playingOnly?: boolean, fixedStretchRatio?: boolean){
|
||||
// TODO: find a way to only change aspect ratio for one video
|
||||
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
if (vd.isPlaying()) {
|
||||
vd.setStretchMode(stretchMode, fixedStretchRatio)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos){
|
||||
for(let vd of this.videos){
|
||||
vd.setStretchMode(stretchMode, fixedStretchRatio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setZoom(zoomLevel, no_announce, playingOnly) {
|
||||
setZoom(zoomLevel, no_announce?: boolean, playingOnly?: boolean) {
|
||||
if (playingOnly) {
|
||||
for(var vd of this.videos) {
|
||||
for(let vd of this.videos) {
|
||||
if (vd.isPlaying()) {
|
||||
vd.setZoom(zoomLevel, no_announce);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var vd of this.videos) {
|
||||
for(let vd of this.videos) {
|
||||
vd.setZoom(zoomLevel, no_announce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoomStep(step, playingOnly) {
|
||||
for(var vd of this.videos){
|
||||
zoomStep(step, playingOnly?: boolean) {
|
||||
for(let vd of this.videos){
|
||||
if (!playingOnly || vd.isPlaying()) {
|
||||
vd.zoomStep(step);
|
||||
}
|
||||
@ -530,19 +558,19 @@ class PageInfo {
|
||||
}
|
||||
|
||||
markPlayer(name, color) {
|
||||
for (var vd of this.videos) {
|
||||
for (let vd of this.videos) {
|
||||
vd.markPlayer(name,color);
|
||||
}
|
||||
}
|
||||
unmarkPlayer() {
|
||||
for (var vd of this.videos) {
|
||||
for (let vd of this.videos) {
|
||||
vd.unmarkPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
announceZoom(scale) {
|
||||
if (this.announceZoomTimeout) {
|
||||
clearTimeout(this.announceZoom);
|
||||
clearTimeout(this.announceZoomTimeout);
|
||||
}
|
||||
this.currentZoomScale = scale;
|
||||
const ths = this;
|
||||
@ -550,13 +578,13 @@ class PageInfo {
|
||||
}
|
||||
|
||||
setManualTick(manualTick) {
|
||||
for(var vd of this.videos) {
|
||||
vd.setManualTick();
|
||||
for(let vd of this.videos) {
|
||||
vd.setManualTick(manualTick);
|
||||
}
|
||||
}
|
||||
|
||||
tick() {
|
||||
for(var vd of this.videos) {
|
||||
for(let vd of this.videos) {
|
||||
vd.tick();
|
||||
}
|
||||
}
|
@ -1,9 +1,14 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import ExtensionMode from '../../../common/enums/extension-mode.enum'
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum'
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import PlayerNotificationUi from '../uwui/PlayerNotificationUI';
|
||||
import PlayerUi from '../uwui/PlayerUI';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import * as _ from 'lodash';
|
||||
import { sleep } from '../../../common/js/utils';
|
||||
import VideoData from './VideoData';
|
||||
import Settings from '../Settings';
|
||||
import Logger from '../Logger';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading: PlayerData.js");
|
||||
@ -37,6 +42,48 @@ if (process.env.CHANNEL !== 'stable'){
|
||||
*/
|
||||
|
||||
class PlayerData {
|
||||
|
||||
//#region helper objects
|
||||
logger: Logger;
|
||||
videoData: VideoData;
|
||||
settings: Settings;
|
||||
notificationService: PlayerNotificationUi;
|
||||
//#endregion
|
||||
|
||||
//#region HTML objects
|
||||
video: any;
|
||||
element: any;
|
||||
overlayNode: any;
|
||||
//#endregion
|
||||
|
||||
//#region flags
|
||||
invalid: boolean = false;
|
||||
private periodicallyRefreshPlayerElement: boolean = false;
|
||||
halted: boolean = true;
|
||||
|
||||
//#region misc stuff
|
||||
extensionMode: any;
|
||||
dimensions: {width?: number, height?: number, fullscreen?: boolean};
|
||||
private playerIdElement: any;
|
||||
private observer: ResizeObserver;
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Gets player aspect ratio. If in full screen, it returns screen aspect ratio unless settings say otherwise.
|
||||
*/
|
||||
get aspectRatio() {
|
||||
try {
|
||||
if (this.dimensions?.fullscreen && !this.settings.getSettingsForSite()?.usePlayerArInFullscreen) {
|
||||
return window.innerWidth / window.innerHeight;
|
||||
}
|
||||
|
||||
return this.dimensions.width / this.dimensions.height;
|
||||
} catch (e) {
|
||||
console.error('cannot determine aspect ratio!', e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(videoData) {
|
||||
try {
|
||||
this.logger = videoData.logger;
|
||||
@ -76,18 +123,24 @@ class PlayerData {
|
||||
}
|
||||
}
|
||||
|
||||
async sleep(timeout) {
|
||||
return new Promise( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we're in fullscreen mode or not.
|
||||
*/
|
||||
static isFullScreen(){
|
||||
return ( window.innerHeight == window.screen.height && window.innerWidth == window.screen.width);
|
||||
const ihdiff = Math.abs(window.screen.height - window.innerHeight);
|
||||
const iwdiff = Math.abs(window.screen.width - window.innerWidth);
|
||||
|
||||
// Chrome on linux on X on mixed PPI displays may return ever so slightly different values
|
||||
// for innerHeight vs screen.height abd innerWidth vs. screen.width, probably courtesy of
|
||||
// fractional scaling or something. This means we'll give ourself a few px of margin — the
|
||||
// window elements visible in not-fullscreen are usually double digit px tall
|
||||
return ( ihdiff < 5 && iwdiff < 5 );
|
||||
}
|
||||
|
||||
// player size observer may not be strictly necessary here
|
||||
onPlayerDimensionsChanged(mutationList, observer, context) {
|
||||
if (context.checkPlayerSizeChange()) {
|
||||
context.videoData.resizer.restore();
|
||||
|
||||
onPlayerDimensionsChanged(mutationList?, observer?) {
|
||||
if (this?.checkPlayerSizeChange()) {
|
||||
this.videoData.resizer.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,8 +166,31 @@ class PlayerData {
|
||||
}
|
||||
|
||||
try {
|
||||
const ths = this;
|
||||
this.observer = new MutationObserver((m,o) => this.onPlayerDimensionsChanged(m,o,ths));
|
||||
if (BrowserDetect.firefox) {
|
||||
this.observer = new ResizeObserver(
|
||||
_.debounce( // don't do this too much:
|
||||
this.onPlayerDimensionsChanged,
|
||||
250, // do it once per this many ms
|
||||
{
|
||||
leading: true, // do it when we call this fallback first
|
||||
trailing: true // do it after the timeout if we call this callback few more times
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Chrome for some reason insists that this.onPlayerDimensionsChanged is not a function
|
||||
// when it's not wrapped into an anonymous function
|
||||
this.observer = new ResizeObserver(
|
||||
_.debounce( // don't do this too much:
|
||||
(m,o) => this.onPlayerDimensionsChanged(m,o),
|
||||
250, // do it once per this many ms
|
||||
{
|
||||
leading: true, // do it when we call this fallback first
|
||||
trailing: true // do it after the timeout if we call this callback few more times
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const observerConf = {
|
||||
attributes: true,
|
||||
@ -122,7 +198,7 @@ class PlayerData {
|
||||
attributeOldValue: true,
|
||||
};
|
||||
|
||||
this.observer.observe(this.element, observerConf);
|
||||
this.observer.observe(this.element);
|
||||
} catch (e) {
|
||||
console.error("failed to set observer",e )
|
||||
}
|
||||
@ -133,11 +209,11 @@ class PlayerData {
|
||||
|
||||
async legacyChangeDetection() {
|
||||
while (!this.halted) {
|
||||
await this.sleep(1000);
|
||||
await sleep(1000);
|
||||
try {
|
||||
this.doPeriodicPlayerElementChangeCheck();
|
||||
} catch (e) {
|
||||
console.error('[PlayerData::legacycd] this message is pretty high on the list of messages you shouldnt see', e);
|
||||
console.error('[PlayerData::legacycd] this message is pretty high on the list of messages you shouldn\'t see', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,7 +233,7 @@ class PlayerData {
|
||||
this.destroyOverlay();
|
||||
}
|
||||
|
||||
var overlay = document.createElement('div');
|
||||
let overlay = document.createElement('div');
|
||||
overlay.style.width = '100%';
|
||||
overlay.style.height = '100%';
|
||||
overlay.style.position = 'absolute';
|
||||
@ -243,7 +319,9 @@ class PlayerData {
|
||||
const videoWidth = this.video.offsetWidth;
|
||||
const videoHeight = this.video.offsetHeight;
|
||||
const elementQ = [];
|
||||
let scorePenalty = 0;
|
||||
const scorePenalty = 10;
|
||||
const sizePenaltyMultiplier = 0.1;
|
||||
let penaltyMultiplier = 0;
|
||||
let score;
|
||||
|
||||
try {
|
||||
@ -297,7 +375,7 @@ class PlayerData {
|
||||
}
|
||||
|
||||
// elements farther away from the video get a penalty
|
||||
score -= (scorePenalty++) * 20;
|
||||
score -= (scorePenalty) * 20;
|
||||
|
||||
// push the element on the queue/stack:
|
||||
elementQ.push({
|
||||
@ -324,7 +402,7 @@ class PlayerData {
|
||||
|
||||
// try to find element the old fashioned way
|
||||
|
||||
while (element){
|
||||
while (element){
|
||||
// odstranimo čudne elemente, ti bi pokvarili zadeve
|
||||
// remove weird elements, those would break our stuff
|
||||
if ( element.offsetWidth == 0 || element.offsetHeight == 0){
|
||||
@ -332,34 +410,41 @@ class PlayerData {
|
||||
continue;
|
||||
}
|
||||
|
||||
// element je player, če je ena stranica enako velika kot video, druga pa večja ali enaka.
|
||||
// za enakost dovolimo mala odstopanja
|
||||
// element is player, if one of the sides is as long as the video and the other bigger (or same)
|
||||
// we allow for tiny variations when checking for equality
|
||||
if ( (element.offsetWidth >= videoWidth && this.equalish(element.offsetHeight, videoHeight, 2))
|
||||
|| (element.offsetHeight >= videoHeight && this.equalish(element.offsetWidth, videoHeight, 2))) {
|
||||
|
||||
// todo — in case the match is only equalish and not exact, take difference into account when
|
||||
// calculating score
|
||||
|
||||
score = 100;
|
||||
// element is player, if at least one of the sides is as long as the video
|
||||
// note that we can't make any additional assumptions with regards to player
|
||||
// size, since there are both cases where the other side is bigger _and_ cases
|
||||
// where other side is smaller than the video.
|
||||
//
|
||||
// Don't bother thinking about this too much, as any "thinking" was quickly
|
||||
// corrected by bugs caused by various edge cases.
|
||||
if (
|
||||
this.equalish(element.offsetHeight, videoHeight, 5)
|
||||
|| this.equalish(element.offsetWidth, videoWidth, 5)
|
||||
) {
|
||||
score = 1000;
|
||||
|
||||
// This entire section is disabled because of some bullshit on vk and some shady CIS streaming sites.
|
||||
// Possibly removal of this criteria is not necessary, because there was also a bug with force player
|
||||
//
|
||||
// -------------------
|
||||
// PENALTIES
|
||||
// -------------------
|
||||
//
|
||||
// Our ideal player will be as close to the video element, and it will als
|
||||
// be as close to the size of the video.
|
||||
|
||||
// prefer elements closer to <video>
|
||||
score -= scorePenalty * penaltyMultiplier++;
|
||||
|
||||
// the bigger the size difference between the video and the player,
|
||||
// the more penalty we'll incur. Since we did some grace ith
|
||||
let playerSizePenalty = 1;
|
||||
if ( element.offsetHeight > (videoHeight + 5)) {
|
||||
playerSizePenalty = (element.offsetWidth - videoHeight) * sizePenaltyMultiplier;
|
||||
}
|
||||
if ( element.offsetWidth > (videoWidth + 5)) {
|
||||
playerSizePenalty *= (element.offsetWidth - videoWidth) * sizePenaltyMultiplier
|
||||
}
|
||||
|
||||
score -= playerSizePenalty;
|
||||
|
||||
// if (element.id.indexOf('player') !== -1) { // prefer elements with 'player' in id
|
||||
// score += 75;
|
||||
// }
|
||||
// this has only been observed on steam
|
||||
// if (element.id.indexOf('movie') !== -1) {
|
||||
// score += 75;
|
||||
// }
|
||||
// if (element.classList.toString().indexOf('player') !== -1) { // prefer elements with 'player' in classlist, but a bit less than id
|
||||
// score += 50;
|
||||
// }
|
||||
score -= scorePenalty++; // prefer elements closer to <video>
|
||||
|
||||
elementQ.push({
|
||||
element: element,
|
||||
score: score,
|
@ -2,11 +2,61 @@ import Debug from '../../conf/Debug';
|
||||
import PlayerData from './PlayerData';
|
||||
import Resizer from '../video-transform/Resizer';
|
||||
import ArDetector from '../ar-detect/ArDetector';
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import * as _ from 'lodash';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import PageInfo from './PageInfo';
|
||||
import { sleep } from '../../../common/js/utils';
|
||||
import { hasDrm } from '../ar-detect/DrmDetecor';
|
||||
|
||||
class VideoData {
|
||||
|
||||
//#region flags
|
||||
arSetupComplete: boolean = false;
|
||||
destroyed: boolean = false;
|
||||
invalid: boolean = false;
|
||||
videoStatusOk: boolean = false;
|
||||
videoLoaded: boolean = false;
|
||||
videoDimensionsLoaded: boolean = false;
|
||||
paused: boolean = false;
|
||||
//#endregion
|
||||
|
||||
//#region misc stuff
|
||||
vdid: string;
|
||||
video: any;
|
||||
observer: ResizeObserver;
|
||||
mutationObserver: MutationObserver;
|
||||
mutationObserverConf: MutationObserverInit = {
|
||||
attributes: true,
|
||||
attributeFilter: ['class', 'style'],
|
||||
attributeOldValue: true,
|
||||
};
|
||||
extensionMode: any;
|
||||
userCssClassName: string;
|
||||
validationId: number;
|
||||
dimensions: any;
|
||||
//#endregion
|
||||
|
||||
//#region helper objects
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
pageInfo: PageInfo;
|
||||
player: PlayerData;
|
||||
resizer: Resizer;
|
||||
arDetector: ArDetector;
|
||||
//#endregion
|
||||
|
||||
|
||||
get aspectRatio() {
|
||||
try {
|
||||
return this.video.videoWidth / this.video.videoHeight;
|
||||
} catch (e) {
|
||||
console.error('cannot determine stream aspect ratio!', e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(video, settings, pageInfo){
|
||||
this.vdid = (Math.random()*100).toFixed();
|
||||
this.logger = pageInfo.logger;
|
||||
@ -23,6 +73,8 @@ class VideoData {
|
||||
this.videoLoaded = false;
|
||||
this.videoDimensionsLoaded = true;
|
||||
|
||||
this.validationId = null;
|
||||
|
||||
this.dimensions = {
|
||||
width: this.video.offsetWidth,
|
||||
height: this.video.offsetHeight,
|
||||
@ -33,6 +85,18 @@ class VideoData {
|
||||
|
||||
async onVideoLoaded() {
|
||||
if (!this.videoLoaded) {
|
||||
|
||||
/**
|
||||
* video.readyState 101:
|
||||
* 0 — no info. Can't play.
|
||||
* 1 — we have metadata but nothing else
|
||||
* 2 — we have data for current playback position, but not future <--- meaning current frame, meaning Aard can work here or higher
|
||||
* 3 — we have a lil bit for the future
|
||||
* 4 — we'll survive to the end
|
||||
*/
|
||||
if (!this.video?.videoWidth || !this.video?.videoHeight || this.video.readyState < 2) {
|
||||
return; // onVideoLoaded is a lie in this case
|
||||
}
|
||||
this.logger.log('info', 'init', '%c[VideoData::onVideoLoaded] ——————————— Initiating phase two of videoData setup ———————————', 'color: #0f9');
|
||||
|
||||
this.videoLoaded = true;
|
||||
@ -48,59 +112,79 @@ class VideoData {
|
||||
|
||||
this.restoreCrop();
|
||||
this.videoDimensionsLoaded = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
videoUnloaded() {
|
||||
this.videoLoaded = false;
|
||||
}
|
||||
|
||||
async injectBaseCss() {
|
||||
try {
|
||||
await this.pageInfo.injectCss(`
|
||||
.uw-ultrawidify-base-wide-screen {
|
||||
margin: 0px 0px 0px 0px !important;
|
||||
width: initial !important;
|
||||
align-self: start !important;
|
||||
justify-self: start !important;
|
||||
if (!this.mutationObserver) {
|
||||
this.setupMutationObserver();
|
||||
}
|
||||
`);
|
||||
await this.pageInfo.injectCss(`
|
||||
.uw-ultrawidify-base-wide-screen {
|
||||
margin: 0px 0px 0px 0px !important;
|
||||
width: initial !important;
|
||||
align-self: start !important;
|
||||
justify-self: start !important;
|
||||
max-height: initial !important;
|
||||
max-width: initial !important;
|
||||
}
|
||||
`);
|
||||
} catch (e) {
|
||||
console.error('Failed to inject base css!', e);
|
||||
}
|
||||
}
|
||||
|
||||
unsetBaseClass() {
|
||||
this.mutationObserver.disconnect();
|
||||
this.mutationObserver = undefined;
|
||||
this.video.classList.remove('uw-ultrawidify-base-wide-screen');
|
||||
}
|
||||
|
||||
//#region <video> event handlers
|
||||
onLoadedData() {
|
||||
this.logger.log('info', 'init', '[VideoData::ctor->video.onloadeddata] Video fired event "loaded data!"');
|
||||
this.onVideoLoaded();
|
||||
}
|
||||
onLoadedMetadata() {
|
||||
this.logger.log('info', 'init', '[VideoData::ctor->video.onloadedmetadata] Video fired event "loaded metadata!"');
|
||||
this.onVideoLoaded();
|
||||
}
|
||||
onTimeUpdate() {
|
||||
this.onVideoLoaded();
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region lifecycle-ish
|
||||
/**
|
||||
* Injects base CSS and sets up handlers for <video> tag events
|
||||
*/
|
||||
async setupStageOne() {
|
||||
this.logger.log('info', 'init', '%c[VideoData::setupStageOne] ——————————— Starting setup stage one! ———————————', 'color: #0f9');
|
||||
// ensure base css is loaded before doing anything
|
||||
this.injectBaseCss();
|
||||
|
||||
// this is in case extension loads before the video
|
||||
this.video.addEventListener('loadeddata', () => {
|
||||
this.logger.log('info', 'init', '[VideoData::ctor->video.onloadeddata] Video fired event "loaded data!"');
|
||||
this.onVideoLoaded();
|
||||
});
|
||||
this.video.addEventListener('loadeddata', this.onLoadedData.bind(this));
|
||||
this.video.addEventListener('loadedmetadata', this.onLoadedMetadata.bind(this));
|
||||
|
||||
// this one is in case extension loads after the video is loaded
|
||||
this.video.addEventListener('timeupdate', () => {
|
||||
this.onVideoLoaded();
|
||||
});
|
||||
this.video.addEventListener('timeupdate', this.onTimeUpdate.bind(this));
|
||||
|
||||
this.logger.log('info', 'init', '%c[VideoData::setupStageOne] ——————————— Setup stage one complete! ———————————', 'color: #0f9');
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the extension for a given video (after the video element is defined well enough
|
||||
* for our standards)
|
||||
*/
|
||||
async setupStageTwo() {
|
||||
// POZOR: VRSTNI RED JE POMEMBEN (arDetect mora bit zadnji)
|
||||
// NOTE: ORDERING OF OBJ INITIALIZATIONS IS IMPORTANT (arDetect needs to go last)
|
||||
|
||||
// NOTE: We only init observers once player is confirmed valid
|
||||
const observerConf = {
|
||||
attributes: true,
|
||||
// attributeFilter: ['style', 'class'],
|
||||
attributeOldValue: true,
|
||||
};
|
||||
|
||||
this.player = new PlayerData(this);
|
||||
if (this.player.invalid) {
|
||||
this.invalid = true;
|
||||
@ -110,11 +194,38 @@ class VideoData {
|
||||
this.resizer = new Resizer(this);
|
||||
|
||||
// INIT OBSERVERS
|
||||
this.observer = new MutationObserver( (m, o) => {
|
||||
this.logger.log('info', 'debug', `[VideoData::setupStageTwo->mutationObserver] Mutation observer detected a mutation:`, {m, o});
|
||||
this.onVideoDimensionsChanged(m, o, this)
|
||||
});
|
||||
this.observer.observe(this.video, observerConf);
|
||||
try {
|
||||
if (BrowserDetect.firefox) {
|
||||
this.observer = new ResizeObserver(
|
||||
_.debounce(
|
||||
this.onVideoDimensionsChanged,
|
||||
250,
|
||||
{
|
||||
leading: true,
|
||||
trailing: true
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Chrome for some reason insists that this.onPlayerDimensionsChanged is not a function
|
||||
// when it's not wrapped into an anonymous function
|
||||
this.observer = new ResizeObserver(
|
||||
_.debounce(
|
||||
(m, o) => {
|
||||
this.onVideoDimensionsChanged(m, o)
|
||||
},
|
||||
250,
|
||||
{
|
||||
leading: true,
|
||||
trailing: true
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[VideoData] Observer setup failed:', e);
|
||||
}
|
||||
this.observer.observe(this.video);
|
||||
|
||||
// INIT AARD
|
||||
this.arDetector = new ArDetector(this); // this starts Ar detection. needs optional parameter that prevets ardetdctor from starting
|
||||
@ -158,6 +269,87 @@ class VideoData {
|
||||
}
|
||||
}
|
||||
|
||||
setupMutationObserver() {
|
||||
try {
|
||||
if (BrowserDetect.firefox) {
|
||||
this.mutationObserver = new MutationObserver(
|
||||
_.debounce(
|
||||
this.onVideoMutation,
|
||||
250,
|
||||
{
|
||||
leading: true,
|
||||
trailing: true
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// Chrome for some reason insists that this.onPlayerDimensionsChanged is not a function
|
||||
// when it's not wrapped into an anonymous function
|
||||
this.mutationObserver = new MutationObserver(
|
||||
_.debounce(
|
||||
(m, o) => {
|
||||
this.onVideoMutation(m, o)
|
||||
},
|
||||
250,
|
||||
{
|
||||
leading: true,
|
||||
trailing: true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[VideoData] Observer setup failed:', e);
|
||||
}
|
||||
this.mutationObserver.observe(this.video, this.mutationObserverConf);
|
||||
}
|
||||
|
||||
/**
|
||||
* cleans up handlers and stuff when the show is over
|
||||
*/
|
||||
destroy() {
|
||||
this.logger.log('info', ['debug', 'init'], `[VideoData::destroy] <vdid:${this.vdid}> received destroy command`);
|
||||
|
||||
if (this.video) {
|
||||
this.video.classList.remove(this.userCssClassName);
|
||||
this.video.classList.remove('uw-ultrawidify-base-wide-screen');
|
||||
|
||||
this.video.removeEventListener('onloadeddata', this.onLoadedData);
|
||||
this.video.removeEventListener('onloadedmetadata', this.onLoadedMetadata);
|
||||
this.video.removeEventListener('ontimeupdate', this.onTimeUpdate);
|
||||
}
|
||||
|
||||
this.pause();
|
||||
this.destroyed = true;
|
||||
try {
|
||||
this.arDetector.stop();
|
||||
this.arDetector.destroy();
|
||||
} catch (e) {}
|
||||
this.arDetector = undefined;
|
||||
try {
|
||||
this.resizer.destroy();
|
||||
} catch (e) {}
|
||||
this.resizer = undefined;
|
||||
try {
|
||||
this.player.destroy();
|
||||
} catch (e) {}
|
||||
try {
|
||||
this.observer.disconnect();
|
||||
} catch (e) {}
|
||||
this.player = undefined;
|
||||
this.video = undefined;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region video status
|
||||
isVideoPlaying() {
|
||||
return this.video && !!(this.video.currentTime > 0 && !this.video.paused && !this.video.ended && this.video.readyState > 2);
|
||||
}
|
||||
|
||||
hasVideoStartedPlaying() {
|
||||
return this.video && this.video.currentTime > 0;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
restoreCrop() {
|
||||
this.logger.log('info', 'debug', '[VideoData::restoreCrop] Attempting to reset aspect ratio.')
|
||||
@ -166,7 +358,7 @@ class VideoData {
|
||||
if (this.pageInfo.defaultCrop) {
|
||||
this.resizer.setAr(this.pageInfo.defaultCrop);
|
||||
} else {
|
||||
this.resizer.reset();
|
||||
this.resizer.reset();
|
||||
|
||||
try {
|
||||
this.stopArDetection();
|
||||
@ -177,9 +369,15 @@ class VideoData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts fallback change detection (validates whether currently applied settings are correct)
|
||||
*/
|
||||
async fallbackChangeDetection() {
|
||||
while (!this.destroyed && !this.invalid) {
|
||||
await this.sleep(500);
|
||||
const validationId = Date.now();
|
||||
this.validationId = validationId;
|
||||
|
||||
while (!this.destroyed && !this.invalid && this.validationId === validationId) {
|
||||
await sleep(500);
|
||||
this.doPeriodicFallbackChangeDetectionCheck();
|
||||
}
|
||||
}
|
||||
@ -188,59 +386,84 @@ class VideoData {
|
||||
this.validateVideoOffsets();
|
||||
}
|
||||
|
||||
async sleep(timeout) {
|
||||
return new Promise( (resolve) => setTimeout(() => resolve(), timeout));
|
||||
onVideoMutation(mutationList?: MutationRecord[], observer?) {
|
||||
// verify that mutation didn't remove our class. Some pages like to do that.
|
||||
let confirmAspectRatioRestore = false;
|
||||
|
||||
if (!this.video) {
|
||||
this.logger.log('error', 'debug', '[VideoData::onVideoMutation] mutation was triggered, but video element is missing. Something is fishy. Terminating this uw instance.');
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
for(const mutation of mutationList) {
|
||||
if (mutation.type === 'attributes') {
|
||||
if( mutation.attributeName === 'class'
|
||||
&& mutation.oldValue.indexOf('uw-ultrawidify-base-wide-screen') !== -1
|
||||
&& !this.video.classList.contains('uw-ultrawidify-base-wide-screen')
|
||||
) {
|
||||
// force the page to include our class in classlist, if the classlist has been removed
|
||||
// while classList.add() doesn't duplicate classes (does nothing if class is already added),
|
||||
// we still only need to make sure we're only adding our class to classlist if it has been
|
||||
// removed. classList.add() will _still_ trigger mutation (even if classlist wouldn't change).
|
||||
// This is a problem because INFINITE RECURSION TIME, and we _really_ don't want that.
|
||||
|
||||
confirmAspectRatioRestore = true;
|
||||
this.video.classList.add(this.userCssClassName);
|
||||
this.video.classList.add('uw-ultrawidify-base-wide-screen');
|
||||
} else if (mutation.attributeName === 'style') {
|
||||
confirmAspectRatioRestore = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.processDimensionsChanged();
|
||||
}
|
||||
|
||||
|
||||
onVideoDimensionsChanged(mutationList, observer, context) {
|
||||
if (!mutationList || context.video === undefined) { // something's wrong
|
||||
if (observer && context.video) {
|
||||
onVideoDimensionsChanged(mutationList, observer) {
|
||||
if (!mutationList || this.video === undefined) { // something's wrong
|
||||
if (observer && this.video) {
|
||||
observer.disconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
let confirmAspectRatioRestore = false;
|
||||
|
||||
for (let mutation of mutationList) {
|
||||
if (mutation.type === 'attributes') {
|
||||
if (mutation.attributeName === 'class') {
|
||||
if(!context.video.classList.contains(this.userCssClassName) ) {
|
||||
// force the page to include our class in classlist, if the classlist has been removed
|
||||
// while classList.add() doesn't duplicate classes (does nothing if class is already added),
|
||||
// we still only need to make sure we're only adding our class to classlist if it has been
|
||||
// removed. classList.add() will _still_ trigger mutation (even if classlist wouldn't change).
|
||||
// This is a problem because INFINITE RECURSION TIME, and we _really_ don't want that.
|
||||
context.video.classList.add(this.userCssClassName);
|
||||
context.video.classList.add('uw-ultrawidify-base-wide-screen');
|
||||
}
|
||||
// always trigger refresh on class changes, since change of classname might trigger change
|
||||
// of the player size as well.
|
||||
confirmAspectRatioRestore = true;
|
||||
}
|
||||
if (mutation.attributeName === 'style') {
|
||||
confirmAspectRatioRestore = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!confirmAspectRatioRestore) {
|
||||
return;
|
||||
}
|
||||
this.processDimensionsChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces Ultrawidify to resotre aspect ratio. You should never call this method directly,
|
||||
* instead you should be calling processDimensionChanged() wrapper function.
|
||||
*/
|
||||
private _processDimensionsChanged() {
|
||||
// adding player observer taught us that if element size gets triggered by a class, then
|
||||
// the 'style' attributes don't necessarily trigger. This means we also need to trigger
|
||||
// restoreAr here, in case video size was changed this way
|
||||
context.player.forceRefreshPlayerElement();
|
||||
context.restoreAr();
|
||||
this.player.forceRefreshPlayerElement();
|
||||
this.restoreAr();
|
||||
|
||||
// sometimes something fucky wucky happens and mutations aren't detected correctly, so we
|
||||
// try to get around that
|
||||
setTimeout( () => {
|
||||
context.validateVideoOffsets();
|
||||
this.validateVideoOffsets();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores aspect ratio and validates video offsets after the restore. Execution uses
|
||||
* debounce to limit how often the function executes.
|
||||
*/
|
||||
private processDimensionsChanged() {
|
||||
_.debounce(
|
||||
this._processDimensionsChanged,
|
||||
250,
|
||||
{
|
||||
leading: true,
|
||||
trailing: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
validateVideoOffsets() {
|
||||
// validate if current video still exists. If not, we destroy current object
|
||||
try {
|
||||
@ -251,17 +474,17 @@ class VideoData {
|
||||
} catch (e) {
|
||||
}
|
||||
// THIS BREAKS PANNING
|
||||
const cs = window.getComputedStyle(this.video);
|
||||
const pcs = window.getComputedStyle(this.player.element);
|
||||
const videoComputedStyle = window.getComputedStyle(this.video);
|
||||
const playerComputedStyle = window.getComputedStyle(this.player.element);
|
||||
|
||||
try {
|
||||
const transformMatrix = cs.transform.split(')')[0].split(',');
|
||||
const transformMatrix = videoComputedStyle.transform.split(')')[0].split(',');
|
||||
const translateX = +transformMatrix[4];
|
||||
const translateY = +transformMatrix[5];
|
||||
const vh = +(cs.height.split('px')[0]);
|
||||
const vw = +(cs.width.split('px')[0]);
|
||||
const ph = +(pcs.height.split('px')[0]);
|
||||
const pw = +(pcs.width.split('px')[0]);
|
||||
const vh = +(videoComputedStyle.height.split('px')[0]);
|
||||
const vw = +(videoComputedStyle.width.split('px')[0]);
|
||||
const ph = +(playerComputedStyle.height.split('px')[0]);
|
||||
const pw = +(playerComputedStyle.width.split('px')[0]);
|
||||
|
||||
// TODO: check & account for panning and alignment
|
||||
if (transformMatrix[0] !== 'none'
|
||||
@ -269,10 +492,11 @@ class VideoData {
|
||||
&& this.isWithin(vw, (pw - (translateX * 2)), 2)) {
|
||||
} else {
|
||||
this.player.forceDetectPlayerElementChange();
|
||||
this.restoreAr();
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
// do nothing on fail
|
||||
console.error('Validating video offsets failed:', e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,7 +508,7 @@ class VideoData {
|
||||
* Gets the contents of the style attribute of the video element
|
||||
* in a form of an object.
|
||||
*/
|
||||
getVideoStyle() {
|
||||
getVideoStyle(): any {
|
||||
// This will _always_ give us an array. Empty string gives an array
|
||||
// that contains one element. That element is an empty string.
|
||||
const styleArray = (this.video.getAttribute('style') || '').split(';');
|
||||
@ -326,8 +550,6 @@ class VideoData {
|
||||
}
|
||||
return heightCompensationFactor;
|
||||
}
|
||||
|
||||
|
||||
firstTimeArdInit(){
|
||||
if(this.destroyed || this.invalid) {
|
||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
||||
@ -358,6 +580,11 @@ class VideoData {
|
||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasDrm(this.video)) {
|
||||
this.player.showNotification('AARD_DRM');
|
||||
}
|
||||
|
||||
if (!this.arDetector) {
|
||||
this.initArDetection();
|
||||
}
|
||||
@ -378,35 +605,6 @@ class VideoData {
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.logger.log('info', ['debug', 'init'], `[VideoData::destroy] <vdid:${this.vdid}> received destroy command`);
|
||||
|
||||
if (this.video) {
|
||||
this.video.classList.remove(this.userCssClassName);
|
||||
this.video.classList.remove('uw-ultrawidify-base-wide-screen');
|
||||
}
|
||||
|
||||
this.pause();
|
||||
this.destroyed = true;
|
||||
try {
|
||||
this.arDetector.stop();
|
||||
this.arDetector.destroy();
|
||||
} catch (e) {}
|
||||
this.arDetector = undefined;
|
||||
try {
|
||||
this.resizer.destroy();
|
||||
} catch (e) {}
|
||||
this.resizer = undefined;
|
||||
try {
|
||||
this.player.destroy();
|
||||
} catch (e) {}
|
||||
try {
|
||||
this.observer.disconnect();
|
||||
} catch (e) {}
|
||||
this.player = undefined;
|
||||
this.video = undefined;
|
||||
}
|
||||
|
||||
pause(){
|
||||
this.paused = true;
|
||||
if(this.arDetector){
|
||||
@ -424,7 +622,7 @@ class VideoData {
|
||||
}
|
||||
this.paused = false;
|
||||
try {
|
||||
this.resizer.start();
|
||||
// this.resizer.start();
|
||||
if (this.player) {
|
||||
this.player.start();
|
||||
}
|
||||
@ -459,12 +657,12 @@ class VideoData {
|
||||
this.resizer.setLastAr(lastAr);
|
||||
}
|
||||
|
||||
setAr(ar, lastAr){
|
||||
setAr(ar, lastAr?){
|
||||
if (this.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ar.type === AspectRatio.Fixed || ar.type === AspectRatio.FitHeight || ar.type === AspectRatio.FitHeight) {
|
||||
if (ar.type === AspectRatioType.Fixed || ar.type === AspectRatioType.FitHeight || ar.type === AspectRatioType.FitHeight) {
|
||||
this.player.forceRefreshPlayerElement();
|
||||
}
|
||||
|
||||
@ -485,7 +683,7 @@ class VideoData {
|
||||
this.resizer.setLastAr('original');
|
||||
}
|
||||
|
||||
panHandler(event, forcePan) {
|
||||
panHandler(event, forcePan?: boolean) {
|
||||
if (this.invalid) {
|
||||
return;
|
||||
}
|
7
src/ext/lib/video-data/enums/RescanReason.enum.ts
Normal file
7
src/ext/lib/video-data/enums/RescanReason.enum.ts
Normal file
@ -0,0 +1,7 @@
|
||||
enum RescanReason {
|
||||
PERIODIC = 0,
|
||||
URL_CHANGE = 1,
|
||||
MANUAL = 2
|
||||
};
|
||||
|
||||
export default RescanReason;
|
@ -1,7 +0,0 @@
|
||||
var RescanReason = Object.freeze({
|
||||
PERIODIC: 0,
|
||||
URL_CHANGE: 1,
|
||||
MANUAL: 2
|
||||
});
|
||||
|
||||
export default RescanReason;
|
@ -24,7 +24,7 @@ class CssHandler {
|
||||
}
|
||||
}
|
||||
|
||||
for (var i in styleArray) {
|
||||
for (let 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
|
||||
@ -45,7 +45,7 @@ class CssHandler {
|
||||
static buildStyleString(styleArray) {
|
||||
let styleString = '';
|
||||
|
||||
for(var i in styleArray) {
|
||||
for(let i in styleArray) {
|
||||
if(styleArray[i]) {
|
||||
styleString += styleArray[i] + "; ";
|
||||
}
|
@ -1,42 +1,67 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import Scaler from './Scaler';
|
||||
import Scaler, { CropStrategy, VideoDimensions } 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';
|
||||
import CropModePersistance from '../../../common/enums/crop-mode-persistence.enum';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
import StretchType from '../../../common/enums/StretchType.enum';
|
||||
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import CropModePersistance from '../../../common/enums/CropModePersistence.enum';
|
||||
import { sleep } from '../Util';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("Loading: Resizer.js");
|
||||
}
|
||||
|
||||
class Resizer {
|
||||
|
||||
//#region flags
|
||||
canPan: boolean = false;
|
||||
destroyed: boolean = false;
|
||||
//#endregion
|
||||
|
||||
//#region helper objects
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
scaler: Scaler;
|
||||
stretcher: Stretcher;
|
||||
zoom: Zoom;
|
||||
conf: VideoData;
|
||||
//#endregion
|
||||
|
||||
//#region HTML elements
|
||||
video: any;
|
||||
//#endregion
|
||||
|
||||
//#region data
|
||||
correctedVideoDimensions: any;
|
||||
currentCss: any;
|
||||
currentStyleString: string;
|
||||
currentPlayerStyleString: any;
|
||||
currentCssValidFor: any;
|
||||
currentVideoSettings: any;
|
||||
lastAr: {type: any, ratio?: number} = {type: AspectRatioType.Initial};
|
||||
resizerId: any;
|
||||
videoAlignment: any;
|
||||
userCss: string;
|
||||
userCssClassName: any;
|
||||
pan: any = null;
|
||||
//#endregion
|
||||
|
||||
constructor(videoData) {
|
||||
this.resizerId = (Math.random(99)*100).toFixed(0);
|
||||
this.resizerId = (Math.random()*100).toFixed(0);
|
||||
this.conf = videoData;
|
||||
this.logger = videoData.logger;
|
||||
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);
|
||||
|
||||
// load up default values
|
||||
this.correctedVideoDimensions = {};
|
||||
this.currentCss = {};
|
||||
this.currentStyleString = "";
|
||||
this.currentPlayerStyleString = "";
|
||||
this.currentCssValidFor = {};
|
||||
|
||||
this.lastAr = {type: AspectRatio.Initial};
|
||||
this.videoAlignment = this.settings.getDefaultVideoAlignment(window.location.hostname); // this is initial video alignment
|
||||
this.destroyed = false;
|
||||
|
||||
@ -47,7 +72,6 @@ class Resizer {
|
||||
this.canPan = false;
|
||||
}
|
||||
|
||||
this.userCss = '';
|
||||
this.userCssClassName = videoData.userCssClassName;
|
||||
}
|
||||
|
||||
@ -74,13 +98,13 @@ class Resizer {
|
||||
|
||||
calculateRatioForLegacyOptions(ar){
|
||||
// also present as modeToAr in Scaler.js
|
||||
if (ar.type !== AspectRatio.FitWidth && ar.type !== AspectRatio.FitHeight && ar.ratio) {
|
||||
if (ar.type !== AspectRatioType.FitWidth && ar.type !== AspectRatioType.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;
|
||||
// handles "legacy" options, such as 'fit to widht', 'fit to height' and AspectRatioType.Reset. No zoom tho
|
||||
let ratioOut;
|
||||
|
||||
if (!this.conf.video) {
|
||||
this.logger.log('info', 'debug', "[Scaler.js::modeToAr] No video??",this.conf.video, "killing videoData");
|
||||
@ -102,15 +126,15 @@ class Resizer {
|
||||
// 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;
|
||||
let fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
|
||||
if (ar.type === AspectRatio.FitWidth){
|
||||
if (ar.type === AspectRatioType.FitWidth){
|
||||
ar.ratio = ratioOut > fileAr ? ratioOut : fileAr;
|
||||
}
|
||||
else if(ar.type === AspectRatio.FitHeight){
|
||||
else if(ar.type === AspectRatioType.FitHeight){
|
||||
ar.ratio = ratioOut < fileAr ? ratioOut : fileAr;
|
||||
}
|
||||
else if(ar.type === AspectRatio.Reset){
|
||||
else if(ar.type === AspectRatioType.Reset){
|
||||
this.logger.log('info', 'debug', "[Scaler.js::modeToAr] Using original aspect ratio -", fileAr);
|
||||
ar.ratio = fileAr;
|
||||
} else {
|
||||
@ -128,7 +152,7 @@ class Resizer {
|
||||
|
||||
// Some options require a bit more testing re: whether they make sense
|
||||
// if they don't, we refuse to update aspect ratio until they do
|
||||
if (ar.type === AspectRatio.Automatic || ar.type === AspectRatio.Fixed) {
|
||||
if (ar.type === AspectRatioType.Automatic || ar.type === AspectRatioType.Fixed) {
|
||||
if (!ar.ratio || isNaN(ar.ratio)) {
|
||||
return;
|
||||
}
|
||||
@ -140,27 +164,29 @@ class Resizer {
|
||||
}
|
||||
}
|
||||
|
||||
async setAr(ar, lastAr) {
|
||||
async setAr(ar: {type: any, ratio?: number}, lastAr?: {type: any, ratio?: number}) {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.video.videoWidth || !this.video.videoHeight) {
|
||||
this.logger.log('warning', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> Video has no width or no height. This is not allowed. Aspect ratio will not be set.');
|
||||
this.logger.log('warning', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> Video has no width or no height. This is not allowed. Aspect ratio will not be set, and videoData will be uninitialized.');
|
||||
this.conf.videoUnloaded();
|
||||
}
|
||||
|
||||
this.logger.log('info', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> trying to set ar. New ar:', ar)
|
||||
this.logger.log('info', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> trying to set ar. New ar:', ar);
|
||||
|
||||
if (ar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const siteSettings = this.settings.active.sites[window.location.hostname];
|
||||
let stretchFactors: {xFactor: number, yFactor: number, arCorrectionFactor?: number, ratio?: number} | any;
|
||||
|
||||
// reset zoom, but only on aspect ratio switch. We also know that aspect ratio gets converted to
|
||||
// AspectRatio.Fixed when zooming, so let's keep that in mind
|
||||
// AspectRatioType.Fixed when zooming, so let's keep that in mind
|
||||
if (
|
||||
(ar.type !== AspectRatio.Fixed && ar.type !== AspectRatio.Manual) // anything not these two _always_ changes AR
|
||||
(ar.type !== AspectRatioType.Fixed && ar.type !== AspectRatioType.Manual) // anything not these two _always_ changes AR
|
||||
|| ar.type !== this.lastAr.type // this also means aspect ratio has changed
|
||||
|| ar.ratio !== this.lastAr.ratio // this also means aspect ratio has changed
|
||||
) {
|
||||
@ -172,51 +198,15 @@ class Resizer {
|
||||
// this means here's the optimal place to set or forget aspect ratio. Saving of current crop ratio
|
||||
// is handled in pageInfo.updateCurrentCrop(), which also makes sure to persist aspect ratio if ar
|
||||
// is set to persist between videos / through current session / until manual reset.
|
||||
if (ar.type === AspectRatio.Automatic ||
|
||||
ar.type === AspectRatio.Reset ||
|
||||
ar.type === AspectRatio.Initial ) {
|
||||
if (ar.type === AspectRatioType.Automatic ||
|
||||
ar.type === AspectRatioType.Reset ||
|
||||
ar.type === AspectRatioType.Initial ) {
|
||||
// reset/undo default
|
||||
this.conf.pageInfo.updateCurrentCrop(undefined);
|
||||
} else {
|
||||
this.conf.pageInfo.updateCurrentCrop(ar);
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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) {
|
||||
// this.logger.log('error', 'debug', "%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.
|
||||
// this.logger.log('info', 'debug', "%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);
|
||||
@ -231,7 +221,7 @@ class Resizer {
|
||||
this.lastAr = {type: ar.type, ratio: ar.ratio}
|
||||
}
|
||||
|
||||
// if (this.extensionMode === ExtensionMode.Basic && !PlayerData.isFullScreen() && ar.type !== AspectRatio.Reset) {
|
||||
// if (this.extensionMode === ExtensionMode.Basic && !PlayerData.isFullScreen() && ar.type !== AspectRatioType.Reset) {
|
||||
// // don't actually apply or calculate css when using basic mode if not in fullscreen
|
||||
// // ... unless we're resetting the aspect ratio to original
|
||||
// return;
|
||||
@ -246,20 +236,20 @@ class Resizer {
|
||||
// * ar.type is auto, but stretch is set to basic basic stretch
|
||||
//
|
||||
// unpause when using other modes
|
||||
if (ar.type !== AspectRatio.Automatic || this.stretcher.mode === Stretch.Basic) {
|
||||
if (ar.type !== AspectRatioType.Automatic || this.stretcher.mode === StretchType.Basic) {
|
||||
this.conf?.arDetector?.pause();
|
||||
} else {
|
||||
if (this.lastAr.type === AspectRatio.Automatic) {
|
||||
if (this.lastAr.type === AspectRatioType.Automatic) {
|
||||
this.conf?.arDetector?.unpause();
|
||||
}
|
||||
}
|
||||
|
||||
// do stretch thingy
|
||||
if (this.stretcher.mode === Stretch.NoStretch
|
||||
|| this.stretcher.mode === Stretch.Conditional
|
||||
|| this.stretcher.mode === Stretch.FixedSource){
|
||||
if (this.stretcher.mode === StretchType.NoStretch
|
||||
|| this.stretcher.mode === StretchType.Conditional
|
||||
|| this.stretcher.mode === StretchType.FixedSource){
|
||||
|
||||
var stretchFactors = this.scaler.calculateCrop(ar);
|
||||
stretchFactors = this.scaler.calculateCrop(ar);
|
||||
|
||||
if(! stretchFactors || stretchFactors.error){
|
||||
this.logger.log('error', 'debug', `[Resizer::setAr] <rid:${this.resizerId}> failed to set AR due to problem with calculating crop. Error:`, stretchFactors?.error);
|
||||
@ -278,33 +268,36 @@ class Resizer {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.stretcher.mode === Stretch.Conditional){
|
||||
if (this.stretcher.mode === StretchType.Conditional){
|
||||
this.stretcher.applyConditionalStretch(stretchFactors, ar.ratio);
|
||||
} else if (this.stretcher.mode === Stretch.FixedSource) {
|
||||
} else if (this.stretcher.mode === StretchType.FixedSource) {
|
||||
this.stretcher.applyStretchFixedSource(stretchFactors);
|
||||
}
|
||||
this.logger.log('info', 'debug', "[Resizer::setAr] Processed stretch factors for ",
|
||||
this.stretcher.mode === Stretch.NoStretch ? 'stretch-free crop.' :
|
||||
this.stretcher.mode === Stretch.Conditional ? 'crop with conditional stretch.' : 'crop with fixed stretch',
|
||||
this.stretcher.mode === StretchType.NoStretch ? 'stretch-free crop.' :
|
||||
this.stretcher.mode === StretchType.Conditional ? 'crop with conditional StretchType.' : 'crop with fixed stretch',
|
||||
'Stretch factors are:', stretchFactors
|
||||
);
|
||||
|
||||
} else if (this.stretcher.mode === Stretch.Hybrid) {
|
||||
var stretchFactors = this.stretcher.calculateStretch(ar.ratio);
|
||||
} else if (this.stretcher.mode === StretchType.Hybrid) {
|
||||
stretchFactors = this.stretcher.calculateStretch(ar.ratio);
|
||||
this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for hybrid stretch/crop. Stretch factors are:', stretchFactors);
|
||||
} else if (this.stretcher.mode === Stretch.Fixed) {
|
||||
var stretchFactors = this.stretchFactors.calculateStretchFixed(ar.ratio)
|
||||
} else if (this.stretcher.mode === Stretch.Basic) {
|
||||
var stretchFactors = this.stretcher.calculateBasicStretch();
|
||||
this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for basic stretch. Stretch factors are:', stretchFactors);
|
||||
} else if (this.stretcher.mode === StretchType.Fixed) {
|
||||
stretchFactors = this.stretcher.calculateStretchFixed(ar.ratio)
|
||||
} else if (this.stretcher.mode === StretchType.Basic) {
|
||||
stretchFactors = this.stretcher.calculateBasicStretch();
|
||||
this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for basic StretchType. Stretch factors are:', stretchFactors);
|
||||
} else {
|
||||
var stretchFactors = {xFactor: 1, yFactor: 1};
|
||||
stretchFactors = {xFactor: 1, yFactor: 1};
|
||||
this.logger.log('error', 'debug', '[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);
|
||||
|
||||
var translate = this.computeOffsets(stretchFactors);
|
||||
this.stretcher.chromeBugMitigation(stretchFactors);
|
||||
|
||||
let translate = this.computeOffsets(stretchFactors);
|
||||
|
||||
this.applyCss(stretchFactors, translate);
|
||||
}
|
||||
|
||||
@ -312,13 +305,13 @@ class Resizer {
|
||||
toFixedAr() {
|
||||
// converting to fixed AR means we also turn off autoAR
|
||||
this.setAr({
|
||||
ar: this.lastAr.ar,
|
||||
type: AspectRatio.Fixed
|
||||
ratio: this.lastAr.ratio,
|
||||
type: AspectRatioType.Fixed
|
||||
});
|
||||
}
|
||||
|
||||
resetLastAr() {
|
||||
this.lastAr = {type: AspectRatio.Initial};
|
||||
this.lastAr = {type: AspectRatioType.Initial};
|
||||
}
|
||||
|
||||
setLastAr(override){
|
||||
@ -329,7 +322,7 @@ class Resizer {
|
||||
return this.lastAr;
|
||||
}
|
||||
|
||||
setStretchMode(stretchMode, fixedStretchRatio){
|
||||
setStretchMode(stretchMode, fixedStretchRatio?){
|
||||
this.stretcher.setStretchMode(stretchMode, fixedStretchRatio);
|
||||
this.restore();
|
||||
}
|
||||
@ -340,10 +333,10 @@ class Resizer {
|
||||
return;
|
||||
}
|
||||
// dont allow weird floats
|
||||
this.videoAlignment = VideoAlignment.Center;
|
||||
this.videoAlignment = VideoAlignmentType.Center;
|
||||
|
||||
// because non-fixed aspect ratios reset panning:
|
||||
if (this.lastAr.type !== AspectRatio.Fixed) {
|
||||
if (this.lastAr.type !== AspectRatioType.Fixed) {
|
||||
this.toFixedAr();
|
||||
}
|
||||
|
||||
@ -359,7 +352,7 @@ class Resizer {
|
||||
}
|
||||
|
||||
resetPan() {
|
||||
this.pan = {};
|
||||
this.pan = {x: 0, y: 0};
|
||||
this.videoAlignment = this.settings.getDefaultVideoAlignment(window.location.hostname);
|
||||
}
|
||||
|
||||
@ -367,7 +360,7 @@ class Resizer {
|
||||
// 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 = {x: 0, y: 0};
|
||||
}
|
||||
|
||||
if (this.settings.active.miscSettings.mousePanReverseMouse) {
|
||||
@ -389,8 +382,8 @@ class Resizer {
|
||||
this.logger.log('info', 'debug', "[Resizer::restore] <rid:"+this.resizerId+"> attempting to restore aspect ratio", {'lastAr': this.lastAr} );
|
||||
|
||||
// this is true until we verify that css has actually been applied
|
||||
if(this.lastAr.type === AspectRatio.Initial){
|
||||
this.setAr({type: AspectRatio.Reset});
|
||||
if(this.lastAr.type === AspectRatioType.Initial){
|
||||
this.setAr({type: AspectRatioType.Reset});
|
||||
}
|
||||
else {
|
||||
if (this.lastAr?.ratio === null) {
|
||||
@ -406,7 +399,7 @@ class Resizer {
|
||||
this.setStretchMode(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});
|
||||
this.setAr({type: AspectRatioType.Reset});
|
||||
}
|
||||
|
||||
setPanMode(mode) {
|
||||
@ -419,10 +412,6 @@ class Resizer {
|
||||
}
|
||||
}
|
||||
|
||||
resetPan(){
|
||||
this.pan = undefined;
|
||||
}
|
||||
|
||||
setZoom(zoomLevel, no_announce) {
|
||||
this.zoom.setZoom(zoomLevel, no_announce);
|
||||
}
|
||||
@ -437,11 +426,11 @@ class Resizer {
|
||||
}
|
||||
|
||||
resetCrop(){
|
||||
this.setAr({type: AspectRatio.Reset});
|
||||
this.setAr({type: AspectRatioType.Reset});
|
||||
}
|
||||
|
||||
resetStretch(){
|
||||
this.stretcher.setStretchMode(Stretch.NoStretch);
|
||||
this.stretcher.setStretchMode(StretchType.NoStretch);
|
||||
this.restore();
|
||||
}
|
||||
|
||||
@ -512,19 +501,55 @@ class Resizer {
|
||||
}
|
||||
}
|
||||
|
||||
computeOffsets(stretchFactors){
|
||||
/**
|
||||
* Sometimes, sites (e.g. new reddit) will guarantee that video fits width of its container
|
||||
* and let the browser figure out the height through the magic of height:auto. This is bad,
|
||||
* because our addon generally relies of videos always being 100% of the height of the
|
||||
* container.
|
||||
*
|
||||
* This sometimes leads to a situation where realVideoHeight and realVideoWidth — at least
|
||||
* one of which should be roughly equal to the player width or hight with the other one being
|
||||
* either smaller or equal — are both smaller than player width or height; and sometimes
|
||||
* rather substantially. Fortunately for us, realVideo[Width|Height] and player dimensions
|
||||
* never lie, which allows us to calculate the extra scale factor we need.
|
||||
*
|
||||
* Returned factor for this function should do fit:contain, not fit:cover.
|
||||
* @param realVideoWidth real video width
|
||||
* @param realVideoHeight real video height
|
||||
* @param playerWidth player width
|
||||
* @param playerHeight player height
|
||||
* @param mode whether to
|
||||
*/
|
||||
computeAutoHeightCompensationFactor(realVideoWidth: number, realVideoHeight: number, playerWidth: number, playerHeight: number, mode: 'height' | 'width'): number {
|
||||
const widthFactor = playerWidth / realVideoWidth;
|
||||
const heightFactor = playerHeight / realVideoHeight;
|
||||
|
||||
return mode === 'height' ? heightFactor : widthFactor;
|
||||
}
|
||||
|
||||
private _computeOffsetsRecursionGuard: boolean = false;
|
||||
computeOffsets(stretchFactors: VideoDimensions){
|
||||
this.logger.log('info', 'debug', "[Resizer::computeOffsets] <rid:"+this.resizerId+"> video will be aligned to ", this.settings.active.sites['@global'].videoAlignment);
|
||||
|
||||
const {realVideoWidth, realVideoHeight, marginX, marginY} = this.computeVideoDisplayedDimensions();
|
||||
|
||||
// correct any remaining element size discrepencies (applicable only to certain crop strategies!)
|
||||
// NOTE: it's possible that we might also need to apply a similar measure for CropPillarbox strategy
|
||||
// (but we'll wait for bug reports before doing so).
|
||||
// We also don't compensate for height:auto if height is provided via element style
|
||||
let autoHeightCompensationFactor;
|
||||
if (
|
||||
stretchFactors.cropStrategy === CropStrategy.CropLetterbox
|
||||
&& (!stretchFactors.styleHeightCompensationFactor || stretchFactors.styleHeightCompensationFactor === 1)
|
||||
) {
|
||||
autoHeightCompensationFactor = this.computeAutoHeightCompensationFactor(realVideoWidth, realVideoHeight, this.conf.player.dimensions.width, this.conf.player.dimensions.height, 'height');
|
||||
stretchFactors.xFactor *= autoHeightCompensationFactor;
|
||||
stretchFactors.yFactor *= autoHeightCompensationFactor;
|
||||
}
|
||||
|
||||
const wdiff = this.conf.player.dimensions.width - realVideoWidth;
|
||||
const hdiff = this.conf.player.dimensions.height - realVideoHeight;
|
||||
|
||||
if (wdiff < 0 && hdiff < 0 && this.zoom.scale > 1) {
|
||||
this.conf.player.restore();
|
||||
}
|
||||
|
||||
const wdiffAfterZoom = realVideoWidth * stretchFactors.xFactor - this.conf.player.dimensions.width;
|
||||
const hdiffAfterZoom = realVideoHeight * stretchFactors.yFactor - this.conf.player.dimensions.height;
|
||||
|
||||
@ -536,32 +561,38 @@ class Resizer {
|
||||
|
||||
|
||||
|
||||
if (this.pan) {
|
||||
if (this.pan.relativeOffsetX || this.pan.relativeOffsetY) {
|
||||
// don't offset when video is smaller than player
|
||||
if(wdiffAfterZoom >= 0 || hdiffAfterZoom >= 0) {
|
||||
translate.x += wdiffAfterZoom * this.pan.relativeOffsetX * this.zoom.scale;
|
||||
translate.y += hdiffAfterZoom * this.pan.relativeOffsetY * this.zoom.scale;
|
||||
}
|
||||
} else {
|
||||
if (this.videoAlignment == VideoAlignment.Left) {
|
||||
if (this.videoAlignment == VideoAlignmentType.Left) {
|
||||
translate.x += wdiffAfterZoom * 0.5;
|
||||
}
|
||||
else if (this.videoAlignment == VideoAlignment.Right) {
|
||||
else if (this.videoAlignment == VideoAlignmentType.Right) {
|
||||
translate.x -= wdiffAfterZoom * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log('info', ['debug', 'resizer'], "[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:\n\n",
|
||||
'---- data in ----',
|
||||
'\nplayer dimensions: ', {w: this.conf.player.dimensions.width, h: this.conf.player.dimensions.height},
|
||||
'\nvideo dimensions: ', {w: this.conf.video.offsetWidth, h: this.conf.video.offsetHeight},
|
||||
'\nreal video dimensions:', {w: realVideoWidth, h: realVideoHeight},
|
||||
'\nstretch factors: ', stretchFactors,
|
||||
'\npan & zoom: ', this.pan, this.zoom.scale,
|
||||
'\nwdiff, hdiff: ', wdiff, 'x', hdiff,
|
||||
'\nwdiff, hdiffAfterZoom:', wdiffAfterZoom, 'x', hdiffAfterZoom,
|
||||
'\n\n---- data out ----\n',
|
||||
'translate:', translate);
|
||||
this.logger.log(
|
||||
'info', ['debug', 'resizer'], "[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:",
|
||||
'\n\n---- elements ----',
|
||||
'\nplayer element: ', this.conf.player.element,
|
||||
'\nvideo element: ', this.conf.video,
|
||||
'\n\n---- data in ----',
|
||||
'\nplayer dimensions: ', {w: this.conf.player.dimensions.width, h: this.conf.player.dimensions.height},
|
||||
'\nvideo dimensions: ', {w: this.conf.video.offsetWidth, h: this.conf.video.offsetHeight},
|
||||
'\nreal video dimensions:', {w: realVideoWidth, h: realVideoHeight},
|
||||
'\nauto compensation: ', 'x', autoHeightCompensationFactor,
|
||||
'\nstretch factors: ', stretchFactors,
|
||||
'\npan & zoom: ', this.pan, this.zoom.scale,
|
||||
'\nwdiff, hdiff: ', wdiff, 'x', hdiff,
|
||||
'\nwdiff, hdiffAfterZoom:', wdiffAfterZoom, 'x', hdiffAfterZoom,
|
||||
'\n\n---- data out ----\n',
|
||||
'translate:', translate
|
||||
);
|
||||
|
||||
// by the way, let's do a quick sanity check whether video player is doing any fuckies wuckies
|
||||
// fucky wucky examples:
|
||||
@ -580,14 +611,22 @@ class Resizer {
|
||||
`Video seems to be both wider and taller (or shorter and narrower) than player element at the same time. This is super duper not supposed to happen.\n\n`,
|
||||
`Player element needs to be checked.`
|
||||
)
|
||||
if (this.conf.player.checkPlayerSizeChange()) {
|
||||
this.conf.player.onPlayerDimensionsChanged();
|
||||
|
||||
// sometimes this appears to randomly recurse.
|
||||
// There seems to be no way to reproduce it.
|
||||
if (! this._computeOffsetsRecursionGuard) {
|
||||
this._computeOffsetsRecursionGuard = true;
|
||||
if (this.conf.player.checkPlayerSizeChange()) {
|
||||
this.conf.player.onPlayerDimensionsChanged();
|
||||
}
|
||||
this._computeOffsetsRecursionGuard = false;
|
||||
}
|
||||
}
|
||||
|
||||
return translate;
|
||||
}
|
||||
|
||||
//#region css handling
|
||||
buildStyleArray(existingStyleString, extraStyleString) {
|
||||
if (existingStyleString) {
|
||||
const styleArray = existingStyleString.split(";");
|
||||
@ -611,7 +650,7 @@ class Resizer {
|
||||
}
|
||||
}
|
||||
|
||||
for (var i in styleArray) {
|
||||
for (let 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
|
||||
@ -634,7 +673,7 @@ class Resizer {
|
||||
buildStyleString(styleArray) {
|
||||
let styleString = '';
|
||||
|
||||
for(var i in styleArray) {
|
||||
for(let i in styleArray) {
|
||||
if(styleArray[i]) {
|
||||
styleString += styleArray[i] + " !important; ";
|
||||
}
|
||||
@ -711,6 +750,7 @@ class Resizer {
|
||||
this.logger.log('info', ['debug', 'resizer'], "[Resizer::setStyleString] <rid:"+this.resizerId+"> Existing css is still valid, doing nothing.");
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export default Resizer;
|
@ -1,12 +1,45 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import Logger from '../Logger';
|
||||
|
||||
|
||||
export enum CropStrategy {
|
||||
/**
|
||||
* Nomenclature explained:
|
||||
*
|
||||
* SP - stream AR < player AR
|
||||
* PS - the opposite of ↑
|
||||
*
|
||||
* ArDominant - given aspect ratio is bigger than stream AR and player AR
|
||||
* PSDominant - stream AR or player AR are bigger than given aspect ratio
|
||||
*/
|
||||
CropLetterbox = 1,
|
||||
NoCropPillarbox = 2,
|
||||
NoCropLetterbox = 3,
|
||||
CropPillarbox = 4
|
||||
}
|
||||
|
||||
export type VideoDimensions = {
|
||||
xFactor?: number;
|
||||
yFactor?: number;
|
||||
cropStrategy?: number;
|
||||
arCorrectionFactor?: number;
|
||||
styleHeightCompensationFactor?: number;
|
||||
actualWidth?: number;
|
||||
actualHeight?: number;
|
||||
}
|
||||
|
||||
// računa velikost videa za približevanje/oddaljevanje
|
||||
// does video size calculations for zooming/cropping
|
||||
|
||||
|
||||
class Scaler {
|
||||
// internal variables
|
||||
//#region helper objects
|
||||
conf: VideoData;
|
||||
logger: Logger;
|
||||
//#endregion
|
||||
|
||||
// functions
|
||||
constructor(videoData) {
|
||||
@ -17,13 +50,13 @@ class Scaler {
|
||||
|
||||
// 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
|
||||
// handles "legacy" options, such as 'fit to widht', 'fit to height' and AspectRatioType.Reset. No zoom tho
|
||||
modeToAr (ar) {
|
||||
if (ar.type !== AspectRatio.FitWidth && ar.type !== AspectRatio.FitHeight && ar.ratio) {
|
||||
if (ar.type !== AspectRatioType.FitWidth && ar.type !== AspectRatioType.FitHeight && ar.ratio) {
|
||||
return ar.ratio;
|
||||
}
|
||||
|
||||
var ratioOut;
|
||||
let ratioOut;
|
||||
|
||||
if (!this.conf.video) {
|
||||
this.logger.log('error', 'debug', "[Scaler.js::modeToAr] No video??",this.conf.video, "killing videoData");
|
||||
@ -44,19 +77,19 @@ class Scaler {
|
||||
// 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;
|
||||
let fileAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
|
||||
if (ar.type === AspectRatio.FitWidth) {
|
||||
if (ar.type === AspectRatioType.FitWidth) {
|
||||
ratioOut > fileAr ? ratioOut : fileAr
|
||||
ar.ratio = ratioOut;
|
||||
return ratioOut;
|
||||
}
|
||||
else if (ar.type === AspectRatio.FitHeight) {
|
||||
else if (ar.type === AspectRatioType.FitHeight) {
|
||||
ratioOut < fileAr ? ratioOut : fileAr
|
||||
ar.ratio = ratioOut;
|
||||
return ratioOut;
|
||||
}
|
||||
else if (ar.type === AspectRatio.Reset) {
|
||||
else if (ar.type === AspectRatioType.Reset) {
|
||||
this.logger.log('info', 'debug', "[Scaler.js::modeToAr] Using original aspect ratio -", fileAr)
|
||||
ar.ar = fileAr;
|
||||
return fileAr;
|
||||
@ -65,7 +98,7 @@ class Scaler {
|
||||
return null;
|
||||
}
|
||||
|
||||
calculateCrop(ar) {
|
||||
calculateCrop(ar: {type: AspectRatioType, ratio?: number}) {
|
||||
/**
|
||||
* STEP 1: NORMALIZE ASPECT RATIO
|
||||
*
|
||||
@ -74,7 +107,7 @@ class Scaler {
|
||||
* undoes any zoom that style="height:123%" on the video element adds.
|
||||
*
|
||||
* There are few exceptions and additional caveatss:
|
||||
* * AspectRatio.FitHeight: we don't want to pre-downscale the video at all, as things
|
||||
* * AspectRatioType.FitHeight: we don't want to pre-downscale the video at all, as things
|
||||
* will be scaled to fit height as-is.
|
||||
* * When player is wider than stream, we want to undo any height compensations site
|
||||
* tacks on the video tag.
|
||||
@ -85,17 +118,17 @@ class Scaler {
|
||||
* * because video width is normalized on 100% of the parent, we don't need to correct
|
||||
* anything when the player is wider than the video.
|
||||
*/
|
||||
const streamAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
const playerAr = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
const streamAr = this.conf.aspectRatio;
|
||||
const playerAr = this.conf.player.aspectRatio;
|
||||
const heightCompensationFactor = this.conf.getHeightCompensationFactor();
|
||||
const compensatedStreamAr = streamAr * heightCompensationFactor;
|
||||
|
||||
let arCorrectionFactor = 1;
|
||||
|
||||
if (ar.type !== AspectRatio.FitHeight) {
|
||||
if (ar.type !== AspectRatioType.FitHeight) {
|
||||
if (playerAr < compensatedStreamAr) {
|
||||
arCorrectionFactor = this.conf.player.dimensions.width / this.conf.video.offsetWidth;
|
||||
} else if (ar.type !== AspectRatio.Reset) {
|
||||
} else if (ar.type !== AspectRatioType.Reset) {
|
||||
arCorrectionFactor /= heightCompensationFactor;
|
||||
}
|
||||
}
|
||||
@ -114,8 +147,8 @@ class Scaler {
|
||||
return {error: "illegal_video_dimensions"};
|
||||
}
|
||||
|
||||
if (ar.type === AspectRatio.Reset){
|
||||
return {xFactor: arCorrectionFactor, yFactor: arCorrectionFactor}
|
||||
if (ar.type === AspectRatioType.Reset){
|
||||
return {xFactor: arCorrectionFactor, yFactor: arCorrectionFactor, arCorrectionFactor: arCorrectionFactor};
|
||||
}
|
||||
|
||||
// handle fuckie-wuckies
|
||||
@ -137,52 +170,74 @@ class Scaler {
|
||||
// Dejansko razmerje stranic datoteke/<video> značke
|
||||
// Actual aspect ratio of the file/<video> tag
|
||||
|
||||
if (ar.type === AspectRatio.Initial || !ar.ratio) {
|
||||
if (ar.type === AspectRatioType.Initial || !ar.ratio) {
|
||||
ar.ratio = streamAr;
|
||||
}
|
||||
|
||||
|
||||
this.logger.log('info', 'scaler', "[Scaler::calculateCrop] ar is " ,ar.ratio, ", file ar is", streamAr, ", this.conf.player.dimensions are ", this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
|
||||
var videoDimensions = {
|
||||
const videoDimensions: VideoDimensions = {
|
||||
xFactor: 1,
|
||||
yFactor: 1,
|
||||
actualWidth: 0, // width of the video (excluding pillarbox) when <video> tag height is equal to width
|
||||
actualHeight: 0, // height of the video (excluding letterbox) when <video> tag height is equal to height
|
||||
arCorrectionFactor: arCorrectionFactor,
|
||||
styleHeightCompensationFactor: heightCompensationFactor
|
||||
}
|
||||
|
||||
this.calculateCropCore(videoDimensions, ar.ratio, streamAr, playerAr)
|
||||
|
||||
return videoDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* The act of calculating aspect ratio is separated due to resue elsewhere in the extension.
|
||||
* We are doing that to avoid surprise recursions.
|
||||
* @param {*} videoDimensions
|
||||
* @param {*} ar
|
||||
* @param {*} streamAr
|
||||
* @param {*} playerAr
|
||||
*/
|
||||
calculateCropCore(videoDimensions: VideoDimensions, ar: number, streamAr: number, playerAr: number) {
|
||||
if (streamAr < playerAr) {
|
||||
if (streamAr < ar.ratio){
|
||||
if (streamAr < ar){
|
||||
// in this situation we have to crop letterbox on top/bottom of the player
|
||||
// we cut it, but never more than the player
|
||||
videoDimensions.xFactor = Math.min(ar.ratio, playerAr) / streamAr;
|
||||
videoDimensions.xFactor = Math.min(ar, playerAr) / streamAr;
|
||||
videoDimensions.yFactor = videoDimensions.xFactor;
|
||||
videoDimensions.cropStrategy = CropStrategy.CropLetterbox;
|
||||
} else {
|
||||
// in this situation, we would be cutting pillarbox. Inside horizontal player.
|
||||
// I don't think so. Except exceptions, we'll wait for bug reports.
|
||||
videoDimensions.xFactor = 1;
|
||||
videoDimensions.yFactor = 1;
|
||||
videoDimensions.cropStrategy = CropStrategy.NoCropPillarbox;
|
||||
}
|
||||
} else {
|
||||
if (streamAr < ar.ratio || playerAr < ar.ratio){
|
||||
if (streamAr < ar || playerAr < ar){
|
||||
// in this situation, we need to add extra letterbox on top of our letterbox
|
||||
// this means we simply don't crop anything _at all_
|
||||
videoDimensions.xFactor = 1;
|
||||
videoDimensions.yFactor = 1;
|
||||
videoDimensions.cropStrategy = CropStrategy.NoCropLetterbox;
|
||||
} else {
|
||||
// meant for handling pillarbox crop. not quite implemented.
|
||||
videoDimensions.xFactor = streamAr / Math.min(ar.ratio, playerAr);
|
||||
videoDimensions.xFactor = streamAr / Math.min(ar, playerAr);
|
||||
videoDimensions.yFactor = videoDimensions.xFactor;
|
||||
videoDimensions.cropStrategy = CropStrategy.CropPillarbox;
|
||||
// videoDimensions.xFactor = Math.max(ar.ratio, playerAr) * fileAr;
|
||||
// videoDimensions.yFactor = videoDimensions.xFactor;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log('info', 'scaler', "[Scaler::calculateCrop] Crop factor calculated — ", videoDimensions.xFactor);
|
||||
|
||||
// correct the factors
|
||||
videoDimensions.xFactor *= arCorrectionFactor;
|
||||
videoDimensions.yFactor *= arCorrectionFactor;
|
||||
this.logger.log('info', 'scaler', "[Scaler::calculateCrop] Crop factor calculated — ", videoDimensions.xFactor);
|
||||
|
||||
// correct the scale factor
|
||||
if (videoDimensions.arCorrectionFactor) {
|
||||
videoDimensions.xFactor *= videoDimensions.arCorrectionFactor;
|
||||
videoDimensions.yFactor *= videoDimensions.arCorrectionFactor;
|
||||
}
|
||||
|
||||
return videoDimensions;
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
import Stretch from '../../../common/enums/stretch.enum';
|
||||
import StretchType from '../../../common/enums/StretchType.enum';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
|
||||
// računa vrednosti za transform-scale (x, y)
|
||||
// transform: scale(x,y) se uporablja za raztegovanje videa, ne pa za približevanje
|
||||
@ -6,8 +11,20 @@ import Stretch from '../../../common/enums/stretch.enum';
|
||||
// transform: scale(x,y) is used for stretching, not zooming.
|
||||
|
||||
class Stretcher {
|
||||
// internal variables
|
||||
//#region flags
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region helper objects
|
||||
conf: VideoData;
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
//#endregion
|
||||
|
||||
//#region misc data
|
||||
mode: any;
|
||||
fixedStretchRatio: any;
|
||||
//#endregion
|
||||
|
||||
// functions
|
||||
constructor(videoData) {
|
||||
@ -18,11 +35,11 @@ class Stretcher {
|
||||
this.fixedStretchRatio = undefined;
|
||||
}
|
||||
|
||||
setStretchMode(stretchMode, fixedStretchRatio) {
|
||||
if (stretchMode === Stretch.Default) {
|
||||
setStretchMode(stretchMode, fixedStretchRatio?) {
|
||||
if (stretchMode === StretchType.Default) {
|
||||
this.mode = this.settings.getDefaultStretchMode(window.location.hostname);
|
||||
} else {
|
||||
if (stretchMode === Stretch.Fixed || stretchMode == Stretch.FixedSource) {
|
||||
if (stretchMode === StretchType.Fixed || stretchMode == StretchType.FixedSource) {
|
||||
this.fixedStretchRatio = fixedStretchRatio;
|
||||
}
|
||||
this.mode = stretchMode;
|
||||
@ -30,17 +47,17 @@ class Stretcher {
|
||||
}
|
||||
|
||||
applyConditionalStretch(stretchFactors, actualAr){
|
||||
var playerAr = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
var videoAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
let playerAr = this.conf.player.aspectRatio;
|
||||
let videoAr = this.conf.aspectRatio;
|
||||
|
||||
if (! actualAr){
|
||||
actualAr = playerAr;
|
||||
}
|
||||
|
||||
var newWidth = this.conf.video.offsetWidth * stretchFactors.xFactor;
|
||||
var newHeight = this.conf.video.offsetHeight * stretchFactors.yFactor;
|
||||
let newWidth = this.conf.video.offsetWidth * stretchFactors.xFactor;
|
||||
let newHeight = this.conf.video.offsetHeight * stretchFactors.yFactor;
|
||||
|
||||
var actualWidth, actualHeight;
|
||||
let actualWidth, actualHeight;
|
||||
|
||||
// determine the dimensions of the video (sans black bars) after scaling
|
||||
if(actualAr < videoAr){
|
||||
@ -51,11 +68,11 @@ class Stretcher {
|
||||
actualWidth = newWidth;
|
||||
}
|
||||
|
||||
var minW = this.conf.player.dimensions.width * (1 - this.settings.active.stretch.conditionalDifferencePercent);
|
||||
var maxW = this.conf.player.dimensions.width * (1 + this.settings.active.stretch.conditionalDifferencePercent);
|
||||
let minW = this.conf.player.dimensions.width * (1 - this.settings.active.stretch.conditionalDifferencePercent);
|
||||
let maxW = this.conf.player.dimensions.width * (1 + this.settings.active.stretch.conditionalDifferencePercent);
|
||||
|
||||
var minH = this.conf.player.dimensions.height * (1 - this.settings.active.stretch.conditionalDifferencePercent);
|
||||
var maxH = this.conf.player.dimensions.height * (1 + this.settings.active.stretch.conditionalDifferencePercent);
|
||||
let minH = this.conf.player.dimensions.height * (1 - this.settings.active.stretch.conditionalDifferencePercent);
|
||||
let maxH = this.conf.player.dimensions.height * (1 + this.settings.active.stretch.conditionalDifferencePercent);
|
||||
|
||||
if (actualWidth >= minW && actualWidth <= maxW) {
|
||||
stretchFactors.xFactor *= this.conf.player.dimensions.width / actualWidth;
|
||||
@ -85,25 +102,25 @@ class Stretcher {
|
||||
// This means we want to calculate stretching using those values, but we don't know
|
||||
// them. This means we have to calculate them.
|
||||
|
||||
const videoAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
if (this.conf.player.dimensions.width > this.conf.player.dimensions.height * videoAr) {
|
||||
const streamAr = this.conf.aspectRatio;
|
||||
if (this.conf.player.dimensions.width > this.conf.player.dimensions.height * streamAr) {
|
||||
return {
|
||||
xFactor: this.conf.player.dimensions.width / (this.conf.player.dimensions.height * videoAr),
|
||||
xFactor: this.conf.player.dimensions.width / (this.conf.player.dimensions.height * streamAr),
|
||||
yFactor: 1
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
xFactor: 1,
|
||||
yFactor: this.conf.player.dimensions.height / (this.conf.player.dimensions.width / videoAr)
|
||||
yFactor: this.conf.player.dimensions.height / (this.conf.player.dimensions.width / streamAr)
|
||||
};
|
||||
}
|
||||
|
||||
applyStretchFixedSource(postCropStretchFactors) {
|
||||
const videoAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
const playerAr = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
const streamAr = this.conf.aspectRatio;
|
||||
const playerAr = this.conf.player.aspectRatio;
|
||||
|
||||
const squeezeFactor = this.fixedStretchRatio / videoAr;
|
||||
const squeezeFactor = this.fixedStretchRatio / streamAr;
|
||||
|
||||
// Whether squeezing happens on X or Y axis depends on whether required AR is wider or narrower than
|
||||
// the player, in which the video is displayed
|
||||
@ -113,16 +130,11 @@ class Stretcher {
|
||||
this.logger.log('info', 'stretcher', `[Stretcher::applyStretchFixedSource] here's what we got:
|
||||
postCropStretchFactors: x=${postCropStretchFactors.xFactor} y=${postCropStretchFactors.yFactor}
|
||||
fixedStretchRatio: ${this.fixedStretchRatio}
|
||||
videoAr: ${videoAr}
|
||||
videoAr: ${streamAr}
|
||||
playerAr: ${playerAr}
|
||||
squeezeFactor: ${squeezeFactor}`, '\nvideo', this.conf.video);
|
||||
|
||||
|
||||
if (this.fixedStretchRatio < playerAr) {
|
||||
postCropStretchFactors.xFactor *= squeezeFactor;
|
||||
} else {
|
||||
postCropStretchFactors.yFactor *= squeezeFactor;
|
||||
}
|
||||
postCropStretchFactors.xFactor *= squeezeFactor;
|
||||
|
||||
this.logger.log('info', 'stretcher', `[Stretcher::applyStretchFixedSource] here's what we'll apply:\npostCropStretchFactors: x=${postCropStretchFactors.x} y=${postCropStretchFactors.y}`);
|
||||
|
||||
@ -134,24 +146,21 @@ squeezeFactor: ${squeezeFactor}`, '\nvideo', this.conf.video);
|
||||
}
|
||||
|
||||
getArCorrectionFactor() {
|
||||
const streamAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
const playerAr = this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
|
||||
let arCorrectionFactor = 1;
|
||||
arCorrectionFactor = this.conf.player.dimensions.width / this.conf.video.offsetWidth;
|
||||
|
||||
return arCorrectionFactor;
|
||||
}
|
||||
|
||||
calculateStretch(actualAr, playerArOverride) {
|
||||
const playerAr = playerArOverride || this.conf.player.dimensions.width / this.conf.player.dimensions.height;
|
||||
const streamAr = this.conf.video.videoWidth / this.conf.video.videoHeight;
|
||||
calculateStretch(actualAr, playerArOverride?) {
|
||||
const playerAr = playerArOverride || this.conf.player.aspectRatio;
|
||||
const streamAr = this.conf.aspectRatio;
|
||||
|
||||
if (! actualAr){
|
||||
actualAr = playerAr;
|
||||
}
|
||||
|
||||
var stretchFactors = {
|
||||
let stretchFactors: any = {
|
||||
xFactor: 1,
|
||||
yFactor: 1
|
||||
};
|
||||
@ -187,6 +196,7 @@ squeezeFactor: ${squeezeFactor}`, '\nvideo', this.conf.video);
|
||||
|
||||
this.logger.log('info', 'stretcher', "[Stretcher.js::calculateStretch] stretching strategy 3")
|
||||
}
|
||||
|
||||
} else {
|
||||
// player adds LETTERBOX
|
||||
|
||||
@ -229,6 +239,69 @@ squeezeFactor: ${squeezeFactor}`, '\nvideo', this.conf.video);
|
||||
|
||||
return stretchFactors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that <video> element is never both taller-ish and wider-ish than the screen, while in fullscreen
|
||||
* on Chromium-based browsers.
|
||||
*
|
||||
* Workaround for Chrome/Edge issue where zooming too much results in video being stretched incorrectly.
|
||||
*
|
||||
* Bug description — if the following are true:
|
||||
* * user is using Chrome or Edge (but surprisingly not Opera)
|
||||
* * user is using hardware acceleration
|
||||
* * user is using a noVideo card
|
||||
* * user is in full screen mode
|
||||
* * the video is both roughly taller and roughly wider than the monitor
|
||||
* Then the video will do StretchType.Basic no matter what you put in `transform: scale(x,y)`.
|
||||
*
|
||||
* In practice, the issue appears slightly _before_ the last condition is met (video needs to be ~3434 px wide
|
||||
* in order for this bug to trigger on my 3440x1440 display).
|
||||
*
|
||||
* Because this issue happens regardless of how you upscale the video (doesn't matter if you use transform:scale
|
||||
* or width+height or anything else), the aspect ratio needs to be limited _before_ applying arCorrectionFactor
|
||||
* (note that arCorrectionFactor is usually <= 1, as it conpensates for zooming that height=[>100%] on <video>
|
||||
* style attribute does).
|
||||
*/
|
||||
chromeBugMitigation(stretchFactors) {
|
||||
if (
|
||||
BrowserDetect.anyChromium
|
||||
&& (this.conf.player?.dimensions?.fullscreen || ! this.settings?.active?.mitigations?.zoomLimit?.fullscreenOnly)
|
||||
&& this.settings?.active?.mitigations?.zoomLimit?.enabled
|
||||
) {
|
||||
const playerAr = this.conf.player.aspectRatio;
|
||||
const streamAr = this.conf.aspectRatio;
|
||||
|
||||
let maxSafeAr: number;
|
||||
let arLimitFactor = this.settings?.active?.mitigations?.zoomLimit?.limit ?? 0.997;
|
||||
|
||||
if (playerAr >= (streamAr * 1.1)) {
|
||||
maxSafeAr = (window.innerWidth * arLimitFactor) / window.innerHeight;
|
||||
} else if (playerAr < (streamAr * 0.95)) {
|
||||
maxSafeAr = window.innerWidth / (window.innerHeight * arLimitFactor);
|
||||
} else {
|
||||
// in some cases, we tolerate minor stretch to avoid tiny black bars
|
||||
return;
|
||||
}
|
||||
|
||||
const maxSafeStretchFactor = this.conf.resizer.scaler.calculateCropCore(
|
||||
{
|
||||
xFactor: 1,
|
||||
yFactor: 1,
|
||||
arCorrectionFactor: stretchFactors.arCorrectionFactor
|
||||
},
|
||||
maxSafeAr,
|
||||
streamAr,
|
||||
playerAr
|
||||
).xFactor;
|
||||
|
||||
// console.info('Stretch factors before:', stretchFactors.xFactor, stretchFactors.yFactor, "max safe:", maxSafeStretchFactor, "max safe ar:", maxSafeAr);
|
||||
|
||||
stretchFactors.xFactor = Math.min(stretchFactors.xFactor, maxSafeStretchFactor);
|
||||
stretchFactors.yFactor = Math.min(stretchFactors.yFactor, maxSafeStretchFactor);
|
||||
|
||||
return stretchFactors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Stretcher;
|
@ -1,19 +1,32 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import Logger from '../Logger';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
|
||||
// računa približevanje ter računa/popravlja odmike videa
|
||||
// calculates zooming and video offsets/panning
|
||||
|
||||
class Zoom {
|
||||
// functions
|
||||
//#region flags
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region helper objects
|
||||
conf: VideoData;
|
||||
logger: Logger;
|
||||
//#endregion
|
||||
|
||||
//#region misc data
|
||||
scale: number = 1;
|
||||
logScale: number = 0;
|
||||
scaleStep: number = 0.1;
|
||||
minScale: number = -1; // 50% (log2(0.5) = -1)
|
||||
maxScale: number = 3; // 800% (log2(8) = 3)
|
||||
//#endregion
|
||||
|
||||
|
||||
constructor(videoData) {
|
||||
this.conf = videoData;
|
||||
this.logger = videoData.logger;
|
||||
|
||||
this.scale = 1;
|
||||
this.logScale = 0;
|
||||
this.scaleStep = 0.1;
|
||||
this.minScale = -1; // 50% (log2(0.5) = -1)
|
||||
this.maxScale = 3; // 800% (log2(8) = 3)
|
||||
}
|
||||
|
||||
reset(){
|
||||
@ -41,7 +54,7 @@ class Zoom {
|
||||
this.conf.announceZoom(this.scale);
|
||||
}
|
||||
|
||||
setZoom(scale, no_announce){
|
||||
setZoom(scale: number, no_announce?){
|
||||
this.logger.log('info', 'debug', "[Zoom::setZoom] Setting zoom to", scale, "!");
|
||||
|
||||
// NOTE: SCALE IS NOT LOGARITHMIC
|
330
src/ext/uw-bg.js
330
src/ext/uw-bg.js
@ -1,18 +1,9 @@
|
||||
import Debug from './conf/Debug.js';
|
||||
import BrowserDetect from './conf/BrowserDetect';
|
||||
import CommsServer from './lib/comms/CommsServer';
|
||||
import Settings from './lib/Settings';
|
||||
import Logger from './lib/Logger';
|
||||
/**
|
||||
* NOTE: we cannot get rid of this js file. I tried for 30 seconds and I couldn't get
|
||||
* extension to work unless I kept this part of extension out of the ts file.
|
||||
*/
|
||||
|
||||
import { sleep } from '../common/js/utils';
|
||||
|
||||
// we need vue in bg script, so we can get vuex.
|
||||
// and we need vuex so popup will be initialized
|
||||
// after the first click without resorting to ugly,
|
||||
// dirty hacks
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import VuexWebExtensions from 'vuex-webextensions';
|
||||
import UWServer from './UWServer';
|
||||
|
||||
var BgVars = {
|
||||
arIsActive: true,
|
||||
@ -20,316 +11,7 @@ var BgVars = {
|
||||
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,
|
||||
}
|
||||
|
||||
this.uiLoggerInitialized = false;
|
||||
}
|
||||
|
||||
async setup() {
|
||||
// logger is the first thing that goes up
|
||||
const loggingOptions = {
|
||||
isBackgroundScript: true,
|
||||
allowLogging: true,
|
||||
useConfFromStorage: true,
|
||||
logAll: true,
|
||||
fileOptions: {
|
||||
enabled: true,
|
||||
},
|
||||
consoleOptions: {
|
||||
enabled: true
|
||||
}
|
||||
};
|
||||
this.logger = new Logger();
|
||||
await this.logger.init(loggingOptions);
|
||||
|
||||
this.settings = new Settings({logger: this.logger});
|
||||
await this.settings.init();
|
||||
this.comms = new CommsServer(this);
|
||||
this.comms.subscribe('show-logger', async () => await this.initUiAndShowLogger());
|
||||
this.comms.subscribe('init-vue', async () => await this.initUi());
|
||||
this.comms.subscribe('uwui-vue-initialized', () => this.uiLoggerInitialized = true);
|
||||
this.comms.subscribe('emit-logs', () => {}); // we don't need to do anything, this gets forwarded to UI content script as is
|
||||
|
||||
|
||||
if(BrowserDetect.firefox) {
|
||||
browser.tabs.onActivated.addListener((m) => {this.onTabSwitched(m)});
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
chrome.tabs.onActivated.addListener((m) => {this.onTabSwitched(m)});
|
||||
}
|
||||
}
|
||||
|
||||
async _promisifyTabsGet(browserObj, tabId){
|
||||
return new Promise( (resolve, reject) => {
|
||||
browserObj.tabs.get(tabId, (tab) => resolve(tab));
|
||||
});
|
||||
}
|
||||
|
||||
async injectCss(css, sender) {
|
||||
try {
|
||||
if (BrowserDetect.firefox || BrowserDetect.edge) {
|
||||
browser.tabs.insertCSS(sender.tab.id, {code: css, cssOrigin: 'user', frameId: sender.frameId});
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
chrome.tabs.insertCSS(sender.tab.id, {code: css, cssOrigin: 'user', frameId: sender.frameId});
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.log('error','debug', '[UwServer::injectCss] Error while injecting css:', {error: e, css, sender});
|
||||
}
|
||||
}
|
||||
async removeCss(css, sender) {
|
||||
try {
|
||||
if (BrowserDetect.firefox || BrowserDetect.edge) {
|
||||
browser.tabs.removeCSS(sender.tab.id, {code: css, cssOrigin: 'user', frameId: sender.frameId});
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
// this doesn't work currently, but hopefully chrome will get this feature in the future
|
||||
chrome.tabs.removeCSS(sender.tab.id, {code: css, cssOrigin: 'user', frameId: sender.frameId});
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.log('error','debug', '[UwServer::injectCss] Error while removing css:', {error: e, css, sender});
|
||||
}
|
||||
}
|
||||
|
||||
async replaceCss(oldCss, newCss, sender) {
|
||||
if (oldCss !== newCss) {
|
||||
this.injectCss(newCss, sender);
|
||||
this.removeCss(oldCss, sender);
|
||||
}
|
||||
}
|
||||
|
||||
extractHostname(url){
|
||||
var hostname;
|
||||
|
||||
if (!url) {
|
||||
return "<no url>";
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
try {
|
||||
this.currentTabId = activeInfo.tabId; // just for readability
|
||||
|
||||
let tab;
|
||||
if (BrowserDetect.firefox) {
|
||||
tab = await browser.tabs.get(this.currentTabId);
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
tab = await this._promisifyTabsGet(chrome, this.currentTabId);
|
||||
}
|
||||
|
||||
this.currentSite = this.extractHostname(tab.url);
|
||||
this.logger.log('info', 'debug', '[UwServer::onTabSwitched] user switched tab. New site:', this.currentSite);
|
||||
} catch(e) {
|
||||
this.logger.log('error', 'debug', '[UwServer::onTabSwitched] there was a problem getting currnet site:', e)
|
||||
}
|
||||
|
||||
this.selectedSubitem = {
|
||||
'siteSettings': undefined,
|
||||
'videoSettings': undefined,
|
||||
}
|
||||
//TODO: change extension icon based on whether there's any videos on current page
|
||||
}
|
||||
|
||||
registerVideo(sender) {
|
||||
this.logger.log('info', 'comms', '[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]?.host != tabHostname) {
|
||||
delete this.videoTabs[sender.tab.id]
|
||||
} else 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]) {
|
||||
this.videoTabs[sender.tab.id].frames[sender.frameId] = {
|
||||
id: sender.frameId,
|
||||
host: frameHostname,
|
||||
url: sender.url,
|
||||
registerTime: Date.now(),
|
||||
}
|
||||
} 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,
|
||||
registerTime: Date.now(),
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log('info', 'comms', '[UWServer::registerVideo] Video registered. current videoTabs:', this.videoTabs);
|
||||
}
|
||||
|
||||
unregisterVideo(sender) {
|
||||
this.logger.log('info', 'comms', '[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];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.log('info', 'comms', '[UwServer::unregisterVideo] Video has been unregistered. Current videoTabs:', this.videoTabs);
|
||||
}
|
||||
|
||||
setSelectedTab(menu, subitem) {
|
||||
this.logger.log('info', 'comms', '[UwServer::setSelectedTab] saving selected tab for', menu, ':', subitem);
|
||||
this.selectedSubitem[menu] = subitem;
|
||||
}
|
||||
|
||||
async initUi() {
|
||||
try {
|
||||
if (BrowserDetect.firefox) {
|
||||
await browser.tabs.executeScript({
|
||||
file: '/ext/uw-ui.js',
|
||||
allFrames: true,
|
||||
});
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
await new Promise( resolve =>
|
||||
chrome.tabs.executeScript({
|
||||
file: '/ext/uw-ui.js',
|
||||
allFrames: true,
|
||||
}, () => resolve())
|
||||
);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
this.logger.log('ERROR', 'uwbg', 'UI initialization failed. Reason:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async initUiAndShowLogger() {
|
||||
// this implementation is less than optimal and very hacky, but it should work
|
||||
// just fine for our use case.
|
||||
this.uiLoggerInitialized = false;
|
||||
|
||||
await this.initUi();
|
||||
|
||||
await new Promise( async (resolve, reject) => {
|
||||
// if content script doesn't give us a response within 5 seconds, something is
|
||||
// obviously wrong and we stop waiting,
|
||||
|
||||
// oh and btw, resolve/reject do not break the loops, so we need to do that
|
||||
// ourselves:
|
||||
// https://stackoverflow.com/questions/55207256/will-resolve-in-promise-loop-break-loop-iteration
|
||||
let isRejected = false;
|
||||
setTimeout( async () => {isRejected = true; reject()}, 5000);
|
||||
|
||||
// check whether UI has been initiated on the FE. If it was, we resolve the
|
||||
// promise and off we go
|
||||
while (!isRejected) {
|
||||
if (this.uiLoggerInitialized) {
|
||||
resolve();
|
||||
return; // remember the bit about resolve() not breaking the loop?
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getCurrentTab() {
|
||||
if (BrowserDetect.firefox) {
|
||||
return (await browser.tabs.query({active: true, currentWindow: true}))[0];
|
||||
} else if (BrowserDetect.anyChromium) {
|
||||
return new Promise((resolve, reject) => chrome.tabs.query({active: true, currentWindow: true}, (x) => resolve(x[0])));
|
||||
}
|
||||
}
|
||||
|
||||
async getVideoTab() {
|
||||
// friendly reminder: if current tab doesn't have a video,
|
||||
// there won't be anything in this.videoTabs[this.currentTabId]
|
||||
|
||||
const ctab = await this.getCurrentTab();
|
||||
|
||||
if (!ctab || !ctab.id) {
|
||||
return {
|
||||
host: 'INVALID SITE',
|
||||
frames: [],
|
||||
}
|
||||
}
|
||||
|
||||
if (this.videoTabs[ctab.id]) {
|
||||
// if video is older than PageInfo's video rescan period (+ 4000ms of grace),
|
||||
// we clean it up from videoTabs[tabId].frames array.
|
||||
const ageLimit = Date.now() - this.settings.active.pageInfo.timeouts.rescan - 4000;
|
||||
console.log("videoTabs[tabId]:", this.videoTabs[ctab.id])
|
||||
try {
|
||||
for (const key in this.videoTabs[ctab.id].frames) {
|
||||
if (this.videoTabs[ctab.id].frames[key].registerTime < ageLimit) {
|
||||
delete this.videoTabs[ctab.id].frames[key];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// something went wrong. There's prolly no frames.
|
||||
return {
|
||||
host: this.extractHostname(ctab.url),
|
||||
frames: [],
|
||||
selected: this.selectedSubitem
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...this.videoTabs[ctab.id],
|
||||
host: this.extractHostname(ctab.url),
|
||||
selected: this.selectedSubitem
|
||||
};
|
||||
}
|
||||
|
||||
// return something more or less empty if this tab doesn't have
|
||||
// a video registered for it
|
||||
return {
|
||||
host: this.extractHostname(ctab.url),
|
||||
frames: [],
|
||||
selected: this.selectedSubitem
|
||||
}
|
||||
}
|
||||
|
||||
// chrome shitiness mitigation
|
||||
sendUnmarkPlayer(message) {
|
||||
this.comms.sendUnmarkPlayer(message);
|
||||
}
|
||||
}
|
||||
|
||||
var server = new UWServer();
|
||||
const server = new UWServer();
|
||||
|
||||
window.sendUnmarkPlayer = (message) => {
|
||||
server.sendUnmarkPlayer(message)
|
||||
|
@ -41,13 +41,13 @@ class UwUi {
|
||||
if (!this.logger) {
|
||||
const loggingOptions = {
|
||||
isContentScript: true,
|
||||
allowLogging: true,
|
||||
allowLogging: false,
|
||||
useConfFromStorage: true,
|
||||
fileOptions: {
|
||||
enabled: false
|
||||
},
|
||||
consoleOptions: {
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"debug": true,
|
||||
"init": true,
|
||||
"settings": true,
|
||||
|
177
src/ext/uw.js
177
src/ext/uw.js
@ -1,13 +1,8 @@
|
||||
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 Comms from './lib/comms/Comms';
|
||||
import CommsClient from './lib/comms/CommsClient';
|
||||
import PageInfo from './lib/video-data/PageInfo';
|
||||
import Logger from './lib/Logger';
|
||||
|
||||
/**
|
||||
* NOTE: we cannot get rid of this js file. I tried for 30 seconds and I couldn't get
|
||||
* extension to work unless I kept this part of extension out of the ts file.
|
||||
*/
|
||||
import UWContent from './UWContent';
|
||||
|
||||
if(process.env.CHANNEL !== 'stable'){
|
||||
console.warn("\n\n\n\n\n\n ——— Sᴛλʀᴛɪɴɢ Uʟᴛʀᴀᴡɪᴅɪꜰʏ ———\n << ʟᴏᴀᴅɪɴɢ ᴍᴀɪɴ ꜰɪʟᴇ >>\n\n\n\n");
|
||||
@ -23,165 +18,5 @@ if(process.env.CHANNEL !== 'stable'){
|
||||
}
|
||||
}
|
||||
|
||||
if (BrowserDetect.edge) {
|
||||
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
|
||||
}
|
||||
|
||||
class UW {
|
||||
constructor(){
|
||||
this.pageInfo = undefined;
|
||||
this.comms = undefined;
|
||||
this.settings = undefined;
|
||||
this.actionHandler = undefined;
|
||||
this.logger = undefined;
|
||||
this.uiInitiated = false;
|
||||
|
||||
this.commsHandlers = {
|
||||
'get-current-zoom': [() => this.pageInfo.requestCurrentZoom()],
|
||||
'set-ar': [(message) => this.pageInfo.setAr({type: message.arg, ratio: message.customArg}, message.playing)],
|
||||
'set-alignment': [(message) => {
|
||||
this.pageInfo.setVideoAlignment(message.arg, message.playing);
|
||||
this.pageInfo.restoreAr();
|
||||
}],
|
||||
'set-stretch': [(message) => this.pageInfo.setStretchMode(message.arg, message.playing, message.customArg)],
|
||||
'set-keyboard': [(message) => this.pageInfo.setKeyboardShortcutsEnabled(message.arg)],
|
||||
'autoar-start': [(message) => {
|
||||
if (message.enabled !== false) {
|
||||
this.pageInfo.initArDetection(message.playing);
|
||||
this.pageInfo.startArDetection(message.playing);
|
||||
} else {
|
||||
this.pageInfo.stopArDetection(message.playing);
|
||||
}
|
||||
}],
|
||||
'pause-processing': [(message) => this.pageInfo.pauseProcessing(message.playing)],
|
||||
'resume-processing': [(message) => this.pageInfo.resumeProcessing(message.autoArStatus, message.playing)],
|
||||
'set-zoom': [(message) => this.pageInfo.setZoom(message.arg, true, message.playing)],
|
||||
'change-zoom': [(message) => this.pageInfo.zoomStep(message.arg, message.playing)],
|
||||
'mark-player': [(message) => this.pageInfo.markPlayer(message.name, message.color)],
|
||||
'unmark-player': [() => this.pageInfo.unmarkPlayer()],
|
||||
'autoar-set-manual-tick': [(message) => this.pageInfo.setManualTick(message.arg)],
|
||||
'autoar-tick': [() => this.pageInfo.tick()],
|
||||
'set-ar-persistence': [() => this.pageInfo.setArPersistence(message.arg)],
|
||||
}
|
||||
}
|
||||
|
||||
reloadSettings() {
|
||||
this.logger.log('info', 'debug', 'Things happened in the popup. Will reload extension settings.');
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init(){
|
||||
if (Debug.debug) {
|
||||
console.log("[uw::main] loading configuration ...");
|
||||
}
|
||||
|
||||
// logger init is the first thing that needs to run
|
||||
try {
|
||||
if (!this.logger) {
|
||||
const loggingOptions = {
|
||||
isContentScript: true,
|
||||
allowLogging: true,
|
||||
useConfFromStorage: true,
|
||||
fileOptions: {
|
||||
enabled: false
|
||||
},
|
||||
consoleOptions: {
|
||||
"enabled": true,
|
||||
"debug": true,
|
||||
"init": true,
|
||||
"settings": true,
|
||||
"keyboard": true,
|
||||
"mousemove": false,
|
||||
"actionHandler": true,
|
||||
"comms": true,
|
||||
"playerDetect": true,
|
||||
"resizer": true,
|
||||
"scaler": true,
|
||||
"stretcher": true,
|
||||
// "videoRescan": true,
|
||||
// "playerRescan": true,
|
||||
"arDetect": true,
|
||||
"arDetect_verbose": true
|
||||
},
|
||||
allowBlacklistedOrigins: {
|
||||
'periodicPlayerCheck': false,
|
||||
'periodicVideoStyleChangeCheck': false,
|
||||
'handleMouseMove': false
|
||||
}
|
||||
};
|
||||
this.logger = new Logger();
|
||||
await this.logger.init(loggingOptions);
|
||||
|
||||
// show popup if logging to file is enabled
|
||||
if (this.logger.isLoggingAllowed() && this.logger.isLoggingToFile()) {
|
||||
console.info("[uw::init] Logging is allowed! Initalizing vue and UI!");
|
||||
|
||||
// CommsClient is not initiated yet, so we use static comms to send the command
|
||||
Comms.sendMessage({cmd: 'show-logger'});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("logger init failed!", e)
|
||||
}
|
||||
|
||||
// init() is re-run any time settings change
|
||||
if (this.comms) {
|
||||
this.comms.destroy();
|
||||
}
|
||||
if (!this.settings) {
|
||||
this.settings = new Settings({
|
||||
onSettingsChanged: () => this.reloadSettings(),
|
||||
logger: this.logger
|
||||
});
|
||||
await this.settings.init();
|
||||
}
|
||||
|
||||
this.comms = new CommsClient('content-main-port', this.logger, this.commsHandlers);
|
||||
|
||||
// če smo razširitev onemogočili v nastavitvah, ne naredimo ničesar
|
||||
// If extension is soft-disabled, don't do shit
|
||||
|
||||
var extensionMode = this.settings.getExtensionMode();
|
||||
|
||||
this.logger.log('info', 'debug', "[uw::init] Extension mode:" + (extensionMode < 0 ? "disabled" : extensionMode == '1' ? 'basic' : 'full'));
|
||||
|
||||
const isSiteDisabled = extensionMode === ExtensionMode.Disabled
|
||||
|
||||
if (isSiteDisabled) {
|
||||
if (this.settings.getExtensionMode('@global') === ExtensionMode.Disabled) {
|
||||
this.logger.log('info', 'debug', "[uw::init] EXTENSION DISABLED, THEREFORE WONT BE STARTED")
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.pageInfo) {
|
||||
this.logger.log('info', 'debug', '[uw.js::setup] An instance of pageInfo already exists and will be destroyed.');
|
||||
this.pageInfo.destroy();
|
||||
}
|
||||
this.pageInfo = new PageInfo(this.comms, this.settings, this.logger, extensionMode, isSiteDisabled);
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] pageInfo initialized.");
|
||||
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] will try to initate ActionHandler.");
|
||||
|
||||
// start action handler only if extension is enabled for this site
|
||||
if (!isSiteDisabled) {
|
||||
if (this.actionHandler) {
|
||||
this.actionHandler.destroy();
|
||||
}
|
||||
this.actionHandler = new ActionHandler(this.pageInfo);
|
||||
this.actionHandler.init();
|
||||
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] ActionHandler initiated.");
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'debug', "[uw::init] FAILED TO START EXTENSION. Error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
var main = new UW();
|
||||
const main = new UWContent();
|
||||
main.init();
|
||||
|
@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "Ultrawidify",
|
||||
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
|
||||
"version": "4.5.1.3",
|
||||
"version": "5.0.4",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "{cf02b1a7-a01a-4e37-a609-516a283f1ed3}"
|
||||
|
@ -111,11 +111,11 @@
|
||||
<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 Debug from '../ext/conf/Debug';
|
||||
import BrowserDetect from '../ext/conf/BrowserDetect';
|
||||
import ExtensionConf from '../ext/conf/ExtensionConf';
|
||||
import ObjectCopy from '../ext/lib/ObjectCopy';
|
||||
import Settings from '../ext/lib/Settings';
|
||||
import GeneralSettings from './GeneralSettings';
|
||||
import ControlsSettings from './controls-settings/ControlsSettings';
|
||||
import AddEditActionPopup from './controls-settings/AddEditActionPopup';
|
||||
|
@ -185,7 +185,7 @@
|
||||
</div>
|
||||
<div class="flex flex-input">
|
||||
<input type="text"
|
||||
v-model="settings.active.arDetect.allowedMisaligned"
|
||||
v-model="settings.active.arDetect.pillarTest.allowMisaligned"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,16 +57,16 @@
|
||||
</div>
|
||||
<div class="flex flex-row button-box">
|
||||
<Button label="Left"
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignment.Left"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignment.Left)">
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignmentType.Left"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignmentType.Left)">
|
||||
</Button>
|
||||
<Button label="Center"
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignment.Center"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignment.Center)">
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignmentType.Center"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignmentType.Center)">
|
||||
</Button>
|
||||
<Button label="Right"
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignment.Right"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignment.Right)">
|
||||
:selected="settings.active.sites['@global'].videoAlignment === VideoAlignmentType.Right"
|
||||
@click.native="setDefaultvideoAlignment(VideoAlignmentType.Right)">
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -75,20 +75,20 @@
|
||||
</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)">
|
||||
:selected="settings.active.sites['@global'].stretch === StretchType.NoStretch"
|
||||
@click.native="setDefaultStretchingMode(StretchType.NoStretch)">
|
||||
</Button>
|
||||
<Button label="Basic stretch"
|
||||
:selected="settings.active.sites['@global'].stretch === Stretch.Basic"
|
||||
@click.native="setDefaultStretchingMode(Stretch.Basic)">
|
||||
:selected="settings.active.sites['@global'].stretch === StretchType.Basic"
|
||||
@click.native="setDefaultStretchingMode(StretchType.Basic)">
|
||||
</Button>
|
||||
<Button label="Hybrid stretch"
|
||||
:selected="settings.active.sites['@global'].stretch === Stretch.Hybrid"
|
||||
@click.native="setDefaultStretchingMode(Stretch.Hybrid)">
|
||||
:selected="settings.active.sites['@global'].stretch === StretchType.Hybrid"
|
||||
@click.native="setDefaultStretchingMode(StretchType.Hybrid)">
|
||||
</Button>
|
||||
<Button label="Thin borders only"
|
||||
:selected="settings.active.sites['@global'].stretch === Stretch.Conditional"
|
||||
@click.native="setDefaultStretchingMode(Stretch.Conditional)"
|
||||
:selected="settings.active.sites['@global'].stretch === StretchType.Conditional"
|
||||
@click.native="setDefaultStretchingMode(StretchType.Conditional)"
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
@ -152,9 +152,9 @@
|
||||
|
||||
<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';
|
||||
import StretchType from '../common/enums/StretchType.enum';
|
||||
import ExtensionMode from '../common/enums/ExtensionMode.enum';
|
||||
import VideoAlignmentType from '../common/enums/VideoAlignmentType.enum';
|
||||
import BrowserDetect from '../ext/conf/BrowserDetect';
|
||||
|
||||
export default {
|
||||
@ -166,9 +166,9 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
StretchType: StretchType,
|
||||
ExtensionMode: ExtensionMode,
|
||||
VideoAlignment: VideoAlignment,
|
||||
VideoAlignmentType: VideoAlignmentType,
|
||||
stretchThreshold: 0,
|
||||
corruptedSettingsError: false,
|
||||
downloadPermissionError: false,
|
||||
|
@ -39,7 +39,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
addonVersion: '[extension version not loaded. This is a bug.]',
|
||||
mailtoLink: 'mailto:tamius.han@gmail.com',
|
||||
mailtoLink: 'mailto:tamius.han+ultrawidify@gmail.com',
|
||||
redditLink: '',
|
||||
}
|
||||
},
|
||||
|
@ -102,7 +102,7 @@
|
||||
|
||||
<script>
|
||||
import ShortcutButton from '../../common/components/ShortcutButton.vue'
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import StretchType from '../../common/enums/StretchType.enum';
|
||||
import KeyboardShortcutParser from '../../common/js/KeyboardShortcutParser';
|
||||
import CommandChain from './command-builder/CommandChain';
|
||||
import CommandAddEdit from './command-builder/CommandAddEdit';
|
||||
@ -121,7 +121,7 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
StretchType: StretchType,
|
||||
action: {
|
||||
name: 'New action',
|
||||
label: 'New action',
|
||||
|
@ -111,7 +111,7 @@
|
||||
|
||||
<script>
|
||||
import Button from '../../common/components/Button';
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import StretchType from '../../common/enums/StretchType.enum';
|
||||
import ActionAlt from '../../common/components/ActionAlt';
|
||||
|
||||
export default {
|
||||
@ -121,7 +121,7 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
StretchType: StretchType,
|
||||
tableVisibility: {
|
||||
crop: true,
|
||||
}
|
||||
|
@ -89,12 +89,12 @@
|
||||
|
||||
<script>
|
||||
import ActionList from '../../../ext/conf/ActionList';
|
||||
import Stretch from '../../../common/enums/stretch.enum';
|
||||
import StretchType from '../../../common/enums/StretchType.enum';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
Stretch: Stretch,
|
||||
StretchType: StretchType,
|
||||
ActionList: ActionList,
|
||||
selectedAction: undefined,
|
||||
selectedArgument: undefined,
|
||||
|
@ -20,17 +20,6 @@
|
||||
Build channel: {{BrowserDetect.processEnvChannel}}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="BrowserDetect.isEdgeUA" style="margin: 2px 12px; border: 1px solid #fa6; color: #fa6" class="flex flex-row flex-center">
|
||||
<div class="flex-nogrow flex-nosrhink flex flex-center" style="font-size: 2em">
|
||||
<Icon icon="exclamation-triangle"></Icon>
|
||||
</div>
|
||||
<div class="flex-grow padding-right: 1em; line-height: 1">
|
||||
<small>
|
||||
<b>NOTE:</b> please ensure your Windows and Edge have the latest updates in order for this extension to work on DRM-protected sites.
|
||||
If your Windows and Edge are not up to date, videos on sites like Netflix, Hulu, and Disney+ may not be aligned and cropped correctly.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="narrowPopup"
|
||||
class="w100 show-more flex flex-row flex-center flex-cross-center menu-button"
|
||||
@ -216,12 +205,13 @@ 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 ExecAction from './js/ExecAction';
|
||||
import DefaultSettingsPanel from './panels/DefaultSettingsPanel';
|
||||
import AboutPanel from './panels/AboutPanel';
|
||||
import ExtensionMode from '../common/enums/extension-mode.enum';
|
||||
import ExtensionMode from '../common/enums/ExtensionMode.enum';
|
||||
import Logger from '../ext/lib/Logger';
|
||||
import {ChromeShittinessMitigations as CSM} from '../common/js/ChromeShittinessMitigations';
|
||||
import { browser } from 'webextension-polyfill-ts';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
@ -260,7 +250,8 @@ export default {
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
|
||||
const port = BrowserDetect.firefox ? browser.runtime.connect({name: 'popup-port'}) : chrome.runtime.connect({name: 'popup-port'});
|
||||
// const port = BrowserDetect.firefox ? browser.runtime.connect({name: 'popup-port'}) : chrome.runtime.connect({name: 'popup-port'});
|
||||
const port = browser.runtime.connect({name: 'popup-port'});
|
||||
port.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
|
||||
CSM.setProperty('port', port);
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
import Comms from '../../ext/lib/comms/Comms';
|
||||
import { browser } from '../../../node_modules/webextension-polyfill-ts/lib/index';
|
||||
import Settings from '../../ext/lib/Settings';
|
||||
|
||||
class ExecAction {
|
||||
settings: Settings;
|
||||
site: any;
|
||||
|
||||
constructor(settings, site) {
|
||||
this.settings = settings;
|
||||
this.site = site;
|
||||
@ -24,7 +28,7 @@ class ExecAction {
|
||||
arg: cmd.arg,
|
||||
customArg: cmd.customArg
|
||||
}
|
||||
Comms.sendMessage(message);
|
||||
browser.runtime.sendMessage(message);
|
||||
} else {
|
||||
|
||||
// set-ar-persistence sends stuff to content scripts as well (!)
|
||||
@ -41,7 +45,7 @@ class ExecAction {
|
||||
}
|
||||
// this hopefully delays settings.save() until current crops are saved on the site
|
||||
// and thus avoid any fucky-wuckies
|
||||
await Comms.sendMessage(message);
|
||||
await browser.runtime.sendMessage(message);
|
||||
}
|
||||
|
||||
let site = this.site;
|
||||
@ -59,7 +63,7 @@ class ExecAction {
|
||||
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") {
|
||||
} else if (cmd.action === "set-ExtensionMode") {
|
||||
this.settings.active.sites[site].mode = cmd.arg;
|
||||
} else if (cmd.action === "set-autoar-mode") {
|
||||
this.settings.active.sites[site].autoar = cmd.arg;
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-column" style="padding-bottom: 20px">
|
||||
<div class="flex flex-column" style="padding-bottom: 20px; position: relative">
|
||||
<!-- <div class="">
|
||||
<div class="label">Player picker</div>
|
||||
<div class="desc">
|
||||
@ -38,18 +38,18 @@
|
||||
Player detection settings<br/><small>for {{site}}</small>
|
||||
</div>
|
||||
<div class="description">
|
||||
Player is the frame around the video. Extension crops/stretches the video to fit the player.
|
||||
If extension doesn't work, you can help a little by telling it where to look for the player.<br/>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="">
|
||||
<input :checked="!playerManualQs"
|
||||
<input :checked="playerManualQs"
|
||||
@change="togglePlayerManualQs"
|
||||
type="checkbox"
|
||||
/> Detect automatically
|
||||
/> Manually specify player
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column">
|
||||
<div class="">Query selectors:</div>
|
||||
<div class="">Query selectors for player:</div>
|
||||
<input type="text"
|
||||
v-model="playerQs"
|
||||
@change="updatePlayerQuerySelector"
|
||||
@ -58,24 +58,48 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="flex flex-row">
|
||||
<input :checked="playerByNodeIndex"
|
||||
:disabled="!playerManualQs"
|
||||
@change="toggleByNodeIndex"
|
||||
type="checkbox"
|
||||
/> Specify player node parent index instead
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column">
|
||||
<div class="">Player node parent index:</div>
|
||||
<input v-model="playerParentNodeIndex"
|
||||
/>
|
||||
<div>
|
||||
Player is n-th parent of video:
|
||||
</div>
|
||||
<input v-model="playerParentNodeIndex"
|
||||
:disabled="!playerByNodeIndex || !playerManualQs"
|
||||
@change="updatePlayerParentNodeIndex"
|
||||
@blur="updatePlayerParentNodeIndex"
|
||||
type="number"
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="description">
|
||||
<small>
|
||||
<b>Hint:</b> Player is a HTML element that represents the portion of the page you expect the video to play in.
|
||||
You can provide player's query selector, or you can tick the 'player is the n-th parent of video'
|
||||
checkbox and try entering values 1-12ish and see if anything works. Note that you need to save
|
||||
settings and reload the page every time you change the number.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<input :checked="usePlayerAr"
|
||||
@change="toggleUsePlayerAr"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div>
|
||||
Do not use monitor AR in fullscreen
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="description">
|
||||
<small>
|
||||
<b>Hint:</b> When in full screen, the extension will assume that player element is as big as your screen.
|
||||
You generally want to keep this option off, unless you like to browse in fullscreen a lot.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="label">
|
||||
@ -114,7 +138,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-column">
|
||||
<div class="flex label-secondary form-label">Additional css</div>
|
||||
<div class="flex label-secondary form-label">Additional style for video element</div>
|
||||
<input type="text"
|
||||
v-model="videoCss"
|
||||
@change="updateVideoCss"
|
||||
@ -122,6 +146,68 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="label">
|
||||
Browser quirk mitigations
|
||||
</div>
|
||||
<div class="description">
|
||||
Sometimes, the extension may misbehave as a result of issues and bugs present in your browser, operating system or your GPU driver.
|
||||
Some of the issues can be fixed by limiting certain functionalities of this addon.
|
||||
</div>
|
||||
<div class="flex flex-column">
|
||||
<div
|
||||
v-if="BrowserDetect.anyChromium"
|
||||
class="workaround flex flex-column"
|
||||
>
|
||||
<div class="flex label-secondary form-label">
|
||||
<input :checked="settings?.active?.mitigations?.zoomLimit?.enabled"
|
||||
@change="setMitigation(['zoomLimit', 'enabled'], $event.target.checked)"
|
||||
type="checkbox"
|
||||
/> Limit zoom.
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="label-secondary form-label">
|
||||
<small>Limit zoom to % of width (1=100%):</small>
|
||||
</div>
|
||||
<input type="number"
|
||||
:value="settings?.active?.mitigations?.zoomLimit?.limit || 0.997"
|
||||
step="0.001"
|
||||
min="0.5"
|
||||
@change="setMitigation(['zoomLimit', 'limit'], +$event.target.value)"
|
||||
@blur="updateVideoQuerySelector"
|
||||
:disabled="!settings?.active?.mitigations?.zoomLimit?.enabled"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex label-secondary form-label">
|
||||
<input :checked="settings?.active?.mitigations?.zoomLimit?.fullscreenOnly"
|
||||
@change="setMitigation(['zoomLimit', 'fullscreenOnly'], $event.target.checked)"
|
||||
:disabled="!settings?.active?.mitigations?.zoomLimit?.enabled"
|
||||
type="checkbox"
|
||||
/> Limit zoom only while in fullscreen
|
||||
</div>
|
||||
<div class="description">
|
||||
<small>
|
||||
<b>Fix for:</b> Chrome and Edge used to have a bug where videos would get incorrectly stretched when zoomed in too far.
|
||||
The issue only appeared in fullscreen, on nVidia GPUs, and with hardware acceleration enabled. While this option only
|
||||
needs to be applied in fullscreen, fullscreen detection in Chrome can be a bit unreliable (depending on your OS and/or
|
||||
display scaling settings). <a href="https://stuff.tamius.net/sacred-texts/2021/02/01/ultrawidify-and-chrome-2021-edition/" target="_blank">More about the issue</a>.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div> </div>
|
||||
|
||||
<div> </div>
|
||||
|
||||
</div>
|
||||
<div id="save-banner-observer-bait">
|
||||
|
||||
</div>
|
||||
<div
|
||||
id="save-banner"
|
||||
class="save-banner"
|
||||
>
|
||||
<div class="button">Save settings</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -130,9 +216,11 @@
|
||||
import ShortcutButton from '../../common/components/ShortcutButton.vue';
|
||||
import QsElement from '../../common/components/QsElement.vue';
|
||||
import QuerySelectorSetting from '../../common/components/QuerySelectorSetting.vue';
|
||||
import ExtensionMode from '../../common/enums/extension-mode.enum';
|
||||
import VideoAlignment from '../../common/enums/video-alignment.enum';
|
||||
import Stretch from '../../common/enums/stretch.enum';
|
||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
||||
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
||||
import StretchType from '../../common/enums/StretchType.enum';
|
||||
import BrowserDetect from '../../ext/conf/BrowserDetect';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
QuerySelectorSetting,
|
||||
@ -149,6 +237,8 @@ export default {
|
||||
playerCss: '',
|
||||
playerByNodeIndex: false,
|
||||
playerParentNodeIndex: undefined,
|
||||
usePlayerAr: false,
|
||||
BrowserDetect
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@ -169,6 +259,7 @@ export default {
|
||||
this.playerQs = this.settings.active.sites[this.site].DOM.player.querySelectors;
|
||||
this.playerByNodeIndex = this.settings.active.sites[this.site].DOM.player.useRelativeAncestor || this.playerByNodeIndex;
|
||||
this.playerParentNodeIndex = this.settings.active.sites[this.site].DOM.player.videoAncestor;
|
||||
this.usePlayerAr = this.settings.active.sites[this.site].usePlayerArInFullscreen;
|
||||
} catch (e) {
|
||||
// that's here just in case relevant settings for this site don't exist yet
|
||||
}
|
||||
@ -179,15 +270,32 @@ export default {
|
||||
// that's here just in case relevant settings for this site don't exist yet
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.createObserver();
|
||||
},
|
||||
methods: {
|
||||
createObserver() {
|
||||
const saveButtonBait = document.getElementById('save-banner-observer-bait');
|
||||
const saveButton = document.getElementById('save-banner');
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([e]) => {
|
||||
// console.log('observer triggered. intersection ratio?', e.intersectionRatio)
|
||||
saveButton.classList.toggle('floating', e.intersectionRatio < 0.95);
|
||||
},
|
||||
{threshold: [0, 0.5, 0.9, 0.95, 1]}
|
||||
);
|
||||
|
||||
observer.observe(saveButtonBait);
|
||||
},
|
||||
ensureSettings(scope) {
|
||||
if (! this.settings.active.sites[this.site]) {
|
||||
this.settings.active.sites[this.site] = {
|
||||
mode: ExtensionMode.Default,
|
||||
autoar: ExtensionMode.Default,
|
||||
type: 'user-added',
|
||||
stretch: Stretch.Default,
|
||||
videoAlignment: VideoAlignment.Default,
|
||||
stretch: StretchType.Default,
|
||||
videoAlignment: VideoAlignmentType.Default,
|
||||
keyboardShortcutsEnabled: ExtensionMode.Default,
|
||||
}
|
||||
}
|
||||
@ -248,12 +356,45 @@ export default {
|
||||
this.settings.active.sites[this.site].DOM.player.useRelativeAncestor = this.playerByNodeIndex;
|
||||
this.settings.save();
|
||||
},
|
||||
toggleUsePlayerAr() {
|
||||
this.ensureSettings('player');
|
||||
this.usePlayerAr = !this.usePlayerAr;
|
||||
this.settings.active.sites[this.site].usePlayerArInFullscreen = this.usePlayerAr;
|
||||
this.settings.save();
|
||||
},
|
||||
setMitigation(mitigation, value) {
|
||||
// ensure mitigations object exists.
|
||||
// it may not exist in the settings on first load
|
||||
if (! this.settings.active.mitigations) {
|
||||
this.settings.active.mitigations = {};
|
||||
}
|
||||
|
||||
if (Array.isArray(mitigation)) {
|
||||
let currentMitigationsParent = this.settings.active.mitigations;
|
||||
|
||||
for (let i = 0; i < mitigation.length - 1; i++) {
|
||||
if (!currentMitigationsParent[mitigation[i]]) {
|
||||
currentMitigationsParent[mitigation[i]] = {};
|
||||
}
|
||||
currentMitigationsParent = currentMitigationsParent[mitigation[i]];
|
||||
}
|
||||
|
||||
currentMitigationsParent[mitigation[mitigation.length - 1]] = value;
|
||||
|
||||
} else {
|
||||
this.settings.active.mitigations[mitigation] = value;
|
||||
}
|
||||
|
||||
this.settings.save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
@import '../../res/css/colors.scss';
|
||||
|
||||
.pp_video {
|
||||
margin: 2px;
|
||||
padding: 5px;
|
||||
@ -279,4 +420,18 @@ export default {
|
||||
padding: 2px;
|
||||
border: 2px solid #f00;
|
||||
}
|
||||
|
||||
.save-banner {
|
||||
display: block;
|
||||
position: sticky;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
background-color: #131313;
|
||||
text-align: center;
|
||||
|
||||
&.floating {
|
||||
box-shadow: 0 2rem 3rem 1rem $selected-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -125,7 +125,8 @@ import ExecAction from '../js/ExecAction';
|
||||
import KeyboardShortcutParser from '../../common/js/KeyboardShortcutParser';
|
||||
import ShortcutButton from '../../common/components/ShortcutButton';
|
||||
import ComputeActionsMixin from '../../common/mixins/ComputeActionsMixin';
|
||||
import CropModePersistence from '../../common/enums/crop-mode-persistence.enum';
|
||||
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
||||
import BrowserDetect from '../../ext/conf/BrowserDetect';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@ -157,7 +158,11 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async openOptionsPage() {
|
||||
(browser ?? chrome).runtime.openOptionsPage();
|
||||
if (BrowserDetect.firefox) {
|
||||
browser.runtime.openOptionsPage();
|
||||
} else {
|
||||
chrome.runtime.openOptionsPage();
|
||||
}
|
||||
},
|
||||
execAction(action) {
|
||||
this.exec.exec(action, 'page', this.frame);
|
||||
|
@ -2,29 +2,19 @@
|
||||
<div>
|
||||
<h2>What's new</h2>
|
||||
<p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md">is available here</a>.</p>
|
||||
<p class="label">4.5.1</p>
|
||||
<p class="label">5.0.4</p>
|
||||
<ul>
|
||||
<li>Fixed the misalignment issue on netflix ... hopefully.</li>
|
||||
<li>
|
||||
'Site settings' tab should now work in Chrome as well (<a href="https://github.com/tamius-han/ultrawidify/issues/126">#126</a>)
|
||||
</li>
|
||||
<li>
|
||||
Popup interface now refreshes properly (<a href="https://github.com/tamius-han/ultrawidify/issues/127">#127</a>)
|
||||
</li>
|
||||
<li>
|
||||
Videos should now be scaled correctly when the display is narrower than video's native aspect ratio (<a href="https://github.com/tamius-han/ultrawidify/issues/118">#118</a>)
|
||||
</li>
|
||||
<li>
|
||||
Fullscreen videos on streamable are aligned correctly (<a href="https://github.com/tamius-han/ultrawidify/issues/118">#116</a>). Note that while this fix hasn't broken any sites I have access to and know of,
|
||||
there is a small possibility that this bugfix may break something somewhere. If this happens on a site you use, please notify me via github, email or PM me on reddit.
|
||||
</li>
|
||||
<li>
|
||||
<b>[4.5.1.1]</b> Streamable fix broke old.reddit + RES on embeds from v.redd.it and streamable.com. We're now using an alternative implementation. (<a href="https://github.com/tamius-han/ultrawidify/issues/128">#128</a>)
|
||||
<b>[4.5.1.2]</b> Fixed the issue where videos would sometimes get misaligned while using hybrid stretch. (<a href="https://github.com/tamius-han/ultrawidify/issues/125">#125</a>)
|
||||
<b>[4.5.1.3]</b> Added a potential fix for disney plus
|
||||
<b>[4.5.1.3]</b> Microsoft Edge has fixed the bugs that prevented the extension from working properly. Popup should no longer be shown.
|
||||
Updated config for disney+. Hopefully it works now. Special thanks to <a href="https://github.com/tamius-han/ultrawidify/issues/84#issuecomment-846334005" target="_blank">@jwannebo</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<small><b>NOTE from older versions:</b> zoom limitations were introduced as a workaround for a bug caused by Chrome's/Edge's faulty hardware acceleration.</small>
|
||||
</p>
|
||||
<p>
|
||||
<small>If you experience issues with videos being stretched incorrectly at certain zoom levels, go to:</small><br/>
|
||||
<small><code>extension popup > Advanced Settings > Browser quirk mitigations > limit zoom.</code></small>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
6
src/shims-vue.d.ts
vendored
Normal file
6
src/shims-vue.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./ts-out",
|
||||
"allowJs": true,
|
||||
"target": "es2018",
|
||||
"types": [
|
||||
"chrome",
|
||||
"node"
|
||||
],
|
||||
"typeRoots": [
|
||||
"node_modules/@types",
|
||||
"node_modules/web-ext-types"
|
||||
],
|
||||
},
|
||||
"include": [ "./src/**/*" ],
|
||||
}
|
@ -22,19 +22,23 @@ const config = {
|
||||
path: __dirname + `/dist-${process.env.BROWSER == 'firefox' ? 'ff' : process.env.BROWSER}`,
|
||||
filename: '[name].js',
|
||||
},
|
||||
|
||||
devtool: "source-map",
|
||||
|
||||
resolve: {
|
||||
// maybe we'll move to TS some day, but today is not the day
|
||||
extensions: [
|
||||
// '.ts', '.tsx',
|
||||
'.ts', '.tsx',
|
||||
'.js', '.vue'
|
||||
],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
// {
|
||||
// test: /\.tsx?$/,
|
||||
// loader: 'ts-loader',
|
||||
// },
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loaders: 'vue-loader',
|
||||
@ -121,7 +125,7 @@ const config = {
|
||||
.substr(2) // YYYY -> YY
|
||||
.replace('-', '') // YY-MM-DD -> YYMM-DD
|
||||
.replace('-', '.') // YYMM-DD -> YYMM.DD
|
||||
}.${process.env.BUILD_NUMBER}`;
|
||||
}.${process.env.BUILD_NUMBER === undefined ? 0 : process.env.BUILD_NUMBER}`;
|
||||
jsonContent.browser_action.default_title = "Ultrawidify Nightly";
|
||||
|
||||
// because we don't want web-ext to submit this as proper release
|
||||
@ -137,7 +141,7 @@ const config = {
|
||||
.substr(2) // YYYY -> YY
|
||||
.replace('-', '') // YY-MM-DD -> YYMM-DD
|
||||
.replace('-', '.') // YYMM-DD -> YYMM.DD
|
||||
}.${process.env.BUILD_NUMBER}`;
|
||||
}.${process.env.BUILD_NUMBER === undefined ? 0 : process.env.BUILD_NUMBER}`;
|
||||
jsonContent.browser_action.default_title = "Ultrawidify Testing";
|
||||
|
||||
// because we don't want web-ext to submit this as proper release
|
||||
@ -159,7 +163,10 @@ const config = {
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.BROWSER': JSON.stringify(process.env.BROWSER),
|
||||
'process.env.CHANNEL': JSON.stringify(process.env.CHANNEL)
|
||||
'process.env.CHANNEL': JSON.stringify(process.env.CHANNEL),
|
||||
|
||||
'__VUE_OPTIONS_API__': true,
|
||||
'__VUE_PROD_DEVTOOLS__': false
|
||||
})
|
||||
],
|
||||
optimization: {
|
||||
@ -175,6 +182,9 @@ const config = {
|
||||
if (config.mode === 'production') {
|
||||
config.plugins = (config.plugins || []).concat([
|
||||
new webpack.DefinePlugin({
|
||||
'__VUE_OPTIONS_API__': true,
|
||||
'__VUE_PROD_DEVTOOLS__': false,
|
||||
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"',
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user