UNPKG

@awayjs/core

Version:
355 lines (354 loc) 14.1 kB
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); }