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
}