Queue in JukeBox (#640)

* Queue in JukeBox

* Translation JukeBox
This commit is contained in:
Jabak
2024-08-19 21:11:35 +03:00
committed by GitHub
parent 92d98cea55
commit b6d3c050a0
10 changed files with 388 additions and 83 deletions

View File

@@ -47,6 +47,16 @@ public sealed class JukeboxBoundUserInterface : BoundUserInterface
_menu.OnSongSelected += SelectSong;
_menu.OnSongQueueAdd += songId =>
{
SendMessage(new JukeboxAddQueueMessage(songId));
};
_menu.OnQueueRemove += index =>
{
SendMessage(new JukeboxRemoveQueueMessage(index));
};
_menu.SetTime += SetTime;
PopulateMusic();
Reload();
@@ -65,17 +75,24 @@ public sealed class JukeboxBoundUserInterface : BoundUserInterface
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);
_menu.SetSelectedSong(songProto.ID, (float) length.TotalSeconds);
}
else
{
_menu.SetSelectedSong(string.Empty, 0f);
_menu.SetSelectedSong(null, 0f);
}
_menu.PopulateQueue(jukebox.SongIdQueue);
_menu.SetIsPlaying(EntMan.System<AudioSystem>().IsPlaying(jukebox.AudioStream));
}
public void PopulateMusic()
{
_menu?.Populate(_protoManager.EnumeratePrototypes<JukeboxPrototype>());
if (_menu == null || !EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox))
return;
_menu.Populate(_protoManager.EnumeratePrototypes<JukeboxPrototype>());
_menu.PopulateQueue(jukebox.SongIdQueue);
}
public void SelectSong(ProtoId<JukeboxPrototype> songid)

View File

@@ -0,0 +1,9 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Horizontal"
HorizontalExpand="True">
<Label Name="SongName" HorizontalAlignment="Left" HorizontalExpand="True"/>
<Button Name="PlayButton" StyleClasses="OpenRight" Text="{Loc 'jukebox-menu-buttonplay'}" />
<Button Name="StopButton" StyleClasses="OpenLeft" Text="{Loc 'jukebox-menu-buttonstop'}" />
<Button Name="RemoveButton" Text="{Loc 'jukebox-menu-buttonremove'}" />
<Button Name="QueueButton" StyleClasses="OpenLeft" Text="{Loc 'jukebox-menu-buttonqueue'}" />
</BoxContainer>

View File

@@ -0,0 +1,127 @@
using Content.Shared.Audio.Jukebox;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Audio.Jukebox;
[GenerateTypedNameReferences]
public sealed partial class JukeboxEntry : BoxContainer
{
private JukeboxPrototype? _song;
private bool _playPauseState = true;
public bool PlayPauseState
{
get {return _playPauseState;}
set {
_playPauseState = value;
UpdateLabel();
}
}
private JukeboxEntryType _entryType;
public JukeboxEntryType EntryType
{
get {return _entryType;}
set {
_entryType = value;
Buttons();
}
}
public JukeboxEntry()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Buttons();
}
public JukeboxEntry(JukeboxPrototype? song) : this()
{
SetSong(song);
Buttons();
}
public void SetSong(JukeboxPrototype? song)
{
_song = song;
if (song == null)
{
Buttons();
return;
}
SongName.Text = song.Name;
Buttons();
}
public void SetOnPressedPlay(Action<JukeboxPrototype?, bool, BaseButton.ButtonEventArgs>? func)
{
if (func == null)
return;
PlayButton.OnPressed += args => {
if (_entryType == JukeboxEntryType.Current)
{
_playPauseState = !_playPauseState;
UpdateLabel();
}
func(_song, _playPauseState, args);
};
}
public void SetOnPressedQueue(Action<JukeboxPrototype?, BaseButton.ButtonEventArgs>? func)
{
if (func == null)
return;
QueueButton.OnPressed += args => {
func(_song, args);
};
}
public void SetOnPressedStop(Action<BaseButton.ButtonEventArgs>? func)
{
StopButton.OnPressed += func;
}
public void SetOnPressedRemove(Action<JukeboxEntry, BaseButton.ButtonEventArgs>? func)
{
if (func == null)
return;
RemoveButton.OnPressed += args => {
func(this, args);
};
}
private void Buttons()
{
PlayButton.Visible = _song != null && (_entryType == JukeboxEntryType.List || _entryType == JukeboxEntryType.Current);
StopButton.Visible = _song != null && _entryType == JukeboxEntryType.Current;
RemoveButton.Visible = _song != null && _entryType == JukeboxEntryType.Queue;
QueueButton.Visible = _song != null && _entryType == JukeboxEntryType.List;
}
private void UpdateLabel()
{
if (!_playPauseState)
{
PlayButton.Text = Loc.GetString("jukebox-menu-buttonplay");
}
else
{
PlayButton.Text = Loc.GetString("jukebox-menu-buttonpause");
}
}
public enum JukeboxEntryType
{
List,
Queue,
Current,
}
}

