NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Framework/Audio/AudioPlayingPool.cs

514 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 音频播放池 - 动态大小(有上限),只包含活跃音频(播放中/暂停)
/// <para>职责:</para>
/// <para>- 按类型管理正在播放/暂停的 AudioPlayerMusic/Sound/Speak</para>
/// <para>- 动态上限,超出时停止最早的(移到停止池)</para>
/// <para>- 追踪播放顺序(用于 LRU 淘汰)</para>
/// <para>- 提供高效的查询、添加、移除操作</para>
/// <para>- 已停止的音频会移到停止池,不会留在播放池</para>
/// </summary>
public class AudioPlayingPool
{
#region Callbacks
/// <summary>
/// 当音频自然停止时触发的回调(用于自动移动到停止池)
/// <para>由 Update 检测并触发,确保一次性播放的音频能正确进入停止池</para>
/// </summary>
public Action<AudioPlayer> OnAudioStopped { get; set; }
#endregion
#region Fields
/// <summary>
/// 按类型分字典存储正在播放的 AudioPlayer
/// Key: AudioType (Music/Sound/Speak)
/// Value: Dictionary of AudioId -> AudioPlayer
/// </summary>
private readonly Dictionary<PhxhSDK.EAudioType, Dictionary<int, AudioPlayer>> _audioByType;
/// <summary>
/// 播放顺序追踪(用于找到最早播放的)
/// Key: AudioType
/// Value: Queue of AudioId (先进先出,队首是最早的)
/// </summary>
private readonly Dictionary<PhxhSDK.EAudioType, Queue<int>> _playOrderByType;
/// <summary>
/// 各类型的播放上限
/// </summary>
private readonly Dictionary<PhxhSDK.EAudioType, int> _maxLimit;
/// <summary>
/// 临时容器(复用,避免频繁 GC
/// 用于批量操作时复制集合,避免遍历时修改集合导致异常
/// </summary>
private readonly List<AudioPlayer> _tempAudioList;
/// <summary>
/// Key 索引 - 用于 O(1) 查找正在播放的音频
/// <para>Key: AudioType</para>
/// <para>Value: Dictionary of PlayingKey -> AudioId</para>
/// <para>用途:快速检测 Music/Speak 是否已在播放(防重播)</para>
/// </summary>
private readonly Dictionary<PhxhSDK.EAudioType, Dictionary<string, int>> _keyIndex;
#endregion
#region Init
public AudioPlayingPool()
{
// 初始化三种类型的字典
_audioByType = new Dictionary<PhxhSDK.EAudioType, Dictionary<int, AudioPlayer>>
{
{ PhxhSDK.EAudioType.Music, new Dictionary<int, AudioPlayer>() },
{ PhxhSDK.EAudioType.Sound, new Dictionary<int, AudioPlayer>() },
{ PhxhSDK.EAudioType.Speak, new Dictionary<int, AudioPlayer>() }
};
// 初始化播放顺序队列
_playOrderByType = new Dictionary<PhxhSDK.EAudioType, Queue<int>>
{
{ PhxhSDK.EAudioType.Music, new Queue<int>() },
{ PhxhSDK.EAudioType.Sound, new Queue<int>() },
{ PhxhSDK.EAudioType.Speak, new Queue<int>() }
};
// 初始化默认上限
_maxLimit = new Dictionary<PhxhSDK.EAudioType, int>
{
{ 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<AudioPlayer>(capacity: 128);
// 初始化 Key 索引(用于 O(1) 查找)
_keyIndex = new Dictionary<PhxhSDK.EAudioType, Dictionary<string, int>>
{
{ PhxhSDK.EAudioType.Music, new Dictionary<string, int>() },
{ PhxhSDK.EAudioType.Sound, new Dictionary<string, int>() },
{ PhxhSDK.EAudioType.Speak, new Dictionary<string, int>() }
};
}
#endregion
#region Public Methods - Configuration
/// <summary>
/// 设置播放池上限
/// </summary>
/// <param name="type">音频类型</param>
/// <param name="limit">上限数量</param>
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;
}
/// <summary>
/// 获取播放池上限
/// </summary>
public int GetLimit(PhxhSDK.EAudioType type)
{
return _maxLimit[type];
}
#endregion
#region Public Methods - Pool Management
/// <summary>
/// 检查播放池是否已满
/// </summary>
/// <param name="type">音频类型</param>
/// <returns>是否已满</returns>
public bool IsFull(PhxhSDK.EAudioType type)
{
return _audioByType[type].Count >= _maxLimit[type];
}
/// <summary>
/// 添加到播放池
/// </summary>
/// <param name="audio">要添加的 AudioPlayer</param>
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;
}
}
/// <summary>
/// 从播放池移除
/// </summary>
/// <param name="id">AudioPlayer 的 ID</param>
/// <returns>是否成功移除</returns>
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;
}
/// <summary>
/// 获取最早播放的 AudioPlayer用于复用
/// </summary>
/// <param name="type">音频类型</param>
/// <returns>最早的 AudioPlayer如果池为空则返回 null</returns>
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
/// <summary>
/// 通过 ID 获取 AudioPlayer
/// </summary>
/// <param name="id">AudioPlayer 的 ID</param>
/// <returns>找到的 AudioPlayer未找到则返回 null</returns>
public AudioPlayer GetById(int id)
{
foreach (var typeDict in _audioByType.Values)
{
if (typeDict.TryGetValue(id, out var audio))
return audio;
}
return null;
}
/// <summary>
/// 查找指定类型和 key 的正在播放的音频O(1) 查找)
/// <para>用于防重播检测Music/Speak 不重复播放)</para>
/// </summary>
/// <param name="type">音频类型</param>
/// <param name="key">音频配置 Key</param>
/// <returns>找到返回正在播放的 AudioPlayer否则返回 null</returns>
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;
}
/// <summary>
/// 检查 AudioPlayer 是否在播放池中
/// </summary>
/// <param name="id">AudioPlayer 的 ID</param>
/// <returns>是否存在</returns>
public bool Contains(int id)
{
foreach (var typeDict in _audioByType.Values)
{
if (typeDict.ContainsKey(id))
return true;
}
return false;
}
/// <summary>
/// 获取指定类型的播放中数量
/// </summary>
public int GetCount(PhxhSDK.EAudioType type)
{
return _audioByType[type].Count;
}
/// <summary>
/// 获取统计信息
/// </summary>
/// <returns>(Music 数量, Sound 数量, Speak 数量)</returns>
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
/// <summary>
/// 停止某类型的所有音频
/// 注意:使用复用容器,避免 Stop() 触发事件 → MoveToStoppedPool → 修改集合导致异常
/// </summary>
/// <param name="type">音频类型</param>
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
}
}
/// <summary>
/// 暂停某类型的所有音频
/// 注意:使用复用容器,避免潜在的集合修改
/// </summary>
public void PauseAllOfType(PhxhSDK.EAudioType type)
{
var audioDict = _audioByType[type];
// ✅ 使用复用容器,避免 GC
_tempAudioList.Clear();
_tempAudioList.AddRange(audioDict.Values);
foreach (var audio in _tempAudioList)
{
audio.Pause();
}
}
/// <summary>
/// 恢复某类型的所有音频
/// 注意:使用复用容器,避免潜在的集合修改
/// </summary>
public void ResumeAllOfType(PhxhSDK.EAudioType type)
{
var audioDict = _audioByType[type];
// ✅ 使用复用容器,避免 GC
_tempAudioList.Clear();
_tempAudioList.AddRange(audioDict.Values);
foreach (var audio in _tempAudioList)
{
audio.Resume();
}
}
/// <summary>
/// 停止所有音频
/// 注意:使用复用容器,避免 Stop() 触发事件 → MoveToStoppedPool → 修改集合导致异常
/// </summary>
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
}
}
/// <summary>
/// 获取指定类型的所有 AudioPlayer用于遍历
/// 注意:返回副本,避免外部在遍历时修改集合导致异常
/// 说明:此方法调用频率低,每次创建新 List 可接受
/// </summary>
public IEnumerable<AudioPlayer> GetAllOfType(PhxhSDK.EAudioType type)
{
// ⚠️ 不能使用 _tempAudioList会在下次调用时被 Clear
// ✅ 返回新副本,避免外部在遍历时 Stop 导致集合修改
return new List<AudioPlayer>(_audioByType[type].Values);
}
/// <summary>
/// 更新所有播放中的音频
/// 注意:使用复用容器,避免 Update 中音频自动停止时修改集合
/// 性能关键:此方法每帧调用,必须使用复用容器
/// </summary>
/// <param name="dt">Delta time</param>
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);
}
}
}
/// <summary>
/// 清空播放池(不销毁 AudioPlayer
/// </summary>
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();
}
}
/// <summary>
/// 释放所有 AudioPlayer 资源并清空播放池
/// <para>适用于场景卸载、关卡退出等需要彻底清理的场景</para>
/// </summary>
/// <returns>释放的 AudioPlayer 数量</returns>
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
}