osu/osu.Game/Overlays/MusicController.cs

305 lines
9.3 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
using System;
using System.Collections.Generic;
2018-04-13 09:19:50 +00:00
using System.Linq;
using osu.Framework.Allocation;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables;
2018-04-13 09:19:50 +00:00
using osu.Framework.Graphics;
2019-08-13 05:38:49 +00:00
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
2018-04-13 09:19:50 +00:00
using osu.Framework.Threading;
using osu.Game.Beatmaps;
2019-08-13 05:38:49 +00:00
using osu.Game.Input.Bindings;
using osu.Game.Overlays.OSD;
using osu.Game.Rulesets.Mods;
2018-04-13 09:19:50 +00:00
namespace osu.Game.Overlays
{
/// <summary>
/// Handles playback of the global music track.
/// </summary>
2019-08-13 05:38:49 +00:00
public class MusicController : Component, IKeyBindingHandler<GlobalAction>
2018-04-13 09:19:50 +00:00
{
[Resolved]
private BeatmapManager beatmaps { get; set; }
2018-04-13 09:19:50 +00:00
2019-09-18 04:14:33 +00:00
public IBindableList<BeatmapSetInfo> BeatmapSets => beatmapSets;
private readonly BindableList<BeatmapSetInfo> beatmapSets = new BindableList<BeatmapSetInfo>();
2018-04-13 09:19:50 +00:00
2019-07-10 15:18:19 +00:00
public bool IsUserPaused { get; private set; }
/// <summary>
/// Fired when the global <see cref="WorkingBeatmap"/> has changed.
/// Includes direction information for display purposes.
/// </summary>
public event Action<WorkingBeatmap, TrackChangeDirection> TrackChanged;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
2019-08-13 05:38:49 +00:00
[Resolved(canBeNull: true)]
private OnScreenDisplay onScreenDisplay { get; set; }
2018-04-13 09:19:50 +00:00
[BackgroundDependencyLoader]
private void load()
2018-04-13 09:19:50 +00:00
{
2019-09-18 04:14:33 +00:00
beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()));
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
}
2018-04-13 09:19:50 +00:00
protected override void LoadComplete()
{
2018-06-07 07:46:54 +00:00
beatmap.BindValueChanged(beatmapChanged, true);
mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
2018-04-13 09:19:50 +00:00
base.LoadComplete();
}
/// <summary>
/// Change the position of a <see cref="BeatmapSetInfo"/> in the current playlist.
/// </summary>
/// <param name="beatmapSetInfo">The beatmap to move.</param>
/// <param name="index">The new position.</param>
public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index)
2018-04-13 09:19:50 +00:00
{
2019-09-18 04:14:33 +00:00
beatmapSets.Remove(beatmapSetInfo);
beatmapSets.Insert(index, beatmapSetInfo);
2018-04-13 09:19:50 +00:00
}
2019-08-13 05:38:49 +00:00
/// <summary>
/// Returns whether the current beatmap track is playing.
/// </summary>
2019-10-03 09:55:53 +00:00
public bool IsPlaying => current?.Track.IsRunning ?? false;
2019-08-13 05:38:49 +00:00
private void handleBeatmapAdded(BeatmapSetInfo set) =>
2019-09-18 04:14:33 +00:00
Schedule(() => beatmapSets.Add(set));
private void handleBeatmapRemoved(BeatmapSetInfo set) =>
2019-09-18 04:14:33 +00:00
Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID));
2018-04-13 09:19:50 +00:00
private ScheduledDelegate seekDelegate;
2018-04-13 09:19:50 +00:00
public void SeekTo(double position)
{
seekDelegate?.Cancel();
seekDelegate = Schedule(() =>
2018-11-02 23:04:30 +00:00
{
if (!beatmap.Disabled)
current?.Track.Seek(position);
});
2018-04-13 09:19:50 +00:00
}
/// <summary>
/// Start playing the current track (if not already playing).
/// </summary>
/// <returns>Whether the operation was successful.</returns>
public bool Play(bool restart = false)
2018-04-13 09:19:50 +00:00
{
var track = current?.Track;
IsUserPaused = false;
2018-04-13 09:19:50 +00:00
if (track == null)
{
2019-08-13 05:38:49 +00:00
if (beatmap.Disabled)
return false;
next(true);
return true;
2018-04-13 09:19:50 +00:00
}
if (restart)
track.Restart();
else if (!IsPlaying)
track.Start();
return true;
}
/// <summary>
/// Stop playing the current track and pause at the current position.
/// </summary>
public void Stop()
{
var track = current?.Track;
IsUserPaused = true;
if (track?.IsRunning == true)
2018-04-13 09:19:50 +00:00
track.Stop();
}
/// <summary>
/// Toggle pause / play.
/// </summary>
/// <returns>Whether the operation was successful.</returns>
public bool TogglePause()
{
var track = current?.Track;
if (track?.IsRunning == true)
Stop();
2018-04-13 09:19:50 +00:00
else
Play();
2019-08-13 05:38:49 +00:00
return true;
2018-04-13 09:19:50 +00:00
}
/// <summary>
/// Play the previous track.
/// </summary>
/// <returns>Whether the operation was successful.</returns>
2019-08-13 05:38:49 +00:00
public bool PrevTrack()
2018-04-13 09:19:50 +00:00
{
queuedDirection = TrackChangeDirection.Prev;
var playable = BeatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? BeatmapSets.LastOrDefault();
2019-04-01 03:16:05 +00:00
if (playable != null)
2018-05-14 08:45:11 +00:00
{
if (beatmap is Bindable<WorkingBeatmap> working)
working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
beatmap.Value.Track.Restart();
2019-08-13 05:38:49 +00:00
return true;
2018-05-14 08:45:11 +00:00
}
2019-08-13 05:38:49 +00:00
return false;
2018-04-13 09:19:50 +00:00
}
/// <summary>
/// Play the next random or playlist track.
/// </summary>
/// <returns>Whether the operation was successful.</returns>
2019-08-13 05:38:49 +00:00
public bool NextTrack() => next();
2019-08-13 05:38:49 +00:00
private bool next(bool instant = false)
2018-04-13 09:19:50 +00:00
{
if (!instant)
queuedDirection = TrackChangeDirection.Next;
var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? BeatmapSets.FirstOrDefault();
2019-04-01 03:16:05 +00:00
if (playable != null)
2018-05-14 08:45:11 +00:00
{
if (beatmap is Bindable<WorkingBeatmap> working)
working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
beatmap.Value.Track.Restart();
2019-08-13 05:38:49 +00:00
return true;
2018-05-14 08:45:11 +00:00
}
2019-08-13 05:38:49 +00:00
return false;
2018-04-13 09:19:50 +00:00
}
private WorkingBeatmap current;
private TrackChangeDirection? queuedDirection;
2018-04-13 09:19:50 +00:00
2019-02-25 10:29:09 +00:00
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
2018-04-13 09:19:50 +00:00
{
TrackChangeDirection direction = TrackChangeDirection.None;
2018-04-13 09:19:50 +00:00
if (current != null)
{
2019-02-25 10:29:09 +00:00
bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false;
2018-04-13 09:19:50 +00:00
if (audioEquals)
direction = TrackChangeDirection.None;
2018-04-13 09:19:50 +00:00
else if (queuedDirection.HasValue)
{
direction = queuedDirection.Value;
queuedDirection = null;
}
else
{
//figure out the best direction based on order in playlist.
var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
2018-04-13 09:19:50 +00:00
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
2018-04-13 09:19:50 +00:00
}
}
current = beatmap.NewValue;
TrackChanged?.Invoke(current, direction);
2018-04-13 09:19:50 +00:00
ResetTrackAdjustments();
2018-04-13 09:19:50 +00:00
queuedDirection = null;
}
public void ResetTrackAdjustments()
{
var track = current?.Track;
if (track == null)
return;
track.ResetSpeedAdjustments();
2019-04-10 03:03:57 +00:00
foreach (var mod in mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(track);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
2019-08-13 05:45:27 +00:00
if (beatmaps != null)
{
beatmaps.ItemAdded -= handleBeatmapAdded;
beatmaps.ItemRemoved -= handleBeatmapRemoved;
}
}
2019-08-13 05:38:49 +00:00
public bool OnPressed(GlobalAction action)
{
if (beatmap.Disabled)
return false;
2019-08-13 05:38:49 +00:00
switch (action)
{
case GlobalAction.MusicPlay:
if (TogglePause())
onScreenDisplay?.Display(new MusicControllerToast(IsPlaying ? "Play track" : "Pause track"));
return true;
case GlobalAction.MusicNext:
if (NextTrack())
onScreenDisplay?.Display(new MusicControllerToast("Next track"));
return true;
case GlobalAction.MusicPrev:
if (PrevTrack())
onScreenDisplay?.Display(new MusicControllerToast("Previous track"));
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => false;
public class MusicControllerToast : Toast
{
public MusicControllerToast(string action)
: base("Music Playback", action, string.Empty)
{
}
}
}
public enum TrackChangeDirection
{
None,
Next,
Prev
2018-04-13 09:19:50 +00:00
}
}