View File

@@ -1,18 +1,23 @@
<ui:FancyWindow xmlns="https://spacestation14.io" xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:jb="clr-namespace:Content.Client.Audio.Jukebox"
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"/>
<Label Text="{Loc 'jukebox-menu-songs'}"/>
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True" Margin="13 3 3 3">
<BoxContainer Name="MusicList" Orientation="Vertical"
HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="8"/>
</ScrollContainer>
<Label Text="{Loc 'jukebox-menu-queue'}"/>
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True" Margin="13 3 3 3">
<BoxContainer Name="MusicListQueue" Orientation="Vertical"
HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="8"/>
</ScrollContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Name="SongSelected" Text="{Loc 'jukebox-menu-selectedsong-none'}" />
<BoxContainer HorizontalExpand="True"/>
<Label Name="DurationLabel" Text="---" />
</BoxContainer>
<Slider Name="PlaybackSlider" Visible="false" HorizontalExpand="True" />
<jb:JukeboxEntry Name="CurrentSong" Visible="false" EntryType="Current"/>
</BoxContainer>
</ui:FancyWindow>

View File

@@ -14,12 +14,10 @@ namespace Content.Client.Audio.Jukebox;
public sealed partial class JukeboxMenu : FancyWindow
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = 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.
@@ -29,6 +27,9 @@ public sealed partial class JukeboxMenu : FancyWindow
public event Action<ProtoId<JukeboxPrototype>>? OnSongSelected;
public event Action<float>? SetTime;
public event Action<ProtoId<JukeboxPrototype>>? OnSongQueueAdd;
public event Action<int>? OnQueueRemove;
private EntityUid? _audio;
private float _lockTimer;
@@ -39,28 +40,18 @@ public sealed partial class JukeboxMenu : FancyWindow
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 =>
CurrentSong.SetOnPressedStop(args =>
{
OnStopPressed?.Invoke();
};
});
CurrentSong.SetOnPressedPlay((song, playPauseState, args) =>
{
OnPlayPressed?.Invoke(playPauseState);
});
PlaybackSlider.OnReleased += PlaybackSliderKeyUp;
SetPlayPauseButton(_audioSystem.IsPlaying(_audio), force: true);
}
public JukeboxMenu(AudioSystem audioSystem)
@@ -84,33 +75,43 @@ public sealed partial class JukeboxMenu : FancyWindow
/// </summary>
public void Populate(IEnumerable<JukeboxPrototype> jukeboxProtos)
{
MusicList.Clear();
MusicList.RemoveAllChildren();
foreach (var entry in jukeboxProtos)
{
MusicList.AddItem(entry.Name, metadata: entry.ID);
// MusicList.AddItem(entry.Name, metadata: entry.ID);
var songControl = new JukeboxEntry(entry) {EntryType = JukeboxEntry.JukeboxEntryType.List};
songControl.SetOnPressedPlay((song, _, args) =>
{
if (song == null)
return;
OnSongSelected?.Invoke(song.ID);
OnPlayPressed?.Invoke(true);
});
songControl.SetOnPressedQueue((song, args) =>
{
if (song == null)
return;
OnSongQueueAdd?.Invoke(song.ID);
});
MusicList.AddChild(songControl);
}
}
public void SetPlayPauseButton(bool playing, bool force = false)
public void SetSelectedSong(ProtoId<JukeboxPrototype>? song, float length)
{
if (_playState == playing && !force)
if (song == null)
return;
_playState = playing;
if (playing)
{
PlayButton.Text = Loc.GetString("jukebox-menu-buttonpause");
if (!_prototype.TryIndex(song, out var songProto))
return;
}
PlayButton.Text = Loc.GetString("jukebox-menu-buttonplay");
}
public void SetSelectedSong(string name, float length)
{
SetSelectedSongText(name);
SongSelected.Text = Loc.GetString("jukebox-menu-selectedsong");
PlaybackSlider.Visible = true;
CurrentSong.Visible = true;
CurrentSong.SetSong(songProto);
PlaybackSlider.MaxValue = length;
PlaybackSlider.SetValueWithoutEvent(0);
}
@@ -145,18 +146,34 @@ public sealed partial class JukeboxMenu : FancyWindow
PlaybackSlider.SetValueWithoutEvent(0f);
}
SetPlayPauseButton(_audioSystem.IsPlaying(_audio, audio));
}
public void SetSelectedSongText(string? text)
/// <summary>
/// Re-populates the queue with avaiable jukebox prototypes.
/// </summary>
public void PopulateQueue(IEnumerable<ProtoId<JukeboxPrototype>> queue)
{
if (!string.IsNullOrEmpty(text))
MusicListQueue.RemoveAllChildren();
int i = 0;
foreach (var song in queue)
{
SongName.Text = text;
}
else
{
SongName.Text = "---";
if (!_prototype.TryIndex(song, out var songProto))
continue;
i += 1;
var songControl = new JukeboxEntry(songProto) {EntryType = JukeboxEntry.JukeboxEntryType.Queue};
MusicListQueue.AddChild(songControl);
songControl.SetOnPressedRemove((source, args) =>
{
int index = source.GetPositionInParent();
OnQueueRemove?.Invoke(index);
});
}
}
public void SetIsPlaying(bool isPlaying)
{
CurrentSong.PlayPauseState = isPlaying;
}
}

