using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
namespace OCES.Audio
{
///
/// 负责递归播放一个 MusicContainer 树。
/// 被 MusicChannelPlayer 和 AmbienceChannelPlayer 共同使用。
/// 不持有状态机逻辑,只负责"把这个 Container 播完"。
///
class MusicContainerPlayer
{
readonly MusicContainerConfig m_containerConfig;
readonly MusicSegmentConfig m_segmentConfig;
readonly AudioSourcePool m_pool;
readonly MonoBehaviour m_coroutineHost;
UnityEngine.Audio.AudioMixerGroup m_mixerGroup;
///
/// 开始播放Container时触发 音乐回调系统
/// container本身,继承来的bpm,进入时刻的dspTime
///
internal event Action OnContainerEntered;
internal event Action OnBlendError;
// Sequence Step 模式的全局游标,key = containerId
readonly Dictionary m_sequenceStepIndex = new();
// Random 模式的不放回历史,key = containerId
readonly Dictionary> m_randomHistory = new();
public MusicContainerPlayer(
MusicContainerConfig containerConfig,
MusicSegmentConfig segmentConfig,
AudioSourcePool pool,
MonoBehaviour coroutineHost)
{
this.m_containerConfig = containerConfig;
this.m_segmentConfig = segmentConfig;
this.m_pool = pool;
this.m_coroutineHost = coroutineHost;
}
// ─────────────────────────────────────────────
// 公开入口
// ─────────────────────────────────────────────
///
/// 开始播放指定 Container,播放完毕后调用 onFinished。
/// 返回可用于外部停止的句柄。
///
internal ContainerPlayHandle Play(uint containerId, Action onFinished = null, float inheritedBpm = 0f)
{
MusicContainer container = this.m_containerConfig.QueryById(containerId);
if (container == null)
{
Debug.LogError($"[MusicContainerPlayer] 找不到 ContainerId: {containerId}");
onFinished?.Invoke();
return null;
}
ContainerPlayHandle handle = new();
handle.TargetVolume = 1f;
handle.Coroutine = this.m_coroutineHost.StartCoroutine(
PlayContainerCoroutine(container, handle, inheritedBpm, onFinished));
return handle;
}
///
/// 停止一个播放句柄(立即停止,不淡出)
///
public void Stop(ContainerPlayHandle handle)
{
if (handle == null) return;
handle.Cancelled = true;
foreach (ContainerPlayHandle child in handle.ChildHandles)
{
Stop(child);
}
handle.ChildHandles.Clear();
if (handle.Coroutine != null)
this.m_coroutineHost.StopCoroutine(handle.Coroutine);
foreach (AudioSource src in handle.ActiveSources)
{
ReturnSource(src);
}
handle.ActiveSources.Clear();
}
// ─────────────────────────────────────────────
// 核心协程
// ─────────────────────────────────────────────
IEnumerator PlayContainerCoroutine(
MusicContainer container,
ContainerPlayHandle handle,
float inheritedBpm,
Action onFinished)
{
float effectiveBpm = container.Bpm > 0f ? container.Bpm : inheritedBpm;
//Debug.Log($"[MusicContainerPlayer] OnContainerEntered firing, container={container.Id}, subscribers={OnContainerEntered != null}");
OnContainerEntered?.Invoke(container, effectiveBpm, AudioSettings.dspTime);
int loopsCompleted = 0;
while (true)
{
yield return this.m_coroutineHost.StartCoroutine(
PlayContainerOnce(container, handle.TargetVolume, handle, effectiveBpm));
if (handle.Cancelled) yield break;
loopsCompleted++;
// -1 = 无限循环,一直重复
if (container.LoopCount == -1)
continue;
// 0 = 不循环,播一次就结束
if (container.LoopCount == 0)
break;
// >= 1,播满次数后结束
if (loopsCompleted >= container.LoopCount)
break;
}
if (!handle.Cancelled)
onFinished?.Invoke();
}
///
/// 播放一个 Container 一轮(不含循环逻辑)
///
IEnumerator PlayContainerOnce(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
{
if (container.Segments == null || container.Segments.Count == 0)
yield break;
switch (container.ContainerType)
{
case ContainerType.Blend:
yield return PlayBlend(container, volumeScale, handle, inheritedBpm);
break;
case ContainerType.Sequence:
yield return PlaySequence(container, volumeScale, handle, inheritedBpm);
break;
case ContainerType.Random:
yield return PlayRandom(container, volumeScale, handle, inheritedBpm);
break;
}
}
// ─────────────────────────────────────────────
// Blend(同时播放)
// ─────────────────────────────────────────────
IEnumerator PlayBlend(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
{
IEnumerable tempos = container.Segments.Select(id =>
{
MusicContainer c = this.m_containerConfig.QueryById(id);
return c?.Bpm ?? 0f;
}).Where(b => b > 0f).Distinct();
if (tempos.Count() > 1)
{
OnBlendError?.Invoke(container);
}
// 同时启动所有子元素,等待全部结束
List childHandles = new();
bool allDone = false;
int remaining = container.Segments.Count;
foreach (uint segId in container.Segments)
{
if (handle.Cancelled) yield break;
ContainerPlayHandle childHandle = PlayChild(segId, volumeScale, () =>
{
remaining--;
if (remaining <= 0) allDone = true;
}, inheritedBpm);
if (childHandle != null)
{
childHandles.Add(childHandle);
handle.ChildHandles.AddRange(childHandles);
}
}
yield return new WaitUntil(() => allDone || handle.Cancelled);
if (handle.Cancelled)
{
foreach (var ch in childHandles)
Stop(ch);
}
}
// ─────────────────────────────────────────────
// Sequence
// ─────────────────────────────────────────────
IEnumerator PlaySequence(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
{
bool isStep = container.ContainerPlayMode;
if (isStep)
{
// Step: 每次只播一个,游标全局推进
int index = GetNextSequenceIndex(container);
yield return PlayChildAndWait(container.Segments[index], volumeScale, handle, inheritedBpm);
}
else
{
// Continuous: 顺序播完所有子元素,算一轮
for (int i = 0; i < container.Segments.Count; i++)
{
if (handle.Cancelled) yield break;
// StrategyParam 作为两段之间的间隔时间(秒)
if (i > 0 && container.StrategyParam > 0)
yield return new WaitForSeconds(container.StrategyParam);
yield return PlayChildAndWait(container.Segments[i], volumeScale, handle, inheritedBpm);
}
}
}
// ─────────────────────────────────────────────
// Random
// ─────────────────────────────────────────────
IEnumerator PlayRandom(MusicContainer container, float volumeScale, ContainerPlayHandle handle, float inheritedBpm)
{
bool isStep = container.ContainerPlayMode; // 同上,音乐系统默认 Continuous
if (isStep)
{
// Step Random: 随机选一个播放,算一次 loopCount
uint chosen = PickRandomChild(container);
yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm);
}
else
{
// Continuous Random: 随机不放回地播完所有子元素,算一轮
var remaining = new List(container.Segments);
while (remaining.Count > 0)
{
if (handle.Cancelled) yield break;
int idx = Random.Range(0, remaining.Count);
uint chosen = remaining[idx];
remaining.RemoveAt(idx);
if (container.StrategyParam > 0 && remaining.Count < container.Segments.Count - 1)
yield return new WaitForSeconds(container.StrategyParam);
yield return PlayChildAndWait(chosen, volumeScale, handle, inheritedBpm);
}
}
}
// ─────────────────────────────────────────────
// 子元素分发(Segment 或嵌套 Container)
// ─────────────────────────────────────────────
///
/// 播放一个子元素(segment 或 container),等待其完成后返回。
///
IEnumerator PlayChildAndWait(uint id, float volumeScale, ContainerPlayHandle parentHandle, float inheritedBpm)
{
bool done = false;
ContainerPlayHandle child = PlayChild(id, volumeScale, () => done = true, inheritedBpm);
if (child != null)
parentHandle.ChildHandles.Add(child);
yield return new WaitUntil(() => done || parentHandle.Cancelled);
if (parentHandle.Cancelled && child != null)
Stop(child);
}
///
/// 启动一个子元素的播放,不等待,返回句柄。
///
ContainerPlayHandle PlayChild(uint id, float volumeScale, Action onDone, float inheritedBpm)
{
// ID < 1000000 是 MusicSegment,否则是嵌套 Container
return id < 1000000u ? PlaySegment(id, volumeScale, onDone) : Play(id, onDone, inheritedBpm: inheritedBpm);
}
// ─────────────────────────────────────────────
// Segment 播放
// ─────────────────────────────────────────────
ContainerPlayHandle PlaySegment(uint segmentId, float volumeScale, Action onFinished)
{
MusicSegment segment = this.m_segmentConfig.QueryById(segmentId);
if (segment == null)
{
Debug.LogError($"[MusicContainerPlayer] 找不到 SegmentId: {segmentId}");
onFinished?.Invoke();
return null;
}
AudioClip clip = Resources.Load($"Audios/{segment.Name}");
if (!clip)
{
Debug.LogError($"[MusicContainerPlayer] 音频文件未找到: {segment.Name}");
onFinished?.Invoke();
return null;
}
AudioSource source = this.m_pool.AcquireAudioSource();
source.clip = clip;
source.loop = false;
source.volume = volumeScale;
source.Play();
var handle = new ContainerPlayHandle();
handle.ActiveSources.Add(source);
handle.Coroutine = this.m_coroutineHost.StartCoroutine(
WaitSegmentFinish(source, handle, onFinished));
return handle;
}
IEnumerator WaitSegmentFinish(AudioSource source, ContainerPlayHandle handle, Action onFinished)
{
yield return new WaitWhile(() => source.isPlaying && !handle.Cancelled);
source.Stop();
ReturnSource(source);
handle.ActiveSources.Remove(source);
if (!handle.Cancelled)
onFinished?.Invoke();
}
// ─────────────────────────────────────────────
// 工具
// ─────────────────────────────────────────────
void ReturnSource(AudioSource source)
{
source.Stop();
this.m_pool.ReturnToPool(source.gameObject);
}
int GetNextSequenceIndex(MusicContainer container)
{
int current = this.m_sequenceStepIndex.GetValueOrDefault(container.Id, 0);
this.m_sequenceStepIndex[container.Id] = (current + 1) % container.Segments.Count;
return current;
}
uint PickRandomChild(MusicContainer container)
{
if (!this.m_randomHistory.TryGetValue(container.Id, out HashSet history))
{
history = new HashSet();
this.m_randomHistory[container.Id] = history;
}
List available = container.Segments.Where(id => !history.Contains(id)).ToList();
if (available.Count == 0)
{
history.Clear();
available = new List(container.Segments);
}
uint chosen = available[Random.Range(0, available.Count)];
history.Add(chosen);
return chosen;
}
}
// ─────────────────────────────────────────────
// 播放句柄
// ─────────────────────────────────────────────
///
/// 一次 Container 播放的句柄,用于外部停止或淡出时访问正在播放的 AudioSource。
///
class ContainerPlayHandle
{
internal Coroutine Coroutine;
internal bool Cancelled;
internal float TargetVolume = 1f;
internal List ActiveSources = new();
internal List ChildHandles = new();
///
/// 递归收集所有正在发声的 AudioSource(用于淡出)
///
internal void CollectActiveSources(List result)
{
result.AddRange(this.ActiveSources);
foreach (ContainerPlayHandle child in this.ChildHandles)
child.CollectActiveSources(result);
}
///
/// 获取第一个正在播放的 Leaf Segment 的 AudioSource。
/// 递归遍历子句柄,优先取当前层级的 ActiveSources,找不到再深入子层级。
///
internal AudioSource GetFirstLeafSource()
{
if (this.ActiveSources.Count > 0) return this.ActiveSources[0];
foreach (ContainerPlayHandle child in this.ChildHandles)
{
AudioSource src = child.GetFirstLeafSource();
if (src) return src;
}
return null;
}
}
}