···18{
19 public class TestSceneAnimation : FrameworkTestScene
20 {
21- private readonly Container animationContainer;
22- private readonly SpriteText timeText;
2324 public override IReadOnlyList<Type> RequiredTypes => new[]
25 {
···3132 private TestAnimation animation;
3334- public TestSceneAnimation()
35- {
36- Children = new Drawable[]
37- {
38- animationContainer = new Container { RelativeSizeAxes = Axes.Both },
39- timeText = new SpriteText { Text = "Animation is loading..." }
40- };
41- }
42-43 [SetUpSteps]
44 public void SetUpSteps()
45 {
46 AddStep("load video", () =>
47 {
48- animationContainer.Child = animation = new TestAnimation
49 {
50- Repeat = false,
51- Clock = new FramedClock(clock = new ManualClock()),
0000000052 };
53 });
54···57 }
5859 [Test]
00000000000000000000060 public void TestJumpForward()
61 {
62 AddStep("Jump ahead by 10 seconds", () => clock.CurrentTime += 10000);
···109110 private class TestAnimation : TextureAnimation
111 {
00112 [Resolved]
113 private FontStore fontStore { get; set; }
114115 public int FramesProcessed;
116117 public TestAnimation()
118- : base(false)
119 {
120 Anchor = Anchor.Centre;
121 Origin = Anchor.Centre;
···130 [BackgroundDependencyLoader]
131 private void load()
132 {
133- for (int i = 0; i <= 72; i++)
134 {
135 AddFrame(new Texture(fontStore.Get(null, (char)('0' + i)).Texture.TextureGL)
136 {
···18{
19 public class TestSceneAnimation : FrameworkTestScene
20 {
21+ private SpriteText timeText;
02223 public override IReadOnlyList<Type> RequiredTypes => new[]
24 {
···3031 private TestAnimation animation;
3200000000033 [SetUpSteps]
34 public void SetUpSteps()
35 {
36 AddStep("load video", () =>
37 {
38+ Children = new Drawable[]
39 {
40+ new Container
41+ {
42+ RelativeSizeAxes = Axes.Both,
43+ Clock = new FramedClock(clock = new ManualClock()),
44+ Child = animation = new TestAnimation
45+ {
46+ Repeat = false,
47+ }
48+ },
49+ timeText = new SpriteText { Text = "Animation is loading..." }
50 };
51 });
52···55 }
5657 [Test]
58+ public void TestFrameSeeking()
59+ {
60+ AddAssert("frame count is correct", () => animation.FrameCount == TestAnimation.LOADABLE_FRAMES);
61+ AddUntilStep("wait for frames to pass", () => animation.CurrentFrameIndex > 10);
62+ AddStep("stop animation", () => animation.Stop());
63+ AddAssert("is stopped", () => !animation.IsPlaying);
64+65+ AddStep("goto frame 60", () => animation.GotoFrame(60));
66+ AddAssert("is at frame 60", () => animation.CurrentFrameIndex == 60);
67+68+ AddStep("goto frame 30", () => animation.GotoFrame(30));
69+ AddAssert("is at frame 30", () => animation.CurrentFrameIndex == 30);
70+71+ AddStep("goto frame 60", () => animation.GotoFrame(60));
72+ AddAssert("is at frame 60", () => animation.CurrentFrameIndex == 60);
73+74+ AddStep("start animation", () => animation.Play());
75+ AddUntilStep("continues to frame 70", () => animation.CurrentFrameIndex == 70);
76+ }
77+78+ [Test]
79 public void TestJumpForward()
80 {
81 AddStep("Jump ahead by 10 seconds", () => clock.CurrentTime += 10000);
···128129 private class TestAnimation : TextureAnimation
130 {
131+ public const int LOADABLE_FRAMES = 72;
132+133 [Resolved]
134 private FontStore fontStore { get; set; }
135136 public int FramesProcessed;
137138 public TestAnimation()
0139 {
140 Anchor = Anchor.Centre;
141 Origin = Anchor.Centre;
···150 [BackgroundDependencyLoader]
151 private void load()
152 {
153+ for (int i = 0; i < LOADABLE_FRAMES; i++)
154 {
155 AddFrame(new Texture(fontStore.Get(null, (char)('0' + i)).Texture.TextureGL)
156 {
+23-11
osu.Framework/Graphics/Animations/Animation.cs
···144 if (!startAtCurrentTime)
145 throw new InvalidOperationException($"A {nameof(Animation<T>)} with {startAtCurrentTime} = false cannot seek as it is dependent on an external clock.");
146147- offsetClock.Offset = offsetClock.CurrentTime + frameData[frameIndex].DisplayTime;
148 currentFrameCache.Invalidate();
149 }
150···166 {
167 var lastFrame = frameData.LastOrDefault();
168169- frame.DisplayTime = lastFrame.DisplayTime + lastFrame.Duration;
170 Duration += frame.Duration;
171172 frameData.Add(frame);
···221 {
222 base.Update();
223224- if (!IsPlaying || frameData.Count <= 0) return;
0225226- while (CurrentFrameIndex < frameData.Count && PlaybackPosition > frameData[CurrentFrameIndex].DisplayTime + frameData[CurrentFrameIndex].Duration)
227- {
228- CurrentFrameIndex++;
229- currentFrameCache.Invalidate();
230- }
231232- while (CurrentFrameIndex > 0 && PlaybackPosition < frameData[CurrentFrameIndex].DisplayTime)
233 {
234- CurrentFrameIndex--;
235- currentFrameCache.Invalidate();
000000000000000236 }
237238 if (!currentFrameCache.IsValid)
···144 if (!startAtCurrentTime)
145 throw new InvalidOperationException($"A {nameof(Animation<T>)} with {startAtCurrentTime} = false cannot seek as it is dependent on an external clock.");
146147+ offsetClock.Offset = frameData[frameIndex].DisplayStartTime - offsetClock.Source.CurrentTime;
148 currentFrameCache.Invalidate();
149 }
150···166 {
167 var lastFrame = frameData.LastOrDefault();
168169+ frame.DisplayStartTime = lastFrame.DisplayEndTime;
170 Duration += frame.Duration;
171172 frameData.Add(frame);
···221 {
222 base.Update();
223224+ if (!IsPlaying)
225+ offsetClock.Offset -= Time.Elapsed;
226227+ if (frameData.Count == 0) return;
0000228229+ switch (PlaybackPosition.CompareTo(frameData[CurrentFrameIndex].DisplayStartTime))
230 {
231+ case -1:
232+ while (CurrentFrameIndex > 0 && PlaybackPosition < frameData[CurrentFrameIndex].DisplayStartTime)
233+ {
234+ CurrentFrameIndex--;
235+ currentFrameCache.Invalidate();
236+ }
237+238+ break;
239+240+ case 1:
241+ while (CurrentFrameIndex < frameData.Count - 1 && PlaybackPosition >= frameData[CurrentFrameIndex].DisplayEndTime)
242+ {
243+ CurrentFrameIndex++;
244+ currentFrameCache.Invalidate();
245+ }
246+247+ break;
248 }
249250 if (!currentFrameCache.IsValid)
+6-1
osu.Framework/Graphics/Animations/FrameData.cs
···22 /// <summary>
23 /// The time at which this frame is displayed in the containing animation.
24 /// </summary>
25- internal double DisplayTime { get; set; }
0000026 }
27}
···22 /// <summary>
23 /// The time at which this frame is displayed in the containing animation.
24 /// </summary>
25+ internal double DisplayStartTime { get; set; }
26+27+ /// <summary>
28+ /// The time at which this frame is no longer displayed.
29+ /// </summary>
30+ internal double DisplayEndTime => DisplayStartTime + Duration;
31 }
32}