using System; using System.Collections.Generic; using UnityEngine; /// /// 音频播放池 - 动态大小(有上限),只包含活跃音频(播放中/暂停) /// 职责: /// - 按类型管理正在播放/暂停的 AudioPlayer(Music/Sound/Speak) /// - 动态上限,超出时停止最早的(移到停止池) /// - 追踪播放顺序(用于 LRU 淘汰) /// - 提供高效的查询、添加、移除操作 /// - 已停止的音频会移到停止池,不会留在播放池 /// public class AudioPlayingPool { #region Callbacks /// /// 当音频自然停止时触发的回调(用于自动移动到停止池) /// 由 Update 检测并触发,确保一次性播放的音频能正确进入停止池 /// public Action OnAudioStopped { get; set; } #endregion #region Fields /// /// 按类型分字典存储正在播放的 AudioPlayer /// Key: AudioType (Music/Sound/Speak) /// Value: Dictionary of AudioId -> AudioPlayer /// private readonly Dictionary> _audioByType; /// /// 播放顺序追踪(用于找到最早播放的) /// Key: AudioType /// Value: Queue of AudioId (先进先出,队首是最早的) /// private readonly Dictionary> _playOrderByType; /// /// 各类型的播放上限 /// private readonly Dictionary _maxLimit; /// /// 临时容器(复用,避免频繁 GC) /// 用于批量操作时复制集合,避免遍历时修改集合导致异常 /// private readonly List _tempAudioList; /// /// Key 索引 - 用于 O(1) 查找正在播放的音频 /// Key: AudioType /// Value: Dictionary of PlayingKey -> AudioId /// 用途:快速检测 Music/Speak 是否已在播放(防重播) /// private readonly Dictionary> _keyIndex; #endregion #region Init public AudioPlayingPool() { // 初始化三种类型的字典 _audioByType = new Dictionary> { { PhxhSDK.EAudioType.Music, new Dictionary() }, { PhxhSDK.EAudioType.Sound, new Dictionary() }, { PhxhSDK.EAudioType.Speak, new Dictionary() } }; // 初始化播放顺序队列 _playOrderByType = new Dictionary> { { PhxhSDK.EAudioType.Music, new Queue() }, { PhxhSDK.EAudioType.Sound, new Queue() }, { PhxhSDK.EAudioType.Speak, new Queue() } }; // 初始化默认上限 _maxLimit = new Dictionary { { PhxhSDK.EAudioType.Music, AudioConstant.MusicPlayingLimit }, // 默认 1 { PhxhSDK.EAudioType.Sound, AudioConstant.SoundPlayingLimit }, // 默认 100 { PhxhSDK.EAudioType.Speak, AudioConstant.SpeakPlayingLimit } // 默认 1 }; // ✅ 初始化临时容器(预分配容量,避免扩容) // 最大容量 = Music(1) + Sound(100) + Speak(1) = 102 _tempAudioList = new List(capacity: 128); // 初始化 Key 索引(用于 O(1) 查找) _keyIndex = new Dictionary> { { PhxhSDK.EAudioType.Music, new Dictionary() }, { PhxhSDK.EAudioType.Sound, new Dictionary() }, { PhxhSDK.EAudioType.Speak, new Dictionary() } }; } #endregion #region Public Methods - Configuration /// /// 设置播放池上限 /// /// 音频类型 /// 上限数量 public void SetLimit(PhxhSDK.EAudioType type, int limit) { if (limit < 0) { Debug.LogWarning($"[AudioPlayingPool] Invalid limit {limit} for {type}, using 0"); limit = 0; } _maxLimit[type] = limit; } /// /// 获取播放池上限 /// public int GetLimit(PhxhSDK.EAudioType type) { return _maxLimit[type]; } #endregion #region Public Methods - Pool Management /// /// 检查播放池是否已满 /// /// 音频类型 /// 是否已满 public bool IsFull(PhxhSDK.EAudioType type) { return _audioByType[type].Count >= _maxLimit[type]; } /// /// 添加到播放池 /// /// 要添加的 AudioPlayer public void Add(AudioPlayer audio) { if (audio == null) { Debug.LogWarning("[AudioPlayingPool] Cannot add null audio"); return; } var type = audio.AudioType; var id = audio.AudioId; // 确保 GameObject 是启用的(播放池中的音频应该是活跃的) audio.SetActive(true); // 检查是否已存在 if (_audioByType[type].ContainsKey(id)) { // 已存在,只需更新播放顺序 // 注意:这里不移除旧的顺序,因为 Queue 不支持高效移除 // 在 GetOldest 时会处理重复的情况 _playOrderByType[type].Enqueue(id); return; } // 加入字典 _audioByType[type][id] = audio; // 记录播放顺序 _playOrderByType[type].Enqueue(id); // 更新 Key 索引(用于 O(1) 查找) if (!string.IsNullOrEmpty(audio.CurrentPlayingKey)) { _keyIndex[type][audio.CurrentPlayingKey] = id; } } /// /// 从播放池移除 /// /// AudioPlayer 的 ID /// 是否成功移除 public bool Remove(int id) { // 遍历所有类型查找并移除 foreach (var kvp in _audioByType) { var type = kvp.Key; var typeDict = kvp.Value; if (typeDict.TryGetValue(id, out var audio)) { // 从字典中移除 typeDict.Remove(id); // 清理 Key 索引 if (!string.IsNullOrEmpty(audio.CurrentPlayingKey)) { var keyDict = _keyIndex[type]; if (keyDict.TryGetValue(audio.CurrentPlayingKey, out var indexedId) && indexedId == id) { keyDict.Remove(audio.CurrentPlayingKey); } } return true; } } return false; } /// /// 获取最早播放的 AudioPlayer(用于复用) /// /// 音频类型 /// 最早的 AudioPlayer,如果池为空则返回 null public AudioPlayer GetOldest(PhxhSDK.EAudioType type) { var orderQueue = _playOrderByType[type]; var audioDict = _audioByType[type]; if (orderQueue.Count == 0) return null; // 循环查找最早的有效 AudioPlayer // (因为 Queue 中可能有重复或已移除的 ID) while (orderQueue.Count > 0) { int oldestId = orderQueue.Dequeue(); if (audioDict.TryGetValue(oldestId, out var audio)) { return audio; } // 如果 ID 已不在字典中,继续查找下一个 } return null; } #endregion #region Public Methods - Query /// /// 通过 ID 获取 AudioPlayer /// /// AudioPlayer 的 ID /// 找到的 AudioPlayer,未找到则返回 null public AudioPlayer GetById(int id) { foreach (var typeDict in _audioByType.Values) { if (typeDict.TryGetValue(id, out var audio)) return audio; } return null; } /// /// 查找指定类型和 key 的正在播放的音频(O(1) 查找) /// 用于防重播检测(Music/Speak 不重复播放) /// /// 音频类型 /// 音频配置 Key /// 找到返回正在播放的 AudioPlayer,否则返回 null public AudioPlayer FindPlayingByKey(PhxhSDK.EAudioType type, string key) { if (string.IsNullOrEmpty(key)) return null; // O(1) 查找:从 Key 索引中获取 AudioId var keyDict = _keyIndex[type]; if (!keyDict.TryGetValue(key, out var audioId)) return null; // O(1) 查找:从 AudioId 获取 AudioPlayer var audioDict = _audioByType[type]; if (!audioDict.TryGetValue(audioId, out var audio)) { // 索引不一致,清理 keyDict.Remove(key); return null; } // 检查是否正在播放(不包括暂停状态) if (!audio.IsPlaying) { // 已暂停或停止,清理索引 keyDict.Remove(key); return null; } return audio; } /// /// 检查 AudioPlayer 是否在播放池中 /// /// AudioPlayer 的 ID /// 是否存在 public bool Contains(int id) { foreach (var typeDict in _audioByType.Values) { if (typeDict.ContainsKey(id)) return true; } return false; } /// /// 获取指定类型的播放中数量 /// public int GetCount(PhxhSDK.EAudioType type) { return _audioByType[type].Count; } /// /// 获取统计信息 /// /// (Music 数量, Sound 数量, Speak 数量) public (int music, int sound, int speak) GetStats() { return (_audioByType[PhxhSDK.EAudioType.Music].Count, _audioByType[PhxhSDK.EAudioType.Sound].Count, _audioByType[PhxhSDK.EAudioType.Speak].Count); } #endregion #region Public Methods - Batch Operations /// /// 停止某类型的所有音频 /// 注意:使用复用容器,避免 Stop() 触发事件 → MoveToStoppedPool → 修改集合导致异常 /// /// 音频类型 public void StopAllOfType(PhxhSDK.EAudioType type) { var audioDict = _audioByType[type]; // ✅ 使用复用容器,避免 GC _tempAudioList.Clear(); _tempAudioList.AddRange(audioDict.Values); foreach (var audio in _tempAudioList) { audio.Stop(); // Stop 触发 OnStopped 事件 → OnAudioStopped → MoveToStoppedPool } } /// /// 暂停某类型的所有音频 /// 注意:使用复用容器,避免潜在的集合修改 /// public void PauseAllOfType(PhxhSDK.EAudioType type) { var audioDict = _audioByType[type]; // ✅ 使用复用容器,避免 GC _tempAudioList.Clear(); _tempAudioList.AddRange(audioDict.Values); foreach (var audio in _tempAudioList) { audio.Pause(); } } /// /// 恢复某类型的所有音频 /// 注意:使用复用容器,避免潜在的集合修改 /// public void ResumeAllOfType(PhxhSDK.EAudioType type) { var audioDict = _audioByType[type]; // ✅ 使用复用容器,避免 GC _tempAudioList.Clear(); _tempAudioList.AddRange(audioDict.Values); foreach (var audio in _tempAudioList) { audio.Resume(); } } /// /// 停止所有音频 /// 注意:使用复用容器,避免 Stop() 触发事件 → MoveToStoppedPool → 修改集合导致异常 /// public void StopAll() { // ✅ 使用复用容器收集所有 AudioPlayer _tempAudioList.Clear(); foreach (var typeDict in _audioByType.Values) { _tempAudioList.AddRange(typeDict.Values); } foreach (var audio in _tempAudioList) { audio.Stop(); // Stop 触发 OnStopped 事件 → OnAudioStopped → MoveToStoppedPool } } /// /// 获取指定类型的所有 AudioPlayer(用于遍历) /// 注意:返回副本,避免外部在遍历时修改集合导致异常 /// 说明:此方法调用频率低,每次创建新 List 可接受 /// public IEnumerable GetAllOfType(PhxhSDK.EAudioType type) { // ⚠️ 不能使用 _tempAudioList(会在下次调用时被 Clear) // ✅ 返回新副本,避免外部在遍历时 Stop 导致集合修改 return new List(_audioByType[type].Values); } /// /// 更新所有播放中的音频 /// 注意:使用复用容器,避免 Update 中音频自动停止时修改集合 /// 性能关键:此方法每帧调用,必须使用复用容器 /// /// Delta time public void Update(float dt) { // ✅ 使用复用容器收集所有 AudioPlayer(每帧调用,减少 GC) _tempAudioList.Clear(); foreach (var typeDict in _audioByType.Values) { _tempAudioList.AddRange(typeDict.Values); } foreach (var audio in _tempAudioList) { // 记录更新前的播放状态 var wasPlaying = audio.IsPlaying || audio.State == PhxhSDK.PlayState.Paused; // 更新音频(可能导致音频自动停止) audio.Update(dt); // ✅ 检测是否刚停止:之前是播放/暂停,现在是停止 if (wasPlaying && audio.State == PhxhSDK.PlayState.Stopped) { // 触发回调通知 AudioManager 处理(移动到停止池) OnAudioStopped?.Invoke(audio); } } } /// /// 清空播放池(不销毁 AudioPlayer) /// public void Clear() { foreach (var typeDict in _audioByType.Values) { typeDict.Clear(); } foreach (var queue in _playOrderByType.Values) { queue.Clear(); } foreach (var keyDict in _keyIndex.Values) { keyDict.Clear(); } } /// /// 释放所有 AudioPlayer 资源并清空播放池 /// 适用于场景卸载、关卡退出等需要彻底清理的场景 /// /// 释放的 AudioPlayer 数量 public int DisposeAll() { int count = 0; // 遍历所有类型的音频 foreach (var typeDict in _audioByType.Values) { // 释放每个 AudioPlayer foreach (var audio in typeDict.Values) { audio.Dispose(); count++; } typeDict.Clear(); } // 清空播放顺序队列 foreach (var queue in _playOrderByType.Values) { queue.Clear(); } return count; } #endregion }