2019-08-13 13:46:57 +08:00
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
2019-01-24 16:43:03 +08:00
|
|
|
// See the LICENCE file in the repository root for full licence text.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
using System;
|
2018-05-14 16:41:35 +08:00
|
|
|
using System.Collections.Generic;
|
2018-04-13 17:19:50 +08:00
|
|
|
using System.Linq;
|
2020-08-04 20:53:00 +08:00
|
|
|
using JetBrains.Annotations;
|
2018-04-13 17:19:50 +08:00
|
|
|
using osu.Framework.Allocation;
|
2020-08-04 20:53:00 +08:00
|
|
|
using osu.Framework.Audio;
|
2020-07-10 15:33:31 +08:00
|
|
|
using osu.Framework.Audio.Track;
|
2019-02-21 18:04:31 +08:00
|
|
|
using osu.Framework.Bindables;
|
2018-04-13 17:19:50 +08:00
|
|
|
using osu.Framework.Graphics;
|
2020-08-04 20:53:00 +08:00
|
|
|
using osu.Framework.Graphics.Audio;
|
|
|
|
using osu.Framework.Graphics.Containers;
|
2019-08-13 13:38:49 +08:00
|
|
|
using osu.Framework.Input.Bindings;
|
2020-01-09 12:43:44 +08:00
|
|
|
using osu.Framework.Utils;
|
2018-04-13 17:19:50 +08:00
|
|
|
using osu.Framework.Threading;
|
2020-08-04 20:53:00 +08:00
|
|
|
using osu.Framework.Timing;
|
2018-04-13 17:19:50 +08:00
|
|
|
using osu.Game.Beatmaps;
|
2019-08-13 13:38:49 +08:00
|
|
|
using osu.Game.Input.Bindings;
|
|
|
|
using osu.Game.Overlays.OSD;
|
2019-04-08 18:16:34 +08:00
|
|
|
using osu.Game.Rulesets.Mods;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
namespace osu.Game.Overlays
|
|
|
|
{
|
2019-08-13 13:29:58 +08:00
|
|
|
/// <summary>
|
|
|
|
/// Handles playback of the global music track.
|
|
|
|
/// </summary>
|
2020-08-04 20:53:00 +08:00
|
|
|
public class MusicController : CompositeDrawable, IKeyBindingHandler<GlobalAction>, ITrack
|
2018-04-13 17:19:50 +08:00
|
|
|
{
|
2019-08-13 13:29:58 +08:00
|
|
|
[Resolved]
|
|
|
|
private BeatmapManager beatmaps { get; set; }
|
2018-04-13 17:19:50 +08:00
|
|
|
|
2020-02-17 15:59:35 +08:00
|
|
|
public IBindableList<BeatmapSetInfo> BeatmapSets
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
if (LoadState < LoadState.Ready)
|
|
|
|
throw new InvalidOperationException($"{nameof(BeatmapSets)} should not be accessed before the music controller is loaded.");
|
|
|
|
|
|
|
|
return beatmapSets;
|
|
|
|
}
|
|
|
|
}
|
2019-09-18 12:14:33 +08:00
|
|
|
|
2019-10-24 12:10:17 +08:00
|
|
|
/// <summary>
|
|
|
|
/// Point in time after which the current track will be restarted on triggering a "previous track" action.
|
|
|
|
/// </summary>
|
2019-10-11 17:41:54 +08:00
|
|
|
private const double restart_cutoff_point = 5000;
|
|
|
|
|
2019-09-18 12:14:33 +08:00
|
|
|
private readonly BindableList<BeatmapSetInfo> beatmapSets = new BindableList<BeatmapSetInfo>();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
2019-07-10 23:18:19 +08:00
|
|
|
public bool IsUserPaused { get; private set; }
|
2019-07-09 17:32:49 +08:00
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
/// <summary>
|
|
|
|
/// Fired when the global <see cref="WorkingBeatmap"/> has changed.
|
|
|
|
/// Includes direction information for display purposes.
|
|
|
|
/// </summary>
|
|
|
|
public event Action<WorkingBeatmap, TrackChangeDirection> TrackChanged;
|
|
|
|
|
2019-04-08 18:16:34 +08:00
|
|
|
[Resolved]
|
2019-08-13 13:29:58 +08:00
|
|
|
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
2019-04-08 18:16:34 +08:00
|
|
|
|
|
|
|
[Resolved]
|
2019-04-10 16:13:12 +08:00
|
|
|
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
2018-05-23 16:37:39 +08:00
|
|
|
|
2019-08-13 13:38:49 +08:00
|
|
|
[Resolved(canBeNull: true)]
|
|
|
|
private OnScreenDisplay onScreenDisplay { get; set; }
|
|
|
|
|
2020-08-04 20:53:00 +08:00
|
|
|
[NotNull]
|
|
|
|
private readonly TrackContainer trackContainer;
|
|
|
|
|
|
|
|
[CanBeNull]
|
|
|
|
private DrawableTrack drawableTrack;
|
|
|
|
|
|
|
|
[CanBeNull]
|
|
|
|
private Track track;
|
|
|
|
|
2020-05-27 15:08:47 +08:00
|
|
|
private IBindable<WeakReference<BeatmapSetInfo>> managerUpdated;
|
2020-05-19 15:44:22 +08:00
|
|
|
private IBindable<WeakReference<BeatmapSetInfo>> managerRemoved;
|
|
|
|
|
2020-08-04 20:53:00 +08:00
|
|
|
public MusicController()
|
|
|
|
{
|
|
|
|
InternalChild = trackContainer = new TrackContainer { RelativeSizeAxes = Axes.Both };
|
|
|
|
}
|
|
|
|
|
2018-04-13 17:19:50 +08:00
|
|
|
[BackgroundDependencyLoader]
|
2019-08-13 13:29:58 +08:00
|
|
|
private void load()
|
2018-04-13 17:19:50 +08:00
|
|
|
{
|
2020-05-27 15:08:47 +08:00
|
|
|
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
|
|
|
|
managerUpdated.BindValueChanged(beatmapUpdated);
|
2020-05-19 15:44:22 +08:00
|
|
|
managerRemoved = beatmaps.ItemRemoved.GetBoundCopy();
|
|
|
|
managerRemoved.BindValueChanged(beatmapRemoved);
|
2020-02-17 15:59:35 +08:00
|
|
|
|
2020-07-10 15:33:31 +08:00
|
|
|
beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next()));
|
2018-05-10 16:15:47 +08:00
|
|
|
}
|
|
|
|
|
2018-04-13 17:19:50 +08:00
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
2020-02-17 15:59:35 +08:00
|
|
|
base.LoadComplete();
|
|
|
|
|
2018-06-07 15:46:54 +08:00
|
|
|
beatmap.BindValueChanged(beatmapChanged, true);
|
2019-09-28 09:18:16 +08:00
|
|
|
mods.BindValueChanged(_ => ResetTrackAdjustments(), true);
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
/// <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 17:19:50 +08:00
|
|
|
{
|
2019-09-18 12:14:33 +08:00
|
|
|
beatmapSets.Remove(beatmapSetInfo);
|
|
|
|
beatmapSets.Insert(index, beatmapSetInfo);
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 13:38:49 +08:00
|
|
|
/// <summary>
|
2020-08-04 20:53:00 +08:00
|
|
|
/// Returns whether the beatmap track is playing.
|
2019-08-13 13:38:49 +08:00
|
|
|
/// </summary>
|
2020-08-04 20:53:00 +08:00
|
|
|
public bool IsPlaying => drawableTrack?.IsRunning ?? false;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns whether the beatmap track is loaded.
|
|
|
|
/// </summary>
|
|
|
|
public bool TrackLoaded => drawableTrack?.IsLoaded == true;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the current time of the beatmap track.
|
|
|
|
/// </summary>
|
|
|
|
public double CurrentTrackTime => drawableTrack?.CurrentTime ?? 0;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the length of the beatmap track.
|
|
|
|
/// </summary>
|
|
|
|
public double TrackLength => drawableTrack?.Length ?? 0;
|
|
|
|
|
|
|
|
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
|
|
|
=> trackContainer.AddAdjustment(type, adjustBindable);
|
|
|
|
|
|
|
|
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable)
|
|
|
|
=> trackContainer.RemoveAdjustment(type, adjustBindable);
|
|
|
|
|
|
|
|
public void Reset() => drawableTrack?.Reset();
|
|
|
|
|
|
|
|
[CanBeNull]
|
|
|
|
public IAdjustableClock GetTrackClock() => track;
|
2019-08-13 13:38:49 +08:00
|
|
|
|
2020-05-27 15:08:47 +08:00
|
|
|
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
|
2020-02-17 15:59:35 +08:00
|
|
|
{
|
2020-05-19 15:44:22 +08:00
|
|
|
if (weakSet.NewValue.TryGetTarget(out var set))
|
|
|
|
{
|
|
|
|
Schedule(() =>
|
|
|
|
{
|
2020-05-27 15:08:47 +08:00
|
|
|
beatmapSets.Remove(set);
|
|
|
|
beatmapSets.Add(set);
|
2020-05-19 15:44:22 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2019-06-26 12:18:03 +08:00
|
|
|
|
2020-05-19 15:44:22 +08:00
|
|
|
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
|
2020-02-17 15:59:35 +08:00
|
|
|
{
|
2020-05-19 15:44:22 +08:00
|
|
|
if (weakSet.NewValue.TryGetTarget(out var set))
|
|
|
|
{
|
|
|
|
Schedule(() =>
|
|
|
|
{
|
|
|
|
beatmapSets.RemoveAll(s => s.ID == set.ID);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
private ScheduledDelegate seekDelegate;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
public void SeekTo(double position)
|
|
|
|
{
|
|
|
|
seekDelegate?.Cancel();
|
|
|
|
seekDelegate = Schedule(() =>
|
2018-11-03 07:04:30 +08:00
|
|
|
{
|
2019-08-13 13:29:58 +08:00
|
|
|
if (!beatmap.Disabled)
|
2020-08-04 20:53:00 +08:00
|
|
|
drawableTrack?.Seek(position);
|
2019-08-13 13:29:58 +08:00
|
|
|
});
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
2019-08-19 10:30:04 +08:00
|
|
|
/// <summary>
|
2020-07-10 17:03:56 +08:00
|
|
|
/// Ensures music is playing, no matter what, unless the user has explicitly paused.
|
|
|
|
/// This means that if the current beatmap has a virtual track (see <see cref="TrackVirtual"/>) a new beatmap will be selected.
|
2019-08-19 10:30:04 +08:00
|
|
|
/// </summary>
|
2020-07-10 17:03:56 +08:00
|
|
|
public void EnsurePlayingSomething()
|
2018-04-13 17:19:50 +08:00
|
|
|
{
|
2020-07-10 17:03:56 +08:00
|
|
|
if (IsUserPaused) return;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
2020-08-04 20:53:00 +08:00
|
|
|
if (drawableTrack == null || drawableTrack.IsDummyDevice)
|
2018-04-13 17:19:50 +08:00
|
|
|
{
|
2019-08-13 13:38:49 +08:00
|
|
|
if (beatmap.Disabled)
|
2020-07-10 17:03:56 +08:00
|
|
|
return;
|
2019-08-13 13:38:49 +08:00
|
|
|
|
2020-07-13 16:28:16 +08:00
|
|
|
NextTrack();
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
2020-07-10 17:03:56 +08:00
|
|
|
else if (!IsPlaying)
|
|
|
|
{
|
|
|
|
Play();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Start playing the current track (if not already playing).
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>Whether the operation was successful.</returns>
|
|
|
|
public bool Play(bool restart = false)
|
|
|
|
{
|
|
|
|
IsUserPaused = false;
|
|
|
|
|
2020-08-04 20:53:00 +08:00
|
|
|
if (drawableTrack == null)
|
2020-07-10 17:03:56 +08:00
|
|
|
return false;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
2019-10-10 15:52:51 +08:00
|
|
|
if (restart)
|
2020-08-04 20:53:00 +08:00
|
|
|
drawableTrack.Restart();
|
2019-10-10 15:52:51 +08:00
|
|
|
else if (!IsPlaying)
|
2020-08-04 20:53:00 +08:00
|
|
|
drawableTrack.Start();
|
2019-10-10 15:52:51 +08:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Stop playing the current track and pause at the current position.
|
|
|
|
/// </summary>
|
|
|
|
public void Stop()
|
|
|
|
{
|
2019-10-10 19:12:47 +08:00
|
|
|
IsUserPaused = true;
|
2020-08-04 20:53:00 +08:00
|
|
|
if (drawableTrack?.IsRunning == true)
|
|
|
|
drawableTrack.Stop();
|
2019-10-10 15:52:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Toggle pause / play.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>Whether the operation was successful.</returns>
|
|
|
|
public bool TogglePause()
|
|
|
|
{
|
2020-08-04 20:53:00 +08:00
|
|
|
if (drawableTrack?.IsRunning == true)
|
2019-10-10 15:52:51 +08:00
|
|
|
Stop();
|
2018-04-13 17:19:50 +08:00
|
|
|
else
|
2019-10-10 15:52:51 +08:00
|
|
|
Play();
|
2019-08-13 13:38:49 +08:00
|
|
|
|
|
|
|
return true;
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
/// <summary>
|
2020-04-28 10:46:08 +08:00
|
|
|
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
2019-08-13 13:29:58 +08:00
|
|
|
/// </summary>
|
2020-04-28 10:46:08 +08:00
|
|
|
public void PreviousTrack() => Schedule(() => prev());
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>The <see cref="PreviousTrackResult"/> that indicate the decided action.</returns>
|
|
|
|
private PreviousTrackResult prev()
|
2018-04-13 17:19:50 +08:00
|
|
|
{
|
2020-07-13 16:28:16 +08:00
|
|
|
if (beatmap.Disabled)
|
|
|
|
return PreviousTrackResult.None;
|
|
|
|
|
2020-08-04 20:53:00 +08:00
|
|
|
var currentTrackPosition = drawableTrack?.CurrentTime;
|
2019-10-11 01:12:36 +08:00
|
|
|
|
2019-10-11 17:41:54 +08:00
|
|
|
if (currentTrackPosition >= restart_cutoff_point)
|
2019-10-11 01:12:36 +08:00
|
|
|
{
|
|
|
|
SeekTo(0);
|
2019-10-24 12:10:17 +08:00
|
|
|
return PreviousTrackResult.Restart;
|
2019-10-11 01:12:36 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
queuedDirection = TrackChangeDirection.Prev;
|
2018-05-14 16:41:35 +08:00
|
|
|
|
2019-09-17 22:08:37 +08:00
|
|
|
var playable = BeatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? BeatmapSets.LastOrDefault();
|
2019-04-01 11:16:05 +08:00
|
|
|
|
2018-05-14 16:41:35 +08:00
|
|
|
if (playable != null)
|
2018-05-14 16:45:11 +08:00
|
|
|
{
|
2019-08-13 13:29:58 +08:00
|
|
|
if (beatmap is Bindable<WorkingBeatmap> working)
|
|
|
|
working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
|
2019-08-13 13:38:49 +08:00
|
|
|
|
2020-07-31 21:02:12 +08:00
|
|
|
restartTrack();
|
2019-10-24 12:10:17 +08:00
|
|
|
return PreviousTrackResult.Previous;
|
2018-05-14 16:45:11 +08:00
|
|
|
}
|
2019-08-13 13:38:49 +08:00
|
|
|
|
2019-10-24 12:10:17 +08:00
|
|
|
return PreviousTrackResult.None;
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
/// <summary>
|
|
|
|
/// Play the next random or playlist track.
|
|
|
|
/// </summary>
|
2020-04-28 10:46:08 +08:00
|
|
|
public void NextTrack() => Schedule(() => next());
|
2019-08-13 13:29:58 +08:00
|
|
|
|
2020-07-10 15:33:31 +08:00
|
|
|
private bool next()
|
2018-04-13 17:19:50 +08:00
|
|
|
{
|
2020-07-13 16:28:16 +08:00
|
|
|
if (beatmap.Disabled)
|
|
|
|
return false;
|
|
|
|
|
2020-07-10 15:33:31 +08:00
|
|
|
queuedDirection = TrackChangeDirection.Next;
|
2018-05-14 16:41:35 +08:00
|
|
|
|
2020-02-01 01:32:47 +08:00
|
|
|
var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault();
|
2019-04-01 11:16:05 +08:00
|
|
|
|
2018-05-14 16:41:35 +08:00
|
|
|
if (playable != null)
|
2018-05-14 16:45:11 +08:00
|
|
|
{
|
2019-08-13 13:29:58 +08:00
|
|
|
if (beatmap is Bindable<WorkingBeatmap> working)
|
|
|
|
working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
|
2020-07-31 19:33:18 +08:00
|
|
|
|
2020-07-31 21:02:12 +08:00
|
|
|
restartTrack();
|
2019-08-13 13:38:49 +08:00
|
|
|
return true;
|
2018-05-14 16:45:11 +08:00
|
|
|
}
|
2019-08-13 13:38:49 +08:00
|
|
|
|
|
|
|
return false;
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
|
|
|
|
2020-07-31 21:02:12 +08:00
|
|
|
private void restartTrack()
|
|
|
|
{
|
|
|
|
// if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase).
|
|
|
|
// we probably want to move this to a central method for switching to a new working beatmap in the future.
|
2020-08-04 20:53:00 +08:00
|
|
|
Schedule(() => drawableTrack?.Restart());
|
2020-07-31 21:02:12 +08:00
|
|
|
}
|
|
|
|
|
2018-04-13 17:19:50 +08:00
|
|
|
private WorkingBeatmap current;
|
2019-08-13 13:29:58 +08:00
|
|
|
|
|
|
|
private TrackChangeDirection? queuedDirection;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
2019-02-25 18:29:09 +08:00
|
|
|
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
|
2018-04-13 17:19:50 +08:00
|
|
|
{
|
2019-08-13 13:29:58 +08:00
|
|
|
TrackChangeDirection direction = TrackChangeDirection.None;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
if (current != null)
|
|
|
|
{
|
2019-02-25 18:29:09 +08:00
|
|
|
bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
if (audioEquals)
|
2019-08-13 13:29:58 +08:00
|
|
|
direction = TrackChangeDirection.None;
|
2018-04-13 17:19:50 +08:00
|
|
|
else if (queuedDirection.HasValue)
|
|
|
|
{
|
|
|
|
direction = queuedDirection.Value;
|
|
|
|
queuedDirection = null;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-05-05 09:31:11 +08:00
|
|
|
// figure out the best direction based on order in playlist.
|
2019-09-17 22:08:37 +08:00
|
|
|
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 17:19:50 +08:00
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
current = beatmap.NewValue;
|
2020-08-04 20:53:00 +08:00
|
|
|
|
|
|
|
drawableTrack?.Expire();
|
|
|
|
drawableTrack = null;
|
|
|
|
track = null;
|
|
|
|
|
|
|
|
if (current != null)
|
|
|
|
trackContainer.Add(drawableTrack = new DrawableTrack(track = current.GetRealTrack()));
|
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
TrackChanged?.Invoke(current, direction);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
2019-09-28 09:18:16 +08:00
|
|
|
ResetTrackAdjustments();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
queuedDirection = null;
|
|
|
|
}
|
|
|
|
|
2019-11-15 12:47:14 +08:00
|
|
|
private bool allowRateAdjustments;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Whether mod rate adjustments are allowed to be applied.
|
|
|
|
/// </summary>
|
|
|
|
public bool AllowRateAdjustments
|
|
|
|
{
|
|
|
|
get => allowRateAdjustments;
|
|
|
|
set
|
|
|
|
{
|
|
|
|
if (allowRateAdjustments == value)
|
|
|
|
return;
|
|
|
|
|
|
|
|
allowRateAdjustments = value;
|
|
|
|
ResetTrackAdjustments();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-28 09:18:16 +08:00
|
|
|
public void ResetTrackAdjustments()
|
2019-04-08 18:16:34 +08:00
|
|
|
{
|
2020-08-04 20:53:00 +08:00
|
|
|
if (drawableTrack == null)
|
2019-04-08 18:16:34 +08:00
|
|
|
return;
|
|
|
|
|
2020-08-04 20:53:00 +08:00
|
|
|
drawableTrack.ResetSpeedAdjustments();
|
2019-04-08 18:16:34 +08:00
|
|
|
|
2019-11-15 12:47:14 +08:00
|
|
|
if (allowRateAdjustments)
|
|
|
|
{
|
2019-12-09 16:34:04 +08:00
|
|
|
foreach (var mod in mods.Value.OfType<IApplicableToTrack>())
|
2020-08-04 20:53:00 +08:00
|
|
|
mod.ApplyToTrack(drawableTrack);
|
2019-11-15 12:47:14 +08:00
|
|
|
}
|
2019-04-08 18:16:34 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 13:38:49 +08:00
|
|
|
public bool OnPressed(GlobalAction action)
|
|
|
|
{
|
2019-08-14 14:19:21 +08:00
|
|
|
if (beatmap.Disabled)
|
|
|
|
return false;
|
|
|
|
|
2019-08-13 13:38:49 +08:00
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case GlobalAction.MusicPlay:
|
|
|
|
if (TogglePause())
|
|
|
|
onScreenDisplay?.Display(new MusicControllerToast(IsPlaying ? "Play track" : "Pause track"));
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case GlobalAction.MusicNext:
|
2020-04-28 10:46:08 +08:00
|
|
|
if (next())
|
2019-08-13 13:38:49 +08:00
|
|
|
onScreenDisplay?.Display(new MusicControllerToast("Next track"));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case GlobalAction.MusicPrev:
|
2020-04-28 10:46:08 +08:00
|
|
|
switch (prev())
|
2019-10-16 21:11:25 +08:00
|
|
|
{
|
2019-10-24 12:10:17 +08:00
|
|
|
case PreviousTrackResult.Restart:
|
2019-10-16 21:11:25 +08:00
|
|
|
onScreenDisplay?.Display(new MusicControllerToast("Restart track"));
|
2019-10-24 09:00:45 +08:00
|
|
|
break;
|
2019-10-16 21:11:25 +08:00
|
|
|
|
2019-10-24 12:10:17 +08:00
|
|
|
case PreviousTrackResult.Previous:
|
2019-10-16 21:11:25 +08:00
|
|
|
onScreenDisplay?.Display(new MusicControllerToast("Previous track"));
|
2019-10-24 09:00:45 +08:00
|
|
|
break;
|
2019-10-16 21:11:25 +08:00
|
|
|
}
|
2019-10-24 09:00:45 +08:00
|
|
|
|
|
|
|
return true;
|
2019-08-13 13:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-01-22 12:22:34 +08:00
|
|
|
public void OnReleased(GlobalAction action)
|
|
|
|
{
|
|
|
|
}
|
2019-08-13 13:38:49 +08:00
|
|
|
|
|
|
|
public class MusicControllerToast : Toast
|
|
|
|
{
|
|
|
|
public MusicControllerToast(string action)
|
|
|
|
: base("Music Playback", action, string.Empty)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
2020-08-04 20:53:00 +08:00
|
|
|
|
|
|
|
private class TrackContainer : AudioContainer<DrawableTrack>
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#region ITrack
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The volume of this component.
|
|
|
|
/// </summary>
|
|
|
|
public BindableNumber<double> Volume => drawableTrack?.Volume; // Todo: Bad
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The playback balance of this sample (-1 .. 1 where 0 is centered)
|
|
|
|
/// </summary>
|
|
|
|
public BindableNumber<double> Balance => drawableTrack?.Balance; // Todo: Bad
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency.
|
|
|
|
/// </summary>
|
|
|
|
public BindableNumber<double> Frequency => drawableTrack?.Frequency; // Todo: Bad
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Rate at which the component is played back (does not affect pitch). 1 is 100% playback speed.
|
|
|
|
/// </summary>
|
|
|
|
public BindableNumber<double> Tempo => drawableTrack?.Tempo; // Todo: Bad
|
|
|
|
|
|
|
|
public IBindable<double> AggregateVolume => drawableTrack?.AggregateVolume; // Todo: Bad
|
|
|
|
|
|
|
|
public IBindable<double> AggregateBalance => drawableTrack?.AggregateBalance; // Todo: Bad
|
|
|
|
|
|
|
|
public IBindable<double> AggregateFrequency => drawableTrack?.AggregateFrequency; // Todo: Bad
|
|
|
|
|
|
|
|
public IBindable<double> AggregateTempo => drawableTrack?.AggregateTempo; // Todo: Bad
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Overall playback rate (1 is 100%, -1 is reversed at 100%).
|
|
|
|
/// </summary>
|
|
|
|
public double Rate => AggregateFrequency.Value * AggregateTempo.Value;
|
|
|
|
|
|
|
|
event Action ITrack.Completed
|
|
|
|
{
|
|
|
|
add
|
|
|
|
{
|
|
|
|
if (drawableTrack != null)
|
|
|
|
drawableTrack.Completed += value;
|
|
|
|
}
|
|
|
|
remove
|
|
|
|
{
|
|
|
|
if (drawableTrack != null)
|
|
|
|
drawableTrack.Completed -= value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
event Action ITrack.Failed
|
|
|
|
{
|
|
|
|
add
|
|
|
|
{
|
|
|
|
if (drawableTrack != null)
|
|
|
|
drawableTrack.Failed += value;
|
|
|
|
}
|
|
|
|
remove
|
|
|
|
{
|
|
|
|
if (drawableTrack != null)
|
|
|
|
drawableTrack.Failed -= value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool Looping
|
|
|
|
{
|
|
|
|
get => drawableTrack?.Looping ?? false;
|
|
|
|
set
|
|
|
|
{
|
|
|
|
if (drawableTrack != null)
|
|
|
|
drawableTrack.Looping = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool IsDummyDevice => drawableTrack?.IsDummyDevice ?? true;
|
|
|
|
|
|
|
|
public double RestartPoint
|
|
|
|
{
|
|
|
|
get => drawableTrack?.RestartPoint ?? 0;
|
|
|
|
set
|
|
|
|
{
|
|
|
|
if (drawableTrack != null)
|
|
|
|
drawableTrack.RestartPoint = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
double ITrack.CurrentTime => CurrentTrackTime;
|
|
|
|
|
|
|
|
double ITrack.Length
|
|
|
|
{
|
|
|
|
get => TrackLength;
|
|
|
|
set
|
|
|
|
{
|
|
|
|
if (drawableTrack != null)
|
|
|
|
drawableTrack.Length = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public int? Bitrate => drawableTrack?.Bitrate;
|
|
|
|
|
|
|
|
bool ITrack.IsRunning => IsPlaying;
|
|
|
|
|
|
|
|
public bool IsReversed => drawableTrack?.IsReversed ?? false;
|
|
|
|
|
|
|
|
public bool HasCompleted => drawableTrack?.HasCompleted ?? false;
|
|
|
|
|
|
|
|
void ITrack.Reset() => drawableTrack?.Reset();
|
|
|
|
|
|
|
|
void ITrack.Restart() => Play(true);
|
|
|
|
|
|
|
|
void ITrack.ResetSpeedAdjustments() => ResetTrackAdjustments();
|
|
|
|
|
|
|
|
bool ITrack.Seek(double seek)
|
|
|
|
{
|
|
|
|
SeekTo(seek);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ITrack.Start() => Play();
|
|
|
|
|
|
|
|
public ChannelAmplitudes CurrentAmplitudes => drawableTrack?.CurrentAmplitudes ?? ChannelAmplitudes.Empty;
|
|
|
|
|
|
|
|
#endregion
|
2019-08-13 13:29:58 +08:00
|
|
|
}
|
2019-06-20 22:40:25 +08:00
|
|
|
|
2019-08-13 13:29:58 +08:00
|
|
|
public enum TrackChangeDirection
|
|
|
|
{
|
|
|
|
None,
|
|
|
|
Next,
|
|
|
|
Prev
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|
2019-10-11 17:41:54 +08:00
|
|
|
|
2019-10-24 12:10:17 +08:00
|
|
|
public enum PreviousTrackResult
|
2019-10-11 17:41:54 +08:00
|
|
|
{
|
|
|
|
None,
|
|
|
|
Restart,
|
|
|
|
Previous
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
}
|