import Logger from '../logger/Logger' ; import SimulcastLayers from '../simulcastlayers/SimulcastLayers' ; import SimulcastTransceiverController from '../transceivercontroller/SimulcastTransceiverController' ; import { Maybe } from '../utils/Types' ; import DefaultVideoAndEncodeParameter from '../videocaptureandencodeparameter/DefaultVideoCaptureAndEncodeParameter' ; import VideoStreamDescription from '../videostreamindex/VideoStreamDescription' ; import VideoStreamIndex from '../videostreamindex/VideoStreamIndex' ; import BitrateParameters from './BitrateParameters' ; import ConnectionMetrics from './ConnectionMetrics' ; import SimulcastUplinkObserver from './SimulcastUplinkObserver' ; import SimulcastUplinkPolicy from './SimulcastUplinkPolicy' ; const enum ActiveStreams { kHi , kHiAndLow , kMidAndLow , kLow } /**
 * [[DefaultSimulcastUplinkPolicy]] determines capture and encode
 *  parameters that reacts to estimated uplink bandwidth
 */ export default class DefaultSimulcastUplinkPolicy implements SimulcastUplinkPolicy { static readonly defaultUplinkBandwidthKbps : number = 1200 ; static readonly startupDurationMs : number = 6000 ; static readonly holdDownDurationMs : number = 4000 ; static readonly defaultMaxFrameRate = 15 ; static readonly kHiDisabledRate = 700 ; static readonly kMidDisabledRate = 240 ; private numSenders : number = 0 ; private shouldDisableSimulcast : boolean = false ; private optimalParameters : DefaultVideoAndEncodeParameter ; private parametersInEffect : DefaultVideoAndEncodeParameter ; private newQualityMap = new Map < string , RTCRtpEncodingParameters > ( ) ; private currentQualityMap = new Map < string , RTCRtpEncodingParameters > ( ) ; private newActiveStreams : ActiveStreams = ActiveStreams . kHiAndLow ; private currentActiveStreams : ActiveStreams = ActiveStreams . kHiAndLow ; private lastUplinkBandwidthKbps : number = DefaultSimulcastUplinkPolicy . defaultUplinkBandwidthKbps ; private startTimeMs : number = 0 ; private lastUpdatedMs : number = Date . now ( ) ; private videoIndex : VideoStreamIndex | null = null ; private currLocalDescriptions : VideoStreamDescription [ ] = [ ] ; private nextLocalDescriptions : VideoStreamDescription [ ] = [ ] ; private activeStreamsToPublish : ActiveStreams ; private observerQueue : Set < SimulcastUplinkObserver > = new Set < SimulcastUplinkObserver > ( ) ; constructor ( private selfAttendeeId , private logger ) { this . optimalParameters = new DefaultVideoAndEncodeParameter ( 0 , 0 , 0 , 0 , true ) ; this . parametersInEffect = new DefaultVideoAndEncodeParameter ( 0 , 0 , 0 , 0 , true ) ; this . lastUplinkBandwidthKbps = DefaultSimulcastUplinkPolicy . defaultUplinkBandwidthKbps ; this . currentQualityMap = this . fillEncodingParamWithBitrates ( [ 300 , 0 , 1200 ] ) ; this . newQualityMap = this . fillEncodingParamWithBitrates ( [ 300 , 0 , 1200 ] ) ; } updateConnectionMetric ( { uplinkKbps = 0 } ) { if ( isNaN ( uplinkKbps ) ) { return ; } if ( this . startTimeMs === 0 ) { this . startTimeMs = Date . now ( ) ; } if ( Date . now ( ) - this . startTimeMs < DefaultSimulcastUplinkPolicy . startupDurationMs ) { this . lastUplinkBandwidthKbps = DefaultSimulcastUplinkPolicy . defaultUplinkBandwidthKbps ; } else { this . lastUplinkBandwidthKbps = uplinkKbps ; } this . logger . debug ( ( ) => { return `simulcast: uplink policy update metrics ${ this . lastUplinkBandwidthKbps }` ; } ) ; let holdTime = DefaultSimulcastUplinkPolicy . holdDownDurationMs ; if ( this . currentActiveStreams === ActiveStreams . kLow ) { holdTime = DefaultSimulcastUplinkPolicy . holdDownDurationMs * 2 ; } else if ( ( this . currentActiveStreams === ActiveStreams . kMidAndLow && uplinkKbps <= DefaultSimulcastUplinkPolicy . kMidDisabledRate ) || ( this . currentActiveStreams === ActiveStreams . kHiAndLow && uplinkKbps <= DefaultSimulcastUplinkPolicy . kHiDisabledRate ) ) { holdTime = DefaultSimulcastUplinkPolicy . holdDownDurationMs / 2 ; } if ( Date . now ( ) < this . lastUpdatedMs + holdTime ) { return ; } this . newQualityMap = this . calculateEncodingParameters ( false ) ; } private calculateEncodingParameters ( numSendersChanged ) { const newBitrates = [ new BitrateParameters ( ) , new BitrateParameters ( ) , new BitrateParameters ( ) , ] ; let hysteresisIncrease = 0 , hysteresisDecrease = 0 ; if ( this . currentActiveStreams === ActiveStreams . kHi ) { hysteresisIncrease = this . lastUplinkBandwidthKbps + 1 ; hysteresisDecrease = 0 ; } else if ( this . currentActiveStreams === ActiveStreams . kHiAndLow ) { hysteresisIncrease = 2400 ; hysteresisDecrease = DefaultSimulcastUplinkPolicy . kHiDisabledRate ; } else if ( this . currentActiveStreams === ActiveStreams . kMidAndLow ) { hysteresisIncrease = 1000 ; hysteresisDecrease = DefaultSimulcastUplinkPolicy . kMidDisabledRate ; } else { hysteresisIncrease = 300 ; hysteresisDecrease = 0 ; } if ( numSendersChanged || this . lastUplinkBandwidthKbps >= hysteresisIncrease || this . lastUplinkBandwidthKbps <= hysteresisDecrease ) { if ( this . shouldDisableSimulcast ) { this . newActiveStreams = ActiveStreams . kHi ; newBitrates [ 0 ] . maxBitrateKbps = 0 ; newBitrates [ 1 ] . maxBitrateKbps = 1200 ; newBitrates [ 2 ] . maxBitrateKbps = 0 ; } else if ( this . numSenders <= 4 && this . lastUplinkBandwidthKbps >= DefaultSimulcastUplinkPolicy . kHiDisabledRate ) { this . newActiveStreams = ActiveStreams . kHiAndLow ; newBitrates [ 0 ] . maxBitrateKbps = 300 ; newBitrates [ 1 ] . maxBitrateKbps = 0 ; newBitrates [ 2 ] . maxBitrateKbps = 1200 ; } else if ( this . lastUplinkBandwidthKbps >= DefaultSimulcastUplinkPolicy . kMidDisabledRate ) { this . newActiveStreams = ActiveStreams . kMidAndLow ; newBitrates [ 0 ] . maxBitrateKbps = this . lastUplinkBandwidthKbps >= 350 ? 200 : 150 ; newBitrates [ 1 ] . maxBitrateKbps = this . numSenders <= 6 ? 600 : 350 ; newBitrates [ 2 ] . maxBitrateKbps = 0 ; } else { this . newActiveStreams = ActiveStreams . kLow ; newBitrates [ 0 ] . maxBitrateKbps = 300 ; newBitrates [ 1 ] . maxBitrateKbps = 0 ; newBitrates [ 2 ] . maxBitrateKbps = 0 ; } const bitrates = newBitrates . map ( ( v , _i , _a ) => { return v . maxBitrateKbps ; } ) ; this . newQualityMap = this . fillEncodingParamWithBitrates ( bitrates ) ; if ( ! this . encodingParametersEqual ( ) ) { this . logger . info ( `simulcast: policy:calculateEncodingParameters bw:${ this . lastUplinkBandwidthKbps } numSources:${ this . numSenders } shouldDisableSimulcast:${ this . shouldDisableSimulcast } newQualityMap: ${ this . getQualityMapString ( this . newQualityMap ) }` ) ; } } return this . newQualityMap ; } chooseMediaTrackConstraints ( ) { const trackConstraint = { width : { ideal : 1280 } , height : { ideal : 768 } , frameRate : { ideal : 15 } , } ; return trackConstraint ; } chooseEncodingParameters ( ) { this . currentQualityMap = this . newQualityMap ; this . currentActiveStreams = this . newActiveStreams ; if ( this . activeStreamsToPublish !== this . newActiveStreams ) { this . activeStreamsToPublish = this . newActiveStreams ; this . publishEncodingSimulcastLayer ( ) ; } return this . currentQualityMap ; } updateIndex ( videoIndex ) { const numSenders = videoIndex . numberOfVideoPublishingParticipantsExcludingSelf ( this . selfAttendeeId ) + 1 ; const numSendersChanged = numSenders !== this . numSenders ; const numParticipants = videoIndex . numberOfParticipants ( ) ; const newShouldDisableSimulcast = numParticipants >= 0 && numParticipants <= 2 ; const shouldDisableSimulcastChanged = this . shouldDisableSimulcast !== newShouldDisableSimulcast ; this . numSenders = numSenders ; this . shouldDisableSimulcast = newShouldDisableSimulcast ; this . optimalParameters = new DefaultVideoAndEncodeParameter ( this . captureWidth ( ) , this . captureHeight ( ) , this . captureFrameRate ( ) , this . maxBandwidthKbps ( ) , false ) ; this . videoIndex = videoIndex ; this . newQualityMap = this . calculateEncodingParameters ( numSendersChanged || shouldDisableSimulcastChanged ) ; } wantsResubscribe ( ) { let constraintDiff = ! this . encodingParametersEqual ( ) ; this . nextLocalDescriptions = this . videoIndex . localStreamDescriptions ( ) ; for ( let i = 0 ; i < this . nextLocalDescriptions . length ; i ++ ) { const streamId = this . nextLocalDescriptions [ i ] . streamId ; if ( streamId !== 0 && ! ! streamId ) { const prevIndex = this . currLocalDescriptions . findIndex ( val => { return val . streamId === streamId ; } ) ; if ( prevIndex !== - 1 ) { if ( this . nextLocalDescriptions [ i ] . disabledByWebRTC !== this . currLocalDescriptions [ prevIndex ] . disabledByWebRTC ) { constraintDiff = true ; } } } } if ( constraintDiff ) { this . lastUpdatedMs = Date . now ( ) ; } this . currLocalDescriptions = this . nextLocalDescriptions ; return constraintDiff ; } private compareEncodingParameter ( encoding1 , encoding2 ) { return JSON . stringify ( encoding1 ) === JSON . stringify ( encoding2 ) ; } private encodingParametersEqual ( ) { let different = false ; for ( const ridName of SimulcastTransceiverController . NAME_ARR_ASCENDING ) { different = different || ! this . compareEncodingParameter ( this . newQualityMap . get ( ridName ) , this . currentQualityMap . get ( ridName ) ) ; if ( different ) { break ; } } return ! different ; } chooseCaptureAndEncodeParameters ( ) { this . parametersInEffect = this . optimalParameters . clone ( ) ; return this . parametersInEffect . clone ( ) ; } private captureWidth ( ) { const width = 1280 ; return width ; } private captureHeight ( ) { const height = 768 ; return height ; } private captureFrameRate ( ) { return 15 ; } maxBandwidthKbps ( ) { return 1400 ; } setIdealMaxBandwidthKbps ( _idealMaxBandwidthKbps ) { } setHasBandwidthPriority ( _hasBandwidthPriority ) { } private fillEncodingParamWithBitrates ( bitratesKbps ) { const newMap = new Map < string , RTCRtpEncodingParameters > ( ) ; const toBps = 1000 ; const nameArr = SimulcastTransceiverController . NAME_ARR_ASCENDING ; const bitrateArr = bitratesKbps ; let scale = this . shouldDisableSimulcast ? 1 : 4 ; for ( let i = 0 ; i < nameArr . length ; i ++ ) { const ridName = nameArr [ i ] ; newMap . set ( ridName , { rid : ridName , active : bitrateArr [ i ] > 0 ? true : false , scaleResolutionDownBy : Math . max ( scale , 1 ) , maxBitrate : bitrateArr [ i ] * toBps , } ) ; scale = scale / 2 ; } return newMap ; } private getQualityMapString ( params ) { let qualityString = '' ; const localDescriptions = this . videoIndex . localStreamDescriptions ( ) ; if ( localDescriptions . length === 3 ) { params . forEach ( ( value ) => { let disabledByWebRTC = false ; if ( value . rid === 'low' ) disabledByWebRTC = localDescriptions [ 0 ] . disabledByWebRTC ; else if ( value . rid === 'mid' ) disabledByWebRTC = localDescriptions [ 1 ] . disabledByWebRTC ; else disabledByWebRTC = localDescriptions [ 2 ] . disabledByWebRTC ; qualityString += `{ rid: ${ value . rid } active:${ value . active } disabledByWebRTC: ${ disabledByWebRTC } maxBitrate:${ value . maxBitrate }}` ; } ) ; } return qualityString ; } getEncodingSimulcastLayer ( activeStreams ) { switch ( activeStreams ) { case ActiveStreams . kHi : return SimulcastLayers . High ; case ActiveStreams . kHiAndLow : return SimulcastLayers . LowAndHigh ; case ActiveStreams . kMidAndLow : return SimulcastLayers . LowAndMedium ; case ActiveStreams . kLow : return SimulcastLayers . Low ; } } private publishEncodingSimulcastLayer ( ) { const simulcastLayers = this . getEncodingSimulcastLayer ( this . activeStreamsToPublish ) ; this . forEachObserver ( observer => { Maybe . of ( observer . encodingSimulcastLayersDidChange ) . map ( f => f . bind ( observer ) ( simulcastLayers ) ) ; } ) ; } addObserver ( observer ) { this . logger . info ( 'adding simulcast uplink observer' ) ; this . observerQueue . add ( observer ) ; } removeObserver ( observer ) { this . logger . info ( 'removing simulcast uplink observer' ) ; this . observerQueue . delete ( observer ) ; } forEachObserver ( observerFunc ) { for ( const observer of this . observerQueue ) { observerFunc ( observer ) ; } } }