View File

@@ -35,7 +35,7 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
var query = AllEntityQuery<JukeboxComponent, UserInterfaceComponent>();
while (query.MoveNext(out _, out var ui))
while (query.MoveNext(out var uid, out var jukebox, out var ui))
{
if (!ui.OpenInterfaces.TryGetValue(JukeboxUiKey.Key, out var baseBui) ||
baseBui is not JukeboxBoundUserInterface bui)
@@ -43,6 +43,24 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
continue;
}
if (obj.Removed?.TryGetValue(typeof(JukeboxPrototype), out var removed) ?? false)
{
var list = new List<ProtoId<JukeboxPrototype>>();
while (jukebox.SongIdQueue.Count > 0)
{
var next = jukebox.SongIdQueue.Dequeue();
if (removed.Contains(next))
continue;
list.Add(next);
}
foreach (var song in list)
{
jukebox.SongIdQueue.Enqueue(song);
}
}
bui.PopulateMusic();
}
}

View File

@@ -15,6 +15,8 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
public override void Initialize()
{
@@ -26,6 +28,9 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
SubscribeLocalEvent<JukeboxComponent, JukeboxSetTimeMessage>(OnJukeboxSetTime);
SubscribeLocalEvent<JukeboxComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<JukeboxComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<JukeboxComponent, JukeboxAddQueueMessage>(OnJukeboxAddQueue);
SubscribeLocalEvent<JukeboxComponent, JukeboxRemoveQueueMessage>(OnJukeboxRemoveQueue);
SubscribeLocalEvent<JukeboxMusicComponent, ComponentShutdown>(OnAudioShutdown);
SubscribeLocalEvent<JukeboxComponent, PowerChangedEvent>(OnPowerChanged);
}
@@ -40,28 +45,44 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
private void OnJukeboxPlay(EntityUid uid, JukeboxComponent component, ref JukeboxPlayingMessage args)
{
if (Exists(component.AudioStream))
OnJukeboxPlay(uid, component);
}
private void OnJukeboxAddQueue(EntityUid uid, JukeboxComponent component, JukeboxAddQueueMessage args)
{
if (component.SelectedSongId == null)
{
Audio.SetState(component.AudioStream, AudioState.Playing);
component.SelectedSongId = args.SongId;
}
else
{
component.AudioStream = Audio.Stop(component.AudioStream);
if (string.IsNullOrEmpty(component.SelectedSongId) ||
!_protoManager.TryIndex(component.SelectedSongId, out var jukeboxProto))
{
return;
}
component.AudioStream = Audio.PlayPvs(jukeboxProto.Path, uid, AudioParams.Default.WithMaxDistance(10f))?.Entity;
Dirty(uid, component);
component.SongIdQueue.Enqueue(args.SongId);
}
Dirty(uid, component);
}
private void OnJukeboxRemoveQueue(EntityUid uid, JukeboxComponent component, JukeboxRemoveQueueMessage args)
{
if (args.Index < 0 || args.Index >= component.SongIdQueue.Count)
return;
var list = new List<ProtoId<JukeboxPrototype>>(component.SongIdQueue);
list.RemoveAt(args.Index);
component.SongIdQueue.Clear();
foreach (var entry in list)
{
component.SongIdQueue.Enqueue(entry);
}
Dirty(uid, component);
}
private void OnJukeboxPause(Entity<JukeboxComponent> ent, ref JukeboxPauseMessage args)
{
Audio.SetState(ent.Comp.AudioStream, AudioState.Paused);
Dirty(ent);
}
private void OnJukeboxSetTime(EntityUid uid, JukeboxComponent component, JukeboxSetTimeMessage args)
@@ -85,6 +106,26 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
Stop(entity);
}
private void OnAudioShutdown(Entity<JukeboxMusicComponent> ent, ref ComponentShutdown args)
{
var query = EntityQueryEnumerator<JukeboxComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.AudioStream == ent.Owner)
{
var next = SongQueueDequeue((uid, comp));
if (next == null)
{
Stop((uid, comp));
continue;
}
OnJukeboxSelected(uid, comp, next.Value);
OnJukeboxPlay(uid, comp);
}
}
}
private void Stop(Entity<JukeboxComponent> entity)
{
Audio.SetState(entity.Comp.AudioStream, AudioState.Stopped);
@@ -93,15 +134,7 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, JukeboxSelectedMessage args)
{
if (!Audio.IsPlaying(component.AudioStream))
{
component.SelectedSongId = args.SongId;
DirectSetVisualState(uid, JukeboxVisualState.Select);
component.Selecting = true;
component.AudioStream = Audio.Stop(component.AudioStream);
}
Dirty(uid, component);
OnJukeboxSelected(uid, component, args.SongId);
}
public override void Update(float frameTime)
@@ -135,6 +168,42 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
_appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, state);
}
private void OnJukeboxPlay(EntityUid uid, JukeboxComponent component)
{
if (Exists(component.AudioStream))
{
Audio.SetState(component.AudioStream, AudioState.Playing);
}
else
{
component.AudioStream = Audio.Stop(component.AudioStream);
if (string.IsNullOrEmpty(component.SelectedSongId) ||
!_protoManager.TryIndex(component.SelectedSongId, out var jukeboxProto))
{
return;
}
component.AudioStream = Audio.PlayPvs(jukeboxProto.Path, uid, AudioParams.Default.WithMaxDistance(10f))?.Entity;
if (component.AudioStream != null)
{
AddComp<JukeboxMusicComponent>(component.AudioStream.Value);
}
Dirty(uid, component);
}
}
private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, ProtoId<JukeboxPrototype> songId)
{
component.SelectedSongId = songId;
DirectSetVisualState(uid, JukeboxVisualState.Select);
component.Selecting = true;
component.AudioStream = Audio.Stop(component.AudioStream);
Dirty(uid, component);
}
private void TryUpdateVisualState(EntityUid uid, JukeboxComponent? jukeboxComponent = null)
{
if (!Resolve(uid, ref jukeboxComponent))
@@ -149,4 +218,14 @@ public sealed class JukeboxSystem : SharedJukeboxSystem
_appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, finalState);
}
private ProtoId<JukeboxPrototype>? SongQueueDequeue(Entity<JukeboxComponent> ent)
{
if (ent.Comp.SongIdQueue.Count == 0)
return null;
var next = ent.Comp.SongIdQueue.Dequeue();
return next;
}
}

