2018-12-31 01:03:07 +01:00
import Debug from '../../conf/Debug' ;
import PlayerData from './PlayerData' ;
import Resizer from '../video-transform/Resizer' ;
import ArDetector from '../ar-detect/ArDetector' ;
2021-02-08 23:04:54 +01:00
import AspectRatioType from '../../../common/enums/AspectRatioType.enum' ;
2021-11-25 00:31:38 +01:00
import CropModePersistence from '../../../common/enums/CropModePersistence.enum' ;
2021-02-08 22:45:51 +01:00
import * as _ from 'lodash' ;
2021-02-08 20:43:56 +01:00
import BrowserDetect from '../../conf/BrowserDetect' ;
2021-02-08 22:45:51 +01:00
import Logger from '../Logger' ;
import Settings from '../Settings' ;
import PageInfo from './PageInfo' ;
import { sleep } from '../../../common/js/utils' ;
2021-02-18 01:08:12 +01:00
import { hasDrm } from '../ar-detect/DrmDetecor' ;
2021-10-26 22:19:41 +02:00
import EventBus from '../EventBus' ;
2023-01-07 03:06:37 +01:00
import { SiteSettings } from '../settings/SiteSettings' ;
2023-01-07 18:57:47 +01:00
import { Ar } from '../../../common/interfaces/ArInterface' ;
2023-01-07 03:06:37 +01:00
/ * *
* 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 .
* /
2018-05-09 00:03:22 +02:00
class VideoData {
2021-10-26 00:30:38 +02:00
private baseCssName : string = 'uw-ultrawidify-base-wide-screen' ;
2021-02-08 22:45:51 +01:00
//#region flags
arSetupComplete : boolean = false ;
2021-10-26 00:30:38 +02:00
enabled : boolean ;
2021-02-08 22:45:51 +01:00
destroyed : boolean = false ;
invalid : boolean = false ;
videoStatusOk : boolean = false ;
videoLoaded : boolean = false ;
videoDimensionsLoaded : boolean = false ;
paused : boolean = false ;
//#endregion
//#region misc stuff
vdid : string ;
video : any ;
2021-03-29 23:30:54 +02:00
observer : ResizeObserver ;
2021-03-30 01:10:37 +02:00
mutationObserver : MutationObserver ;
mutationObserverConf : MutationObserverInit = {
attributes : true ,
attributeFilter : [ 'class' , 'style' ] ,
attributeOldValue : true ,
} ;
2021-02-08 22:45:51 +01:00
userCssClassName : string ;
validationId : number ;
dimensions : any ;
2021-08-25 22:30:06 +02:00
hasDrm : boolean ;
2021-02-08 22:45:51 +01:00
//#endregion
//#region helper objects
logger : Logger ;
2023-03-29 22:07:50 +02:00
settings : Settings ; // AARD needs it
2023-01-07 03:06:37 +01:00
siteSettings : SiteSettings ;
2021-02-08 22:45:51 +01:00
pageInfo : PageInfo ;
player : PlayerData ;
resizer : Resizer ;
arDetector : ArDetector ;
2021-10-26 22:19:41 +02:00
eventBus : EventBus ;
2021-02-08 22:45:51 +01:00
//#endregion
2020-11-06 00:03:11 +01:00
2021-04-10 04:08:09 +02:00
get aspectRatio() {
2021-04-12 19:01:28 +02:00
try {
return this . video . videoWidth / this . video . videoHeight ;
} catch ( e ) {
console . error ( 'cannot determine stream aspect ratio!' , e ) ;
return 1 ;
}
2021-04-10 04:08:09 +02:00
}
2021-10-25 23:11:34 +02:00
2023-03-29 22:07:50 +02:00
/ * *
* Creates new VideoData object
* @param video
* @param settings NEEDED FOR AARD
* @param siteSettings
* @param pageInfo
* /
constructor ( video , settings : Settings , siteSettings : SiteSettings , pageInfo : PageInfo ) {
2019-09-03 23:01:23 +02:00
this . logger = pageInfo . logger ;
2018-05-16 23:26:47 +02:00
this . arSetupComplete = false ;
2018-05-09 00:03:22 +02:00
this . video = video ;
2018-05-21 22:43:56 +02:00
this . destroyed = false ;
2023-03-29 22:07:50 +02:00
this . settings = settings ;
2023-01-07 03:06:37 +01:00
this . siteSettings = siteSettings ;
2018-11-02 02:52:01 +01:00
this . pageInfo = pageInfo ;
2020-06-01 23:54:10 +02:00
this . videoStatusOk = false ;
2018-11-02 21:19:34 +01:00
2019-08-23 02:25:48 +02:00
this . userCssClassName = ` uw-fuck-you-and-do-what-i-tell-you_ ${ this . vdid } ` ;
2018-11-02 21:19:34 +01:00
2020-06-01 23:54:10 +02:00
this . videoLoaded = false ;
2020-06-04 21:51:22 +02:00
this . videoDimensionsLoaded = true ;
2020-06-01 23:54:10 +02:00
2021-02-08 20:44:34 +01:00
this . validationId = null ;
2020-06-01 23:54:10 +02:00
this . dimensions = {
width : this.video.offsetWidth ,
height : this.video.offsetHeight ,
} ;
2021-10-26 22:19:41 +02:00
this . eventBus = new EventBus ( ) ;
2022-05-06 00:23:01 +02:00
if ( pageInfo . eventBus ) {
this . eventBus . setUpstreamBus ( pageInfo . eventBus ) ;
2022-07-28 00:45:27 +02:00
this . eventBus . subscribe ( 'get-drm-status' , { function : ( ) = > {
this . hasDrm = hasDrm ( this . video ) ;
this . eventBus . send ( 'uw-config-broadcast' , { type : 'drm-status' , hasDrm : this.hasDrm } ) ;
} } ) ;
2022-05-06 00:23:01 +02:00
}
2023-01-07 03:06:37 +01:00
this . setupEventListeners ( ) ;
2020-06-01 23:54:10 +02:00
}
async onVideoLoaded() {
if ( ! this . videoLoaded ) {
2021-10-25 23:11:34 +02:00
2021-02-18 01:08:12 +01:00
/ * *
* video . readyState 101 :
2021-10-25 23:11:34 +02:00
* 0 — no info . Can ' t play .
2021-02-18 01:08:12 +01:00
* 1 — we have metadata but nothing else
* 2 — we have data for current playback position , but not future < -- - meaning current frame , meaning Aard can work here or higher
* 3 — we have a lil bit for the future
* 4 — we ' ll survive to the end
2021-10-25 23:11:34 +02:00
* /
2021-02-18 01:08:12 +01:00
if ( ! this . video ? . videoWidth || ! this . video ? . videoHeight || this . video . readyState < 2 ) {
2021-01-12 23:28:17 +01:00
return ; // onVideoLoaded is a lie in this case
}
2020-06-01 23:54:10 +02:00
this . logger . log ( 'info' , 'init' , '%c[VideoData::onVideoLoaded] ——————————— Initiating phase two of videoData setup ———————————' , 'color: #0f9' ) ;
this . videoLoaded = true ;
2020-06-04 21:51:22 +02:00
this . videoDimensionsLoaded = true ;
2020-06-01 23:54:10 +02:00
try {
await this . setupStageTwo ( ) ;
this . logger . log ( 'info' , 'init' , '%c[VideoData::onVideoLoaded] ——————————— videoData setup stage two complete ———————————' , 'color: #0f9' ) ;
} catch ( e ) {
this . logger . log ( 'error' , 'init' , '%c[VideoData::onVideoLoaded] ——————————— Setup stage two failed. ———————————\n' , 'color: #f00' , e ) ;
}
2020-06-04 21:51:22 +02:00
} else if ( ! this . videoDimensionsLoaded ) {
2020-06-04 22:15:32 +02:00
this . logger . log ( 'info' , 'debug' , "%c[VideoData::restoreCrop] Recovering from illegal video dimensions. Resetting aspect ratio." , "background: #afd, color: #132" ) ;
2020-06-04 21:51:22 +02:00
this . restoreCrop ( ) ;
this . videoDimensionsLoaded = true ;
2020-06-01 23:54:10 +02:00
}
}
2021-01-12 23:28:17 +01:00
videoUnloaded() {
this . videoLoaded = false ;
}
2020-11-06 00:03:11 +01:00
async injectBaseCss() {
try {
2021-03-30 01:10:37 +02:00
if ( ! this . mutationObserver ) {
this . setupMutationObserver ( ) ;
2020-11-06 00:03:11 +01:00
}
2022-07-31 00:15:28 +02:00
this . eventBus . send (
'inject-css' ,
`
. uw - ultrawidify - base - wide - screen {
margin : 0px 0 px 0 px 0 px ! important ;
width : initial ! important ;
align - self : start ! important ;
justify - self : start ! important ;
max - height : initial ! important ;
max - width : initial ! important ;
}
`
) ;
2020-11-06 00:03:11 +01:00
} catch ( e ) {
console . error ( 'Failed to inject base css!' , e ) ;
}
}
unsetBaseClass() {
2021-03-30 01:10:37 +02:00
this . mutationObserver . disconnect ( ) ;
this . mutationObserver = undefined ;
2020-11-06 00:03:11 +01:00
this . video . classList . remove ( 'uw-ultrawidify-base-wide-screen' ) ;
}
2021-01-12 23:28:27 +01:00
//#region <video> event handlers
2021-01-12 23:24:20 +01:00
onLoadedData() {
this . logger . log ( 'info' , 'init' , '[VideoData::ctor->video.onloadeddata] Video fired event "loaded data!"' ) ;
this . onVideoLoaded ( ) ;
}
onLoadedMetadata() {
this . logger . log ( 'info' , 'init' , '[VideoData::ctor->video.onloadedmetadata] Video fired event "loaded metadata!"' ) ;
this . onVideoLoaded ( ) ;
}
onTimeUpdate() {
this . onVideoLoaded ( ) ;
}
2021-01-12 23:28:27 +01:00
//#endregion
2021-01-27 00:41:42 +01:00
2021-01-12 23:28:27 +01:00
//#region lifecycle-ish
/ * *
2023-01-07 03:06:37 +01:00
* Sets up event listeners for this video
2021-01-12 23:28:27 +01:00
* /
2023-01-07 03:06:37 +01:00
async setupEventListeners() {
this . logger . log ( 'info' , 'init' , '%c[VideoData::setupEventListeners] ——————————— Starting event listener setup! ———————————' , 'color: #0f9' ) ;
2020-11-06 00:03:11 +01:00
// this is in case extension loads before the video
2021-01-12 23:24:20 +01:00
this . video . addEventListener ( 'loadeddata' , this . onLoadedData . bind ( this ) ) ;
this . video . addEventListener ( 'loadedmetadata' , this . onLoadedMetadata . bind ( this ) ) ;
2020-11-06 00:03:11 +01:00
// this one is in case extension loads after the video is loaded
2021-01-12 23:24:20 +01:00
this . video . addEventListener ( 'timeupdate' , this . onTimeUpdate . bind ( this ) ) ;
2020-11-06 00:03:11 +01:00
2023-01-07 03:06:37 +01:00
this . logger . log ( 'info' , 'init' , '%c[VideoData::setupEventListeners] ——————————— Event listeners setup complete! ———————————' , 'color: #0f9' ) ;
2020-11-06 00:03:11 +01:00
}
2021-01-12 23:28:27 +01:00
/ * *
* Launches the extension for a given video ( after the video element is defined well enough
* for our standards )
* /
2020-06-01 23:54:10 +02:00
async setupStageTwo() {
2020-06-02 00:52:23 +02:00
// NOTE: ORDERING OF OBJ INITIALIZATIONS IS IMPORTANT (arDetect needs to go last)
2019-09-03 22:42:38 +02:00
this . player = new PlayerData ( this ) ;
2021-11-25 00:31:38 +01:00
2019-09-18 01:03:04 +02:00
if ( this . player . invalid ) {
this . invalid = true ;
return ;
}
2020-02-11 19:54:41 +01:00
this . resizer = new Resizer ( this ) ;
2021-11-25 00:31:38 +01:00
this . arDetector = new ArDetector ( this ) ; // this starts Ar detection. needs optional parameter that prevents ArDetector from starting
2023-03-30 00:43:30 +02:00
this . logger . log ( 'info' , [ 'debug' , 'init' ] , '[VideoData::ctor] Created videoData with vdid' , this . vdid ) ;
2021-11-25 00:31:38 +01:00
// Everything is set up at this point. However, we are still purely "read-only" at this point. Player CSS should not be changed until
// after we receive a "please crop" or "please stretch".
// Time to apply any crop from address of crop mode persistence
2023-01-07 18:57:47 +01:00
const defaultCrop = this . siteSettings . getDefaultOption ( 'crop' ) as Ar ;
2023-01-07 03:06:37 +01:00
const defaultStretch = this . siteSettings . getDefaultOption ( 'stretch' ) ;
this . resizer . setAr ( defaultCrop ) ;
this . resizer . setStretchMode ( defaultStretch ) ;
2021-11-25 00:31:38 +01:00
}
/ * *
* Must be triggered on first action . TODO
* /
preparePage() {
this . injectBaseCss ( ) ;
this . pageInfo . initMouseActionHandler ( this ) ;
// aspect ratio autodetection cannot be properly initialized at this time,
// so we'll avoid doing that
this . enable ( ) ;
// start fallback video/player size detection
this . fallbackChangeDetection ( ) ;
}
2020-02-11 19:54:41 +01:00
2021-11-25 00:31:38 +01:00
initializeObservers() {
2021-02-08 20:43:56 +01:00
try {
if ( BrowserDetect . firefox ) {
2021-10-25 23:11:34 +02:00
this . observer = new ResizeObserver (
2021-02-08 20:43:56 +01:00
_ . debounce (
this . onVideoDimensionsChanged ,
250 ,
{
leading : true ,
trailing : true
}
)
) ;
} else {
// Chrome for some reason insists that this.onPlayerDimensionsChanged is not a function
// when it's not wrapped into an anonymous function
2021-10-25 23:11:34 +02:00
this . observer = new ResizeObserver (
2021-02-08 20:43:56 +01:00
_ . debounce (
( m , o ) = > {
this . onVideoDimensionsChanged ( m , o )
2021-10-25 23:11:34 +02:00
} ,
2021-02-08 20:43:56 +01:00
250 ,
{
leading : true ,
trailing : true
}
)
) ;
}
} catch ( e ) {
console . error ( '[VideoData] Observer setup failed:' , e ) ;
}
2021-03-29 23:30:54 +02:00
this . observer . observe ( this . video ) ;
2019-09-21 21:49:31 +02:00
}
2021-03-30 01:10:37 +02:00
setupMutationObserver() {
try {
if ( BrowserDetect . firefox ) {
this . mutationObserver = new MutationObserver (
_ . debounce (
this . onVideoMutation ,
250 ,
{
leading : true ,
trailing : true
}
)
)
} else {
// Chrome for some reason insists that this.onPlayerDimensionsChanged is not a function
// when it's not wrapped into an anonymous function
this . mutationObserver = new MutationObserver (
_ . debounce (
( m , o ) = > {
this . onVideoMutation ( m , o )
} ,
250 ,
{
leading : true ,
trailing : true
}
)
)
}
} catch ( e ) {
console . error ( '[VideoData] Observer setup failed:' , e ) ;
}
this . mutationObserver . observe ( this . video , this . mutationObserverConf ) ;
}
2021-01-12 23:28:27 +01:00
/ * *
* cleans up handlers and stuff when the show is over
* /
2021-01-12 23:24:20 +01:00
destroy() {
this . logger . log ( 'info' , [ 'debug' , 'init' ] , ` [VideoData::destroy] <vdid: ${ this . vdid } > received destroy command ` ) ;
if ( this . video ) {
this . video . classList . remove ( this . userCssClassName ) ;
2021-10-25 23:11:34 +02:00
this . video . classList . remove ( 'uw-ultrawidify-base-wide-screen' ) ;
2021-01-12 23:24:20 +01:00
this . video . removeEventListener ( 'onloadeddata' , this . onLoadedData ) ;
this . video . removeEventListener ( 'onloadedmetadata' , this . onLoadedMetadata ) ;
this . video . removeEventListener ( 'ontimeupdate' , this . onTimeUpdate ) ;
}
2021-10-26 00:30:38 +02:00
this . disable ( ) ;
2021-01-12 23:24:20 +01:00
this . destroyed = true ;
2022-05-06 00:28:13 +02:00
this . eventBus ? . unsetUpstreamBus ( ) ;
2021-01-12 23:24:20 +01:00
try {
2023-04-16 01:55:11 +02:00
this . arDetector . stop ( ) ;
2021-01-12 23:24:20 +01:00
this . arDetector . destroy ( ) ;
} catch ( e ) { }
this . arDetector = undefined ;
try {
this . resizer . destroy ( ) ;
} catch ( e ) { }
this . resizer = undefined ;
try {
this . player . destroy ( ) ;
} catch ( e ) { }
try {
this . observer . disconnect ( ) ;
} catch ( e ) { }
this . player = undefined ;
this . video = undefined ;
}
2021-01-12 23:28:27 +01:00
//#endregion
2021-10-26 00:30:38 +02:00
/ * *
* Enables ultrawidify in general .
* @param options
* /
2021-11-25 00:31:38 +01:00
enable ( options ? : { fromPlayer? : boolean } ) {
2021-10-26 00:30:38 +02:00
this . enabled = true ;
// NOTE — since base class for our <video> element depends on player aspect ratio,
// we handle it in PlayerData class.
this . video . classList . add ( this . baseCssName ) ;
this . video . classList . add ( this . userCssClassName ) ; // this also needs to be applied BEFORE we initialize resizer! — O RLY? NEEDS TO BE CHECKED
if ( ! options ? . fromPlayer ) {
this . player ? . enable ( ) ;
}
2021-11-25 22:16:48 +01:00
// this.restoreCrop();
2021-10-26 00:30:38 +02:00
}
/ * *
* Disables ultrawidify in general .
* @param options
* /
disable ( options ? : { fromPlayer? : boolean } ) {
this . enabled = false ;
2023-04-16 01:55:11 +02:00
this . arDetector ? . stop ( ) ;
2021-10-26 00:30:38 +02:00
this . video . classList . remove ( this . baseCssName ) ;
this . video . classList . remove ( this . userCssClassName ) ;
if ( ! options . fromPlayer ) {
this . player ? . disable ( ) ;
}
}
2021-01-13 01:11:55 +01:00
//#region video status
isVideoPlaying() {
return this . video && ! ! ( this . video . currentTime > 0 && ! this . video . paused && ! this . video . ended && this . video . readyState > 2 ) ;
}
2021-01-12 23:28:27 +01:00
2021-01-13 01:11:55 +01:00
hasVideoStartedPlaying() {
return this . video && this . video . currentTime > 0 ;
}
//#endregion
2020-11-06 00:03:11 +01:00
2021-10-25 23:11:34 +02:00
restoreCrop() {
2021-10-26 00:30:38 +02:00
if ( ! this . resizer ) {
this . logger . log ( 'warn' , 'debug' , '[VideoData::restoreCrop] Resizer has not been initialized yet. Crop will not be restored.' ) ;
return ;
}
2020-10-21 23:51:58 +02:00
this . logger . log ( 'info' , 'debug' , '[VideoData::restoreCrop] Attempting to reset aspect ratio.' )
2020-06-04 21:51:22 +02:00
// if we have default crop set for this page, apply this.
// otherwise, reset crop
2021-11-25 22:16:48 +01:00
2020-06-04 21:51:22 +02:00
if ( this . pageInfo . defaultCrop ) {
this . resizer . setAr ( this . pageInfo . defaultCrop ) ;
} else {
2021-10-25 23:11:34 +02:00
this . resizer . reset ( ) ;
2020-06-04 21:51:22 +02:00
try {
2020-10-21 23:51:58 +02:00
this . stopArDetection ( ) ;
2020-06-04 21:51:22 +02:00
this . startArDetection ( ) ;
} catch ( e ) {
this . logger . log ( 'warn' , 'debug' , '[VideoData::restoreCrop] Autodetection not resumed. Reason:' , e ) ;
}
}
}
2021-02-08 20:44:34 +01:00
/ * *
* Starts fallback change detection ( validates whether currently applied settings are correct )
* /
2019-09-21 21:49:31 +02:00
async fallbackChangeDetection() {
2021-02-08 20:44:34 +01:00
const validationId = Date . now ( ) ;
this . validationId = validationId ;
while ( ! this . destroyed && ! this . invalid && this . validationId === validationId ) {
2021-02-08 22:45:51 +01:00
await sleep ( 500 ) ;
2020-01-28 01:27:30 +01:00
this . doPeriodicFallbackChangeDetectionCheck ( ) ;
2019-09-21 21:49:31 +02:00
}
}
2020-01-28 01:27:30 +01:00
doPeriodicFallbackChangeDetectionCheck() {
this . validateVideoOffsets ( ) ;
}
2021-03-30 01:10:37 +02:00
onVideoMutation ( mutationList? : MutationRecord [ ] , observer ? ) {
// verify that mutation didn't remove our class. Some pages like to do that.
2019-10-27 16:51:31 +01:00
let confirmAspectRatioRestore = false ;
2021-05-11 22:44:24 +02:00
if ( ! this . video ) {
this . logger . log ( 'error' , 'debug' , '[VideoData::onVideoMutation] mutation was triggered, but video element is missing. Something is fishy. Terminating this uw instance.' ) ;
this . destroy ( ) ;
return ;
}
2021-10-26 00:30:38 +02:00
if ( ! this . enabled ) {
this . logger . log ( 'info' , 'info' , '[VideoData::onVideoMutation] mutation was triggered, but the extension is disabled. Is the player window too small?' ) ;
return ;
}
2021-03-30 01:10:37 +02:00
for ( const mutation of mutationList ) {
2019-10-27 16:51:31 +01:00
if ( mutation . type === 'attributes' ) {
2021-10-26 00:30:38 +02:00
if ( mutation . attributeName === 'class'
&& mutation . oldValue . indexOf ( this . baseCssName ) !== - 1
&& ! this . video . classList . contains ( this . baseCssName )
2021-03-30 01:10:37 +02:00
) {
// force the page to include our class in classlist, if the classlist has been removed
// while classList.add() doesn't duplicate classes (does nothing if class is already added),
// we still only need to make sure we're only adding our class to classlist if it has been
// removed. classList.add() will _still_ trigger mutation (even if classlist wouldn't change).
// This is a problem because INFINITE RECURSION TIME, and we _really_ don't want that.
2019-10-27 16:51:31 +01:00
confirmAspectRatioRestore = true ;
2021-03-30 01:10:37 +02:00
this . video . classList . add ( this . userCssClassName ) ;
2021-10-26 00:30:38 +02:00
this . video . classList . add ( this . baseCssName ) ;
2021-03-30 01:10:37 +02:00
} else if ( mutation . attributeName === 'style' ) {
2019-10-27 16:51:31 +01:00
confirmAspectRatioRestore = true ;
}
2019-09-18 01:03:04 +02:00
}
}
2021-03-30 01:10:37 +02:00
this . processDimensionsChanged ( ) ;
}
onVideoDimensionsChanged ( mutationList , observer ) {
if ( ! mutationList || this . video === undefined ) { // something's wrong
if ( observer && this . video ) {
2021-10-26 00:30:38 +02:00
this . logger . log (
'warn' , 'debug' ,
'onVideoDimensionChanged encountered a weird state. video and observer exist, but mutationlist does not.\n\nmutationList:' , mutationList ,
'\nobserver:' , observer ,
'\nvideo:' , this . video ,
'\n\nObserver will be disconnected.'
) ;
2021-03-30 01:10:37 +02:00
observer . disconnect ( ) ;
}
2019-10-27 16:51:31 +01:00
return ;
}
2021-03-30 01:10:37 +02:00
this . processDimensionsChanged ( ) ;
}
/ * *
* Forces Ultrawidify to resotre aspect ratio . You should never call this method directly ,
* instead you should be calling processDimensionChanged ( ) wrapper function .
* /
private _processDimensionsChanged() {
2021-08-25 22:30:06 +02:00
if ( ! this . player ) {
2021-10-26 00:30:38 +02:00
this . logger . log ( 'warn' , 'debug' , ` [VideoData::_processDimensionsChanged] Player is not defined. This is super haram. ` , this . player ) ;
return ;
2021-08-25 22:30:06 +02:00
}
2019-09-18 01:03:04 +02:00
// adding player observer taught us that if element size gets triggered by a class, then
// the 'style' attributes don't necessarily trigger. This means we also need to trigger
// restoreAr here, in case video size was changed this way
2021-02-08 20:43:56 +01:00
this . player . forceRefreshPlayerElement ( ) ;
this . restoreAr ( ) ;
2019-09-18 01:03:04 +02:00
// sometimes something fucky wucky happens and mutations aren't detected correctly, so we
// try to get around that
setTimeout ( ( ) = > {
2021-02-08 20:43:56 +01:00
this . validateVideoOffsets ( ) ;
2019-09-18 01:03:04 +02:00
} , 100 ) ;
}
2021-03-30 01:10:37 +02:00
/ * *
* Restores aspect ratio and validates video offsets after the restore . Execution uses
* debounce to limit how often the function executes .
* /
private processDimensionsChanged() {
_ . debounce (
this . _processDimensionsChanged ,
250 ,
{
leading : true ,
trailing : true
}
) ;
}
2019-09-18 01:03:04 +02:00
validateVideoOffsets() {
2019-09-22 02:07:04 +02:00
// validate if current video still exists. If not, we destroy current object
try {
if ( ! document . body . contains ( this . video ) ) {
this . destroy ( ) ;
return ;
}
} catch ( e ) {
}
2019-09-18 01:03:04 +02:00
// THIS BREAKS PANNING
2021-05-11 22:42:51 +02:00
const videoComputedStyle = window . getComputedStyle ( this . video ) ;
const playerComputedStyle = window . getComputedStyle ( this . player . element ) ;
2019-09-18 01:03:04 +02:00
try {
2021-05-11 22:42:51 +02:00
const transformMatrix = videoComputedStyle . transform . split ( ')' ) [ 0 ] . split ( ',' ) ;
2019-09-18 01:03:04 +02:00
const translateX = + transformMatrix [ 4 ] ;
const translateY = + transformMatrix [ 5 ] ;
2021-05-11 22:42:51 +02:00
const vh = + ( videoComputedStyle . height . split ( 'px' ) [ 0 ] ) ;
const vw = + ( videoComputedStyle . width . split ( 'px' ) [ 0 ] ) ;
const ph = + ( playerComputedStyle . height . split ( 'px' ) [ 0 ] ) ;
const pw = + ( playerComputedStyle . width . split ( 'px' ) [ 0 ] ) ;
2019-09-18 01:03:04 +02:00
// TODO: check & account for panning and alignment
2019-10-27 16:48:05 +01:00
if ( transformMatrix [ 0 ] !== 'none'
&& this . isWithin ( vh , ( ph - ( translateY * 2 ) ) , 2 )
&& this . isWithin ( vw , ( pw - ( translateX * 2 ) ) , 2 ) ) {
2019-09-18 01:03:04 +02:00
} else {
2021-11-25 22:16:48 +01:00
// this.player.forceRefreshPlayerElement();
// this.restoreAr();
2019-08-24 17:05:04 +02:00
}
2021-10-25 23:11:34 +02:00
2019-09-18 01:03:04 +02:00
} catch ( e ) {
2021-02-08 20:47:06 +01:00
console . error ( 'Validating video offsets failed:' , e )
2019-08-24 17:05:04 +02:00
}
2018-05-12 02:51:58 +02:00
}
2019-09-18 01:03:04 +02:00
isWithin ( a , b , diff ) {
return a < b + diff && a > b - diff
}
2020-12-18 01:44:45 +01:00
/ * *
* Gets the contents of the style attribute of the video element
* in a form of an object .
* /
2021-02-08 22:45:51 +01:00
getVideoStyle ( ) : any {
2020-12-18 01:44:45 +01:00
// This will _always_ give us an array. Empty string gives an array
// that contains one element. That element is an empty string.
const styleArray = ( this . video . getAttribute ( 'style' ) || '' ) . split ( ';' ) ;
2021-10-25 23:11:34 +02:00
2020-12-18 01:44:45 +01:00
const styleObject = { } ;
for ( const style of styleArray ) {
// not a valid CSS, so we skip those
if ( style . indexOf ( ':' ) === - 1 ) {
continue ;
}
// let's play _very_ safe
2020-12-19 03:02:20 +01:00
let [ property , value ] = style . split ( '!important' ) [ 0 ] . split ( ':' ) ;
2020-12-18 01:44:45 +01:00
value = value . trim ( ) ;
styleObject [ property ] = value ;
}
return styleObject ;
}
/ * *
2021-10-25 23:11:34 +02:00
* Some sites try to accommodate ultrawide users by "cropping" videos
2020-12-18 01:44:45 +01:00
* by setting 'style' attribute of the video element to 'height: X%' ,
* where 'X' is something greater than 100 .
2021-10-25 23:11:34 +02:00
*
2020-12-18 01:44:45 +01:00
* This function gets that percentage and converts it into a factor .
* /
getHeightCompensationFactor() {
const heightStyle = this . getVideoStyle ( ) ? . height ;
if ( ! heightStyle || ! heightStyle . endsWith ( '%' ) ) {
return 1 ;
}
const heightCompensationFactor = heightStyle . split ( '%' ) [ 0 ] / 100 ;
if ( isNaN ( heightCompensationFactor ) ) {
return 1 ;
}
return heightCompensationFactor ;
}
2018-05-16 23:26:47 +02:00
2022-06-10 00:22:06 +02:00
//#region AARD handlers — TODO: remove, AARD handlers shouldn't be here
2018-05-14 20:39:15 +02:00
initArDetection() {
2019-09-18 01:03:04 +02:00
if ( this . destroyed || this . invalid ) {
2019-08-25 01:52:04 +02:00
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
return ;
2018-09-23 19:46:40 +02:00
}
2019-10-24 00:45:11 +02:00
if ( this . arDetector ) {
2018-05-16 23:26:47 +02:00
this . arDetector . init ( ) ;
2018-05-20 23:17:09 +02:00
}
else {
2018-05-16 23:26:47 +02:00
this . arDetector = new ArDetector ( this ) ;
2018-05-20 23:17:09 +02:00
this . arDetector . init ( ) ;
}
2018-05-12 02:51:58 +02:00
}
2021-10-25 23:11:34 +02:00
2022-07-28 00:45:27 +02:00
2018-05-14 20:39:15 +02:00
startArDetection() {
2019-07-18 21:25:58 +02:00
this . logger . log ( 'info' , 'debug' , "[VideoData::startArDetection] starting AR detection" )
2019-09-18 01:03:04 +02:00
if ( this . destroyed || this . invalid ) {
2019-08-25 01:52:04 +02:00
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
return ;
2018-09-23 19:46:40 +02:00
}
2021-02-18 01:08:12 +01:00
2018-05-23 23:58:34 +02:00
try {
2021-10-26 00:30:38 +02:00
if ( hasDrm ( this . video ) ) {
this . player . showNotification ( 'AARD_DRM' ) ;
this . hasDrm = true ;
} else {
this . hasDrm = false ;
2018-11-02 02:52:01 +01:00
}
2021-10-26 00:30:38 +02:00
if ( ! this . arDetector ) {
this . initArDetection ( ) ;
}
this . arDetector . start ( ) ;
2018-05-23 23:58:34 +02:00
} catch ( e ) {
2021-10-26 00:30:38 +02:00
this . logger . log ( 'warn' , 'debug' , '[VideoData::startArDetection()] Could not start aard for some reason. Was the function was called too early?' , e ) ;
2018-05-23 23:58:34 +02:00
}
}
resumeAutoAr ( ) {
if ( this . arDetector ) {
this . startArDetection ( ) ;
}
}
2021-10-26 00:30:38 +02:00
stopArDetection() {
if ( this . arDetector ) {
2023-04-16 01:55:11 +02:00
this . arDetector . stop ( ) ;
2019-02-16 01:19:29 +01:00
}
}
2021-10-26 00:30:38 +02:00
//#endregion
2019-02-16 01:19:29 +01:00
2021-10-26 00:30:38 +02:00
//#region shit that gets propagated to resizer and should be removed. Implement an event bus instead
2018-11-18 18:44:44 +01:00
2021-02-20 00:09:17 +01:00
panHandler ( event , forcePan? : boolean ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-09-23 19:46:40 +02:00
if ( this . destroyed ) {
2019-08-25 01:52:04 +02:00
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
return ;
2018-09-23 19:46:40 +02:00
}
2018-09-18 00:42:30 +02:00
if ( ! this . resizer ) {
this . destroy ( ) ;
return ;
}
2018-12-02 23:51:34 +01:00
this . resizer . panHandler ( event , forcePan ) ;
2018-09-13 23:47:20 +02:00
}
setPanMode ( mode ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-09-13 23:47:20 +02:00
this . resizer . setPanMode ( mode ) ;
}
2018-07-15 16:22:32 +02:00
restoreAr ( ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-07-15 16:22:32 +02:00
this . resizer . restore ( ) ;
}
2018-12-06 23:55:54 +01:00
markPlayer ( name , color ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-12-06 23:55:54 +01:00
if ( this . player ) {
this . player . markPlayer ( name , color )
}
}
2021-11-25 20:00:32 +01:00
2018-12-06 23:55:54 +01:00
unmarkPlayer() {
this . player . unmarkPlayer ( ) ;
}
2018-11-21 21:58:13 +01:00
isPlaying() {
return this . video && this . video . currentTime > 0 && ! this . video . paused && ! this . video . ended ;
}
2021-10-26 00:30:38 +02:00
//#endregion
2019-09-21 23:50:06 +02:00
checkVideoSizeChange ( ) {
const videoWidth = this . video . offsetWidth ;
const videoHeight = this . video . offsetHeight ;
// this 'if' is just here for debugging — real code starts later. It's safe to collapse and
// ignore the contents of this if (unless we need to change how logging works)
if ( this . logger . canLog ( 'debug' ) ) {
if ( ! this . video ) {
this . logger . log ( 'info' , 'videoDetect' , "[VideoDetect] player element isn't defined" ) ;
}
2020-01-28 23:34:36 +01:00
if ( this . video &&
( this . dimensions ? . width != videoWidth ||
this . dimensions ? . height != videoHeight )
2019-09-21 23:50:06 +02:00
) {
this . logger . log ( 'info' , 'debug' , "[VideoDetect] player size changed. reason: dimension change. Old dimensions?" , this . dimensions . width , this . dimensions . height , "new dimensions:" , this . video . offsetWidth , this . video . offsetHeight ) ;
}
}
2021-10-25 23:11:34 +02:00
2019-09-21 23:50:06 +02:00
// if size doesn't match, update & return true
2020-01-28 23:34:36 +01:00
if ( this . dimensions ? . width != videoWidth
|| this . dimensions ? . height != videoHeight ) {
2019-09-21 23:50:06 +02:00
this . dimensions = {
width : videoWidth ,
height : videoHeight ,
} ;
return true ;
}
return false ;
}
2022-06-10 00:22:06 +02:00
/ * *
* Returns :
* * number of parent elements on route from < video > to < body >
* * parent index of automatically detected player element
* * index of current player element
* /
getPageOutline() {
}
2018-12-31 01:03:07 +01:00
}
export default VideoData ;