using System; using System.Collections.Generic; using System.Reflection; using AlicizaX.ObjectPool; using AlicizaX.Resource.Runtime; using UnityEngine; using UnityEngine.Audio; using YooAsset; namespace AlicizaX.Audio.Runtime { internal sealed class AudioService : ServiceBase, IAudioService, IAudioDebugService, IServiceTickable { private const string SourcePoolName = "Audio Source Pool"; private const int DefaultCacheCapacity = 128; private const float DefaultClipTtl = 30f; private const int HandleIndexBits = 20; private const ulong HandleIndexMask = (1UL << HandleIndexBits) - 1UL; private static readonly string[] VolumeParameterNames = { "SoundVolume", "UISoundVolume", "MusicVolume", "VoiceVolume", "AmbientVolume" }; private readonly AudioCategory[] _categories = new AudioCategory[(int)AudioType.Max]; private readonly float[] _categoryVolumes = new float[(int)AudioType.Max]; private readonly bool[] _categoryEnables = new bool[(int)AudioType.Max]; private readonly Dictionary _clipCache = new Dictionary(DefaultCacheCapacity, StringComparer.Ordinal); private readonly AudioSourceObject[][] _sourceObjects = new AudioSourceObject[(int)AudioType.Max][]; private IResourceService _resourceService; private IObjectPool _sourcePool; private AudioMixer _audioMixer; private Transform _instanceRoot; private AudioGroupConfig[] _configs; private AudioAgent[] _handleAgents = Array.Empty(); private uint[] _handleGenerations = Array.Empty(); private AudioListener _listenerCache; private AudioClipCacheEntry _lruHead; private AudioClipCacheEntry _lruTail; private AudioClipCacheEntry _allHead; private AudioClipCacheEntry _allTail; private int _clipCacheCapacity = DefaultCacheCapacity; private float _clipTtl = DefaultClipTtl; private float _volume = 1f; private bool _enable = true; private bool _unityAudioDisabled; private bool _initialized; private bool _ownsInstanceRoot; public AudioMixer AudioMixer => _audioMixer; public Transform InstanceRoot => _instanceRoot; public Transform ListenerTransform => _listenerCache != null && _listenerCache.enabled && _listenerCache.gameObject.activeInHierarchy ? _listenerCache.transform : null; int IAudioDebugService.CategoryCount => _categories.Length; int IAudioDebugService.ClipCacheCount => _clipCache.Count; int IAudioDebugService.ClipCacheCapacity => _clipCacheCapacity; int IAudioDebugService.HandleCapacity => _handleAgents.Length; bool IAudioDebugService.Initialized => _initialized; bool IAudioDebugService.UnityAudioDisabled => _unityAudioDisabled; AudioClipCacheEntry IAudioDebugService.FirstClipCacheEntry => _allHead; public int Priority => 0; public float Volume { get => _unityAudioDisabled ? 0f : _volume; set { if (_unityAudioDisabled) { return; } _volume = Mathf.Clamp01(value); AudioListener.volume = _enable ? _volume : 0f; } } public bool Enable { get => !_unityAudioDisabled && _enable; set { if (_unityAudioDisabled) { return; } _enable = value; AudioListener.volume = _enable ? _volume : 0f; } } internal float MusicVolume { get => GetCategoryVolume(AudioType.Music); set => SetCategoryVolume(AudioType.Music, value); } internal float SoundVolume { get => GetCategoryVolume(AudioType.Sound); set => SetCategoryVolume(AudioType.Sound, value); } internal float UISoundVolume { get => GetCategoryVolume(AudioType.UISound); set => SetCategoryVolume(AudioType.UISound, value); } internal float VoiceVolume { get => GetCategoryVolume(AudioType.Voice); set => SetCategoryVolume(AudioType.Voice, value); } internal float AmbientVolume { get => GetCategoryVolume(AudioType.Ambient); set => SetCategoryVolume(AudioType.Ambient, value); } internal bool MusicEnable { get => GetCategoryEnable(AudioType.Music); set => SetCategoryEnable(AudioType.Music, value); } internal bool SoundEnable { get => GetCategoryEnable(AudioType.Sound); set => SetCategoryEnable(AudioType.Sound, value); } internal bool UISoundEnable { get => GetCategoryEnable(AudioType.UISound); set => SetCategoryEnable(AudioType.UISound, value); } internal bool VoiceEnable { get => GetCategoryEnable(AudioType.Voice); set => SetCategoryEnable(AudioType.Voice, value); } internal bool AmbientEnable { get => GetCategoryEnable(AudioType.Ambient); set => SetCategoryEnable(AudioType.Ambient, value); } protected override void OnInitialize() { } protected override void OnDestroyService() { Shutdown(true); } public void Initialize(AudioGroupConfig[] audioGroupConfigs, Transform instanceRoot = null, AudioMixer audioMixer = null) { Shutdown(false); _resourceService = AppServices.Require(); IObjectPoolService objectPoolService = AppServices.Require(); _sourcePool = objectPoolService.HasObjectPool(SourcePoolName) ? objectPoolService.GetObjectPool(SourcePoolName) : objectPoolService.CreatePool(new ObjectPoolCreateOptions(SourcePoolName, false, 10f, int.MaxValue, float.MaxValue, 10)); if (audioGroupConfigs == null || audioGroupConfigs.Length == 0) { throw new GameFrameworkException("AudioGroupConfig[] is invalid."); } _configs = audioGroupConfigs; if (instanceRoot != null) { _instanceRoot = instanceRoot; _ownsInstanceRoot = false; } else if (_instanceRoot == null) { _instanceRoot = new GameObject("[AudioService Instances]").transform; _ownsInstanceRoot = true; } _instanceRoot.localScale = Vector3.one; if (_ownsInstanceRoot) { UnityEngine.Object.DontDestroyOnLoad(_instanceRoot.gameObject); } _unityAudioDisabled = IsUnityAudioDisabled(); if (_unityAudioDisabled) { _initialized = true; return; } _audioMixer = audioMixer != null ? audioMixer : Resources.Load("AudioMixer"); if (_audioMixer == null) { throw new GameFrameworkException("AudioMixer is invalid."); } int totalAgentCount = 0; for (int i = 0; i < (int)AudioType.Max; i++) { AudioGroupConfig config = FindConfig((AudioType)i); totalAgentCount += config != null ? config.AgentHelperCount : 1; } if ((ulong)totalAgentCount > HandleIndexMask) { throw new GameFrameworkException("Audio agent count exceeds handle capacity."); } _handleAgents = new AudioAgent[totalAgentCount]; _handleGenerations = new uint[totalAgentCount]; MemoryPool.Add(totalAgentCount); MemoryPool.Add(totalAgentCount); MemoryPool.Add(_clipCacheCapacity); int globalIndexOffset = 0; for (int i = 0; i < (int)AudioType.Max; i++) { AudioGroupConfig config = FindConfig((AudioType)i); if (config == null) { config = CreateRuntimeDefaultConfig((AudioType)i); } _categoryVolumes[i] = Mathf.Clamp(config.Volume, 0.0001f, 1f); _categoryEnables[i] = !config.Mute; _sourceObjects[i] = new AudioSourceObject[config.AgentHelperCount]; _categories[i] = new AudioCategory(this, _audioMixer, config, globalIndexOffset); globalIndexOffset += config.AgentHelperCount; ApplyMixerVolume(config, _categoryVolumes[i], _categoryEnables[i]); } _initialized = true; } public void Restart() { Initialize(_configs, _instanceRoot, _audioMixer); } public ulong Play(AudioType type, string path, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true) { AudioPlayRequest request = MemoryPool.Acquire(); request.Set2D(type, path, loop, volume, async, cacheClip); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong Play(AudioType type, AudioClip clip, bool loop = false, float volume = 1f) { AudioPlayRequest request = MemoryPool.Acquire(); request.Set2D(type, clip, loop, volume); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong Play3D(AudioType type, string path, in Vector3 position, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true) { AudioPlayRequest request = MemoryPool.Acquire(); request.Set3D(type, path, position, loop, volume, async, cacheClip); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong Play3D(AudioType type, string path, in Vector3 position, float minDistance, float maxDistance, AudioRolloffMode rolloffMode, float spatialBlend = 1f, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true) { AudioPlayRequest request = MemoryPool.Acquire(); request.Set3D(type, path, position, minDistance, maxDistance, rolloffMode, spatialBlend, loop, volume, async, cacheClip); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong Play3D(AudioType type, AudioClip clip, in Vector3 position, bool loop = false, float volume = 1f) { if (clip == null) { return 0UL; } AudioPlayRequest request = MemoryPool.Acquire(); request.Set3D(type, clip, position, loop, volume); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong Play3D(AudioType type, AudioClip clip, in Vector3 position, float minDistance, float maxDistance, AudioRolloffMode rolloffMode, float spatialBlend = 1f, bool loop = false, float volume = 1f) { if (clip == null) { return 0UL; } AudioPlayRequest request = MemoryPool.Acquire(); request.Set3D(type, clip, position, minDistance, maxDistance, rolloffMode, spatialBlend, loop, volume); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong PlayFollow(AudioType type, string path, Transform target, in Vector3 localOffset, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true) { if (target == null) { return 0UL; } AudioPlayRequest request = MemoryPool.Acquire(); request.SetFollow(type, path, target, localOffset, loop, volume, async, cacheClip); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong PlayFollow(AudioType type, AudioClip clip, Transform target, in Vector3 localOffset, bool loop = false, float volume = 1f) { if (target == null || clip == null) { return 0UL; } AudioPlayRequest request = MemoryPool.Acquire(); request.SetFollow(type, clip, target, localOffset, loop, volume); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong PlayFollow(AudioType type, AudioClip clip, Transform target, in Vector3 localOffset, float minDistance, float maxDistance, AudioRolloffMode rolloffMode, float spatialBlend = 1f, bool loop = false, float volume = 1f) { if (target == null || clip == null) { return 0UL; } AudioPlayRequest request = MemoryPool.Acquire(); request.SetFollow(type, clip, target, localOffset, minDistance, maxDistance, rolloffMode, spatialBlend, loop, volume); ulong handle = Play(request); MemoryPool.Release(request); return handle; } public ulong PlayFollow(AudioType type, string path, Transform target, in Vector3 localOffset, float minDistance, float maxDistance, AudioRolloffMode rolloffMode, float spatialBlend = 1f, bool loop = false, float volume = 1f, bool async = false, bool cacheClip = true) { if (target == null) { return 0UL; } AudioPlayRequest request = MemoryPool.Acquire(); request.SetFollow(type, path, target, localOffset, minDistance, maxDistance, rolloffMode, spatialBlend, loop, volume, async, cacheClip); ulong handle = Play(request); MemoryPool.Release(request); return handle; } internal ulong Play(AudioPlayRequest request) { if (!_initialized || _unityAudioDisabled || request == null) { return 0UL; } int index = (int)request.Type; if ((uint)index >= (uint)_categories.Length) { return 0UL; } AudioCategory category = _categories[index]; return category != null ? category.Play(request) : 0UL; } public bool Stop(ulong handle, bool fadeout = false) { AudioAgent agent = ResolveHandle(handle); if (agent == null) { return false; } agent.Stop(fadeout); return true; } public bool IsPlaying(ulong handle) { AudioAgent agent = ResolveHandle(handle); return agent != null && agent.IsPlayingState; } public void Stop(AudioType type, bool fadeout) { int index = (int)type; if ((uint)index < (uint)_categories.Length && _categories[index] != null) { _categories[index].Stop(fadeout); } } public void StopAll(bool fadeout) { for (int i = 0; i < _categories.Length; i++) { AudioCategory category = _categories[i]; if (category != null) { category.Stop(fadeout); } } } public void Pause(ulong handle) { AudioAgent agent = ResolveHandle(handle); if (agent != null) { agent.Pause(); } } public void Resume(ulong handle) { AudioAgent agent = ResolveHandle(handle); if (agent != null) { agent.Resume(); } } public void Preload(IList paths, bool pin = true) { if (paths == null || _unityAudioDisabled) { return; } for (int i = 0; i < paths.Count; i++) { string path = paths[i]; if (string.IsNullOrEmpty(path)) { continue; } AudioClipCacheEntry entry = GetOrCreateClipEntry(path, pin); entry.Pinned = entry.Pinned || pin; entry.CacheAfterUse = true; TouchClip(entry); if (entry.Handle == null) { BeginLoad(entry, true); } } } public void Unload(IList paths) { if (paths == null) { return; } for (int i = 0; i < paths.Count; i++) { string path = paths[i]; if (string.IsNullOrEmpty(path) || !_clipCache.TryGetValue(path, out AudioClipCacheEntry entry)) { continue; } entry.Pinned = false; if (entry.RefCount <= 0 && !entry.Loading && entry.PendingHead == null) { RemoveClipEntry(entry); } } } public void ClearCache() { AudioClipCacheEntry entry = _allHead; while (entry != null) { AudioClipCacheEntry next = entry.AllNext; if (entry.RefCount <= 0 && !entry.Loading && entry.PendingHead == null) { RemoveClipEntry(entry); } entry = next; } } void IServiceTickable.Tick(float deltaTime) { if (!_initialized || _unityAudioDisabled) { return; } for (int i = 0; i < _categories.Length; i++) { AudioCategory category = _categories[i]; if (category != null) { category.Update(deltaTime); } } TrimClipCache(); } internal AudioSourceObject AcquireSourceObject(AudioCategory category, int index) { string name = BuildSourceName(category.TypeIndex, index); AudioSourceObject sourceObject = _sourcePool.Spawn(name); if (sourceObject == null) { sourceObject = CreateSourceObject(category, index, name); _sourcePool.Register(sourceObject, true); } _sourceObjects[category.TypeIndex][index] = sourceObject; return sourceObject; } internal void ReleaseSourceObject(int typeIndex, int index) { AudioSourceObject[] sourceObjects = _sourceObjects[typeIndex]; if (sourceObjects == null) { return; } AudioSourceObject sourceObject = sourceObjects[index]; if (sourceObject == null) { return; } sourceObjects[index] = null; if (sourceObject.Source != null) { sourceObject.Source.transform.SetParent(_instanceRoot, false); } _sourcePool.Unspawn(sourceObject); } internal ulong AllocateHandle(AudioAgent agent) { int index = agent.GlobalIndex; if ((uint)index >= (uint)_handleAgents.Length) { return 0UL; } uint generation = _handleGenerations[index] + 1U; if (generation == 0) { generation = 1U; } _handleGenerations[index] = generation; _handleAgents[index] = agent; return ((ulong)generation << HandleIndexBits) | (uint)(index + 1); } internal void ReleaseHandle(ulong handle, AudioAgent agent) { int index = (int)((handle & HandleIndexMask) - 1UL); if ((uint)index < (uint)_handleAgents.Length && ReferenceEquals(_handleAgents[index], agent)) { _handleAgents[index] = null; } } private AudioAgent ResolveHandle(ulong handle) { if (handle == 0) { return null; } int index = (int)((handle & HandleIndexMask) - 1UL); uint generation = (uint)(handle >> HandleIndexBits); if ((uint)index >= (uint)_handleAgents.Length || _handleGenerations[index] != generation) { return null; } return _handleAgents[index]; } internal AudioClipCacheEntry RequestClip(string address, bool async, bool cacheClip, AudioAgent agent, int generation) { AudioClipCacheEntry entry = GetOrCreateClipEntry(address, cacheClip); entry.CacheAfterUse = entry.CacheAfterUse || cacheClip; TouchClip(entry); if (entry.IsLoaded) { return entry; } AudioLoadRequest request = MemoryPool.Acquire(); request.Agent = agent; request.Generation = generation; entry.AddPending(request); if (entry.Handle == null) { BeginLoad(entry, async); } return null; } internal void RetainClip(AudioClipCacheEntry entry) { entry.RefCount++; RemoveFromLru(entry); TouchClip(entry); } internal void ReleaseClip(AudioClipCacheEntry entry) { if (entry.RefCount > 0) { entry.RefCount--; } TouchClip(entry); if (entry.RefCount <= 0) { if (entry.CacheAfterUse) { AddToLruTail(entry); } else { RemoveClipEntry(entry); } } } internal void OnClipLoadCompleted(AudioClipCacheEntry entry, AssetHandle handle) { if (entry == null || !_clipCache.TryGetValue(entry.Address, out AudioClipCacheEntry mapped) || !ReferenceEquals(mapped, entry)) { if (handle is { IsValid: true }) { handle.Dispose(); } return; } entry.Loading = false; if (handle != null) { handle.Completed -= entry.CompletedCallback; } bool success = handle is { IsValid: true } && handle.AssetObject is AudioClip; if (success) { entry.Handle = handle; entry.Clip = (AudioClip)handle.AssetObject; TouchClip(entry); } AudioLoadRequest request = entry.PendingHead; entry.PendingHead = null; entry.PendingTail = null; while (request != null) { AudioLoadRequest next = request.Next; if (success) { request.Agent?.OnClipReady(entry, request.Generation); } else { request.Agent?.OnClipLoadFailed(request.Generation); } MemoryPool.Release(request); request = next; } if (!success) { RemoveClipEntry(entry); if (handle is { IsValid: true }) { handle.Dispose(); } } else if (entry.RefCount <= 0 && entry.CacheAfterUse) { AddToLruTail(entry); } else if (success && entry.RefCount <= 0) { RemoveClipEntry(entry); } } public float GetCategoryVolume(AudioType type) { if (_unityAudioDisabled) { return 0f; } int index = (int)type; return (uint)index < (uint)_categoryVolumes.Length ? _categoryVolumes[index] : 0f; } public void SetCategoryVolume(AudioType type, float value) { if (_unityAudioDisabled) { return; } int index = (int)type; if ((uint)index >= (uint)_categoryVolumes.Length) { return; } float volume = Mathf.Clamp(value, 0.0001f, 1f); _categoryVolumes[index] = volume; AudioGroupConfig config = GetConfig(type); ApplyMixerVolume(config, volume, _categoryEnables[index]); } public bool GetCategoryEnable(AudioType type) { if (_unityAudioDisabled) { return false; } int index = (int)type; return (uint)index < (uint)_categoryEnables.Length && _categoryEnables[index]; } public void SetCategoryEnable(AudioType type, bool value) { if (_unityAudioDisabled) { return; } int index = (int)type; if ((uint)index >= (uint)_categoryEnables.Length) { return; } _categoryEnables[index] = value; AudioCategory category = _categories[index]; if (category != null) { category.Enabled = value; } AudioGroupConfig config = GetConfig(type); ApplyMixerVolume(config, _categoryVolumes[index], value); } public void RegisterListener(AudioListener listener) { if (listener == null) { return; } _listenerCache = listener; } public void UnregisterListener(AudioListener listener) { if (listener == null) { return; } if (ReferenceEquals(_listenerCache, listener)) { _listenerCache = null; } } void IAudioDebugService.FillServiceDebugInfo(AudioServiceDebugInfo info) { if (info == null) { return; } info.Initialized = _initialized; info.UnityAudioDisabled = _unityAudioDisabled; info.Enable = Enable; info.Volume = Volume; info.CategoryCount = _categories.Length; info.ActiveAgentCount = CountActiveAgents(); info.HandleCapacity = _handleAgents.Length; info.ClipCacheCount = _clipCache.Count; info.ClipCacheCapacity = _clipCacheCapacity; info.Listener = _listenerCache; info.InstanceRoot = _instanceRoot; } bool IAudioDebugService.FillCategoryDebugInfo(int typeIndex, AudioCategoryDebugInfo info) { if (info == null || (uint)typeIndex >= (uint)_categories.Length) { return false; } AudioCategory category = _categories[typeIndex]; if (category == null) { info.Clear(); info.Type = (AudioType)typeIndex; return false; } float volume = (uint)typeIndex < (uint)_categoryVolumes.Length ? _categoryVolumes[typeIndex] : 0f; category.FillDebugInfo(volume, info); return true; } bool IAudioDebugService.FillAgentDebugInfo(int typeIndex, int agentIndex, AudioAgentDebugInfo info) { if (info == null || (uint)typeIndex >= (uint)_categories.Length) { return false; } AudioCategory category = _categories[typeIndex]; if (category == null || !category.TryGetAgent(agentIndex, out AudioAgent agent) || agent == null) { info.Clear(); return false; } agent.FillDebugInfo(info); return true; } bool IAudioDebugService.FillClipCacheDebugInfo(AudioClipCacheEntry entry, AudioClipCacheDebugInfo info) { if (entry == null || info == null) { return false; } entry.FillDebugInfo(info); return true; } private void ApplyMixerVolume(AudioGroupConfig config, float volume, bool enabled) { if (_audioMixer == null || config == null) { return; } string parameter = string.IsNullOrEmpty(config.ExposedVolumeParameter) ? VolumeParameterNames[(int)config.AudioType] : config.ExposedVolumeParameter; _audioMixer.SetFloat(parameter, enabled ? Mathf.Log10(volume) * 20f : -80f); } private AudioClipCacheEntry GetOrCreateClipEntry(string address, bool pinned) { if (_clipCache.TryGetValue(address, out AudioClipCacheEntry entry)) { return entry; } entry = MemoryPool.Acquire(); entry.Initialize(this, address, pinned); _clipCache.Add(address, entry); AddToAllList(entry); if (pinned) { AddToLruTail(entry); } return entry; } private void BeginLoad(AudioClipCacheEntry entry, bool async) { entry.Loading = async; if (async) { entry.Handle = _resourceService.LoadAssetAsyncHandle(entry.Address); entry.Handle.Completed += entry.CompletedCallback; return; } AssetHandle handle = _resourceService.LoadAssetSyncHandle(entry.Address); entry.Handle = handle; OnClipLoadCompleted(entry, handle); } private void TouchClip(AudioClipCacheEntry entry) { entry.LastUseTime = Time.realtimeSinceStartup; if (entry.RefCount <= 0 && entry.IsLoaded) { MoveLruToTail(entry); } } private void TrimClipCache() { float now = Time.realtimeSinceStartup; while (_clipCache.Count > _clipCacheCapacity && _lruHead != null) { AudioClipCacheEntry entry = _lruHead; if (!CanEvict(entry, now, false)) { break; } RemoveClipEntry(entry); } AudioClipCacheEntry current = _lruHead; while (current != null) { AudioClipCacheEntry next = current.LruNext; if (!CanEvict(current, now, true)) { break; } RemoveClipEntry(current); current = next; } } private bool CanEvict(AudioClipCacheEntry entry, float now, bool requireExpired) { if (entry == null || entry.RefCount > 0 || entry.Pinned || entry.Loading) { return false; } if (!requireExpired) { return true; } return now - entry.LastUseTime >= _clipTtl; } private void RemoveClipEntry(AudioClipCacheEntry entry) { if (entry == null) { return; } _clipCache.Remove(entry.Address); RemoveFromLru(entry); RemoveFromAllList(entry); MemoryPool.Release(entry); } private void AddToLruTail(AudioClipCacheEntry entry) { if (entry.InLru || entry.RefCount > 0 || entry.Loading || !entry.CacheAfterUse) { return; } entry.InLru = true; entry.LruPrev = _lruTail; entry.LruNext = null; if (_lruTail != null) { _lruTail.LruNext = entry; } else { _lruHead = entry; } _lruTail = entry; } private void RemoveFromLru(AudioClipCacheEntry entry) { if (!entry.InLru) { return; } AudioClipCacheEntry prev = entry.LruPrev; AudioClipCacheEntry next = entry.LruNext; if (prev != null) { prev.LruNext = next; } else { _lruHead = next; } if (next != null) { next.LruPrev = prev; } else { _lruTail = prev; } entry.LruPrev = null; entry.LruNext = null; entry.InLru = false; } private void MoveLruToTail(AudioClipCacheEntry entry) { if (!entry.InLru || ReferenceEquals(_lruTail, entry)) { return; } RemoveFromLru(entry); AddToLruTail(entry); } private void AddToAllList(AudioClipCacheEntry entry) { entry.AllPrev = _allTail; entry.AllNext = null; if (_allTail != null) { _allTail.AllNext = entry; } else { _allHead = entry; } _allTail = entry; } private void RemoveFromAllList(AudioClipCacheEntry entry) { AudioClipCacheEntry prev = entry.AllPrev; AudioClipCacheEntry next = entry.AllNext; if (prev != null) { prev.AllNext = next; } else { _allHead = next; } if (next != null) { next.AllPrev = prev; } else { _allTail = prev; } entry.AllPrev = null; entry.AllNext = null; } private AudioSourceObject CreateSourceObject(AudioCategory category, int index, string name) { GameObject host = new GameObject(name); host.transform.SetParent(category.InstanceRoot, false); AudioSource source = host.AddComponent(); source.playOnAwake = false; source.outputAudioMixerGroup = category.MixerGroup; source.rolloffMode = category.Config.RolloffMode; source.minDistance = category.Config.MinDistance; source.maxDistance = category.Config.MaxDistance; AudioLowPassFilter lowPassFilter = host.AddComponent(); lowPassFilter.enabled = false; host.SetActive(true); return AudioSourceObject.Create(name, source, lowPassFilter); } private static string BuildSourceName(int typeIndex, int index) { return "AudioSource_" + typeIndex + "_" + index; } private AudioGroupConfig GetConfig(AudioType type) { AudioCategory category = _categories[(int)type]; if (category != null) { return category.Config; } return FindConfig(type); } private AudioGroupConfig FindConfig(AudioType type) { if (_configs == null) { return null; } for (int i = 0; i < _configs.Length; i++) { AudioGroupConfig config = _configs[i]; if (config != null && config.AudioType == type) { return config; } } return null; } private int CountActiveAgents() { int count = 0; for (int i = 0; i < _categories.Length; i++) { AudioCategory category = _categories[i]; if (category != null) { count += category.ActiveCount; } } return count; } private static AudioGroupConfig CreateRuntimeDefaultConfig(AudioType type) { AudioGroupConfig config = new AudioGroupConfig(); config.SetDefaults(type, type.ToString(), VolumeParameterNames[(int)type], 8, type == AudioType.Music || type == AudioType.UISound ? 0f : 1f, type == AudioType.Voice || type == AudioType.Ambient); return config; } private void Shutdown(bool destroyRoot) { StopAll(false); for (int i = 0; i < _categories.Length; i++) { AudioCategory category = _categories[i]; if (category != null) { category.Shutdown(); _categories[i] = null; } } ClearCache(); if (_sourcePool != null) { _sourcePool.ReleaseAllUnused(); } Array.Clear(_handleAgents, 0, _handleAgents.Length); Array.Clear(_handleGenerations, 0, _handleGenerations.Length); _handleAgents = Array.Empty(); _handleGenerations = Array.Empty(); _resourceService = null; _sourcePool = null; _audioMixer = null; _listenerCache = null; _initialized = false; for (int i = 0; i < _sourceObjects.Length; i++) { _sourceObjects[i] = null; } if (destroyRoot) { DestroyOwnedRoot(); } } private void DestroyOwnedRoot() { if (_ownsInstanceRoot && _instanceRoot != null) { UnityEngine.Object.Destroy(_instanceRoot.gameObject); } _instanceRoot = null; _ownsInstanceRoot = false; } private static bool IsUnityAudioDisabled() { #if UNITY_EDITOR try { TypeInfo typeInfo = typeof(AudioSettings).GetTypeInfo(); PropertyInfo propertyInfo = typeInfo.GetDeclaredProperty("unityAudioDisabled"); return propertyInfo != null && (bool)propertyInfo.GetValue(null); } catch (Exception exception) { Log.Error(exception.ToString()); } #endif return false; } } }