···1818{
1919 public class TestSceneAnimation : FrameworkTestScene
2020 {
2121- private readonly Container animationContainer;
2222- private readonly SpriteText timeText;
2121+ private SpriteText timeText;
23222423 public override IReadOnlyList<Type> RequiredTypes => new[]
2524 {
···31303231 private TestAnimation animation;
33323434- public TestSceneAnimation()
3535- {
3636- Children = new Drawable[]
3737- {
3838- animationContainer = new Container { RelativeSizeAxes = Axes.Both },
3939- timeText = new SpriteText { Text = "Animation is loading..." }
4040- };
4141- }
4242-4333 [SetUpSteps]
4434 public void SetUpSteps()
4535 {
4636 AddStep("load video", () =>
4737 {
4848- animationContainer.Child = animation = new TestAnimation
3838+ Children = new Drawable[]
4939 {
5050- Repeat = false,
5151- Clock = new FramedClock(clock = new ManualClock()),
4040+ new Container
4141+ {
4242+ RelativeSizeAxes = Axes.Both,
4343+ Clock = new FramedClock(clock = new ManualClock()),
4444+ Child = animation = new TestAnimation
4545+ {
4646+ Repeat = false,
4747+ }
4848+ },
4949+ timeText = new SpriteText { Text = "Animation is loading..." }
5250 };
5351 });
5452···5755 }
58565957 [Test]
5858+ public void TestFrameSeeking()
5959+ {
6060+ AddAssert("frame count is correct", () => animation.FrameCount == TestAnimation.LOADABLE_FRAMES);
6161+ AddUntilStep("wait for frames to pass", () => animation.CurrentFrameIndex > 10);
6262+ AddStep("stop animation", () => animation.Stop());
6363+ AddAssert("is stopped", () => !animation.IsPlaying);
6464+6565+ AddStep("goto frame 60", () => animation.GotoFrame(60));
6666+ AddAssert("is at frame 60", () => animation.CurrentFrameIndex == 60);
6767+6868+ AddStep("goto frame 30", () => animation.GotoFrame(30));
6969+ AddAssert("is at frame 30", () => animation.CurrentFrameIndex == 30);
7070+7171+ AddStep("goto frame 60", () => animation.GotoFrame(60));
7272+ AddAssert("is at frame 60", () => animation.CurrentFrameIndex == 60);
7373+7474+ AddStep("start animation", () => animation.Play());
7575+ AddUntilStep("continues to frame 70", () => animation.CurrentFrameIndex == 70);
7676+ }
7777+7878+ [Test]
6079 public void TestJumpForward()
6180 {
6281 AddStep("Jump ahead by 10 seconds", () => clock.CurrentTime += 10000);
···109128110129 private class TestAnimation : TextureAnimation
111130 {
131131+ public const int LOADABLE_FRAMES = 72;
132132+112133 [Resolved]
113134 private FontStore fontStore { get; set; }
114135115136 public int FramesProcessed;
116137117138 public TestAnimation()
118118- : base(false)
119139 {
120140 Anchor = Anchor.Centre;
121141 Origin = Anchor.Centre;
···130150 [BackgroundDependencyLoader]
131151 private void load()
132152 {
133133- for (int i = 0; i <= 72; i++)
153153+ for (int i = 0; i < LOADABLE_FRAMES; i++)
134154 {
135155 AddFrame(new Texture(fontStore.Get(null, (char)('0' + i)).Texture.TextureGL)
136156 {
+23-11
osu.Framework/Graphics/Animations/Animation.cs
···144144 if (!startAtCurrentTime)
145145 throw new InvalidOperationException($"A {nameof(Animation<T>)} with {startAtCurrentTime} = false cannot seek as it is dependent on an external clock.");
146146147147- offsetClock.Offset = offsetClock.CurrentTime + frameData[frameIndex].DisplayTime;
147147+ offsetClock.Offset = frameData[frameIndex].DisplayStartTime - offsetClock.Source.CurrentTime;
148148 currentFrameCache.Invalidate();
149149 }
150150···166166 {
167167 var lastFrame = frameData.LastOrDefault();
168168169169- frame.DisplayTime = lastFrame.DisplayTime + lastFrame.Duration;
169169+ frame.DisplayStartTime = lastFrame.DisplayEndTime;
170170 Duration += frame.Duration;
171171172172 frameData.Add(frame);
···221221 {
222222 base.Update();
223223224224- if (!IsPlaying || frameData.Count <= 0) return;
224224+ if (!IsPlaying)
225225+ offsetClock.Offset -= Time.Elapsed;
225226226226- while (CurrentFrameIndex < frameData.Count && PlaybackPosition > frameData[CurrentFrameIndex].DisplayTime + frameData[CurrentFrameIndex].Duration)
227227- {
228228- CurrentFrameIndex++;
229229- currentFrameCache.Invalidate();
230230- }
227227+ if (frameData.Count == 0) return;
231228232232- while (CurrentFrameIndex > 0 && PlaybackPosition < frameData[CurrentFrameIndex].DisplayTime)
229229+ switch (PlaybackPosition.CompareTo(frameData[CurrentFrameIndex].DisplayStartTime))
233230 {
234234- CurrentFrameIndex--;
235235- currentFrameCache.Invalidate();
231231+ case -1:
232232+ while (CurrentFrameIndex > 0 && PlaybackPosition < frameData[CurrentFrameIndex].DisplayStartTime)
233233+ {
234234+ CurrentFrameIndex--;
235235+ currentFrameCache.Invalidate();
236236+ }
237237+238238+ break;
239239+240240+ case 1:
241241+ while (CurrentFrameIndex < frameData.Count - 1 && PlaybackPosition >= frameData[CurrentFrameIndex].DisplayEndTime)
242242+ {
243243+ CurrentFrameIndex++;
244244+ currentFrameCache.Invalidate();
245245+ }
246246+247247+ break;
236248 }
237249238250 if (!currentFrameCache.IsValid)
+6-1
osu.Framework/Graphics/Animations/FrameData.cs
···2222 /// <summary>
2323 /// The time at which this frame is displayed in the containing animation.
2424 /// </summary>
2525- internal double DisplayTime { get; set; }
2525+ internal double DisplayStartTime { get; set; }
2626+2727+ /// <summary>
2828+ /// The time at which this frame is no longer displayed.
2929+ /// </summary>
3030+ internal double DisplayEndTime => DisplayStartTime + Duration;
2631 }
2732}