Fixed looped audio playback position calculation (#6325)

* fix: looped audio position calculation

* refactor: tiny clean up

* move comment

* fix: review changes

* remove `

* rerun tests

* Remove unecessary formatting changes

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
This commit is contained in:
ThereDrD
2026-01-19 23:05:44 +03:00
committed by GitHub
parent dc1464b462
commit 17662baaf7
2 changed files with 38 additions and 16 deletions

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using JetBrains.Annotations;
using OpenTK.Audio.OpenAL;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
@@ -17,7 +16,6 @@ using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
@@ -182,14 +180,16 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
// If playback position changed then update it.
var position = (float) ((entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart).TotalSeconds;
var totalLen = GetAudioLengthImpl(entity.Comp.FileName).TotalSeconds;
var position = CalculateAudioPosition(entity, (float) totalLen);
var currentPosition = entity.Comp.Source.PlaybackPosition;
var diff = Math.Abs(position - currentPosition);
// Don't try to set the audio too far ahead.
if (!string.IsNullOrEmpty(entity.Comp.FileName))
{
if (position > GetAudioLengthImpl(entity.Comp.FileName).TotalSeconds - _audioEndBuffer)
if (position > totalLen - _audioEndBuffer)
{
entity.Comp.StopPlaying();
return;
@@ -251,7 +251,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
length ??= GetAudioLength(component.FileName);
// If audio came into range then start playback at the correct position.
var offset = ((entity.Comp.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
var offset = CalculateAudioPosition(entity, (float) length.Value.TotalSeconds);
if (TryAudioLimit(component.FileName))
{
@@ -289,7 +289,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
if (offset > 0)
{
component.PlaybackPosition = (float) offset;
component.PlaybackPosition = offset;
}
}
@@ -755,10 +755,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
var comp = entity.Comp;
var source = comp.Source;
// TODO clamp the offset inside of SetPlaybackPosition() itself.
var offset = audioP.PlayOffsetSeconds;
var maxOffset = Math.Max((float) stream.Length.TotalSeconds - 0.01f, 0f);
offset = Math.Clamp(offset, 0f, maxOffset);
var offset = CalculateAudioPosition(entity, (float)stream.Length.TotalSeconds, audioP.PlayOffsetSeconds);
source.PlaybackPosition = offset;
source.StartPlaying();

View File

@@ -75,6 +75,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
return;
var audioLength = GetAudioLength(entity.Comp.FileName);
position = CalculateAudioPosition(entity!, (float)audioLength.TotalSeconds, position);
if (audioLength.TotalSeconds < position)
{
@@ -86,12 +87,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
return;
}
if (position < 0f)
{
Log.Error($"Tried to set playback position for {ToPrettyString(entity.Owner)} / {entity.Comp.FileName} outside of bounds");
return;
}
// If we're paused then the current position is <pause time - start time>, else it's <cur time - start time>
var currentPos = (entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart;
var timeOffset = TimeSpan.FromSeconds(position - currentPos.TotalSeconds);
@@ -315,6 +310,36 @@ public abstract partial class SharedAudioSystem : EntitySystem
return GetAudioPath(resolved);
}
/// <summary>
/// Calculates the current playback position of an audio entity
/// and clamps it to the range from 0 to (length - 0.01f).
/// </summary>
/// <param name="ent">The audio entity.</param>
/// <param name="length">
/// The total length of the audio file.
/// If null, the method retrieves the length using <see cref="GetAudioLength"/>.
/// </param>
/// <param name="position">
/// A precomputed playback position.
/// If provided, it will be added to the calculation.
/// </param>
/// <returns>The playback position as a float.</returns>
protected float CalculateAudioPosition(Entity<AudioComponent> ent, float? length = null, float? position = null)
{
position ??= (float) ((ent.Comp.PauseTime ?? Timing.CurTime) - ent.Comp.AudioStart).TotalSeconds;
length ??= (float) GetAudioLength(ent.Comp.FileName).TotalSeconds;
// Looped audio has no conceptual start or end.
if (ent.Comp.Params.Loop)
position %= length;
// TODO clamp the offset inside of AudioSource.SetPlaybackPosition() itself.
var maxOffset = Math.Max((float) length - 0.01f, 0f);
position = Math.Clamp(position.Value, 0f, maxOffset);
return position.Value;
}
#region AudioParams
[return: NotNullIfNotNull(nameof(specifier))]