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' ;
2019-11-29 01:33:58 +01:00
import AspectRatio from '../../../common/enums/aspect-ratio.enum' ;
2018-12-31 01:03:07 +01:00
2018-05-09 00:03:22 +02:00
class VideoData {
2019-09-03 00:48:18 +02:00
constructor ( video , settings , pageInfo ) {
2019-09-03 23:49:22 +02:00
this . vdid = ( Math . random ( ) * 100 ) . toFixed ( ) ;
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 ;
2018-08-05 23:48:56 +02:00
this . settings = settings ;
2018-11-02 02:52:01 +01:00
this . pageInfo = pageInfo ;
this . extensionMode = pageInfo . extensionMode ;
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
2019-09-18 01:03:04 +02:00
// We only init observers once player is confirmed valid
2019-08-24 17:05:04 +02:00
const observerConf = {
attributes : true ,
// attributeFilter: ['style', 'class'],
attributeOldValue : true ,
} ;
2019-09-18 01:03:04 +02:00
2019-09-22 02:07:04 +02:00
2019-08-24 17:05:04 +02:00
2018-05-14 20:39:15 +02:00
// POZOR: VRSTNI RED JE POMEMBEN (arDetect mora bit zadnji)
// 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 ) ;
2019-09-18 01:03:04 +02:00
if ( this . player . invalid ) {
this . invalid = true ;
return ;
}
2019-09-22 02:07:04 +02:00
const ths = this ;
this . observer = new MutationObserver ( ( m , o ) => this . onVideoDimensionsChanged ( m , o , ths ) ) ;
this . observer . observe ( video , observerConf ) ;
2019-09-21 23:50:06 +02:00
this . dimensions = {
width : this . video . offsetWidth ,
height : this . video . offsetHeight ,
} ;
2019-09-18 01:03:04 +02:00
this . resizer = new Resizer ( this ) ;
2019-10-24 00:45:11 +02:00
2019-09-03 22:42:38 +02:00
this . arDetector = new ArDetector ( this ) ; // this starts Ar detection. needs optional parameter that prevets ardetdctor from starting
2018-05-12 02:51:58 +02:00
// player dimensions need to be in:
// this.player.dimensions
2019-02-16 01:54:41 +01:00
// apply default align and stretch
2019-07-18 21:25:58 +02:00
this . logger . log ( 'info' , 'debug' , "%c[VideoData::ctor] Initial resizer reset!" , { background : '#afd' , color : '#132' } ) ;
2019-02-16 01:54:41 +01:00
this . resizer . reset ( ) ;
2019-09-03 00:48:18 +02:00
this . logger . log ( 'info' , [ 'debug' , 'init' ] , '[VideoData::ctor] Created videoData with vdid' , this . vdid , '\nextension mode:' , this . extensionMode )
2018-12-02 23:51:34 +01:00
this . pageInfo . initMouseActionHandler ( this ) ;
2019-08-24 17:05:04 +02:00
this . video . classList . add ( this . userCssClassName ) ; // this also needs to be applied BEFORE we initialize resizer!
2019-09-21 21:49:31 +02:00
// start fallback video/player size detection
this . fallbackChangeDetection ( ) ;
2019-10-24 00:45:11 +02:00
// force reload last aspect ratio (if default crop ratio exists)
if ( this . pageInfo . defaultCrop ) {
this . resizer . setAr ( this . pageInfo . defaultCrop ) ;
}
2019-09-21 21:49:31 +02:00
}
async fallbackChangeDetection ( ) {
while ( ! this . destroyed && ! this . invalid ) {
2019-09-21 23:50:06 +02:00
await this . sleep ( 500 ) ;
2019-09-21 21:49:31 +02:00
this . validateVideoOffsets ( ) ;
}
}
async sleep ( timeout ) {
return new Promise ( ( resolve , reject ) => setTimeout ( ( ) => resolve ( ) , timeout ) ) ;
2019-08-24 17:05:04 +02:00
}
2019-09-21 21:49:31 +02:00
2019-09-14 23:23:00 +02:00
onVideoDimensionsChanged ( mutationList , observer , context ) {
if ( ! mutationList || context . video === undefined ) { // something's wrong
if ( observer && context . video ) {
2019-08-25 01:52:04 +02:00
observer . disconnect ( ) ;
}
return ;
}
2019-10-27 16:51:31 +01:00
let confirmAspectRatioRestore = false ;
2019-08-24 17:05:04 +02:00
for ( let mutation of mutationList ) {
2019-10-27 16:51:31 +01:00
if ( mutation . type === 'attributes' ) {
if ( mutation . attributeName === 'class' ) {
if ( ! context . video . classList . contains ( this . userCssClassName ) ) {
// 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.
context . video . classList . add ( this . userCssClassName ) ;
}
// always trigger refresh on class changes, since change of classname might trigger change
// of the player size as well.
confirmAspectRatioRestore = true ;
}
if ( mutation . attributeName === 'style' ) {
confirmAspectRatioRestore = true ;
}
2019-09-18 01:03:04 +02:00
}
}
2019-10-27 16:51:31 +01:00
if ( ! confirmAspectRatioRestore ) {
return ;
}
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
context . player . forceRefreshPlayerElement ( ) ;
context . restoreAr ( ) ;
// sometimes something fucky wucky happens and mutations aren't detected correctly, so we
// try to get around that
setTimeout ( ( ) => {
context . validateVideoOffsets ( ) ;
} , 100 ) ;
}
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
const cs = window . getComputedStyle ( this . video ) ;
const pcs = window . getComputedStyle ( this . player . element ) ;
try {
const transformMatrix = cs . transform . split ( ')' ) [ 0 ] . split ( ',' ) ;
const translateX = + transformMatrix [ 4 ] ;
const translateY = + transformMatrix [ 5 ] ;
const vh = + ( cs . height . split ( 'px' ) [ 0 ] ) ;
const vw = + ( cs . width . split ( 'px' ) [ 0 ] ) ;
const ph = + ( pcs . height . split ( 'px' ) [ 0 ] ) ;
const pw = + ( pcs . width . split ( 'px' ) [ 0 ] ) ;
// 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 {
this . player . forceRefreshPlayerElement ( ) ;
this . restoreAr ( ) ;
2019-08-24 17:05:04 +02:00
}
2019-09-18 01:03:04 +02:00
} catch ( e ) {
// do nothing on fail
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
}
2018-05-16 23:26:47 +02:00
firstTimeArdInit ( ) {
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
}
2018-05-16 23:26:47 +02:00
if ( ! this . arSetupComplete ) {
this . arDetector = new ArDetector ( this ) ;
}
}
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
}
2018-05-24 20:50:37 +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
}
2019-10-24 00:45:11 +02:00
if ( ! this . arDetector ) {
this . initArDetection ( ) ;
2018-08-31 00:35:52 +02:00
}
2018-05-12 02:51:58 +02:00
this . arDetector . start ( ) ;
2018-05-09 00:03:22 +02:00
}
2018-11-02 21:41:26 +01:00
rebootArDetection ( ) {
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-11-02 21:41:26 +01:00
}
this . arDetector . init ( ) ;
}
2018-05-24 20:50:37 +02:00
stopArDetection ( ) {
2018-08-31 00:35:52 +02:00
if ( this . arDetector ) {
this . arDetector . stop ( ) ;
}
2018-05-24 20:50:37 +02:00
}
2018-05-09 00:03:22 +02:00
destroy ( ) {
2019-07-18 21:25:58 +02:00
this . logger . log ( 'info' , [ 'debug' , 'init' ] , ` [VideoData::destroy] <vdid: ${ this . vdid } > received destroy command ` ) ;
2018-08-30 00:56:15 +02:00
2019-09-22 02:07:04 +02:00
if ( this . video ) {
this . video . classList . remove ( this . userCssClassName ) ;
}
2019-08-31 18:21:49 +02:00
2018-09-23 19:46:40 +02:00
this . pause ( ) ;
2018-05-21 22:43:56 +02:00
this . destroyed = true ;
2019-09-22 02:07:04 +02:00
try {
this . arDetector . stop ( ) ;
this . arDetector . destroy ( ) ;
} catch ( e ) { }
2019-08-25 01:52:04 +02:00
this . arDetector = undefined ;
2019-09-22 02:07:04 +02:00
try {
this . resizer . destroy ( ) ;
} catch ( e ) { }
2019-08-25 01:52:04 +02:00
this . resizer = undefined ;
2019-09-22 02:07:04 +02:00
try {
this . player . destroy ( ) ;
} catch ( e ) { }
try {
this . observer . disconnect ( ) ;
} catch ( e ) { }
2019-08-25 01:52:04 +02:00
this . player = undefined ;
this . video = undefined ;
2018-05-09 00:03:22 +02:00
}
2018-05-09 00:34:22 +02:00
2018-05-23 23:58:34 +02:00
pause ( ) {
this . paused = true ;
if ( this . arDetector ) {
this . arDetector . stop ( ) ;
}
if ( this . player ) {
this . player . stop ( ) ;
}
}
resume ( ) {
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
}
2018-05-23 23:58:34 +02:00
this . paused = false ;
try {
this . resizer . start ( ) ;
2018-11-02 02:52:01 +01:00
if ( this . player ) {
this . player . start ( ) ;
}
2018-05-23 23:58:34 +02:00
} catch ( e ) {
2019-07-18 21:25:58 +02:00
this . logger . log ( 'error' , 'debug' , "[VideoData.js::resume] cannot resume for reasons. Will destroy videoData. Error here:" , e ) ;
2018-05-23 23:58:34 +02:00
this . destroy ( ) ;
}
}
resumeAutoAr ( ) {
if ( this . arDetector ) {
this . startArDetection ( ) ;
}
}
2019-02-16 01:19:29 +01:00
setManualTick ( manualTick ) {
if ( this . arDetector ) {
this . arDetector . setManualTick ( manualTick ) ;
}
}
2019-02-16 01:54:41 +01:00
2019-02-16 01:19:29 +01:00
tick ( ) {
if ( this . arDetector ) {
this . arDetector . tick ( ) ;
}
}
2018-05-09 00:34:22 +02:00
setLastAr ( lastAr ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-05-09 00:34:22 +02:00
this . resizer . setLastAr ( lastAr ) ;
}
2018-05-13 21:05:11 +02:00
setAr ( ar , lastAr ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2019-11-29 01:33:58 +01:00
if ( ar . type === AspectRatio . Fixed || ar . type === AspectRatio . FitHeight || ar . type === AspectRatio . FitHeight ) {
this . player . forceRefreshPlayerElement ( ) ;
}
2018-05-13 21:05:11 +02:00
this . resizer . setAr ( ar , lastAr ) ;
}
2018-09-14 00:10:57 +02:00
resetAr ( ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-09-14 00:10:57 +02:00
this . resizer . reset ( ) ;
}
2018-11-18 18:44:44 +01:00
resetLastAr ( ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-11-18 18:44:44 +01:00
this . resizer . setLastAr ( 'original' ) ;
}
2018-12-02 23:51:34 +01:00
panHandler ( event , forcePan ) {
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 ) ;
}
2019-09-03 23:49:22 +02:00
setVideoAlignment ( videoAlignment ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2019-09-03 23:49:22 +02:00
this . resizer . setVideoAlignment ( videoAlignment ) ;
2018-09-23 02:39:27 +02:00
}
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 ( ) ;
}
2019-12-06 00:17:09 +01:00
setStretchMode ( stretchMode , fixedStretchRatio ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2019-12-06 00:17:09 +01:00
this . resizer . setStretchMode ( stretchMode , fixedStretchRatio ) ;
2018-05-13 21:05:11 +02:00
}
2018-05-13 13:49:25 +02:00
2018-09-21 00:26:08 +02:00
setZoom ( zoomLevel , no _announce ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-09-21 00:26:08 +02:00
this . resizer . setZoom ( zoomLevel , no _announce ) ;
2018-09-18 23:37:33 +02:00
}
2018-05-24 20:50:37 +02:00
zoomStep ( step ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-09-13 23:47:20 +02:00
this . resizer . zoomStep ( step ) ;
2018-05-24 20:50:37 +02:00
}
2018-09-21 00:26:08 +02:00
announceZoom ( scale ) {
2019-09-18 01:03:04 +02:00
if ( this . invalid ) {
return ;
}
2018-09-21 00:26:08 +02:00
this . pageInfo . announceZoom ( scale ) ;
}
2018-11-21 21:58:13 +01:00
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 )
}
}
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 ;
}
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" ) ;
}
if ( this . video && this . dimensions &&
( this . dimensions . width != videoWidth ||
this . dimensions . height != videoHeight )
) {
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 ) ;
}
}
// if size doesn't match, update & return true
if ( ! this . dimensions
|| this . dimensions . width != videoWidth
|| this . dimensions . height != videoHeight ) {
this . dimensions = {
width : videoWidth ,
height : videoHeight ,
} ;
return true ;
}
return false ;
}
2018-12-31 01:03:07 +01:00
}
export default VideoData ;