You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-27 06:23:53 +01:00
147 lines
7.4 KiB
JavaScript
147 lines
7.4 KiB
JavaScript
/*
|
|
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;
|
|
}());
|
|
//# sourceMappingURL=3las.liveaudioplayer.js.map
|