@awayjs/core
Version:
AwayJS core classes
355 lines (354 loc) • 14.1 kB
JavaScript
import { __extends } from "tslib";
import { BaseAudioChannel } from './BaseAudioChannel';
var WebAudioChannel = /** @class */ (function (_super) {
__extends(WebAudioChannel, _super);
function WebAudioChannel(groupID, groupVolume, groupPan) {
if (groupID === void 0) { groupID = 0; }
if (groupVolume === void 0) { groupVolume = 1; }
if (groupPan === void 0) { groupPan = 1; }
var _this = _super.call(this) || this;
_this._volume = 1;
_this._pan = -1;
_this._groupID = 0;
_this._groupVolume = 1;
_this._groupPan = 0;
_this._startTime = 0;
_this._groupID = groupID;
_this._groupVolume = groupVolume;
_this._groupPan = groupPan;
_this._audioCtx = WebAudioChannel.getAudioContext();
_this._usingNativePanner = typeof _this._audioCtx.createStereoPanner === 'function';
_this._gainNode = _this._audioCtx.createGain();
_this._gainNode.gain.value = _this._groupVolume * _this._volume;
_this._pannerNode = _this._usingNativePanner ?
_this._audioCtx.createStereoPanner() :
_this._audioCtx.createPanner();
_this.pan = 0;
_this._gainNode.connect(_this._pannerNode);
_this._pannerNode.connect(_this._audioCtx.destination);
_this._onEndedDelegate = function (event) { return _this._onEnded(event); };
return _this;
}
WebAudioChannel.getOfflineContext = function (channels, samples, rate) {
if (channels === void 0) { channels = 1; }
if (samples === void 0) { samples = 0; }
if (rate === void 0) { rate = 44100; }
// eslint-disable-next-line max-len
var Context = (self.OfflineAudioContext || self.webkitOfflineAudioContext);
return Context ? new Context(channels, samples, rate) : null;
};
WebAudioChannel.getAudioContext = function () {
// eslint-disable-next-line max-len
var Context = (self.AudioContext || self.webkitAudioContext);
if (!WebAudioChannel._audioCtx && Context)
WebAudioChannel._audioCtx = new Context();
if (WebAudioChannel._audioCtx && WebAudioChannel._audioCtx.state == 'suspended')
WebAudioChannel._audioCtx.resume();
return WebAudioChannel._audioCtx;
};
WebAudioChannel.stopAllSounds = function (channelGroup) {
if (channelGroup === void 0) { channelGroup = -1; }
var len = WebAudioChannel._channels.length;
if (channelGroup < 0) {
for (var j = 0; j < len; j++) {
WebAudioChannel._channels[j].stop();
}
WebAudioChannel._channels.length = 0;
return;
}
var aliveChannels = [];
for (var j = 0; j < len; j++) {
if (WebAudioChannel._channels[j].groupID == channelGroup) {
WebAudioChannel._channels[j].stop();
}
else {
aliveChannels[aliveChannels.length] = WebAudioChannel._channels[j];
}
}
WebAudioChannel._channels = aliveChannels;
};
WebAudioChannel.setChannelGroupVolume = function (value, channelGroup) {
if (channelGroup === void 0) { channelGroup = -1; }
var len = WebAudioChannel._channels.length;
if (channelGroup < 0) {
for (var j = 0; j < len; j++) {
WebAudioChannel._channels[j].groupVolume = value;
}
return;
}
for (var j = 0; j < len; j++) {
if (WebAudioChannel._channels[j].groupID == channelGroup) {
WebAudioChannel._channels[j].groupVolume = value;
}
}
};
Object.defineProperty(WebAudioChannel.prototype, "duration", {
get: function () {
return this._duration;
},
enumerable: false,
configurable: true
});
Object.defineProperty(WebAudioChannel.prototype, "currentTime", {
get: function () {
if (this._isDecoding) {
return this._currentTime;
}
return this._audioCtx.currentTime - this._startTime;
},
enumerable: false,
configurable: true
});
Object.defineProperty(WebAudioChannel.prototype, "groupID", {
get: function () {
return this._groupID;
},
set: function (value) {
this._groupID = value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(WebAudioChannel.prototype, "groupVolume", {
get: function () {
return this._groupVolume;
},
set: function (value) {
if (this._groupVolume == value)
return;
this._groupVolume = value;
this._gainNode.gain.value = this._groupVolume * this._volume;
},
enumerable: false,
configurable: true
});
Object.defineProperty(WebAudioChannel.prototype, "groupPan", {
get: function () {
return this._groupPan;
},
set: function (value) {
},
enumerable: false,
configurable: true
});
Object.defineProperty(WebAudioChannel.prototype, "volume", {
get: function () {
return this._volume;
},
set: function (value) {
if (this._volume == value)
return;
this._volume = value;
this._gainNode.gain.value = this._groupVolume * this._volume;
},
enumerable: false,
configurable: true
});
Object.defineProperty(WebAudioChannel.prototype, "pan", {
get: function () {
return this._pan;
},
set: function (value) {
if (this._pan == value)
return;
this._pan = value;
if (this._usingNativePanner) {
this._pannerNode.pan.value = this._pan;
}
else {
var pan = this._pan * (Math.PI / 2);
this._pannerNode.setPosition(Math.sin(pan), 0, Math.cos(pan));
}
},
enumerable: false,
configurable: true
});
WebAudioChannel.prototype.restart = function () {
this._isPlaying = false;
if (this._stopped) {
throw 'You can\'t restart channel that was fully stopped';
}
var buffer = WebAudioChannel._decodeCache[this._id];
if (!buffer) {
return false;
}
this._isPlaying = true;
this.executeBuffer(buffer);
this.dispatchRestart();
return true;
};
WebAudioChannel.prototype.play = function (buffer, offset, loop, id, meta) {
if (offset === void 0) { offset = 0; }
if (loop === void 0) { loop = false; }
if (id === void 0) { id = 0; }
if (buffer.byteLength === 0) {
console.warn('[WabAudioChannel] Input buffer is empty');
return;
}
_super.prototype.play.call(this, buffer, offset, loop, id, meta);
this._isLooping = this._loops > 0;
this._isPlaying = true;
this._currentTime = offset;
this._isDecoding = true;
//fast path for short sounds
if (WebAudioChannel._decodeCache[id]) {
this.executeBuffer(WebAudioChannel._decodeCache[id]);
}
else if (!WebAudioChannel._errorCache[id]) {
this._decodeAndExecute(buffer, meta);
}
else {
this.stopInternally(false);
this.dispatchStop(true);
}
};
WebAudioChannel.prototype.stopInternally = function (emitComplete) {
if (emitComplete === void 0) { emitComplete = true; }
// for AVM1 it is a bug to stop a audio thats currently decoding.
// when we call stopAllASounds() during audio-decode, this audio should still play
//if(this._isDecoding){
// return;
//}
if (!this._isPlaying)
return;
this._isPlaying = false;
this._isLooping = false;
this._isDecoding = false;
if (this._source) {
this._disposeSource();
}
// will be try restart
_super.prototype.completeInternally.call(this, emitComplete, emitComplete);
};
WebAudioChannel.prototype.stop = function () {
this.stopInternally(false);
this.dispatchStop(false);
};
WebAudioChannel.prototype._decodeAndExecute = function (buffer, meta) {
var _this = this;
var ctx = this._audioCtx;
// try to transcode to RIGHT target
// with RIGHT sample rate, to increase quality
try {
if (meta && meta.sampleRate && meta.sampleRate !== this._audioCtx.sampleRate && meta.samplesCount) {
ctx = WebAudioChannel.getOfflineContext(2, meta.samplesCount + meta.startOffset, meta.sampleRate);
}
}
catch (e) {
console.warn('[WebAudioChannel] Error when try create Offline Context:', e.message, meta);
}
var promise = ctx.decodeAudioData(buffer.slice(0), function (buffer) { return _this._onDecodeComplete(buffer, meta); }, function (event) { return _this._onError(event); });
// when decodeAudioData is Promise - it not emit error to callback, need catch promise
// @ts-ignore
if (promise instanceof self.Promise) {
promise.catch(function (e) { return _this._onError(e); });
}
};
WebAudioChannel.prototype._onDecodeComplete = function (buffer, meta) {
if (!WebAudioChannel._decodeCache[this._id]) {
// transform mp3 buffer, because it has silent regions
// IS VERY IMPORTANT FOR FLASH
if (meta && meta.startOffset > 0) {
buffer = this.removeSilent(buffer, meta.startOffset, meta.samplesCount, meta.sampleRate);
}
WebAudioChannel._decodeCache[this._id] = buffer;
}
buffer = WebAudioChannel._decodeCache[this._id];
if (!this._isPlaying)
return;
this.executeBuffer(buffer);
};
WebAudioChannel.prototype.executeBuffer = function (buffer) {
if (this._source)
this._disposeSource();
this._isDecoding = false;
this._source = this._audioCtx.createBufferSource();
//this._source.loop = this._isLooping;
this._source.connect(this._gainNode);
this._source.buffer = buffer;
this._duration = buffer.duration;
this._pan = 0;
this._startTime = this._audioCtx.currentTime - this._currentTime;
this._source.onended = this._onEndedDelegate;
try {
// retrig setter by reset cache
var vol = this._groupVolume;
var pan = this._pan;
this._groupVolume = -1;
this._pan = -1;
this.groupVolume = vol;
this.pan = pan;
// TODO: offset / startTime make problem in dino-run game:
this._source.start(this._audioCtx.currentTime, this._currentTime);
}
catch (error) {
console.warn('[WebAudioChannel] Error starting audio: ' + error);
this.dispatchStop(true);
this._disposeSource();
}
};
WebAudioChannel.prototype._onError = function (_event) {
console.warn('[WebAudioChannel] Error with decoding audio data:', _event);
WebAudioChannel._errorCache[this._id] = true;
this._isDecoding = false;
this.stopInternally(false);
this.dispatchStop(true);
};
WebAudioChannel.prototype._onEnded = function (_event) {
// we dispatch STOP only for manually stopped and if channel still alive
this.stopInternally(true);
};
WebAudioChannel.prototype._disposeSource = function () {
//clean up
this._source.onended = null;
this._source.stop(this._audioCtx.currentTime);
this._source.disconnect();
this._source = null;
};
WebAudioChannel.prototype.removeSilent = function (buffer, begin, length, originalRate) {
if (begin === void 0) { begin = 0; }
if (length === void 0) { length = 0; }
if (originalRate === void 0) { originalRate = 0; }
length = length || buffer.length;
originalRate = originalRate || buffer.sampleRate;
// we should transform to buffer rate
begin = Math.ceil((buffer.sampleRate * begin) / originalRate);
length = ((buffer.sampleRate * length) / originalRate) | 0;
var end = Math.min(buffer.length, length + begin);
var res = this._audioCtx.createBuffer(buffer.numberOfChannels, length, buffer.sampleRate);
for (var i = 0; i < buffer.numberOfChannels; i++) {
// interate over channels and copy part of channel
res.copyToChannel(buffer.getChannelData(i).subarray(begin, end), i, 0);
}
return res;
};
WebAudioChannel.maxChannels = 128;
WebAudioChannel._channels = new Array();
WebAudioChannel._decodeCache = {};
WebAudioChannel._errorCache = {};
return WebAudioChannel;
}(BaseAudioChannel));
export { WebAudioChannel };
var audioCtx = WebAudioChannel.getAudioContext();
// context state at this time is `undefined` in iOS8 Safari
if (audioCtx && audioCtx.state === 'suspended') {
var resume_1 = function () {
audioCtx.resume();
//create empty buffer
var buffer = audioCtx.createBuffer(1, 1, 22050);
var source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(audioCtx.destination);
source.start();
setTimeout(function () {
if (audioCtx.state === 'running') {
document.removeEventListener('touchstart', resume_1, false);
document.removeEventListener('touchend', resume_1, false);
}
}, 0);
};
// iOS 6-8
document.addEventListener('touchstart', resume_1, false);
// iOS 9
document.addEventListener('touchend', resume_1, false);
}