import Debug from '../../conf/Debug'; import VideoData from './VideoData'; import RescanReason from './enums/RescanReason.enum'; import AspectRatioType from '../../../common/enums/AspectRatioType.enum'; import CropModePersistence from '../../../common/enums/CropModePersistence.enum'; import Logger from '../Logger'; import Settings from '../Settings'; import ExtensionMode from '../../../common/enums/ExtensionMode.enum'; import CommsClient from '../comms/CommsClient'; import EventBus from '../EventBus'; import { SiteSettings } from '../settings/SiteSettings'; import IframeManager from './IframeManager'; if (process.env.CHANNEL !== 'stable'){ console.info("Loading PageInfo"); } /** * * The classes kinda go like this: * * PageInfo — one per page/frame. * | * +——— VideoData — child of PageInfo. There may be more than one of those * | +—— PlayerData — VideoData has exactly ONE (1) PlayerData * | +—— AspectRatioDetector — VideoData has 0-1 AARD things * | +—— Resizer * | +—— Scaler * | +—— Stretcher * | +—— Zoom * +——— VideoData * | +—— PlayerData * | : * : * * There is as many VideoData objects as there are videos. */ class PageInfo { //#region flags readOnly: boolean = false; hasVideos: boolean = false; siteDisabled: boolean = false; isFullscreen: boolean = false; //#endregion //#region timers and timeouts rescanTimer: any; urlCheckTimer: any; announceZoomTimeout: any; //#endregion //#region helper objects logger: Logger; settings: Settings; siteSettings: SiteSettings; comms: CommsClient; eventBus: EventBus; videos: {videoData: VideoData, element: HTMLVideoElement}[] = []; iframeManager: IframeManager; //#endregion //#region misc stuff lastUrl: string; defaultCrop: any; currentCrop: any; keyboardHandlerInitQueue: any[] = []; currentZoomScale: number = 1; keyboardHandler: any; fsStatus = {fullscreen: true}; // fsStatus needs to be passed to VideoData, so fullScreen property is shared between videoData instances //#endregion fsEventListener = { that: this, handleEvent: function(event: Event) { this.that.fullscreenHandler(); } }; constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: Logger, readOnly = false){ this.logger = logger; this.settings = settings; this.siteSettings = siteSettings; this.lastUrl = window.location.href; this.readOnly = readOnly; this.isFullscreen = !!document.fullscreenElement; this.iframeManager = new IframeManager({eventBus}); if (eventBus){ this.eventBus = eventBus; } try { // request inject css immediately 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 } this.currentCrop = this.defaultCrop; this.rescan(RescanReason.PERIODIC); this.scheduleUrlCheck(); document.addEventListener('fullscreenchange', this.fsEventListener); } destroy() { this.logger.log('info', ['debug', 'init'], "[PageInfo::destroy] destroying all videos!") if(this.rescanTimer){ clearTimeout(this.rescanTimer); } for (let video of this.videos) { try { this.eventBus.send('noVideo', undefined); video.videoData.destroy(); } catch (e) { this.logger.log('error', ['debug', 'init'], '[PageInfo::destroy] unable to destroy video! Error:', e); } } try { 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 } } /** * Runs when browser enters full screen. */ enterFullscreen() { this.fsStatus.fullscreen = true; this.eventBus.send('page-fs-enter', {}); } /** * Runs when browser exits full screen */ exitFullscreen() { this.fsStatus.fullscreen = false; this.eventBus.send('page-fs-exit', {}); } /** * Handler for fullscreenchanged event. */ fullscreenHandler() { this.isFullscreen = !!document.fullscreenElement; if (this.isFullscreen) { this.enterFullscreen(); } else { this.exitFullscreen(); } } reset() { for(let video of this.videos) { video.videoData.destroy(); video.videoData = null; } this.videos = []; this.rescan(RescanReason.MANUAL); } initMouseActionHandler(videoData) { if (this.keyboardHandler) { this.keyboardHandler.registerHandleMouse(videoData); } else { this.keyboardHandlerInitQueue.push(videoData); } } getVideos(host) { const videoQs = this.siteSettings.getCustomDOMQuerySelector('video'); if (videoQs){ const videos = document.querySelectorAll(videoQs) as NodeListOf; if (videos.length) { return videos; } } return document.getElementsByTagName('video'); } hasVideo() { return this.readOnly ? this.hasVideos : this.videos.length; } /** * Re-scans the page for videos. Removes any videos that no longer exist from our list * of videos. Destroys all videoData objects for all the videos that don't have their * own