feat: StartOffset

- 实现startOffset
- 修复EndOffset = 0 + 循环播放时,音乐会大量重复播放的错误。
- 增加数据校验和 BeatClock 联动。StartOffset不正确时停止bar+级 callback。
- BeatClock 现在会在每次重新播放时重启,以解决EndOffset配置错误被舍弃时,拍子对不上的问题。
This commit is contained in:
2026-05-12 15:47:04 +08:00
parent bb472da311
commit 8b6fabda12
10 changed files with 143 additions and 60 deletions
@@ -47,6 +47,7 @@ namespace OCES.Audio
this.m_beatClock = new BeatClock(coroutineHost, onBeat, onBar, onGrid);
player.OnBlendError += this.m_beatClock.OnBlendError;
player.OnContainerLooped += OnContainerLooped;
}
// ─────────────────────────────────────────────
@@ -58,12 +59,11 @@ namespace OCES.Audio
/// </summary>
internal void SwitchTo(uint newContainerId)
{
Debug.Log($"[MusicChannelPlayer] SwitchTo({newContainerId}): CurrentContainerId={this.m_fader.CurrentContainerId}, CurrentHandle={this.m_fader.CurrentHandle}");
//Debug.Log($"[MusicChannelPlayer] SwitchTo({newContainerId}): CurrentContainerId={this.m_fader.CurrentContainerId}, CurrentHandle={this.m_fader.CurrentHandle}");
if (newContainerId == this.m_fader.CurrentContainerId && this.m_fader.CurrentHandle != null)
return; // 已经在播目标,无需切换
MusicTransition transition = ResolveTransition((int)this.m_fader.CurrentContainerId, (int)newContainerId);
//Debug.Log($"[MusicChannelPlayer] Switch from {this.m_fader.CurrentContainerId} to {newContainerId} with transition {transition.Id}");
if (this.m_transitionCoroutine != null)
this.m_coroutineHost.StopCoroutine(this.m_transitionCoroutine);
@@ -91,6 +91,7 @@ namespace OCES.Audio
IEnumerator DoTransition(uint newContainerId, MusicTransition transition)
{
// Debug.Log($"[MusicChannelPlayer] Scheduled transition from {this.m_fader.CurrentContainerId} to {newContainerId} with transition {transition.Id}");
if (this.m_currentFadeInCoroutine != null)
{
this.m_coroutineHost.StopCoroutine(this.m_currentFadeInCoroutine);
@@ -114,14 +115,15 @@ namespace OCES.Audio
if (syncState.Mode == SyncPoint.SameAsCurrentSegment)
{
Debug.Log($"[MusicChannelPlayer] DoTransition L117: CurrentHandle={this.m_fader.CurrentHandle}, CurrentContainerId={this.m_fader.CurrentContainerId}");
// Debug.Log($"[MusicChannelPlayer] DoTransition L117: CurrentHandle={this.m_fader.CurrentHandle}, CurrentContainerId={this.m_fader.CurrentContainerId}");
// 旧 Container 不存在(如游戏首次启动),静默降级为 Start
if (this.m_fader.CurrentHandle == null)
{
syncState.Mode = SyncPoint.Start;
}
// Blend 类型不支持 SameAsCurrentSegment,降级为 Start
else if (this.m_currentContainer is { ContainerType: ContainerType.Blend })
else if (this.m_currentContainer is { ContainerType: ContainerType.Blend }
|| this.m_containerConfig.QueryById(newContainerId) is { ContainerType: ContainerType.Blend })
{
Debug.LogWarning($"[MusicChannelPlayer] SyncPoint.SameAsCurrentSegment 不支持 Blend 类型 Container({this.m_currentContainer.Id}),降级为 Start");
syncState.Mode = SyncPoint.Start;
@@ -144,9 +146,10 @@ namespace OCES.Audio
}
}
// ── 1. 等待节拍对齐(由 Transition 的 AlignMode 决定)──
// ── 1. 等待节拍对齐(由 Transition 的 AlignMode 决定)── TODO 支持FadeInTime为负值时,等待FadeIn
if (transition != null && this.m_currentContainer != null)
{
Debug.Log("[MusicChannelPlayer] Waiting for Alignment.");
yield return this.m_coroutineHost.StartCoroutine(
WaitForAlignment(transition.AlignMode, this.m_currentContainer));
}
@@ -158,6 +161,7 @@ namespace OCES.Audio
ContainerPlayHandle outgoing = this.m_fader.CurrentHandle;
float outVol = this.m_fader.CurrentVolume;
// Debug.Log("[MusicChannelPlayer] Start Crossfade.");
this.m_currentFadeOutCoroutine = this.m_coroutineHost.StartCoroutine(
this.m_fader.FadeOutBranch(outgoing, outVol, transition, syncState));
@@ -200,6 +204,7 @@ namespace OCES.Audio
double fadeInTime = transition?.FadeInTime ?? 0f;
startPlayTime = newStartOffset - fadeInTime;
startPlayTime = Math.Clamp(startPlayTime, 0, double.PositiveInfinity);
newStartOffset = 0;
}
syncState.StartPlayTime = startPlayTime;
dspTime = AudioSettings.dspTime;
@@ -277,5 +282,12 @@ namespace OCES.Audio
return child != null ? GetEffectiveStartOffset(child) : 0.0;
}
void OnContainerLooped(MusicContainer container, float bpm, double dspTime)
{
if (this.m_currentContainer == null || this.m_currentContainer.Id != container.Id)
return;
this.m_beatClock.Restart(container, bpm, dspTime);
}
}
}