Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
# Conflicts:
#	.github/workflows/changelog.yml
#	Tools/actions_changelogs_since_last_run.py
  • Loading branch information
Evgencheg committed Sep 2, 2024
2 parents 449bb57 + 5530834 commit b63fc09
Show file tree
Hide file tree
Showing 32 changed files with 981 additions and 13 deletions.
119 changes: 119 additions & 0 deletions Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
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;
}
}

18 changes: 18 additions & 0 deletions Content.Client/Audio/Jukebox/JukeboxMenu.xaml
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>
166 changes: 166 additions & 0 deletions Content.Client/Audio/Jukebox/JukeboxMenu.xaml.cs
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 = "---";
}
}
}
Loading

0 comments on commit b63fc09

Please sign in to comment.