using System; using System.Collections.Generic; using System.Threading; using Code.Infrastructure.Configs; using Code.Infrastructure.EventBus; using Cysharp.Threading.Tasks; using PrimeTween; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.Audio; namespace Code.Infrastructure.Audio { public interface IAudioService { AudioClip GetClip(AudioNames groupName, int id); void PlayTest(); UniTaskVoid PlayOneShot(AudioNames groupName, int id, AudioGroupType audioGroupType, Vector3 position = new Vector3(), AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null); UniTaskVoid PlayRepeatedly(AudioNames groupName, int id, AudioGroupType audioGroupType, AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null, CancellationTokenSource cancellationTokenSource = null); bool CheckClip(AudioNames groupName, int id); void CancelMusic(AudioNames groupName, int id); void ReleaseCancellationSource(AudioNames groupName, int id); void ApplyParameter(string effectName, float value, float time, Action callback = null); void ChangeGroupVolume(AudioGroupType groupType, float value, float time, Action callback = null); } public partial class AudioService : IAudioClipsEventHandle, IAudioService { private EventBus.EventBus _eventBus; private IAudioFactory _audioFactory; private IAudioSourcePool _pool; private Dictionary> _audioClips = new(); private AudioMixer _mixer; private Dictionary _audioMixerGroups = new(); public AudioService(EventBus.EventBus eventBus, IAudioFactory audioFactory, IAudioSourcePool pool, IConfigLocator configLocator) { _eventBus = eventBus; _audioFactory = audioFactory; _pool = pool; _eventBus.Subscribe(this); for (int i = 0; i < typeof(AudioNames).GetEnumValues().Length; i++) _audioClips.Add((AudioNames)i, new Dictionary()); LoadMixerAsset(configLocator.TryGetConfigAssetReference()).Forget(); } private partial void InitializeMixer(); private async UniTaskVoid LoadMixerAsset(AssetReference audioMixerConfig) { var audioMixer = await Addressables.LoadAssetAsync(audioMixerConfig); _mixer = audioMixer.mixerAsset; InitializeMixer(); _eventBus.RaiseEvent(x=> x.AudioServiceLoaded()); } public void PlayTest() { _pool.GetSource(AudioSourceType.NoBlend).PlayOneShot(GetClip(AudioNames.UISounds, 0)); } public async UniTaskVoid PlayOneShot(AudioNames groupName, int id, AudioGroupType audioGroupType, Vector3 position = new Vector3(), AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null) { if (!CheckClip(groupName, id)) return; if (!audioSource) audioSource = _pool.GetSource(sourceType); audioSource.transform.position = position; audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType]; audioSource.PlayOneShot(GetClip(groupName, id)); await UniTask.NextFrame(); await UniTask.WaitUntilValueChanged(audioSource, _ => !audioSource.isPlaying); _pool.ReleaseSource(audioSource); } private Dictionary _cancellationTokenSources = new(); public void CancelMusic(AudioNames groupName, int id) { var key = new AudioNameIdPair(groupName, id); if (!_cancellationTokenSources.ContainsKey(key)) return; _cancellationTokenSources[key].Cancel(); _cancellationTokenSources.Remove(key); } public void ReleaseCancellationSource(AudioNames groupName, int id) { // _cancellationTokenSources[new KeyValuePair(groupName, id)] = null; никаких полумер _cancellationTokenSources.Remove(new AudioNameIdPair(groupName, id)); } public async UniTaskVoid PlayRepeatedly(AudioNames groupName, int id, AudioGroupType audioGroupType, AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null, CancellationTokenSource cancellationTokenSource = null) { if (!CheckClip(groupName, id)) return; if(cancellationTokenSource == null) { cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSources[new AudioNameIdPair(groupName, id)] = cancellationTokenSource; } if (!audioSource) audioSource = _pool.GetSource(sourceType); if(!audioSource.resource) audioSource.resource = GetClip(groupName, id); audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType]; audioSource.Play(); // await UniTask.WaitUntil(audioSource, _ => audioSource.isPlaying); var (isCanceled, _) = await UniTask.WaitUntilValueChanged(audioSource, _ => !audioSource.isPlaying, cancellationToken: cancellationTokenSource.Token).SuppressCancellationThrow(); if (isCanceled) { cancellationTokenSource.Dispose(); _pool.ReleaseSource(audioSource); ReleaseCancellationSource(groupName,id); return; } PlayRepeatedly(groupName, id, AudioGroupType.Music, sourceType, audioSource, cancellationTokenSource).Forget(); } public AudioClip GetClip(AudioNames groupName, int id) => _audioClips[groupName][id]; public bool CheckClip(AudioNames groupName, int id) => _audioClips[groupName].ContainsKey(id); public void AudioClipsLoaded(AudioNames groupname, Dictionary clips) { _audioClips[groupname] = clips; _eventBus.RaiseEvent(x => x.OnReadyToPlay(groupname)); } public void AudioClipsUnLoaded(AudioNames groupName) { _audioClips[groupName].Clear(); _audioClips[groupName] = null; } public void ApplyParameter(string effectName, float value, float time, Action callback = null) { _mixer.GetFloat(effectName, out float startValue); Tween.Custom(startValue, value, time, newValue => { _mixer.SetFloat(effectName, newValue); }).OnComplete(callback); } public void ChangeGroupVolume(AudioGroupType groupType, float value, float time, Action callback = null) { ApplyParameter(_audioMixerGroups[groupType].name, Mathf.Log10(value == 0f ? 0.001f : value) * 20, time, callback); } private struct AudioNameIdPair : IEquatable { public AudioNames AudioNames; public int ID; public AudioNameIdPair(AudioNames audioNames, int id) { AudioNames = audioNames; ID = id; } public bool Equals(AudioNameIdPair other) { return AudioNames == other.AudioNames && ID == other.ID; } public override bool Equals(object obj) { return obj is AudioNameIdPair other && Equals(other); } public override int GetHashCode() { return HashCode.Combine((int)AudioNames, ID); } } } public interface IReadyToPlayHandle : IGlobalSubscriber { void OnReadyToPlay(AudioNames groupName); } public interface IAudioServiceHandle : IGlobalSubscriber { void AudioServiceLoaded(); } }