sound-manager-ts
Version:
A lightweight, TypeScript-based Web Audio API manager for seamless sound control in web apps and games
32 lines • 55 kB
JavaScript
(function(P,m){typeof exports=="object"&&typeof module<"u"?m(exports):typeof define=="function"&&define.amd?define(["exports"],m):(P=typeof globalThis<"u"?globalThis:P||self,m(P.SoundManagerTS={}))})(this,function(P){"use strict";var q=Object.defineProperty;var X=(P,m,k)=>m in P?q(P,m,{enumerable:!0,configurable:!0,writable:!0,value:k}):P[m]=k;var S=(P,m,k)=>X(P,typeof m!="symbol"?m+"":m,k);var m=(r=>(r.Stereo="stereo",r.Spatial="spatial",r))(m||{});class k{connectNodes(t,e){t.source&&(this.disconnectNodes(t),t.panType===m.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 l=(r=>(r.ENDED="ended",r.ERROR="error",r.FADE_IN_COMPLETED="fade_in_completed",r.FADE_MASTER_IN_COMPLETED="fade_master_in_completed",r.FADE_MASTER_OUT_COMPLETED="fade_master_out_completed",r.FADE_OUT_COMPLETED="fade_out_completed",r.GLOBAL_SPATIAL_POSITION_CHANGED="global_spatial_position_changed",r.LOADED="loaded",r.LOOP_COMPLETED="loop_completed",r.MASTER_PAN_CHANGED="master_pan_changed",r.MASTER_VOLUME_CHANGED="master_volume_changed",r.MUTE_GLOBAL="mute_global",r.MUTED="muted",r.OPTIONS_UPDATED="options_updated",r.PAN_CHANGED="pan_changed",r.PAN_RESET="pan_reset",r.PAUSED="paused",r.PLAYBACK_RATE_CHANGED="playback_rate_changed",r.PROGRESS="progress",r.RESET="reset",r.RESUMED="resumed",r.SEEKED="seeked",r.SPATIAL_POSITION_CHANGED="spatial_position_changed",r.SPATIAL_POSITION_RESET="spatial_position_reset",r.SPRITE_SET="sprite_set",r.STARTED="started",r.STOPPED="stopped",r.UNLOADED="unloaded",r.UNMUTE_GLOBAL="unmute_global",r.UNMUTED="unmuted",r.UPDATED_URL="updated_url",r.VOLUME_CHANGED="volume_changed",r))(l||{}),F=(r=>(r.HRTF="HRTF",r.EqualPower="equalpower",r))(F||{}),U=(r=>(r.Linear="linear",r.Inverse="inverse",r.Exponential="exponential",r))(U||{});const A={panningModel:"HRTF",distanceModel:"inverse",refDistance:1,maxDistance:1e4,rolloffFactor:.2,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:m.Stereo,defaultPlaybackRate:1,defaultStartTime:0,defaultVolume:1,fadeInDuration:.5,fadeOutDuration:.5,loopSounds:!1,maxLoops:-1,pannerNodeConfig:A,spatialAudio:!0,trackProgress:!0};var y=(r=>(r.Playing="playing",r.Paused="paused",r.Stopped="stopped",r))(y||{});class j{constructor(){S(this,"callbacks",new Map);S(this,"animationFrameId",null);S(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={}){S(this,"config");S(this,"context");S(this,"sounds",new Map);S(this,"masterGainNode");S(this,"masterStereoPanner");S(this,"masterPannerNode");S(this,"previousGlobalVolume",1);S(this,"isMuted",!1);S(this,"previousGlobalPan",0);S(this,"PROGRESS_UPDATE_INTERVAL",50);S(this,"eventListeners",new Map);S(this,"activeSources",new Map);S(this,"activeFadeCallbacks",new Map);S(this,"isHandlingError",!1);S(this,"audioNodeConnector",new k);S(this,"ticker");S(this,"lastError",null);S(this,"masterSpatialPosition",{x:0,y:0,z:0});S(this,"DEFAULT_PRECISION",2);S(this,"soundGroups",new Map);S(this,"instanceCounters",new Map);S(this,"unlockHandlers",null);this.ticker=new j,this.config={debug:!1,...t},Object.values(l).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.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!==m.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===y.Playing&&((o=t.playOptions)!=null&&o.loop?this.handleLoopIteration(t):this.handleSoundEnded(t))},i}handleLoopIteration(t){var i,n,s,a,o,c;if(this.debugLog(`Restarting loop for sound ${t.id}`),((i=t.playOptions)==null?void 0:i.maxLoops)!==void 0&&((n=t.playOptions)==null?void 0:n.maxLoops)>0&&(t.currentLoopCount??0)>=((s=t.playOptions)==null?void 0:s.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=(((a=t.playOptions)==null?void 0:a.startTime)??0)/(((o=t.playOptions)==null?void 0:o.playbackRate)??1);(c=t.playOptions)!=null&&c.createNewInstance&&(t.playOptions.createNewInstance=!1),this.seek(t.id,e,!0),this.dispatchEvent({type:l.LOOP_COMPLETED,soundId:t.id,timestamp:this.context.currentTime,sound:t})}handleSoundEnded(t){var e,i,n,s,a,o;if(t.state!==y.Stopped){if((e=t.playOptions)!=null&&e.pauseAtDurationReached&&((i=t.playOptions)==null?void 0:i.duration)!==void 0&&t.playOptions.duration>0){this.pause(t.id),t.startTime=void 0,t.pausedAt=((n=t.playOptions)==null?void 0:n.startTime)??0,t.currentTime=0;return}t.state=y.Stopped,t.startTime=void 0,t.pausedAt=((s=t.playOptions)==null?void 0:s.startTime)??0,t.currentTime=0,(a=t.playOptions)!=null&&a.createNewInstance?this.cleanupExistingSource(t.id):this.cleanupSound(t.id),this.stopProgressTracking(t.id),(o=t.playOptions)!=null&&o.createNewInstance&&this.removeEventListenersForInstance(t.id),this.dispatchEvent({type:l.ENDED,soundId: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:l.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===y.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,c,d,h,p,f,g,T,b,O,E,w,D,x;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,R,N=e.groupId||L.groupId;if(N){let v=this.soundGroups.get(N);if(!v){this.debugLog(`Group ${N} not found.`);return}if(this.debugLog(`Group ${N} has ${v.sounds.size} instances. Max instances: ${v.maxInstances}`),v.maxInstances&&v.sounds.size>=v.maxInstances){const V=Array.from(v.sounds)[0];this.debugLog(`Max instances reached. Stopping oldest instance: ${V}`),this.stop(V),v.sounds.delete(V),this.debugLog(`Stopped and removed oldest instance ${V} from group ${N}.`)}}if(Y){const v=t.split(":")[0],V=this.getInstanceCounter(v);$=`${v}:${V}`,this.debugLog(`Creating new instance with ID: ${$}`);const I=JSON.parse(JSON.stringify({...L.playOptions,...e,createNewInstance:!1})),_=this.context.createGain(),M=e.volume??L.volume??this.config.defaultVolume??1;_.gain.value=M,R={...L,id:$,gainNode:_,state:y.Stopped,currentTime:0,startTime:(I==null?void 0:I.startTime)??0,pausedAt:0,currentLoopCount:0,playOptions:I,volume:M,originalVolume:M,pan:0,panSpatialPosition:{x:0,y:0,z:0},panType:I.panType??m.Stereo,source:null,stereoPanner:null,pannerNode:null},this.sounds.set($,R),N&&(this.addToSoundGroup(N,$),this.debugLog(`Added new instance ${$} to group ${N}.`))}const u=R||L;if(!u){this.debugLog(`Failed to create sound instance for ${t}`);return}R===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 G=0;if(u.pausedAt!==void 0&&u.pausedAt!==0?G=u.pausedAt:((s=u.playOptions)==null?void 0:s.startTime)!==void 0&&(G=u.playOptions.startTime),u.startTime=this.context.currentTime-G/C,this.reconnectAudioNodes(u.id),u.state=y.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!==m.Spatial&&this.setPan(u.id,u.playOptions.pan,!0),((c=u.playOptions)==null?void 0:c.panSpatialPosition)!==void 0&&u.panType===m.Spatial&&this.setSpatialPosition(u.playOptions.panSpatialPosition.x,u.playOptions.panSpatialPosition.y,u.playOptions.panSpatialPosition.z,u.id,void 0,!0),((d=u.playOptions)==null?void 0:d.fadeInDuration)!==void 0&&this.fadeIn(u.id,((h=u.playOptions)==null?void 0:h.fadeInDuration)??((p=this.config)==null?void 0:p.fadeInDuration)??1,void 0,(f=u.playOptions)==null?void 0:f.volume),((g=u.playOptions)==null?void 0:g.fadeOutDuration)!==void 0&&this.fadeOut(u.id,u.playOptions.fadeOutDuration??((T=this.config)==null?void 0:T.fadeOutDuration)??1),((b=u.playOptions)==null?void 0:b.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 v=u.playOptions.fadeOutBeforeEndDuration,_=(u.playOptions.duration??((w=u.buffer)==null?void 0:w.duration)??0)-(u.pausedAt||0)-v;_>0&&this.scheduleFadeOut(u.id,_,u.playOptions.fadeOutBeforeEndDuration??1)}return B.start(0,G,((D=u.playOptions)==null?void 0:D.duration)!==void 0&&u.playOptions.duration>0?u.playOptions.duration*C:void 0),((x=u.playOptions)==null?void 0:x.trackProgress)===!0&&this.startProgressTracking(u.id),i||this.dispatchEvent({type:l.STARTED,soundId:t,timestamp:this.context.currentTime,sound:u}),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=y.Paused,this.stopProgressTracking(t),this.cleanupExistingSource(t),e||this.dispatchEvent({type:l.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:l.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=y.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:l.STOPPED,soundId:t,timestamp:this.context.currentTime,sound:n})}catch(n){console.error(`Error stopping sound ${t}:`,n),this.dispatchEvent({type:l.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:c,currentTime:d}=this.getSoundState(t);if(e>=c){if(o.state===y.Stopped)return;o.state===y.Playing&&((n=o.playOptions)!=null&&n.loop)?this.handleLoopIteration(o):this.handleSoundEnded(o);return}const h=((s=o.buffer)==null?void 0:s.duration)||0,p=((a=o.playOptions)==null?void 0:a.playbackRate)||1,f=e*p,g=Math.max(0,Math.min(f,h));if(o.currentTime=g,o.pausedAt=g,o.state===y.Playing&&(this.cleanupExistingSource(t),o.startTime=this.context.currentTime-g/p,this.play(t,o.playOptions)),i)return;this.dispatchEvent({type:l.SEEKED,soundId:t,currentTime:d,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===y.Playing&&this.pause(e)})}resumeAllSounds(){this.sounds.forEach((t,e)=>{t.state==y.Paused&&this.resume(e)})}fadeIn(t,e,i,n,s=!1){var h,p;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 c=n??a.volume??((h=a.playOptions)==null?void 0:h.volume)??this.config.defaultVolume??1;c===0&&(c=1);let d;i!==void 0?d=i:a.isFadingOut?d=o:o>=c?d=((p=a.playOptions)==null?void 0:p.fadeInStartVolume)??0:d=o,a.gainNode.gain.cancelScheduledValues(this.context.currentTime),a.gainNode.gain.setValueAtTime(d,this.context.currentTime),a.state!==y.Playing&&this.play(t,{volume:d}),this.fadeSound(t,d,c,e,()=>{a.volume=c,a.playOptions&&(a.playOptions.volume=c),s||this.dispatchEvent({type:l.FADE_IN_COMPLETED,soundId:t,timestamp:this.context.currentTime,sound:a})})}fadeOut(t,e=this.config.fadeOutDuration,i,n,s=!1,a=!1){var p;const o=this.getValidatedSound(t);this.cancelFadeAnimation(t),o.isFadingIn=!1,o.isFadingOut=!0;const c=this.roundValue(o.gainNode.gain.value,2),d=i??c,h=n??((p=o.playOptions)==null?void 0:p.fadeOutEndVolume)??0;o.previousVolume=c,o.gainNode.gain.setValueAtTime(d,this.context.currentTime),o.state!==y.Playing&&this.play(t,{volume:d}),this.fadeSound(t,d,h,e,()=>{a||this.dispatchEvent({type:l.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),c=this.context.currentTime,d=c+o,h=()=>{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:l.VOLUME_CHANGED,soundId:t,timestamp:this.context.currentTime,volume:a.volume,sound:a}),this.activeFadeCallbacks.delete(t)};this.activeFadeCallbacks.set(t,h);const p=`fade_${t}`,f=()=>{const g=this.context.currentTime;if(g>=d){this.ticker.removeCallback(p),h();return}const T=(g-c)/o,b=e+(i-e)*T;a.gainNode.gain.setValueAtTime(b,g),a.volume=this.roundValue(b),a.playOptions={...a.playOptions,volume:a.volume},this.dispatchEvent({type:l.VOLUME_CHANGED,soundId:t,timestamp:g,volume:a.volume,sound:a})};this.ticker.addCallback(p,f),this.debugLog(`Fade scheduled for sound ${t}:
Start time: ${c}
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,c=a+o,d="fade_global_in",h=()=>{const p=this.context.currentTime;if(p>=c){this.masterGainNode.gain.setValueAtTime(s,p),this.isMuted=!1,this.dispatchEvent({type:l.FADE_MASTER_IN_COMPLETED,timestamp:p,volume:s}),this.ticker.removeCallback(d);return}const f=(p-a)/o,g=n+(s-n)*f;this.masterGainNode.gain.setValueAtTime(g,p),this.dispatchEvent({type:l.MASTER_VOLUME_CHANGED,timestamp:p,volume:g,isMaster:!0})};this.ticker.addCallback(d,h)}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,c="fade_global_out",d=()=>{const h=this.context.currentTime;if(h>=o){this.masterGainNode.gain.setValueAtTime(i,h),this.isMuted=i===0,this.dispatchEvent({type:l.FADE_MASTER_OUT_COMPLETED,timestamp:h,volume:i}),this.ticker.removeCallback(c);return}const p=(h-s)/a,f=n+(i-n)*p;this.masterGainNode.gain.setValueAtTime(f,h),this.dispatchEvent({type:l.MASTER_VOLUME_CHANGED,timestamp:h,volume:f,isMaster:!0})};this.ticker.addCallback(c,d)}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:l.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:l.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:l.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:l.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:l.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:l.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){const i=this.config.fetchStrategy==="proxy-first"&&this.shouldUseProxy(e)?["proxy","direct"]:["direct"];for(const n of i)try{const s=n==="proxy"||this.config.corsProxy?this.getProxyUrl(e):e;this.debugLog(`Trying ${n} strategy for ${t}`,{originalUrl:e,fetchUrl:s});const a=await this.fetchWithRetry(s,{mode:"cors",credentials:"omit",cache:this.config.audioCache?"default":"no-cache",headers:{Accept:"audio/mpeg, audio/*"}});return await this.processAudioResponse(t,a)}catch(s){if(this.debugLog(`${n} strategy failed for ${t}`,{error:s instanceof Error?s.message:String(s),url:e}),n===i[i.length-1])throw new Error(`Failed to load sound ${t}: ${s instanceof Error?s.message:String(s)}`)}}async fetchWithRetry(t,e){const{fetchRetries:i=2,retryDelay:n=.5,fetchTimeout:s=10}=this.config;let a=null;const o=this.config.credentialStrategy==="auto"?this.config.crossOrigin==="use-credentials"?["include","omit"]:["omit"]:[this.config.credentialStrategy||"omit"];for(const c of o)for(let d=0;d<=i;d++){const h=Date.now(),p=new AbortController,f=setTimeout(()=>p.abort(),s*1e3);try{const g={...e,credentials:c,signal:p.signal};this.debugLog(`Attempt ${d+1} with credentials=${c}`);const T=await fetch(t,g);if(clearTimeout(f),!T.ok)throw new Error(`HTTP ${T.status}`);if(e.mode==="cors"){const b=T.headers.get("access-control-allow-origin");if(c==="include"&&b==="*")throw new Error("Invalid CORS: Credentialed request with wildcard origin")}return this.debugLog(`Success after ${Date.now()-h}ms`),T}catch(g){clearTimeout(f),a=g instanceof Error?g:new Error(String(g)),this.debugLog(`Attempt ${d+1} failed: ${a.message}`)}d<i&&await new Promise(g=>setTimeout(g,n*1e3))}throw a||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){if(!this.config.html5AudioFallback)throw new Error("HTML5 Audio fallback is disabled in configuration");return new Promise((i,n)=>{const s=new Audio;s.crossOrigin=this.config.crossOrigin==="use-credentials"?"use-credentials":"anonymous",s.preload="auto",s.src=e;const a=()=>{s.oncanplaythrough=null,s.onerror=null,s.removeEventListener("error",o)},o=()=>{a(),n(new Error(`HTML5 Audio load error: ${s.error?s.error.message:"unknown"}`))};s.oncanplaythrough=async()=>{a();try{await this.loadWithWebAudio(t,e),this.debugLog(`Sound ${t} loaded with HTML5 Audio fallback`),i()}catch(c){n(c)}},s.onerror=o,s.addEventListener("error",o),s.load()})}async loadSounds(t){if(t.length)try{const{maxParallelLoads:e=10,webAudioPreferred:i=!0}=this.config,n=Math.max(1,e),s=[];for(let a=0;a<t.length;a+=n)s.push(t.slice(a,a+n));for(const a of s){const o=a.map(async({id:h,url:p})=>{if(this.sounds.has(h)){this.debugLog(`Sound with id ${h} already exists. Skipping.`);return}try{if(i)try{await this.loadWithWebAudio(h,p);return}catch(f){this.debugLog(`Web Audio load failed for ${h}`,f)}await this.loadWithHtml5Audio(h,p)}catch(f){throw this.handleError("loading sound",f,h),f}}),c=await Promise.allSettled(o);if(c.filter(h=>h.status==="rejected").length){const h=a.filter((p,f)=>c[f].status==="rejected").map(p=>p.id);throw new Error(`Failed to load sounds: ${h.join(", ")}`)}}}catch(e){throw this.handleError("preloading sounds",e),e}}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:y.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??m.Stereo};this.sounds.set(t,o),this.dispatchEvent({type:l.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){try{await this.loadSounds([{id:t,url:e}])}catch(i){throw this.handleError("loading sound",i,t),i}}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:l.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:l.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 c=i.buffer.sampleRate,d=Math.floor(s*c),h=Math.floor(a*c),p=a-s,f=i.buffer.numberOfChannels,g=this.context.createBuffer(f,h-d,c);for(let E=0;E<f;E++){const w=i.buffer.getChannelData(E),D=g.getChannelData(E);for(let x=0;x<D.length;x++)D[x]=w[x+d]}const T=this.context.createGain(),b=i.volume??this.config.defaultVolume??1;T.gain.value=b,T.connect(this.masterGainNode);const O={id:o,buffer:g,currentTime:0,source:this.context.createBufferSource(),originalVolume:b,state:y.Stopped,gainNode:T,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(T),O.stereoPanner=E}this.sounds.set(o,O),this.debugLog(`Created sprite sound ${o}:
Duration: ${p}s
Sample rate: ${c}
Channels: ${f}
Buffer length: ${g.length} samples
`),this.dispatchEvent({type:l.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}isPlaying(t){return this.getValidatedSound(t).state===y.Playing}isPaused(t){try{return this.getValidatedSound(t).state==y.Paused}catch{return!1}}isStopped(t){try{return this.getValidatedSound(t).state===y.Stopped}catch{return!0}}getSoundState(t){var p,f,g,T,b,O,E;const e=this.getValidatedSound(t);e||this.debugLog("sound not found in getSoundState",e);const i=((p=e.playOptions)==null?void 0:p.playbackRate)??1,n=((f=e.buffer)==null?void 0:f.duration)??0,s=n/i;let a=0,o=0;e.state===y.Playing&&e.startTime!==void 0?(o=(this.context.currentTime-e.startTime)*i,a=o,(g=e.playOptions)!=null&&g.loop&&(a=a%n)):(a=e.pausedAt||e.currentTime||0,o=a);const c=n>0?a/n:0,d=o/i,h=a/i;return this.debugLog(`Sound state for ${t}:
State: ${e.state}
Progress: ${c}
Initial offset: ${((T=e.playOptions)==null?void 0:T.startTime)||0}ms
Sound currentTime: ${e.currentTime}s
Elapsed time: ${e.state===y.Playing?o:0}s
Current time: ${a}s
Raw Duration: ${n}s
Duration playOptions: ${((b=e.playOptions)==null?void 0:b.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(c,4),startTime:e.startTime||0,currentTime:this.roundValue(h,4),elapsedTime:this.roundValue(d,4),adjustedElapsedTime:this.roundValue(o,4),duration:this.roundValue(s,4),rawDuration:this.roundValue(n,4),state:e.state||y.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 g,T;const n=this.sounds.get(t);if(!n||n.state!==y.Playing){this.stopProgressTracking(t);return}const s=this.getSoundState(t),{currentTime:a,duration:o,rawDuration:c,elapsedTime:d,adjustedElapsedTime:h,playbackRate:p}=s,f=o?d/o:0;if(((g=n.playOptions)==null?void 0:g.duration)!==void 0&&n.playOptions.duration>0&&h>=n.playOptions.duration*(p||1)+(n.playOptions.startTime??0)){n.playOptions.pauseAtDurationReached&&!n.playOptions.loop?this.pause(t):(T=n.playOptions)!=null&&T.loop?this.handleLoopIteration(n):this.handleSoundEnded(n);return}this.dispatchEvent({type:l.PROGRESS,soundId:t,originalId:e,instanceId:t,currentTime:a,duration:o||0,progress:f,progressInfo:{soundId:t,currentTime:a,duration:o||0,rawDuration:c||0,progress:f},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=m.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=m.Stereo,s.stereoPanner&&s.stereoPanner.pan.setValueAtTime(s.pan,this.context.currentTime),this.audioNodeConnector.connectNodes(s,this.masterGainNode),i||this.dispatchEvent({type:l.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:l.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:l.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:l.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(){try{if(!("AudioContext"in window||"webkitAudioContext"in window)||!("PannerNode"in window))return!1;const t=new(window.AudioContext||window.webkitAudioContext),e="positionX"in t.listener&&"positionY"in t.listener&&"positionZ"in t.listener;return t.close(),e}catch(t){return this.debugLog("Spatial audio support check failed:",t),!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 c=o.source;o.stereoPanner&&(this.removePan(n),this.debugLog(`Removed stereo panner, and overwritten with spatial panning for sound ${n}`)),o.panType=m.Spatial,o.playOptions={...o.playOptions,panSpatialPosition:{x:t,y:e,z:i}};try{const d={...A,...this.config.pannerNodeConfig||{},...s||{}};o.pannerNode?s&&Object.keys(s).length!==0&&Object.entries(s).forEach(([h,p])=>{p!==void 0&&(o.pannerNode[h]=p)}):(o.pannerNode=this.context.createPanner(),o.pannerNode.panningModel=d.panningModel,o.pannerNode.distanceModel=d.distanceModel,o.pannerNode.refDistance=d.refDistance,o.pannerNode.maxDistance=d.maxDistance,o.pannerNode.rolloffFactor=d.rolloffFactor,o.pannerNode.coneInnerAngle=d.coneInnerAngle,o.pannerNode.coneOuterAngle=d.coneOuterAngle,o.pannerNode.coneOuterGain=d.coneOuterGain),c==null||c.disconnect(),c==null||c.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:l.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(d){this.handleError("setting sound position",d)}}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:l.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:l.SPATIAL_POSITION_RESET,timestamp:this.context.currentTime,isMaster:!0});return}const e=this.sounds.get(t);if(!e){this.debugLog(`Sound ${t} not found for position reset`);return}e.pannerNode&&(this.setSpatialPosition(0,0,0,t),this.debugLog(`Reset position for sound ${t}`),this.dispatchEvent({type:l.SPATIAL_POSITION_RESET,soundId:t,timestamp:this.context.currentTime,isMaster:!1,sound:e}))}resetMasterSpatialPosition(){try{this.masterPannerNode&&(this.masterSpatialPosition={x:0,y:0,z:0},this.masterPannerNode.positionX.setValueAtTime(this.masterSpatialPosition.x,this.context.currentTime),this.masterPannerNode.positionY.setValueAtTime(this.masterSpatialPosition.y,this.context.currentTime),this.masterPannerNode.positionZ.setValueAtTime(this.masterSpatialPosition.z,this.context.currentTime),this.masterPannerNode.coneInnerAngle=A.coneInnerAngle??360,this.masterPannerNode.coneOuterAngle=A.coneOuterAngle??360,this.masterPannerNode.coneOuterGain=A.coneOuterGain??0,this.masterPannerNode.distanceModel=A.distanceModel??"inverse",this.masterPannerNode.maxDistance=A.maxDistance??1e4,this.masterPannerNode.panningModel=A.panningModel??"HRTF",this.masterPannerNode.refDistance=A.refDistance??1,this.masterPannerNode.rolloffFactor=A.rolloffFactor??.2,this.masterGainNode.disconnect(),this.masterGainNode.connect(this.context.destination),this.masterPannerNode=null),this.dispatchEvent({type:l.GLOBAL_SPATIAL_POSITION_CHANGED,timestamp:this.context.currentTime,position:this.masterSpatialPosition}),this.debugLog("Reset master spatial position to (0, 0, 0) and cleared spatial settings.")}catch(t){this.handleError("resetting master spatial position",t)}}setPlaybackRate(t,e,i=!1){if(!t||typeof e!="number"||isNaN(e)||e<=0){this.debugLog("Invalid parameters for playback rate change");return}try{const n=this.getValidatedSound(t),s=n.source;if(n.playOptions={...n.playOptions,playbackRate:e},!n){this.debugLog(`Sound ${t} not found for playback rate change`);return}if(!s){this.debugLog(`No active source found for sound ${t}, pla