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

545 lines
14 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 PhxhSDK;
using UnityEngine;
using PlayState = PhxhSDK.PlayState;
using AudioType = PhxhSDK.AudioType;
/// <summary>
/// 音频播放器
/// 实现 IAudioPlayer音频播放核心接口、IAudioSpatial3D音效、IAudioFade淡入淡出接口
/// </summary>
public class AudioPlayer : PhxhSDK.IAudioPlayer, PhxhSDK.IAudioSpatial, PhxhSDK.IAudioFade
{
#region Static Fields
private static int _audioIDCount;
private static long _curPlayID;
private static Dictionary<string, int> _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
/// <summary>
/// 判断播放是否已结束
/// </summary>
private bool _isPlayEnd()
{
if (_audioSource == null || _clip == null)
return false;
return _audioSource.isPlaying
? _audioSource.time > _clip.length
: true;
}
#region Static Methods
/// <summary>
/// 生成唯一的 AudioID
/// </summary>
public static int GenAudioID()
{
return ++_audioIDCount;
}
/// <summary>
/// 重置 AudioID 计数器
/// </summary>
public static void ReleaseAudioIDs()
{
_audioIDCount = 0;
}
/// <summary>
/// 重置 PlayID 计数器
/// </summary>
public static void ResetPlayID()
{
_curPlayID = 0;
}
/// <summary>
/// 释放所有已加载的资源
/// </summary>
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<AudioSource>();
if (!source)
{
source = _rootGo.AddComponent<AudioSource>();
}
return source;
}
#endregion
#region Lifecycle
/// <summary>
/// 生成新的 AudioID重新初始化时使用
/// </summary>
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
/// <summary>
/// 生成唯一的播放ID
/// </summary>
private long _genPlayID() => ++_curPlayID;
/// <summary>
/// 获取 AudioSource 组件
/// </summary>
public AudioSource GetAudioSource() => _audioSource;
/// <summary>
/// 统一的音量设置方法,根据音频类型和是否为语音自动选择对应的音量系数
/// </summary>
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
/// <summary>
/// 异步加载并播放音频(不带淡入淡出)
/// </summary>
public void Play(string path)
{
_playState = PlayState.Loading;
_loadAndPlayInternal(path, useFade: false);
}
/// <summary>
/// 异步加载并播放音频(带淡入淡出)
/// </summary>
public void PlayWithFade(string path,
float fadeInTime = AudioConstant.DefaultFadeInDuration,
float fadeOutTime = AudioConstant.DefaultFadeOutDuration)
{
_playState = PlayState.Loading;
_loadAndPlayInternal(path, useFade: true, fadeInTime, fadeOutTime);
}
/// <summary>
/// 统一的异步加载和播放逻辑(消除重复代码)
/// </summary>
private async void _loadAndPlayInternal(string path, bool useFade,
float fadeInTime = AudioConstant.DefaultFadeInDuration,
float fadeOutTime = AudioConstant.DefaultFadeOutDuration)
{
// 1. 异步加载音频资源
_clip = await AssetManager.Instance.LoadAssetAsync<AudioClip>(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();
}
/// <summary>
/// 跟踪已加载的资源(用于引用计数)
/// </summary>
private void _trackLoadedAsset(string path)
{
if (_loadedAssetPaths.ContainsKey(path))
++_loadedAssetPaths[path];
else
_loadedAssetPaths.TryAdd(path, 1);
}
/// <summary>
/// 更新播放信息生成PlayID、时间戳、通知Manager
/// </summary>
private void _updatePlayInfo()
{
_playID = _genPlayID();
_playTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
AudioManager.Instance.AudioMgrObj.Add2Playing(audioType, audioID, _isSpeak);
}
/// <summary>
/// 设置是否启用 3D 空间音效
/// </summary>
public void SetIs3D(bool is3D = false)
{
if (is3D)
{
_spatialController.Enable3D();
}
else
{
_spatialController.Disable3D();
}
}
/// <summary>
/// 设置跟随对象(自动启用 3D
/// </summary>
public void SetFollowTarget(GameObject target = null)
{
_spatialController.SetFollowTarget(target);
}
/// <summary>
/// 设置静态位置(自动启用 3D
/// </summary>
public void SetPosition(Vector3 position)
{
_spatialController.SetStaticPosition(position, enable3D: true);
}
/// <summary>
/// 设置音量IAudioPlayer 接口实现)
/// </summary>
public void SetVolume(float volume)
{
_targetVolume = Mathf.Clamp01(volume);
}
/// <summary>
/// 设置循环播放,同步更新 AudioSource
/// </summary>
public void SetLoop(bool isLoop)
{
loop = isLoop;
if (_audioSource != null)
_audioSource.loop = isLoop;
}
/// <summary>
/// 设置是否为语音
/// </summary>
public void SetIsSpeak(bool isSpeak) => _isSpeak = isSpeak;
/// <summary>
/// 设置归一化音量
/// </summary>
public void SetNormalizeVolume(float vol = 1f) => _normalizeVolume = vol;
/// <summary>
/// 获取音频名称
/// </summary>
public string GetCueSheetName() => _audioSource && _clip ? _clip.name : "";
/// <summary>
/// 获取当前播放时间IAudioPlayer 接口实现)
/// </summary>
public float GetPlaybackTime() => _audioSource ? _audioSource.time : -1f;
/// <summary>
/// 获取音频频谱数据(用于可视化)
/// </summary>
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
// ========================================
/// <summary>
/// 设置音频片段IAudioPlayer 接口实现)
/// </summary>
public void SetClip(AudioClip clip)
{
_clip = clip;
if (_audioSource != null)
_audioSource.clip = clip;
}
// 注IAudioSpatial 的 SetPosition 和 SetFollowTarget 已在 API 区域实现
// ========================================
// IAudioFade 接口实现
// ========================================
/// <summary>
/// 淡入播放会自动调用Play
/// </summary>
/// <param name="duration">淡入时长(秒)</param>
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(); // 使用统一的更新方法
}
/// <summary>
/// 淡出停止淡出完成后自动调用Stop
/// </summary>
/// <param name="duration">淡出时长(秒)</param>
public void StopWithFadeOut(float duration = AudioConstant.DefaultFadeOutDuration)
{
_fadeEffect.StartFadeOut(duration);
}
#endregion
}