diff --git a/private/Nitrocid.Analyzers.Test/Nitrocid.Analyzers.Test.csproj b/private/Nitrocid.Analyzers.Test/Nitrocid.Analyzers.Test.csproj index 7eb3088f23..ab09a563be 100644 --- a/private/Nitrocid.Analyzers.Test/Nitrocid.Analyzers.Test.csproj +++ b/private/Nitrocid.Analyzers.Test/Nitrocid.Analyzers.Test.csproj @@ -1,4 +1,4 @@ - + ..\KSTest\ diff --git a/private/Nitrocid.LocaleCheck/Nitrocid.LocaleCheck.csproj b/private/Nitrocid.LocaleCheck/Nitrocid.LocaleCheck.csproj index 995c7624ee..e932ae1004 100644 --- a/private/Nitrocid.LocaleCheck/Nitrocid.LocaleCheck.csproj +++ b/private/Nitrocid.LocaleCheck/Nitrocid.LocaleCheck.csproj @@ -18,7 +18,7 @@ true - + diff --git a/private/Nitrocid.LocaleClean/Nitrocid.LocaleClean.csproj b/private/Nitrocid.LocaleClean/Nitrocid.LocaleClean.csproj index ca57c71a03..57c5336caa 100644 --- a/private/Nitrocid.LocaleClean/Nitrocid.LocaleClean.csproj +++ b/private/Nitrocid.LocaleClean/Nitrocid.LocaleClean.csproj @@ -18,7 +18,7 @@ true - + diff --git a/private/Nitrocid.LocaleTrim/Nitrocid.LocaleTrim.csproj b/private/Nitrocid.LocaleTrim/Nitrocid.LocaleTrim.csproj index f35b29512d..84ad7f3867 100644 --- a/private/Nitrocid.LocaleTrim/Nitrocid.LocaleTrim.csproj +++ b/private/Nitrocid.LocaleTrim/Nitrocid.LocaleTrim.csproj @@ -18,7 +18,7 @@ true - + diff --git a/private/Nitrocid.Tests/Nitrocid.Tests.csproj b/private/Nitrocid.Tests/Nitrocid.Tests.csproj index 215b55b718..25b43712ca 100644 --- a/private/Nitrocid.Tests/Nitrocid.Tests.csproj +++ b/private/Nitrocid.Tests/Nitrocid.Tests.csproj @@ -1,4 +1,4 @@ - + KSTest\ @@ -61,7 +61,7 @@ - + diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/BassBoomInit.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/BassBoomInit.cs index be09cd4f44..f9a43da229 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/BassBoomInit.cs +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/BassBoomInit.cs @@ -55,11 +55,17 @@ using Nitrocid.Files.Paths; using Nitrocid.Modifications; using System.Linq; +using Terminaux.Colors; +using Terminaux.Colors.Data; +using Nitrocid.Shell.ShellBase.Switches; namespace Nitrocid.Extras.BassBoom { internal class BassBoomInit : IAddon { + internal static Version mpgVer; + internal static Version outVer; + internal static Color white = new(ConsoleColors.White); private readonly ExtensionHandler[] handlers = [ new(".mp3", "Mp3BassBoom", PlayerHandler.Handle, PlayerHandler.InfoHandle), new(".mp2", "Mp3BassBoom", PlayerHandler.Handle, PlayerHandler.InfoHandle), @@ -80,10 +86,13 @@ internal class BassBoomInit : IAddon new CommandInfo("musicplayer", /* Localizable */ "Opens an interactive music player", [ - new CommandArgumentInfo(new[] - { + new CommandArgumentInfo( + [ new CommandArgumentPart(false, "musicFile"), - }) + ], + [ + new SwitchInfo("r", /* Localizable */ "Opens the radio station player instead") + ]) ], new MusicPlayerCommand()), new CommandInfo("playlyric", /* Localizable */ "Plays a lyric file", @@ -135,6 +144,10 @@ void IAddon.StartAddon() if (!InitBasolia.BasoliaInitialized) InitBasolia.Init(PathsManagement.AddonsPath + "/Extras.BassBoom"); ExtensionHandlerTools.extensionHandlers.AddRange(handlers); + + // Initialize versions + mpgVer = InitBasolia.MpgLibVersion; + outVer = InitBasolia.OutLibVersion; } void IAddon.StopAddon() diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Commands/MusicPlayer.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Commands/MusicPlayer.cs index ba35d4295d..55e0bddb7c 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Commands/MusicPlayer.cs +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Commands/MusicPlayer.cs @@ -19,12 +19,12 @@ using Terminaux.Writer.ConsoleWriters; using Nitrocid.Extras.BassBoom.Player; -using Nitrocid.Files; using Nitrocid.Files.Operations.Querying; using Nitrocid.Kernel.Debugging; using Nitrocid.Languages; using Nitrocid.Shell.ShellBase.Commands; using System; +using System.Linq; namespace Nitrocid.Extras.BassBoom.Commands { @@ -42,28 +42,27 @@ public override int Execute(CommandParameters parameters, ref string variableVal try { // First, prompt for the music path if no arguments are provided. + bool isRadio = parameters.SwitchesList.Contains("-r"); if (parameters.ArgumentsList.Length != 0) { - string musicPath = FilesystemTools.NeutralizePath(parameters.ArgumentsList[0]); + string musicPath = parameters.ArgumentsList[0]; // Check for existence. - if (string.IsNullOrEmpty(musicPath)) - { - TextWriterColor.Write(Translate.DoTranslation("Music file not specified.")); - return 30; - } - if (!Checking.FileExists(musicPath)) + if (string.IsNullOrEmpty(musicPath) || (!isRadio && !Checking.FileExists(musicPath))) { TextWriterColor.Write(Translate.DoTranslation("Music file '{0}' doesn't exist."), musicPath); return 31; } - if (!PlayerTui.musicFiles.Contains(musicPath)) - PlayerTui.musicFiles.Add(musicPath); - PlayerControls.PopulateMusicFileInfo(musicPath); + if (!isRadio) + PlayerTui.passedMusicPaths.Add(musicPath); } // Now, open an interactive TUI - PlayerTui.PlayerLoop(); + Common.exiting = false; + if (isRadio) + Radio.RadioLoop(); + else + PlayerTui.PlayerLoop(); } catch (Exception ex) { diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Nitrocid.Extras.BassBoom.csproj b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Nitrocid.Extras.BassBoom.csproj index 337bc004a7..92bae0e74e 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Nitrocid.Extras.BassBoom.csproj +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Nitrocid.Extras.BassBoom.csproj @@ -37,7 +37,7 @@ - + diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Common.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Common.cs new file mode 100644 index 0000000000..995863e43e --- /dev/null +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Common.cs @@ -0,0 +1,301 @@ +// +// BassBoom Copyright (C) 2023 Aptivi +// +// This file is part of BassBoom +// +// BassBoom is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// BassBoom is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using BassBoom.Basolia; +using BassBoom.Basolia.Devices; +using BassBoom.Basolia.File; +using BassBoom.Basolia.Format; +using BassBoom.Basolia.Playback; +using Nitrocid.Extras.BassBoom.Player.Tools; +using Nitrocid.Languages; +using SpecProbe.Platform; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using Terminaux.Base.Buffered; +using Terminaux.Base.Extensions; +using Terminaux.Inputs; +using Terminaux.Inputs.Styles.Infobox; + +namespace Nitrocid.Extras.BassBoom.Player +{ + internal static class Common + { + internal static double volume = 1.0; + internal static bool enableDisco = false; + internal static int currentPos = 1; + internal static bool exiting = false; + internal static bool advance = false; + internal static bool populate = true; + internal static bool paused = false; + internal static bool failedToPlay = false; + internal static bool isRadioMode = false; + internal static readonly List cachedInfos = []; + + internal static CachedSongInfo CurrentCachedInfo => + cachedInfos.Count > 0 ? cachedInfos[currentPos - 1] : null; + + internal static void RaiseVolume() + { + volume += 0.05; + if (volume > 1) + volume = 1; + PlaybackTools.SetVolume(volume); + } + + internal static void LowerVolume() + { + volume -= 0.05; + if (volume < 0) + volume = 0; + PlaybackTools.SetVolume(volume); + } + + internal static void Exit() + { + exiting = true; + advance = false; + if (FileTools.IsOpened) + PlaybackTools.Stop(); + } + + internal static void Switch(string musicPath) + { + if (FileTools.IsOpened) + FileTools.CloseFile(); + if (isRadioMode) + FileTools.OpenUrl(musicPath); + else + FileTools.OpenFile(musicPath); + } + + internal static void ShowDeviceDriver() + { + var builder = new StringBuilder(); + var currentBuilder = new StringBuilder(); + if (PlaybackTools.Playing) + { + var (driver, device) = DeviceTools.GetCurrent(); + var cached = DeviceTools.GetCurrentCached(); + currentBuilder.AppendLine( + $$""" + {{Translate.DoTranslation("Device")}}: {{device}} + {{Translate.DoTranslation("Driver")}}: {{driver}} + {{Translate.DoTranslation("Device (cached")}}: {{cached.device}} + {{Translate.DoTranslation("Driver (cached")}}: {{cached.driver}} + """ + ); + } + else + currentBuilder.AppendLine(Translate.DoTranslation("Can't query current devices while not playing.")); + var drivers = DeviceTools.GetDrivers(); + string activeDevice = ""; + foreach (var driver in drivers) + { + try + { + builder.AppendLine($"- {driver.Key}: {driver.Value}"); + var devices = DeviceTools.GetDevices(driver.Key, ref activeDevice); + foreach (var device in devices) + builder.AppendLine($" - {device.Key}: {device.Value}"); + } + catch + { + continue; + } + } + string section1 = Translate.DoTranslation("Device and Driver"); + string section2 = Translate.DoTranslation("Available devices and drivers"); + InfoBoxColor.WriteInfoBox( + $$""" + {{section1}} + {{new string('=', ConsoleChar.EstimateCellWidth(section1))}} + + {{currentBuilder}} + + {{section2}} + {{new string('=', ConsoleChar.EstimateCellWidth(section2))}} + + {{builder}} + """ + ); + } + + internal static void ShowSpecs() + { + string section1 = Translate.DoTranslation("BassBoom specifications"); + string section2 = Translate.DoTranslation("Decoders"); + string section3 = Translate.DoTranslation("System specifications"); + InfoBoxColor.WriteInfoBox( + $$""" + {{section1}} + {{new string('=', ConsoleChar.EstimateCellWidth(section1))}} + + {{Translate.DoTranslation("Basolia version")}}: {{InitBasolia.BasoliaVersion}} + {{Translate.DoTranslation("MPG123 version")}}: {{InitBasolia.MpgLibVersion}} + {{Translate.DoTranslation("OUT123 version")}}: {{InitBasolia.OutLibVersion}} + + {{section2}} + {{new string('=', ConsoleChar.EstimateCellWidth(section2))}} + + {{Translate.DoTranslation("Supported decoders")}}: + - {{string.Join("\n - ", DecodeTools.GetDecoders(true))}} + + {{Translate.DoTranslation("All decoders")}}: + - {{string.Join("\n - ", DecodeTools.GetDecoders(false))}} + + {{section3}} + {{new string('=', ConsoleChar.EstimateCellWidth(section3))}} + + {{Translate.DoTranslation("System")}}: {{(PlatformHelper.IsOnWindows() ? "Windows" : PlatformHelper.IsOnMacOS() ? "macOS" : "Unix/Linux")}} + {{Translate.DoTranslation("System Architecture")}}: {{RuntimeInformation.OSArchitecture}} + {{Translate.DoTranslation("Process Architecture")}}: {{RuntimeInformation.ProcessArchitecture}} + {{Translate.DoTranslation("System description")}}: {{RuntimeInformation.OSDescription}} + {{Translate.DoTranslation(".NET description")}}: {{RuntimeInformation.FrameworkDescription}} + """ + ); + } + + internal static void ShowHelp() + { + string section1 = Translate.DoTranslation("Available keystrokes"); + InfoBoxColor.WriteInfoBox( + $$""" + {{section1}} + {{new string('=', ConsoleChar.EstimateCellWidth(section1))}} + + [SPACE] {{Translate.DoTranslation("Play/Pause")}} + [ESC] {{Translate.DoTranslation("Stop")}} + [Q] {{Translate.DoTranslation("Exit")}} + [UP/DOWN] {{Translate.DoTranslation("Volume control")}} + [<-/->] {{Translate.DoTranslation("Seek control")}} + [CTRL] + [<-/->] {{Translate.DoTranslation("Seek duration control")}} + [I] {{Translate.DoTranslation("Song info")}} + [A] {{Translate.DoTranslation("Add a music file")}} + [S] {{Translate.DoTranslation("(when idle) Add a music directory to the playlist")}} + [B] {{Translate.DoTranslation("Previous song")}} + [N] {{Translate.DoTranslation("Next song")}} + [R] {{Translate.DoTranslation("Remove current song")}} + [CTRL] + [R] {{Translate.DoTranslation("Remove all songs")}} + [S] {{Translate.DoTranslation("(when playing) Selectively seek")}} + [F] {{Translate.DoTranslation("(when playing) Seek to previous lyric")}} + [G] {{Translate.DoTranslation("(when playing) Seek to next lyric")}} + [J] {{Translate.DoTranslation("(when playing) Seek to current lyric")}} + [K] {{Translate.DoTranslation("(when playing) Seek to which lyric")}} + [C] {{Translate.DoTranslation("Set repeat checkpoint")}} + [SHIFT] + [C] {{Translate.DoTranslation("Seek to repeat checkpoint")}} + [E] {{Translate.DoTranslation("Opens the equalizer")}} + [D] {{Translate.DoTranslation("Device and driver info")}} + [CTRL] + [D] {{Translate.DoTranslation("Set device and driver")}} + [SHIFT] + [D] {{Translate.DoTranslation("Reset device and driver")}} + [Z] {{Translate.DoTranslation("System info")}} + """ + ); + } + + internal static void ShowHelpRadio() + { + string section1 = Translate.DoTranslation("Available keystrokes"); + InfoBoxColor.WriteInfoBox( + $$""" + {{section1}} + {{new string('=', ConsoleChar.EstimateCellWidth(section1))}} + + [SPACE] {{Translate.DoTranslation("Play/Pause")}} + [ESC] {{Translate.DoTranslation("Stop")}} + [Q] {{Translate.DoTranslation("Exit")}} + [UP/DOWN] {{Translate.DoTranslation("Volume control")}} + [I] {{Translate.DoTranslation("Radio station info")}} + [CTRL] + [I] {{Translate.DoTranslation("Radio station extended info")}} + [A] {{Translate.DoTranslation("Add a radio station")}} + [B] {{Translate.DoTranslation("Previous radio station")}} + [N] {{Translate.DoTranslation("Next radio station")}} + [R] {{Translate.DoTranslation("Remove current radio station")}} + [CTRL] + [R] {{Translate.DoTranslation("Remove all radio stations")}} + [E] {{Translate.DoTranslation("Opens the equalizer")}} + [D] {{Translate.DoTranslation("Device and driver info")}} + [CTRL] + [D] {{Translate.DoTranslation("Set device and driver")}} + [SHIFT] + [D] {{Translate.DoTranslation("Reset device and driver")}} + [Z] {{Translate.DoTranslation("System info")}} + """ + ); + } + + internal static void HandleKeypressCommon(ConsoleKeyInfo keystroke, Screen playerScreen, bool radio) + { + switch (keystroke.Key) + { + case ConsoleKey.UpArrow: + RaiseVolume(); + break; + case ConsoleKey.DownArrow: + LowerVolume(); + break; + case ConsoleKey.H: + if (radio) + ShowHelpRadio(); + else + ShowHelp(); + playerScreen.RequireRefresh(); + break; + case ConsoleKey.E: + Equalizer.OpenEqualizer(playerScreen); + playerScreen.RequireRefresh(); + break; + case ConsoleKey.Z: + ShowSpecs(); + playerScreen.RequireRefresh(); + break; + case ConsoleKey.L: + enableDisco = !enableDisco; + break; + case ConsoleKey.D: + if (keystroke.Modifiers == ConsoleModifiers.Control) + { + var drivers = DeviceTools.GetDrivers().Select((kvp) => new InputChoiceInfo(kvp.Key, kvp.Value)).ToArray(); + int driverIdx = InfoBoxSelectionColor.WriteInfoBoxSelection(drivers, Translate.DoTranslation("Select a driver. ESC to quit.")); + playerScreen.RequireRefresh(); + if (driverIdx < 0) + return; + var driver = drivers[driverIdx]; + string active = ""; + var devices = DeviceTools.GetDevices(driver.ChoiceName, ref active).Select((kvp) => new InputChoiceInfo(kvp.Key, kvp.Value)).ToArray(); + int deviceIdx = InfoBoxSelectionColor.WriteInfoBoxSelection(devices, Translate.DoTranslation("Select a device. Current driver is {0}. ESC to quit."), active); + playerScreen.RequireRefresh(); + if (deviceIdx < 0) + return; + var device = devices[deviceIdx]; + DeviceTools.SetActiveDriver(driver.ChoiceName); + DeviceTools.SetActiveDevice(driver.ChoiceName, device.ChoiceName); + } + else if (keystroke.Modifiers == ConsoleModifiers.Shift) + DeviceTools.Reset(); + else + ShowDeviceDriver(); + playerScreen.RequireRefresh(); + break; + case ConsoleKey.Q: + Exit(); + break; + } + } + } +} diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Equalizer.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Equalizer.cs index fb7c0aead4..0e266b5c2b 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Equalizer.cs +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Equalizer.cs @@ -1,28 +1,7 @@ // -// Nitrocid KS Copyright (C) 2018-2024 Aptivi -// -// This file is part of Nitrocid KS -// -// Nitrocid KS is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Nitrocid KS is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY, without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -// This file was taken from BassBoom. License notes below: - -// // BassBoom Copyright (C) 2023 Aptivi // -// This file is part of Nitrocid KS +// This file is part of BassBoom // // BassBoom is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -47,9 +26,11 @@ using Terminaux.Inputs.Styles.Infobox; using Terminaux.Writer.ConsoleWriters; using Terminaux.Writer.FancyWriters; -using Terminaux.Sequences.Builder.Types; -using Terminaux.Base.Extensions; using Terminaux.Reader; +using Terminaux.Inputs; +using System.Collections.Generic; +using Terminaux.Inputs.Styles.Selection; +using Terminaux.Base.Extensions; using Nitrocid.Languages; namespace Nitrocid.Extras.BassBoom.Player @@ -64,9 +45,8 @@ internal static void OpenEqualizer(Screen screen) // First, initialize a screen part to handle drawing ScreenPart screenPart = new(); screenPart.AddDynamicText(HandleDraw); + screen.RemoveBufferedParts(); screen.AddBufferedPart("BassBoom Player - Equalizer", screenPart); - if (screen.CheckBufferedPart("BassBoom Player")) - screen.RemoveBufferedPart("BassBoom Player"); // Then, clear the screen to draw our TUI while (!exiting) @@ -96,8 +76,7 @@ internal static void OpenEqualizer(Screen screen) // Restore state exiting = false; - if (screen.CheckBufferedPart("BassBoom Player - Equalizer")) - screen.RemoveBufferedPart("BassBoom Player - Equalizer"); + screen.RemoveBufferedParts(); ColorTools.LoadBack(); } @@ -153,53 +132,50 @@ private static string HandleDraw() $" - [Q] {Translate.DoTranslation("Exit")}"; drawn.Append(CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 2, keystrokes)); - // Print the separator - string separator = new('=', ConsoleWrapper.WindowWidth); + // Print the separator and the music file info + string separator = new('═', ConsoleWrapper.WindowWidth); drawn.Append(CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 4, separator)); // Write powered by... - drawn.Append(TextWriterWhereColor.RenderWhere($"[ {Translate.DoTranslation("Powered by BassBoom")} ]", 2, ConsoleWrapper.WindowHeight - 4)); + drawn.Append(TextWriterWhereColor.RenderWhere($"╣ {Translate.DoTranslation("Powered by BassBoom")} ╠", 2, ConsoleWrapper.WindowHeight - 4)); // Write current song - if (PlayerTui.musicFiles.Count > 0) - drawn.Append(PlayerControls.RenderSongName(PlayerTui.musicFiles[PlayerTui.currentSong - 1])); + if (Common.cachedInfos.Count > 0) + { + if (Common.isRadioMode) + drawn.Append(RadioControls.RenderStationName()); + else + drawn.Append(PlayerControls.RenderSongName(Common.CurrentCachedInfo.MusicPath)); + } + else + drawn.Append( + TextWriterWhereColor.RenderWhere(ConsoleClearing.GetClearLineToRightSequence(), 0, 1) + + CenteredTextColor.RenderCentered(1, Translate.DoTranslation("Not playing. Music player is idle."), ConsoleColors.White) + ); // Now, print the list of bands and their values. - int startPos = 3; - int endPos = ConsoleWrapper.WindowHeight - 5; - int songsPerPage = endPos - startPos; - int currentPage = currentBandIdx / songsPerPage; - int startIndex = songsPerPage * currentPage; - var eqs = new StringBuilder(); - for (int i = 0; i <= songsPerPage - 1; i++) + var choices = new List(); + int startPos = 4; + int endPos = ConsoleWrapper.WindowHeight - 6; + int bandsPerPage = endPos - startPos; + for (int i = 0; i < 32; i++) { - // Populate the first pane - string finalEntry = ""; - int finalIndex = i + startIndex; - bool selected = finalIndex == currentBandIdx; - if (finalIndex <= 31) - { - // Here, it's getting uglier as we don't have ElementAt() in IEnumerable, too! - double val = EqualizerControls.GetEqualizer(finalIndex); - string eqKey = $"Equalizer Band #{finalIndex + 1}"; - string renderedVal = $"[{val:0.00}] {(selected ? "<<<" : " ")}"; - string dataObject = $" {(selected ? ">>>" : " ")} {eqKey}".Truncate(ConsoleWrapper.WindowWidth - renderedVal.Length - 5); - string spaces = new(' ', ConsoleWrapper.WindowWidth - 2 - renderedVal.Length - dataObject.Length); - finalEntry = dataObject + spaces + renderedVal; - } + // Get the equalizer value for this band + double val = EqualizerControls.GetEqualizer(i); + string eqType = + i == 0 ? Translate.DoTranslation("Bass") : + i == 1 ? Translate.DoTranslation("Upper Mid") : + i > 1 ? Translate.DoTranslation("Treble") : + Translate.DoTranslation("Unknown band type"); - // Render an entry - var finalForeColor = selected ? new Color(ConsoleColors.Green) : new Color(ConsoleColors.Silver); - int top = startPos + finalIndex - startIndex; - eqs.Append( - $"{CsiSequences.GenerateCsiCursorPosition(1, top + 1)}" + - $"{finalForeColor.VTSequenceForeground}" + - finalEntry + - new string(' ', ConsoleWrapper.WindowWidth - finalEntry.Length) + - $"{ColorTools.CurrentForegroundColor.VTSequenceForeground}" - ); + // Now, render it + string bandData = $"[{val:0.00}] {Translate.DoTranslation("Equalizer Band")} #{i + 1} - {eqType}"; + choices.Add(new($"{i + 1}", bandData)); } - drawn.Append(eqs); + drawn.Append( + BoxFrameColor.RenderBoxFrame(2, 3, ConsoleWrapper.WindowWidth - 6, bandsPerPage) + + SelectionInputTools.RenderSelections([.. choices], 3, 4, currentBandIdx, bandsPerPage, ConsoleWrapper.WindowWidth - 6, selectedForegroundColor: new Color(ConsoleColors.Green), foregroundColor: new Color(ConsoleColors.Silver)) + ); return drawn.ToString(); } } diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/EqualizerControls.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/EqualizerControls.cs index 342a6bad8c..88dcabde8d 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/EqualizerControls.cs +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/EqualizerControls.cs @@ -1,28 +1,7 @@ // -// Nitrocid KS Copyright (C) 2018-2024 Aptivi -// -// This file is part of Nitrocid KS -// -// Nitrocid KS is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Nitrocid KS is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY, without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -// This file was taken from BassBoom. License notes below: - -// // BassBoom Copyright (C) 2023 Aptivi // -// This file is part of Nitrocid KS +// This file is part of BassBoom // // BassBoom is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -38,8 +17,8 @@ // along with this program. If not, see . // +using BassBoom.Basolia.Enumerations; using BassBoom.Basolia.Playback; -using BassBoom.Native.Interop.Play; namespace Nitrocid.Extras.BassBoom.Player { @@ -53,11 +32,11 @@ internal static double GetCachedEqualizer(int band) => bands[band]; internal static double GetEqualizer(int band) => - PlaybackTools.GetEqualizer(mpg123_channels.MPG123_LR, band); + PlaybackTools.GetEqualizer(PlaybackChannels.Both, band); internal static void SetEqualizer(int band, double value) { - PlaybackTools.SetEqualizer(mpg123_channels.MPG123_LR, band, value); + PlaybackTools.SetEqualizer(PlaybackChannels.Both, band, value); UpdateEqualizers(); } diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerControls.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerControls.cs index 44a0ff15bf..4894b14003 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerControls.cs +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerControls.cs @@ -1,14 +1,14 @@ // -// Nitrocid KS Copyright (C) 2018-2024 Aptivi +// BassBoom Copyright (C) 2023 Aptivi // -// This file is part of Nitrocid KS +// This file is part of BassBoom // -// Nitrocid KS is free software: you can redistribute it and/or modify +// BassBoom is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Nitrocid KS is distributed in the hope that it will be useful, +// BassBoom is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY, without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. @@ -17,41 +17,24 @@ // along with this program. If not, see . // -// This file was taken from BassBoom. License notes below: - -// BassBoom Copyright (C) 2023 Aptivi -// -// This file is part of BassBoom -// -// BassBoom is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// BassBoom is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -using BassBoom.Basolia.File; +using BassBoom.Basolia.Enumerations; using BassBoom.Basolia.Format; -using BassBoom.Basolia.Format.Cache; using BassBoom.Basolia.Lyrics; using BassBoom.Basolia.Playback; +using Nitrocid.Extras.BassBoom.Player.Tools; +using Nitrocid.Languages; using System; using System.IO; using System.Linq; using System.Text; using System.Threading; +using Terminaux.Base.Buffered; +using Terminaux.Base.Extensions; +using Terminaux.Colors.Data; +using Terminaux.Inputs; using Terminaux.Inputs.Styles.Infobox; using Terminaux.Writer.ConsoleWriters; -using Nitrocid.ConsoleBase.Colors; -using Nitrocid.Languages; using Terminaux.Writer.FancyWriters; -using Terminaux.Base; namespace Nitrocid.Extras.BassBoom.Player { @@ -59,41 +42,25 @@ internal static class PlayerControls { internal static double seekRate = 3.0d; - internal static void RaiseVolume() - { - PlayerTui.volume += 0.05; - if (PlayerTui.volume > 1) - PlayerTui.volume = 1; - PlaybackTools.SetVolume(PlayerTui.volume); - } - - internal static void LowerVolume() - { - PlayerTui.volume -= 0.05; - if (PlayerTui.volume < 0) - PlayerTui.volume = 0; - PlaybackTools.SetVolume(PlayerTui.volume); - } - internal static void SeekForward() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; - PlayerTui.position += (int)(PlayerTui.formatInfo.rate * seekRate); - if (PlayerTui.position > PlayerTui.total) - PlayerTui.position = PlayerTui.total; + PlayerTui.position += (int)(Common.CurrentCachedInfo.FormatInfo.rate * seekRate); + if (PlayerTui.position > Common.CurrentCachedInfo.Duration) + PlayerTui.position = Common.CurrentCachedInfo.Duration; PlaybackPositioningTools.SeekToFrame(PlayerTui.position); } internal static void SeekBackward() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; - PlayerTui.position -= (int)(PlayerTui.formatInfo.rate * seekRate); + PlayerTui.position -= (int)(Common.CurrentCachedInfo.FormatInfo.rate * seekRate); if (PlayerTui.position < 0) PlayerTui.position = 0; PlaybackPositioningTools.SeekToFrame(PlayerTui.position); @@ -102,76 +69,154 @@ internal static void SeekBackward() internal static void SeekBeginning() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; PlaybackPositioningTools.SeekToTheBeginning(); PlayerTui.position = 0; } + internal static void SeekPreviousLyric() + { + // In case we have no songs in the playlist, or we have no lyrics... + if (Common.cachedInfos.Count == 0) + return; + if (Common.CurrentCachedInfo.LyricInstance is null) + return; + + var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesCurrent(); + if (lyrics.Length == 0) + return; + var lyric = lyrics.Length == 1 ? lyrics[0] : lyrics[lyrics.Length - 2]; + PlaybackPositioningTools.SeekLyric(lyric); + } + + internal static void SeekCurrentLyric() + { + // In case we have no songs in the playlist, or we have no lyrics... + if (Common.cachedInfos.Count == 0) + return; + if (Common.CurrentCachedInfo.LyricInstance is null) + return; + + var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesCurrent(); + if (lyrics.Length == 0) + return; + var lyric = lyrics[lyrics.Length - 1]; + PlaybackPositioningTools.SeekLyric(lyric); + } + + internal static void SeekNextLyric() + { + // In case we have no songs in the playlist, or we have no lyrics... + if (Common.cachedInfos.Count == 0) + return; + if (Common.CurrentCachedInfo.LyricInstance is null) + return; + + var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesUpcoming(); + if (lyrics.Length == 0) + { + SeekCurrentLyric(); + return; + } + var lyric = lyrics[0]; + PlaybackPositioningTools.SeekLyric(lyric); + } + + internal static void SeekWhichLyric() + { + // In case we have no songs in the playlist, or we have no lyrics... + if (Common.cachedInfos.Count == 0) + return; + if (Common.CurrentCachedInfo.LyricInstance is null) + return; + + var lyrics = Common.CurrentCachedInfo.LyricInstance.Lines; + var choices = lyrics.Select((line) => new InputChoiceInfo($"{line.LineSpan}", line.Line)).ToArray(); + int index = InfoBoxSelectionColor.WriteInfoBoxSelection(choices, Translate.DoTranslation("Select a lyric to seek to")); + if (index == -1) + return; + var lyric = lyrics[index]; + PlaybackPositioningTools.SeekLyric(lyric); + } + + internal static void SeekTo(TimeSpan target) + { + // In case we have no songs in the playlist... + if (Common.cachedInfos.Count == 0) + return; + + PlayerTui.position = (int)(target.TotalSeconds * Common.CurrentCachedInfo.FormatInfo.rate); + if (PlayerTui.position > Common.CurrentCachedInfo.Duration) + PlayerTui.position = 0; + PlaybackPositioningTools.SeekToFrame(PlayerTui.position); + } + internal static void Play() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; if (PlaybackTools.State == PlaybackState.Stopped) // There could be a chance that the music has fully stopped without any user interaction. PlaybackPositioningTools.SeekToTheBeginning(); - PlayerTui.advance = true; + Common.advance = true; PlayerTui.playerThread.Start(); - SpinWait.SpinUntil(() => PlaybackTools.Playing || PlayerTui.failedToPlay); - PlayerTui.failedToPlay = false; + SpinWait.SpinUntil(() => PlaybackTools.Playing || Common.failedToPlay); + Common.failedToPlay = false; } internal static void Pause() { - PlayerTui.advance = false; - PlayerTui.paused = true; + Common.advance = false; + Common.paused = true; PlaybackTools.Pause(); } internal static void Stop(bool resetCurrentSong = true) { - PlayerTui.advance = false; - PlayerTui.paused = false; + Common.advance = false; + Common.paused = false; if (resetCurrentSong) - PlayerTui.currentSong = 1; + Common.currentPos = 1; PlaybackTools.Stop(); } internal static void NextSong() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; - PlayerTui.currentSong++; - if (PlayerTui.currentSong > PlayerTui.musicFiles.Count) - PlayerTui.currentSong = 1; + Common.currentPos++; + if (Common.currentPos > Common.cachedInfos.Count) + Common.currentPos = 1; } internal static void PreviousSong() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; - PlayerTui.currentSong--; - if (PlayerTui.currentSong <= 0) - PlayerTui.currentSong = PlayerTui.musicFiles.Count; + Common.currentPos--; + if (Common.currentPos <= 0) + Common.currentPos = Common.cachedInfos.Count; } internal static void PromptForAddSong() { string path = InfoBoxInputColor.WriteInfoBoxInput(Translate.DoTranslation("Enter a path to the music file")); + ScreenTools.CurrentScreen.RequireRefresh(); if (File.Exists(path)) { int currentPos = PlayerTui.position; - PlayerTui.populate = true; + Common.populate = true; PopulateMusicFileInfo(path); - PlayerTui.populate = true; - PopulateMusicFileInfo(PlayerTui.musicFiles[PlayerTui.currentSong - 1]); + Common.populate = true; + PopulateMusicFileInfo(Common.CurrentCachedInfo.MusicPath); PlaybackPositioningTools.SeekToFrame(currentPos); } else @@ -181,19 +226,20 @@ internal static void PromptForAddSong() internal static void PromptForAddDirectory() { string path = InfoBoxInputColor.WriteInfoBoxInput(Translate.DoTranslation("Enter a path to the music library directory")); + ScreenTools.CurrentScreen.RequireRefresh(); if (Directory.Exists(path)) { int currentPos = PlayerTui.position; - var musicFiles = Directory.GetFiles(path, "*.mp3"); - if (musicFiles.Length > 0) + var cachedInfos = Directory.GetFiles(path, "*.mp3"); + if (cachedInfos.Length > 0) { - foreach (string musicFile in musicFiles) + foreach (string musicFile in cachedInfos) { - PlayerTui.populate = true; + Common.populate = true; PopulateMusicFileInfo(musicFile); } - PlayerTui.populate = true; - PopulateMusicFileInfo(PlayerTui.musicFiles[PlayerTui.currentSong - 1]); + Common.populate = true; + PopulateMusicFileInfo(Common.CurrentCachedInfo.MusicPath); PlaybackPositioningTools.SeekToFrame(currentPos); } } @@ -201,68 +247,27 @@ internal static void PromptForAddDirectory() InfoBoxColor.WriteInfoBox(Translate.DoTranslation("Music library directory is not found.")); } - internal static void Exit() - { - PlayerTui.exiting = true; - PlayerTui.advance = false; - } - - internal static bool TryOpenMusicFile(string musicPath) - { - try - { - if (FileTools.IsOpened) - FileTools.CloseFile(); - FileTools.OpenFile(musicPath); - FileTools.CloseFile(); - return true; - } - catch (Exception ex) - { - InfoBoxColor.WriteInfoBox(Translate.DoTranslation("Can't open {0}: {1}"), true, musicPath, ex.Message); - } - return false; - } - internal static void PopulateMusicFileInfo(string musicPath) { // Try to open the file after loading the library - if (PlaybackTools.Playing || !PlayerTui.populate) + if (PlaybackTools.Playing || !Common.populate) return; - PlayerTui.populate = false; - if (!TryOpenMusicFile(musicPath)) - return; - FileTools.OpenFile(musicPath); - if (PlayerTui.cachedInfos.Any((csi) => csi.MusicPath == musicPath)) - { - var instance = PlayerTui.cachedInfos.Single((csi) => csi.MusicPath == musicPath); - PlayerTui.total = instance.Duration; - PlayerTui.formatInfo = instance.FormatInfo; - PlayerTui.totalSpan = AudioInfoTools.GetDurationSpanFromSamples(PlayerTui.total, PlayerTui.formatInfo.rate); - PlayerTui.frameInfo = instance.FrameInfo; - PlayerTui.managedV1 = instance.MetadataV1; - PlayerTui.managedV2 = instance.MetadataV2; - PlayerTui.lyricInstance = instance.LyricInstance; - if (!PlayerTui.musicFiles.Contains(musicPath)) - PlayerTui.musicFiles.Add(musicPath); - } - else + Common.populate = false; + Common.Switch(musicPath); + if (!Common.cachedInfos.Any((csi) => csi.MusicPath == musicPath)) { + ScreenTools.CurrentScreen.RequireRefresh(); InfoBoxColor.WriteInfoBox(Translate.DoTranslation("Loading BassBoom to open {0}..."), false, musicPath); - PlayerTui.total = AudioInfoTools.GetDuration(true); - PlayerTui.totalSpan = AudioInfoTools.GetDurationSpanFromSamples(PlayerTui.total); - PlayerTui.formatInfo = FormatTools.GetFormatInfo(); - PlayerTui.frameInfo = AudioInfoTools.GetFrameInfo(); - AudioInfoTools.GetId3Metadata(out PlayerTui.managedV1, out PlayerTui.managedV2); + var total = AudioInfoTools.GetDuration(true); + var formatInfo = FormatTools.GetFormatInfo(); + var frameInfo = AudioInfoTools.GetFrameInfo(); + AudioInfoTools.GetId3Metadata(out var managedV1, out var managedV2); // Try to open the lyrics - OpenLyrics(musicPath); - var instance = new CachedSongInfo(musicPath, PlayerTui.managedV1, PlayerTui.managedV2, PlayerTui.total, PlayerTui.formatInfo, PlayerTui.frameInfo, PlayerTui.lyricInstance); - PlayerTui.cachedInfos.Add(instance); + var lyric = OpenLyrics(musicPath); + var instance = new CachedSongInfo(musicPath, managedV1, managedV2, total, formatInfo, frameInfo, lyric, "", false); + Common.cachedInfos.Add(instance); } - TextWriterWhereColor.WriteWhere(new string(' ', ConsoleWrapper.WindowWidth), 0, 1); - if (!PlayerTui.musicFiles.Contains(musicPath)) - PlayerTui.musicFiles.Add(musicPath); } internal static string RenderSongName(string musicPath) @@ -271,13 +276,15 @@ internal static string RenderSongName(string musicPath) var (musicName, musicArtist, _) = GetMusicNameArtistGenre(musicPath); // Print the music name - return CenteredTextColor.RenderCentered(1, Translate.DoTranslation("Now playing") + ": {0} - {1}", KernelColorTools.GetColor(KernelColorType.NeutralText), KernelColorTools.GetColor(KernelColorType.Background), musicArtist, musicName); + return + TextWriterWhereColor.RenderWhere(ConsoleClearing.GetClearLineToRightSequence(), 0, 1) + + CenteredTextColor.RenderCentered(1, Translate.DoTranslation("Now playing") + ": {0} - {1}", ConsoleColors.White, ConsoleColors.Black, musicArtist, musicName); } internal static (string musicName, string musicArtist, string musicGenre) GetMusicNameArtistGenre(string musicPath) { - var metadatav2 = PlayerTui.managedV2; - var metadatav1 = PlayerTui.managedV1; + var metadatav2 = Common.CurrentCachedInfo.MetadataV2; + var metadatav1 = Common.CurrentCachedInfo.MetadataV1; string musicName = !string.IsNullOrEmpty(metadatav2.Title) ? metadatav2.Title : !string.IsNullOrEmpty(metadatav1.Title) ? metadatav1.Title : @@ -295,7 +302,7 @@ internal static (string musicName, string musicArtist, string musicGenre) GetMus internal static (string musicName, string musicArtist, string musicGenre) GetMusicNameArtistGenre(int cachedInfoIdx) { - var cachedInfo = PlayerTui.cachedInfos[cachedInfoIdx]; + var cachedInfo = Common.cachedInfos[cachedInfoIdx]; var metadatav2 = cachedInfo.MetadataV2; var metadatav1 = cachedInfo.MetadataV1; var path = cachedInfo.MusicPath; @@ -314,127 +321,122 @@ internal static (string musicName, string musicArtist, string musicGenre) GetMus return (musicName, musicArtist, musicGenre); } - internal static void OpenLyrics(string musicPath) + internal static Lyric OpenLyrics(string musicPath) { string lyricsPath = Path.GetDirectoryName(musicPath) + "/" + Path.GetFileNameWithoutExtension(musicPath) + ".lrc"; try { InfoBoxColor.WriteInfoBox(Translate.DoTranslation("Trying to open lyrics file") + " {0}...", false, lyricsPath); if (File.Exists(lyricsPath)) - PlayerTui.lyricInstance = LyricReader.GetLyrics(lyricsPath); + return LyricReader.GetLyrics(lyricsPath); else - PlayerTui.lyricInstance = null; + return null; } catch (Exception ex) { InfoBoxColor.WriteInfoBox(Translate.DoTranslation("Can't open lyrics file") + " {0}... {1}", lyricsPath, ex.Message); } + return null; } internal static void RemoveCurrentSong() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; - PlayerTui.cachedInfos.RemoveAt(PlayerTui.currentSong - 1); - PlayerTui.musicFiles.RemoveAt(PlayerTui.currentSong - 1); - if (PlayerTui.musicFiles.Count > 0) + Common.cachedInfos.RemoveAt(Common.currentPos - 1); + if (Common.cachedInfos.Count > 0) { - PlayerTui.currentSong--; - if (PlayerTui.currentSong == 0) - PlayerTui.currentSong = 1; - PlayerTui.populate = true; - PopulateMusicFileInfo(PlayerTui.musicFiles[PlayerTui.currentSong - 1]); + Common.currentPos--; + if (Common.currentPos == 0) + Common.currentPos = 1; + Common.populate = true; + PopulateMusicFileInfo(Common.CurrentCachedInfo.MusicPath); } } internal static void RemoveAllSongs() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; - for (int i = PlayerTui.musicFiles.Count; i > 0; i--) + for (int i = Common.cachedInfos.Count; i > 0; i--) RemoveCurrentSong(); } internal static void PromptSeek() { // In case we have no songs in the playlist... - if (PlayerTui.musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) return; // Prompt the user to set the current position to the specified time string time = InfoBoxInputColor.WriteInfoBoxInput(Translate.DoTranslation("Write the target position in this format") + ": HH:MM:SS"); if (TimeSpan.TryParse(time, out TimeSpan duration)) { - PlayerTui.position = (int)(PlayerTui.cachedInfos[PlayerTui.currentSong - 1].FormatInfo.rate * duration.TotalSeconds); - if (PlayerTui.position > PlayerTui.total) - PlayerTui.position = PlayerTui.total; + PlayerTui.position = (int)(Common.CurrentCachedInfo.FormatInfo.rate * duration.TotalSeconds); + if (PlayerTui.position > Common.CurrentCachedInfo.Duration) + PlayerTui.position = Common.CurrentCachedInfo.Duration; PlaybackPositioningTools.SeekToFrame(PlayerTui.position); } } - internal static void ShowHelp() - { - InfoBoxColor.WriteInfoBox( - $$""" - -- {{Translate.DoTranslation("Available keystrokes")}} -- - - [SPACE] {{Translate.DoTranslation("Play/Pause")}} - [ESC] {{Translate.DoTranslation("Stop")}} - [Q] {{Translate.DoTranslation("Exit")}} - [UP/DOWN] {{Translate.DoTranslation("Volume control")}} - [<-/->] {{Translate.DoTranslation("Seek control")}} - [CTRL] + [<-/->] {{Translate.DoTranslation("Seek duration control")}} - [I] {{Translate.DoTranslation("Song info")}} - [A] {{Translate.DoTranslation("Add a music file")}} - [S] {{Translate.DoTranslation("Add a music directory to the playlist")}} - [B] {{Translate.DoTranslation("Previous song")}} - [N] {{Translate.DoTranslation("Next song")}} - [R] {{Translate.DoTranslation("Remove current song")}} - [CTRL] + [R] {{Translate.DoTranslation("Remove all songs")}} - [S] {{Translate.DoTranslation("Selectively seek")}} - [E] {{Translate.DoTranslation("Open equalizer")}} - """ - ); - } - internal static void ShowSongInfo() { var textsBuilder = new StringBuilder(); - foreach (var text in PlayerTui.managedV2.Texts) + var idv2 = Common.CurrentCachedInfo.MetadataV2; + var idv1 = Common.CurrentCachedInfo.MetadataV1; + foreach (var text in idv2.Texts) textsBuilder.AppendLine($"T - {text.Item1}: {text.Item2}"); - foreach (var text in PlayerTui.managedV2.Extras) + foreach (var text in idv2.Extras) textsBuilder.AppendLine($"E - {text.Item1}: {text.Item2}"); + string section1 = Translate.DoTranslation("Song info"); + string section2 = Translate.DoTranslation("Layer info"); + string section3 = Translate.DoTranslation("Native State"); + string section4 = Translate.DoTranslation("Texts and Extras"); InfoBoxColor.WriteInfoBox( $$""" - -- {{Translate.DoTranslation("Song info")}} -- - - {{Translate.DoTranslation("Artist")}}: {{(!string.IsNullOrEmpty(PlayerTui.managedV2.Artist) ? PlayerTui.managedV2.Artist : !string.IsNullOrEmpty(PlayerTui.managedV1.Artist) ? PlayerTui.managedV1.Artist : Translate.DoTranslation("Unknown"))}} - {{Translate.DoTranslation("Title")}}: {{(!string.IsNullOrEmpty(PlayerTui.managedV2.Title) ? PlayerTui.managedV2.Title : !string.IsNullOrEmpty(PlayerTui.managedV1.Title) ? PlayerTui.managedV1.Title : "")}} - {{Translate.DoTranslation("Album")}}: {{(!string.IsNullOrEmpty(PlayerTui.managedV2.Album) ? PlayerTui.managedV2.Album : !string.IsNullOrEmpty(PlayerTui.managedV1.Album) ? PlayerTui.managedV1.Album : "")}} - {{Translate.DoTranslation("Genre")}}: {{(!string.IsNullOrEmpty(PlayerTui.managedV2.Genre) ? PlayerTui.managedV2.Genre : !string.IsNullOrEmpty(PlayerTui.managedV1.Genre.ToString()) ? PlayerTui.managedV1.Genre.ToString() : "")}} - {{Translate.DoTranslation("Comment")}}: {{(!string.IsNullOrEmpty(PlayerTui.managedV2.Comment) ? PlayerTui.managedV2.Comment : !string.IsNullOrEmpty(PlayerTui.managedV1.Comment) ? PlayerTui.managedV1.Comment : "")}} - {{Translate.DoTranslation("Duration")}}: {{PlayerTui.totalSpan}} - {{Translate.DoTranslation("Lyrics")}}: {{(PlayerTui.lyricInstance is not null ? $"{PlayerTui.lyricInstance.Lines.Count} " + Translate.DoTranslation("lines") : Translate.DoTranslation("No lyrics"))}} + {{section1}} + {{new string('=', ConsoleChar.EstimateCellWidth(section1))}} + + {{Translate.DoTranslation("Artist")}}: {{(!string.IsNullOrEmpty(idv2.Artist) ? idv2.Artist : !string.IsNullOrEmpty(idv1.Artist) ? idv1.Artist : Translate.DoTranslation("Unknown"))}} + {{Translate.DoTranslation("Title")}}: {{(!string.IsNullOrEmpty(idv2.Title) ? idv2.Title : !string.IsNullOrEmpty(idv1.Title) ? idv1.Title : "")}} + {{Translate.DoTranslation("Album")}}: {{(!string.IsNullOrEmpty(idv2.Album) ? idv2.Album : !string.IsNullOrEmpty(idv1.Album) ? idv1.Album : "")}} + {{Translate.DoTranslation("Genre")}}: {{(!string.IsNullOrEmpty(idv2.Genre) ? idv2.Genre : !string.IsNullOrEmpty(idv1.Genre.ToString()) ? idv1.Genre.ToString() : "")}} + {{Translate.DoTranslation("Comment")}}: {{(!string.IsNullOrEmpty(idv2.Comment) ? idv2.Comment : !string.IsNullOrEmpty(idv1.Comment) ? idv1.Comment : "")}} + {{Translate.DoTranslation("Duration")}}: {{Common.CurrentCachedInfo.DurationSpan}} + {{Translate.DoTranslation("Lyrics")}}: {{(Common.CurrentCachedInfo.LyricInstance is not null ? $"{Common.CurrentCachedInfo.LyricInstance.Lines.Count} " + Translate.DoTranslation("lines") : Translate.DoTranslation("No lyrics"))}} + + {{section2}} + {{new string('=', ConsoleChar.EstimateCellWidth(section2))}} + + {{Translate.DoTranslation("Version")}}: {{Common.CurrentCachedInfo.FrameInfo.Version}} + {{Translate.DoTranslation("Layer")}}: {{Common.CurrentCachedInfo.FrameInfo.Layer}} + {{Translate.DoTranslation("Rate")}}: {{Common.CurrentCachedInfo.FrameInfo.Rate}} + {{Translate.DoTranslation("Mode")}}: {{Common.CurrentCachedInfo.FrameInfo.Mode}} + {{Translate.DoTranslation("Mode Ext")}}: {{Common.CurrentCachedInfo.FrameInfo.ModeExt}} + {{Translate.DoTranslation("Frame Size")}}: {{Common.CurrentCachedInfo.FrameInfo.FrameSize}} + {{Translate.DoTranslation("Flags")}}: {{Common.CurrentCachedInfo.FrameInfo.Flags}} + {{Translate.DoTranslation("Emphasis")}}: {{Common.CurrentCachedInfo.FrameInfo.Emphasis}} + {{Translate.DoTranslation("Bitrate")}}: {{Common.CurrentCachedInfo.FrameInfo.BitRate}} + {{Translate.DoTranslation("ABR Rate")}}: {{Common.CurrentCachedInfo.FrameInfo.AbrRate}} + {{Translate.DoTranslation("Variable bitrate")}}: {{Common.CurrentCachedInfo.FrameInfo.Vbr}} - -- {{Translate.DoTranslation("Layer info")}} -- - - {{Translate.DoTranslation("Version")}}: {{PlayerTui.frameInfo.Version}} - {{Translate.DoTranslation("Layer")}}: {{PlayerTui.frameInfo.Layer}} - {{Translate.DoTranslation("Rate")}}: {{PlayerTui.frameInfo.Rate}} - {{Translate.DoTranslation("Mode")}}: {{PlayerTui.frameInfo.Mode}} - {{Translate.DoTranslation("Mode Ext")}}: {{PlayerTui.frameInfo.ModeExt}} - {{Translate.DoTranslation("Frame Size")}}: {{PlayerTui.frameInfo.FrameSize}} - {{Translate.DoTranslation("Flags")}}: {{PlayerTui.frameInfo.Flags}} - {{Translate.DoTranslation("Emphasis")}}: {{PlayerTui.frameInfo.Emphasis}} - {{Translate.DoTranslation("Bitrate")}}: {{PlayerTui.frameInfo.BitRate}} - {{Translate.DoTranslation("ABR Rate")}}: {{PlayerTui.frameInfo.AbrRate}} - {{Translate.DoTranslation("Variable bitrate")}}: {{PlayerTui.frameInfo.Vbr}} - - -- {{Translate.DoTranslation("Texts and Extras")}} -- + {{section3}} + {{new string('=', ConsoleChar.EstimateCellWidth(section3))}} + + {{Translate.DoTranslation("Accurate rendering")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.Accurate)}} + {{Translate.DoTranslation("Buffer fill")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.BufferFill)}} + {{Translate.DoTranslation("Decoding delay")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.DecodeDelay)}} + {{Translate.DoTranslation("Encoding delay")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.EncodeDelay)}} + {{Translate.DoTranslation("Encoding padding")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.EncodePadding)}} + {{Translate.DoTranslation("Frankenstein stream")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.Frankenstein)}} + {{Translate.DoTranslation("Fresh decoder")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.FreshDecoder)}} + + {{section4}} + {{new string('=', ConsoleChar.EstimateCellWidth(section4))}} {{textsBuilder}} """ diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerHandler.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerHandler.cs index 531b00ae91..be14482020 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerHandler.cs +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerHandler.cs @@ -36,8 +36,8 @@ public static void Handle(string path) InfoBoxColor.WriteInfoBoxColor(Translate.DoTranslation("Can't open music file '{0}' because it's not found."), KernelColorTools.GetColor(KernelColorType.Error), path); return; } - if (!PlayerTui.musicFiles.Contains(path)) - PlayerTui.musicFiles.Add(path); + if (!PlayerTui.passedMusicPaths.Contains(path)) + PlayerTui.passedMusicPaths.Add(path); PlayerControls.PopulateMusicFileInfo(path); PlayerTui.PlayerLoop(); } diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerTui.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerTui.cs index 2c04be9c24..e85f84d51d 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerTui.cs +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/PlayerTui.cs @@ -1,14 +1,14 @@ // -// Nitrocid KS Copyright (C) 2018-2024 Aptivi +// BassBoom Copyright (C) 2023 Aptivi // -// This file is part of Nitrocid KS +// This file is part of BassBoom // -// Nitrocid KS is free software: you can redistribute it and/or modify +// BassBoom is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // -// Nitrocid KS is distributed in the hope that it will be useful, +// BassBoom is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY, without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. @@ -17,157 +17,90 @@ // along with this program. If not, see . // -// This file was taken from BassBoom. License notes below: - -// BassBoom Copyright (C) 2023 Aptivi -// -// This file is part of BassBoom -// -// BassBoom is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// BassBoom is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - using BassBoom.Basolia; using BassBoom.Basolia.File; -using BassBoom.Basolia.Format; -using BassBoom.Basolia.Format.Cache; -using BassBoom.Basolia.Lyrics; using BassBoom.Basolia.Playback; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; +using Terminaux.Base; +using Terminaux.Base.Buffered; using Terminaux.Colors; -using Terminaux.Sequences.Builder.Types; +using Terminaux.Colors.Data; using Terminaux.Inputs.Styles.Infobox; using Terminaux.Writer.ConsoleWriters; -using Nitrocid.ConsoleBase.Colors; -using Terminaux.Base.Buffered; -using Nitrocid.Misc.Screensaver; -using Nitrocid.Languages; using Terminaux.Writer.FancyWriters; -using Terminaux.Base; -using Terminaux.Colors.Data; -using Terminaux.Base.Extensions; using Terminaux.Reader; +using Terminaux.Inputs.Styles.Selection; +using Terminaux.Inputs; +using Nitrocid.Languages; namespace Nitrocid.Extras.BassBoom.Player { internal static class PlayerTui { internal static Thread playerThread; - internal static Lyric lyricInstance = null; - internal static FrameInfo frameInfo = null; - internal static Id3V1Metadata managedV1 = null; - internal static Id3V2Metadata managedV2 = null; - internal static TimeSpan totalSpan = new(); - internal static int total = 0; - internal static (long rate, int channels, int encoding) formatInfo = new(); - internal static int currentSong = 1; - internal static double volume = 1.0; - internal static bool exiting = false; internal static int position = 0; - internal static bool advance = false; - internal static bool populate = true; - internal static bool paused = false; - internal static bool failedToPlay = false; - internal static string cachedLyric = ""; - internal static readonly List musicFiles = []; - internal static readonly List cachedInfos = []; + internal static readonly List passedMusicPaths = []; public static void PlayerLoop() { - // Prevent screensaver lock - ScreensaverManager.PreventLock(); - - volume = PlaybackTools.GetVolume().baseLinear; - exiting = false; - paused = false; - populate = true; - advance = false; + Common.volume = PlaybackTools.GetVolume().baseLinear; // Populate the screen Screen playerScreen = new(); ScreenTools.SetCurrent(playerScreen); - // First, make a screen part to draw our TUI + // Make a screen part to draw our TUI ScreenPart screenPart = new(); - // Redraw if necessary - bool wasRerendered = true; + // Handle drawing screenPart.AddDynamicText(HandleDraw); // Current duration + int hue = 0; screenPart.AddDynamicText(() => { + if (Common.CurrentCachedInfo is null) + return ""; var buffer = new StringBuilder(); position = FileTools.IsOpened ? PlaybackPositioningTools.GetCurrentDuration() : 0; var posSpan = FileTools.IsOpened ? PlaybackPositioningTools.GetCurrentDurationSpan() : new(); - string indicator = - Translate.DoTranslation("Seek") + $": {PlayerControls.seekRate:0.00} | " + - Translate.DoTranslation("Volume") + $": {volume:0.00}"; - buffer.Append( - ProgressBarColor.RenderProgress(100 * (position / (double)total), 2, ConsoleWrapper.WindowHeight - 8, ConsoleWrapper.WindowWidth - 6, KernelColorTools.GetColor(KernelColorType.Progress), ColorTools.GetGray(), KernelColorTools.GetColor(KernelColorType.Background)) + - TextWriterWhereColor.RenderWhere($"{posSpan} / {totalSpan}", 3, ConsoleWrapper.WindowHeight - 9, KernelColorTools.GetColor(KernelColorType.NeutralText), KernelColorTools.GetColor(KernelColorType.Background)) + - TextWriterWhereColor.RenderWhere(indicator, ConsoleWrapper.WindowWidth - indicator.Length - 3, ConsoleWrapper.WindowHeight - 9, KernelColorTools.GetColor(KernelColorType.NeutralText), KernelColorTools.GetColor(KernelColorType.Background)) - ); - return buffer.ToString(); - }); - - // Get the lyrics - screenPart.AddDynamicText(() => - { - var buffer = new StringBuilder(); + var disco = PlaybackTools.Playing && Common.enableDisco ? new Color($"hsl:{hue};50;50") : BassBoomInit.white; if (PlaybackTools.Playing) { - // Print the lyrics, if any - if (lyricInstance is not null) - { - string current = lyricInstance.GetLastLineCurrent(); - if (current != cachedLyric || wasRerendered) - { - cachedLyric = current; - buffer.Append( - TextWriterWhereColor.RenderWhere(ConsoleClearing.GetClearLineToRightSequence(), 0, ConsoleWrapper.WindowHeight - 10, KernelColorTools.GetColor(KernelColorType.NeutralText), KernelColorTools.GetColor(KernelColorType.Background)) + - CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 10, lyricInstance.GetLastLineCurrent(), KernelColorTools.GetColor(KernelColorType.NeutralText), KernelColorTools.GetColor(KernelColorType.Background)) - ); - } - } - else - cachedLyric = ""; - } - else - { - cachedLyric = ""; - buffer.Append( - TextWriterWhereColor.RenderWhere(ConsoleClearing.GetClearLineToRightSequence(), 0, ConsoleWrapper.WindowHeight - 10, KernelColorTools.GetColor(KernelColorType.NeutralText), KernelColorTools.GetColor(KernelColorType.Background)) - ); + hue++; + if (hue >= 360) + hue = 0; } + string indicator = + $"╣ {Translate.DoTranslation("Seek")}: {PlayerControls.seekRate:0.00} | " + + $"{Translate.DoTranslation("Volume")}: {Common.volume:0.00} ╠"; + string lyric = Common.CurrentCachedInfo.LyricInstance is not null ? Common.CurrentCachedInfo.LyricInstance.GetLastLineCurrent() : ""; + string finalLyric = string.IsNullOrWhiteSpace(lyric) ? "..." : lyric; + buffer.Append( + ProgressBarColor.RenderProgress(100 * (position / (double)Common.CurrentCachedInfo.Duration), 2, ConsoleWrapper.WindowHeight - 8, ConsoleWrapper.WindowWidth - 6, disco, disco) + + TextWriterWhereColor.RenderWhereColor($"╣ {posSpan} / {Common.CurrentCachedInfo.DurationSpan} ╠", 4, ConsoleWrapper.WindowHeight - 8, disco) + + TextWriterWhereColor.RenderWhereColor(indicator, ConsoleWrapper.WindowWidth - indicator.Length - 4, ConsoleWrapper.WindowHeight - 8, disco) + + CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 6, Common.CurrentCachedInfo.LyricInstance is not null && PlaybackTools.Playing ? $"╣ {finalLyric} ╠" : "", disco) + ); return buffer.ToString(); }); // Render the buffer playerScreen.AddBufferedPart("BassBoom Player", screenPart); + playerScreen.ResetResize = false; // Then, the main loop - while (!exiting) + while (!Common.exiting) { Thread.Sleep(1); try { if (!playerScreen.CheckBufferedPart("BassBoom Player")) playerScreen.AddBufferedPart("BassBoom Player", screenPart); - wasRerendered = ConsoleResizeHandler.WasResized(false); ScreenTools.Render(); // Handle the keystroke @@ -210,7 +143,6 @@ public static void PlayerLoop() // Restore state ConsoleWrapper.CursorVisible = true; ColorTools.LoadBack(); - ScreensaverManager.AllowLock(); playerScreen.RemoveBufferedParts(); ScreenTools.UnsetCurrent(playerScreen); } @@ -219,12 +151,6 @@ private static void HandleKeypressIdleMode(ConsoleKeyInfo keystroke, Screen play { switch (keystroke.Key) { - case ConsoleKey.UpArrow: - PlayerControls.RaiseVolume(); - break; - case ConsoleKey.DownArrow: - PlayerControls.LowerVolume(); - break; case ConsoleKey.Spacebar: playerThread = new(HandlePlay); PlayerControls.Play(); @@ -232,18 +158,10 @@ private static void HandleKeypressIdleMode(ConsoleKeyInfo keystroke, Screen play case ConsoleKey.B: PlayerControls.SeekBeginning(); PlayerControls.PreviousSong(); - playerThread = new(HandlePlay); - PlayerControls.Play(); break; case ConsoleKey.N: PlayerControls.SeekBeginning(); PlayerControls.NextSong(); - playerThread = new(HandlePlay); - PlayerControls.Play(); - break; - case ConsoleKey.H: - PlayerControls.ShowHelp(); - playerScreen.RequireRefresh(); break; case ConsoleKey.I: PlayerControls.ShowSongInfo(); @@ -265,12 +183,16 @@ private static void HandleKeypressIdleMode(ConsoleKeyInfo keystroke, Screen play else PlayerControls.RemoveCurrentSong(); break; - case ConsoleKey.E: - Equalizer.OpenEqualizer(playerScreen); - playerScreen.RequireRefresh(); + case ConsoleKey.C: + if (Common.CurrentCachedInfo is null) + return; + if (keystroke.Modifiers == ConsoleModifiers.Shift) + PlayerControls.SeekTo(Common.CurrentCachedInfo.RepeatCheckpoint); + else + Common.CurrentCachedInfo.RepeatCheckpoint = PlaybackPositioningTools.GetCurrentDurationSpan(); break; - case ConsoleKey.Q: - PlayerControls.Exit(); + default: + Common.HandleKeypressCommon(keystroke, playerScreen, false); break; } } @@ -279,12 +201,6 @@ private static void HandleKeypressPlayMode(ConsoleKeyInfo keystroke, Screen play { switch (keystroke.Key) { - case ConsoleKey.UpArrow: - PlayerControls.RaiseVolume(); - break; - case ConsoleKey.DownArrow: - PlayerControls.LowerVolume(); - break; case ConsoleKey.RightArrow: if (keystroke.Modifiers == ConsoleModifiers.Control) PlayerControls.seekRate += 0.05d; @@ -304,6 +220,19 @@ private static void HandleKeypressPlayMode(ConsoleKeyInfo keystroke, Screen play playerThread = new(HandlePlay); PlayerControls.Play(); break; + case ConsoleKey.F: + PlayerControls.SeekPreviousLyric(); + break; + case ConsoleKey.G: + PlayerControls.SeekNextLyric(); + break; + case ConsoleKey.J: + PlayerControls.SeekCurrentLyric(); + break; + case ConsoleKey.K: + PlayerControls.SeekWhichLyric(); + playerScreen.RequireRefresh(); + break; case ConsoleKey.N: PlayerControls.Stop(false); PlayerControls.SeekBeginning(); @@ -325,10 +254,6 @@ private static void HandleKeypressPlayMode(ConsoleKeyInfo keystroke, Screen play case ConsoleKey.Escape: PlayerControls.Stop(); break; - case ConsoleKey.H: - PlayerControls.ShowHelp(); - playerScreen.RequireRefresh(); - break; case ConsoleKey.I: PlayerControls.ShowSongInfo(); playerScreen.RequireRefresh(); @@ -337,12 +262,23 @@ private static void HandleKeypressPlayMode(ConsoleKeyInfo keystroke, Screen play PlayerControls.PromptSeek(); playerScreen.RequireRefresh(); break; - case ConsoleKey.E: - Equalizer.OpenEqualizer(playerScreen); + case ConsoleKey.D: + PlayerControls.Pause(); + Common.HandleKeypressCommon(keystroke, playerScreen, false); + playerThread = new(HandlePlay); + PlayerControls.Play(); playerScreen.RequireRefresh(); break; - case ConsoleKey.Q: - PlayerControls.Exit(); + case ConsoleKey.C: + if (Common.CurrentCachedInfo is null) + return; + if (keystroke.Modifiers == ConsoleModifiers.Shift) + PlayerControls.SeekTo(Common.CurrentCachedInfo.RepeatCheckpoint); + else + Common.CurrentCachedInfo.RepeatCheckpoint = PlaybackPositioningTools.GetCurrentDurationSpan(); + break; + default: + Common.HandleKeypressCommon(keystroke, playerScreen, false); break; } } @@ -351,18 +287,18 @@ private static void HandlePlay() { try { - foreach (var musicFile in musicFiles.Skip(currentSong - 1)) + foreach (var musicFile in Common.cachedInfos.Skip(Common.currentPos - 1)) { - if (!advance || exiting) + if (!Common.advance || Common.exiting) return; else - populate = true; - currentSong = musicFiles.IndexOf(musicFile) + 1; - PlayerControls.PopulateMusicFileInfo(musicFile); - TextWriterRaw.WritePlain(PlayerControls.RenderSongName(musicFile), false); - if (paused) + Common.populate = true; + Common.currentPos = Common.cachedInfos.IndexOf(musicFile) + 1; + PlayerControls.PopulateMusicFileInfo(musicFile.MusicPath); + TextWriterRaw.WritePlain(PlayerControls.RenderSongName(musicFile.MusicPath), false); + if (Common.paused) { - paused = false; + Common.paused = false; PlaybackPositioningTools.SeekToFrame(position); } PlaybackTools.Play(); @@ -371,11 +307,7 @@ private static void HandlePlay() catch (Exception ex) { InfoBoxColor.WriteInfoBox(Translate.DoTranslation("Playback failure") + $": {ex.Message}"); - failedToPlay = true; - } - finally - { - lyricInstance = null; + Common.failedToPlay = true; } } @@ -394,59 +326,55 @@ private static string HandleDraw() drawn.Append(CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 2, keystrokes)); // Print the separator and the music file info - string separator = new('=', ConsoleWrapper.WindowWidth); + string separator = new('═', ConsoleWrapper.WindowWidth); drawn.Append(CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 4, separator)); // Write powered by... - drawn.Append(TextWriterWhereColor.RenderWhere($"[ {Translate.DoTranslation("Powered by BassBoom")} ]", 2, ConsoleWrapper.WindowHeight - 4)); + drawn.Append(TextWriterWhereColor.RenderWhere($"╣ {Translate.DoTranslation("Powered by BassBoom")} ╠", 2, ConsoleWrapper.WindowHeight - 4)); // In case we have no songs in the playlist... - if (musicFiles.Count == 0) + if (Common.cachedInfos.Count == 0) { - int height = (ConsoleWrapper.WindowHeight - 10) / 2; - drawn.Append(CenteredTextColor.RenderCentered(height, Translate.DoTranslation("Press 'A' to insert a single song to the playlist, or 'S' to insert the whole music library."))); - return drawn.ToString(); + if (passedMusicPaths.Count > 0) + { + foreach (string path in passedMusicPaths) + { + PlayerControls.PopulateMusicFileInfo(path); + Common.populate = true; + } + passedMusicPaths.Clear(); + } + else + { + int height = (ConsoleWrapper.WindowHeight - 6) / 2; + drawn.Append(CenteredTextColor.RenderCentered(height, Translate.DoTranslation("Press 'A' to insert a single song to the playlist, or 'S' to insert the whole music library."))); + return drawn.ToString(); + } } // Populate music file info, as necessary - if (populate) - PlayerControls.PopulateMusicFileInfo(musicFiles[currentSong - 1]); - drawn.Append(PlayerControls.RenderSongName(musicFiles[currentSong - 1])); + if (Common.populate) + PlayerControls.PopulateMusicFileInfo(Common.CurrentCachedInfo.MusicPath); + drawn.Append(PlayerControls.RenderSongName(Common.CurrentCachedInfo.MusicPath)); // Now, print the list of songs. - int startPos = 3; + var choices = new List(); + int startPos = 4; int endPos = ConsoleWrapper.WindowHeight - 10; int songsPerPage = endPos - startPos; - int currentPage = (currentSong - 1) / songsPerPage; - int startIndex = songsPerPage * currentPage; - var playlist = new StringBuilder(); - for (int i = 0; i <= songsPerPage - 1; i++) + int max = Common.cachedInfos.Select((_, idx) => idx).Max((idx) => $" {idx + 1}) ".Length); + for (int i = 0; i < Common.cachedInfos.Count; i++) { // Populate the first pane - string finalEntry = ""; - int finalIndex = i + startIndex; - if (finalIndex <= musicFiles.Count - 1) - { - // Here, it's getting uglier as we don't have ElementAt() in IEnumerable, too! - var (musicName, musicArtist, _) = PlayerControls.GetMusicNameArtistGenre(finalIndex); - string duration = cachedInfos[finalIndex].DurationSpan; - string renderedDuration = $"[{duration}]"; - string dataObject = $" {musicArtist} - {musicName}".Truncate(ConsoleWrapper.WindowWidth - renderedDuration.Length - 5); - string spaces = new(' ', ConsoleWrapper.WindowWidth - 4 - duration.Length - dataObject.Length); - finalEntry = dataObject + spaces + renderedDuration; - } - - // Render an entry - var finalForeColor = finalIndex == currentSong - 1 ? new Color(ConsoleColors.Green) : new Color(ConsoleColors.Silver); - int top = startPos + finalIndex - startIndex; - playlist.Append( - $"{CsiSequences.GenerateCsiCursorPosition(1, top + 1)}" + - $"{finalForeColor.VTSequenceForeground}" + - finalEntry + - new string(' ', ConsoleWrapper.WindowWidth - finalEntry.Length) - ); + var (musicName, musicArtist, _) = PlayerControls.GetMusicNameArtistGenre(i); + string duration = Common.cachedInfos[i].DurationSpan; + string songPreview = $"[{duration}] {musicArtist} - {musicName}"; + choices.Add(new($"{i + 1}", songPreview)); } - drawn.Append(playlist); + drawn.Append( + BoxFrameColor.RenderBoxFrame(2, 3, ConsoleWrapper.WindowWidth - 6, songsPerPage) + + SelectionInputTools.RenderSelections([.. choices], 3, 4, Common.currentPos - 1, songsPerPage, ConsoleWrapper.WindowWidth - 6, selectedForegroundColor: new Color(ConsoleColors.Green), foregroundColor: new Color(ConsoleColors.Silver)) + ); return drawn.ToString(); } } diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Radio.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Radio.cs new file mode 100644 index 0000000000..c65fd777f8 --- /dev/null +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Radio.cs @@ -0,0 +1,308 @@ +// +// BassBoom Copyright (C) 2023 Aptivi +// +// This file is part of BassBoom +// +// BassBoom is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// BassBoom is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using BassBoom.Basolia; +using BassBoom.Basolia.File; +using BassBoom.Basolia.Playback; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using Terminaux.Base; +using Terminaux.Base.Buffered; +using Terminaux.Colors; +using Terminaux.Colors.Data; +using Terminaux.Inputs.Styles.Infobox; +using Terminaux.Writer.ConsoleWriters; +using Terminaux.Writer.FancyWriters; +using Terminaux.Reader; +using Terminaux.Inputs.Styles.Selection; +using Terminaux.Inputs; +using Nitrocid.Languages; + +namespace Nitrocid.Extras.BassBoom.Player +{ + internal static class Radio + { + internal static Thread playerThread; + + public static void RadioLoop() + { + Common.volume = PlaybackTools.GetVolume().baseLinear; + Common.isRadioMode = true; + + // Populate the screen + Screen radioScreen = new(); + ScreenTools.SetCurrent(radioScreen); + + // Make a screen part to draw our TUI + ScreenPart screenPart = new(); + + // Handle drawing + screenPart.AddDynamicText(HandleDraw); + + // Current volume + int hue = 0; + screenPart.AddDynamicText(() => + { + if (Common.CurrentCachedInfo is null) + return ""; + var buffer = new StringBuilder(); + string indicator = $"╣ {Translate.DoTranslation("Volume")}: {Common.volume:0.00} ╠"; + var disco = PlaybackTools.Playing && Common.enableDisco ? new Color($"hsl:{hue};50;50") : BassBoomInit.white; + if (PlaybackTools.Playing) + { + hue++; + if (hue >= 360) + hue = 0; + } + buffer.Append( + BoxFrameColor.RenderBoxFrame(2, ConsoleWrapper.WindowHeight - 8, ConsoleWrapper.WindowWidth - 6, 1, disco) + + TextWriterWhereColor.RenderWhereColor(indicator, ConsoleWrapper.WindowWidth - indicator.Length - 4, ConsoleWrapper.WindowHeight - 8, disco) + ); + return buffer.ToString(); + }); + + // Render the buffer + radioScreen.AddBufferedPart("BassBoom Player", screenPart); + radioScreen.ResetResize = false; + + // Then, the main loop + while (!Common.exiting) + { + Thread.Sleep(1); + try + { + if (!radioScreen.CheckBufferedPart("BassBoom Player")) + radioScreen.AddBufferedPart("BassBoom Player", screenPart); + ScreenTools.Render(); + + // Handle the keystroke + if (ConsoleWrapper.KeyAvailable) + { + var keystroke = TermReader.ReadKey(); + if (PlaybackTools.Playing) + HandleKeypressPlayMode(keystroke, radioScreen); + else + HandleKeypressIdleMode(keystroke, radioScreen); + } + } + catch (BasoliaException bex) + { + if (PlaybackTools.Playing) + PlaybackTools.Stop(); + InfoBoxColor.WriteInfoBox(Translate.DoTranslation("There's an error with Basolia when trying to process the music file.") + "\n\n" + bex.Message); + radioScreen.RequireRefresh(); + } + catch (BasoliaOutException bex) + { + if (PlaybackTools.Playing) + PlaybackTools.Stop(); + InfoBoxColor.WriteInfoBox(Translate.DoTranslation("There's an error with Basolia output when trying to process the music file.") + "\n\n" + bex.Message); + radioScreen.RequireRefresh(); + } + catch (Exception ex) + { + if (PlaybackTools.Playing) + PlaybackTools.Stop(); + InfoBoxColor.WriteInfoBox(Translate.DoTranslation("There's an unknown error when trying to process the music file.") + "\n\n" + ex.Message); + radioScreen.RequireRefresh(); + } + } + + // Close the file if open + if (FileTools.IsOpened) + FileTools.CloseFile(); + + // Restore state + ConsoleWrapper.CursorVisible = true; + ColorTools.LoadBack(); + radioScreen.RemoveBufferedParts(); + ScreenTools.UnsetCurrent(radioScreen); + } + + private static void HandleKeypressIdleMode(ConsoleKeyInfo keystroke, Screen playerScreen) + { + switch (keystroke.Key) + { + case ConsoleKey.Spacebar: + playerThread = new(HandlePlay); + RadioControls.Play(); + break; + case ConsoleKey.B: + RadioControls.PreviousStation(); + break; + case ConsoleKey.N: + RadioControls.NextStation(); + break; + case ConsoleKey.I: + if (keystroke.Modifiers == ConsoleModifiers.Control) + RadioControls.ShowExtendedStationInfo(); + else + RadioControls.ShowStationInfo(); + playerScreen.RequireRefresh(); + break; + case ConsoleKey.A: + RadioControls.PromptForAddStation(); + playerScreen.RequireRefresh(); + break; + case ConsoleKey.R: + RadioControls.Stop(false); + if (keystroke.Modifiers == ConsoleModifiers.Control) + RadioControls.RemoveAllStations(); + else + RadioControls.RemoveCurrentStation(); + break; + default: + Common.HandleKeypressCommon(keystroke, playerScreen, true); + break; + } + } + + private static void HandleKeypressPlayMode(ConsoleKeyInfo keystroke, Screen playerScreen) + { + switch (keystroke.Key) + { + case ConsoleKey.B: + RadioControls.Stop(false); + RadioControls.PreviousStation(); + playerThread = new(HandlePlay); + RadioControls.Play(); + break; + case ConsoleKey.N: + RadioControls.Stop(false); + RadioControls.NextStation(); + playerThread = new(HandlePlay); + RadioControls.Play(); + break; + case ConsoleKey.Spacebar: + RadioControls.Pause(); + break; + case ConsoleKey.R: + RadioControls.Stop(false); + if (keystroke.Modifiers == ConsoleModifiers.Control) + RadioControls.RemoveAllStations(); + else + RadioControls.RemoveCurrentStation(); + break; + case ConsoleKey.Escape: + RadioControls.Stop(); + break; + case ConsoleKey.I: + if (keystroke.Modifiers == ConsoleModifiers.Control) + RadioControls.ShowExtendedStationInfo(); + else + RadioControls.ShowStationInfo(); + playerScreen.RequireRefresh(); + break; + case ConsoleKey.D: + RadioControls.Pause(); + Common.HandleKeypressCommon(keystroke, playerScreen, true); + playerThread = new(HandlePlay); + RadioControls.Play(); + playerScreen.RequireRefresh(); + break; + default: + Common.HandleKeypressCommon(keystroke, playerScreen, true); + break; + } + } + + private static void HandlePlay() + { + try + { + foreach (var musicFile in Common.cachedInfos.Skip(Common.currentPos - 1)) + { + if (!Common.advance || Common.exiting) + return; + else + Common.populate = true; + Common.currentPos = Common.cachedInfos.IndexOf(musicFile) + 1; + RadioControls.PopulateRadioStationInfo(musicFile.MusicPath); + TextWriterRaw.WritePlain(RadioControls.RenderStationName(), false); + if (Common.paused) + Common.paused = false; + PlaybackTools.Play(); + } + } + catch (Exception ex) + { + InfoBoxColor.WriteInfoBox(Translate.DoTranslation("Playback failure") + $": {ex.Message}"); + Common.failedToPlay = true; + } + } + + private static string HandleDraw() + { + // Prepare things + var drawn = new StringBuilder(); + ConsoleWrapper.CursorVisible = false; + + // First, print the keystrokes + string keystrokes = + "[SPACE] " + Translate.DoTranslation("Play/Pause") + + " - [ESC] " + Translate.DoTranslation("Stop") + + " - [Q] " + Translate.DoTranslation("Exit") + + " - [H] " + Translate.DoTranslation("Help"); + drawn.Append(CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 2, keystrokes)); + + // Print the separator and the music file info + string separator = new('═', ConsoleWrapper.WindowWidth); + drawn.Append(CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 4, separator)); + + // Write powered by... + drawn.Append(TextWriterWhereColor.RenderWhere($"╣ {Translate.DoTranslation("Powered by BassBoom")} ╠", 2, ConsoleWrapper.WindowHeight - 4)); + + // In case we have no stations in the playlist... + if (Common.cachedInfos.Count == 0) + { + int height = (ConsoleWrapper.WindowHeight - 6) / 2; + drawn.Append(CenteredTextColor.RenderCentered(height, Translate.DoTranslation("Press 'A' to insert a radio station to the playlist."))); + return drawn.ToString(); + } + + // Populate music file info, as necessary + if (Common.populate) + RadioControls.PopulateRadioStationInfo(Common.CurrentCachedInfo.MusicPath); + drawn.Append(RadioControls.RenderStationName()); + + // Now, print the list of stations. + var choices = new List(); + int startPos = 4; + int endPos = ConsoleWrapper.WindowHeight - 10; + int stationsPerPage = endPos - startPos; + int max = Common.cachedInfos.Select((_, idx) => idx).Max((idx) => $" {idx + 1}) ".Length); + for (int i = 0; i < Common.cachedInfos.Count; i++) + { + // Populate the first pane + string stationName = Common.cachedInfos[i].StationName; + string duration = Common.cachedInfos[i].DurationSpan; + string stationPreview = $"[{duration}] {stationName}"; + choices.Add(new($"{i + 1}", stationPreview)); + } + drawn.Append( + BoxFrameColor.RenderBoxFrame(2, 3, ConsoleWrapper.WindowWidth - 6, stationsPerPage) + + SelectionInputTools.RenderSelections([.. choices], 3, 4, Common.currentPos - 1, stationsPerPage, ConsoleWrapper.WindowWidth - 6, selectedForegroundColor: new Color(ConsoleColors.Green), foregroundColor: new Color(ConsoleColors.Silver)) + ); + return drawn.ToString(); + } + } +} diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/RadioControls.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/RadioControls.cs new file mode 100644 index 0000000000..1bc6dcc903 --- /dev/null +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/RadioControls.cs @@ -0,0 +1,246 @@ +// +// BassBoom Copyright (C) 2023 Aptivi +// +// This file is part of BassBoom +// +// BassBoom is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// BassBoom is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using BassBoom.Basolia.Enumerations; +using BassBoom.Basolia.File; +using BassBoom.Basolia.Format; +using BassBoom.Basolia.Playback; +using BassBoom.Basolia.Radio; +using Nitrocid.Extras.BassBoom.Player.Tools; +using Nitrocid.Languages; +using System.Linq; +using System.Text; +using System.Threading; +using Terminaux.Base.Buffered; +using Terminaux.Base.Extensions; +using Terminaux.Colors.Data; +using Terminaux.Inputs.Styles.Infobox; +using Terminaux.Writer.ConsoleWriters; +using Terminaux.Writer.FancyWriters; +using Textify.General; + +namespace Nitrocid.Extras.BassBoom.Player +{ + internal static class RadioControls + { + internal static void Play() + { + // In case we have no stations in the playlist... + if (Common.cachedInfos.Count == 0) + return; + + // There could be a chance that the music has fully stopped without any user interaction, but since we're on + // a radio station, we should seek nothing; just drop. + if (PlaybackTools.State == PlaybackState.Stopped) + PlaybackPositioningTools.Drop(); + Common.advance = true; + Radio.playerThread.Start(); + SpinWait.SpinUntil(() => PlaybackTools.Playing || Common.failedToPlay); + Common.failedToPlay = false; + } + + internal static void Pause() + { + Common.advance = false; + Common.paused = true; + PlaybackTools.Pause(); + } + + internal static void Stop(bool resetCurrentStation = true) + { + Common.advance = false; + Common.paused = false; + if (resetCurrentStation) + Common.currentPos = 1; + PlaybackTools.Stop(); + } + + internal static void NextStation() + { + // In case we have no stations in the playlist... + if (Common.cachedInfos.Count == 0) + return; + + PlaybackTools.Stop(); + Common.currentPos++; + if (Common.currentPos > Common.cachedInfos.Count) + Common.currentPos = 1; + } + + internal static void PreviousStation() + { + // In case we have no stations in the playlist... + if (Common.cachedInfos.Count == 0) + return; + + PlaybackTools.Stop(); + Common.currentPos--; + if (Common.currentPos <= 0) + Common.currentPos = Common.cachedInfos.Count; + } + + internal static void PromptForAddStation() + { + string path = InfoBoxInputColor.WriteInfoBoxInput(Translate.DoTranslation("Enter a path to the radio station. The URL to the station must provide an MPEG radio station. AAC ones are not supported yet.")); + ScreenTools.CurrentScreen.RequireRefresh(); + Common.populate = true; + PopulateRadioStationInfo(path); + Common.populate = true; + PopulateRadioStationInfo(Common.CurrentCachedInfo.MusicPath); + } + + internal static void PopulateRadioStationInfo(string musicPath) + { + // Try to open the file after loading the library + if (PlaybackTools.Playing || !Common.populate) + return; + Common.populate = false; + Common.Switch(musicPath); + if (!Common.cachedInfos.Any((csi) => csi.MusicPath == musicPath)) + { + InfoBoxColor.WriteInfoBox(Translate.DoTranslation("Loading BassBoom to open {0}..."), false, musicPath); + var formatInfo = FormatTools.GetFormatInfo(); + var frameInfo = AudioInfoTools.GetFrameInfo(); + + // Try to open the lyrics + var instance = new CachedSongInfo(musicPath, null, null, -1, formatInfo, frameInfo, null, FileTools.CurrentFile.StationName, true); + Common.cachedInfos.Add(instance); + } + } + + internal static string RenderStationName() + { + // Render the station name + string icy = PlaybackTools.RadioNowPlaying; + + // Print the music name + return + TextWriterWhereColor.RenderWhere(ConsoleClearing.GetClearLineToRightSequence(), 0, 1) + + CenteredTextColor.RenderCentered(1, Translate.DoTranslation("Now playing") + ": {0}", ConsoleColors.White, ConsoleColors.Black, icy); + } + + internal static void RemoveCurrentStation() + { + // In case we have no stations in the playlist... + if (Common.cachedInfos.Count == 0) + return; + + Common.cachedInfos.RemoveAt(Common.currentPos - 1); + if (Common.cachedInfos.Count > 0) + { + Common.currentPos--; + if (Common.currentPos == 0) + Common.currentPos = 1; + Common.populate = true; + PopulateRadioStationInfo(Common.CurrentCachedInfo.MusicPath); + } + } + + internal static void RemoveAllStations() + { + // In case we have no stations in the playlist... + if (Common.cachedInfos.Count == 0) + return; + + for (int i = Common.cachedInfos.Count; i > 0; i--) + RemoveCurrentStation(); + } + + internal static void ShowStationInfo() + { + string section1 = Translate.DoTranslation("Station info"); + string section2 = Translate.DoTranslation("Layer info"); + string section3 = Translate.DoTranslation("Native State"); + InfoBoxColor.WriteInfoBox( + $$""" + {{section1}} + {{new string('=', ConsoleChar.EstimateCellWidth(section1))}} + + {{Translate.DoTranslation("Radio station URL")}}: {{Common.CurrentCachedInfo.MusicPath}} + {{Translate.DoTranslation("Radio station name")}}: {{Common.CurrentCachedInfo.StationName}} + {{Translate.DoTranslation("Radio station current song")}}: {{PlaybackTools.RadioNowPlaying}} + + {{section2}} + {{new string('=', ConsoleChar.EstimateCellWidth(section2))}} + + {{Translate.DoTranslation("Version")}}: {{Common.CurrentCachedInfo.FrameInfo.Version}} + {{Translate.DoTranslation("Layer")}}: {{Common.CurrentCachedInfo.FrameInfo.Layer}} + {{Translate.DoTranslation("Rate")}}: {{Common.CurrentCachedInfo.FrameInfo.Rate}} + {{Translate.DoTranslation("Mode")}}: {{Common.CurrentCachedInfo.FrameInfo.Mode}} + {{Translate.DoTranslation("Mode Ext")}}: {{Common.CurrentCachedInfo.FrameInfo.ModeExt}} + {{Translate.DoTranslation("Frame Size")}}: {{Common.CurrentCachedInfo.FrameInfo.FrameSize}} + {{Translate.DoTranslation("Flags")}}: {{Common.CurrentCachedInfo.FrameInfo.Flags}} + {{Translate.DoTranslation("Emphasis")}}: {{Common.CurrentCachedInfo.FrameInfo.Emphasis}} + {{Translate.DoTranslation("Bitrate")}}: {{Common.CurrentCachedInfo.FrameInfo.BitRate}} + {{Translate.DoTranslation("ABR Rate")}}: {{Common.CurrentCachedInfo.FrameInfo.AbrRate}} + {{Translate.DoTranslation("Variable bitrate")}}: {{Common.CurrentCachedInfo.FrameInfo.Vbr}} + + {{section3}} + {{new string('=', ConsoleChar.EstimateCellWidth(section3))}} + + {{Translate.DoTranslation("Accurate rendering")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.Accurate)}} + {{Translate.DoTranslation("Buffer fill")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.BufferFill)}} + {{Translate.DoTranslation("Decoding delay")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.DecodeDelay)}} + {{Translate.DoTranslation("Encoding delay")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.EncodeDelay)}} + {{Translate.DoTranslation("Encoding padding")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.EncodePadding)}} + {{Translate.DoTranslation("Frankenstein stream")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.Frankenstein)}} + {{Translate.DoTranslation("Fresh decoder")}}: {{PlaybackTools.GetNativeState(PlaybackStateType.FreshDecoder)}} + """ + ); + } + + internal static void ShowExtendedStationInfo() + { + var station = RadioTools.GetRadioInfo(Common.CurrentCachedInfo.MusicPath); + var streamBuilder = new StringBuilder(); + foreach (var stream in station.Streams) + { + streamBuilder.AppendLine($"{Translate.DoTranslation("Name")}: {stream.StreamTitle}"); + streamBuilder.AppendLine($"{Translate.DoTranslation("Home page")}: {stream.StreamHomepage}"); + streamBuilder.AppendLine($"{Translate.DoTranslation("Genre")}: {stream.StreamGenre}"); + streamBuilder.AppendLine($"{Translate.DoTranslation("Now playing")}: {stream.SongTitle}"); + streamBuilder.AppendLine($"{Translate.DoTranslation("Stream path")}: {stream.StreamPath}"); + streamBuilder.AppendLine(Translate.DoTranslation("Listeners: {0} with {1} at peak").FormatString(stream.CurrentListeners, stream.PeakListeners)); + streamBuilder.AppendLine($"{Translate.DoTranslation("Bit rate")}: {stream.BitRate} kbps"); + streamBuilder.AppendLine($"{Translate.DoTranslation("Media type")}: {stream.MimeInfo}"); + streamBuilder.AppendLine("==============================="); + } + string section1 = Translate.DoTranslation("Radio server info"); + string section2 = Translate.DoTranslation("Stream info"); + InfoBoxColor.WriteInfoBox( + $$""" + {{section1}} + {{new string('=', ConsoleChar.EstimateCellWidth(section1))}} + + {{Translate.DoTranslation("Radio station URL")}}: {{station.ServerHostFull}} + {{Translate.DoTranslation("Radio station uses HTTPS")}}: {{station.ServerHttps}} + {{Translate.DoTranslation("Radio station server type")}}: {{station.ServerType}} + {{Translate.DoTranslation("Radio station streams: {0} with {1} active").FormatString(station.TotalStreams, station.ActiveStreams)}} + {{Translate.DoTranslation("Radio station listeners: {0} with {1} at peak").FormatString(station.CurrentListeners, station.PeakListeners)}} + + {{section2}} + {{new string('=', ConsoleChar.EstimateCellWidth(section2))}} + + =============================== + {{streamBuilder}} + """ + ); + } + } +} diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Tools/CachedSongInfo.cs b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Tools/CachedSongInfo.cs new file mode 100644 index 0000000000..f50cdfc209 --- /dev/null +++ b/public/Nitrocid.Addons/Nitrocid.Extras.BassBoom/Player/Tools/CachedSongInfo.cs @@ -0,0 +1,102 @@ +// +// BassBoom Copyright (C) 2023 Aptivi +// +// This file is part of BassBoom +// +// BassBoom is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// BassBoom is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using BassBoom.Basolia.Format; +using BassBoom.Basolia.Lyrics; +using System; + +namespace Nitrocid.Extras.BassBoom.Player.Tools +{ + /// + /// Cached song info + /// + internal class CachedSongInfo + { + /// + /// A full path to the music file + /// + public string MusicPath { get; private set; } + /// + /// ID3v1 metadata + /// + public Id3V1Metadata MetadataV1 { get; private set; } + /// + /// ID3v2 metadata + /// + public Id3V2Metadata MetadataV2 { get; private set; } + /// + /// Radio station name + /// + public string StationName { get; private set; } + /// + /// Music duration in samples + /// + public int Duration { get; private set; } + /// + /// Music duration in a string representation of the time span + /// + public string DurationSpan => + AudioInfoTools.GetDurationSpanFromSamples(Duration, FormatInfo.rate).ToString(); + /// + /// Format information (rate, channels, and encoding) + /// + public (long rate, int channels, int encoding) FormatInfo { get; private set; } + /// + /// MPEG frame info + /// + public FrameInfo FrameInfo { get; private set; } + /// + /// An instance of the music lyrics (if any) + /// + public Lyric LyricInstance { get; private set; } + /// + /// Checks to see if this cached song info instance is a radio station or not + /// + public bool IsRadio { get; private set; } + /// + /// Repeat checkpoint (not for radio stations) + /// + public TimeSpan RepeatCheckpoint { get; internal set; } = new(); + + /// + /// A cached song information + /// + /// A full path to the music file + /// ID3v1 metadata + /// ID3v2 metadata + /// Music duration in samples + /// Format information (rate, channels, and encoding) + /// MPEG frame info + /// An instance of the music lyrics (if any) + /// Radio station name + /// Is this cached song info instance is a radio station or not? + public CachedSongInfo(string musicPath, Id3V1Metadata metadataV1, Id3V2Metadata metadataV2, int duration, (long rate, int channels, int encoding) formatInfo, FrameInfo frameInfo, Lyric lyricInstance, string stationName, bool isRadioStation) + { + MusicPath = musicPath; + MetadataV1 = metadataV1; + MetadataV2 = metadataV2; + Duration = duration; + FormatInfo = formatInfo; + FrameInfo = frameInfo; + LyricInstance = lyricInstance; + StationName = stationName; + IsRadio = isRadioStation; + } + } +} diff --git a/public/Nitrocid.Analyzers/Nitrocid.StandaloneAnalyzer/Nitrocid.StandaloneAnalyzer.csproj b/public/Nitrocid.Analyzers/Nitrocid.StandaloneAnalyzer/Nitrocid.StandaloneAnalyzer.csproj index a5dd448a96..0e462fe375 100644 --- a/public/Nitrocid.Analyzers/Nitrocid.StandaloneAnalyzer/Nitrocid.StandaloneAnalyzer.csproj +++ b/public/Nitrocid.Analyzers/Nitrocid.StandaloneAnalyzer/Nitrocid.StandaloneAnalyzer.csproj @@ -34,7 +34,7 @@ - + diff --git a/public/Nitrocid.LocaleGen/Nitrocid.LocaleGen.csproj b/public/Nitrocid.LocaleGen/Nitrocid.LocaleGen.csproj index c23fa28ab6..7babf624cc 100644 --- a/public/Nitrocid.LocaleGen/Nitrocid.LocaleGen.csproj +++ b/public/Nitrocid.LocaleGen/Nitrocid.LocaleGen.csproj @@ -149,7 +149,7 @@ - + diff --git a/public/Nitrocid/Nitrocid.csproj b/public/Nitrocid/Nitrocid.csproj index ba0cec8cf9..0feeec0aad 100644 --- a/public/Nitrocid/Nitrocid.csproj +++ b/public/Nitrocid/Nitrocid.csproj @@ -51,8 +51,8 @@ - - + + global,TextifyDep