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) {
|
||||
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)});
|
||||
}
|
||||
|
||||
storageChangeListener(changes, area) {
|
||||
private storageChangeListener(changes, area) {
|
||||
if (!changes.uwSettings) {
|
||||
return;
|
||||
}
|
||||
@ -84,7 +84,7 @@ class Settings {
|
||||
return Settings.getExtensionVersion();
|
||||
}
|
||||
|
||||
compareExtensionVersions(a, b) {
|
||||
private compareExtensionVersions(a, b) {
|
||||
let aa = a.split('.');
|
||||
let bb = b.split('.');
|
||||
|
||||
@ -135,7 +135,7 @@ class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
getPrereleaseVersionHierarchy(version) {
|
||||
private getPrereleaseVersionHierarchy(version) {
|
||||
if (version.startsWith('dev')) {
|
||||
return 0;
|
||||
}
|
||||
@ -148,15 +148,15 @@ class Settings {
|
||||
return 3;
|
||||
}
|
||||
|
||||
sortConfPatches(patchesIn) {
|
||||
private sortConfPatches(patchesIn) {
|
||||
return patchesIn.sort( (a, b) => this.compareExtensionVersions(a.forVersion, b.forVersion));
|
||||
}
|
||||
|
||||
findFirstNecessaryPatch(version, extconfPatches) {
|
||||
private findFirstNecessaryPatch(version, extconfPatches) {
|
||||
const sorted = this.sortConfPatches(extconfPatches);
|
||||
return sorted.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
|
||||
}
|
||||
applySettingsPatches(oldVersion, patches) {
|
||||
private applySettingsPatches(oldVersion, patches) {
|
||||
let index = this.findFirstNecessaryPatch(oldVersion, patches);
|
||||
if (index === -1) {
|
||||
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 CommsClient from '../comms/CommsClient';
|
||||
import EventBus from '../EventBus';
|
||||
import { SiteSettings } from '../settings/SiteSettings';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading PageInfo");
|
||||
@ -40,6 +41,7 @@ class PageInfo {
|
||||
readOnly: boolean = false;
|
||||
hasVideos: boolean = false;
|
||||
siteDisabled: boolean = false;
|
||||
isFullscreen: boolean = false;
|
||||
//#endregion
|
||||
|
||||
//#region timers and timeouts
|
||||
@ -51,6 +53,7 @@ class PageInfo {
|
||||
//#region helper objects
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
siteSettings: SiteSettings;
|
||||
comms: CommsClient;
|
||||
eventBus: EventBus;
|
||||
videos: {videoData: VideoData, element: HTMLVideoElement}[] = [];
|
||||
@ -71,17 +74,21 @@ class PageInfo {
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
|
||||
this.siteSettings = new SiteSettings(settings, window.location.hostname);
|
||||
|
||||
this.lastUrl = window.location.href;
|
||||
this.extensionMode = extensionMode;
|
||||
this.readOnly = readOnly;
|
||||
|
||||
this.isFullscreen = !!document.fullscreenElement;
|
||||
|
||||
if (eventBus){
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
try {
|
||||
// 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});
|
||||
} catch (e) {
|
||||
// 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.scheduleUrlCheck();
|
||||
|
||||
document.addEventListener('fullscreenchange', this.fullscreenHandler);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@ -108,13 +117,40 @@ class PageInfo {
|
||||
}
|
||||
|
||||
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});
|
||||
} catch (e) {
|
||||
// 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() {
|
||||
for(let video of this.videos) {
|
||||
video.videoData.destroy();
|
||||
@ -133,9 +169,9 @@ class PageInfo {
|
||||
}
|
||||
|
||||
getVideos(host) {
|
||||
if (this.settings.active.sites[host]?.DOM?.video?.manual
|
||||
&& this.settings.active.sites[host]?.DOM?.video?.querySelectors){
|
||||
const videos = document.querySelectorAll(this.settings.active.sites[host].DOM.video.querySelectors) as NodeListOf<HTMLVideoElement>;
|
||||
const videoQs = this.siteSettings.getCustomDOMQuerySelector('video');
|
||||
if (videoQs){
|
||||
const videos = document.querySelectorAll(videoQs) as NodeListOf<HTMLVideoElement>;
|
||||
|
||||
if (videos.length) {
|
||||
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")
|
||||
|
||||
try {
|
||||
const newVideo = new VideoData(videoElement, this.settings, this);
|
||||
const newVideo = new VideoData(videoElement, this.siteSettings, this);
|
||||
this.videos.push({videoData: newVideo, element: videoElement});
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
|
||||
@ -327,51 +363,21 @@ class PageInfo {
|
||||
this.scheduleUrlCheck();
|
||||
}
|
||||
|
||||
setArPersistence(persistenceMode) {
|
||||
// name of this function is mildly misleading — we don't really _set_ ar persistence. (Ar persistence
|
||||
// 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 (persistenceMode === CropModePersistence.CurrentSession) {
|
||||
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(this.currentCrop));
|
||||
} else if (persistenceMode === 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'] = this.currentCrop;
|
||||
} else {
|
||||
this.settings.active.sites[window.location.hostname] = this.settings.getDefaultOption();
|
||||
this.settings.active.sites[window.location.hostname]['defaultAr'] = this.currentCrop;
|
||||
}
|
||||
|
||||
this.settings.saveWithoutReload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates current crop configuration.
|
||||
*
|
||||
* If crop persistence is set to; then
|
||||
* disabled do nothing
|
||||
* until tab reload set this.defaultCrop
|
||||
* current session set current AR to session storage
|
||||
* forever save settings
|
||||
* @param ar
|
||||
* @returns
|
||||
*/
|
||||
updateCurrentCrop(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);
|
||||
|
||||
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();
|
||||
}
|
||||
this.siteSettings.updatePersistentOption('crop', ar);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,8 @@ class PlayerData {
|
||||
invalid: boolean = false;
|
||||
private periodicallyRefreshPlayerElement: boolean = false;
|
||||
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
|
||||
extensionMode: any;
|
||||
@ -79,7 +81,7 @@ class PlayerData {
|
||||
}],
|
||||
'update-player': [{
|
||||
function: () => this.getPlayer()
|
||||
}]
|
||||
}],
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@ -88,7 +90,7 @@ class PlayerData {
|
||||
*/
|
||||
get aspectRatio() {
|
||||
try {
|
||||
if (this.dimensions?.fullscreen && !this.settings.getSettingsForSite()?.usePlayerArInFullscreen) {
|
||||
if (this.isFullscreen && !this.settings.getSettingsForSite()?.usePlayerArInFullscreen) {
|
||||
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(){
|
||||
const ihdiff = Math.abs(window.screen.height - window.innerHeight);
|
||||
const iwdiff = Math.abs(window.screen.width - window.innerWidth);
|
||||
private detectTheaterMode() {
|
||||
const oldTheaterMode = this.isTheaterMode;
|
||||
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
|
||||
// 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 );
|
||||
this.isTheaterMode = newTheaterMode;
|
||||
|
||||
// theater mode changed
|
||||
if (oldTheaterMode !== newTheaterMode) {
|
||||
if (newTheaterMode) {
|
||||
this.eventBus.send('player-theater-enter', {});
|
||||
} else {
|
||||
this.eventBus.send('player-theater-exit', {});
|
||||
}
|
||||
}
|
||||
|
||||
return newTheaterMode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
trackDimensionChanges() {
|
||||
|
||||
// get player dimensions _once_
|
||||
let currentPlayerDimensions;
|
||||
const isFullScreen = PlayerData.isFullScreen();
|
||||
|
||||
if (isFullScreen) {
|
||||
if (this.isFullscreen) {
|
||||
currentPlayerDimensions = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
fullscreen: true
|
||||
};
|
||||
} else {
|
||||
currentPlayerDimensions = {
|
||||
width: this.element.offsetWidth,
|
||||
height: this.element.offsetHeight,
|
||||
fullscreen: false,
|
||||
}
|
||||
height: this.element.offsetHeight
|
||||
};
|
||||
|
||||
this.detectTheaterMode();
|
||||
}
|
||||
|
||||
// if dimensions of the player box are the same as the last known
|
||||
@ -216,7 +225,7 @@ class PlayerData {
|
||||
private handleSizeConstraints(currentPlayerDimensions: PlayerDimensions) {
|
||||
|
||||
// never disable ultrawidify in full screen
|
||||
if (currentPlayerDimensions.fullscreen) {
|
||||
if (this.isFullscreen) {
|
||||
this.enable();
|
||||
return;
|
||||
}
|
||||
|
@ -12,7 +12,15 @@ import PageInfo from './PageInfo';
|
||||
import { sleep } from '../../../common/js/utils';
|
||||
import { hasDrm } from '../ar-detect/DrmDetecor';
|
||||
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 {
|
||||
private baseCssName: string = 'uw-ultrawidify-base-wide-screen';
|
||||
|
||||
@ -46,7 +54,7 @@ class VideoData {
|
||||
|
||||
//#region helper objects
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
siteSettings: SiteSettings;
|
||||
pageInfo: PageInfo;
|
||||
player: PlayerData;
|
||||
resizer: Resizer;
|
||||
@ -64,12 +72,12 @@ class VideoData {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(video, settings, pageInfo){
|
||||
constructor(video, siteSettings: SiteSettings, pageInfo){
|
||||
this.logger = pageInfo.logger;
|
||||
this.arSetupComplete = false;
|
||||
this.video = video;
|
||||
this.destroyed = false;
|
||||
this.settings = settings;
|
||||
this.siteSettings = siteSettings;
|
||||
this.pageInfo = pageInfo;
|
||||
this.extensionMode = pageInfo.extensionMode;
|
||||
this.videoStatusOk = false;
|
||||
@ -96,7 +104,7 @@ class VideoData {
|
||||
}});
|
||||
}
|
||||
|
||||
this.setupStageOne();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
async onVideoLoaded() {
|
||||
@ -181,10 +189,10 @@ class VideoData {
|
||||
|
||||
//#region lifecycle-ish
|
||||
/**
|
||||
* Injects base CSS and sets up handlers for <video> tag events
|
||||
* Sets up event listeners for this video
|
||||
*/
|
||||
async setupStageOne() {
|
||||
this.logger.log('info', 'init', '%c[VideoData::setupStageOne] ——————————— Starting setup stage one! ———————————', 'color: #0f9');
|
||||
async setupEventListeners() {
|
||||
this.logger.log('info', 'init', '%c[VideoData::setupEventListeners] ——————————— Starting event listener setup! ———————————', 'color: #0f9');
|
||||
|
||||
// this is in case extension loads before the video
|
||||
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.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".
|
||||
|
||||
// Time to apply any crop from address of crop mode persistence
|
||||
const defaultCrop = this.settings.getDefaultCrop();
|
||||
const defaultStretch = this.settings.getDefaultStretchMode();
|
||||
const defaultCrop = this.siteSettings.getDefaultOption('crop');
|
||||
const defaultStretch = this.siteSettings.getDefaultOption('stretch');
|
||||
|
||||
// Crop mode persistence is intended to avoid resetting video aspect ratio to site or extension default
|
||||
// when going from one video to another. As such, crop persistence takes priority over site defaults.
|
||||
// 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);
|
||||
}
|
||||
this.resizer.setAr(defaultCrop);
|
||||
this.resizer.setStretchMode(defaultStretch);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user