-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'upstream/master'
# Conflicts: # .github/workflows/changelog.yml # Tools/actions_changelogs_since_last_run.py
- Loading branch information
Showing
32 changed files
with
981 additions
and
13 deletions.
There are no files selected for viewing
119 changes: 119 additions & 0 deletions
119
Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
using Content.Shared.Audio.Jukebox; | ||
using Robust.Client.Audio; | ||
using Robust.Client.Player; | ||
using Robust.Shared.Audio.Components; | ||
using Robust.Shared.Player; | ||
using Robust.Shared.Prototypes; | ||
|
||
namespace Content.Client.Audio.Jukebox; | ||
|
||
public sealed class JukeboxBoundUserInterface : BoundUserInterface | ||
{ | ||
[Dependency] private readonly IPlayerManager _player = default!; | ||
[Dependency] private readonly IPrototypeManager _protoManager = default!; | ||
|
||
[ViewVariables] | ||
private JukeboxMenu? _menu; | ||
|
||
public JukeboxBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) | ||
{ | ||
IoCManager.InjectDependencies(this); | ||
} | ||
|
||
protected override void Open() | ||
{ | ||
base.Open(); | ||
|
||
_menu = new JukeboxMenu(); | ||
_menu.OnClose += Close; | ||
_menu.OpenCentered(); | ||
|
||
_menu.OnPlayPressed += args => | ||
{ | ||
if (args) | ||
{ | ||
SendMessage(new JukeboxPlayingMessage()); | ||
} | ||
else | ||
{ | ||
SendMessage(new JukeboxPauseMessage()); | ||
} | ||
}; | ||
|
||
_menu.OnStopPressed += () => | ||
{ | ||
SendMessage(new JukeboxStopMessage()); | ||
}; | ||
|
||
_menu.OnSongSelected += SelectSong; | ||
|
||
_menu.SetTime += SetTime; | ||
PopulateMusic(); | ||
Reload(); | ||
} | ||
|
||
/// <summary> | ||
/// Reloads the attached menu if it exists. | ||
/// </summary> | ||
public void Reload() | ||
{ | ||
if (_menu == null || !EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox)) | ||
return; | ||
|
||
_menu.SetAudioStream(jukebox.AudioStream); | ||
|
||
if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto)) | ||
{ | ||
var length = EntMan.System<AudioSystem>().GetAudioLength(songProto.Path.Path.ToString()); | ||
_menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds); | ||
} | ||
else | ||
{ | ||
_menu.SetSelectedSong(string.Empty, 0f); | ||
} | ||
} | ||
|
||
public void PopulateMusic() | ||
{ | ||
_menu?.Populate(_protoManager.EnumeratePrototypes<JukeboxPrototype>()); | ||
} | ||
|
||
public void SelectSong(ProtoId<JukeboxPrototype> songid) | ||
{ | ||
SendMessage(new JukeboxSelectedMessage(songid)); | ||
} | ||
|
||
public void SetTime(float time) | ||
{ | ||
var sentTime = time; | ||
|
||
// You may be wondering, what the fuck is this | ||
// Well we want to be able to predict the playback slider change, of which there are many ways to do it | ||
// We can't just use SendPredictedMessage because it will reset every tick and audio updates every frame | ||
// so it will go BRRRRT | ||
// Using ping gets us close enough that it SHOULD, MOST OF THE TIME, fall within the 0.1 second tolerance | ||
// that's still on engine so our playback position never gets corrected. | ||
if (EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox) && | ||
EntMan.TryGetComponent(jukebox.AudioStream, out AudioComponent? audioComp)) | ||
{ | ||
audioComp.PlaybackPosition = time; | ||
} | ||
|
||
SendMessage(new JukeboxSetTimeMessage(sentTime)); | ||
} | ||
|
||
protected override void Dispose(bool disposing) | ||
{ | ||
base.Dispose(disposing); | ||
if (!disposing) | ||
return; | ||
|
||
if (_menu == null) | ||
return; | ||
|
||
_menu.OnClose -= Close; | ||
_menu.Dispose(); | ||
_menu = null; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<ui:FancyWindow xmlns="https://spacestation14.io" xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls" | ||
SetSize="400 500" Title="{Loc 'jukebox-menu-title'}"> | ||
<BoxContainer Margin="4 0" Orientation="Vertical"> | ||
<ItemList Name="MusicList" SelectMode="Button" Margin="3 3 3 3" | ||
HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="8"/> | ||
<BoxContainer Orientation="Vertical"> | ||
<Label Name="SongSelected" Text="{Loc 'jukebox-menu-selectedsong'}" /> | ||
<Label Name="SongName" Text="---" /> | ||
<Slider Name="PlaybackSlider" HorizontalExpand="True" /> | ||
</BoxContainer> | ||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" | ||
VerticalExpand="False" SizeFlagsStretchRatio="1"> | ||
<Button Name="PlayButton" Text="{Loc 'jukebox-menu-buttonplay'}" /> | ||
<Button Name="StopButton" Text="{Loc 'jukebox-menu-buttonstop'}" /> | ||
<Label Name="DurationLabel" Text="00:00 / 00:00" HorizontalAlignment="Right" HorizontalExpand="True"/> | ||
</BoxContainer> | ||
</BoxContainer> | ||
</ui:FancyWindow> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
using Content.Shared.Audio.Jukebox; | ||
using Robust.Client.Audio; | ||
using Robust.Client.AutoGenerated; | ||
using Robust.Client.UserInterface; | ||
using Robust.Client.UserInterface.Controls; | ||
using Robust.Client.UserInterface.XAML; | ||
using Robust.Shared.Audio.Components; | ||
using Robust.Shared.Input; | ||
using Robust.Shared.Prototypes; | ||
using Robust.Shared.Timing; | ||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow; | ||
|
||
namespace Content.Client.Audio.Jukebox; | ||
|
||
[GenerateTypedNameReferences] | ||
public sealed partial class JukeboxMenu : FancyWindow | ||
{ | ||
[Dependency] private readonly IEntityManager _entManager = default!; | ||
private AudioSystem _audioSystem; | ||
|
||
/// <summary> | ||
/// Are we currently 'playing' or paused for the play / pause button. | ||
/// </summary> | ||
private bool _playState; | ||
|
||
/// <summary> | ||
/// True if playing, false if paused. | ||
/// </summary> | ||
public event Action<bool>? OnPlayPressed; | ||
public event Action? OnStopPressed; | ||
public event Action<ProtoId<JukeboxPrototype>>? OnSongSelected; | ||
public event Action<float>? SetTime; | ||
|
||
private EntityUid? _audio; | ||
|
||
private float _lockTimer; | ||
|
||
public JukeboxMenu() | ||
{ | ||
RobustXamlLoader.Load(this); | ||
IoCManager.InjectDependencies(this); | ||
_audioSystem = _entManager.System<AudioSystem>(); | ||
|
||
MusicList.OnItemSelected += args => | ||
{ | ||
var entry = MusicList[args.ItemIndex]; | ||
|
||
if (entry.Metadata is not string juke) | ||
return; | ||
|
||
OnSongSelected?.Invoke(juke); | ||
}; | ||
|
||
PlayButton.OnPressed += args => | ||
{ | ||
OnPlayPressed?.Invoke(!_playState); | ||
}; | ||
|
||
StopButton.OnPressed += args => | ||
{ | ||
OnStopPressed?.Invoke(); | ||
}; | ||
PlaybackSlider.OnReleased += PlaybackSliderKeyUp; | ||
|
||
SetPlayPauseButton(_audioSystem.IsPlaying(_audio), force: true); | ||
} | ||
|
||
public JukeboxMenu(AudioSystem audioSystem) | ||
{ | ||
_audioSystem = audioSystem; | ||
} | ||
|
||
public void SetAudioStream(EntityUid? audio) | ||
{ | ||
_audio = audio; | ||
} | ||
|
||
private void PlaybackSliderKeyUp(Slider args) | ||
{ | ||
SetTime?.Invoke(PlaybackSlider.Value); | ||
_lockTimer = 0.5f; | ||
} | ||
|
||
/// <summary> | ||
/// Re-populates the list of jukebox prototypes available. | ||
/// </summary> | ||
public void Populate(IEnumerable<JukeboxPrototype> jukeboxProtos) | ||
{ | ||
MusicList.Clear(); | ||
|
||
foreach (var entry in jukeboxProtos) | ||
{ | ||
MusicList.AddItem(entry.Name, metadata: entry.ID); | ||
} | ||
} | ||
|
||
public void SetPlayPauseButton(bool playing, bool force = false) | ||
{ | ||
if (_playState == playing && !force) | ||
return; | ||
|
||
_playState = playing; | ||
|
||
if (playing) | ||
{ | ||
PlayButton.Text = Loc.GetString("jukebox-menu-buttonpause"); | ||
return; | ||
} | ||
|
||
PlayButton.Text = Loc.GetString("jukebox-menu-buttonplay"); | ||
} | ||
|
||
public void SetSelectedSong(string name, float length) | ||
{ | ||
SetSelectedSongText(name); | ||
PlaybackSlider.MaxValue = length; | ||
PlaybackSlider.SetValueWithoutEvent(0); | ||
} | ||
|
||
protected override void FrameUpdate(FrameEventArgs args) | ||
{ | ||
base.FrameUpdate(args); | ||
|
||
if (_lockTimer > 0f) | ||
{ | ||
_lockTimer -= args.DeltaSeconds; | ||
} | ||
|
||
PlaybackSlider.Disabled = _lockTimer > 0f; | ||
|
||
if (_entManager.TryGetComponent(_audio, out AudioComponent? audio)) | ||
{ | ||
DurationLabel.Text = $@"{TimeSpan.FromSeconds(audio.PlaybackPosition):mm\:ss} / {_audioSystem.GetAudioLength(audio.FileName):mm\:ss}"; | ||
} | ||
else | ||
{ | ||
DurationLabel.Text = $"00:00 / 00:00"; | ||
} | ||
|
||
if (PlaybackSlider.Grabbed) | ||
return; | ||
|
||
if (audio != null || _entManager.TryGetComponent(_audio, out audio)) | ||
{ | ||
PlaybackSlider.SetValueWithoutEvent(audio.PlaybackPosition); | ||
} | ||
else | ||
{ | ||
PlaybackSlider.SetValueWithoutEvent(0f); | ||
} | ||
|
||
SetPlayPauseButton(_audioSystem.IsPlaying(_audio, audio)); | ||
} | ||
|
||
public void SetSelectedSongText(string? text) | ||
{ | ||
if (!string.IsNullOrEmpty(text)) | ||
{ | ||
SongName.Text = text; | ||
} | ||
else | ||
{ | ||
SongName.Text = "---"; | ||
} | ||
} | ||
} |
Oops, something went wrong.