2022-04-04 13:35:42 +02:00
2022-03-31 14:40:12 +02:00
/ * *
* EGroupware Custom Html Elements - Multi Video Web Components
*
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
* @ link https : //www.egroupware.org
* @ author Hadi Nategh < hn [ at ] egroupware . org >
* @ copyright EGroupware GmbH
* /
2022-04-04 13:35:42 +02:00
// Unfortunately compiled version of module:ES2015 TS would break webcomponent constructor.
/ *
This web component allows to merge multiple videos and display them as one single widget / element
most of the html video attributes and methodes are supported . No controls attribute supported yet .
* /
2022-03-31 14:40:12 +02:00
// Create a class for the element
2022-04-04 13:35:42 +02:00
class multi _video extends HTMLElement {
/ * *
* shadow dom container
* @ private
* /
_shadow = null ;
/ * *
* wrapper container holds video tags
* @ private
* /
/ * *
* contains video objects of type VideoTagsArray
* @ private
* /
_videos = [ ] ;
/ * *
* keeps duration time internally
* @ private
* /
_duration = 0 ;
/ * *
* keeps currentTime internally
* @ private
* /
_currentTime = 0 ;
/ * *
* Keeps video playing state internally
* @ private
* /
_ _playing = false ;
constructor ( ) {
super ( ) ; // Create a shadow root
this . _shadow = this . attachShadow ( {
mode : 'open'
} ) ; // Create videos wrapper
this . _wrapper = document . createElement ( 'div' ) ;
this . _wrapper . setAttribute ( 'class' , 'wrapper' ) ; // Create some CSS to apply to the shadow dom
this . _style = document . createElement ( 'style' ) ;
this . _style . textContent = '.wrapper {' + 'width: 100%;' + 'height: auto;' + 'display: block;' + '}' + '.wrapper video {' + 'width: 100%;' + 'height: auto;' + '}' ; // attach to the shadow dom
this . _shadow . appendChild ( this . _style ) ;
this . _shadow . appendChild ( this . _wrapper ) ;
}
/ * *
* set observable attributes
* @ return { string [ ] }
* /
static get observedAttributes ( ) {
return [ 'src' , 'type' ] ;
}
/ * *
* Gets called on observable attributes changes
* @ param name attribute name
* @ param _
* @ param newVal new value
* /
attributeChangedCallback ( name , _ , newVal ) {
switch ( name ) {
case 'src' :
this . _ _buildVideoTags ( newVal ) ;
break ;
case 'type' :
this . _videos . forEach ( _item => {
_item . node . setAttribute ( 'type' , newVal ) ;
} ) ;
break ;
}
}
/ * *
* init / update video tags
* @ param _value
* @ private
* /
_ _buildVideoTags ( _value ) {
let value = _value . split ( ',' ) ;
let video = null ;
for ( let i = 0 ; i < value . length ; i ++ ) {
video = document . createElement ( 'video' ) ;
video . src = value [ i ] ;
this . _videos [ i ] = {
node : this . _wrapper . appendChild ( video ) ,
loadedmetadata : false ,
timeupdate : false ,
duration : 0 ,
previousDurations : 0 ,
currentTime : 0 ,
active : false ,
index : i
} ; // loadmetadata event
this . _videos [ i ] [ 'node' ] . addEventListener ( "loadedmetadata" , function ( _i , _event ) {
this . _videos [ _i ] [ 'loadedmetadata' ] = true ;
this . _ _check _loadedmetadata ( ) ;
} . bind ( this , i ) ) ; //timeupdate event
this . _videos [ i ] [ 'node' ] . addEventListener ( "timeupdate" , function ( _i , _event ) {
this . _currentTime = this . _videos [ i ] [ 'previousDurations' ] + _event . target . currentTime ; // push the next video to start otherwise the time update gets paused as it ends automatically
// with the current video being ended
if ( this . _videos [ i ] . node . ended && ! this . ended ) this . currentTime = this . _currentTime + 0.1 ;
this . _ _pushEvent ( 'timeupdate' ) ;
} . bind ( this , i ) ) ;
}
}
/ * *
* calculates duration of videos
* @ return { number } returns accumulated durations
* @ private
* /
_ _duration ( ) {
let duration = 0 ;
this . _videos . forEach ( _item => {
duration += _item . duration ;
} ) ;
return duration ;
}
/ * *
* Get current active video
* @ return { * [ ] } returns an array of object consist of current video displayed node
* @ private
* /
_ _getActiveVideo ( ) {
return this . _videos . filter ( _item => {
return _item . active ;
} ) ;
}
/ * *
* check if all meta data from videos are ready then pushes the event
* @ private
* /
_ _check _loadedmetadata ( ) {
let allReady = true ;
this . _videos . forEach ( _item => {
allReady = allReady && _item . loadedmetadata ;
} ) ;
if ( allReady ) {
this . _videos . forEach ( _item => {
_item . duration = _item . node . duration ;
_item . previousDurations = _item . index > 0 ? this . _videos [ _item . index - 1 ] [ 'duration' ] + this . _videos [ _item . index - 1 ] [ 'previousDurations' ] : 0 ;
} ) ;
this . duration = this . _ _duration ( ) ;
this . currentTime = 0 ;
this . _ _pushEvent ( 'loadedmetadata' ) ;
}
}
/ * *
* Creates event and dispatches it
* @ param _name
* /
_ _pushEvent ( _name ) {
let event = document . createEvent ( "Event" ) ;
event . initEvent ( _name , true , true ) ;
this . dispatchEvent ( event ) ;
}
/**************************** PUBLIC ATTRIBUTES & METHODES *************************************************/
/****************************** ATTRIBUTES **************************************/
/ * *
* set src
* @ param _value
* /
set src ( _value ) {
let value = _value . split ( ',' ) ;
Array . from ( this . _wrapper . children ) . forEach ( _ch => {
_ch . remove ( ) ;
} ) ;
this . _ _buildVideoTags ( value ) ;
}
/ * *
* get src
* @ return string returns comma separated sources
* /
get src ( ) {
return this . src ;
}
/ * *
* currentTime
* @ param _time
* /
set currentTime ( _time ) {
let ctime = _time ;
this . _currentTime = _time ;
this . _videos . forEach ( _item => {
if ( ctime < _item . duration + _item . previousDurations && ( ctime == 0 && _item . previousDurations == 0 || ctime > _item . previousDurations ) ) {
if ( this . _ _playing && _item . node . paused ) _item . node . play ( ) ;
_item . currentTime = Math . abs ( _item . previousDurations - ctime ) ;
_item . node . currentTime = _item . currentTime ;
_item . active = true ;
} else {
_item . active = false ;
_item . node . pause ( ) ;
}
_item . node . hidden = ! _item . active ;
} ) ;
}
/ * *
* get currentTime
* @ return { number }
* /
get currentTime ( ) {
return this . _currentTime ;
}
/ * *
* set video duration time attribute
* @ param _value
* /
set duration ( _value ) {
this . _duration = _value ;
}
/ * *
* get video duration time
* /
get duration ( ) {
return this . _duration ;
}
/ * *
* get paused attribute
* /
get paused ( ) {
return this . _ _getActiveVideo ( ) [ 0 ] ? . node ? . paused ;
}
/ * *
* set muted attribute
* @ param _value
* /
set muted ( _value ) {
this . _videos . forEach ( _item => {
_item . node . muted = _value ;
} ) ;
}
/ * *
* get muted attribute
* /
get muted ( ) {
return this . _ _getActiveVideo ( ) [ 0 ] ? . node ? . muted ;
}
/ * *
* get video ended attribute
* /
get ended ( ) {
return this . _videos [ this . _videos . length - 1 ] ? . node ? . ended ;
}
/ * *
* set playbackRate
* @ param _value
* /
set playbackRate ( _value ) {
this . _videos . forEach ( _item => {
_item . node . playbackRate = _value ;
} ) ;
}
/ * *
* get playbackRate
* /
get playbackRate ( ) {
return this . _ _getActiveVideo ( ) [ 0 ] ? . node ? . playbackRate ;
}
/ * *
* set volume
* /
set volume ( _value ) {
this . _videos . forEach ( _item => {
_item . node . volume = _value ;
} ) ;
}
/ * *
* get volume
* /
get volume ( ) {
return this . _ _getActiveVideo ( ) [ 0 ] ? . node ? . volume ;
}
/************************************************* METHODES ******************************************/
/ * *
* Play video
* /
play ( ) {
this . _ _playing = true ;
return this . _ _getActiveVideo ( ) [ 0 ] ? . node ? . play ( ) ;
}
/ * *
* pause video
* /
pause ( ) {
this . _ _playing = false ;
this . _ _getActiveVideo ( ) [ 0 ] ? . node ? . pause ( ) ;
}
} // Define the new multi-video element
customElements . define ( 'multi-video' , multi _video ) ;