Compare commits

..

18 Commits

Author SHA1 Message Date
e56d32180c Add GL canvas wrapper 2024-10-09 23:54:21 +02:00
6c92837317 wip 2024-06-12 20:29:00 +02:00
d4015a86ef Version bump 2024-06-12 09:17:52 +02:00
4b4be96e26 Fix one more link 2024-06-12 09:09:25 +02:00
823e1d983c Remove currently inactive downloads permission 2024-06-12 09:06:12 +02:00
47fba5b81a Fix external links 2024-06-12 03:15:02 +02:00
ee23ebb705 Update permissions for mmanifest v3 2024-06-12 02:17:03 +02:00
d6d3a95daf Last pre-publish fixes 2024-06-12 01:55:12 +02:00
e839e632fc Merge branch 'feature/player-ui' 2024-06-11 02:39:09 +02:00
cb6f0a9a32 Changelog.MD update 2024-06-11 02:36:36 +02:00
12644bfbea get manifest v3 to approximately work 2024-05-30 01:02:09 +02:00
5ca9325e16 fix 2024-01-28 13:08:31 +01:00
b8fdf0911a Update changelog 2024-01-28 13:01:45 +01:00
a7302fee4b fix angle detection 2024-01-28 13:01:16 +01:00
41bf4d2764 fix 2024-01-28 13:00:48 +01:00
0970140a3a Version bump 2024-01-27 23:29:03 +01:00
b8eb8ec052 rework about panel 2024-01-27 23:06:56 +01:00
46b7d6a25a Round pixel offset 2024-01-27 22:24:09 +01:00
23 changed files with 671 additions and 327 deletions

View File

@ -11,12 +11,43 @@
## v7.0 (planned major)
* WebGL autodetection
## v6.0 (next major)
## v6.0 (current major)
* in-player GUI
* Fix UI logger
### v6.0.1
## v5.x (current major)
* Fixed external links
### v6.0.0
Chrome only, because I needed to rush manifest v3 migration before ensuring things work in Firefox.
* In player UI
* New alignment options (can align video both vertically, as well as horizontally)
* Extension does a little bit of a better job differentiating between levels of support for a given site
* Changed how cropping and panning works. This should lead to fewer problems and better generic support.
* REGRESSION: no manual panning with shift + mouse movement
* "Player select" screen, which provides a GUI way for picking HTML element that acts as the video player area
Regressions and potential issues:
* Settings were largely not migrated from previous versions. This is as intended, since changes to cropping (and some other aspects)
rendered certain old settings obsolete.
* Extension can no longer discriminate between iframes — extension popup commands get sent to ALL iframes on page
* Extension probably can't discriminate between multiple videos on a single page
## v5.x
### v5.1.7
Firefox-only.
* When cropping and panning video, CSS' `transform: translate(x,y)` now uses integers _always_. Before that fix, `translate(x,y)` could be offset by x.5 px, and that half of a pixel introduced a 1px thick line on the portion of the left edge of the video.
### v5.1.7
* When aligning videos, ensure video is never translated by a fractional value
* As of recent nVidia driver update in Edge, Angle detection fails in a way that prevents popup from showing up. This problem should be fixed now.
The angle detection problem was fixed by disabling the angle detection check for now, as Chrome, et al. appear to have fixed buggy video hardware acceleration.
### v5.1.6

25
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "ultrawidify",
"version": "6.0.0",
"version": "6.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -3796,6 +3796,16 @@
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"optional": true,
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
@ -6811,6 +6821,13 @@
"integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==",
"dev": true
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true,
"optional": true
},
"filename-reserved-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
@ -7485,6 +7502,11 @@
}
}
},
"gl-matrix": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@ -16019,6 +16041,7 @@
"dev": true,
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
},

View File

