From 27569e2ed5f0006d9066a05cf2f5d44cb703bc8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Aug 2022 18:53:10 +0900 Subject: [PATCH 1/5] Remove `FrameStableClock` (and redirect usages to `FrameStabilityContainer`) --- osu.Game.Tests/NonVisual/GameplayClockTest.cs | 3 +- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- .../Rulesets/UI/FrameStabilityContainer.cs | 97 ++++++++++--------- osu.Game/Rulesets/UI/IFrameStableClock.cs | 2 - osu.Game/Screens/Play/GameplayClock.cs | 8 +- .../Screens/Play/GameplayClockContainer.cs | 2 +- osu.Game/Screens/Play/IGameplayClock.cs | 2 +- .../Play/MasterGameplayClockContainer.cs | 2 +- 9 files changed, 63 insertions(+), 57 deletions(-) diff --git a/osu.Game.Tests/NonVisual/GameplayClockTest.cs b/osu.Game.Tests/NonVisual/GameplayClockTest.cs index 5b8aacd281..162734f9da 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockTest.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Timing; @@ -30,7 +31,7 @@ namespace osu.Game.Tests.NonVisual { public List> MutableNonGameplayAdjustments { get; } = new List>(); - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + public override IEnumerable NonGameplayAdjustments => MutableNonGameplayAdjustments.Select(b => b.Value); public TestGameplayClock(IFrameBasedClock underlyingClock) : base(underlyingClock) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index a42e86933f..0aa412a4fd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -363,7 +363,7 @@ namespace osu.Game.Tests.Visual.Gameplay private Player player => Stack.CurrentScreen as Player; private double currentFrameStableTime - => player.ChildrenOfType().First().FrameStableClock.CurrentTime; + => player.ChildrenOfType().First().CurrentTime; private void waitForPlayer() => AddUntilStep("wait for player", () => (Stack.CurrentScreen as Player)?.IsLoaded == true); diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index f7f62d2af0..59c1146995 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.UI public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both }; - public override IFrameStableClock FrameStableClock => frameStabilityContainer.FrameStableClock; + public override IFrameStableClock FrameStableClock => frameStabilityContainer; private bool frameStablePlayback = true; diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index bfad22b4f0..7b7003302a 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -21,7 +19,9 @@ namespace osu.Game.Rulesets.UI /// A container which consumes a parent gameplay clock and standardises frame counts for children. /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks. /// - public class FrameStabilityContainer : Container, IHasReplayHandler + [Cached(typeof(IGameplayClock))] + [Cached(typeof(IFrameStableClock))] + public class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock, IGameplayClock { private readonly double gameplayStartTime; @@ -35,16 +35,17 @@ namespace osu.Game.Rulesets.UI /// internal bool FrameStablePlayback = true; - public IFrameStableClock FrameStableClock => frameStableClock; + public readonly Bindable IsCatchingUp = new Bindable(); - [Cached(typeof(IGameplayClock))] - private readonly FrameStabilityClock frameStableClock; + public readonly Bindable WaitingOnFrames = new Bindable(); + + public IBindable IsPaused { get; } = new BindableBool(); public FrameStabilityContainer(double gameplayStartTime = double.MinValue) { RelativeSizeAxes = Axes.Both; - frameStableClock = new FrameStabilityClock(framedClock = new FramedClock(manualClock = new ManualClock())); + framedClock = new FramedClock(manualClock = new ManualClock()); this.gameplayStartTime = gameplayStartTime; } @@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.UI private readonly FramedClock framedClock; - private IFrameBasedClock parentGameplayClock; + private IGameplayClock? parentGameplayClock; /// /// The current direction of playback to be exposed to frame stable children. @@ -63,13 +64,13 @@ namespace osu.Game.Rulesets.UI /// private int direction = 1; - [BackgroundDependencyLoader(true)] - private void load(IGameplayClock clock) + [BackgroundDependencyLoader] + private void load(IGameplayClock? clock) { if (clock != null) { - parentGameplayClock = frameStableClock.ParentGameplayClock = clock; - ((IBindable)frameStableClock.IsPaused).BindTo(clock.IsPaused); + parentGameplayClock = clock; + IsPaused.BindTo(parentGameplayClock.IsPaused); } } @@ -111,12 +112,12 @@ namespace osu.Game.Rulesets.UI private void updateClock() { - if (frameStableClock.WaitingOnFrames.Value) + if (WaitingOnFrames.Value) { // if waiting on frames, run one update loop to determine if frames have arrived. state = PlaybackState.Valid; } - else if (frameStableClock.IsPaused.Value) + else if (IsPaused.Value) { // time should not advance while paused, nor should anything run. state = PlaybackState.NotValid; @@ -154,8 +155,8 @@ namespace osu.Game.Rulesets.UI double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime); - frameStableClock.IsCatchingUp.Value = timeBehind > 200; - frameStableClock.WaitingOnFrames.Value = state == PlaybackState.NotValid; + IsCatchingUp.Value = timeBehind > 200; + WaitingOnFrames.Value = state == PlaybackState.NotValid; manualClock.CurrentTime = proposedTime; manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; @@ -177,6 +178,8 @@ namespace osu.Game.Rulesets.UI /// Whether playback is still valid. private bool updateReplay(ref double proposedTime) { + Debug.Assert(ReplayInputHandler != null); + double? newTime; if (FrameStablePlayback) @@ -238,18 +241,39 @@ namespace osu.Game.Rulesets.UI private void setClock() { - if (parentGameplayClock == null) - { - // in case a parent gameplay clock isn't available, just use the parent clock. - parentGameplayClock ??= Clock; - } - else - { - Clock = frameStableClock; - } + if (parentGameplayClock != null) + Clock = this; } - public ReplayInputHandler ReplayInputHandler { get; set; } + public ReplayInputHandler? ReplayInputHandler { get; set; } + + #region Delegation of IFrameStableClock + + public double CurrentTime => framedClock.CurrentTime; + + public double Rate => framedClock.Rate; + + public bool IsRunning => framedClock.IsRunning; + + public void ProcessFrame() => framedClock.ProcessFrame(); + + public double ElapsedFrameTime => framedClock.ElapsedFrameTime; + + public double FramesPerSecond => framedClock.FramesPerSecond; + + public FrameTimeInfo TimeInfo => framedClock.TimeInfo; + + #endregion + + #region Delegation of IGameplayClock + + public double TrueGameplayRate => parentGameplayClock?.TrueGameplayRate ?? 1; + + public double? StartTime => parentGameplayClock?.StartTime; + + public IEnumerable NonGameplayAdjustments => parentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty(); + + #endregion private enum PlaybackState { @@ -270,24 +294,7 @@ namespace osu.Game.Rulesets.UI Valid } - private class FrameStabilityClock : GameplayClock, IFrameStableClock - { - public IGameplayClock ParentGameplayClock; - - public readonly Bindable IsCatchingUp = new Bindable(); - - public readonly Bindable WaitingOnFrames = new Bindable(); - - public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); - - public FrameStabilityClock(FramedClock underlyingClock) - : base(underlyingClock) - { - } - - IBindable IFrameStableClock.IsCatchingUp => IsCatchingUp; - - IBindable IFrameStableClock.WaitingOnFrames => WaitingOnFrames; - } + IBindable IFrameStableClock.IsCatchingUp => IsCatchingUp; + IBindable IFrameStableClock.WaitingOnFrames => WaitingOnFrames; } } diff --git a/osu.Game/Rulesets/UI/IFrameStableClock.cs b/osu.Game/Rulesets/UI/IFrameStableClock.cs index 132605adaf..569ef5e06c 100644 --- a/osu.Game/Rulesets/UI/IFrameStableClock.cs +++ b/osu.Game/Rulesets/UI/IFrameStableClock.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Timing; diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 454229fb31..b650922173 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play IBindable IGameplayClock.IsPaused => IsPaused; - public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); + public virtual IEnumerable NonGameplayAdjustments => Enumerable.Empty(); public GameplayClock(IFrameBasedClock underlyingClock) { @@ -46,12 +46,12 @@ namespace osu.Game.Screens.Play { double baseRate = Rate; - foreach (var adjustment in NonGameplayAdjustments) + foreach (double adjustment in NonGameplayAdjustments) { - if (Precision.AlmostEquals(adjustment.Value, 0)) + if (Precision.AlmostEquals(adjustment, 0)) return 0; - baseRate /= adjustment.Value; + baseRate /= adjustment; } return baseRate; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index df1eb32f99..27b37094ad 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Play } } - public IEnumerable> NonGameplayAdjustments => GameplayClock.NonGameplayAdjustments; + public IEnumerable NonGameplayAdjustments => GameplayClock.NonGameplayAdjustments; /// /// The final clock which is exposed to gameplay components. diff --git a/osu.Game/Screens/Play/IGameplayClock.cs b/osu.Game/Screens/Play/IGameplayClock.cs index c3d61be5d5..5f54ce691a 100644 --- a/osu.Game/Screens/Play/IGameplayClock.cs +++ b/osu.Game/Screens/Play/IGameplayClock.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play /// /// All adjustments applied to this clock which don't come from gameplay or mods. /// - IEnumerable> NonGameplayAdjustments { get; } + IEnumerable NonGameplayAdjustments { get; } IBindable IsPaused { get; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index d7f6992fee..587d2d40a1 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -303,7 +303,7 @@ namespace osu.Game.Screens.Play private class MasterGameplayClock : GameplayClock { public readonly List> MutableNonGameplayAdjustments = new List>(); - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + public override IEnumerable NonGameplayAdjustments => MutableNonGameplayAdjustments.Select(b => b.Value); public MasterGameplayClock(FramedOffsetClock underlyingClock) : base(underlyingClock) From 828b6f2c30b779d4cee1d10c798c3b9a1353d305 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Aug 2022 19:01:28 +0900 Subject: [PATCH 2/5] Remove unnecessary `setClock` shenanigans --- .../Rulesets/UI/FrameStabilityContainer.cs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 7b7003302a..c115a0b6ac 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.UI protected override void LoadComplete() { base.LoadComplete(); - setClock(); + Clock = this; } private PlaybackState state; @@ -128,12 +128,7 @@ namespace osu.Game.Rulesets.UI state = PlaybackState.Valid; } - if (parentGameplayClock == null) - setClock(); // LoadComplete may not be run yet, but we still want the clock. - - Debug.Assert(parentGameplayClock != null); - - double proposedTime = parentGameplayClock.CurrentTime; + double proposedTime = Clock.CurrentTime; if (FrameStablePlayback) // if we require frame stability, the proposed time will be adjusted to move at most one known @@ -153,14 +148,14 @@ namespace osu.Game.Rulesets.UI if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime) direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; - double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime); + double timeBehind = Math.Abs(proposedTime - Clock.CurrentTime); IsCatchingUp.Value = timeBehind > 200; WaitingOnFrames.Value = state == PlaybackState.NotValid; manualClock.CurrentTime = proposedTime; - manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; - manualClock.IsRunning = parentGameplayClock.IsRunning; + manualClock.Rate = Math.Abs(Clock.Rate) * direction; + manualClock.IsRunning = Clock.IsRunning; // determine whether catch-up is required. if (state == PlaybackState.Valid && timeBehind > 0) @@ -239,12 +234,6 @@ namespace osu.Game.Rulesets.UI } } - private void setClock() - { - if (parentGameplayClock != null) - Clock = this; - } - public ReplayInputHandler? ReplayInputHandler { get; set; } #region Delegation of IFrameStableClock From 04d88b82163a9cf3895073784bfd53fb797451ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Aug 2022 19:17:22 +0900 Subject: [PATCH 3/5] Use constraint based assertions in `TestSceneFrameStabilityContainer` --- .../Visual/Gameplay/TestSceneFrameStabilityContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs index 97ffbfc796..ef74024b4b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs @@ -137,13 +137,13 @@ namespace osu.Game.Tests.Visual.Gameplay private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); - private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time); + private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime, () => Is.EqualTo(time)); private void checkFrameCount(int frames) => - AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames); + AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames, () => Is.EqualTo(frames)); private void checkRate(double rate) => - AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate == rate); + AddAssert($"clock rate is {rate}", () => consumer.Clock.Rate, () => Is.EqualTo(rate)); public class ClockConsumingChild : CompositeDrawable { From 9bc2e91de01864e16711765869e264ade0cd1ae0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Aug 2022 19:17:41 +0900 Subject: [PATCH 4/5] Fix incorrect handling of reference clocks when no parent `IGameplayClock` is available --- .../Rulesets/UI/FrameStabilityContainer.cs | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index c115a0b6ac..411217e314 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; @@ -56,6 +57,8 @@ namespace osu.Game.Rulesets.UI private IGameplayClock? parentGameplayClock; + private IClock referenceClock = null!; + /// /// The current direction of playback to be exposed to frame stable children. /// @@ -65,18 +68,15 @@ namespace osu.Game.Rulesets.UI private int direction = 1; [BackgroundDependencyLoader] - private void load(IGameplayClock? clock) + private void load(IGameplayClock? gameplayClock) { - if (clock != null) + if (gameplayClock != null) { - parentGameplayClock = clock; + parentGameplayClock = gameplayClock; IsPaused.BindTo(parentGameplayClock.IsPaused); } - } - protected override void LoadComplete() - { - base.LoadComplete(); + referenceClock = gameplayClock ?? Clock; Clock = this; } @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.UI state = PlaybackState.Valid; } - double proposedTime = Clock.CurrentTime; + double proposedTime = referenceClock.CurrentTime; if (FrameStablePlayback) // if we require frame stability, the proposed time will be adjusted to move at most one known @@ -148,14 +148,14 @@ namespace osu.Game.Rulesets.UI if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime) direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; - double timeBehind = Math.Abs(proposedTime - Clock.CurrentTime); + double timeBehind = Math.Abs(proposedTime - CurrentTime); IsCatchingUp.Value = timeBehind > 200; WaitingOnFrames.Value = state == PlaybackState.NotValid; manualClock.CurrentTime = proposedTime; - manualClock.Rate = Math.Abs(Clock.Rate) * direction; - manualClock.IsRunning = Clock.IsRunning; + manualClock.Rate = Math.Abs(referenceClock.Rate) * direction; + manualClock.IsRunning = referenceClock.IsRunning; // determine whether catch-up is required. if (state == PlaybackState.Valid && timeBehind > 0) @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.UI public bool IsRunning => framedClock.IsRunning; - public void ProcessFrame() => framedClock.ProcessFrame(); + public void ProcessFrame() { } public double ElapsedFrameTime => framedClock.ElapsedFrameTime; @@ -256,7 +256,23 @@ namespace osu.Game.Rulesets.UI #region Delegation of IGameplayClock - public double TrueGameplayRate => parentGameplayClock?.TrueGameplayRate ?? 1; + public double TrueGameplayRate + { + get + { + double baseRate = Rate; + + foreach (double adjustment in NonGameplayAdjustments) + { + if (Precision.AlmostEquals(adjustment, 0)) + return 0; + + baseRate /= adjustment; + } + + return baseRate; + } + } public double? StartTime => parentGameplayClock?.StartTime; From 87760bbc066b7e30b0b4a2986acd7f452a53b664 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Aug 2022 20:16:14 +0900 Subject: [PATCH 5/5] Fix `IsCatchingUp` not being in correct state --- .../Gameplay/TestSceneGameplaySamplePlayback.cs | 12 +++++------- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index f1084bca5f..1fe2dfd4df 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -21,22 +19,22 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestAllSamplesStopDuringSeek() { - DrawableSlider slider = null; - PoolableSkinnableSample[] samples = null; - ISamplePlaybackDisabler sampleDisabler = null; + DrawableSlider? slider = null; + PoolableSkinnableSample[] samples = null!; + ISamplePlaybackDisabler sampleDisabler = null!; AddUntilStep("get variables", () => { sampleDisabler = Player; slider = Player.ChildrenOfType().MinBy(s => s.HitObject.StartTime); - samples = slider?.ChildrenOfType().ToArray(); + samples = slider.ChildrenOfType().ToArray(); return slider != null; }); AddUntilStep("wait for slider sliding then seek", () => { - if (!slider.Tracking.Value) + if (slider?.Tracking.Value != true) return false; if (!samples.Any(s => s.Playing)) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 411217e314..e75e5cc5f3 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.UI if (state == PlaybackState.Valid && proposedTime != manualClock.CurrentTime) direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; - double timeBehind = Math.Abs(proposedTime - CurrentTime); + double timeBehind = Math.Abs(proposedTime - referenceClock.CurrentTime); IsCatchingUp.Value = timeBehind > 200; WaitingOnFrames.Value = state == PlaybackState.NotValid;