From 1a1603f0dba556e30a598a1deabe468a3455d810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Oct 2021 19:27:07 +0200 Subject: [PATCH] Implement preview track playback --- .../Beatmaps/TestSceneBeatmapCardThumbnail.cs | 7 +- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 5 +- .../Drawables/Cards/BeatmapCardThumbnail.cs | 49 ++++++++-- .../Drawables/Cards/Buttons/PlayButton.cs | 92 ++++++++++++++++++- 4 files changed, 135 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs index 73424241cb..59df0dc7ca 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -41,6 +41,7 @@ namespace osu.Game.Tests.Visual.Beatmaps InputManager.MoveMouseTo(playButton); InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value); iconIs(FontAwesome.Solid.Stop); AddStep("click again", () => @@ -48,6 +49,7 @@ namespace osu.Game.Tests.Visual.Beatmaps InputManager.MoveMouseTo(playButton); InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for stop", () => !playButton.Playing.Value && playButton.Enabled.Value); iconIs(FontAwesome.Solid.Play); AddStep("click again", () => @@ -55,16 +57,17 @@ namespace osu.Game.Tests.Visual.Beatmaps InputManager.MoveMouseTo(playButton); InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for start", () => playButton.Playing.Value && playButton.Enabled.Value); iconIs(FontAwesome.Solid.Stop); AddStep("disable dim", () => thumbnail.Dimmed.Value = false); AddWaitStep("wait some", 3); AddAssert("button still visible", () => playButton.IsPresent); - AddStep("end track playback", () => playButton.Playing.Value = false); + AddUntilStep("wait for track to end", () => !playButton.Playing.Value); AddUntilStep("button hidden", () => !playButton.IsPresent); } - private void iconIs(IconUsage usage) => AddAssert("icon is correct", () => playButton.ChildrenOfType().Single().Icon.Equals(usage)); + private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType().Any(icon => icon.Icon.Equals(usage))); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index d52e8f6c20..4ffad0f065 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -101,6 +101,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Name = @"Left (icon) area", Size = new Vector2(height), + Padding = new MarginPadding { Right = corner_radius }, Child = leftIconArea = new FillFlowContainer { Margin = new MarginPadding(5), @@ -310,10 +311,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; if (beatmapSet.HasVideo) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Film)); + leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); if (beatmapSet.HasStoryboard) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Image)); + leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); if (beatmapSet.HasExplicitContent) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index ce1f856b84..f11a5916e1 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -1,12 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Screens.Ranking.Expanded.Accuracy; +using osuTK; using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables.Cards @@ -15,18 +19,22 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public BindableBool Dimmed { get; } = new BindableBool(); - private readonly APIBeatmapSet beatmapSetInfo; + public new MarginPadding Padding + { + get => foreground.Padding; + set => foreground.Padding = value; + } private readonly UpdateableOnlineBeatmapSetCover cover; + private readonly Container foreground; private readonly PlayButton playButton; + private readonly SmoothCircularProgress progress; private readonly Container content; protected override Container Content => content; public BeatmapCardThumbnail(APIBeatmapSet beatmapSetInfo) { - this.beatmapSetInfo = beatmapSetInfo; - InternalChildren = new Drawable[] { cover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) @@ -34,22 +42,45 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Both, OnlineInfo = beatmapSetInfo }, - playButton = new PlayButton(beatmapSetInfo) + foreground = new Container { - RelativeSizeAxes = Axes.Both - }, - content = new Container - { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + playButton = new PlayButton(beatmapSetInfo) + { + RelativeSizeAxes = Axes.Both + }, + progress = new SmoothCircularProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(50), + InnerRadius = 0.2f + }, + content = new Container + { + RelativeSizeAxes = Axes.Both + } + } } }; } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + progress.Colour = colourProvider.Highlight1; + } + protected override void LoadComplete() { base.LoadComplete(); Dimmed.BindValueChanged(_ => updateState()); + playButton.Playing.BindValueChanged(_ => updateState(), true); + ((IBindable)progress.Current).BindTo(playButton.Progress); + FinishTransforms(true); } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index c48999ed1a..4574d37da0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -1,25 +1,43 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class PlayButton : OsuHoverContainer { + public IBindable Progress => progress; + private readonly BindableDouble progress = new BindableDouble(); + public BindableBool Playing { get; } = new BindableBool(); - private readonly BeatmapSetInfo beatmapSetInfo; + private readonly IBeatmapSetInfo beatmapSetInfo; + + protected override IEnumerable EffectTargets => icon.Yield(); private readonly SpriteIcon icon; + private readonly LoadingSpinner loadingSpinner; - public PlayButton(BeatmapSetInfo beatmapSetInfo) + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } = null!; + + private PreviewTrack? previewTrack; + + public PlayButton(IBeatmapSetInfo beatmapSetInfo) { this.beatmapSetInfo = beatmapSetInfo; @@ -34,6 +52,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons Icon = FontAwesome.Solid.Play, Size = new Vector2(14) }, + loadingSpinner = new LoadingSpinner + { + Size = new Vector2(14) + } }; Action = () => Playing.Toggle(); @@ -49,12 +71,72 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { base.LoadComplete(); - Playing.BindValueChanged(_ => updateState(), true); + Playing.BindValueChanged(updateState, true); } - private void updateState() + protected override void Update() { - icon.Icon = Playing.Value ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; + base.Update(); + + if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded) + progress.Value = previewTrack.CurrentTime / previewTrack.Length; + else + progress.Value = 0; + } + + private void updateState(ValueChangedEvent playing) + { + icon.Icon = playing.NewValue ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; + + if (!playing.NewValue) + { + stopPreview(); + return; + } + + if (previewTrack == null) + { + toggleLoading(true); + LoadComponentAsync(previewTrack = previewTrackManager.Get(beatmapSetInfo), onPreviewLoaded); + } + else + tryStartPreview(); + } + + private void stopPreview() + { + toggleLoading(false); + Playing.Value = false; + previewTrack?.Stop(); + } + + private void onPreviewLoaded(PreviewTrack loadedPreview) + { + // another async load might have completed before this one. + // if so, do not make any changes. + if (loadedPreview != previewTrack) + return; + + AddInternal(loadedPreview); + toggleLoading(false); + + loadedPreview.Stopped += () => Schedule(() => Playing.Value = false); + + if (Playing.Value) + tryStartPreview(); + } + + private void tryStartPreview() + { + if (previewTrack?.Start() == false) + Playing.Value = false; + } + + private void toggleLoading(bool loading) + { + Enabled.Value = !loading; + icon.FadeTo(loading ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + loadingSpinner.State.Value = loading ? Visibility.Visible : Visibility.Hidden; } } }