class LiveAudioPlayer { private static readonly SpeedCorrectionFactor : number = 40 / 1.0e6 ; private static readonly OffsetVariance : number = 0.2 ; private readonly Audio : AudioContext ; private readonly Logger : Logging ; private readonly MaxVolume : number ; private readonly StartOffset : number ; private readonly VariableSpeed : boolean ; private readonly OffsetMin : number ; private readonly OffsetMax : number ; private PlaybackSpeed : number ; private NextScheduleTime : number ; private Amplification : GainNode ; constructor ( audio , logger , maxVolume = 1.0 , startOffset = 0.33 , variableSpeed = false ) { this . Audio = audio ; this . Logger = logger ; this . MaxVolume = maxVolume ; this . StartOffset = startOffset ; this . VariableSpeed = variableSpeed ; this . OffsetMin = this . StartOffset - LiveAudioPlayer . OffsetVariance ; this . OffsetMax = this . StartOffset + LiveAudioPlayer . OffsetVariance ; this . PlaybackSpeed = 1.0 ; this . NextScheduleTime = 0.0 ; this . Amplification = this . Audio . createGain ( ) ; this . Amplification . gain . value = 1.0 ; this . Amplification . connect ( this . Audio . destination ) ; } public set Volume ( value ) { if ( value > 1.0 ) value = this . MaxVolume ; else if ( value <= 0.0 ) value = 1e-20 ; this . Amplification . gain . cancelScheduledValues ( this . Audio . currentTime ) ; this . Amplification . gain . exponentialRampToValueAtTime ( value , this . Audio . currentTime + 0.5 ) ; } public get Volume ( ) : number { return this . Amplification . gain . value ; } public PushBuffer ( buffer ) { if ( this . NextScheduleTime == 0.0 ) { this . NextScheduleTime = this . Audio . currentTime + this . StartOffset ; } let duration ; if ( this . VariableSpeed ) duration = buffer . duration / this . PlaybackSpeed ; else duration = buffer . duration ; if ( this . NextScheduleTime + duration > this . Audio . currentTime ) { let skipDurationTime ; if ( this . Audio . currentTime >= this . NextScheduleTime ) { skipDurationTime = this . Audio . currentTime - this . NextScheduleTime + 0.05 ; } else { skipDurationTime = 0.0 ; } if ( skipDurationTime < duration ) { let sourceNode = this . Audio . createBufferSource ( ) ; sourceNode . onended = function ( _ev ) { sourceNode . disconnect ( ) ; } ; sourceNode . loop = false ; sourceNode . buffer = buffer ; sourceNode . connect ( this . Amplification ) ; if ( this . VariableSpeed ) { let scheduleOffset = this . NextScheduleTime - this . Audio . currentTime ; if ( this . NextScheduleTime - this . Audio . currentTime > this . OffsetMax ) { if ( this . PlaybackSpeed < 1.0 + LiveAudioPlayer . SpeedCorrectionFactor ) { this . Logger . Log ( "Buffer size too large, speeding up playback." ) ; this . PlaybackSpeed = 1.0 + LiveAudioPlayer . SpeedCorrectionFactor ; duration = buffer . duration / this . PlaybackSpeed ; } } else if ( this . NextScheduleTime - this . Audio . currentTime < this . OffsetMin ) { if ( this . PlaybackSpeed > 1.0 - LiveAudioPlayer . SpeedCorrectionFactor ) { this . Logger . Log ( "Buffer size too small, slowing down playback." ) ; this . PlaybackSpeed = 1.0 - LiveAudioPlayer . SpeedCorrectionFactor ; duration = buffer . duration / this . PlaybackSpeed ; } } else { if ( ( this . PlaybackSpeed > 1.0 && ( this . NextScheduleTime - this . Audio . currentTime < this . StartOffset ) ) || ( this . PlaybackSpeed < 1.0 && ( this . NextScheduleTime - this . Audio . currentTime > this . StartOffset ) ) ) { this . Logger . Log ( "Buffer size within limits, using normal playback speed." ) ; this . PlaybackSpeed = 1.0 ; duration = buffer . duration ; } } sourceNode . playbackRate . value = this . PlaybackSpeed ; } sourceNode . start ( this . NextScheduleTime + skipDurationTime , skipDurationTime ) ; } else { this . Logger . Log ( "Skipped buffer because it became too old." ) ; } } else { this . Logger . Log ( "Skipped buffer because it was too old." ) ; } this . NextScheduleTime += duration ; } public Reset ( ) { this . NextScheduleTime = 0.0 ; } public CheckBeforeDecode ( playbackLength ) { if ( this . NextScheduleTime == 0 ) return true ; return this . NextScheduleTime + playbackLength > this . Audio . currentTime ; } }