using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; namespace OCES.Audio { /// /// SyncPoint 协调状态,在 FadeOutBranch 和 FadeInBranch 之间共享。 /// internal class SyncPointState { public SyncPoint Mode; public int BaseTimeSamples; // 读取Source Segment 的 timeSamples public double BaseDspTime; // 读取Source Segment 的 dspTime public int SampleRate; // Source Segment 的采样率 public bool Ready; public double SourceStartOffset; public double TargetAudioTime; public double StartPlayTime; } /// /// 与Transition无关的音量/生命周期管理逻辑 /// public class ChannelFader { readonly LongAudioContainerPlayer m_player; readonly MonoBehaviour m_coroutineHost; internal ContainerPlayHandle CurrentHandle { get; private set; } public uint CurrentContainerId { get; private set; } public float CurrentVolume { get; private set; } internal ChannelFader(LongAudioContainerPlayer player, MonoBehaviour coroutineHost) { this.m_player = player; this.m_coroutineHost = coroutineHost; } void StartNew(uint containerId, float startVolume) { CurrentContainerId = containerId; CurrentVolume = startVolume; CurrentHandle = this.m_player.Play(containerId); //Debug.Log($"[ChannelFader] StartNew: containerId={containerId}, CurrentHandle={CurrentHandle}"); } public void StopCurrent() { //Debug.Log($"[ChannelFader] StopCurrent called! CurrentHandle={CurrentHandle}, stack=\n{Environment.StackTrace}"); StopHandle(CurrentHandle); CurrentHandle = null; CurrentContainerId = 0; } void StopHandle(ContainerPlayHandle handle) { if (handle == null) return; this.m_player.Stop(handle); } /// /// 淡出分支:fire-and-forget,由调用方 StartCoroutine /// internal IEnumerator FadeOutBranch( ContainerPlayHandle outgoingHandle, float outgoingVolume, ITransitionConfig transition, SyncPointState syncState = null) { if (outgoingHandle == null) yield break; if (transition?.FadeOutOffset > 0f) { //Debug.Log($"Waiting for {transition.FadeOutOffset} to fade out."); yield return new WaitForSeconds(transition.FadeOutOffset); } if (transition?.FadeOutTime > 0f ) { yield return this.m_coroutineHost.StartCoroutine( FadeOut(outgoingHandle, outgoingVolume, transition.FadeOutTime)); } else { StopHandle(outgoingHandle); } } /// /// 淡入分支:等待 FadeInOffset 后启动新音乐并淡入。 /// 主协程 yield return 此分支,以便 DoTransition 在新音乐就绪后才结束。 /// internal IEnumerator FadeInBranch(uint newContainerId, ITransitionConfig transition, SyncPointState syncState = null, Action onContainerStarted = null) { if (transition?.FadeInOffset > 0f) { //Debug.Log($"Waiting {transition.FadeInOffset} to fade in."); yield return new WaitForSeconds(transition.FadeInOffset); } if (newContainerId == 0) { CurrentHandle = null; CurrentContainerId = 0; if (syncState != null) syncState.Ready = true; yield break; } float startVolume = transition?.FadeInTime > 0f ? 0f : 1f; StartNew(newContainerId, startVolume); onContainerStarted?.Invoke(); switch (syncState) { // SyncPoint: 获取新 Segment 的 AudioSource 并设置 timeSamples case { Mode: SyncPoint.SameAsCurrentSegment }: { AudioSource newSource = CurrentHandle?.GetFirstLeafSource(); if (newSource && newSource.clip) { double targetAudioTime = syncState.TargetAudioTime; if (targetAudioTime > 0) { int targetTimeSamples = (int)(targetAudioTime * newSource.clip.frequency); if (targetTimeSamples >= 0 && targetTimeSamples < newSource.clip.samples) { newSource.timeSamples = targetTimeSamples; } else { Debug.LogError( $"[ChannelFader] SyncPoint: 目标 samples({targetTimeSamples}) 超出范围 [0, {newSource.clip.samples}),降级为 Start"); } } } else { Debug.LogError($"[ChannelFader] SyncPoint.SameAsCurrentSegment: 未能获取新 Segment 的 AudioSource,降级为 Start"); } syncState.Ready = true; break; } case { Mode: SyncPoint.Start, StartPlayTime: > 0 }: { AudioSource newSource = CurrentHandle?.GetFirstLeafSource(); if (!newSource || !newSource.clip) yield break; //在赋值的时候减过fade in time了 int targetTimeSamples = (int)(syncState.StartPlayTime * newSource.clip.frequency); if (targetTimeSamples >= 0 && targetTimeSamples < newSource.clip.samples) { newSource.timeSamples = targetTimeSamples; } break; } } if (transition?.FadeInTime > 0f) { yield return this.m_coroutineHost.StartCoroutine( FadeIn(CurrentHandle, transition.FadeInTime)); } } IEnumerator FadeOut(ContainerPlayHandle handle, float fromVolume, float duration) { // Debug.Log($"Fading out in {duration} seconds."); if (handle == null || handle.Cancelled) yield break; float elapsed = 0f; List sources = new(); handle.CollectActiveSources(sources); while (elapsed < duration) { if (handle.Cancelled) break; elapsed += Time.deltaTime; // 为每个 source 创建 DOFade(仅创建一次) sources.Clear(); handle.CollectActiveSources(sources); foreach (AudioSource src in sources) { if (!src) continue; if (DOTween.IsTweening(src)) continue; src.volume = fromVolume; src.DOFade(0f, duration - elapsed).SetEase(Ease.InSine); //TODO 支持读表 } yield return null; } // 确保最终状态 sources.Clear(); handle.CollectActiveSources(sources); foreach (AudioSource src in sources) if (src) src.volume = 0f; StopHandle(handle); // Debug.Log($"[ChannelFader] Faded out in {duration} seconds."); } IEnumerator FadeIn(ContainerPlayHandle handle, float duration) { // Debug.Log($"Fading in in {duration} seconds."); if (handle == null || handle.Cancelled) yield break; List sources = new(); float elapsed = 0f; while (elapsed < duration) { if (handle.Cancelled) yield break; elapsed += Time.deltaTime; // 每帧收集当前活跃的 sources,并为新加入的 source 创建 tween sources.Clear(); handle.CollectActiveSources(sources); foreach (AudioSource src in sources) { if (!src) continue; // 如果这个 source 还没被 tween 过,则创建一个 DOFade if (DOTween.IsTweening(src)) continue; src.volume = 0f; src.DOFade(1f, duration - elapsed).SetEase(Ease.OutSine); //TODO 支持读表 } yield return null; } // 确保最终音量 sources.Clear(); handle.CollectActiveSources(sources); foreach (AudioSource src in sources) if (src) src.volume = 1f; CurrentVolume = 1f; // Debug.Log($"[ChannelFader] Faded in in {duration} seconds."); } } }