feat: SyncPoint.SameAsCurrentSegment music transition
- Add SameAsCurrentSegment mode to align new container's timeSamples with the old container's playback position, accounting for FadeInOffset - Fix BeatClock callback burst when Restart is called with a past dspTime - Add GetFirstLeafSource() for resolving playback position across nested containers - Manual BeatClock.Restart replaces OnContainerEntered subscription for accurate timing with SyncPoint
This commit is contained in:
@@ -6,6 +6,18 @@ using DG.Tweening;
|
||||
|
||||
namespace OCES.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// SyncPoint 协调状态,在 FadeOutBranch 和 FadeInBranch 之间共享。
|
||||
/// </summary>
|
||||
internal class SyncPointState
|
||||
{
|
||||
public SyncPoint Mode;
|
||||
public int BaseTimeSamples; // 读取旧 Source 时的 timeSamples
|
||||
public double BaseDspTime; // 读取旧 Source 时的 dspTime
|
||||
public int SampleRate; // 旧 Source 的采样率
|
||||
public bool Ready;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 与Transition无关的音量/生命周期管理逻辑
|
||||
/// </summary>
|
||||
@@ -47,16 +59,20 @@ namespace OCES.Audio
|
||||
/// <summary>
|
||||
/// 淡出分支:fire-and-forget,由调用方 StartCoroutine
|
||||
/// </summary>
|
||||
internal IEnumerator FadeOutBranch(ContainerPlayHandle outgoingHandle, float outgoingVolume, ITransitionConfig transition)
|
||||
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));
|
||||
@@ -68,23 +84,52 @@ namespace OCES.Audio
|
||||
/// 淡入分支:等待 FadeInOffset 后启动新音乐并淡入。
|
||||
/// 主协程 yield return 此分支,以便 DoTransition 在新音乐就绪后才结束。
|
||||
/// </summary>
|
||||
internal IEnumerator FadeInBranch(uint newContainerId, ITransitionConfig transition, Action onContainerStarted = null)
|
||||
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 != null && newSource.clip != null)
|
||||
{
|
||||
// 计算从读取旧 Source 到现在经过的 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)
|
||||
|
||||
Reference in New Issue
Block a user