UNPKG

sound-manager-ts

Version:

Lightweight TypeScript soundmanager for Web Audio API. Seamless audio control in web apps and games with full type safety and modern API

32 lines 59 kB
(function(b,S){typeof exports=="object"&&typeof module<"u"?S(exports):typeof define=="function"&&define.amd?define(["exports"],S):(b=typeof globalThis<"u"?globalThis:b||self,S(b.SoundManagerTS={}))})(this,function(b){"use strict";var q=Object.defineProperty;var X=(b,S,k)=>S in b?q(b,S,{enumerable:!0,configurable:!0,writable:!0,value:k}):b[S]=k;var y=(b,S,k)=>X(b,typeof S!="symbol"?S+"":S,k);var S=(d=>(d.Stereo="stereo",d.Spatial="spatial",d))(S||{});class k{connectNodes(t,e){t.source&&(this.disconnectNodes(t),t.panType===S.Spatial&&t.pannerNode?(t.source.connect(t.pannerNode),t.pannerNode.connect(t.gainNode)):t.stereoPanner?(t.source.connect(t.stereoPanner),t.stereoPanner.connect(t.gainNode)):t.source.connect(t.gainNode),t.gainNode.connect(e))}disconnectNodes(t){var e;t.source&&t.source.disconnect(),t.stereoPanner&&t.stereoPanner.disconnect(),t.pannerNode&&t.pannerNode.disconnect(),(e=t.playOptions)!=null&&e.createNewInstance&&t.gainNode.disconnect()}}var h=(d=>(d.ENDED="ended",d.ERROR="error",d.FADE_IN_COMPLETED="fade_in_completed",d.FADE_MASTER_IN_COMPLETED="fade_master_in_completed",d.FADE_MASTER_OUT_COMPLETED="fade_master_out_completed",d.FADE_OUT_COMPLETED="fade_out_completed",d.GLOBAL_SPATIAL_POSITION_CHANGED="global_spatial_position_changed",d.LOADED="loaded",d.LOOP_COMPLETED="loop_completed",d.MASTER_PAN_CHANGED="master_pan_changed",d.MASTER_VOLUME_CHANGED="master_volume_changed",d.MUTE_GLOBAL="mute_global",d.MUTED="muted",d.OPTIONS_UPDATED="options_updated",d.PAN_CHANGED="pan_changed",d.PAN_RESET="pan_reset",d.PAUSED="paused",d.PLAYBACK_RATE_CHANGED="playback_rate_changed",d.PROGRESS="progress",d.RESET="reset",d.RESUMED="resumed",d.SEEKED="seeked",d.SPATIAL_POSITION_CHANGED="spatial_position_changed",d.SPATIAL_POSITION_RESET="spatial_position_reset",d.SPRITE_SET="sprite_set",d.STARTED="started",d.STOPPED="stopped",d.UNLOADED="unloaded",d.UNMUTE_GLOBAL="unmute_global",d.UNMUTED="unmuted",d.UPDATED_URL="updated_url",d.VOLUME_CHANGED="volume_changed",d))(h||{}),F=(d=>(d.HRTF="HRTF",d.EqualPower="equalpower",d))(F||{}),U=(d=>(d.Linear="linear",d.Inverse="inverse",d.Exponential="exponential",d))(U||{});const v={panningModel:"HRTF",distanceModel:"inverse",refDistance:1,maxDistance:1e4,rolloffFactor:1,coneInnerAngle:360,coneOuterAngle:360,coneOuterGain:0},H={autoUnlock:!0,autoMuteOnHidden:!0,autoResumeOnFocus:!0,createNewInstance:!1,webAudioPreferred:!0,html5AudioFallback:!0,maxParallelLoads:10,retryDelay:.5,fetchRetries:2,fetchTimeout:8,corsProxy:void 0,fetchStrategy:"direct-first",maxAudioSize:50*1024*1024,audioCache:!0,crossOrigin:null,credentialStrategy:"auto",debug:!1,defaultDuration:void 0,defaultPan:0,defaultPanSpatialPosition:{x:0,y:0,z:0},defaultPanType:S.Stereo,defaultPlaybackRate:1,defaultStartTime:0,defaultVolume:1,fadeInDuration:.5,fadeOutDuration:.5,loopSounds:!1,maxLoops:-1,pannerNodeConfig:v,spatialAudio:!0,trackProgress:!0};var g=(d=>(d.Playing="playing",d.Paused="paused",d.Stopped="stopped",d))(g||{});class j{constructor(){y(this,"callbacks",new Map);y(this,"animationFrameId",null);y(this,"lastTick",0);this.tick=this.tick.bind(this)}tick(t){const e=t-(this.lastTick||t);this.lastTick=t,this.callbacks.forEach(i=>{if(!i.interval){i.callback(e);return}i.lastUpdate=i.lastUpdate||t,t-i.lastUpdate>=i.interval&&(i.callback(e),i.lastUpdate=t)}),this.animationFrameId=requestAnimationFrame(this.tick)}start(){this.animationFrameId||(this.lastTick=performance.now(),this.animationFrameId=requestAnimationFrame(this.tick))}stop(){this.animationFrameId!==null&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null)}addCallback(t,e,i){this.callbacks.set(t,{id:t,callback:e,interval:i}),this.start()}removeCallback(t){this.callbacks.delete(t),this.callbacks.size===0&&this.stop()}clear(){this.callbacks.clear(),this.stop()}}class W{constructor(t={}){y(this,"config");y(this,"context");y(this,"sounds",new Map);y(this,"masterGainNode");y(this,"masterStereoPanner");y(this,"masterPannerNode");y(this,"previousGlobalVolume",1);y(this,"isMuted",!1);y(this,"previousGlobalPan",0);y(this,"PROGRESS_UPDATE_INTERVAL",50);y(this,"eventListeners",new Map);y(this,"activeSources",new Map);y(this,"activeFadeCallbacks",new Map);y(this,"isHandlingError",!1);y(this,"audioNodeConnector",new k);y(this,"ticker");y(this,"lastError",null);y(this,"masterSpatialPosition",{x:0,y:0,z:0});y(this,"_spatialAudioSupported",null);y(this,"DEFAULT_PRECISION",2);y(this,"soundGroups",new Map);y(this,"instanceCounters",new Map);y(this,"unlockHandlers",null);y(this,"VERSION","5.7.2");this.ticker=new j,this.config={debug:!1,...t},Object.values(h).forEach(e=>{this.eventListeners.set(e,new Set)}),t.defaultVolume!==void 0&&(t.defaultVolume=this.setValidatedVolume(t.defaultVolume)),t.fadeInDuration!==void 0&&t.fadeInDuration<0&&(t.fadeInDuration=0),t.fadeOutDuration!==void 0&&t.fadeOutDuration<0&&(t.fadeOutDuration=0),t.spatialAudio&&!this.isSpatialAudioSupported()&&(this.debugLog("Spatial audio requested but not supported, disabling feature"),t.spatialAudio=!1),this.config={...H,...t};try{const e=window.AudioContext||window.webkitAudioContext;this.context=new e({latencyHint:"interactive"}),this.masterGainNode=this.context.createGain(),this.masterStereoPanner=this.context.createStereoPanner(),this.masterStereoPanner.connect(this.context.destination),this.masterGainNode.connect(this.masterStereoPanner),this.masterStereoPanner.pan.value=this.config.defaultPan??0,this.previousGlobalPan=this.config.defaultPan??0,this.masterGainNode.gain.value=this.config.defaultVolume,this.previousGlobalVolume=this.config.defaultVolume,this.setupAudioUnlock(),this.initialize()}catch(e){this.handleError("constructor, initialize",e)}}initialize(){this.showConsoleInfo(),this.setupContextResumeHandlers(),this.config.autoMuteOnHidden&&this.setupVisibilityHandling(),this.config.spatialAudio&&this.initializeSpatialAudio(),this.debugLog("Initialized with config:",this.config)}debugLog(...t){this.config.debug&&console.log("[SoundManager]",...t)}setupVisibilityHandling(){document.addEventListener("visibilitychange",()=>{document.hidden?(this.debugLog("Page hidden, auto-muting sounds"),this.muteAllSounds()):this.config.autoResumeOnFocus&&(this.debugLog("Page visible, auto-resuming sounds"),this.unmuteAllSounds())})}initializeSpatialAudio(){if(!this.isSpatialAudioSupported()){this.debugLog("Spatial audio not supported, disabling feature"),this.config.spatialAudio=!1;return}try{const t=this.context.listener;t.positionX.setValueAtTime(0,this.context.currentTime),t.positionY.setValueAtTime(0,this.context.currentTime),t.positionZ.setValueAtTime(0,this.context.currentTime),t.forwardX.setValueAtTime(0,this.context.currentTime),t.forwardY.setValueAtTime(0,this.context.currentTime),t.forwardZ.setValueAtTime(-1,this.context.currentTime),t.upX.setValueAtTime(0,this.context.currentTime),t.upY.setValueAtTime(1,this.context.currentTime),t.upZ.setValueAtTime(0,this.context.currentTime),this.debugLog("Spatial audio initialized")}catch(t){this.handleError("initializing spatial audio",t)}}setupAudioUnlock(){if(!this.config.autoUnlock||(this.removeUnlockListeners(),!this.isMobileLikeEnvironment()))return;let t=!1;const e=async()=>{if(!(t||this.context.state!=="suspended"))try{const a=this.context.createBuffer(1,1,22050),o=this.context.createBufferSource();o.buffer=a,o.connect(this.context.destination),o.start(0,0,.1),await this.context.resume(),t=!0,this.removeUnlockListeners(),this.debugLog("Audio context successfully unlocked")}catch(a){this.debugLog("Audio unlock attempt failed:",a)}},i=()=>e(),n=()=>e();this.unlockHandlers={touchstart:i,touchend:i,click:n};const s={passive:!0,capture:!0};document.addEventListener("touchstart",this.unlockHandlers.touchstart,s),document.addEventListener("touchend",this.unlockHandlers.touchend,s),document.addEventListener("click",this.unlockHandlers.click,s),e()}removeUnlockListeners(){this.unlockHandlers&&(document.removeEventListener("touchstart",this.unlockHandlers.touchstart,!0),document.removeEventListener("touchend",this.unlockHandlers.touchend,!0),document.removeEventListener("click",this.unlockHandlers.click,!0),this.unlockHandlers=null)}isMobileLikeEnvironment(){return"ontouchstart"in window||navigator.maxTouchPoints>0||/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}setupContextResumeHandlers(){const t=async()=>{if(this.context.state==="suspended")try{await this.context.resume(),this.debugLog("AudioContext resumed after user interaction"),["click","touchstart","keydown"].forEach(e=>{document.removeEventListener(e,t)})}catch(e){this.debugLog("Failed to resume AudioContext:",e)}};["click","touchstart","keydown"].forEach(e=>{document.addEventListener(e,t,{once:!0})}),this.debugLog("Context resume handlers set up, waiting for user interaction")}setupAudioSource(t){var n,s,a;const e=((n=t.playOptions)==null?void 0:n.playbackRate)??1,i=this.context.createBufferSource();if(i.buffer=t.buffer,t.source=i,i.playbackRate.setValueAtTime(e,this.context.currentTime),((s=t.playOptions)==null?void 0:s.pan)!==void 0&&t.panType!==S.Spatial&&this.setPan(t.id,t.playOptions.pan,!0),(a=t.playOptions)!=null&&a.panSpatialPosition&&(t.playOptions.panSpatialPosition.x!==0||t.playOptions.panSpatialPosition.y!==0||t.playOptions.panSpatialPosition.z!==0)){const o=t.playOptions.panSpatialPosition;this.setSpatialPosition(o.x,o.y,o.z,t.id,void 0,!0)}return this.audioNodeConnector.connectNodes(t,this.masterGainNode),this.activeSources.set(t.id,i),i.onended=()=>{var o;this.debugLog(`Sound ${t.id} ended naturally`),t.state===g.Playing&&((o=t.playOptions)!=null&&o.loop?this.handleLoopIteration(t):this.handleSoundEnded(t))},i}handleLoopIteration(t){var i,n,s,a,o,r,c;if(this.debugLog(`Restarting loop for sound ${t.id}`),!((i=t.playOptions)!=null&&i.loop)){this.debugLog(`Loop was disabled for ${t.id}, handling as ended`),this.handleSoundEnded(t);return}if(t.state!==g.Playing){this.debugLog(`Sound ${t.id} is no longer playing (state: ${t.state}), skipping loop iteration`),this.handleSoundEnded(t);return}if(((n=t.playOptions)==null?void 0:n.maxLoops)!==void 0&&((s=t.playOptions)==null?void 0:s.maxLoops)>0&&(t.currentLoopCount??0)>=((a=t.playOptions)==null?void 0:a.maxLoops)-1){this.debugLog(`Max loops reached for ${t.id}, stopping`),t.currentLoopCount=0,this.stop(t.id);return}t.currentLoopCount=(t.currentLoopCount??0)+1,this.debugLog(`Loop count: ${t.currentLoopCount}`);const e=(((o=t.playOptions)==null?void 0:o.startTime)??0)/(((r=t.playOptions)==null?void 0:r.playbackRate)??1);(c=t.playOptions)!=null&&c.createNewInstance&&(t.playOptions.createNewInstance=!1),this.seek(t.id,e,!0),this.dispatchEvent({type:h.LOOP_COMPLETED,soundId:t.id,timestamp:this.context.currentTime,sound:t})}handleSoundEnded(t){var i,n,s,a,o,r;if(t.state===g.Stopped)return;if((i=t.playOptions)!=null&&i.pauseAtDurationReached&&((n=t.playOptions)==null?void 0:n.duration)!==void 0&&t.playOptions.duration>0){this.pause(t.id),t.startTime=void 0,t.pausedAt=((s=t.playOptions)==null?void 0:s.startTime)??0,t.currentTime=0;return}t.state=g.Stopped,t.startTime=void 0,t.pausedAt=((a=t.playOptions)==null?void 0:a.startTime)??0,t.currentTime=0,(o=t.playOptions)!=null&&o.createNewInstance?this.cleanupExistingSource(t.id):this.cleanupSound(t.id),this.stopProgressTracking(t.id),(r=t.playOptions)!=null&&r.createNewInstance&&this.removeEventListenersForInstance(t.id);const e=t.id.includes(":")?t.id.split(":")[0]:t.id;this.dispatchEvent({type:h.ENDED,soundId:t.id,originalId:e,instanceId:t.id,timestamp:this.context.currentTime,sound:t})}scheduleFadeOut(t,e,i){if(!this.sounds.get(t))return;const s=this.context.currentTime+e,a=()=>{this.fadeOut(t,i)};this.ticker.addCallback(`fadeOut_${t}`,()=>{this.context.currentTime>=s&&(a(),this.ticker.removeCallback(`fadeOut_${t}`))})}cancelScheduledFadeOut(t){this.ticker.removeCallback(`fadeOut_${t}`)}cancelFadeAnimation(t){this.ticker.removeCallback(`fade_${t}`);const e=this.activeFadeCallbacks.get(t);e&&(e(),this.activeFadeCallbacks.delete(t));const i=this.sounds.get(t);i!=null&&i.gainNode&&i.gainNode.gain.cancelScheduledValues(this.context.currentTime),i&&(i.isFadingIn=!1,i.isFadingOut=!1)}getValidatedSound(t){const e=this.sounds.get(t);return e!=null&&e.buffer||this.handleError("validating sound","Sound not found or not loaded properly",t),e}setValidatedVolume(t){return Math.max(0,Math.min(1,t))}cleanupSound(t){const e=this.sounds.get(t);e&&(this.debugLog(`Cleaning up sound ${t}`),this.cancelScheduledFadeOut(t),this.cancelFadeAnimation(t),this.stopProgressTracking(t),this.removeEventListenersForInstance(t),this.audioNodeConnector.disconnectNodes(e))}cleanup(){this.sounds.forEach((t,e)=>{this.cleanupSound(e)}),this.sounds.clear(),this.activeSources.forEach(t=>{if(t)try{t.onended=null,t.stop(),t.disconnect()}catch{}}),this.activeSources.clear(),this.cleanupGlobalPan(),this.sounds.forEach(t=>{t.pannerNode&&t.pannerNode.disconnect(),t.stereoPanner&&t.stereoPanner.disconnect(),t.source&&(t.source.stop(),t.source.disconnect(),t.source.onended=null),t.gainNode.disconnect()}),this.sounds.clear(),this.masterStereoPanner.disconnect(),this.masterGainNode.disconnect()}handleError(t,e,i){const n=e instanceof Error?e.message:String(e),s=i?` (Sound ID: ${i})`:"",a=`[SoundManager] Error ${t}${s}: ${n}`;if(this.lastError=e instanceof Error?e:new Error(n),this.config.debug?console.error(a,e):console.error(a),!this.isHandlingError){this.isHandlingError=!0;try{this.dispatchEvent({type:h.ERROR,timestamp:this.context.currentTime,error:new Error(a)})}finally{this.isHandlingError=!1}}}cleanupExistingSource(t){var e,i;try{let n=this.getValidatedSound(t);if(!n.source)return;const s=this.context.currentTime,a=n.startTime||0,o=s-a<(((e=n.buffer)==null?void 0:e.duration)||0);if(n.state===g.Playing&&o&&((i=n.playOptions)!=null&&i.createNewInstance))return;n.source.onended&&(n.source.onended=null),n.startTime!==void 0&&n.source.stop(),n.source.disconnect(),this.activeSources.delete(t),n.source=null}catch(n){this.debugLog(`Error cleaning up source for ${t}: ${n}`)}}getInstanceCounter(t){const e=t.split(":")[0];let i=this.instanceCounters.get(e)||0;return this.instanceCounters.set(e,i+1),i+1}resetCounterForSound(t){const e=t.split(":")[0];this.instanceCounters.delete(e),this.debugLog(`Counter reset for sound ${e}`)}reconnectAudioNodes(t){const e=this.sounds.get(t);!e||!e.source||this.audioNodeConnector.connectNodes(e,this.masterGainNode)}play(t,e={},i=!1){var n,s,a,o,r,c,p,l,m,f,P,T,O,E,_,D,N;this.context.state==="suspended"&&this.config.autoUnlock&&this.setupAudioUnlock();try{const L=this.getValidatedSound(t);L||this.debugLog(`Sound ${t} not found`);let z={...L.playOptions,...e};const Y=z.createNewInstance??this.config.createNewInstance??!1;let $=t,M,x=e.groupId||L.groupId;if(x){let A=this.soundGroups.get(x);if(!A){this.debugLog(`Group ${x} not found.`);return}if(this.debugLog(`Group ${x} has ${A.sounds.size} instances. Max instances: ${A.maxInstances}`),A.maxInstances&&A.sounds.size>=A.maxInstances){const V=Array.from(A.sounds)[0];this.debugLog(`Max instances reached. Stopping oldest instance: ${V}`),this.stop(V),A.sounds.delete(V),this.debugLog(`Stopped and removed oldest instance ${V} from group ${x}.`)}}if(Y){const A=t.split(":")[0],V=this.getInstanceCounter(A);$=`${A}:${V}`,this.debugLog(`Creating new instance with ID: ${$}`);const I=JSON.parse(JSON.stringify({...L.playOptions,...e,createNewInstance:!1})),w=this.context.createGain(),G=e.volume??L.volume??this.config.defaultVolume??1;w.gain.value=G,M={...L,id:$,gainNode:w,state:g.Stopped,currentTime:0,startTime:(I==null?void 0:I.startTime)??0,pausedAt:0,currentLoopCount:0,playOptions:I,volume:G,originalVolume:G,pan:0,panSpatialPosition:{x:0,y:0,z:0},panType:I.panType??S.Stereo,source:null,stereoPanner:null,pannerNode:null},this.sounds.set($,M),x&&(this.addToSoundGroup(x,$),this.debugLog(`Added new instance ${$} to group ${x}.`))}const u=M||L;if(!u){this.debugLog(`Failed to create sound instance for ${t}`);return}M===void 0&&(u.playOptions=z),this.cleanupExistingSource(u.id);const B=this.setupAudioSource(u);if(!B){this.debugLog(`Failed to create audio source for sound ${t}`);return}const C=((n=u.playOptions)==null?void 0:n.playbackRate)||1;let R=0;if(u.pausedAt!==void 0&&u.pausedAt!==0?R=u.pausedAt:((s=u.playOptions)==null?void 0:s.startTime)!==void 0&&(R=u.playOptions.startTime),u.startTime=this.context.currentTime-R/C,u.state=g.Playing,((a=u.playOptions)==null?void 0:a.volume)!==void 0&&this.setSoundVolume(u.id,u.playOptions.volume,!0),((o=u.playOptions)==null?void 0:o.pan)!==void 0&&u.panType!==S.Spatial&&this.setPan(u.id,u.playOptions.pan,!0),((r=u.playOptions)==null?void 0:r.panSpatialPosition)!==void 0&&u.panType===S.Spatial&&this.setSpatialPosition(u.playOptions.panSpatialPosition.x,u.playOptions.panSpatialPosition.y,u.playOptions.panSpatialPosition.z,u.id,void 0,!0),((c=u.playOptions)==null?void 0:c.fadeInDuration)!==void 0&&this.fadeIn(u.id,((p=u.playOptions)==null?void 0:p.fadeInDuration)??((l=this.config)==null?void 0:l.fadeInDuration)??1,void 0,(m=u.playOptions)==null?void 0:m.volume),((f=u.playOptions)==null?void 0:f.fadeOutDuration)!==void 0&&this.fadeOut(u.id,u.playOptions.fadeOutDuration??((P=this.config)==null?void 0:P.fadeOutDuration)??1),((T=u.playOptions)==null?void 0:T.playbackRate)!==void 0&&this.setPlaybackRate(u.id,C,!0),((O=u.playOptions)==null?void 0:O.loop)!==void 0&&this.setLoop(u.id,u.playOptions.loop,u.playOptions.maxLoops),((E=u.playOptions)==null?void 0:E.fadeOutBeforeEndDuration)!==void 0){this.cancelScheduledFadeOut(u.id);const A=u.playOptions.fadeOutBeforeEndDuration,w=(u.playOptions.duration??((_=u.buffer)==null?void 0:_.duration)??0)-(u.pausedAt||0)-A;w>0&&this.scheduleFadeOut(u.id,w,u.playOptions.fadeOutBeforeEndDuration??1)}if(B.start(0,R,((D=u.playOptions)==null?void 0:D.duration)!==void 0&&u.playOptions.duration>0?u.playOptions.duration*C:void 0),((N=u.playOptions)==null?void 0:N.trackProgress)===!0&&this.startProgressTracking(u.id),!i){const A=u.id.includes(":")?u.id.split(":")[0]:u.id;this.dispatchEvent({type:h.STARTED,soundId:u.id,originalId:A,instanceId:u.id,timestamp:this.context.currentTime,sound:u})}return u}catch(L){this.handleError("playing sound",L,t)}}playSprite(t,e,i,n=!1){const s=`${t}_${e}`;this.play(s,i,n)}pause(t,e=!1){var i;try{const n=this.getValidatedSound(t);if(!this.isPlaying(t)||this.isPaused(t))return;this.cancelScheduledFadeOut(t);const s=((i=n.playOptions)==null?void 0:i.playbackRate)??1,a=(this.context.currentTime-(n.startTime||0))*s;n.currentTime=a,n.pausedAt=a,this.debugLog(`Pausing sound ${t}: Raw elapsed time: ${a} Adjusted time: ${a/s} StartTime: ${n.startTime} CurrentTime: ${n.currentTime} PausedAt: ${n.pausedAt} PlaybackRate: ${s} `),n.state=g.Paused,this.stopProgressTracking(t),this.cleanupExistingSource(t),e||this.dispatchEvent({type:h.PAUSED,soundId:t,timestamp:this.context.currentTime,sound:n})}catch(n){this.handleError("pausing sound",n,t)}}resume(t,e=!1){let i=this.getValidatedSound(t);this.play(t,i==null?void 0:i.playOptions);try{e||this.dispatchEvent({type:h.RESUMED,soundId:t,timestamp:this.context.currentTime,sound:i}),this.debugLog(`Resumed sound ${t}:`)}catch(n){this.handleError("resuming sound",n,t)}}stop(t,e=!1){var i;try{const n=this.sounds.get(t);if(!n){this.debugLog(`Sound ${t} not found for stopping`);return}this.cancelScheduledFadeOut(t),this.cancelFadeAnimation(t),this.stopProgressTracking(t),this.cleanupExistingSource(t),n.state=g.Stopped,n.startTime=((i=n.playOptions)==null?void 0:i.startTime)??0,n.pausedAt=0,n.currentTime=0,this.removeEventListenersForInstance(t),this.resetCounterForSound(t),e||this.dispatchEvent({type:h.STOPPED,soundId:t,timestamp:this.context.currentTime,sound:n})}catch(n){console.error(`Error stopping sound ${t}:`,n),this.dispatchEvent({type:h.ERROR,timestamp:this.context.currentTime,error:new Error(`Failed to stop sound ${t}: ${n}`)})}}seek(t,e,i=!1){var n,s,a;try{const o=this.getValidatedSound(t),{duration:r,currentTime:c}=this.getSoundState(t);if(e>=r){if(o.state===g.Stopped)return;o.state===g.Playing&&((n=o.playOptions)!=null&&n.loop)?this.handleLoopIteration(o):this.handleSoundEnded(o);return}const p=((s=o.buffer)==null?void 0:s.duration)||0,l=((a=o.playOptions)==null?void 0:a.playbackRate)||1,m=e*l,f=Math.max(0,Math.min(m,p));if(o.currentTime=f,o.pausedAt=f,o.state===g.Playing&&(this.cleanupExistingSource(t),o.startTime=this.context.currentTime-f/l,this.play(t,o.playOptions)),i)return;this.dispatchEvent({type:h.SEEKED,soundId:t,currentTime:c,timestamp:this.context.currentTime,sound:o})}catch(o){this.handleError("seeking sound",o,t)}}stopAllSounds(){try{Array.from(this.activeSources.keys()).forEach(e=>this.stop(e)),this.debugLog("All sounds stopped")}catch(t){this.handleError("stopping all sounds",t)}}pauseAllSounds(){this.sounds.forEach((t,e)=>{t.state===g.Playing&&this.pause(e)})}resumeAllSounds(){this.sounds.forEach((t,e)=>{t.state==g.Paused&&this.resume(e)})}fadeIn(t,e,i,n,s=!1){var p,l;const a=this.getValidatedSound(t);console.log("fade in",t),this.cancelFadeAnimation(t),a.isFadingOut=!1,a.isFadingIn=!0;const o=this.roundValue(a.gainNode.gain.value,2);let r;n!==void 0?r=n:(r=a.volume??((p=a.playOptions)==null?void 0:p.volume)??this.config.defaultVolume??1,r===0&&(r=1));let c;i!==void 0?c=i:a.isFadingOut?c=o:o>=r?c=((l=a.playOptions)==null?void 0:l.fadeInStartVolume)??0:c=o,a.gainNode.gain.cancelScheduledValues(this.context.currentTime),a.gainNode.gain.setValueAtTime(c,this.context.currentTime),a.state!==g.Playing&&this.play(t,{volume:c}),this.fadeSound(t,c,r,e,()=>{a.volume=r,a.playOptions&&(a.playOptions.volume=r),s||this.dispatchEvent({type:h.FADE_IN_COMPLETED,soundId:t,timestamp:this.context.currentTime,sound:a})})}fadeOut(t,e=this.config.fadeOutDuration,i,n,s=!1,a=!1){var l;const o=this.getValidatedSound(t);this.cancelFadeAnimation(t),o.isFadingIn=!1,o.isFadingOut=!0;const r=this.roundValue(o.gainNode.gain.value,2),c=i??r,p=n??((l=o.playOptions)==null?void 0:l.fadeOutEndVolume)??0;o.previousVolume=r,o.gainNode.gain.setValueAtTime(c,this.context.currentTime),o.state!==g.Playing&&this.play(t,{volume:c}),this.fadeSound(t,c,p,e,()=>{a||this.dispatchEvent({type:h.FADE_OUT_COMPLETED,soundId:t,timestamp:this.context.currentTime,sound:o}),n===0&&s&&this.stop(t)})}fadeSound(t,e,i,n,s){try{this.cancelFadeAnimation(t);const a=this.getValidatedSound(t);a.volume=e,a.gainNode.gain.cancelScheduledValues(this.context.currentTime);const o=Math.max(0,n-.02),r=this.context.currentTime,c=r+o,p=()=>{a.isFadingIn=!1,a.isFadingOut=!1,a.volume=this.roundValue(i),a.gainNode.gain.setValueAtTime(i,this.context.currentTime),s==null||s(),this.dispatchEvent({type:h.VOLUME_CHANGED,soundId:t,timestamp:this.context.currentTime,volume:a.volume,sound:a}),this.activeFadeCallbacks.delete(t)};this.activeFadeCallbacks.set(t,p);const l=`fade_${t}`,m=()=>{const f=this.context.currentTime;if(f>=c){this.ticker.removeCallback(l),p();return}const P=(f-r)/o,T=e+(i-e)*P;a.gainNode.gain.setValueAtTime(T,f),a.volume=this.roundValue(T),a.playOptions={...a.playOptions,volume:a.volume},this.dispatchEvent({type:h.VOLUME_CHANGED,soundId:t,timestamp:f,volume:a.volume,sound:a})};this.ticker.addCallback(l,m),this.debugLog(`Fade scheduled for sound ${t}: Start time: ${r} Duration: ${o} Start volume: ${e} Target volume: ${i} `)}catch(a){this.handleError("fading sound",a,t)}}fadeGlobalIn(t=this.config.fadeInDuration,e,i){try{const n=e??0,s=i??(this.masterGainNode.gain.value||this.previousGlobalVolume);this.masterGainNode.gain.cancelScheduledValues(this.context.currentTime);const a=this.context.currentTime,o=t,r=a+o,c="fade_global_in",p=()=>{const l=this.context.currentTime;if(l>=r){this.masterGainNode.gain.setValueAtTime(s,l),this.isMuted=!1,this.dispatchEvent({type:h.FADE_MASTER_IN_COMPLETED,timestamp:l,volume:s}),this.ticker.removeCallback(c);return}const m=(l-a)/o,f=n+(s-n)*m;this.masterGainNode.gain.setValueAtTime(f,l),this.dispatchEvent({type:h.MASTER_VOLUME_CHANGED,timestamp:l,volume:f,isMaster:!0})};this.ticker.addCallback(c,p)}catch(n){this.handleError("fading in master volume",n)}}fadeGlobalOut(t=this.config.fadeOutDuration,e,i=0){if(t<=0){this.debugLog(`Invalid fade duration: ${t}`);return}try{const n=e??this.roundValue(this.masterGainNode.gain.value,2);this.previousGlobalVolume=n;const s=this.context.currentTime,a=t,o=s+a,r="fade_global_out",c=()=>{const p=this.context.currentTime;if(p>=o){this.masterGainNode.gain.setValueAtTime(i,p),this.isMuted=i===0,this.dispatchEvent({type:h.FADE_MASTER_OUT_COMPLETED,timestamp:p,volume:i}),this.ticker.removeCallback(r);return}const l=(p-s)/a,m=n+(i-n)*l;this.masterGainNode.gain.setValueAtTime(m,p),this.dispatchEvent({type:h.MASTER_VOLUME_CHANGED,timestamp:p,volume:m,isMaster:!0})};this.ticker.addCallback(r,c)}catch(n){this.handleError("fading out master volume",n)}}getVolume(t){return this.getSoundVolume(t)}setSoundVolume(t,e,i=!1){try{this.cancelFadeAnimation(t);const n=this.getValidatedSound(t),s=this.setValidatedVolume(e);n.volume=this.roundValue(s),n.originalVolume=s,n.playOptions={...n.playOptions,volume:s},n.gainNode.gain.cancelScheduledValues(this.context.currentTime),n.gainNode.gain.setValueAtTime(s,this.context.currentTime),i||this.dispatchEvent({type:h.VOLUME_CHANGED,soundId:t,timestamp:this.context.currentTime,volume:n.volume,sound:n})}catch(n){this.handleError("setting volume",n,t)}}getSoundVolume(t){var e;try{const i=this.getValidatedSound(t);return i.originalVolume??i.volume??((e=i.playOptions)==null?void 0:e.volume)??this.config.defaultVolume??1}catch(i){return this.handleError("getting volume",i,t),0}}setGlobalVolume(t){this.previousGlobalVolume=this.setValidatedVolume(t),this.isMuted||(this.masterGainNode.gain.value=this.previousGlobalVolume),this.dispatchEvent({type:h.MASTER_VOLUME_CHANGED,timestamp:this.context.currentTime,volume:this.previousGlobalVolume,isMaster:!0})}getGlobalVolume(){return this.isMuted?0:this.roundValue(this.masterGainNode.gain.value,2)}muteAllSounds(){this.previousGlobalVolume=this.roundValue(this.masterGainNode.gain.value,2),this.masterGainNode.gain.setValueAtTime(0,this.context.currentTime),this.isMuted=!0,this.dispatchEvent({type:h.MUTE_GLOBAL,timestamp:this.context.currentTime,isMuted:!0,volume:0})}unmuteAllSounds(){this.masterGainNode.gain.setValueAtTime(this.previousGlobalVolume,this.context.currentTime),this.isMuted=!1,this.dispatchEvent({type:h.UNMUTE_GLOBAL,timestamp:this.context.currentTime,isMuted:!1,volume:this.previousGlobalVolume})}mute(t){try{const e=this.getValidatedSound(t);e.previousVolume=e.volume,e.volume=0,e.gainNode.gain.setValueAtTime(0,this.context.currentTime),this.dispatchEvent({type:h.MUTED,soundId:t,timestamp:this.context.currentTime,volume:0,isMuted:!0,sound:e})}catch(e){this.handleError("muting sound",e,t)}}unmute(t){try{const e=this.getValidatedSound(t),i=e.previousVolume??this.config.defaultVolume??1;e.volume=i,e.gainNode.gain.setValueAtTime(i,this.context.currentTime),this.dispatchEvent({type:h.UNMUTED,soundId:t,timestamp:this.context.currentTime,volume:i,isMuted:!1,sound:e})}catch(e){this.handleError("unmuting sound",e,t)}}toggleMute(t){var i;const e=this.getValidatedSound(t);((e==null?void 0:e.volume)??(e==null?void 0:e.originalVolume)??((i=e.playOptions)==null?void 0:i.volume)??1)>0?this.mute(t):this.unmute(t)}toggleGlobalMute(){this.isMuted?this.unmuteAllSounds():this.muteAllSounds()}setLoop(t,e,i=-1){const n=this.sounds.get(t);if(!n){this.debugLog(`Sound ${t} not found for setting loop`);return}n.playOptions={...n.playOptions,loop:e,maxLoops:i},this.debugLog(`Loop set for sound ${t}: ${e}`)}getLoop(t){var i;const e=this.sounds.get(t);return e?((i=e.playOptions)==null?void 0:i.loop)??!1:(this.debugLog(`Sound ${t} not found for getting loop`),!1)}shouldUseProxy(t){return!(!this.config.corsProxy||this.isLocalUrl(t))}isLocalUrl(t){return t.startsWith("/")||t.startsWith("./")||t.startsWith("../")||t.startsWith("blob:")||t.startsWith("data:")||!/^https?:/i.test(t)}getProxyUrl(t){if(!this.shouldUseProxy(t))return t;const e=this.config.corsProxy;if(e.includes("cors-anywhere"))return`${e}${t}`;if(e.includes("?")){const i=e.includes("url=")?"":"url=";return`${e}${i}${encodeURIComponent(t)}`}return`${e}${t}`}async loadWithWebAudio(t,e,i){if(i!=null&&i.aborted)throw i.reason??new DOMException("Aborted","AbortError");const n=this.config.fetchStrategy==="proxy-first"&&this.shouldUseProxy(e)?["proxy","direct"]:["direct"];for(const s of n)try{const a=s==="proxy"||this.config.corsProxy?this.getProxyUrl(e):e;this.debugLog(`Trying ${s} strategy for ${t}`,{originalUrl:e,fetchUrl:a});const o=await this.fetchWithRetry(a,{mode:"cors",credentials:"omit",cache:this.config.audioCache?"default":"no-cache",headers:{Accept:"audio/mpeg, audio/*"}},i);return await this.processAudioResponse(t,o)}catch(a){if(i!=null&&i.aborted||a instanceof DOMException&&a.name==="AbortError")throw a;if(this.debugLog(`${s} strategy failed for ${t}`,{error:a instanceof Error?a.message:String(a),url:e}),s===n[n.length-1])throw new Error(`Failed to load sound ${t}: ${a instanceof Error?a.message:String(a)}`)}}async fetchWithRetry(t,e,i){const{fetchRetries:n=2,retryDelay:s=.5,fetchTimeout:a=10}=this.config;let o=null;if(i!=null&&i.aborted)throw i.reason??new DOMException("Aborted","AbortError");const r=this.config.credentialStrategy==="auto"?this.config.crossOrigin==="use-credentials"?["include","omit"]:["omit"]:[this.config.credentialStrategy||"omit"];for(const c of r)for(let p=0;p<=n;p++){const l=Date.now(),m=new AbortController,f=setTimeout(()=>m.abort(),a*1e3),P=i?AbortSignal.any([i,m.signal]):m.signal;try{const T={...e,credentials:c,signal:P};this.debugLog(`Attempt ${p+1} with credentials=${c}`);const O=await fetch(t,T);if(clearTimeout(f),!O.ok)throw new Error(`HTTP ${O.status}`);if(e.mode==="cors"){const E=O.headers.get("access-control-allow-origin");if(c==="include"&&E==="*")throw new Error("Invalid CORS: Credentialed request with wildcard origin")}return this.debugLog(`Success after ${Date.now()-l}ms`),O}catch(T){if(clearTimeout(f),i!=null&&i.aborted)throw T;o=T instanceof Error?T:new Error(String(T)),this.debugLog(`Attempt ${p+1} failed: ${o.message}`)}if(p<n){if(i!=null&&i.aborted)throw i.reason??new DOMException("Aborted","AbortError");await new Promise(T=>setTimeout(T,s*1e3))}}throw o||new Error("Failed after all retries")}async processAudioResponse(t,e){const i=e.headers.get("content-type");if(!(i!=null&&i.includes("audio/")))throw new Error(`Invalid content type: ${i}`);const n=e.headers.get("content-length");if(n&&this.config.maxAudioSize&&parseInt(n)>this.config.maxAudioSize)throw new Error(`Audio file too large: ${n} bytes (max ${this.config.maxAudioSize} bytes), change the maxAudioSize config in your SoundManagerConfig`);const s=n?parseInt(n):void 0,a=await e.arrayBuffer(),o=await this.context.decodeAudioData(a);this.createSoundNode(t,o,s),this.debugLog(`Sound ${t} loaded successfully`)}async loadWithHtml5Audio(t,e,i){if(!this.config.html5AudioFallback)throw new Error("HTML5 Audio fallback is disabled in configuration");if(i!=null&&i.aborted)throw i.reason??new DOMException("Aborted","AbortError");return new Promise((n,s)=>{const a=new Audio;a.crossOrigin=this.config.crossOrigin==="use-credentials"?"use-credentials":"anonymous",a.preload="auto",a.src=e;const o=()=>{a.oncanplaythrough=null,a.onerror=null,a.removeEventListener("error",c),i==null||i.removeEventListener("abort",r)},r=()=>{o(),a.src="",s(i.reason??new DOMException("Aborted","AbortError"))},c=()=>{o(),s(new Error(`HTML5 Audio load error: ${a.error?a.error.message:"unknown"}`))};a.oncanplaythrough=async()=>{o();try{await this.loadWithWebAudio(t,e,i),this.debugLog(`Sound ${t} loaded with HTML5 Audio fallback`),n()}catch(p){s(p)}},a.onerror=c,a.addEventListener("error",c),i&&i.addEventListener("abort",r,{once:!0}),a.load()})}async loadSounds(t,e){if(t.length){if(e!=null&&e.aborted)throw e.reason??new DOMException("Aborted","AbortError");try{const{maxParallelLoads:i=10,webAudioPreferred:n=!0}=this.config,s=Math.max(1,i),a=[];for(let o=0;o<t.length;o+=s)a.push(t.slice(o,o+s));for(const o of a){if(e!=null&&e.aborted)throw e.reason??new DOMException("Aborted","AbortError");const r=o.map(async({id:l,url:m})=>{if(this.sounds.has(l)){this.debugLog(`Sound with id ${l} already exists. Skipping.`);return}try{if(n)try{await this.loadWithWebAudio(l,m,e);return}catch(f){if(e!=null&&e.aborted)throw f;this.debugLog(`Web Audio load failed for ${l}`,f)}await this.loadWithHtml5Audio(l,m,e)}catch(f){throw f instanceof DOMException&&f.name==="AbortError"||this.handleError("loading sound",f,l),f}}),c=await Promise.allSettled(r),p=c.filter(l=>l.status==="rejected");if(p.length){const l=p[0].reason;if(l instanceof DOMException&&l.name==="AbortError")throw l;const m=o.filter((f,P)=>c[P].status==="rejected").map(f=>f.id);throw new Error(`Failed to load sounds: ${m.join(", ")}`)}}}catch(i){throw i instanceof DOMException&&i.name==="AbortError"||this.handleError("preloading sounds",i),i}}}calculateAudioSize(t){return t.numberOfChannels*t.length*4}createSoundNode(t,e,i){const n=this.context.createGain();n.gain.value=this.config.defaultVolume??1,n.connect(this.masterGainNode);const s=this.context.createBufferSource();s.buffer=e;const a=this.calculateAudioSize(e),o={id:t,buffer:e,gainNode:n,source:s,startTime:void 0,currentTime:0,pausedAt:void 0,state:g.Stopped,volume:this.config.defaultVolume??1,currentLoopCount:0,originalVolume:this.config.defaultVolume??1,playOptions:{startTime:this.config.defaultStartTime??0,loop:this.config.loopSounds??!1,maxLoops:this.config.maxLoops??-1,playbackRate:this.config.defaultPlaybackRate??1,pan:this.config.defaultPan??0,volume:this.config.defaultVolume??1,trackProgress:this.config.trackProgress??!1},panSpatialPosition:this.config.defaultPanSpatialPosition??{x:0,y:0,z:0},pan:this.config.defaultPan??0,panType:this.config.defaultPanType??S.Stereo};this.sounds.set(t,o),this.dispatchEvent({type:h.LOADED,soundId:t,timestamp:this.context.currentTime,sound:o,duration:e.duration,bufferSize:a,fileSize:i,sampleRate:e.sampleRate,channels:e.numberOfChannels})}async loadSound(t,e,i){try{await this.loadSounds([{id:t,url:e}],i)}catch(n){throw n instanceof DOMException&&n.name==="AbortError"||this.handleError("loading sound",n,t),n}}async updateSoundUrl(t,e){try{const i=this.sounds.get(t);if(!i){this.debugLog(`Sound ${t} not found for URL update`);return}this.cleanupSound(t),await this.loadSound(t,e),this.dispatchEvent({type:h.UPDATED_URL,soundId:t,timestamp:this.context.currentTime,sound:i}),this.debugLog(`Sound ${t} URL updated to ${e}`)}catch(i){this.handleError("updating sound URL",i,t)}}unloadSound(t){const e=this.sounds.get(t);if(!e){this.debugLog(`Sound ${t} not found for unloading`);return}this.stop(t,!1),this.cleanupSound(t),this.resetCounterForSound(t),this.dispatchEvent({type:h.UNLOADED,soundId:t,timestamp:this.context.currentTime,sound:e}),this.debugLog(`Sound ${t} unloaded`)}removeSound(t){try{if(!this.sounds.get(t))return;this.unloadSound(t),this.sounds.delete(t),this.debugLog(`Removed sound ${t}`)}catch(e){this.handleError("removing sound",e,t)}}isSoundLoaded(t){const e=this.sounds.get(t);return(e==null?void 0:e.buffer)!=null}createSoundGroup(t,e={}){if(this.soundGroups.has(t)){this.debugLog(`Group with id ${t} already exists.`);return}this.soundGroups.set(t,{id:t,sounds:new Set,maxInstances:e.maxInstances,playOptions:e.playOptions}),this.debugLog(`Created group ${t} with options:`,e)}addToSoundGroup(t,e){const i=this.soundGroups.get(t);if(!i){this.debugLog(`Group ${t} not found.`);return}if(i.maxInstances&&i.sounds.size>=i.maxInstances){const s=Array.from(i.sounds)[0];this.stop(s),i.sounds.delete(s),this.debugLog(`Stopped oldest instance ${s} to make room for new instance in group ${t}.`)}const n=this.sounds.get(e);n&&(n.groupId=t),n&&i.playOptions&&(n.playOptions={...i.playOptions,...n.playOptions}),i.sounds.add(e),this.debugLog(`Added sound ${e} to group ${t}.`)}removeFromSoundGroup(t,e){const i=this.soundGroups.get(t);if(!i){this.debugLog(`Group ${t} not found.`);return}i.sounds.delete(e),this.debugLog(`Removed sound ${e} from group ${t}.`)}getGroup(t){return this.soundGroups.get(t)}removeSoundGroup(t){const e=this.soundGroups.get(t);if(!e){this.debugLog(`Group ${t} not found.`);return}e.sounds.forEach(i=>{this.stop(i),this.sounds.delete(i)}),e.sounds.clear(),this.soundGroups.delete(t),this.debugLog(`Cleaned up group ${t}.`)}setSoundSprite(t,e){try{const i=this.getValidatedSound(t);if(!i||!i.buffer)throw new Error(`Sound ${t} not found or buffer not loaded`);Object.entries(e).forEach(([n,[s,a]])=>{const o=`${t}_${n}`;this.debugLog(`Creating sprite ${o} for sound ${t}: Start=${s}ms, End=${a}ms, Duration=${a-s}ms`);const r=i.buffer.sampleRate,c=Math.floor(s*r),p=Math.floor(a*r),l=a-s,m=i.buffer.numberOfChannels,f=this.context.createBuffer(m,p-c,r);for(let E=0;E<m;E++){const _=i.buffer.getChannelData(E),D=f.getChannelData(E);for(let N=0;N<D.length;N++)D[N]=_[N+c]}const P=this.context.createGain(),T=i.volume??this.config.defaultVolume??1;P.gain.value=T,P.connect(this.masterGainNode);const O={id:o,buffer:f,currentTime:0,source:this.context.createBufferSource(),originalVolume:T,state:g.Stopped,gainNode:P,playOptions:{...i.playOptions},currentLoopCount:0,panSpatialPosition:this.config.defaultPanSpatialPosition||{x:0,y:0,z:0},pan:i.pan??this.config.defaultPan??0};if(i.stereoPanner){const E=this.context.createStereoPanner();E.connect(P),O.stereoPanner=E}this.sounds.set(o,O),this.debugLog(`Created sprite sound ${o}: Duration: ${l}s Sample rate: ${r} Channels: ${m} Buffer length: ${f.length} samples `),this.dispatchEvent({type:h.SPRITE_SET,soundId:o,timestamp:this.context.currentTime,sound:O})})}catch(i){this.handleError("setting sound sprite",i,t)}}getSpriteConfig(t){try{const e=this.getValidatedSound(t);return e==null?void 0:e.sprite}catch(e){this.handleError("getting sprite config",e,t);return}}removeSpriteSound(t){try{const e=Array.from(this.sounds.keys()).filter(i=>i.includes(`_${t}`));if(e.length===0){this.debugLog(`No sprite instances found for key: ${t}`);return}e.forEach(i=>{this.stop(i),this.cleanupSound(i),this.sounds.delete(i),this.debugLog(`Removed sprite instance: ${i}`)}),this.debugLog(`All instances of sprite ${t} removed`)}catch(e){this.handleError("removing sprite sound",e,t)}}removeSpriteConfig(t){try{const e=this.getValidatedSound(t);e&&(e.sprite=void 0,this.debugLog(`Removed sprite config for sound ${t}`))}catch(e){this.handleError("removing sprite config",e,t)}}async suspendContext(){try{await this.context.suspend(),this.debugLog("Audio context suspended")}catch(t){this.handleError("suspending context",t)}}async resumeContext(){try{await this.context.resume(),this.debugLog("Audio context resumed")}catch(t){this.handleError("resuming context",t)}}getContext(){return this.context}getMasterOutput(){return this.masterStereoPanner}isPlaying(t){return this.getValidatedSound(t).state===g.Playing}isPaused(t){try{return this.getValidatedSound(t).state==g.Paused}catch{return!1}}isStopped(t){try{return this.getValidatedSound(t).state===g.Stopped}catch{return!0}}getSoundState(t){var l,m,f,P,T,O,E;const e=this.getValidatedSound(t);e||this.debugLog("sound not found in getSoundState",e);const i=((l=e.playOptions)==null?void 0:l.playbackRate)??1,n=((m=e.buffer)==null?void 0:m.duration)??0,s=n/i;let a=0,o=0;e.state===g.Playing&&e.startTime!==void 0?(o=(this.context.currentTime-e.startTime)*i,a=o,(f=e.playOptions)!=null&&f.loop&&(a=a%n)):(a=e.pausedAt||e.currentTime||0,o=a);const r=n>0?a/n:0,c=o/i,p=a/i;return this.debugLog(`Sound state for ${t}: State: ${e.state} Progress: ${r} Initial offset: ${((P=e.playOptions)==null?void 0:P.startTime)||0}ms Sound currentTime: ${e.currentTime}s Elapsed time: ${e.state===g.Playing?o:0}s Current time: ${a}s Raw Duration: ${n}s Duration playOptions: ${((T=e.playOptions)==null?void 0:T.duration)||0}s Adjusted Duration: ${this.roundValue(o,4)}s Playback Rate: ${i} Volume: ${e.volume} Pan: ${e.pan}, Pan Spatial Position: ${JSON.stringify(e.panSpatialPosition)} `),{progress:this.roundValue(r,4),startTime:e.startTime||0,currentTime:this.roundValue(p,4),elapsedTime:this.roundValue(c,4),adjustedElapsedTime:this.roundValue(o,4),duration:this.roundValue(s,4),rawDuration:this.roundValue(n,4),state:e.state||g.Stopped,volume:e.volume??((O=e==null?void 0:e.playOptions)==null?void 0:O.volume)??this.config.defaultVolume??1,playbackRate:i,pan:e.pan??((E=e==null?void 0:e.playOptions)==null?void 0:E.pan)??0,panSpatialPosition:(e==null?void 0:e.panSpatialPosition)||{x:0,y:0,z:0}}}getCurrentTime(t){const{currentTime:e}=this.getSoundState(t);return e}getDuration(t){var e;try{const i=this.getValidatedSound(t);return((e=i==null?void 0:i.buffer)==null?void 0:e.duration)||0}catch(i){return this.handleError("getting duration",i,t),0}}getProgress(t){const{currentTime:e,duration:i}=this.getSoundState(t);return i?e/i:0}getProgressPercentage(t){return this.getProgress(t)*100}startProgressTracking(t){this.stopProgressTracking(t);const e=t.includes(":")?t.split(":")[0]:t,i=()=>{var f,P;const n=this.sounds.get(t);if(!n||n.state!==g.Playing){this.stopProgressTracking(t);return}const s=this.getSoundState(t),{currentTime:a,duration:o,rawDuration:r,elapsedTime:c,adjustedElapsedTime:p,playbackRate:l}=s,m=o?c/o:0;if(((f=n.playOptions)==null?void 0:f.duration)!==void 0&&n.playOptions.duration>0&&p>=(n.playOptions.duration+(n.playOptions.startTime??0))/(l||1)){n.playOptions.pauseAtDurationReached&&!n.playOptions.loop?this.pause(t):(P=n.playOptions)!=null&&P.loop?this.handleLoopIteration(n):this.handleSoundEnded(n);return}this.dispatchEvent({type:h.PROGRESS,soundId:t,originalId:e,instanceId:t,currentTime:a,duration:o||0,progress:m,progressInfo:{soundId:t,currentTime:a,duration:o||0,rawDuration:r||0,progress:m},state:s,timestamp:this.context.currentTime,volume:n.volume,sound:n})};this.ticker.addCallback(`progress_${t}`,i,this.PROGRESS_UPDATE_INTERVAL)}stopProgressTracking(t){this.ticker.removeCallback(`progress_${t}`)}setProgressUpdateInterval(t){this.PROGRESS_UPDATE_INTERVAL=t}setPan(t,e,i=!1){var n;try{const s=this.getValidatedSound(t),a=Math.max(-1,Math.min(1,e));if(this.isSpatialAudioActive(t)){this.debugLog(`Removed 3D spatial audio, and overwritten with stereo panner for sound ${t}`),this.removeSpatialEffect(t),s.panType=S.Stereo;const o={x:0,y:0,z:0};s.panSpatialPosition=o,s.playOptions&&(s.playOptions.panSpatialPosition=o)}s.stereoPanner||(s.stereoPanner=this.context.createStereoPanner()),s.pan=a,s.playOptions&&(s.playOptions.pan=a),s.panType=S.Stereo,s.stereoPanner&&s.stereoPanner.pan.setValueAtTime(s.pan,this.context.currentTime),this.audioNodeConnector.connectNodes(s,this.masterGainNode),i||this.dispatchEvent({type:h.PAN_CHANGED,soundId:t,timestamp:((n=this.context)==null?void 0:n.currentTime)??0,pan:s.pan||0,previousPan:this.previousGlobalPan,sound:s}),this.debugLog(`Pan set for sound ${s.id}: ${s.pan}`)}catch(s){this.handleError("setting pan",s,t)}}resetPan(t){if(!t){this.resetGlobalPan();return}const e=this.getValidatedSound(t);this.setPan(t,0,!0),this.dispatchEvent({type:h.PAN_RESET,soundId:t,timestamp:this.context.currentTime,isMaster:!1,sound:e})}removePan(t){try{const e=this.getValidatedSound(t);if(e.stereoPanner){const i=e.source;i&&(i.disconnect(),i.connect(e.gainNode)),e.stereoPanner.disconnect(),e.stereoPanner=void 0,e.pan=0}}catch(e){this.handleError("removing pan",e,t)}}setGlobalPan(t){var e;try{this.previousGlobalPan=this.masterStereoPanner.pan.value,this.sounds.forEach((n,s)=>{this.isSpatialAudioActive(s)&&this.removeSpatialEffect(s),n.stereoPanner&&(n.stereoPanner.pan.setValueAtTime(t,this.context.currentTime),n.pan=t)});const i=Math.max(-1,Math.min(1,t));this.masterStereoPanner.pan.setValueAtTime(i,this.context.currentTime),this.dispatchEvent({type:h.MASTER_PAN_CHANGED,timestamp:((e=this.context)==null?void 0:e.currentTime)??0,pan:i,previousPan:this.previousGlobalPan,isMaster:!0}),this.debugLog(`Master pan set to: ${i}`)}catch(i){this.handleError("setting master pan",i)}}getGlobalPan(){return this.masterStereoPanner.pan.value}resetGlobalPan(){this.setGlobalPan(0),this.dispatchEvent({type:h.PAN_RESET,timestamp:this.context.currentTime,isMaster:!0})}getPreviousMasterPan(){return this.previousGlobalPan}cleanupGlobalPan(){this.masterPannerNode&&(this.masterPannerNode.disconnect(),this.masterGainNode.disconnect(),this.masterGainNode.connect(this.context.destination),this.masterPannerNode=null)}isStereoPanActive(t){const e=this.sounds.get(t);return!!(e!=null&&e.stereoPanner)}isSpatialAudioEnabled(){return this.config.spatialAudio===!0&&this.isSpatialAudioSupported()}isSpatialAudioSupported(){if(this._spatialAudioSupported!==null)return this._spatialAudioSupported;try{if(!("AudioContext"in window||"webkitAudioContext"in window))return this._spatialAudioSupported=!1;if(!("PannerNode"in window))return this._spatialAudioSupported=!1;const t=new(window.AudioContext||window.webkitAudioContext),e="positionX"in t.listener&&"positionY"in t.listener&&"positionZ"in t.listener;return t.close(),this._spatialAudioSupported=e}catch(t){return this.debugLog("Spatial audio support check failed:",t),this._spatialAudioSupported=!1}}setSpatialPosition(t,e,i,n,s,a=!1){if(!this.isSpatialAudioEnabled()){this.debugLog("Spatial audio is not enabled or supported");return}if(t=this.roundValue(t,2),e=this.roundValue(e,2),i=this.roundValue(i,2),!n){this.debugLog(`Sound ${n} not found, global sound position will be used`),this.setMasterSpatialPosition(t,e,i,s);return}const o=this.sounds.get(n);if(!o){this.debugLog(`Sound ${n} not found for position setting`);return}const r=o.source;o.stereoPanner&&(this.removePan(n),this.debugLog(`Removed stereo panner, and overwritten with spatial panning for sound ${n}`)),o.panType=S.Spatial,o.playOptions?o.playOptions.panSpatialPosition={x:t,y:e,z:i}:o.playOptions={panSpatialPosition:{x:t,y:e,z:i}};try{if(o.pannerNode)r==null||r.disconnect(),r==null||r.connect(o.pannerNode),o.pannerNode.connect(o.gainNode),s&&Object.keys(s).length!==0&&Object.entries(s).forEach(([c,p])=>{p!==void 0&&(o.pannerNode[c]=p)});else{const c={...v,...this.config.pannerNodeConfig||{},...s||{}};o.pannerNode=this.context.createPanner(),o.pannerNode.panningModel=c.panningModel,o.pannerNode.distanceModel=c.distanceModel,o.pannerNode.refDistance=c.refDistance,o.pannerNode.maxDistance=c.maxDistance,o.pannerNode.rolloffFactor=c.rolloffFactor,o.pannerNode.coneInnerAngle=c.coneInnerAngle,o.pannerNode.coneOuterAngle=c.coneOuterAngle,o.pannerNode.coneOuterGain=c.coneOuterGain,r==null||r.disconnect(),r==null||r.connect(o.pannerNode),o.pannerNode.connect(o.gainNode)}o.pannerNode.positionX.setValueAtTime(t,this.context.currentTime),o.pannerNode.positionY.setValueAtTime(e,this.context.currentTime),o.pannerNode.positionZ.setValueAtTime(i,this.context.currentTime),o.panSpatialPosition={x:t,y:e,z:i},a||this.dispatchEvent({type:h.SPATIAL_POSITION_CHANGED,soundId:n,timestamp:this.context.currentTime,position:o.panSpatialPosition,pannerConfig:s,sound:o}),this.debugLog(`Set position for sound ${n}: x=${t}, y=${e}, z=${i}`)}catch(c){this.handleError("setting sound position",c)}}getSpatialPosition(t){var e;return((e=this.sounds.get(t))==null?void 0:e.panSpatialPosition)??null}setMasterSpatialPosition(t,e,i,n={},s=!1){try{this.masterPannerNode||(this.masterPannerNode=this.context.createPanner(),this.masterGainNode.disconnect(),this.masterPannerNode.connect(this.context.destination),this.masterGainNode.connect(this.masterPannerNode)),this.masterPannerNode.positionX.setValueAtTime(t,this.context.currentTime),this.masterPannerNode.positionY.setValueAtTime(e,this.context.currentTime),this.masterPannerNode.positionZ.setValueAtTime(i,this.context.currentTime),this.masterSpatialPosition={x:t,y:e,z:i},n.coneInnerAngle!==void 0&&(this.masterPannerNode.coneInnerAngle=n.coneInnerAngle),n.coneOuterAngle!==void 0&&(this.masterPannerNode.coneOuterAngle=n.coneOuterAngle),n.coneOuterGain!==void 0&&(this.masterPannerNode.coneOuterGain=n.coneOuterGain),n.distanceModel!==void 0&&(this.masterPannerNode.distanceModel=n.distanceModel),n.maxDistance!==void 0&&(this.masterPannerNode.maxDistance=n.maxDistance),n.panningModel!==void 0&&(this.masterPannerNode.panningModel=n.panningModel),n.refDistance!==void 0&&(this.masterPannerNode.refDistance=n.refDistance),n.rolloffFactor!==void 0&&(this.masterPannerNode.rolloffFactor=n.rolloffFactor),s||this.dispatchEvent({type:h.GLOBAL_SPATIAL_POSITION_CHANGED,timestamp:this.context.currentTime,position:{x:t,y:e,z:i}}),this.debugLog(`Set master spatial position: x=${t}, y=${e}, z=${i}`)}catch(a){this.handleError("setting master spatial position",a)}}getMasterSpatialPosition(){return this.masterSpatialPosition}removeSpatialEffect(t){try{const e=this.getValidatedSound(t);if(e.pannerNode){const i=e.source;i&&(i.disconnect(),i.connect(e.gainNode)),e.pannerNode.disconnect(),e.pannerNode=null}e.panSpatialPosition={x:0,y:0,z:0},e.gainNode.disconnect(),e.gainNode.connect(this.masterGainNode)}catch(e){this.handleError("removing spatial effect",e,t)}}isSpatialAudioActive(t){const e=this.sounds.get(t);return!!(e!=null&&e.pannerNode)}updatePannerConfigById(t,e){const i=this.sounds.get(t);if(!(i!=null&&i.pannerNode)){this.debugLog(`No panner node found for sound ${t}`);return}try{Object.entries(e).forEach(([n,s])=>{s!==void 0&&(i.pannerNode[n]=s)}),this.debugLog(`Updated panner config for sound ${t}`,e)}catch(n){this.handleError("updating panner configuration",n)}}resetSpatialPosition(t){if(!this.config.spatialAudio){this.debugLog("Spatial audio is not enabled");return}if(!t){this.debugLog("Resetting master spatial position"),this.setMasterSpatialPosition(0,0,0),this.dispatchEvent({type:h.SPATIAL_POSITION_RESET,timestamp