import AudioVideoController from '../audiovideocontroller/AudioVideoController' ; import AudioVideoObserver from '../audiovideoobserver/AudioVideoObserver' ; import DefaultDevicePixelRatioMonitor from '../devicepixelratiomonitor/DefaultDevicePixelRatioMonitor' ; import DevicePixelRatioWindowSource from '../devicepixelratiosource/DevicePixelRatioWindowSource' ; import Logger from '../logger/Logger' ; import { Maybe } from '../utils/Types' ; import VideoTile from '../videotile/VideoTile' ; import VideoTileState from '../videotile/VideoTileState' ; import VideoTileFactory from '../videotilefactory/VideoTileFactory' ; import VideoTileController from './VideoTileController' ; export default class DefaultVideoTileController implements VideoTileController { private tileMap = new Map < number , VideoTile > ( ) ; private nextTileId : number = 1 ; private currentLocalTile : VideoTile | null = null ; private devicePixelRatioMonitor : undefined | DefaultDevicePixelRatioMonitor ; private currentPausedTilesByIds : Set < number > = new Set < number > ( ) ; keepLastFrameWhenPaused : boolean | undefined = false ; constructor ( private tileFactory , private audioVideoController , private logger ) { this . keepLastFrameWhenPaused = audioVideoController . configuration . keepLastFrameWhenPaused ; } private createDevicePixelRatioMonitorIfNeeded ( ) { if ( this . devicePixelRatioMonitor ) { return ; } this . devicePixelRatioMonitor = new DefaultDevicePixelRatioMonitor ( new DevicePixelRatioWindowSource ( ) , this . logger ) ; } private async discardDevicePixelRatioMonitorIfNotNeeded ( ) { if ( this . tileMap . size || ! this . devicePixelRatioMonitor ) { return ; } const monitor = this . devicePixelRatioMonitor ; this . devicePixelRatioMonitor = undefined ; return monitor . destroy ( ) ; } bindVideoElement ( tileId , videoElement ) { const tile = this . getVideoTile ( tileId ) ; if ( tile === null ) { this . logger . warn ( `Ignoring video element binding for unknown tile id ${ tileId }` ) ; return ; } tile . bindVideoElement ( videoElement ) ; } unbindVideoElement ( tileId ) { this . bindVideoElement ( tileId , null ) ; } startLocalVideoTile ( ) { const tile = this . findOrCreateLocalVideoTile ( ) ; this . currentLocalTile . stateRef ( ) . localTileStarted = true ; this . audioVideoController . update ( { needsRenegotiation : true } ) ; return tile . id ( ) ; } stopLocalVideoTile ( ) { if ( ! this . currentLocalTile ) { return ; } this . currentLocalTile . stateRef ( ) . localTileStarted = false ; this . currentLocalTile . bindVideoStream ( this . audioVideoController . configuration . credentials . attendeeId , true , null , null , null , null , this . audioVideoController . configuration . credentials . externalUserId ) ; this . audioVideoController . update ( { needsRenegotiation : true } ) ; } hasStartedLocalVideoTile ( ) { return ! ! ( this . currentLocalTile && this . currentLocalTile . stateRef ( ) . localTileStarted ) ; } removeLocalVideoTile ( ) { if ( this . currentLocalTile ) { this . removeVideoTile ( this . currentLocalTile . id ( ) ) ; } } getLocalVideoTile ( ) { return this . currentLocalTile ; } pauseVideoTile ( tileId ) { const tile = this . getVideoTile ( tileId ) ; if ( tile ) { if ( ! this . currentPausedTilesByIds . has ( tileId ) ) { this . audioVideoController . pauseReceivingStream ( tile . stateRef ( ) . streamId ) ; this . currentPausedTilesByIds . add ( tileId ) ; } tile . pause ( ) ; } } unpauseVideoTile ( tileId ) { const tile = this . getVideoTile ( tileId ) ; if ( tile ) { if ( this . currentPausedTilesByIds . has ( tileId ) ) { this . audioVideoController . resumeReceivingStream ( tile . stateRef ( ) . streamId ) ; this . currentPausedTilesByIds . delete ( tileId ) ; } tile . unpause ( ) ; } } getVideoTile ( tileId ) { return this . tileMap . has ( tileId ) ? this . tileMap . get ( tileId ) : null ; } getVideoTileArea ( tile ) { const state = tile . state ( ) ; let tileHeight = 0 ; let tileWidth = 0 ; if ( state . boundVideoElement ) { tileHeight = state . boundVideoElement . clientHeight * state . devicePixelRatio ; tileWidth = state . boundVideoElement . clientWidth * state . devicePixelRatio ; } return tileHeight * tileWidth ; } getAllRemoteVideoTiles ( ) { const result = new Array < VideoTile > ( ) ; this . tileMap . forEach ( ( tile , tileId ) : void => { if ( ! this . currentLocalTile || tileId !== this . currentLocalTile . id ( ) ) { result . push ( tile ) ; } } ) ; return result ; } getAllVideoTiles ( ) { return Array . from ( this . tileMap . values ( ) ) ; } addVideoTile ( localTile = false ) { const tileId = this . nextTileId ; this . nextTileId += 1 ; this . createDevicePixelRatioMonitorIfNeeded ( ) ; const tile = this . tileFactory . makeTile ( tileId , localTile , this , this . devicePixelRatioMonitor ) ; this . tileMap . set ( tileId , tile ) ; return tile ; } removeVideoTile ( tileId ) { if ( ! this . tileMap . has ( tileId ) ) { return ; } const tile = this . tileMap . get ( tileId ) ; if ( this . currentLocalTile === tile ) { this . currentLocalTile = null ; } tile . destroy ( ) ; this . tileMap . delete ( tileId ) ; this . audioVideoController . forEachObserver ( ( observer ) => { Maybe . of ( observer . videoTileWasRemoved ) . map ( f => f . bind ( observer ) ( tileId ) ) ; } ) ; this . discardDevicePixelRatioMonitorIfNotNeeded ( ) ; } removeVideoTilesByAttendeeId ( attendeeId ) { const tilesRemoved = [ ] ; for ( const tile of this . getAllVideoTiles ( ) ) { const state = tile . state ( ) ; if ( state . boundAttendeeId === attendeeId ) { this . removeVideoTile ( state . tileId ) ; tilesRemoved . push ( state . tileId ) ; } } return tilesRemoved ; } removeAllVideoTiles ( ) { const tileIds = Array . from ( this . tileMap . keys ( ) ) ; for ( const tileId of tileIds ) { this . removeVideoTile ( tileId ) ; } } sendTileStateUpdate ( tileState ) { this . audioVideoController . forEachObserver ( ( observer ) => { Maybe . of ( observer . videoTileDidUpdate ) . map ( f => f . bind ( observer ) ( tileState ) ) ; } ) ; } haveVideoTilesWithStreams ( ) { for ( const tile of this . getAllVideoTiles ( ) ) { if ( tile . state ( ) . boundVideoStream ) { return true ; } } return false ; } haveVideoTileForAttendeeId ( attendeeId ) { return ! ! this . getVideoTileForAttendeeId ( attendeeId ) ; } getVideoTileForAttendeeId ( attendeeId ) { for ( const tile of this . getAllVideoTiles ( ) ) { const state = tile . state ( ) ; if ( state . boundAttendeeId === attendeeId ) { return tile ; } } return undefined ; } captureVideoTile ( tileId ) { const tile = this . getVideoTile ( tileId ) ; if ( ! tile ) { return null ; } return tile . capture ( ) ; } private findOrCreateLocalVideoTile ( ) { if ( this . currentLocalTile ) { return this . currentLocalTile ; } this . currentLocalTile = this . addVideoTile ( true ) ; this . currentLocalTile . bindVideoStream ( this . audioVideoController . configuration . credentials . attendeeId , true , null , null , null , null , this . audioVideoController . configuration . credentials . externalUserId ) ; return this . currentLocalTile ; } }