using System;
using System.Collections.Generic;
using PhxhSDK;
using UnityEngine;
using PlayState = PhxhSDK.PlayState;
using AudioType = PhxhSDK.AudioType;
///
/// 音频播放器
/// 实现 IAudioPlayer(音频播放核心接口)、IAudioSpatial(3D音效)、IAudioFade(淡入淡出)接口
///
public class AudioPlayer : PhxhSDK.IAudioPlayer, PhxhSDK.IAudioSpatial, PhxhSDK.IAudioFade
{
#region Static Fields
private static int _audioIDCount;
private static long _curPlayID;
private static Dictionary _loadedAssetPaths = new();
#endregion
#region Core Fields
// Unity 组件
private GameObject _rootGo;
private AudioSource _audioSource;
private AudioClip _clip;
// 状态字段
private PlayState _playState;
private int _audioID;
private AudioType _audioType;
private bool _loop;
// 播放追踪
private long _playID = -1;
private long _playTime;
#endregion
#region Volume Fields
private float _targetVolume;
private float _normalizeVolume = 1f;
private bool _isSpeak;
#endregion
#region Effect Controllers
private AudioFadeEffect _fadeEffect;
private AudioSpatialController _spatialController;
#endregion
#region IAudioPlayer Interface Properties
public int AudioId => _audioID;
public PlayState State => _playState;
public bool IsPlaying => _playState == PlayState.Playing;
public AudioClip Clip => _clip;
public float Volume => _audioSource?.volume ?? 0f;
#endregion
#region Public Properties
public int audioID
{
get => _audioID;
protected set => _audioID = value;
}
public AudioType audioType
{
get => _audioType;
set => _audioType = value;
}
public bool loop
{
get => _loop;
set => _loop = value;
}
public GameObject RootGo => _rootGo;
public long PlayID => _playID;
public long PlayTime => _playTime;
public bool IsSpeak => _isSpeak;
#endregion
///
/// 判断播放是否已结束
///
private bool _isPlayEnd()
{
if (_audioSource == null || _clip == null)
return false;
return _audioSource.isPlaying
? _audioSource.time > _clip.length
: true;
}
#region Static Methods
///
/// 生成唯一的 AudioID
///
public static int GenAudioID()
{
return ++_audioIDCount;
}
///
/// 重置 AudioID 计数器
///
public static void ReleaseAudioIDs()
{
_audioIDCount = 0;
}
///
/// 重置 PlayID 计数器
///
public static void ResetPlayID()
{
_curPlayID = 0;
}
///
/// 释放所有已加载的资源
///
public static void ReleaseLoadedAssets()
{
foreach (var kv in _loadedAssetPaths)
{
var key = kv.Key;
var count = kv.Value;
while (count > 0)
{
AssetManager.Instance.Unload(key, false, true);
--count;
}
}
_loadedAssetPaths.Clear();
}
#endregion
#region Constructor
public AudioPlayer(AudioType audioType, GameObject rootGo, GameObject posGo, AudioInfo audioInfo,
bool loop = false, bool is3D = false)
{
// 初始化 audioID(从 AudioBase 构造函数)
audioID = GenAudioID();
this.audioType = audioType;
_rootGo = rootGo;
_audioSource = _getSource();
_audioSource.volume = AudioVolumeSettings.Instance.GlobalVolume;
_targetVolume = 1f;
_isSpeak = audioInfo.isSpeak;
// 初始化淡入淡出效果
_fadeEffect = new AudioFadeEffect(
fadeInDuration: AudioConstant.DefaultFadeInDuration,
fadeOutDuration: AudioConstant.DefaultFadeOutDuration);
// 初始化空间音效控制器
_spatialController = new AudioSpatialController(_rootGo.transform, _audioSource);
// 如果构造时指定了跟随对象,设置跟随
if (posGo != null)
{
_spatialController.SetFollowTarget(posGo);
}
}
private AudioSource _getSource()
{
var source = _rootGo.GetComponent();
if (!source)
{
source = _rootGo.AddComponent();
}
return source;
}
#endregion
#region Lifecycle
///
/// 生成新的 AudioID(重新初始化时使用)
///
public void GenNewID()
{
audioID = GenAudioID();
}
#endregion
#region Core Playback Methods
public void Update(float dt)
{
if (!_audioSource || !_clip)
{
return;
}
// 更新淡入淡出效果
bool shouldStop = _fadeEffect.Update(dt);
if (_playState == PlayState.Playing || _audioSource.isPlaying)
{
_setVolume();
}
if (_playState == PlayState.Paused)
{
return;
}
// 更新空间位置跟随
if (_playState == PlayState.Playing)
{
_spatialController.Update();
}
// 更新播放状态
// 检查是否应该停止(播放结束或淡出完成)
if (_isPlayEnd() || shouldStop || _playState == PlayState.Stopped)
{
if (_playState == PlayState.Loading)
return;
Stop();
}
}
public void Pause()
{
_audioSource.Pause();
_playState = PlayState.Paused;
}
public void Resume()
{
_audioSource.UnPause();
_playState = PlayState.Playing;
}
public void Play()
{
_playState = PlayState.Playing;
_fadeEffect.Stop(); // 停止任何淡入淡出效果
_targetVolume = 1f;
_audioSource.Play();
_updatePlayInfo(); // 使用统一的更新方法
}
public void Stop()
{
_fadeEffect.Stop(); // 停止淡入淡出效果
AudioManager.Instance.AudioMgrObj.RemovePlaying(audioType, audioID);
if (_isPlayEnd())
{
_playState = PlayState.Stopped;
return;
}
if (_audioSource)
{
_audioSource.Stop();
}
_playState = PlayState.Stopped;
}
#endregion
#region Private Helper Methods
///
/// 生成唯一的播放ID
///
private long _genPlayID() => ++_curPlayID;
///
/// 获取 AudioSource 组件
///
public AudioSource GetAudioSource() => _audioSource;
///
/// 统一的音量设置方法,根据音频类型和是否为语音自动选择对应的音量系数
///
private void _setVolume()
{
var settings = AudioVolumeSettings.Instance;
var fadeValue = _fadeEffect.FadeValue;
// 根据音频类型和是否为语音选择对应的音量系数
float typeVolume = audioType switch
{
AudioType.Music => settings.MusicVolume,
AudioType.Sound => _isSpeak ? settings.SpeakVolume : settings.SoundVolume,
_ => 1f
};
_audioSource.volume = fadeValue * typeVolume * settings.GlobalVolume * _targetVolume * _normalizeVolume;
}
#endregion
#region Public API Methods
///
/// 异步加载并播放音频(不带淡入淡出)
///
public void Play(string path)
{
_playState = PlayState.Loading;
_loadAndPlayInternal(path, useFade: false);
}
///
/// 异步加载并播放音频(带淡入淡出)
///
public void PlayWithFade(string path,
float fadeInTime = AudioConstant.DefaultFadeInDuration,
float fadeOutTime = AudioConstant.DefaultFadeOutDuration)
{
_playState = PlayState.Loading;
_loadAndPlayInternal(path, useFade: true, fadeInTime, fadeOutTime);
}
///
/// 统一的异步加载和播放逻辑(消除重复代码)
///
private async void _loadAndPlayInternal(string path, bool useFade,
float fadeInTime = AudioConstant.DefaultFadeInDuration,
float fadeOutTime = AudioConstant.DefaultFadeOutDuration)
{
// 1. 异步加载音频资源
_clip = await AssetManager.Instance.LoadAssetAsync(path, null, true);
// 2. 跟踪已加载的资源(用于后续卸载)
_trackLoadedAsset(path);
// 3. 加载失败处理
if (_clip == null)
{
_playState = PlayState.Stopped;
return;
}
// 4. 设置AudioSource
_audioSource.clip = _clip;
_targetVolume = 1f;
// 5. 根据是否需要淡入淡出设置音量
if (useFade)
{
// 淡入模式:从0音量开始,逐渐增加
_fadeEffect.FadeInDuration = fadeInTime;
_fadeEffect.FadeOutDuration = fadeOutTime;
_fadeEffect.StartFadeIn();
_audioSource.volume = 0f;
}
else
{
// 正常模式:停止淡入淡出效果
_fadeEffect.Stop();
}
// 6. 开始播放
_audioSource.Play();
_playState = PlayState.Playing;
// 7. 更新播放信息并通知Manager
_updatePlayInfo();
}
///
/// 跟踪已加载的资源(用于引用计数)
///
private void _trackLoadedAsset(string path)
{
if (_loadedAssetPaths.ContainsKey(path))
++_loadedAssetPaths[path];
else
_loadedAssetPaths.TryAdd(path, 1);
}
///
/// 更新播放信息(生成PlayID、时间戳、通知Manager)
///
private void _updatePlayInfo()
{
_playID = _genPlayID();
_playTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
AudioManager.Instance.AudioMgrObj.Add2Playing(audioType, audioID, _isSpeak);
}
///
/// 设置是否启用 3D 空间音效
///
public void SetIs3D(bool is3D = false)
{
if (is3D)
{
_spatialController.Enable3D();
}
else
{
_spatialController.Disable3D();
}
}
///
/// 设置跟随对象(自动启用 3D)
///
public void SetFollowTarget(GameObject target = null)
{
_spatialController.SetFollowTarget(target);
}
///
/// 设置静态位置(自动启用 3D)
///
public void SetPosition(Vector3 position)
{
_spatialController.SetStaticPosition(position, enable3D: true);
}
///
/// 设置音量(IAudioPlayer 接口实现)
///
public void SetVolume(float volume)
{
_targetVolume = Mathf.Clamp01(volume);
}
///
/// 设置循环播放,同步更新 AudioSource
///
public void SetLoop(bool isLoop)
{
loop = isLoop;
if (_audioSource != null)
_audioSource.loop = isLoop;
}
///
/// 设置是否为语音
///
public void SetIsSpeak(bool isSpeak) => _isSpeak = isSpeak;
///
/// 设置归一化音量
///
public void SetNormalizeVolume(float vol = 1f) => _normalizeVolume = vol;
///
/// 获取音频名称
///
public string GetCueSheetName() => _audioSource && _clip ? _clip.name : "";
///
/// 获取当前播放时间(秒)(IAudioPlayer 接口实现)
///
public float GetPlaybackTime() => _audioSource ? _audioSource.time : -1f;
///
/// 获取音频频谱数据(用于可视化)
///
public void GetSpectrumData(float[] spectrums)
{
if (_audioSource != null)
{
_audioSource.GetSpectrumData(spectrums, 0, FFTWindow.Blackman);
}
}
#endregion
#region Interface Implementations
// ========================================
// IAudioPlayer 接口完整实现
// AudioId, State, IsPlaying, Clip, Volume, Play/Pause/Resume/Stop, SetClip, SetVolume, SetLoop, GetPlaybackTime
// ========================================
///
/// 设置音频片段(IAudioPlayer 接口实现)
///
public void SetClip(AudioClip clip)
{
_clip = clip;
if (_audioSource != null)
_audioSource.clip = clip;
}
// 注:IAudioSpatial 的 SetPosition 和 SetFollowTarget 已在 API 区域实现
// ========================================
// IAudioFade 接口实现
// ========================================
///
/// 淡入播放(会自动调用Play)
///
/// 淡入时长(秒)
public void PlayWithFadeIn(float duration = AudioConstant.DefaultFadeInDuration)
{
if (_clip == null)
{
DebugUtil.LogError("AudioPlayer: Cannot PlayWithFadeIn without clip");
return;
}
_fadeEffect.FadeInDuration = duration;
_fadeEffect.StartFadeIn();
_playState = PlayState.Playing;
_audioSource.volume = 0f;
_audioSource.Play();
_updatePlayInfo(); // 使用统一的更新方法
}
///
/// 淡出停止(淡出完成后自动调用Stop)
///
/// 淡出时长(秒)
public void StopWithFadeOut(float duration = AudioConstant.DefaultFadeOutDuration)
{
_fadeEffect.StartFadeOut(duration);
}
#endregion
}