@ -1,6 +1,6 @@
{
"name": "ultrawidify",
"version": "6.0.0",
"version": "6.0.1",
"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": {
@ -31,6 +31,7 @@
"@types/resize-observer-browser": "^0.1.6",
"concurrently": "^5.3.0",
"fs-extra": "^7.0.1",
"gl-matrix": "^3.4.3",
"json-cyclic": "0.0.3",
"lodash": "^4.17.21",
"mdi-vue": "^3.0.11",

View File

@ -185,6 +185,12 @@ interface SettingsInterface {
}
},
ui: {
inPlayer: {
enabled: boolean
}
}
restrictions?: RestrictionsSettings;
crop: {
@ -310,52 +316,6 @@ interface SettingsInterface {
sites: {
[x: string]: SiteSettingsInterface,
}
// sites: {
// [x: string]: {
// defaultCrop?: any, // v6 new
// defaultStretch?: any, // v6 new
// enabled: ExtensionEnvironmentSettingsInterface, // v6 new
// enabledAard: ExtensionEnvironmentSettingsInterface,// v6 new
// // everything 'superseded by' needs to be implemented
// // as well as ported from the old settings
// mode?: ExtensionMode, // v6 — superseded by looking at enableIn
// autoar?: ExtensionMode, // v6 — superseded by looking at enableIn
// autoarFallback?: ExtensionMode, // v6 — deprecated, no replacement
// stretch?: StretchType, // v6 — superseded by defaultStretch
// 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;
// restrictions?: RestrictionsSettings;
// }
// }
}
export interface SiteSettingsInterface {

View File

@ -29,7 +29,7 @@
</ul>
<p>
<small>
[1] If using anything other than Firefox, Chrome, or Edge, please check if issue also happens in Chrome or Edge. Bugs affecting Opera will only get fixed if they also affect Chrome, because <a href="https://stuff.tamius.net/sacred-texts/2024/06/08/why-im-boycotting-opera/">I'm salty</a>.<br/>
[1] If using anything other than Firefox, Chrome, or Edge, please check if issue also happens in Chrome or Edge. Bugs affecting Opera will only get fixed if they also affect Chrome, because <a target="_blank" href="https://stuff.tamius.net/sacred-texts/2024/06/08/why-im-boycotting-opera/">I'm salty</a>.<br/>
[2] It is recommended that users of Microsoft Edge install Ultrawidify from the Microsoft Edge Addons source
</small>
</p>
@ -41,7 +41,7 @@
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction.
</p>
<p class="text-center">
<a class="donate" href="https://www.paypal.com/paypalme/tamius">Donate on Paypal</a>
<a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a>
</p>
<h2>Fun stuff</h2>
<p>

View File

@ -3,7 +3,10 @@
<div class="flex flex-row gap-2 bg-black">
<div class="w-[1/2]" style="width: 50%">
<h1>What's new</h1>
<p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md">is available here</a>.</p>
<p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p>
<h2>6.0.1</h2>
<p>Fixed external links.</p>
<h2>6.0.0</h2>
<p>
@ -32,12 +35,12 @@
<li>
The in-player UI should now display a warning whenever Ultrawidify detects automatic aspect ratio detection isn't happening because the site uses DRM.
</li>
<li>
<!-- <li>
Better zooming. The slider is back, baby.
</li>
<li>
</li> -->
<!-- <li>
Panning option that's a bit more intuitive.
</li>
</li> -->
</ul>
<h3>Unbaked features</h3>
<ul>
@ -72,7 +75,7 @@
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction.
</p>
<p class="text-center">
<a class="donate" href="https://www.paypal.com/paypalme/tamius">Donate on Paypal</a>
<a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a>
</p>
<h2>Fun stuff</h2>
<p>

View File

@ -132,11 +132,11 @@
<div class="select">
<select
v-model="siteDefaultCrop"
@click="setOption('defaults.crop', $event)"
@change="setOption('defaults.crop', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="undefined"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getCommandValue(settings?.active.commands.crop, siteSettings.data.defaults.crop)}})
</option>
@ -158,11 +158,11 @@
<div class="select">
<select
v-model="siteDefaultStretch"
@click="setOption('defaults.stretch', $event)"
@change="setOption('defaults.stretch', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="undefined"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getCommandValue(settings?.active.commands.stretch, siteSettings.data.defaults.stretch)}})
</option>
@ -183,11 +183,11 @@
<div class="select">
<select
v-model="siteDefaultAlignment"
@click="setOption('defaults.alignment', $event)"
@change="setOption('defaults.alignment', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="undefined"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getAlignmentLabel(siteSettings.data.defaults.alignment)}})
</option>
@ -268,13 +268,14 @@ export default {
}
},
siteDefaultCrop() {
return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : undefined;
// console.log('Getting default site crop:', this.siteSettings.raw?.defaults?.crop ? 'yay' : '{useDefault}', this.siteSettings.raw?.defaults?.crop)
return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true});
},
siteDefaultStretch() {
return this.siteSettings.raw?.defaults?.stretch ? JSON.stringify(this.siteSettings.raw?.defaults?.stretch) : undefined;
return this.siteSettings.raw?.defaults?.stretch ? JSON.stringify(this.siteSettings.raw?.defaults?.stretch) : JSON.stringify({useDefault: true});
},
siteDefaultAlignment() {
return this.siteSettings.raw?.defaults?.alignment ? JSON.stringify(this.siteSettings.raw?.defaults?.alignment) : undefined;
return this.siteSettings.raw?.defaults?.alignment ? JSON.stringify(this.siteSettings.raw?.defaults?.alignment) : JSON.stringify({useDefault: true});
},
siteDefaultCropPersistence() {
return this.siteSettings.raw?.persistCSA ?? undefined;
@ -386,17 +387,24 @@ export default {
getOption(option) {
},
setOption(option, $event) {
async setOption(option, $event) {
const value = $event.target.value;
let commandArguments;
// if argument is json, parse json. Otherwise, pass the value as-is
try {
commandArguments = $event.target.value !== undefined ? JSON.parse($event.target.value) : undefined;
commandArguments = value !== undefined ? JSON.parse(value) : undefined;
} catch(e) {
commandArguments = $event.target.value;
commandArguments = value;
}
this.siteSettings.set(option, commandArguments);
if (commandArguments.useDefault) {
commandArguments = undefined;
}
// console.log('setting option', option, 'to cmd:', commandArguments, 'event data in:', value);
await this.siteSettings.set(option, commandArguments);
this.$nextTick( () => this.$forceUpdate() );
},
setExtensionMode(component, event) {
const option = event.target.value;

View File

@ -120,7 +120,7 @@
<div class="select">
<select
:value="siteDefaultCrop"
@click="setDefaultCrop($event, 'site')"
@change="setDefaultCrop($event, 'site')"
>
<option
v-for="(command, index) of settings?.active.commands.crop"

View File

@ -134,7 +134,7 @@
<div class="select">
<select
v-model="siteDefaultStretchMode"
@click="setDefaultStretchingMode($event, 'site')"
@change="setDefaultStretchingMode($event, 'site')"
>
<option
v-for="(command, index) of settings?.active.commands.stretch"

View File

@ -40,6 +40,7 @@ import Comms from '../../ext/lib/comms/Comms';
import ShortcutButton from '../../common/components/ShortcutButton';
import BrowserDetect from '../../ext/conf/BrowserDetect';
export default {
components: {
ShortcutButton,
@ -53,11 +54,13 @@ export default {
loggerSettingsError: false,
lastLoadedLoggerSettings: undefined,
mailtoLink: '',
redditLink: '',
showEasterEgg: false,
gmailLink: '',
}
},
async created() {
const b64="dGFtaXVzLmhhbkBnbWFpbC5jb20";
this.gmailLink = atob(b64);
const messageTemplate = encodeURIComponent(
`Describe your issue in more detail. In case of misaligned videos, please provide screenshots. When reporting\
issues with autodetection not detecting aspect ratio correctly, please provide a link with timestamp to the\

View File

@ -11,129 +11,14 @@ import AspectRatioType from '../../common/enums/AspectRatioType.enum';
const ExtensionConfPatch = [
{
forVersion: '5.0.5',
sites: {
"app.plex.tv": {
mode: 3,
autoar: 3,
type: "community",
stretch: -1,
videoAlignment: -1,
keyboardShortcutsEnabled: 0,
DOM: {
player: {
manual: false,
querySelectors: "",
additionalCss: "",
useRelativeAncestor: false,
playerNodeCss: ""
}
},
css: "body {\n background-color: #000;\n}\n\n.application {\n background-color: #000;\n}"
}
}
}, {
forVersion: '5.0.6',
sites: {
"metaivi.com": {
mode: 0,
autoar: 0,
type: "community",
stretch: -1,
videoAlignment: -1,
DOM: {
video: {
manual: false,
querySelectors: "",
additionalCss: "position: absolute !important;"
},
player: {
manual: false,
querySelectors: "",
additionalCss: "",
useRelativeAncestor: false,
playerNodeCss: ""
}
},
"css": ""
},
"piped.kavin.rocks": {
mode: 0,
autoar: 0,
type: 'community',
autoarFallback: 0,
stretch: 0,
videoAlignment: -1,
keyboardShortcutsEnabled: 0,
DOM: {
player: {
manual: false,
querySelectors: "",
additionalCss: "",
useRelativeAncestor: false,
playerNodeCss: ""
}
},
css: ".shaka-video-container {\n flex-direction: column !important;\n}"
},
},
updateFn: (userOptions, defaultOptions) => {
// 5.0.5 initially incorrectly had app.plex.tv marked as 'user-added'
// when 'user-added' is generally reserved for marking sites with user-
// changed configuration. Site patches submitted by community should have
// 'community' type. extConfPatch for 5.0.5 was also retroactively corrected.
userOptions.sites['app.plex.tv'].type = 'community';
userOptions.sites['piped.kavin.rocks'] = {
mode: 0,
autoar: 0,
type: 'community',
autoarFallback: 0,
stretch: 0,
videoAlignment: -1,
keyboardShortcutsEnabled: 0,
DOM: {
player: {
manual: false,
querySelectors: "",
additionalCss: "",
useRelativeAncestor: false,
playerNodeCss: ""
}
},
css: ".shaka-video-container {\n flex-direction: column !important;\n}"
};
}
}, {
forVersion: '5.0.7',
updateFn: (userOptions, defaultOptions) => {
// 5.0.5 initially incorrectly had app.plex.tv marked as 'user-added'
// when 'user-added' is generally reserved for marking sites with user-
// changed configuration. Site patches submitted by community should have
// 'community' type. extConfPatch for 5.0.5 was also retroactively corrected.
userOptions.sites['www.youtube.com'].DOM.player = {
manual: true,
querySelectors: "#movie_player, #player, #c4-player",
additionalCss: "",
useRelativeAncestor: false,
playerNodeCss: "",
}
}
}, {
forVersion: '5.0.8',
updateFn: (userOptions, defaultOptions) => {
userOptions.sites['www.netflix.com'].DOM.player = {
manual: false
}
}
}, {
forVersion: '5.99.0-1',
forVersion: '6.0.1-1',
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
// add new commands
userOptions.commands = defaultOptions.commands;
}
}, {
// NOTE - when releasing shit, ensure ALL alpha migrations are combined together in one function
forVersion: '5.99.0-2',
forVersion: '6.0.1-2',
updateFn: (userOptions, defaultOptions) => {
userOptions.commands = defaultOptions.commands;
@ -204,7 +89,7 @@ const ExtensionConfPatch = [
}
}
}, {
forVersion: '5.99.0-3',
forVersion: '6.0.1-3',
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
delete (userOptions as any).sites['@global'].persistOption;
delete (userOptions as any).sites['@empty'].persistOption;
@ -213,7 +98,7 @@ const ExtensionConfPatch = [
userOptions.sites['@empty'].persistCSA = CropModePersistence.Disabled;
}
}, {
forVersion: '5.99.0-4',
forVersion: '6.0.1-4',
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
// deprecated much?
@ -254,7 +139,7 @@ const ExtensionConfPatch = [
})
}
}, {
forVersion: '5.99.5',
forVersion: '6.0.1-5',
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
if (!userOptions.sites['@global'].defaults.alignment || !userOptions.sites['@global'].defaults.alignment.x || !userOptions.sites['@global'].defaults.alignment.y) {
userOptions.sites['@global'].defaults.alignment = {
@ -265,7 +150,7 @@ const ExtensionConfPatch = [
userOptions.sites['@empty'].defaults.alignment = {x: VideoAlignmentType.Default, y: VideoAlignmentType.Default};
}
}, {
forVersion: '5.99.6',
forVersion: '6.0.1-6',
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
for (const site in userOptions.sites) {
userOptions.sites[site].defaultType = userOptions.sites[site].type as any;
@ -273,6 +158,20 @@ const ExtensionConfPatch = [
userOptions.sites['@global'].defaultType = 'unknown';
userOptions.sites['@empty'].defaultType = 'modified';
}
}, {
forVersion: '6.0.1-7',
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
// remove custom CSS, as it is no longer needed
for (const site in userOptions.sites) {
for (const domOption in userOptions.sites[site].DOMConfig)
userOptions.sites[site].DOMConfig[domOption].customCss;
}
userOptions.ui = {
inPlayer: {
enabled: false, // keep disabled for existing users
}
}
}
}
];

View File

@ -141,6 +141,13 @@ const ExtensionConf: SettingsInterface = {
testRowOffset: 0.02 // we test this % of height from detected edge
}
},
ui: {
inPlayer: {
enabled: true, // enable by default on new installs
}
},
crop: {
default: {
type: AspectRatioType.Automatic,
@ -1445,7 +1452,7 @@ const ExtensionConf: SettingsInterface = {
persistCSA: CropModePersistence.Disabled,
defaults: {
crop: {type: AspectRatioType.Reset}, // does NOT override Aard
crop: {type: AspectRatioType.Automatic},
stretch: StretchType.NoStretch,
alignment: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Center},
}
@ -1561,7 +1568,7 @@ const ExtensionConf: SettingsInterface = {
querySelectors: ".btm-media-client-element"
}
},
customCss: ".hudson-container { height: 100%; }"
// customCss: ".hudson-container { height: 100%; }"
}
}
},
@ -1584,32 +1591,6 @@ const ExtensionConf: SettingsInterface = {
type: 'official',
defaultType: 'official',
},
// "streamable.com": {
// enable: {
// fullscreen: ExtensionMode.Default,
// theater: ExtensionMode.Default,
// normal: ExtensionMode.Default,
// },
// enableAard: {
// fullscreen: ExtensionMode.Default,
// theater: ExtensionMode.Default,
// normal: ExtensionMode.Default,
// },
// enableKeyboard: {
// fullscreen: ExtensionMode.Default,
// theater: ExtensionMode.Default,
// normal: ExtensionMode.Default
// },
// type: 'official',
// type: 'defaultType',
// activeDOMConfig: 'official',
// DOMConfig: {
// 'official': {
// type: 'official',
// customCss: ".player {text-align: left}"
// }
// }
// },
"vimeo.com": {
enable: {
fullscreen: ExtensionMode.Default,
@ -1632,7 +1613,7 @@ const ExtensionConf: SettingsInterface = {
DOMConfig: {
'official': {
type: 'official',
customCss: ".player_outro_area {\n width: 100% !important;\n display: flex !important;\n justify-content: center !important;\n}\n\n.player_container, .player {\n width: 100% !important; \n}",
// customCss: ".player_outro_area {\n width: 100% !important;\n display: flex !important;\n justify-content: center !important;\n}\n\n.player_container, .player {\n width: 100% !important; \n}",
elements: {
player: {
manual: true,
@ -1664,7 +1645,7 @@ const ExtensionConf: SettingsInterface = {
DOMConfig: {
'official': {
type: 'official',
customCss: 'video {\n width: 100% !important;\n height: 100% !important;\n}',
// customCss: 'video {\n width: 100% !important;\n height: 100% !important;\n}',
elements: {
player: {
manual: false,
@ -1696,7 +1677,7 @@ const ExtensionConf: SettingsInterface = {
DOMConfig: {
'official': {
type: 'official',
customCss: 'video {\n width: 100% !important;\n height: 100% !important;\n}',
// customCss: 'video {\n width: 100% !important;\n height: 100% !important;\n}',
elements: {
player: {
manual: false,
@ -1816,7 +1797,7 @@ const ExtensionConf: SettingsInterface = {
DOMConfig: {
'community': {
type: 'community',
customCss: "body {\n background-color: #000;\n}\n\n.application {\n background-color: #000;\n}"
// customCss: "body {\n background-color: #000;\n}\n\n.application {\n background-color: #000;\n}"
}
}
},
@ -1872,7 +1853,7 @@ const ExtensionConf: SettingsInterface = {
DOMConfig: {
'community': {
type: 'community',
customCss: ".shaka-video-container {\n flex-direction: column !important;\n}"
// customCss: ".shaka-video-container {\n flex-direction: column !important;\n}"
}
}
},

View File

@ -0,0 +1,326 @@
import { mat4 } from 'gl-matrix';
import { GlCanvasBuffers, initBuffers } from './gl-init';
export interface GlCanvasOptions {
width: number;
height: number;
}
// Vertex shader program
const vsSource = `
attribute vec4 aVertexPosition;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;
uniform mat4 uNormalMatrix;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
varying highp vec2 vTextureCoord;
void main(void) {
gl_Position = uProjectionMatrix * aVertexPosition;
vTextureCoord = aTextureCoord;
}
`;
// Fragment shader program
const fsSource = `
varying highp vec2 vTextureCoord;
uniform sampler2D uSampler;
void main(void) {
highp vec4 texelColor = texture2D(uSampler, vTextureCoord);
gl_FragColor = vec4(texelColor.rgb, texelColor.a);
}
`;
interface GlCanvasProgramInfo {
program: WebGLProgram;
attribLocations: {
vertexPosition: number;
vertexNormal: number;
textureCoord: number;
};
uniformLocations: {
projectionMatrix: WebGLUniformLocation;
modelViewMatrix: WebGLUniformLocation;
normalMatrix: WebGLUniformLocation;
uSampler: WebGLUniformLocation;
};
}
export class GlCanvas {
private _canvas: HTMLCanvasElement;
private set canvas(x: HTMLCanvasElement) {
this._canvas = x;
};
public get canvas(): HTMLCanvasElement {
return this._canvas;
};
private _context: WebGLRenderingContext;
private set gl(x: WebGLRenderingContext) {
this._context = x;
};
private get gl(): WebGLRenderingContext {
return this._context;
}
private frameBufferSize: number;
private _frameBuffer: Uint8Array;
private set frameBuffer(x: Uint8Array) {
this._frameBuffer = x;
}
public get frameBuffer(): Uint8Array {
return this._frameBuffer;
}
private buffers: GlCanvasBuffers;
private texture: WebGLTexture;
private programInfo: GlCanvasProgramInfo;
private projectionMatrix: mat4;
constructor(options: GlCanvasOptions) {
this.canvas = document.createElement('canvas');
this.gl = this.canvas.getContext('webgl');
if (!this.gl) {
throw new Error('WebGL not supported');
}
this.canvas.width = options.width;
this.canvas.height = options.height;
this.frameBufferSize = options.width * options.height * 4;
this.initWebgl();
}
/**
* Draws video frame to the GL canvas
* @param video video to extract a frame from
*/
drawVideoFrame(video: HTMLVideoElement): void {
this.updateTexture(video);
this.drawScene();
}
/**
* Reads pixels from the canvas
* @returns
*/
getImageData(): Uint8Array {
this.gl.readPixels(0, 0, this.canvas.width, this.canvas.height, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.frameBuffer);
return this.frameBuffer;
}
private initWebgl() {
// Initialize the GL context
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// Create shader program
const shaderProgram = this.initShaderProgram();
// Setup params for shader program
this.programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: this.gl.getAttribLocation(shaderProgram, "aVertexPosition"),
vertexNormal: this.gl.getAttribLocation(shaderProgram, "aVertexNormal"),
textureCoord: this.gl.getAttribLocation(shaderProgram, "aTextureCoord"),
},
uniformLocations: {
projectionMatrix: this.gl.getUniformLocation(
shaderProgram,
"uProjectionMatrix"
),
modelViewMatrix: this.gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
normalMatrix: this.gl.getUniformLocation(shaderProgram, "uNormalMatrix"),
uSampler: this.gl.getUniformLocation(shaderProgram, "uSampler"),
},
};
// Here's where we call the routine that builds all the
// objects we'll be drawing.
this.buffers = initBuffers(this.gl);
this.initTexture();
// Flip image pixels into the bottom-to-top order that WebGL expects.
this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);
// Since our matrix is never going to change, we can define projection outside of drawScene function:
this.projectionMatrix = mat4.create();
mat4.ortho(this.projectionMatrix, -1, 1, -1, 1, -10, 10);
// we will be reusing our frame buffer for all draws and reads
// this improves performance and lessens production of garbage,
// translating into fewer garbage collections (probably), resulting
// in fewer hitches and other performance issues (probably)
this.frameBuffer = new Uint8Array(this.frameBufferSize);
}
private loadShader(type, source) {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
// TODO: warn if shader failed to compile
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
this.gl.deleteShader(shader);
return null;
}
return shader;
}
private initShaderProgram() {
const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource);
const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource);
// Create the shader program
const shaderProgram = this.gl.createProgram();
this.gl.attachShader(shaderProgram, vertexShader);
this.gl.attachShader(shaderProgram, fragmentShader);
this.gl.linkProgram(shaderProgram);
// TODO: maybe give a warning if program failed to initialize
if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
return null;
}
return shaderProgram;
}
private initTexture(): void {
this.texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
// Because video has to be download over the internet
// they might take a moment until it's ready so
// put a single pixel in the texture so we can
// use it immediately.
const level = 0;
const internalFormat = this.gl.RGBA;
const width = 1;
const height = 1;
const border = 0;
const srcFormat = this.gl.RGBA;
const srcType = this.gl.UNSIGNED_BYTE;
const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
this.gl.texImage2D(
this.gl.TEXTURE_2D,
level,
internalFormat,
width,
height,
border,
srcFormat,
srcType,
pixel
);
// Turn off mips and set wrapping to clamp to edge so it
// will work regardless of the dimensions of the video.
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
}
private updateTexture(video: HTMLVideoElement) {
const level = 0;
const internalFormat = this.gl.RGBA;
const srcFormat = this.gl.RGBA;
const srcType = this.gl.UNSIGNED_BYTE;
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
this.gl.texImage2D(
this.gl.TEXTURE_2D,
level,
internalFormat,
srcFormat,
srcType,
video
);
}
private setTextureAttribute() {
const num = 2; // every coordinate composed of 2 values
const type = this.gl.FLOAT; // the data in the buffer is 32-bit float
const normalize = false; // don't normalize
const stride = 0; // how many bytes to get from one set to the next
const offset = 0; // how many bytes inside the buffer to start from
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.textureCoord);
this.gl.vertexAttribPointer(
this.programInfo.attribLocations.textureCoord,
num,
type,
normalize,
stride,
offset
);
this.gl.enableVertexAttribArray(this.programInfo.attribLocations.textureCoord);
}
private setPositionAttribute() {
const numComponents = 3;
const type = this.gl.FLOAT; // the data in the buffer is 32bit floats
const normalize = false; // don't normalize
const stride = 0; // how many bytes to get from one set of values to the next
// 0 = use type and numComponents above
const offset = 0; // how many bytes inside the buffer to start from
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.position);
this.gl.vertexAttribPointer(
this.programInfo.attribLocations.vertexPosition,
numComponents,
type,
normalize,
stride,
offset
);
this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition);
}
private drawScene(): void {
/**
* Since we are drawing our frames in a way such that the entire canvas is
* always covered by rendered video, and given our video is the only object
* being rendered to the canvas, we can avoid some things, such as:
* * clearing the canvas
* * any sort of depth tests
*/
// Tell WebGL how to pull out the positions from the position
// buffer into the vertexPosition attribute.
this.setPositionAttribute();
this.setTextureAttribute();
// Tell WebGL which indices to use to index the vertices, and to use
// our program when drawing video frame to the canvas
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.indices);
this.gl.useProgram(this.programInfo.program);
// Set the shader uniforms
this.gl.uniformMatrix4fv(
this.programInfo.uniformLocations.projectionMatrix,
false,
this.projectionMatrix
);
// Tell WebGL we want to affect texture unit 0, bind texture to texture unit 0,
// and tell the shader that we bound the texture to texture unit 0.
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
this.gl.uniform1i(this.programInfo.uniformLocations.uSampler, 0);
// draw geometry
const vertexCount = 6;
const type = this.gl.UNSIGNED_SHORT;
const offset = 0;
this.gl.drawElements(this.gl.TRIANGLES, vertexCount, type, offset);
}
}

View File

@ -0,0 +1,84 @@
export interface GlCanvasBuffers {
position: WebGLBuffer,
normal: WebGLBuffer,
textureCoord: WebGLBuffer,
indices: WebGLBuffer,
};
export function initBuffers(gl: WebGLRenderingContext): GlCanvasBuffers {
const positionBuffer = initPositionBuffer(gl);
const textureCoordBuffer = initTextureBuffer(gl);
const indexBuffer = initIndexBuffer(gl);
const normalBuffer = initNormalBuffer(gl);
return {
position: positionBuffer,
normal: normalBuffer,
textureCoord: textureCoordBuffer,
indices: indexBuffer,
};
}
function initPositionBuffer(gl: WebGLRenderingContext) {
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
-1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
return positionBuffer;
}
function initIndexBuffer(gl: WebGLRenderingContext) {
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
const indices = [
0, 1, 2,
0, 2, 3,
];
gl.bufferData(
gl.ELEMENT_ARRAY_BUFFER,
new Uint16Array(indices),
gl.STATIC_DRAW
);
return indexBuffer;
}
function initTextureBuffer(gl: WebGLRenderingContext) {
const textureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
const textureCoordinates = [
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
];
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(textureCoordinates),
gl.STATIC_DRAW
);
return textureCoordBuffer;
}
function initNormalBuffer(gl: WebGLRenderingContext) {
const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
const vertexNormals = [
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
];
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(vertexNormals),
gl.STATIC_DRAW
);
return normalBuffer;
}

View File

@ -18,6 +18,7 @@ export enum AngleVersion {
* @returns
*/
function detectDX(shaderSource: string) {
try {
const glsl = shaderSource.match(/#version (\d+)( es)?$/m);
const glslVer = +glsl[1];
@ -28,6 +29,9 @@ function detectDX(shaderSource: string) {
if (glslVer >= 100) {
return AngleVersion.D3D9;
}
} catch (e) {
return AngleVersion.NotAvailable;
}
}
/**
@ -68,7 +72,7 @@ function detectGl(shaderSource: string) {
* @returns
*/
function detectBackend(str) {
try {
if (str.match(/metal::float4/)) {
return AngleVersion.Metal;
}
@ -78,6 +82,9 @@ function detectBackend(str) {
}
return detectGl(str);
} catch (e) {
return AngleVersion.NotAvailable;
}
}
/**

View File

@ -324,7 +324,7 @@ export class SiteSettings {
let iterated = '';
for (i = 0; i < pathParts.length - 1; i++) {
iterated = `${iterated}.${pathParts[i]}`
iterated = `${iterated}.${pathParts[i]}`;
if (!iterator[pathParts[i]]) { // some optional paths may still be undefined, even after cloning empty object
iterator[pathParts[i]] = {};
@ -341,6 +341,7 @@ export class SiteSettings {
await this.settings.save();
} else {
await this.settings.saveWithoutReload();
this.compileSettingsObject();
}
}
}

View File

@ -152,7 +152,15 @@ class PlayerData {
this.initEventBus();
// this.notificationService = new PlayerNotificationUi(this.element, this.settings, this.eventBus);
this.ui = new UI('ultrawidifyUi', {parentElement: this.element, eventBus: this.eventBus});
if (this.videoData.settings.active.ui?.inPlayer?.enabled) {
this.ui = new UI(
'ultrawidifyUi',
{
parentElement: this.element,
eventBus: this.eventBus
}
);
}
this.dimensions = undefined;
this.overlayNode = undefined;
@ -215,7 +223,6 @@ class PlayerData {
destroy() {
document.removeEventListener('fullscreenchange', this.dimensionChangeListener);
this.stopChangeDetection();
this.destroyOverlay();
this.notificationService?.destroy();
}
//#endregion
@ -326,11 +333,14 @@ class PlayerData {
const canEnable = this.siteSettings.isEnabledForEnvironment(this.isFullscreen, this.isTheaterMode) === ExtensionMode.Enabled;
if (this.runLevel === RunLevel.Off && canEnable) {
console.log('runLevel: off -> [anything]');
this.eventBus.send('restore-ar', null);
// must be called after
this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
} else if (!canEnable && this.runLevel !== RunLevel.Off) {
// must be called before
console.log('runLevel: [anything] -> off');
this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
this.setRunLevel(RunLevel.Off);
}
@ -338,8 +348,8 @@ class PlayerData {
private handleDimensionChanges(newDimensions: PlayerDimensions, oldDimensions: PlayerDimensions) {
console.log('handling dimension changes');
if (!this.enabled) {
console.log('handling dimension changes\n\nold dimensions:', oldDimensions, '\nnew dimensions:', newDimensions, '\n\nis enabled:', this.enabled, this.runLevel, RunLevel);
if (this.runLevel === RunLevel.Off ) {
this.logger.log('info', 'debug', "[PlayerDetect] player size changed, but PlayerDetect is in disabled state. The player element is probably too small.");
return;
}
@ -354,8 +364,10 @@ class PlayerData {
|| newDimensions?.height != oldDimensions?.height
|| newDimensions?.fullscreen != oldDimensions?.fullscreen
){
console.log('dimensions changed + we are enabled. Sending restore-ar ...');
// If player size changes, we restore aspect ratio
this.eventBus.send('restore-ar', null);
this.eventBus.send('delayed-restore-ar', {delay: 500});
// this.videoData.resizer?.restore();
}
}
@ -424,59 +436,6 @@ class PlayerData {
this.observer.disconnect();
}
//#region interface
makeOverlay() {
if (!this.overlayNode) {
this.destroyOverlay();
}
let overlay = document.createElement('div');
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.position = 'absolute';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.zIndex = '1000000000';
overlay.style.pointerEvents = 'none';
this.overlayNode = overlay;
this.element.appendChild(overlay);
}
destroyOverlay() {
if(this.playerIdElement) {
this.playerIdElement.remove();
this.playerIdElement = undefined;
}
if (this.overlayNode) {
this.overlayNode.remove();
this.overlayNode = undefined;
}
}
markPlayer(name, color) {
if (!this.overlayNode) {
this.makeOverlay();
}
if (this.playerIdElement) {
this.playerIdElement.remove();
}
this.playerIdElement = document.createElement('div');
this.playerIdElement.innerHTML = `<div style="background-color: ${color}; color: #fff; position: absolute; top: 0; left: 0">${name}</div>`;
this.overlayNode.appendChild(this.playerIdElement);
}
unmarkPlayer() {
this.logger.log('info', 'debug', "[PlayerData::unmarkPlayer] unmarking player!", {playerIdElement: this.playerIdElement});
if (this.playerIdElement) {
this.playerIdElement.innerHTML = '';
this.playerIdElement.remove();
}
this.playerIdElement = undefined;
}
//#endregion
//#region helper functions
collectionHas(collection, element) {

View File

@ -739,19 +739,6 @@ class VideoData {
this.resizer.restore();
}
markPlayer(name, color) {
if (this.invalid) {
return;
}
if (this.player) {
this.player.markPlayer(name, color)
}
}
unmarkPlayer() {
this.player.unmarkPlayer();
}
isPlaying() {
return this.video && this.video.currentTime > 0 && !this.video.paused && !this.video.ended;
}

View File

@ -18,6 +18,7 @@ import { _cp } from '../../../common/js/utils';
import Settings from '../Settings';
import { Ar } from '../../../common/interfaces/ArInterface';
import { RunLevel } from '../../enum/run-level.enum';
import * as _ from 'lodash';
if(Debug.debug) {
console.log("Loading: Resizer.js");
@ -133,6 +134,18 @@ class Resizer {
}],
'restore-ar': [{
function: () => this.restore()
}],
'delayed-restore-ar': [{
function: () => {
_.debounce(
this.restore,
500,
{
leading: true,
trailing: true
}
)
}
}]
}
//#endregion
@ -738,7 +751,7 @@ class Resizer {
const alignXOffset = (realVideoWidth * stretchFactors.xFactor - this.videoData.player.dimensions.width) * 0.5;
const alignYOffset = (realVideoHeight * stretchFactors.yFactor - this.videoData.player.dimensions.height) * 0.5;
if (this.pan.relativeOffsetX || this.pan.relativeOffsetY) {
if (this.pan?.relativeOffsetX || this.pan?.relativeOffsetY) {
// don't offset when video is smaller than player
if(alignXOffset >= 0 || alignYOffset >= 0) {
translate.x += alignXOffset * this.pan.relativeOffsetX * this.zoom.scale;

View File

@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "6.0.0",
"version": "6.0.1",
"icons": {
"32":"res/icons/uw-32.png",
"64":"res/icons/uw-64.png"
@ -50,13 +50,11 @@
],
"permissions": [
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"*://*/*"
],
"optional_permissions": [
"downloads"
]
}

60
src/manifest.v2.json Normal file
View File

@ -0,0 +1,60 @@
{
"manifest_version": 2,
"name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "5.99.5",
"applications": {
"gecko": {
"id": "{cf02b1a7-a01a-4e37-a609-516a283f1ed3}"
}
},
"icons": {
"32":"res/icons/uw-32.png",
"64":"res/icons/uw-64.png"
},
"browser_action": {
"default_title": "Ultrawidify",
"default_popup": "popup/popup.html"
},
"content_scripts": [{
"matches": ["*://*/*"],
"js": [
"common/lib/browser-polyfill.js",
"ext/uw.js"
],
"all_frames": true
}],
"background": {
"persistent": true,
"scripts": [
"ext/uw-bg.js"
]
},
"options_ui": {
"page": "options/options.html",
"browser_style": false,
"open_in_tab": true
},
"web_accessible_resources": [
"./*",
"ext/*",
"res/fonts/*",
"res/css/*",
"res/img/settings/about-bg.png",
"res/icons/*",
"res/img/*",
"csui/*"
],
"permissions": [
"storage",
"activeTab",
"<all_urls>"
],
"optional_permissions": [
"downloads"
]
}