UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

403 lines (400 loc) 11 kB
import { EventHandler } from '../../../core/event-handler.js'; import { math } from '../../../core/math/math.js'; import { Vec3 } from '../../../core/math/vec3.js'; import { Asset } from '../../asset/asset.js'; import { SoundInstance } from '../../../platform/sound/instance.js'; import { SoundInstance3d } from '../../../platform/sound/instance3d.js'; const instanceOptions = { volume: 0, pitch: 0, loop: false, startTime: 0, duration: 0, position: new Vec3(), maxDistance: 0, refDistance: 0, rollOffFactor: 0, distanceModel: 0, onPlay: null, onPause: null, onResume: null, onStop: null, onEnd: null }; class SoundSlot extends EventHandler { static{ this.EVENT_PLAY = 'play'; } static{ this.EVENT_PAUSE = 'pause'; } static{ this.EVENT_RESUME = 'resume'; } static{ this.EVENT_STOP = 'stop'; } static{ this.EVENT_END = 'end'; } static{ this.EVENT_LOAD = 'load'; } constructor(component, name = 'Untitled', options = {}){ super(), this.instances = []; this._component = component; this._assets = component.system.app.assets; this._manager = component.system.manager; this.name = name; this._volume = options.volume !== undefined ? math.clamp(Number(options.volume) || 0, 0, 1) : 1; this._pitch = options.pitch !== undefined ? Math.max(0.01, Number(options.pitch) || 0) : 1; this._loop = !!(options.loop !== undefined ? options.loop : false); this._duration = options.duration > 0 ? options.duration : null; this._startTime = Math.max(0, Number(options.startTime) || 0); this._overlap = !!options.overlap; this._autoPlay = !!options.autoPlay; this._firstNode = null; this._lastNode = null; this._asset = options.asset; if (this._asset instanceof Asset) { this._asset = this._asset.id; } this._onInstancePlayHandler = this._onInstancePlay.bind(this); this._onInstancePauseHandler = this._onInstancePause.bind(this); this._onInstanceResumeHandler = this._onInstanceResume.bind(this); this._onInstanceStopHandler = this._onInstanceStop.bind(this); this._onInstanceEndHandler = this._onInstanceEnd.bind(this); } play() { if (!this.overlap) { this.stop(); } if (!this.isLoaded && !this._hasAsset()) { return undefined; } const instance = this._createInstance(); this.instances.push(instance); if (!this.isLoaded) { const onLoad = function(sound) { const playWhenLoaded = instance._playWhenLoaded; instance.sound = sound; if (playWhenLoaded) { instance.play(); } }; this.off('load', onLoad); this.once('load', onLoad); this.load(); } else { instance.play(); } return instance; } pause() { let paused = false; const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ if (instances[i].pause()) { paused = true; } } return paused; } resume() { let resumed = false; const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ if (instances[i].resume()) { resumed = true; } } return resumed; } stop() { let stopped = false; const instances = this.instances; let i = instances.length; while(i--){ instances[i].stop(); stopped = true; } instances.length = 0; return stopped; } load() { if (!this._hasAsset()) { return; } const asset = this._assets.get(this._asset); if (!asset) { this._assets.off(`add:${this._asset}`, this._onAssetAdd, this); this._assets.once(`add:${this._asset}`, this._onAssetAdd, this); return; } asset.off('remove', this._onAssetRemoved, this); asset.on('remove', this._onAssetRemoved, this); if (!asset.resource) { asset.off('load', this._onAssetLoad, this); asset.once('load', this._onAssetLoad, this); this._assets.load(asset); return; } this.fire('load', asset.resource); } setExternalNodes(firstNode, lastNode) { if (!firstNode) { console.error('The firstNode must have a valid AudioNode'); return; } if (!lastNode) { lastNode = firstNode; } this._firstNode = firstNode; this._lastNode = lastNode; if (!this._overlap) { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ instances[i].setExternalNodes(firstNode, lastNode); } } } clearExternalNodes() { this._firstNode = null; this._lastNode = null; if (!this._overlap) { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ instances[i].clearExternalNodes(); } } } getExternalNodes() { return [ this._firstNode, this._lastNode ]; } _hasAsset() { return this._asset != null; } _createInstance() { let instance = null; const component = this._component; let sound = null; if (this._hasAsset()) { const asset = this._assets.get(this._asset); if (asset) { sound = asset.resource; } } const data = instanceOptions; data.volume = this._volume * component.volume; data.pitch = this._pitch * component.pitch; data.loop = this._loop; data.startTime = this._startTime; data.duration = this._duration; data.onPlay = this._onInstancePlayHandler; data.onPause = this._onInstancePauseHandler; data.onResume = this._onInstanceResumeHandler; data.onStop = this._onInstanceStopHandler; data.onEnd = this._onInstanceEndHandler; if (component.positional) { data.position.copy(component.entity.getPosition()); data.maxDistance = component.maxDistance; data.refDistance = component.refDistance; data.rollOffFactor = component.rollOffFactor; data.distanceModel = component.distanceModel; instance = new SoundInstance3d(this._manager, sound, data); } else { instance = new SoundInstance(this._manager, sound, data); } if (this._firstNode) { instance.setExternalNodes(this._firstNode, this._lastNode); } return instance; } _onInstancePlay(instance) { this.fire('play', instance); this._component.fire('play', this, instance); } _onInstancePause(instance) { this.fire('pause', instance); this._component.fire('pause', this, instance); } _onInstanceResume(instance) { this.fire('resume', instance); this._component.fire('resume', this, instance); } _onInstanceStop(instance) { const idx = this.instances.indexOf(instance); if (idx !== -1) { this.instances.splice(idx, 1); } this.fire('stop', instance); this._component.fire('stop', this, instance); } _onInstanceEnd(instance) { const idx = this.instances.indexOf(instance); if (idx !== -1) { this.instances.splice(idx, 1); } this.fire('end', instance); this._component.fire('end', this, instance); } _onAssetAdd(asset) { this.load(); } _onAssetLoad(asset) { this.load(); } _onAssetRemoved(asset) { asset.off('remove', this._onAssetRemoved, this); this._assets.off(`add:${asset.id}`, this._onAssetAdd, this); this.stop(); } updatePosition(position) { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ instances[i].position = position; } } set asset(value) { const old = this._asset; if (old) { this._assets.off(`add:${old}`, this._onAssetAdd, this); const oldAsset = this._assets.get(old); if (oldAsset) { oldAsset.off('remove', this._onAssetRemoved, this); } } this._asset = value; if (this._asset instanceof Asset) { this._asset = this._asset.id; } if (this._hasAsset() && this._component.enabled && this._component.entity.enabled) { this.load(); } } get asset() { return this._asset; } set autoPlay(value) { this._autoPlay = !!value; } get autoPlay() { return this._autoPlay; } set duration(value) { this._duration = Math.max(0, Number(value) || 0) || null; if (!this._overlap) { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ instances[i].duration = this._duration; } } } get duration() { let assetDuration = 0; if (this._hasAsset()) { const asset = this._assets.get(this._asset); assetDuration = asset?.resource ? asset.resource.duration : 0; } if (this._duration != null) { return this._duration % (assetDuration || 1); } return assetDuration; } get isLoaded() { if (this._hasAsset()) { const asset = this._assets.get(this._asset); if (asset) { return !!asset.resource; } } return false; } get isPaused() { const instances = this.instances; const len = instances.length; if (len === 0) { return false; } for(let i = 0; i < len; i++){ if (!instances[i].isPaused) { return false; } } return true; } get isPlaying() { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ if (instances[i].isPlaying) { return true; } } return false; } get isStopped() { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ if (!instances[i].isStopped) { return false; } } return true; } set loop(value) { this._loop = !!value; const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ instances[i].loop = this._loop; } } get loop() { return this._loop; } set overlap(value) { this._overlap = !!value; } get overlap() { return this._overlap; } set pitch(value) { this._pitch = Math.max(Number(value) || 0, 0.01); if (!this._overlap) { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ instances[i].pitch = this.pitch * this._component.pitch; } } } get pitch() { return this._pitch; } set startTime(value) { this._startTime = Math.max(0, Number(value) || 0); if (!this._overlap) { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ instances[i].startTime = this._startTime; } } } get startTime() { return this._startTime; } set volume(value) { this._volume = math.clamp(Number(value) || 0, 0, 1); if (!this._overlap) { const instances = this.instances; for(let i = 0, len = instances.length; i < len; i++){ instances[i].volume = this._volume * this._component.volume; } } } get volume() { return this._volume; } } export { SoundSlot };