View File

@@ -14,6 +14,9 @@ public sealed partial class JukeboxComponent : Component
[DataField, AutoNetworkedField]
public EntityUid? AudioStream;
[DataField, AutoNetworkedField]
public Queue<ProtoId<JukeboxPrototype>> SongIdQueue = new();
/// <summary>
/// RSI state for the jukebox being on.
/// </summary>
@@ -39,6 +42,9 @@ public sealed partial class JukeboxComponent : Component
public float SelectAccumulator;
}
[RegisterComponent]
public sealed partial class JukeboxMusicComponent : Component;
[Serializable, NetSerializable]
public sealed class JukeboxPlayingMessage : BoundUserInterfaceMessage;
@@ -60,6 +66,18 @@ public sealed class JukeboxSetTimeMessage(float songTime) : BoundUserInterfaceMe
public float SongTime { get; } = songTime;
}
[Serializable, NetSerializable]
public sealed class JukeboxAddQueueMessage(ProtoId<JukeboxPrototype> songId) : BoundUserInterfaceMessage
{
public ProtoId<JukeboxPrototype> SongId { get; } = songId;
}
[Serializable, NetSerializable]
public sealed class JukeboxRemoveQueueMessage(int index) : BoundUserInterfaceMessage
{
public int Index { get; } = index;
}
[Serializable, NetSerializable]
public enum JukeboxVisuals : byte
{

View File

@@ -1,5 +1,10 @@
jukebox-menu-title = Jukebox
jukebox-menu-selectedsong = Selected Song:
jukebox-menu-selectedsong-none = No Song Selected
jukebox-menu-buttonplay = Play
jukebox-menu-buttonpause = Pause
jukebox-menu-buttonstop = Stop
jukebox-menu-buttonqueue = Queue
jukebox-menu-buttonremove = Remove
jukebox-menu-songs = Songs:
jukebox-menu-queue = Song queue:

View File

@@ -0,0 +1,10 @@
jukebox-menu-title = Jukebox
jukebox-menu-selectedsong = Выбранная песня:
jukebox-menu-selectedsong-none = Песня не выбрана
jukebox-menu-buttonplay = Играть
jukebox-menu-buttonpause = Пауза
jukebox-menu-buttonstop = Стоп
jukebox-menu-buttonqueue = Очередь
jukebox-menu-buttonremove = Убрать
jukebox-menu-songs = Песни:
jukebox-menu-queue = Очередь: