diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 2918dde2db..05bfae7e63 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Editing.Checks { // While this is a problem, it is out of scope for this check and is caught by a different one. beatmap.Metadata.BackgroundFile = string.Empty; - var context = getContext(null, System.Array.Empty()); + var context = getContext(null, new MemoryStream(System.Array.Empty())); Assert.That(check.Run(context), Is.Empty); } @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestTooUncompressed() { - var context = getContext(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); + var context = getContext(new Texture(1920, 1080), new MemoryStream(new byte[1024 * 1024 * 3])); var issues = check.Run(context).ToList(); @@ -111,19 +111,32 @@ namespace osu.Game.Tests.Editing.Checks Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); } - private BeatmapVerifierContext getContext(Texture background, [CanBeNull] byte[] fileBytes = null) + [Test] + public void TestStreamClosed() { - return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, fileBytes).Object); + var background = new Texture(1920, 1080); + var stream = new Mock(new byte[1024 * 1024]); + + var context = getContext(background, stream.Object); + + Assert.That(check.Run(context), Is.Empty); + + stream.Verify(x => x.Close(), Times.Once()); + } + + private BeatmapVerifierContext getContext(Texture background, [CanBeNull] Stream stream = null) + { + return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(background, stream).Object); } /// - /// Returns the mock of the working beatmap with the given background and filesize. + /// Returns the mock of the working beatmap with the given background and its file stream. /// /// The texture of the background. - /// The bytes that represent the background file. - private Mock getMockWorkingBeatmap(Texture background, [CanBeNull] byte[] fileBytes = null) + /// The stream representing the background file. + private Mock getMockWorkingBeatmap(Texture background, [CanBeNull] Stream stream = null) { - var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]); + stream ??= new MemoryStream(new byte[1024 * 1024]); var mock = new Mock(); mock.SetupGet(w => w.Beatmap).Returns(beatmap); diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 89e20043fb..82b6710a17 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -13,10 +13,17 @@ namespace osu.Game.Tests.Visual.Components { public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner { - private readonly TestPreviewTrackManager trackManager = new TestPreviewTrackManager(); + private readonly IAdjustableAudioComponent gameTrackAudio = new AudioAdjustments(); + + private readonly TestPreviewTrackManager trackManager; private AudioManager audio; + public TestScenePreviewTrackManager() + { + trackManager = new TestPreviewTrackManager(gameTrackAudio); + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -151,19 +158,19 @@ namespace osu.Game.Tests.Visual.Components audio.VolumeTrack.Value = 1; }); - AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0); + AddAssert("game not muted", () => gameTrackAudio.AggregateVolume.Value != 0); AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack()))); AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start track", () => track.Start()); - AddAssert("game is muted", () => audio.Tracks.AggregateVolume.Value == 0); + AddAssert("game is muted", () => gameTrackAudio.AggregateVolume.Value == 0); if (stopAnyPlaying) AddStep("stop any playing", () => trackManager.StopAnyPlaying(owner)); else AddStep("stop track", () => track.Stop()); - AddAssert("game not muted", () => audio.Tracks.AggregateVolume.Value != 0); + AddAssert("game not muted", () => gameTrackAudio.AggregateVolume.Value != 0); } [Test] @@ -224,6 +231,11 @@ namespace osu.Game.Tests.Visual.Components public new PreviewTrack CurrentTrack => base.CurrentTrack; + public TestPreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments) + : base(mainTrackAdjustments) + { + } + protected override TrackManagerPreviewTrack CreatePreviewTrack(IBeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore); public override bool UpdateSubTree() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index ad92886bab..1efa8d6810 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -127,9 +127,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead); + AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType().All(d => d.DisplayedTeam == null)); + AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus)); AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); + + AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType().All(d => d.DisplayedTeam != null)); } private void createRoom(Func room) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index ca63add31d..fd9d5a97c6 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -1,13 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Audio.Mixing; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -19,27 +14,26 @@ namespace osu.Game.Audio { public class PreviewTrackManager : Component { + private readonly IAdjustableAudioComponent mainTrackAdjustments; + private readonly BindableDouble muteBindable = new BindableDouble(); [Resolved] private AudioManager audio { get; set; } - private PreviewTrackStore trackStore; + private ITrackStore trackStore; protected TrackManagerPreviewTrack CurrentTrack; - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(OsuGameBase.GLOBAL_TRACK_VOLUME_ADJUST); + public PreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments) + { + this.mainTrackAdjustments = mainTrackAdjustments; + } [BackgroundDependencyLoader] private void load(AudioManager audioManager) { - // this is a temporary solution to get around muting ourselves. - // todo: update this once we have a BackgroundTrackManager or similar. - trackStore = new PreviewTrackStore(audioManager.TrackMixer, new OnlineStore()); - - audio.AddItem(trackStore); - trackStore.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust); - trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack); + trackStore = audioManager.GetTrackStore(new OnlineStore()); } /// @@ -55,7 +49,7 @@ namespace osu.Game.Audio { CurrentTrack?.Stop(); CurrentTrack = track; - audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); + mainTrackAdjustments.AddAdjustment(AdjustableProperty.Volume, muteBindable); }); track.Stopped += () => Schedule(() => @@ -64,7 +58,7 @@ namespace osu.Game.Audio return; CurrentTrack = null; - audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); + mainTrackAdjustments.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); }); return track; @@ -116,52 +110,5 @@ namespace osu.Game.Audio protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo.OnlineID}.mp3"); } - - private class PreviewTrackStore : AudioCollectionManager, ITrackStore - { - private readonly AudioMixer defaultMixer; - private readonly IResourceStore store; - - internal PreviewTrackStore(AudioMixer defaultMixer, IResourceStore store) - { - this.defaultMixer = defaultMixer; - this.store = store; - } - - public Track GetVirtual(double length = double.PositiveInfinity) - { - if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}"); - - var track = new TrackVirtual(length); - AddItem(track); - return track; - } - - public Track Get(string name) - { - if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}"); - - if (string.IsNullOrEmpty(name)) return null; - - var dataStream = store.GetStream(name); - - if (dataStream == null) - return null; - - // Todo: This is quite unsafe. TrackBass shouldn't be exposed as public. - Track track = new TrackBass(dataStream); - - defaultMixer.Add(track); - AddItem(track); - - return track; - } - - public Task GetAsync(string name) => Task.Run(() => Get(name)); - - public Stream GetStream(string name) => store.GetStream(name); - - public IEnumerable GetAvailableResources() => store.GetAvailableResources(); - } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1e33b54a8f..48a6663adb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -10,6 +10,8 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Audio; +using osu.Framework.Audio.Mixing; +using osu.Framework.Audio.Track; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Testing; @@ -30,18 +32,23 @@ namespace osu.Game.Beatmaps [ExcludeFromDynamicCompile] public class BeatmapManager : IModelDownloader, IModelManager, IModelFileManager, IModelImporter, IWorkingBeatmapCache, IDisposable { + public ITrackStore BeatmapTrackStore { get; } + private readonly BeatmapModelManager beatmapModelManager; private readonly BeatmapModelDownloader beatmapModelDownloader; private readonly WorkingBeatmapCache workingBeatmapCache; private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue; - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, - WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore gameResources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false, AudioMixer mainTrackMixer = null) { + var userResources = new FileStore(contextFactory, storage).Store; + + BeatmapTrackStore = audioManager.GetTrackStore(userResources); + beatmapModelManager = CreateBeatmapModelManager(storage, contextFactory, rulesets, api, host); beatmapModelDownloader = CreateBeatmapModelDownloader(beatmapModelManager, api, host); - workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, resources, new FileStore(contextFactory, storage).Store, defaultBeatmap, host); + workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); workingBeatmapCache.BeatmapManager = beatmapModelManager; beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; @@ -58,8 +65,10 @@ namespace osu.Game.Beatmaps return new BeatmapModelDownloader(modelManager, api, host); } - protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) => - new WorkingBeatmapCache(audioManager, resources, storage, defaultBeatmap, host); + protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore resources, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost host) + { + return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); + } protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, GameHost host) => new BeatmapModelManager(storage, contextFactory, rulesets, host); diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 11257e3abc..0af28902a0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -41,7 +41,7 @@ namespace osu.Game.Beatmaps [CanBeNull] private readonly GameHost host; - public WorkingBeatmapCache([NotNull] AudioManager audioManager, IResourceStore resources, IResourceStore files, WorkingBeatmap defaultBeatmap = null, GameHost host = null) + public WorkingBeatmapCache(ITrackStore trackStore, AudioManager audioManager, IResourceStore resources, IResourceStore files, WorkingBeatmap defaultBeatmap = null, GameHost host = null) { DefaultBeatmap = defaultBeatmap; @@ -50,7 +50,7 @@ namespace osu.Game.Beatmaps this.host = host; this.files = files; largeTextureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(files)); - trackStore = audioManager.GetTrackStore(files); + this.trackStore = trackStore; } public void Invalidate(BeatmapSetInfo info) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 10166daf00..e207d9ce3b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -65,7 +65,7 @@ namespace osu.Game /// /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. /// - internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.8; + private const double global_track_volume_adjust = 0.8; public bool UseDevelopmentServer { get; } @@ -159,7 +159,7 @@ namespace osu.Game private Bindable fpsDisplayVisible; - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); + private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); private RealmRulesetStore realmRulesetStore; @@ -315,7 +315,7 @@ namespace osu.Game dependencies.Cache(globalBindings); PreviewTrackManager previewTrackManager; - dependencies.Cache(previewTrackManager = new PreviewTrackManager()); + dependencies.Cache(previewTrackManager = new PreviewTrackManager(BeatmapManager.BeatmapTrackStore)); Add(previewTrackManager); AddInternal(MusicController = new MusicController()); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 8fa79e2ee8..7ce2ee802e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.IO; using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks @@ -48,10 +49,14 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); string storagePath = context.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); - double filesizeMb = context.WorkingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); - if (filesizeMb > max_filesize_mb) - yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); + using (Stream stream = context.WorkingBeatmap.GetStream(storagePath)) + { + double filesizeMb = stream.Length / (1024d * 1024d); + + if (filesizeMb > max_filesize_mb) + yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); + } } public class IssueTemplateTooHighResolution : IssueTemplate diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 833fbd6605..1bf62241f2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -29,52 +29,51 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private OsuColour colours { get; set; } + private OsuClickableContainer clickableContent; + public TeamDisplay(MultiplayerRoomUser user) { this.user = user; RelativeSizeAxes = Axes.Y; - Width = 15; + + AutoSizeAxes = Axes.X; Margin = new MarginPadding { Horizontal = 3 }; - - Alpha = 0; - Scale = new Vector2(0, 1); } [BackgroundDependencyLoader] private void load(AudioManager audio) { - box = new Container + InternalChild = clickableContent = new OsuClickableContainer { - RelativeSizeAxes = Axes.Both, - CornerRadius = 5, - Masking = true, + Width = 15, + Alpha = 0, Scale = new Vector2(0, 1), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = new Box + RelativeSizeAxes = Axes.Y, + Action = changeTeam, + Child = box = new Container { - Colour = Color4.White, RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Masking = true, + Scale = new Vector2(0, 1), Anchor = Anchor.Centre, Origin = Anchor.Centre, + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } }; if (Client.LocalUser?.Equals(user) == true) { - InternalChild = new OsuClickableContainer - { - RelativeSizeAxes = Axes.Both, - TooltipText = "Change team", - Action = changeTeam, - Child = box - }; - } - else - { - InternalChild = box; + clickableContent.Action = changeTeam; + clickableContent.TooltipText = "Change team"; } sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap"); @@ -88,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants }); } - private int? displayedTeam; + public int? DisplayedTeam { get; private set; } protected override void OnRoomUpdated() { @@ -102,28 +101,28 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants int? newTeam = (userRoomState as TeamVersusUserState)?.TeamID; - if (newTeam == displayedTeam) + if (newTeam == DisplayedTeam) return; // only play the sample if an already valid team changes to another valid team. // this avoids playing a sound for each user if the match type is changed to/from a team mode. - if (newTeam != null && displayedTeam != null) + if (newTeam != null && DisplayedTeam != null) sampleTeamSwap?.Play(); - displayedTeam = newTeam; + DisplayedTeam = newTeam; - if (displayedTeam != null) + if (DisplayedTeam != null) { - box.FadeColour(getColourForTeam(displayedTeam.Value), duration, Easing.OutQuint); + box.FadeColour(getColourForTeam(DisplayedTeam.Value), duration, Easing.OutQuint); box.ScaleTo(new Vector2(box.Scale.X < 0 ? 1 : -1, 1), duration, Easing.OutQuint); - this.ScaleTo(Vector2.One, duration, Easing.OutQuint); - this.FadeIn(duration); + clickableContent.ScaleTo(Vector2.One, duration, Easing.OutQuint); + clickableContent.FadeIn(duration); } else { - this.ScaleTo(new Vector2(0, 1), duration, Easing.OutQuint); - this.FadeOut(duration); + clickableContent.ScaleTo(new Vector2(0, 1), duration, Easing.OutQuint); + clickableContent.FadeOut(duration); } } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 4b02306d33..07152b5a3e 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual private readonly TestBeatmapManager testBeatmapManager; public TestWorkingBeatmapCache(TestBeatmapManager testBeatmapManager, AudioManager audioManager, IResourceStore resourceStore, IResourceStore storage, WorkingBeatmap defaultBeatmap, GameHost gameHost) - : base(audioManager, resourceStore, storage, defaultBeatmap, gameHost) + : base(testBeatmapManager.BeatmapTrackStore, audioManager, resourceStore, storage, defaultBeatmap, gameHost) { this.testBeatmapManager = testBeatmapManager; }