224 lines
7.8 KiB
C#
224 lines
7.8 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using DG.Tweening;
|
|
|
|
namespace OCES.Audio
|
|
{
|
|
/// <summary>
|
|
/// SyncPoint 协调状态,在 FadeOutBranch 和 FadeInBranch 之间共享。
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 与Transition无关的音量/生命周期管理逻辑
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
public void StopCurrent()
|
|
{
|
|
StopHandle(CurrentHandle);
|
|
CurrentHandle = null;
|
|
CurrentContainerId = 0;
|
|
}
|
|
|
|
void StopHandle(ContainerPlayHandle handle)
|
|
{
|
|
if (handle == null) return;
|
|
this.m_player.Stop(handle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 淡出分支:fire-and-forget,由调用方 StartCoroutine
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 淡入分支:等待 FadeInOffset 后启动新音乐并淡入。
|
|
/// 主协程 yield return 此分支,以便 DoTransition 在新音乐就绪后才结束。
|
|
/// </summary>
|
|
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);
|
|
|
|
// SyncPoint: 获取新 Segment 的 AudioSource 并设置 timeSamples
|
|
if (syncState is { Mode: SyncPoint.SameAsCurrentSegment })
|
|
{
|
|
AudioSource newSource = CurrentHandle?.GetFirstLeafSource();
|
|
if (newSource && newSource.clip)
|
|
{
|
|
// 计算从读取Source Segment 到现在经过的 samples
|
|
double elapsedSeconds = AudioSettings.dspTime - syncState.BaseDspTime;
|
|
int elapsedSamples = (int)(elapsedSeconds * syncState.SampleRate);
|
|
int targetTimeSamples = syncState.BaseTimeSamples + elapsedSamples;
|
|
|
|
if (targetTimeSamples < newSource.clip.samples)
|
|
{
|
|
newSource.timeSamples = targetTimeSamples;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"[ChannelFader] SyncPoint.SameAsCurrentSegment: 新音频 samples({newSource.clip.samples}) <= 目标 samples({targetTimeSamples}),降级为 Start");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"[ChannelFader] SyncPoint.SameAsCurrentSegment: 未能获取新 Segment 的 AudioSource,降级为 Start");
|
|
}
|
|
syncState.Ready = true;
|
|
}
|
|
|
|
onContainerStarted?.Invoke();
|
|
|
|
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<AudioSource> 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);
|
|
}
|
|
|
|
IEnumerator FadeIn(ContainerPlayHandle handle, float duration)
|
|
{
|
|
if (handle == null || handle.Cancelled) yield break;
|
|
|
|
List<AudioSource> 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;
|
|
}
|
|
|
|
|
|
}
|
|
}
|