UNPKG

sound-manager-ts

Version:

A lightweight, TypeScript-based Web Audio API manager for seamless sound control in web apps and games

32 lines 57.1 kB
(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 y=(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 S=(r=>(r.Playing="playing",r.Paused="paused",r.Stopped="stopped",r))(S||{});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(n=>{if(!n.interval){n.callback(e);return}n.lastUpdate=n.lastUpdate||t,t-n.lastUpdate>=n.interval&&(n.callback(e),n.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,n){this.callbacks.set(t,{id:t,callback:e,interval:n}),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,"DEFAULT_PRECISION",2);y(this,"soundGroups",new Map);y(this,"instanceCounters",new Map);y(this,"unlockHandlers",null);y(this,"VERSION","5.5.4");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.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)}},n=()=>e(),i=()=>e();this.unlockHandlers={touchstart:n,touchend:n,click:i};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 i,s,a;const e=((i=t.playOptions)==null?void 0:i.playbackRate)??1,n=this.context.createBufferSource();if(n.buffer=t.buffer,t.source=n,n.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,n),n.onended=()=>{var o;this.debugLog(`Sound ${t.id} ended naturally`),t.state===S.Playing&&((o=t.playOptions)!=null&&o.loop?this.handleLoopIteration(t):this.handleSoundEnded(t))},n}handleLoopIteration(t){var n,i,s,a,o,c;if(this.debugLog(`Restarting loop for sound ${t.id}`),((n=t.playOptions)==null?void 0:n.maxLoops)!==void 0&&((i=t.playOptions)==null?void 0:i.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,n,i,s,a,o;if(t.state!==S.Stopped){if((e=t.playOptions)!=null&&e.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=((i=t.playOptions)==null?void 0:i.startTime)??0,t.currentTime=0;return}t.state=S.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,n){if(!this.sounds.get(t))return;const s=this.context.currentTime+e,a=()=>{this.fadeOut(t,n)};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 n=this.sounds.get(t);n!=null&&n.gainNode&&n.gainNode.gain.cancelScheduledValues(this.context.currentTime),n&&(n.isFadingIn=!1,n.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,n){const i=e instanceof Error?e.message:String(e),s=n?` (Sound ID: ${n})`:"",a=`[SoundManager] Error ${t}${s}: ${i}`;if(this.lastError=e instanceof Error?e:new Error(i),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,n;try{let i=this.getValidatedSound(t);if(!i.source)return;const s=this.context.currentTime,a=i.startTime||0,o=s-a<(((e=i.buffer)==null?void 0:e.duration)||0);if(i.state===S.Playing&&o&&((n=i.playOptions)!=null&&n.createNewInstance))return;i.source.onended&&(i.source.onended=null),i.startTime!==void 0&&i.source.stop(),i.source.disconnect(),this.activeSources.delete(t),i.source=null}catch(i){this.debugLog(`Error cleaning up source for ${t}: ${i}`)}}getInstanceCounter(t){const e=t.split(":")[0];let n=this.instanceCounters.get(e)||0;return this.instanceCounters.set(e,n+1),n+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={},n=!1){var i,s,a,o,c,d,h,p,f,g,T,b,O,E,_,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})),w=this.context.createGain(),M=e.volume??L.volume??this.config.defaultVolume??1;w.gain.value=M,R={...L,id:$,gainNode:w,state:S.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 G=((i=u.playOptions)==null?void 0:i.playbackRate)||1;let C=0;if(u.pausedAt!==void 0&&u.pausedAt!==0?C=u.pausedAt:((s=u.playOptions)==null?void 0:s.startTime)!==void 0&&(C=u.playOptions.startTime),u.startTime=this.context.currentTime-C/G,this.reconnectAudioNodes(u.id),u.state=S.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,G,!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,w=(u.playOptions.duration??((_=u.buffer)==null?void 0:_.duration)??0)-(u.pausedAt||0)-v;w>0&&this.scheduleFadeOut(u.id,w,u.playOptions.fadeOutBeforeEndDuration??1)}return B.start(0,C,((D=u.playOptions)==null?void 0:D.duration)!==void 0&&u.playOptions.duration>0?u.playOptions.duration*G:void 0),((x=u.playOptions)==null?void 0:x.trackProgress)===!0&&this.startProgressTracking(u.id),n||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,n,i=!1){const s=`${t}_${e}`;this.play(s,n,i)}pause(t,e=!1){var n;try{const i=this.getValidatedSound(t);if(!this.isPlaying(t)||this.isPaused(t))return;this.cancelScheduledFadeOut(t);const s=((n=i.playOptions)==null?void 0:n.playbackRate)??1,a=(this.context.currentTime-(i.startTime||0))*s;i.currentTime=a,i.pausedAt=a,this.debugLog(`Pausing sound ${t}: Raw elapsed time: ${a} Adjusted time: ${a/s} StartTime: ${i.startTime} CurrentTime: ${i.currentTime} PausedAt: ${i.pausedAt} PlaybackRate: ${s} `),i.state=S.Paused,this.stopProgressTracking(t),this.cleanupExistingSource(t),e||this.dispatchEvent({type:l.PAUSED,soundId:t,timestamp:this.context.currentTime,sound:i})}catch(i){this.handleError("pausing sound",i,t)}}resume(t,e=!1){let n=this.getValidatedSound(t);this.play(t,n==null?void 0:n.playOptions);try{e||this.dispatchEvent({type:l.RESUMED,soundId:t,timestamp:this.context.currentTime,sound:n}),this.debugLog(`Resumed sound ${t}:`)}catch(i){this.handleError("resuming sound",i,t)}}stop(t,e=!1){var n;try{const i=this.sounds.get(t);if(!i){this.debugLog(`Sound ${t} not found for stopping`);return}this.cancelScheduledFadeOut(t),this.cancelFadeAnimation(t),this.stopProgressTracking(t),this.cleanupExistingSource(t),i.state=S.Stopped,i.startTime=((n=i.playOptions)==null?void 0:n.startTime)??0,i.pausedAt=0,i.currentTime=0,this.removeEventListenersForInstance(t),this.resetCounterForSound(t),e||this.dispatchEvent({type:l.STOPPED,soundId:t,timestamp:this.context.currentTime,sound:i})}catch(i){console.error(`Error stopping sound ${t}:`,i),this.dispatchEvent({type:l.ERROR,timestamp:this.context.currentTime,error:new Error(`Failed to stop sound ${t}: ${i}`)})}}seek(t,e,n=!1){var i,s,a;try{const o=this.getValidatedSound(t),{duration:c,currentTime:d}=this.getSoundState(t);if(e>=c){if(o.state===S.Stopped)return;o.state===S.Playing&&((i=o.playOptions)!=null&&i.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===S.Playing&&(this.cleanupExistingSource(t),o.startTime=this.context.currentTime-g/p,this.play(t,o.playOptions)),n)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===S.Playing&&this.pause(e)})}resumeAllSounds(){this.sounds.forEach((t,e)=>{t.state==S.Paused&&this.resume(e)})}fadeIn(t,e,n,i,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=i??a.volume??((h=a.playOptions)==null?void 0:h.volume)??this.config.defaultVolume??1;c===0&&(c=1);let d;n!==void 0?d=n: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!==S.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,n,i,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=n??c,h=i??((p=o.playOptions)==null?void 0:p.fadeOutEndVolume)??0;o.previousVolume=c,o.gainNode.gain.setValueAtTime(d,this.context.currentTime),o.state!==S.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}),i===0&&s&&this.stop(t)})}fadeSound(t,e,n,i,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,i-.02),c=this.context.currentTime,d=c+o,h=()=>{a.isFadingIn=!1,a.isFadingOut=!1,a.volume=this.roundValue(n),a.gainNode.gain.setValueAtTime(n,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+(n-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: ${n} `)}catch(a){this.handleError("fading sound",a,t)}}fadeGlobalIn(t=this.config.fadeInDuration,e,n){try{const i=e??0,s=n??(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=i+(s-i)*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(i){this.handleError("fading in master volume",i)}}fadeGlobalOut(t=this.config.fadeOutDuration,e,n=0){if(t<=0){this.debugLog(`Invalid fade duration: ${t}`);return}try{const i=e??this.roundValue(this.masterGainNode.gain.value,2);this.previousGlobalVolume=i;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(n,h),this.isMuted=n===0,this.dispatchEvent({type:l.FADE_MASTER_OUT_COMPLETED,timestamp:h,volume:n}),this.ticker.removeCallback(c);return}const p=(h-s)/a,f=i+(n-i)*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(i){this.handleError("fading out master volume",i)}}getVolume(t){return this.getSoundVolume(t)}setSoundVolume(t,e,n=!1){try{this.cancelFadeAnimation(t);const i=this.getValidatedSound(t),s=this.setValidatedVolume(e);i.volume=this.roundValue(s),i.originalVolume=s,i.playOptions={...i.playOptions,volume:s},i.gainNode.gain.cancelScheduledValues(this.context.currentTime),i.gainNode.gain.setValueAtTime(s,this.context.currentTime),n||this.dispatchEvent({type:l.VOLUME_CHANGED,soundId:t,timestamp:this.context.currentTime,volume:i.volume,sound:i})}catch(i){this.handleError("setting volume",i,t)}}getSoundVolume(t){var e;try{const n=this.getValidatedSound(t);return n.originalVolume??n.volume??((e=n.playOptions)==null?void 0:e.volume)??this.config.defaultVolume??1}catch(n){return this.handleError("getting volume",n,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),n=e.previousVolume??this.config.defaultVolume??1;e.volume=n,e.gainNode.gain.setValueAtTime(n,this.context.currentTime),this.dispatchEvent({type:l.UNMUTED,soundId:t,timestamp:this.context.currentTime,volume:n,isMuted:!1,sound:e})}catch(e){this.handleError("unmuting sound",e,t)}}toggleMute(t){var n;const e=this.getValidatedSound(t);((e==null?void 0:e.volume)??(e==null?void 0:e.originalVolume)??((n=e.playOptions)==null?void 0:n.volume)??1)>0?this.mute(t):this.unmute(t)}toggleGlobalMute(){this.isMuted?this.unmuteAllSounds():this.muteAllSounds()}setLoop(t,e,n=-1){const i=this.sounds.get(t);if(!i){this.debugLog(`Sound ${t} not found for setting loop`);return}i.playOptions={...i.playOptions,loop:e,maxLoops:n},this.debugLog(`Loop set for sound ${t}: ${e}`)}getLoop(t){var n;const e=this.sounds.get(t);return e?((n=e.playOptions)==null?void 0:n.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 n=e.includes("url=")?"":"url=";return`${e}${n}${encodeURIComponent(t)}`}return`${e}${t}`}async loadWithWebAudio(t,e){const n=this.config.fetchStrategy==="proxy-first"&&this.shouldUseProxy(e)?["proxy","direct"]:["direct"];for(const i of n)try{const s=i==="proxy"||this.config.corsProxy?this.getProxyUrl(e):e;this.debugLog(`Trying ${i} 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(`${i} strategy failed for ${t}`,{error:s instanceof Error?s.message:String(s),url:e}),i===n[n.length-1])throw new Error(`Failed to load sound ${t}: ${s instanceof Error?s.message:String(s)}`)}}async fetchWithRetry(t,e){const{fetchRetries:n=2,retryDelay:i=.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<=n;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<n&&await new Promise(g=>setTimeout(g,i*1e3))}throw a||new Error("Failed after all retries")}async processAudioResponse(t,e){const n=e.headers.get("content-type");if(!(n!=null&&n.includes("audio/")))throw new Error(`Invalid content type: ${n}`);const i=e.headers.get("content-length");if(i&&this.config.maxAudioSize&&parseInt(i)>this.config.maxAudioSize)throw new Error(`Audio file too large: ${i} bytes (max ${this.config.maxAudioSize} bytes), change the maxAudioSize config in your SoundManagerConfig`);const s=i?parseInt(i):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((n,i)=>{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(),i(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`),n()}catch(c){i(c)}},s.onerror=o,s.addEventListener("error",o),s.load()})}async loadSounds(t){if(t.length)try{const{maxParallelLoads:e=10,webAudioPreferred:n=!0}=this.config,i=Math.max(1,e),s=[];for(let a=0;a<t.length;a+=i)s.push(t.slice(a,a+i));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(n)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,n){const i=this.context.createGain();i.gain.value=this.config.defaultVolume??1,i.connect(this.masterGainNode);const s=this.context.createBufferSource();s.buffer=e;const a=this.calculateAudioSize(e),o={id:t,buffer:e,gainNode:i,source:s,startTime:void 0,currentTime:0,pausedAt:void 0,state:S.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:n,sampleRate:e.sampleRate,channels:e.numberOfChannels})}async loadSound(t,e){try{await this.loadSounds([{id:t,url:e}])}catch(n){throw this.handleError("loading sound",n,t),n}}async updateSoundUrl(t,e){try{const n=this.sounds.get(t);if(!n){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:n}),this.debugLog(`Sound ${t} URL updated to ${e}`)}catch(n){this.handleError("updating sound URL",n,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 n=this.soundGroups.get(t);if(!n){this.debugLog(`Group ${t} not found.`);return}if(n.maxInstances&&n.sounds.size>=n.maxInstances){const s=Array.from(n.sounds)[0];this.stop(s),n.sounds.delete(s),this.debugLog(`Stopped oldest instance ${s} to make room for new instance in group ${t}.`)}const i=this.sounds.get(e);i&&(i.groupId=t),i&&n.playOptions&&(i.playOptions={...n.playOptions,...i.playOptions}),n.sounds.add(e),this.debugLog(`Added sound ${e} to group ${t}.`)}removeFromSoundGroup(t,e){const n=this.soundGroups.get(t);if(!n){this.debugLog(`Group ${t} not found.`);return}n.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(n=>{this.stop(n),this.sounds.delete(n)}),e.sounds.clear(),this.soundGroups.delete(t),this.debugLog(`Cleaned up group ${t}.`)}setSoundSprite(t,e){try{const n=this.getValidatedSound(t);if(!n||!n.buffer)throw new Error(`Sound ${t} not found or buffer not loaded`);Object.entries(e).forEach(([i,[s,a]])=>{const o=`${t}_${i}`;this.debugLog(`Creating sprite ${o} for sound ${t}: Start=${s}ms, End=${a}ms, Duration=${a-s}ms`);const c=n.buffer.sampleRate,d=Math.floor(s*c),h=Math.floor(a*c),p=a-s,f=n.buffer.numberOfChannels,g=this.context.createBuffer(f,h-d,c);for(let E=0;E<f;E++){const _=n.buffer.getChannelData(E),D=g.getChannelData(E);for(let x=0;x<D.length;x++)D[x]=_[x+d]}const T=this.context.createGain(),b=n.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:S.Stopped,gainNode:T,playOptions:{...n.playOptions},currentLoopCount:0,panSpatialPosition:this.config.defaultPanSpatialPosition||{x:0,y:0,z:0},pan:n.pan??this.config.defaultPan??0};if(n.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(n){this.handleError("setting sound sprite",n,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(n=>n.includes(`_${t}`));if(e.length===0){this.debugLog(`No sprite instances found for key: ${t}`);return}e.forEach(n=>{this.stop(n),this.cleanupSound(n),this.sounds.delete(n),this.debugLog(`Removed sprite instance: ${n}`)}),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===S.Playing}isPaused(t){try{return this.getValidatedSound(t).state==S.Paused}catch{return!1}}isStopped(t){try{return this.getValidatedSound(t).state===S.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 n=((p=e.playOptions)==null?void 0:p.playbackRate)??1,i=((f=e.buffer)==null?void 0:f.duration)??0,s=i/n;let a=0,o=0;e.state===S.Playing&&e.startTime!==void 0?(o=(this.context.currentTime-e.startTime)*n,a=o,(g=e.playOptions)!=null&&g.loop&&(a=a%i)):(a=e.pausedAt||e.currentTime||0,o=a);const c=i>0?a/i:0,d=o/n,h=a/n;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===S.Playing?o:0}s Current time: ${a}s Raw Duration: ${i}s Duration playOptions: ${((b=e.playOptions)==null?void 0:b.duration)||0}s Adjusted Duration: ${this.roundValue(o,4)}s Playback Rate: ${n} 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(i,4),state:e.state||S.Stopped,volume:e.volume??((O=e==null?void 0:e.playOptions)==null?void 0:O.volume)??this.config.defaultVolume??1,playbackRate:n,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 n=this.getValidatedSound(t);return((e=n==null?void 0:n.buffer)==null?void 0:e.duration)||0}catch(n){return this.handleError("getting duration",n,t),0}}getProgress(t){const{currentTime:e,duration:n}=this.getSoundState(t);return n?e/n:0}getProgressPercentage(t){return this.getProgress(t)*100}startProgressTracking(t){this.stopProgressTracking(t);const e=t.includes(":")?t.split(":")[0]:t,n=()=>{var g,T;const i=this.sounds.get(t);if(!i||i.state!==S.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=i.playOptions)==null?void 0:g.duration)!==void 0&&i.playOptions.duration>0&&h>=i.playOptions.duration*(p||1)+(i.playOptions.startTime??0)){i.playOptions.pauseAtDurationReached&&!i.playOptions.loop?this.pause(t):(T=i.playOptions)!=null&&T.loop?this.handleLoopIteration(i):this.handleSoundEnded(i);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:i.volume,sound:i})};this.ticker.addCallback(`progress_${t}`,n,this.PROGRESS_UPDATE_INTERVAL)}stopProgressTracking(t){this.ticker.removeCallback(`progress_${t}`)}setProgressUpdateInterval(t){this.PROGRESS_UPDATE_INTERVAL=t}setPan(t,e,n=!1){var i;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),n||this.dispatchEvent({type:l.PAN_CHANGED,soundId:t,timestamp:((i=this.context)==null?void 0:i.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 n=e.source;n&&(n.disconnect(),n.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((i,s)=>{this.isSpatialAudioActive(s)&&this.removeSpatialEffect(s),i.stereoPanner&&(i.stereoPanner.pan.setValueAtTime(t,this.context.currentTime),i.pan=t)});const n=Math.max(-1,Math.min(1,t));this.masterStereoPanner.pan.setValueAtTime(n,this.context.currentTime),this.dispatchEvent({type:l.MASTER_PAN_CHANGED,timestamp:((e=this.context)==null?void 0:e.currentTime)??0,pan:n,previousPan:this.previousGlobalPan,isMaster:!0}),this.debugLog(`Master pan set to: ${n}`)}catch(n){this.handleError("setting master pan",n)}}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,n,i,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),n=this.roundValue(n,2),!i){this.debugLog(`Sound ${i} not found, global sound position will be used`),this.setMasterSpatialPosition(t,e,n,s);return}const o=this.sounds.get(i);if(!o){this.debugLog(`Sound ${i} not found for position setting`);return}const c=o.source;o.stereoPanner&&(this.removePan(i),this.debugLog(`Removed stereo panner, and overwritten with spatial panning for sound ${i}`)),o.panType=m.Spatial,o.playOptions={...o.playOptions,panSpatialPosition:{x:t,y:e,z:n}};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(n,this.context.currentTime),o.panSpatialPosition={x:t,y:e,z:n},a||this.dispatchEvent({type:l.SPATIAL_POSITION_CHANGED,soundId:i,timestamp:this.context.currentTime,position:o.panSpatialPosition,pannerConfig:s,sound:o}),this.debugLog(`Set position for sound ${i}: x=${t}, y=${e}, z=${n}`)}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,n,i={},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(n,this.context.currentTime),this.masterSpatialPosition={x:t,y:e,z:n},i.coneInnerAngle!==void 0&&(this.masterPannerNode.coneInnerAngle=i.coneInnerAngle),i.coneOuterAngle!==void 0&&(this.masterPannerNode.coneOuterAngle=i.coneOuterAngle),i.coneOuterGain!==void 0&&(this.masterPannerNode.coneOuterGain=i.coneOuterGain),i.distanceModel!==void 0&&(this.masterPannerNode.distanceModel=i.distanceModel),i.maxDistance!==void 0&&(this.masterPannerNode.maxDistance=i.maxDistance),i.panningModel!==void 0&&(this.masterPannerNode.panningModel=i.panningModel),i.refDistance!==void 0&&(this.masterPannerNode.refDistance=i.refDistance),i.rolloffFactor!==void 0&&(this.masterPannerNode.rolloffFactor=i.rolloffFactor),s||this.dispatchEvent({type:l.GLOBAL_SPATIAL_POSITION_CHANGED,timestamp:this.context.currentTime,position:{x:t,y:e,z:n}}),this.debugLog(`Set master spatial position: x=${t}, y=${e}, z=${n}`)}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 n=e.source;n&&(n.disconnect(),n.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 n=this.sounds.get(t);if(!(n!=null&&n.pannerNode)){this.debugLog(`No panner node found for sound ${t}`);return}try{Object.entries(e).forEach(([i,s])=>{s!==void 0&&(n.pannerNode[i]=s)}),this.debugLog(`Updated panner config for sound ${t}`,e)}catch(i){this.handleError("updating panner configuration",i)}}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,n=!1){if(!t||typeof e!="number"||isNaN(e)||e<=0){this.debugLog("Invalid parameters for playback rate change");return}try{const i=this.getValidatedSound(t),s=i.source;if(i.playOptions={...i.playOptions,playbackRate:e},!i){this.debugLog(`Sound ${t} not found for playback rate change`);return}if(!s){this.deb