/*
    Live audio player is part of 3LAS (Low Latency Live Audio Streaming)
    https://github.com/JoJoBond/3LAS
*/
var LiveAudioPlayer = /** @class */ (function () {
    function LiveAudioPlayer(audio, logger, maxVolume, startOffset, variableSpeed) {
        if (maxVolume === void 0) { maxVolume = 1.0; }
        if (startOffset === void 0) { startOffset = 0.33; }
        if (variableSpeed === void 0) { 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;
        // Set speed to default
        this.PlaybackSpeed = 1.0;
        // Reset variable for scheduling times
        this.NextScheduleTime = 0.0;
        // Create gain node for volume control
        this.Amplification = this.Audio.createGain();
        // Set volume to max
        this.Amplification.gain.value = 1.0;
        // Connect gain node to context
        this.Amplification.connect(this.Audio.destination);
    }
    Object.defineProperty(LiveAudioPlayer.prototype, "Volume", {
        get: function () {
            // Get volume from gain node
            return this.Amplification.gain.value;
        },
        set: function (value) {
            // Clamp value to [1e-20 ; MaxVolume]
            if (value > 1.0)
                value = this.MaxVolume;
            else if (value <= 0.0)
                value = 1e-20;
            // Cancel any scheduled ramps
            this.Amplification.gain.cancelScheduledValues(this.Audio.currentTime);
            // Change volume following a ramp (more userfriendly)
            this.Amplification.gain.exponentialRampToValueAtTime(value, this.Audio.currentTime + 0.5);
        },
        enumerable: false,
        configurable: true
    });
    // Recieves an audiobuffer and schedules it for seamless playback
    LiveAudioPlayer.prototype.PushBuffer = function (buffer) {
        // Check if this is the first buffer we received
        if (this.NextScheduleTime == 0.0) {
            // Start playing [StartOffset] s from now
            this.NextScheduleTime = this.Audio.currentTime + this.StartOffset;
        }
        var duration;
        if (this.VariableSpeed)
            duration = buffer.duration / this.PlaybackSpeed; // Use regular duration
        else
            duration = buffer.duration; // Use duration adjusted for playback speed
        // Before creating a buffer and scheduling playback, check if playing this buffer makes sense at all
        // If a buffer should have been started so far in the past that it would have finished playing by now, we are better of skipping it.
        // But we still need to move the time forward to keep future timings right.
        if (this.NextScheduleTime + duration > this.Audio.currentTime) {
            var skipDurationTime = void 0;
            // If the playback start time is in the past but the playback end time is in the future, we need to partially play the buffer.
            if (this.Audio.currentTime >= this.NextScheduleTime) {
                // Calculate the time we need to skip
                skipDurationTime = this.Audio.currentTime - this.NextScheduleTime + 0.05;
            }
            else {
                // No skipping needed
                skipDurationTime = 0.0;
            }
            // Check if we'd skip the whole buffer anyway
            if (skipDurationTime < duration) {
                // Create new audio source for the buffer
                var sourceNode_1 = this.Audio.createBufferSource();
                // Make sure the node deletes itself after playback
                sourceNode_1.onended = function (_ev) {
                    sourceNode_1.disconnect();
                };
                // Prevent looping (the standard says that it should be off by default)
                sourceNode_1.loop = false;
                // Pass audio data to source
                sourceNode_1.buffer = buffer;
                //Connect the source to the gain node
                sourceNode_1.connect(this.Amplification);
                if (this.VariableSpeed) {
                    var scheduleOffset = this.NextScheduleTime - this.Audio.currentTime;
                    // Check if we are to far or too close to target schedule time
                    if (this.NextScheduleTime - this.Audio.currentTime > this.OffsetMax) {
                        if (this.PlaybackSpeed < 1.0 + LiveAudioPlayer.SpeedCorrectionFactor) {
                            // We are too slow, speed up playback (somewhat noticeable)
                            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) {
                            // We are too fast, slow down playback (somewhat noticeable)
                            this.Logger.Log("Buffer size too small, slowing down playback.");
                            this.PlaybackSpeed = 1.0 - LiveAudioPlayer.SpeedCorrectionFactor;
                            duration = buffer.duration / this.PlaybackSpeed;
                        }
                    }
                    else {
                        // Check if we are in time		
                        if ((this.PlaybackSpeed > 1.0 && (this.NextScheduleTime - this.Audio.currentTime < this.StartOffset)) ||
                            (this.PlaybackSpeed < 1.0 && (this.NextScheduleTime - this.Audio.currentTime > this.StartOffset))) {
                            // We are within our min/max offset, set playpacks to default
                            this.Logger.Log("Buffer size within limits, using normal playback speed.");
                            this.PlaybackSpeed = 1.0;
                            duration = buffer.duration;
                        }
                    }
                    // Set playback speed
                    sourceNode_1.playbackRate.value = this.PlaybackSpeed;
                }
                // Schedule playback
                sourceNode_1.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.");
        }
        // Move time forward
        this.NextScheduleTime += duration;
    };
    LiveAudioPlayer.prototype.Reset = function () {
        this.NextScheduleTime = 0.0;
    };
    LiveAudioPlayer.prototype.CheckBeforeDecode = function (playbackLength) {
        if (this.NextScheduleTime == 0)
            return true;
        return this.NextScheduleTime + playbackLength > this.Audio.currentTime;
    };
    // Crystal oscillator have a variance of about +/- 20ppm
    // So worst case would be a difference of 40ppm between two oscillators.
    LiveAudioPlayer.SpeedCorrectionFactor = 40 / 1.0e6;
    // Hystersis value for speed up/down trigger
    LiveAudioPlayer.OffsetVariance = 0.2;
    return LiveAudioPlayer;
}());