migrate settings to new and simplified SiteSettings
This commit is contained in:
parent
d0ba619b45
commit
a58edad8ea
@ -1,3 +1,12 @@
|
|||||||
export async function sleep(timeout) {
|
export async function sleep(timeout) {
|
||||||
return new Promise<void>( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
return new Promise<void>( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates deep copy of an object
|
||||||
|
* @param obj
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function _cp(obj) {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
@ -1,189 +0,0 @@
|
|||||||
class PlayerPickerHelper {
|
|
||||||
constructor (settings, callbacks) {
|
|
||||||
this.settings = settings;
|
|
||||||
this.videos = document.selectElementsByTagName('video');
|
|
||||||
this.selectedParentIndex = this.findPlayerForVideos(settings, this.videos)[0];
|
|
||||||
this.savedCss = [];
|
|
||||||
this.markVideos();
|
|
||||||
this.markIndexForAll(index);
|
|
||||||
this.markInitialQuerySelectors();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Internal functions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
saveBorder(element) {
|
|
||||||
if (this.savedCss.findIndex(x => x.element === element) !== -1) {
|
|
||||||
this.savedCss.push({element: element, border: element.style.border});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
restoreBorders() {
|
|
||||||
for (const e of this.savedCss) {
|
|
||||||
e.element.style.border = e.border;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findPlayerForVideos(settings, videos) {
|
|
||||||
const playerIndexes = [];
|
|
||||||
for (const v of videos) {
|
|
||||||
playerIndexes.push(this.findPlayerForVideo(settings, v));
|
|
||||||
}
|
|
||||||
return playerIndexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
findPlayerForVideo(settings, video) {
|
|
||||||
const host = window.location.hostname;
|
|
||||||
let element = video.parentNode;
|
|
||||||
|
|
||||||
if (this.settings.active.sites[host]
|
|
||||||
&& this.settings.active.sites[host].DOM
|
|
||||||
&& this.settings.active.sites[host].DOM.player
|
|
||||||
&& this.settings.active.sites[host].DOM.player.manual) {
|
|
||||||
if (this.settings.active.sites[host].DOM.player.useRelativeAncestor
|
|
||||||
&& this.settings.active.sites[host].DOM.player.videoAncestor) {
|
|
||||||
|
|
||||||
return this.settings.active.sites[host].DOM.player.videoAncestor;
|
|
||||||
} else if (this.settings.active.sites[host].DOM.player.querySelectors) {
|
|
||||||
const allSelectors = document.querySelectorAll(this.settings.active.sites[host].DOM.player.querySelectors);
|
|
||||||
let elementIndex = 1;
|
|
||||||
while (element && !this.collectionHas(allSelectors, element)) {
|
|
||||||
element = element.parentNode;
|
|
||||||
elementIndex++;
|
|
||||||
}
|
|
||||||
return elementIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let elementIndex = 0;
|
|
||||||
|
|
||||||
var trustCandidateAfterGrows = 2; // if candidate_width or candidate_height increases in either dimensions this many
|
|
||||||
// times, we say we found our player. (This number ignores weird elements)
|
|
||||||
// in case our <video> is bigger than player in one dimension but smaller in the other
|
|
||||||
// if site is coded properly, player can't be wider than that
|
|
||||||
var candidate_width = Math.max(element.offsetWidth, window.innerWidth);
|
|
||||||
var candidate_height = Math.max(element.offsetHeight, window.innerHeight);
|
|
||||||
var playerCandidateNode = element;
|
|
||||||
|
|
||||||
// if we haven't found element using fancy methods, we resort to the good old fashioned way
|
|
||||||
var grows = trustCandidateAfterGrows;
|
|
||||||
while(element != undefined){
|
|
||||||
// odstranimo čudne elemente, ti bi pokvarili zadeve
|
|
||||||
// remove weird elements, those would break our stuff
|
|
||||||
if ( element.offsetWidth == 0 || element.offsetHeight == 0){
|
|
||||||
element = element.parentNode;
|
|
||||||
elementIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( element.offsetHeight <= candidate_height &&
|
|
||||||
element.offsetWidth <= candidate_width ){
|
|
||||||
|
|
||||||
// if we're in fullscreen, we only consider elements that are exactly as big as the monitor.
|
|
||||||
if( ! isFullScreen ||
|
|
||||||
(element.offsetWidth == window.innerWidth && element.offsetHeight == window.innerHeight) ){
|
|
||||||
|
|
||||||
playerCandidateNode = element;
|
|
||||||
candidate_width = element.offsetWidth;
|
|
||||||
candidate_height = element.offsetHeight;
|
|
||||||
|
|
||||||
grows = trustCandidateAfterGrows;
|
|
||||||
|
|
||||||
this.logger.log('info', 'debug', "Found new candidate for player. Dimensions: w:", candidate_width, "h:",candidate_height, "node:", playerCandidateNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(grows --<= 0){
|
|
||||||
this.logger.log('info', 'playerDetect', "Current element grew in comparrison to the child. We probably found the player. breaking loop, returning current result");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
element = element.parentNode;
|
|
||||||
elementIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return elementIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
markVideos() {
|
|
||||||
for (const v of this.videos) {
|
|
||||||
this.markVideo(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
markVideo(video) {
|
|
||||||
this.saveBorder(video);
|
|
||||||
video.style.border = "1px solid #00f";
|
|
||||||
}
|
|
||||||
|
|
||||||
markIndexForAll(index){
|
|
||||||
for (const v of this.videos) {
|
|
||||||
this.markIndex(index, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markIndex(index, video) {
|
|
||||||
el = video.parentNode;
|
|
||||||
while (index --> 1) {
|
|
||||||
el = el.parentNode;
|
|
||||||
}
|
|
||||||
this.saveBorder(el);
|
|
||||||
el.style.border = "1px solid #88f";
|
|
||||||
}
|
|
||||||
markInitialQuerySelectors() {
|
|
||||||
try {
|
|
||||||
if (this.settings.active.sites[host].DOM.player.querySelectors.trim()) {
|
|
||||||
this.markQuerySelectorMatches(this.settings.active.sites[host].DOM.player.querySelectors);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// nothing to see here. something in that if spaghett is undefined, which causes
|
|
||||||
// everything to fail. Since this means we've got zero query string matches to mark,
|
|
||||||
// we just ignore the failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markQuerySelectorMatches(qsString) {
|
|
||||||
const allSelectors = document.querySelectorAll(qsString);
|
|
||||||
for (e of allSelectors) {
|
|
||||||
this.saveBorder(e);
|
|
||||||
e.style.border = "1px dashed fd2";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markQsPlayerDetection(qsString, index, video) {
|
|
||||||
let element = video.parentNode;
|
|
||||||
let elementIndex = 1;
|
|
||||||
|
|
||||||
const allSelectors = document.querySelectorAll(qsString);
|
|
||||||
while (element && !this.collectionHas(allSelectors, element)) {
|
|
||||||
element = element.parentNode;
|
|
||||||
elementIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveBorder(element)
|
|
||||||
if (elementIndex > index) {
|
|
||||||
element.style.border = "2px solid #f00"
|
|
||||||
} else if (elementIndex === index) {
|
|
||||||
element.style.border = "2px solid #027a5c"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Function that actually interface with playerpicker and do stuff
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
setQuerySelectors(querySelectorString) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PlayerPickerHelper
|
|
@ -50,7 +50,7 @@ class Settings {
|
|||||||
browser.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
|
browser.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
|
||||||
}
|
}
|
||||||
|
|
||||||
storageChangeListener(changes, area) {
|
private storageChangeListener(changes, area) {
|
||||||
if (!changes.uwSettings) {
|
if (!changes.uwSettings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ class Settings {
|
|||||||
return Settings.getExtensionVersion();
|
return Settings.getExtensionVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
compareExtensionVersions(a, b) {
|
private compareExtensionVersions(a, b) {
|
||||||
let aa = a.split('.');
|
let aa = a.split('.');
|
||||||
let bb = b.split('.');
|
let bb = b.split('.');
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrereleaseVersionHierarchy(version) {
|
private getPrereleaseVersionHierarchy(version) {
|
||||||
if (version.startsWith('dev')) {
|
if (version.startsWith('dev')) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -148,15 +148,15 @@ class Settings {
|
|||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
sortConfPatches(patchesIn) {
|
private sortConfPatches(patchesIn) {
|
||||||
return patchesIn.sort( (a, b) => this.compareExtensionVersions(a.forVersion, b.forVersion));
|
return patchesIn.sort( (a, b) => this.compareExtensionVersions(a.forVersion, b.forVersion));
|
||||||
}
|
}
|
||||||
|
|
||||||
findFirstNecessaryPatch(version, extconfPatches) {
|
private findFirstNecessaryPatch(version, extconfPatches) {
|
||||||
const sorted = this.sortConfPatches(extconfPatches);
|
const sorted = this.sortConfPatches(extconfPatches);
|
||||||
return sorted.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
|
return sorted.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
|
||||||
}
|
}
|
||||||
applySettingsPatches(oldVersion, patches) {
|
private applySettingsPatches(oldVersion, patches) {
|
||||||
let index = this.findFirstNecessaryPatch(oldVersion, patches);
|
let index = this.findFirstNecessaryPatch(oldVersion, patches);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
this.logger?.log('info','settings','[Settings::applySettingsPatches] There are no pending conf patches.');
|
this.logger?.log('info','settings','[Settings::applySettingsPatches] There are no pending conf patches.');
|
||||||
|
213
src/ext/lib/settings/SiteSettings.ts
Normal file
213
src/ext/lib/settings/SiteSettings.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
|
||||||
|
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||||
|
import { SiteSettingsInterface } from '../../../common/interfaces/SettingsInterface';
|
||||||
|
import { _cp } from '../../../common/js/utils';
|
||||||
|
import Settings from '../Settings';
|
||||||
|
import { browser } from 'webextension-polyfill-ts';
|
||||||
|
|
||||||
|
export class SiteSettings {
|
||||||
|
private settings: Settings;
|
||||||
|
private site: string;;
|
||||||
|
|
||||||
|
data: SiteSettingsInterface;
|
||||||
|
temporaryData: SiteSettingsInterface;
|
||||||
|
sessionData: SiteSettingsInterface;
|
||||||
|
private defaultSettings: SiteSettingsInterface;
|
||||||
|
|
||||||
|
//#region lifecycle
|
||||||
|
constructor(settings: Settings, site: string) {
|
||||||
|
this.settings = settings;
|
||||||
|
this.data = settings.active.sites[site];
|
||||||
|
this.defaultSettings = settings.default.sites['@global'];
|
||||||
|
|
||||||
|
this.compileSettingsObject();
|
||||||
|
|
||||||
|
// temporary data starts as a copy of real data. This ensures defaults are correct if nothing
|
||||||
|
// modifies temporary settings. (Purpose: providing until-reload persistence option)
|
||||||
|
this.temporaryData = _cp(this.data);
|
||||||
|
|
||||||
|
this.sessionData = JSON.parse(sessionStorage.getItem('uw-session-defaults'));
|
||||||
|
if (!this.sessionData) {
|
||||||
|
this.sessionData = _cp(this.data);
|
||||||
|
sessionStorage.setItem('uw-session-defaults', JSON.stringify(this.sessionData));
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges defaultSettings into site settings where appropriate.
|
||||||
|
* Alan pls ensure default settings object follows the correct structure
|
||||||
|
*/
|
||||||
|
private compileSettingsObject() {
|
||||||
|
if (!this.data) {
|
||||||
|
this.data = _cp(this.defaultSettings);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data.defaultCrop = this.data.defaultCrop ?? _cp(this.defaultSettings.defaultCrop);
|
||||||
|
this.data.defaultStretch = this.data.defaultStretch ?? _cp(this.defaultSettings.defaultStretch);
|
||||||
|
|
||||||
|
|
||||||
|
for (const enableSegment of ['enable', 'enableAard', 'enableKeyboard']) {
|
||||||
|
if (!this.data[enableSegment]) {
|
||||||
|
this.data[enableSegment] = {};
|
||||||
|
}
|
||||||
|
for (const environment of ['normal', 'theater', 'fullscreen']) {
|
||||||
|
if (!this.data[enableSegment][environment] || this.data[enableSegment][environment] === ExtensionMode.Default) {
|
||||||
|
this.data[enableSegment][environment] = _cp(this.defaultSettings[enableSegment][environment]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure data.persistOption exists:
|
||||||
|
if (!this.data.persistOption) {
|
||||||
|
this.data.persistOption = {} as any; // this will get populated correctly soon
|
||||||
|
}
|
||||||
|
for (const persistOption of ['crop', 'stretch', 'alignment']) {
|
||||||
|
if ( (this.data.persistOption[persistOption] ?? CropModePersistence.Default) === CropModePersistence.Default ) {
|
||||||
|
this.data.persistOption[persistOption] = this.defaultSettings.persistOption[persistOption];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (this.data.activeDOMConfig && this.data.DOMConfig) {
|
||||||
|
this.data.currentDOMConfig = this.data.DOMConfig[this.data.activeDOMConfig];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region events
|
||||||
|
/**
|
||||||
|
* Updates setting values for current and default site
|
||||||
|
* @param changes
|
||||||
|
* @param area
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private storageChangeListener(changes, area) {
|
||||||
|
if (!changes.uwSettings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedSettings = JSON.parse(changes.uwSettings.newValue);
|
||||||
|
this.data = parsedSettings.active.sites[this.site];
|
||||||
|
this.defaultSettings = parsedSettings.active.sites['@global'];
|
||||||
|
|
||||||
|
this.compileSettingsObject();
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets custom query selector for player or video, if element exists, is manually defined, and has querySelectors property.
|
||||||
|
* @param element player or video
|
||||||
|
* @returns querySelector if possible, undefined otherwise
|
||||||
|
*/
|
||||||
|
getCustomDOMQuerySelector(element: 'video' | 'player'): string | undefined {
|
||||||
|
return this.data.currentDOMConfig?.elements?.[element]?.manual && this.data.currentDOMConfig?.elements?.[element]?.querySelectors || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets option value.
|
||||||
|
* @param optionPath path to value in object notation (dot separated)
|
||||||
|
* @param optionValue new value of option
|
||||||
|
* @param reload whether we should trigger a reload in components that require it
|
||||||
|
*/
|
||||||
|
async set(optionPath: string, optionValue: any, reload: boolean = false) {
|
||||||
|
// if no settings exist for this site, create an empty object
|
||||||
|
if (!this.settings.active.sites[this.site]) {
|
||||||
|
this.settings.active.sites[this.site] = _cp(this.settings.active.sites['@empty']);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathParts = optionPath.split('.');
|
||||||
|
|
||||||
|
let iterator = this.settings.active.sites[this.site];
|
||||||
|
let i;
|
||||||
|
for (i = 0; i < pathParts.length - 1; i++) {
|
||||||
|
iterator = iterator[pathParts[i]];
|
||||||
|
}
|
||||||
|
iterator[pathParts[i]] = optionValue;
|
||||||
|
|
||||||
|
if (reload) {
|
||||||
|
this.settings.save();
|
||||||
|
} else {
|
||||||
|
this.settings.saveWithoutReload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets temporary option value (for Persistence.UntilReload)
|
||||||
|
* @param optionPath path to value in object notation (dot separated)
|
||||||
|
* @param optionValue new value of option
|
||||||
|
*/
|
||||||
|
setTemporary(optionPath: string, optionValue: any) {
|
||||||
|
const pathParts = optionPath.split('.');
|
||||||
|
|
||||||
|
let iterator = this.temporaryData;
|
||||||
|
let i;
|
||||||
|
for (i = 0; i < pathParts.length - 1; i++) {
|
||||||
|
iterator = iterator[pathParts[i]];
|
||||||
|
}
|
||||||
|
iterator[pathParts[i]] = optionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets option value to sessionStorage (for Persistence.CurrentSession)
|
||||||
|
* @param optionPath path to value in object notation (dot separated)
|
||||||
|
* @param optionValue new value of option
|
||||||
|
*/
|
||||||
|
setSession(optionPath: string, optionValue: any) {
|
||||||
|
const pathParts = optionPath.split('.');
|
||||||
|
|
||||||
|
let iterator = this.sessionData;
|
||||||
|
let i;
|
||||||
|
for (i = 0; i < pathParts.length - 1; i++) {
|
||||||
|
iterator = iterator[pathParts[i]];
|
||||||
|
}
|
||||||
|
iterator[pathParts[i]] = optionValue;
|
||||||
|
|
||||||
|
sessionStorage.setItem('uw-session-defaults', JSON.stringify(this.sessionData));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets default crop mode for extension, while taking persistence settings into account
|
||||||
|
*/
|
||||||
|
getDefaultOption(option: 'crop' | 'stretch' | 'alignment') {
|
||||||
|
const persistenceLevel = this.data.persistOption[option];
|
||||||
|
|
||||||
|
switch (persistenceLevel) {
|
||||||
|
case CropModePersistence.UntilPageReload:
|
||||||
|
return this.temporaryData.defaults[option];
|
||||||
|
case CropModePersistence.CurrentSession:
|
||||||
|
return this.sessionData.defaults[option];
|
||||||
|
case CropModePersistence.Disabled:
|
||||||
|
case CropModePersistence.Default:
|
||||||
|
case CropModePersistence.Forever:
|
||||||
|
default:
|
||||||
|
return this.data.defaults[option];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates options while accounting for persistence settings
|
||||||
|
* @param option
|
||||||
|
* @param value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async updatePersistentOption(option: 'crop' | 'stretch' | 'alignment', value) {
|
||||||
|
const persistenceLevel = this.data.persistOption[option];
|
||||||
|
switch (persistenceLevel) {
|
||||||
|
case CropModePersistence.Disabled:
|
||||||
|
return;
|
||||||
|
case CropModePersistence.UntilPageReload:
|
||||||
|
return this.temporaryData.defaults[option] = value;
|
||||||
|
case CropModePersistence.CurrentSession:
|
||||||
|
this.sessionData.defaults[option] = value;
|
||||||
|
return sessionStorage.setItem('uw-session-defaults', JSON.stringify(this.sessionData));
|
||||||
|
case CropModePersistence.Forever:
|
||||||
|
return this.set(`defaults.${option}`, value); // async, but we don't care in this case IIRC
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -8,6 +8,7 @@ import Settings from '../Settings';
|
|||||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||||
import CommsClient from '../comms/CommsClient';
|
import CommsClient from '../comms/CommsClient';
|
||||||
import EventBus from '../EventBus';
|
import EventBus from '../EventBus';
|
||||||
|
import { SiteSettings } from '../settings/SiteSettings';
|
||||||
|
|
||||||
if (process.env.CHANNEL !== 'stable'){
|
if (process.env.CHANNEL !== 'stable'){
|
||||||
console.info("Loading PageInfo");
|
console.info("Loading PageInfo");
|
||||||
@ -40,6 +41,7 @@ class PageInfo {
|
|||||||
readOnly: boolean = false;
|
readOnly: boolean = false;
|
||||||
hasVideos: boolean = false;
|
hasVideos: boolean = false;
|
||||||
siteDisabled: boolean = false;
|
siteDisabled: boolean = false;
|
||||||
|
isFullscreen: boolean = false;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region timers and timeouts
|
//#region timers and timeouts
|
||||||
@ -51,6 +53,7 @@ class PageInfo {
|
|||||||
//#region helper objects
|
//#region helper objects
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
|
siteSettings: SiteSettings;
|
||||||
comms: CommsClient;
|
comms: CommsClient;
|
||||||
eventBus: EventBus;
|
eventBus: EventBus;
|
||||||
videos: {videoData: VideoData, element: HTMLVideoElement}[] = [];
|
videos: {videoData: VideoData, element: HTMLVideoElement}[] = [];
|
||||||
@ -71,17 +74,21 @@ class PageInfo {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
||||||
|
this.siteSettings = new SiteSettings(settings, window.location.hostname);
|
||||||
|
|
||||||
this.lastUrl = window.location.href;
|
this.lastUrl = window.location.href;
|
||||||
this.extensionMode = extensionMode;
|
this.extensionMode = extensionMode;
|
||||||
this.readOnly = readOnly;
|
this.readOnly = readOnly;
|
||||||
|
|
||||||
|
this.isFullscreen = !!document.fullscreenElement;
|
||||||
|
|
||||||
if (eventBus){
|
if (eventBus){
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// request inject css immediately
|
// request inject css immediately
|
||||||
const playerStyleString = this.settings.active.sites[window.location.hostname].css.replace('\\n', '');
|
const playerStyleString = this.siteSettings.data.currentDOMConfig?.customCss?.replace('\\n', '');
|
||||||
this.eventBus.send('inject-css', {cssString: playerStyleString});
|
this.eventBus.send('inject-css', {cssString: playerStyleString});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing. It's ok if there's no special settings for the player element or crop persistence
|
// do nothing. It's ok if there's no special settings for the player element or crop persistence
|
||||||
@ -91,6 +98,8 @@ class PageInfo {
|
|||||||
|
|
||||||
this.rescan(RescanReason.PERIODIC);
|
this.rescan(RescanReason.PERIODIC);
|
||||||
this.scheduleUrlCheck();
|
this.scheduleUrlCheck();
|
||||||
|
|
||||||
|
document.addEventListener('fullscreenchange', this.fullscreenHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -108,13 +117,40 @@ class PageInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const playerStyleString = this.settings.active.sites[window.location.hostname].css;
|
const playerStyleString = this.siteSettings.data.currentDOMConfig?.customCss?.replace('\\n', '');
|
||||||
this.eventBus.send('eject-css', {cssString: playerStyleString});
|
this.eventBus.send('eject-css', {cssString: playerStyleString});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing. It's ok if there's no special settings for the player element
|
// do nothing. It's ok if there's no special settings for the player element
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for fullscreenchanged event.
|
||||||
|
*/
|
||||||
|
fullscreenHandler() {
|
||||||
|
this.isFullscreen = !!document.fullscreenElement;
|
||||||
|
|
||||||
|
if (this.isFullscreen) {
|
||||||
|
this.enterFullscreen();
|
||||||
|
} else {
|
||||||
|
this.exitFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs when browser enters full screen.
|
||||||
|
*/
|
||||||
|
enterFullscreen() {
|
||||||
|
this.eventBus.send('page-fs-enter', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs when browser exits full screen
|
||||||
|
*/
|
||||||
|
exitFullscreen() {
|
||||||
|
this.eventBus.send('page-fs-exit', {});
|
||||||
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
for(let video of this.videos) {
|
for(let video of this.videos) {
|
||||||
video.videoData.destroy();
|
video.videoData.destroy();
|
||||||
@ -133,9 +169,9 @@ class PageInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getVideos(host) {
|
getVideos(host) {
|
||||||
if (this.settings.active.sites[host]?.DOM?.video?.manual
|
const videoQs = this.siteSettings.getCustomDOMQuerySelector('video');
|
||||||
&& this.settings.active.sites[host]?.DOM?.video?.querySelectors){
|
if (videoQs){
|
||||||
const videos = document.querySelectorAll(this.settings.active.sites[host].DOM.video.querySelectors) as NodeListOf<HTMLVideoElement>;
|
const videos = document.querySelectorAll(videoQs) as NodeListOf<HTMLVideoElement>;
|
||||||
|
|
||||||
if (videos.length) {
|
if (videos.length) {
|
||||||
return videos;
|
return videos;
|
||||||
@ -214,7 +250,7 @@ class PageInfo {
|
|||||||
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] found new video candidate:", videoElement, "NOTE:: Video initialization starts here:\n--------------------------------\n")
|
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] found new video candidate:", videoElement, "NOTE:: Video initialization starts here:\n--------------------------------\n")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newVideo = new VideoData(videoElement, this.settings, this);
|
const newVideo = new VideoData(videoElement, this.siteSettings, this);
|
||||||
this.videos.push({videoData: newVideo, element: videoElement});
|
this.videos.push({videoData: newVideo, element: videoElement});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
|
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
|
||||||
@ -327,51 +363,21 @@ class PageInfo {
|
|||||||
this.scheduleUrlCheck();
|
this.scheduleUrlCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
setArPersistence(persistenceMode) {
|
/**
|
||||||
// name of this function is mildly misleading — we don't really _set_ ar persistence. (Ar persistence
|
* Updates current crop configuration.
|
||||||
// mode is set and saved via popup or keyboard shortcuts, if user defined them) We just save the current
|
*
|
||||||
// aspect ratio whenever aspect ratio persistence mode changes.
|
* If crop persistence is set to; then
|
||||||
if (persistenceMode === CropModePersistence.CurrentSession) {
|
* disabled do nothing
|
||||||
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(this.currentCrop));
|
* until tab reload set this.defaultCrop
|
||||||
} else if (persistenceMode === CropModePersistence.Forever) {
|
* current session set current AR to session storage
|
||||||
if (this.settings.active.sites[window.location.hostname]) {
|
* forever save settings
|
||||||
// | key may be missing, so we do this
|
* @param ar
|
||||||
this.settings.active.sites[window.location.hostname]['defaultAr'] = this.currentCrop;
|
* @returns
|
||||||
} else {
|
*/
|
||||||
this.settings.active.sites[window.location.hostname] = this.settings.getDefaultOption();
|
|
||||||
this.settings.active.sites[window.location.hostname]['defaultAr'] = this.currentCrop;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCurrentCrop(ar) {
|
updateCurrentCrop(ar) {
|
||||||
this.currentCrop = ar;
|
this.currentCrop = ar;
|
||||||
// This means crop persistance is disabled. If crop persistance is enabled, then settings for current
|
|
||||||
// site MUST exist (crop persistence mode is disabled by default)
|
|
||||||
|
|
||||||
const cropModePersistence = this.settings.getDefaultCropPersistenceMode(window.location.hostname);
|
this.siteSettings.updatePersistentOption('crop', ar);
|
||||||
|
|
||||||
if (cropModePersistence === CropModePersistence.Disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.defaultCrop = ar;
|
|
||||||
|
|
||||||
if (cropModePersistence === CropModePersistence.CurrentSession) {
|
|
||||||
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(ar));
|
|
||||||
} else if (cropModePersistence === CropModePersistence.Forever) {
|
|
||||||
if (this.settings.active.sites[window.location.hostname]) {
|
|
||||||
// | key may be missing, so we do this
|
|
||||||
this.settings.active.sites[window.location.hostname]['defaultAr'] = ar;
|
|
||||||
} else {
|
|
||||||
this.settings.active.sites[window.location.hostname] = this.settings.getDefaultOption();
|
|
||||||
this.settings.active.sites[window.location.hostname]['defaultAr'] = ar;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +57,8 @@ class PlayerData {
|
|||||||
invalid: boolean = false;
|
invalid: boolean = false;
|
||||||
private periodicallyRefreshPlayerElement: boolean = false;
|
private periodicallyRefreshPlayerElement: boolean = false;
|
||||||
halted: boolean = true;
|
halted: boolean = true;
|
||||||
|
isFullscreen: boolean = !!document.fullscreenElement;
|
||||||
|
isTheaterMode: boolean = false; // note: fullscreen mode will count as theaterMode if player was in theater mode before fs switch. This is desired, so far.
|
||||||
|
|
||||||
//#region misc stuff
|
//#region misc stuff
|
||||||
extensionMode: any;
|
extensionMode: any;
|
||||||
@ -79,7 +81,7 @@ class PlayerData {
|
|||||||
}],
|
}],
|
||||||
'update-player': [{
|
'update-player': [{
|
||||||
function: () => this.getPlayer()
|
function: () => this.getPlayer()
|
||||||
}]
|
}],
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
@ -88,7 +90,7 @@ class PlayerData {
|
|||||||
*/
|
*/
|
||||||
get aspectRatio() {
|
get aspectRatio() {
|
||||||
try {
|
try {
|
||||||
if (this.dimensions?.fullscreen && !this.settings.getSettingsForSite()?.usePlayerArInFullscreen) {
|
if (this.isFullscreen && !this.settings.getSettingsForSite()?.usePlayerArInFullscreen) {
|
||||||
return window.innerWidth / window.innerHeight;
|
return window.innerWidth / window.innerHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,40 +153,47 @@ class PlayerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether we're in fullscreen mode or not.
|
* Detects whether player element is in theater mode or not.
|
||||||
|
* If theater mode changed, emits event.
|
||||||
|
* @returns whether player is in theater mode
|
||||||
*/
|
*/
|
||||||
static isFullScreen(){
|
private detectTheaterMode() {
|
||||||
const ihdiff = Math.abs(window.screen.height - window.innerHeight);
|
const oldTheaterMode = this.isTheaterMode;
|
||||||
const iwdiff = Math.abs(window.screen.width - window.innerWidth);
|
const newTheaterMode = this.equalish(window.innerWidth, this.element.offsetWidth, 32);
|
||||||
|
|
||||||
// Chrome on linux on X on mixed PPI displays may return ever so slightly different values
|
this.isTheaterMode = newTheaterMode;
|
||||||
// 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
|
// theater mode changed
|
||||||
// window elements visible in not-fullscreen are usually double digit px tall
|
if (oldTheaterMode !== newTheaterMode) {
|
||||||
return ( ihdiff < 5 && iwdiff < 5 );
|
if (newTheaterMode) {
|
||||||
|
this.eventBus.send('player-theater-enter', {});
|
||||||
|
} else {
|
||||||
|
this.eventBus.send('player-theater-exit', {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTheaterMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
trackDimensionChanges() {
|
trackDimensionChanges() {
|
||||||
|
|
||||||
// get player dimensions _once_
|
// get player dimensions _once_
|
||||||
let currentPlayerDimensions;
|
let currentPlayerDimensions;
|
||||||
const isFullScreen = PlayerData.isFullScreen();
|
|
||||||
|
|
||||||
if (isFullScreen) {
|
if (this.isFullscreen) {
|
||||||
currentPlayerDimensions = {
|
currentPlayerDimensions = {
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
fullscreen: true
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
currentPlayerDimensions = {
|
currentPlayerDimensions = {
|
||||||
width: this.element.offsetWidth,
|
width: this.element.offsetWidth,
|
||||||
height: this.element.offsetHeight,
|
height: this.element.offsetHeight
|
||||||
fullscreen: false,
|
};
|
||||||
}
|
|
||||||
|
this.detectTheaterMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if dimensions of the player box are the same as the last known
|
// if dimensions of the player box are the same as the last known
|
||||||
@ -216,7 +225,7 @@ class PlayerData {
|
|||||||
private handleSizeConstraints(currentPlayerDimensions: PlayerDimensions) {
|
private handleSizeConstraints(currentPlayerDimensions: PlayerDimensions) {
|
||||||
|
|
||||||
// never disable ultrawidify in full screen
|
// never disable ultrawidify in full screen
|
||||||
if (currentPlayerDimensions.fullscreen) {
|
if (this.isFullscreen) {
|
||||||
this.enable();
|
this.enable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,15 @@ import PageInfo from './PageInfo';
|
|||||||
import { sleep } from '../../../common/js/utils';
|
import { sleep } from '../../../common/js/utils';
|
||||||
import { hasDrm } from '../ar-detect/DrmDetecor';
|
import { hasDrm } from '../ar-detect/DrmDetecor';
|
||||||
import EventBus from '../EventBus';
|
import EventBus from '../EventBus';
|
||||||
|
import { SiteSettings } from '../settings/SiteSettings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VideoData — handles CSS for the video element.
|
||||||
|
*
|
||||||
|
* To quickly disable or revert all modifications extension has made to the
|
||||||
|
* video element, you can call disable() function. Calling disable() also
|
||||||
|
* toggles autodetection off.
|
||||||
|
*/
|
||||||
class VideoData {
|
class VideoData {
|
||||||
private baseCssName: string = 'uw-ultrawidify-base-wide-screen';
|
private baseCssName: string = 'uw-ultrawidify-base-wide-screen';
|
||||||
|
|
||||||
@ -46,7 +54,7 @@ class VideoData {
|
|||||||
|
|
||||||
//#region helper objects
|
//#region helper objects
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
settings: Settings;
|
siteSettings: SiteSettings;
|
||||||
pageInfo: PageInfo;
|
pageInfo: PageInfo;
|
||||||
player: PlayerData;
|
player: PlayerData;
|
||||||
resizer: Resizer;
|
resizer: Resizer;
|
||||||
@ -64,12 +72,12 @@ class VideoData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(video, settings, pageInfo){
|
constructor(video, siteSettings: SiteSettings, pageInfo){
|
||||||
this.logger = pageInfo.logger;
|
this.logger = pageInfo.logger;
|
||||||
this.arSetupComplete = false;
|
this.arSetupComplete = false;
|
||||||
this.video = video;
|
this.video = video;
|
||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
this.settings = settings;
|
this.siteSettings = siteSettings;
|
||||||
this.pageInfo = pageInfo;
|
this.pageInfo = pageInfo;
|
||||||
this.extensionMode = pageInfo.extensionMode;
|
this.extensionMode = pageInfo.extensionMode;
|
||||||
this.videoStatusOk = false;
|
this.videoStatusOk = false;
|
||||||
@ -96,7 +104,7 @@ class VideoData {
|
|||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setupStageOne();
|
this.setupEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onVideoLoaded() {
|
async onVideoLoaded() {
|
||||||
@ -181,10 +189,10 @@ class VideoData {
|
|||||||
|
|
||||||
//#region lifecycle-ish
|
//#region lifecycle-ish
|
||||||
/**
|
/**
|
||||||
* Injects base CSS and sets up handlers for <video> tag events
|
* Sets up event listeners for this video
|
||||||
*/
|
*/
|
||||||
async setupStageOne() {
|
async setupEventListeners() {
|
||||||
this.logger.log('info', 'init', '%c[VideoData::setupStageOne] ——————————— Starting setup stage one! ———————————', 'color: #0f9');
|
this.logger.log('info', 'init', '%c[VideoData::setupEventListeners] ——————————— Starting event listener setup! ———————————', 'color: #0f9');
|
||||||
|
|
||||||
// this is in case extension loads before the video
|
// this is in case extension loads before the video
|
||||||
this.video.addEventListener('loadeddata', this.onLoadedData.bind(this));
|
this.video.addEventListener('loadeddata', this.onLoadedData.bind(this));
|
||||||
@ -193,7 +201,7 @@ class VideoData {
|
|||||||
// this one is in case extension loads after the video is loaded
|
// this one is in case extension loads after the video is loaded
|
||||||
this.video.addEventListener('timeupdate', this.onTimeUpdate.bind(this));
|
this.video.addEventListener('timeupdate', this.onTimeUpdate.bind(this));
|
||||||
|
|
||||||
this.logger.log('info', 'init', '%c[VideoData::setupStageOne] ——————————— Setup stage one complete! ———————————', 'color: #0f9');
|
this.logger.log('info', 'init', '%c[VideoData::setupEventListeners] ——————————— Event listeners setup complete! ———————————', 'color: #0f9');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,21 +226,11 @@ class VideoData {
|
|||||||
// after we receive a "please crop" or "please stretch".
|
// after we receive a "please crop" or "please stretch".
|
||||||
|
|
||||||
// Time to apply any crop from address of crop mode persistence
|
// Time to apply any crop from address of crop mode persistence
|
||||||
const defaultCrop = this.settings.getDefaultCrop();
|
const defaultCrop = this.siteSettings.getDefaultOption('crop');
|
||||||
const defaultStretch = this.settings.getDefaultStretchMode();
|
const defaultStretch = this.siteSettings.getDefaultOption('stretch');
|
||||||
|
|
||||||
// Crop mode persistence is intended to avoid resetting video aspect ratio to site or extension default
|
this.resizer.setAr(defaultCrop);
|
||||||
// when going from one video to another. As such, crop persistence takes priority over site defaults.
|
this.resizer.setStretchMode(defaultStretch);
|
||||||
// This option should only trigger if we have modified the aspect ratio manually.
|
|
||||||
if (
|
|
||||||
this.settings.getDefaultCropPersistenceMode(window.location.hostname) !== CropModePersistence.Disabled
|
|
||||||
&& this.pageInfo.defaultCrop
|
|
||||||
) {
|
|
||||||
this.resizer.setAr(this.pageInfo.defaultCrop);
|
|
||||||
} else {
|
|
||||||
this.resizer.setAr(defaultCrop);
|
|
||||||
this.resizer.setStretchMode(defaultStretch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user