From 1d0cf746ff9a6e6afb07f36d69f4d80fbcb57d7c Mon Sep 17 00:00:00 2001 From: Aptivi Date: Mon, 16 Sep 2024 13:49:59 +0300 Subject: [PATCH] add - brk|doc - Added Basolia instance for multi p... ...layback --- If you want to be able to play more than one file, this is the right time! --- Type: add Breaking: True Doc Required: True Backport Required: False Part: 1/1 --- BassBoom.Basolia/BasoliaMedia.cs | 80 +++++++++ BassBoom.Basolia/Devices/DeviceTools.cs | 35 ++-- BassBoom.Basolia/File/FileTools.cs | 29 +-- BassBoom.Basolia/Format/AudioInfoTools.cs | 84 ++++++--- BassBoom.Basolia/Format/DecodeTools.cs | 39 ++-- BassBoom.Basolia/Format/FormatTools.cs | 12 +- BassBoom.Basolia/Lyrics/Lyric.cs | 20 ++- .../Playback/PlaybackPositioningTools.cs | 68 ++++--- BassBoom.Basolia/Playback/PlaybackTools.cs | 170 +++++++++++------- BassBoom.Basolia/Radio/RadioTools.cs | 3 +- BassBoom.Cli/BassBoomCli.cs | 3 +- BassBoom.Cli/CliBase/Common.cs | 28 +-- BassBoom.Cli/CliBase/EqualizerControls.cs | 6 +- BassBoom.Cli/CliBase/Player.cs | 38 ++-- BassBoom.Cli/CliBase/PlayerControls.cs | 62 +++---- BassBoom.Cli/CliBase/Radio.cs | 24 +-- BassBoom.Cli/CliBase/RadioControls.cs | 38 ++-- BassBoom.Native/MpgNative.cs | 60 ++----- 18 files changed, 497 insertions(+), 302 deletions(-) create mode 100644 BassBoom.Basolia/BasoliaMedia.cs diff --git a/BassBoom.Basolia/BasoliaMedia.cs b/BassBoom.Basolia/BasoliaMedia.cs new file mode 100644 index 0000000..9686772 --- /dev/null +++ b/BassBoom.Basolia/BasoliaMedia.cs @@ -0,0 +1,80 @@ +// +// 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.Playback; +using BassBoom.Native; +using BassBoom.Native.Exceptions; +using BassBoom.Native.Interop.Init; +using BassBoom.Native.Interop.Output; +using System; +using System.Diagnostics; + +namespace BassBoom.Basolia +{ + /// + /// Basolia instance for media manipulation + /// + public unsafe class BasoliaMedia + { + internal bool bufferPlaying = false; + internal bool holding = false; + internal string radioIcy = ""; + internal PlaybackState state = PlaybackState.Stopped; + + internal mpg123_handle* _mpg123Handle; + internal out123_handle* _out123Handle; + + /// + /// Makes a new Basolia instance and initializes the library, if necessary. + /// + /// Root directory that contains native library files + /// + public BasoliaMedia(string root = "") + { + if (!InitBasolia.BasoliaInitialized) + InitBasolia.Init(root); + + // Verify that we've actually loaded the library! + try + { + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInit.mpg123_new)); + var handle = @delegate.Invoke(null, null); + Debug.WriteLine($"Verifying mpg123 version: {MpgNative.MpgLibVersion}"); + _mpg123Handle = handle; + } + catch (Exception ex) + { + throw new BasoliaNativeLibraryException($"mpg123 library path {MpgNative.mpg123LibPath} doesn't contain a valid mpg123 library. mpg123_new() was called. {ex.Message}"); + } + + // Do the same for the out123 library! + try + { + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_new)); + var handle = @delegate.Invoke(); + Debug.WriteLine($"Verifying out123 version: {MpgNative.OutLibVersion}"); + _out123Handle = handle; + } + catch (Exception ex) + { + throw new BasoliaNativeLibraryException($"out123 library path {MpgNative.out123LibPath} doesn't contain a valid out123 library. out123_new() was called. {ex.Message}"); + } + } + } +} diff --git a/BassBoom.Basolia/Devices/DeviceTools.cs b/BassBoom.Basolia/Devices/DeviceTools.cs index f96e935..1309f1e 100644 --- a/BassBoom.Basolia/Devices/DeviceTools.cs +++ b/BassBoom.Basolia/Devices/DeviceTools.cs @@ -40,11 +40,14 @@ public static class DeviceTools /// /// Gets a read only dictionary that lists all the drivers /// + /// Basolia instance that contains a valid handle /// A dictionary containing the driver names and their descriptions /// - public static ReadOnlyDictionary GetDrivers() + public static ReadOnlyDictionary GetDrivers(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); Dictionary drivers = []; // We're now entering the dangerous zone @@ -54,7 +57,7 @@ public static ReadOnlyDictionary GetDrivers() unsafe { // Query the drivers - var handle = MpgNative._out123Handle; + var handle = basolia._out123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_drivers)); int driversStatus = @delegate.Invoke(handle, ref names, ref descr); if (driversStatus == (int)mpg123_errors.MPG123_ERR) @@ -79,13 +82,16 @@ public static ReadOnlyDictionary GetDrivers() /// /// Gets a read only dictionary that lists all the devices detected by the driver /// + /// Basolia instance that contains a valid handle /// A specific driver to use /// An output for the active device name /// A dictionary containing the device names and their descriptions /// - public static ReadOnlyDictionary GetDevices(string driver, ref string activeDevice) + public static ReadOnlyDictionary GetDevices(BasoliaMedia? basolia, string driver, ref string activeDevice) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); Dictionary devices = []; // We're now entering the dangerous zone @@ -95,7 +101,7 @@ public static ReadOnlyDictionary GetDevices(string driver, ref s unsafe { // Query the devices - var handle = MpgNative._out123Handle; + var handle = basolia._out123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_devices)); int devicesStatus = @delegate.Invoke(handle, driver, out names, out descr, ref active); if (devicesStatus == (int)mpg123_errors.MPG123_ERR) @@ -119,17 +125,20 @@ public static ReadOnlyDictionary GetDevices(string driver, ref s /// /// Gets the current device and driver /// + /// Basolia instance that contains a valid handle /// Current device and driver /// - public static (string driver, string device) GetCurrent() + public static (string driver, string device) GetCurrent(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // We're now entering the dangerous zone unsafe { // Query the devices - var handle = MpgNative._out123Handle; + var handle = basolia._out123Handle; IntPtr driverPtr = IntPtr.Zero; IntPtr devicePtr = IntPtr.Zero; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_driver_info)); @@ -156,11 +165,14 @@ public static (string? driver, string? device) GetCurrentCached() /// /// Sets the active driver /// + /// Basolia instance that contains a valid handle /// Driver to use /// - public static void SetActiveDriver(string driver) + public static void SetActiveDriver(BasoliaMedia? basolia, string driver) { - var driverList = GetDrivers(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); + var driverList = GetDrivers(basolia); if (!driverList.ContainsKey(driver)) throw new BasoliaException($"Driver {driver} doesn't exist", mpg123_errors.MPG123_ERR); activeDriver = driver; @@ -169,13 +181,16 @@ public static void SetActiveDriver(string driver) /// /// Sets the active device /// + /// Basolia instance that contains a valid handle /// Driver to use /// Device to use /// - public static void SetActiveDevice(string driver, string device) + public static void SetActiveDevice(BasoliaMedia? basolia, string driver, string device) { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); activeDevice = ""; - var deviceList = GetDevices(driver, ref activeDevice); + var deviceList = GetDevices(basolia, driver, ref activeDevice); if (string.IsNullOrEmpty(device)) return; if (!deviceList.ContainsKey(device)) diff --git a/BassBoom.Basolia/File/FileTools.cs b/BassBoom.Basolia/File/FileTools.cs index ce05be0..d0a4bbb 100644 --- a/BassBoom.Basolia/File/FileTools.cs +++ b/BassBoom.Basolia/File/FileTools.cs @@ -75,9 +75,11 @@ public static class FileTools /// /// Opens a media file /// - public static void OpenFile(string path) + public static void OpenFile(BasoliaMedia? basolia, string path) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (IsOpened) @@ -95,7 +97,7 @@ public static void OpenFile(string path) unsafe { // Open the file - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInput.mpg123_open)); int openStatus = @delegate.Invoke(handle, path); if (openStatus == (int)mpg123_errors.MPG123_ERR) @@ -108,15 +110,17 @@ public static void OpenFile(string path) /// /// Opens a remote radio station /// - public static void OpenUrl(string path) => - Task.Run(() => OpenUrlAsync(path)).Wait(); + public static void OpenUrl(BasoliaMedia? basolia, string path) => + Task.Run(() => OpenUrlAsync(basolia, path)).Wait(); /// /// Opens a remote radio station /// - public static async Task OpenUrlAsync(string path) + public static async Task OpenUrlAsync(BasoliaMedia? basolia, string path) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (IsOpened) @@ -146,7 +150,7 @@ public static async Task OpenUrlAsync(string path) unsafe { // Open the radio station - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInput.mpg123_open_feed)); int openStatus = @delegate.Invoke(handle); if (openStatus == (int)mpg123_errors.MPG123_ERR) @@ -157,29 +161,32 @@ public static async Task OpenUrlAsync(string path) currentFile = new(true, path, await reply.Content.ReadAsStreamAsync().ConfigureAwait(false), reply.Headers, reply.Headers.GetValues("icy-name").First()); // If necessary, feed. - PlaybackTools.FeedRadio(); + PlaybackTools.FeedRadio(basolia); } /// /// Closes a currently opened media file /// - public static void CloseFile() + public static void CloseFile(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!IsOpened) throw new BasoliaException("Can't close a file or a radio station that's already closed", mpg123_errors.MPG123_BAD_FILE); // First, stop the playing song - if (PlaybackTools.State == PlaybackState.Playing || PlaybackTools.State == PlaybackState.Paused) - PlaybackTools.Stop(); + var state = PlaybackTools.GetState(basolia); + if (state == PlaybackState.Playing || state == PlaybackState.Paused) + PlaybackTools.Stop(basolia); // We're now entering the dangerous zone unsafe { // Close the file - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInput.mpg123_close)); int closeStatus = @delegate.Invoke(handle); if (closeStatus == (int)mpg123_errors.MPG123_ERR) diff --git a/BassBoom.Basolia/Format/AudioInfoTools.cs b/BassBoom.Basolia/Format/AudioInfoTools.cs index d107b44..78344d9 100644 --- a/BassBoom.Basolia/Format/AudioInfoTools.cs +++ b/BassBoom.Basolia/Format/AudioInfoTools.cs @@ -43,19 +43,22 @@ public static class AudioInfoTools /// /// Gets the duration of the file in samples /// + /// Basolia instance that contains a valid handle /// Whether to scan the whole music file or not (seeks to the beginning of the music; don't use during playback. /// Number of samples detected by MPG123. If you want to get seconds, use 's rate result to divide the samples by it. - public static int GetDuration(bool scan) + public static int GetDuration(BasoliaMedia? basolia, bool scan) { int length; InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) throw new BasoliaException("Can't query a file that's not open", mpg123_errors.MPG123_BAD_FILE); // Check to see if we're playing - if (PlaybackTools.Playing) + if (PlaybackTools.IsPlaying(basolia)) throw new BasoliaException("Trying to get the duration during playback causes playback corruption! Don't call this function during playback.", mpg123_errors.MPG123_ERR_READER); // Always zero for radio stations @@ -65,7 +68,7 @@ public static int GetDuration(bool scan) // We're now entering the dangerous zone unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; if (scan) { lock (PlaybackPositioningTools.PositionLock) @@ -92,16 +95,20 @@ public static int GetDuration(bool scan) /// /// Gets the duration of the file in the time span /// + /// Basolia instance that contains a valid handle /// Whether to scan the whole music file or not (seeks to the beginning of the music; don't use during playback. /// A instance containing the duration in human-readable format - public static TimeSpan GetDurationSpan(bool scan) + public static TimeSpan GetDurationSpan(BasoliaMedia? basolia, bool scan) { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); + // First, get the format information - var formatInfo = FormatTools.GetFormatInfo(); + var formatInfo = FormatTools.GetFormatInfo(basolia); // Get the required values long rate = formatInfo.rate; - int durationSamples = GetDuration(scan); + int durationSamples = GetDuration(basolia, scan); long seconds = durationSamples / rate; return TimeSpan.FromSeconds(seconds); } @@ -109,12 +116,16 @@ public static TimeSpan GetDurationSpan(bool scan) /// /// Gets the duration from the number of samples /// + /// Basolia instance that contains a valid handle /// Number of samples /// A instance containing the duration in human-readable format - public static TimeSpan GetDurationSpanFromSamples(int samples) + public static TimeSpan GetDurationSpanFromSamples(BasoliaMedia? basolia, int samples) { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); + // First, get the format information - var (rate, _, _) = FormatTools.GetFormatInfo(); + var (rate, _, _) = FormatTools.GetFormatInfo(basolia); return GetDurationSpanFromSamples(samples, rate); } @@ -134,13 +145,16 @@ public static TimeSpan GetDurationSpanFromSamples(int samples, long rate) /// /// Gets the frame size from the currently open music file /// + /// Basolia instance that contains a valid handle /// The MPEG frame size /// /// - public static int GetFrameSize() + public static int GetFrameSize(BasoliaMedia? basolia) { int frameSize; InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -148,7 +162,7 @@ public static int GetFrameSize() unsafe { - var outHandle = MpgNative._out123Handle; + var outHandle = basolia._out123Handle; // Get the output format to get the frame size var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_getformat)); @@ -163,12 +177,15 @@ public static int GetFrameSize() /// /// Gets the frame length /// + /// Basolia instance that contains a valid handle /// Frame length in samples /// - public static int GetFrameLength() + public static int GetFrameLength(BasoliaMedia? basolia) { int getStatus; InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -176,7 +193,7 @@ public static int GetFrameLength() unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // Get the frame length var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_framelength)); @@ -191,12 +208,15 @@ public static int GetFrameLength() /// /// Gets the number of samples per frame /// + /// Basolia instance that contains a valid handle /// Number of samples per frame /// - public static int GetSamplesPerFrame() + public static int GetSamplesPerFrame(BasoliaMedia? basolia) { int getStatus; InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -204,7 +224,7 @@ public static int GetSamplesPerFrame() unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // Get the samples per frame var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_spf)); @@ -219,12 +239,15 @@ public static int GetSamplesPerFrame() /// /// Gets the buffer size from the currently open music file. /// + /// Basolia instance that contains a valid handle /// Buffer size /// - public static int GetBufferSize() + public static int GetBufferSize(BasoliaMedia? basolia) { int bufferSize; InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -232,7 +255,7 @@ public static int GetBufferSize() unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // Now, buffer the entire music file and create an empty array based on its size var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeLowIo.mpg123_outblock)); @@ -245,26 +268,29 @@ public static int GetBufferSize() /// /// Gets the ID3 metadata (v2 and v1) /// + /// Basolia instance that contains a valid handle /// An output to the managed instance of the ID3 metadata version 1 /// An output to the managed instance of the ID3 metadata version 2 /// - public static void GetId3Metadata(out Id3V1Metadata managedV1, out Id3V2Metadata managedV2) + public static void GetId3Metadata(BasoliaMedia? basolia, out Id3V1Metadata managedV1, out Id3V2Metadata managedV2) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) throw new BasoliaException("Can't query a file that's not open", mpg123_errors.MPG123_BAD_FILE); // Check to see if we're playing - if (PlaybackTools.Playing) + if (PlaybackTools.IsPlaying(basolia)) throw new BasoliaException("Trying to get the ID3 metadata during playback causes playback corruption! Don't call this function during playback.", mpg123_errors.MPG123_ERR_READER); IntPtr v1 = IntPtr.Zero; IntPtr v2 = IntPtr.Zero; unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // We need to scan the file to get accurate info if (!FileTools.IsRadioStation) @@ -402,24 +428,27 @@ public static void GetId3Metadata(out Id3V1Metadata managedV1, out Id3V2Metadata /// /// Gets the ICY metadata /// + /// Basolia instance that contains a valid handle /// A string containing ICY metadata /// - public static string GetIcyMetadata() + public static string GetIcyMetadata(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) throw new BasoliaException("Can't query a file that's not open", mpg123_errors.MPG123_BAD_FILE); // Check to see if we're playing - if (PlaybackTools.Playing) + if (PlaybackTools.IsPlaying(basolia)) throw new BasoliaException("Trying to get the ICY metadata during playback causes playback corruption! Don't call this function during playback.", mpg123_errors.MPG123_ERR_READER); string icy = ""; unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // We need to scan the file to get accurate info if (!FileTools.IsRadioStation) @@ -442,18 +471,21 @@ public static string GetIcyMetadata() /// /// Gets the frame information /// + /// Basolia instance that contains a valid handle /// An instance of containing MPEG frame information about the music file /// - public static FrameInfo GetFrameInfo() + public static FrameInfo GetFrameInfo(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) throw new BasoliaException("Can't query a file that's not open", mpg123_errors.MPG123_BAD_FILE); // Check to see if we're playing - if (PlaybackTools.Playing) + if (PlaybackTools.IsPlaying(basolia)) throw new BasoliaException("Trying to get the frame information during playback causes playback corruption! Don't call this function during playback.", mpg123_errors.MPG123_ERR_READER); // Some variables @@ -475,7 +507,7 @@ public static FrameInfo GetFrameInfo() mpg123_frameinfo_win frameInfo = default; unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // We need to scan the file to get accurate info, but it only works with files if (!FileTools.IsRadioStation) @@ -511,7 +543,7 @@ public static FrameInfo GetFrameInfo() mpg123_frameinfo frameInfo = default; unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // We need to scan the file to get accurate info if (!FileTools.IsRadioStation) diff --git a/BassBoom.Basolia/Format/DecodeTools.cs b/BassBoom.Basolia/Format/DecodeTools.cs index c1dbe56..5b8a5eb 100644 --- a/BassBoom.Basolia/Format/DecodeTools.cs +++ b/BassBoom.Basolia/Format/DecodeTools.cs @@ -34,25 +34,19 @@ namespace BassBoom.Basolia.Format /// public static class DecodeTools { - /// - /// Decoder to use - /// - public static string Decoder - { - get => GetCurrentDecoder(); - set => SetCurrentDecoder(value); - } - /// /// Decodes next MPEG frame to internal buffer or reads a frame and returns after setting a new format. /// + /// Basolia instance that contains a valid handle /// Frame offset /// Array of decoded audio bytes /// Number of bytes to read /// MPG123_OK on success. - public static int DecodeFrame(ref int num, ref byte[]? audio, ref int bytes) + public static int DecodeFrame(BasoliaMedia? basolia, ref int num, ref byte[]? audio, ref int bytes) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -61,7 +55,7 @@ public static int DecodeFrame(ref int num, ref byte[]? audio, ref int bytes) // We're now entering the dangerous zone unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // Get the frame IntPtr numPtr, bytesPtr, audioPtr = IntPtr.Zero; @@ -104,23 +98,38 @@ public static string[] GetDecoders(bool onlySupported) } } - private static string GetCurrentDecoder() + /// + /// Gets the current decoder + /// + /// Basolia instance that contains a valid handle + /// + public static string GetCurrentDecoder(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Try to set the equalizer value unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeDecoder.mpg123_current_decoder)); IntPtr decoderPtr = @delegate.Invoke(handle); return Marshal.PtrToStringAnsi(decoderPtr); } } - private static void SetCurrentDecoder(string decoderName) + /// + /// Sets the current decoder + /// + /// Basolia instance that contains a valid handle + /// Decoder name + /// + public static void SetCurrentDecoder(BasoliaMedia? basolia, string decoderName) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Try to set the equalizer value unsafe @@ -131,7 +140,7 @@ private static void SetCurrentDecoder(string decoderName) string[] supportedDecoders = GetDecoders(true); if (!supportedDecoders.Contains(decoderName)) throw new BasoliaException($"Decoder {decoderName} not supported by your device", mpg123_errors.MPG123_BAD_DECODER); - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeDecoder.mpg123_decoder)); int status = @delegate.Invoke(handle, decoderName); if (status != (int)mpg123_errors.MPG123_OK) diff --git a/BassBoom.Basolia/Format/FormatTools.cs b/BassBoom.Basolia/Format/FormatTools.cs index e460768..b905d63 100644 --- a/BassBoom.Basolia/Format/FormatTools.cs +++ b/BassBoom.Basolia/Format/FormatTools.cs @@ -38,15 +38,17 @@ public static class FormatTools /// /// Gets the format information /// - public static (long rate, int channels, int encoding) GetFormatInfo() + public static (long rate, int channels, int encoding) GetFormatInfo(BasoliaMedia? basolia) { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); long fileRate; int fileChannel, fileEncoding; // We're now entering the dangerous zone unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // Get the rate, the number of channels, and encoding var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeOutput.mpg123_getformat)); @@ -62,8 +64,10 @@ public static (long rate, int channels, int encoding) GetFormatInfo() /// /// Gets the supported formats /// - public static FormatInfo[] GetFormats() + public static FormatInfo[] GetFormats(BasoliaMedia? basolia) { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); var formats = new List(); // We're now entering the dangerous zone @@ -71,7 +75,7 @@ public static FormatInfo[] GetFormats() nint fmtlist = IntPtr.Zero; unsafe { - var outHandle = MpgNative._out123Handle; + var outHandle = basolia._out123Handle; // Get the list of supported formats var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_formats)); diff --git a/BassBoom.Basolia/Lyrics/Lyric.cs b/BassBoom.Basolia/Lyrics/Lyric.cs index 7ac0d86..feba030 100644 --- a/BassBoom.Basolia/Lyrics/Lyric.cs +++ b/BassBoom.Basolia/Lyrics/Lyric.cs @@ -37,30 +37,33 @@ public class Lyric /// /// Gets all the lines from the start to the current music duration /// + /// Basolia instance that contains a valid handle /// Array of lyric lines from the start to the current music duration - public LyricLine[] GetLinesCurrent() + public LyricLine[] GetLinesCurrent(BasoliaMedia? basolia) { - var currentSpan = PlaybackPositioningTools.GetCurrentDurationSpan(); + var currentSpan = PlaybackPositioningTools.GetCurrentDurationSpan(basolia); return GetLinesToSpan(currentSpan); } /// /// Gets all the lines from the current music duration to the end /// + /// Basolia instance that contains a valid handle /// Array of lyric lines from the current music duration to the end - public LyricLine[] GetLinesUpcoming() + public LyricLine[] GetLinesUpcoming(BasoliaMedia? basolia) { - var currentSpan = PlaybackPositioningTools.GetCurrentDurationSpan(); + var currentSpan = PlaybackPositioningTools.GetCurrentDurationSpan(basolia); return GetLinesFromSpan(currentSpan); } /// /// Gets the last lyric line from the current music duration /// + /// Basolia instance that contains a valid handle /// Last lyric line from the current music duration - public string GetLastLineCurrent() + public string GetLastLineCurrent(BasoliaMedia? basolia) { - var processedLines = GetLinesCurrent(); + var processedLines = GetLinesCurrent(basolia); if (processedLines.Length > 0) return processedLines[processedLines.Length - 1].Line; return ""; @@ -69,10 +72,11 @@ public string GetLastLineCurrent() /// /// Gets the last lyric line words from the current music duration /// + /// Basolia instance that contains a valid handle /// Last lyric line word from the current music duration - public List GetLastLineWordsCurrent() + public List GetLastLineWordsCurrent(BasoliaMedia? basolia) { - var processedLines = GetLinesCurrent(); + var processedLines = GetLinesCurrent(basolia); if (processedLines.Length > 0) return processedLines[processedLines.Length - 1].LineWords; return []; diff --git a/BassBoom.Basolia/Playback/PlaybackPositioningTools.cs b/BassBoom.Basolia/Playback/PlaybackPositioningTools.cs index daa9889..ebe1414 100644 --- a/BassBoom.Basolia/Playback/PlaybackPositioningTools.cs +++ b/BassBoom.Basolia/Playback/PlaybackPositioningTools.cs @@ -40,10 +40,14 @@ public static class PlaybackPositioningTools /// /// Gets the current duration of the file (samples) /// - public static int GetCurrentDuration() + /// Basolia instance that contains a valid handle + /// Current duration in samples + public static int GetCurrentDuration(BasoliaMedia? basolia) { int length; InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -52,7 +56,7 @@ public static int GetCurrentDuration() // We're now entering the dangerous zone unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // Get the length var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativePositioning.mpg123_tell)); @@ -68,15 +72,19 @@ public static int GetCurrentDuration() /// /// Gets the current duration of the file (time span) /// + /// Basolia instance that contains a valid handle /// A time span instance that describes the current duration of the file - public static TimeSpan GetCurrentDurationSpan() + public static TimeSpan GetCurrentDurationSpan(BasoliaMedia? basolia) { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); + // First, get the format information - var formatInfo = FormatTools.GetFormatInfo(); + var formatInfo = FormatTools.GetFormatInfo(basolia); // Get the required values long rate = formatInfo.rate; - int durationSamples = GetCurrentDuration(); + int durationSamples = GetCurrentDuration(basolia); long seconds = durationSamples / rate; return TimeSpan.FromSeconds(seconds); } @@ -84,11 +92,14 @@ public static TimeSpan GetCurrentDurationSpan() /// /// Seeks to the beginning of the music /// - public static void SeekToTheBeginning() + /// Basolia instance that contains a valid handle + public static void SeekToTheBeginning(BasoliaMedia? basolia) { lock (PositionLock) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -97,17 +108,17 @@ public static void SeekToTheBeginning() // We're now entering the dangerous zone unsafe { - var handle = MpgNative._mpg123Handle; - var outHandle = MpgNative._out123Handle; + var handle = basolia._mpg123Handle; + var outHandle = basolia._out123Handle; // Get the length - PlaybackTools.holding = true; - while (PlaybackTools.bufferPlaying) + basolia.holding = true; + while (basolia.bufferPlaying) Thread.Sleep(1); - Drop(); + Drop(basolia); var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativePositioning.mpg123_seek)); int status = @delegate.Invoke(handle, 0, 0); - PlaybackTools.holding = false; + basolia.holding = false; if (status == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't seek to the beginning of the file", mpg123_errors.MPG123_LSEEK_FAILED); } @@ -117,12 +128,15 @@ public static void SeekToTheBeginning() /// /// Seeks to a specific frame /// + /// Basolia instance that contains a valid handle /// An MPEG frame number - public static void SeekToFrame(int frame) + public static void SeekToFrame(BasoliaMedia? basolia, int frame) { lock (PositionLock) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -131,17 +145,17 @@ public static void SeekToFrame(int frame) // We're now entering the dangerous zone unsafe { - var handle = MpgNative._mpg123Handle; - var outHandle = MpgNative._out123Handle; + var handle = basolia._mpg123Handle; + var outHandle = basolia._out123Handle; // Get the length - PlaybackTools.holding = true; - while (PlaybackTools.bufferPlaying) + basolia.holding = true; + while (basolia.bufferPlaying) Thread.Sleep(1); - Drop(); + Drop(basolia); var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativePositioning.mpg123_seek)); int status = @delegate.Invoke(handle, frame, 0); - PlaybackTools.holding = false; + basolia.holding = false; if (status == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException($"Can't seek to frame #{frame} of the file", (mpg123_errors)status); } @@ -151,13 +165,16 @@ public static void SeekToFrame(int frame) /// /// Seeks according to the lyric line /// + /// Basolia instance that contains a valid handle /// Lyric line instance /// - public static void SeekLyric(LyricLine lyricLine) + public static void SeekLyric(BasoliaMedia? basolia, LyricLine lyricLine) { lock (PositionLock) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -167,20 +184,23 @@ public static void SeekLyric(LyricLine lyricLine) // Get the length, convert it to frames, and seek var length = lyricLine.LineSpan.TotalSeconds; - int frame = (int)(length * FormatTools.GetFormatInfo().rate); - SeekToFrame(frame); + int frame = (int)(length * FormatTools.GetFormatInfo(basolia).rate); + SeekToFrame(basolia, frame); } } /// /// Drops all MPEG frames to the device /// + /// Basolia instance that contains a valid handle /// - public static void Drop() + public static void Drop(BasoliaMedia? basolia) { lock (PositionLock) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -189,7 +209,7 @@ public static void Drop() // We're now entering the dangerous zone unsafe { - var outHandle = MpgNative._out123Handle; + var outHandle = basolia._out123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_drop)); @delegate.Invoke(outHandle); } diff --git a/BassBoom.Basolia/Playback/PlaybackTools.cs b/BassBoom.Basolia/Playback/PlaybackTools.cs index 53b3e36..8c50593 100644 --- a/BassBoom.Basolia/Playback/PlaybackTools.cs +++ b/BassBoom.Basolia/Playback/PlaybackTools.cs @@ -43,52 +43,65 @@ namespace BassBoom.Basolia.Playback /// public static class PlaybackTools { - internal static bool bufferPlaying = false; - internal static bool holding = false; - internal static string radioIcy = ""; - private static PlaybackState state = PlaybackState.Stopped; - /// /// Checks to see whether the music is playing /// - public static bool Playing => - state == PlaybackState.Playing; + /// Basolia instance that contains a valid handle + public static bool IsPlaying(BasoliaMedia? basolia) + { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); + return basolia.state == PlaybackState.Playing; + } /// /// The current state of the playback /// - public static PlaybackState State => - state; + /// Basolia instance that contains a valid handle + public static PlaybackState GetState(BasoliaMedia? basolia) + { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); + return basolia.state; + } /// /// Current radio ICY metadata /// - public static string RadioIcy => - radioIcy; + /// Basolia instance that contains a valid handle + public static string GetRadioIcy(BasoliaMedia? basolia) + { + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); + return basolia.radioIcy; + } /// /// Current radio ICY metadata /// - public static string RadioNowPlaying + /// Basolia instance that contains a valid handle + public static string GetRadioNowPlaying(BasoliaMedia? basolia) { - get - { - string icy = RadioIcy; - if (icy.Length == 0 || !FileTools.IsRadioStation) - return ""; - icy = Regex.Match(icy, @"StreamTitle='(.+?(?=\';))'").Groups[1].Value.Trim().Replace("\\'", "'"); - return icy; - } + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); + string icy = GetRadioIcy(basolia); + if (icy.Length == 0 || !FileTools.IsRadioStation) + return ""; + icy = Regex.Match(icy, @"StreamTitle='(.+?(?=\';))'").Groups[1].Value.Trim().Replace("\\'", "'"); + return icy; } /// /// Plays the currently open file (synchronous) /// + /// Basolia instance that contains a valid handle /// /// - public static void Play() + public static void Play(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) @@ -97,11 +110,11 @@ public static void Play() // We're now entering the dangerous zone unsafe { - var handle = MpgNative._mpg123Handle; - var outHandle = MpgNative._out123Handle; + var handle = basolia._mpg123Handle; + var outHandle = basolia._out123Handle; // First, get formats and reset them - var (rate, channels, encoding) = FormatTools.GetFormatInfo(); + var (rate, channels, encoding) = FormatTools.GetFormatInfo(basolia); var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeOutput.mpg123_format_none)); int resetStatus = @delegate.Invoke(handle); if (resetStatus != (int)mpg123_errors.MPG123_OK) @@ -127,10 +140,10 @@ public static void Play() throw new BasoliaOutException($"Can't start the output.", (out123_error)startStatus); // Now, buffer the entire music file and create an empty array based on its size - var bufferSize = AudioInfoTools.GetBufferSize(); + var bufferSize = AudioInfoTools.GetBufferSize(basolia); Debug.WriteLine($"Buffer size is {bufferSize}"); int err; - state = PlaybackState.Playing; + basolia.state = PlaybackState.Playing; do { int num = 0; @@ -138,74 +151,83 @@ public static void Play() byte[]? audio = null; // First, let Basolia "hold on" until hold is released - while (holding) + while (basolia.holding) Thread.Sleep(1); // Now, play the MPEG buffer to the device - bufferPlaying = true; - err = DecodeTools.DecodeFrame(ref num, ref audio, ref audioBytes); - PlayBuffer(audio); - bufferPlaying = false; + basolia.bufferPlaying = true; + err = DecodeTools.DecodeFrame(basolia, ref num, ref audio, ref audioBytes); + PlayBuffer(basolia, audio); + basolia.bufferPlaying = false; // Check to see if we need more (radio) if (FileTools.IsRadioStation && err == (int)mpg123_errors.MPG123_NEED_MORE) { err = (int)mpg123_errors.MPG123_OK; - FeedRadio(); + FeedRadio(basolia); } - } while (err == (int)mpg123_errors.MPG123_OK && Playing); - if (Playing || state == PlaybackState.Stopping) - state = PlaybackState.Stopped; + } while (err == (int)mpg123_errors.MPG123_OK && IsPlaying(basolia)); + if (IsPlaying(basolia) || basolia.state == PlaybackState.Stopping) + basolia.state = PlaybackState.Stopped; } } /// /// Plays the currently open file (asynchronous) /// - public static async Task PlayAsync() => - await Task.Run(Play); + /// Basolia instance that contains a valid handle + public static async Task PlayAsync(BasoliaMedia? basolia) => + await Task.Run(() => Play(basolia)); /// /// Pauses the currently open file /// /// - public static void Pause() + public static void Pause(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) throw new BasoliaException("Can't pause a file that's not open", mpg123_errors.MPG123_BAD_FILE); - state = PlaybackState.Paused; + basolia.state = PlaybackState.Paused; } /// /// Stops the playback /// + /// Basolia instance that contains a valid handle /// - public static void Stop() + public static void Stop(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check to see if the file is open if (!FileTools.IsOpened) throw new BasoliaException("Can't stop a file that's not open", mpg123_errors.MPG123_BAD_FILE); // Stop the music and seek to the beginning - state = state == PlaybackState.Playing ? PlaybackState.Stopping : PlaybackState.Stopped; - SpinWait.SpinUntil(() => state == PlaybackState.Stopped); + basolia.state = basolia.state == PlaybackState.Playing ? PlaybackState.Stopping : PlaybackState.Stopped; + SpinWait.SpinUntil(() => basolia.state == PlaybackState.Stopped); if (!FileTools.IsRadioStation) - PlaybackPositioningTools.SeekToTheBeginning(); + PlaybackPositioningTools.SeekToTheBeginning(basolia); } /// /// Sets the volume of this application /// + /// Basolia instance that contains a valid handle /// Volume from 0.0 to 1.0, inclusive /// - public static void SetVolume(double volume) + public static void SetVolume(BasoliaMedia? basolia, double volume) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Check the volume if (volume < 0) @@ -216,7 +238,7 @@ public static void SetVolume(double volume) // Try to set the volume unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_volume)); int status = @delegate.Invoke(handle, volume); if (status != (int)out123_error.OUT123_OK) @@ -227,11 +249,14 @@ public static void SetVolume(double volume) /// /// Gets the volume information /// + /// Basolia instance that contains a valid handle /// A base linear volume from 0.0 to 1.0, an actual linear volume from 0.0 to 1.0, and the RVA volume in decibels (dB) /// - public static (double baseLinear, double actualLinear, double decibelsRva) GetVolume() + public static (double baseLinear, double actualLinear, double decibelsRva) GetVolume(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); double baseLinearAddr = 0; double actualLinearAddr = 0; @@ -240,7 +265,7 @@ public static (double baseLinear, double actualLinear, double decibelsRva) GetVo // Try to get the volume unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_getvolume)); int status = @delegate.Invoke(handle, ref baseLinearAddr, ref actualLinearAddr, ref decibelsRvaAddr); if (status != (int)out123_error.OUT123_OK) @@ -254,18 +279,21 @@ public static (double baseLinear, double actualLinear, double decibelsRva) GetVo /// /// Sets the equalizer band to any value /// + /// Basolia instance that contains a valid handle /// Mono, stereo, or both /// Band index from 0 to 31 /// Value of the equalizer /// - public static void SetEqualizer(PlaybackChannels channels, int bandIdx, double value) + public static void SetEqualizer(BasoliaMedia? basolia, PlaybackChannels channels, int bandIdx, double value) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Try to set the equalizer value unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_eq)); int status = @delegate.Invoke(handle, (mpg123_channels)channels, bandIdx, value); if (status != (int)mpg123_errors.MPG123_OK) @@ -276,19 +304,22 @@ public static void SetEqualizer(PlaybackChannels channels, int bandIdx, double v /// /// Sets the equalizer bands to any value /// + /// Basolia instance that contains a valid handle /// Mono, stereo, or both /// Band index from 0 to 31 (first band to start from) /// Band index from 0 to 31 (second band to end to) /// Value of the equalizer /// - public static void SetEqualizerRange(PlaybackChannels channels, int bandIdxStart, int bandIdxEnd, double value) + public static void SetEqualizerRange(BasoliaMedia? basolia, PlaybackChannels channels, int bandIdxStart, int bandIdxEnd, double value) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Try to set the equalizer value unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_eq_bands)); int status = @delegate.Invoke(handle, (int)channels, bandIdxStart, bandIdxEnd, value); if (status != (int)mpg123_errors.MPG123_OK) @@ -299,17 +330,20 @@ public static void SetEqualizerRange(PlaybackChannels channels, int bandIdxStart /// /// Gets the equalizer band value /// + /// Basolia instance that contains a valid handle /// Mono, stereo, or both /// Band index from 0 to 31 /// - public static double GetEqualizer(PlaybackChannels channels, int bandIdx) + public static double GetEqualizer(BasoliaMedia? basolia, PlaybackChannels channels, int bandIdx) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Try to set the equalizer value unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_geteq)); double eq = @delegate.Invoke(handle, (mpg123_channels)channels, bandIdx); return eq; @@ -319,15 +353,18 @@ public static double GetEqualizer(PlaybackChannels channels, int bandIdx) /// /// Resets the equalizer band to its natural value /// + /// Basolia instance that contains a valid handle /// - public static void ResetEqualizer() + public static void ResetEqualizer(BasoliaMedia? basolia) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Try to set the equalizer value unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_reset_eq)); int status = @delegate.Invoke(handle); if (status != (int)mpg123_errors.MPG123_OK) @@ -338,19 +375,22 @@ public static void ResetEqualizer() /// /// Gets the native state /// + /// Basolia instance that contains a valid handle /// A native state to get /// A number that represents the value of this state /// - public static (long, double) GetNativeState(PlaybackStateType state) + public static (long, double) GetNativeState(BasoliaMedia? basolia, PlaybackStateType state) { InitBasolia.CheckInited(); + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); // Try to set the equalizer value unsafe { long stateInt = 0; double stateDouble = 0; - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_getstate)); int status = @delegate.Invoke(handle, (mpg123_state)state, ref stateInt, ref stateDouble); if (status != (int)mpg123_errors.MPG123_OK) @@ -359,7 +399,7 @@ public static (long, double) GetNativeState(PlaybackStateType state) } } - internal static void FeedRadio() + internal static void FeedRadio(BasoliaMedia? basolia) { if (!FileTools.IsOpened || !FileTools.IsRadioStation) return; @@ -369,10 +409,12 @@ internal static void FeedRadio() return; if (FileTools.CurrentFile.Stream is null) return; + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); unsafe { - var handle = MpgNative._mpg123Handle; + var handle = basolia._mpg123Handle; // Get the MP3 frame length first string metaIntStr = FileTools.CurrentFile.Headers.GetValues("icy-metaint").First(); @@ -397,8 +439,8 @@ internal static void FeedRadio() FileTools.CurrentFile.Stream.Read(metadataBytes, 0, metaBytesToRead); string icy = Encoding.UTF8.GetString(metadataBytes).Replace("\0", "").Trim(); if (!string.IsNullOrEmpty(icy)) - radioIcy = icy; - Debug.WriteLine($"{radioIcy}"); + basolia.radioIcy = icy; + Debug.WriteLine($"{basolia.radioIcy}"); // Copy the data to MPG123 IntPtr data = Marshal.AllocHGlobal(buffer.Length); @@ -410,13 +452,15 @@ internal static void FeedRadio() } } - internal static int PlayBuffer(byte[]? buffer) + internal static int PlayBuffer(BasoliaMedia? basolia, byte[]? buffer) { if (buffer is null) return 0; + if (basolia is null) + throw new BasoliaException("Basolia instance is not provided", mpg123_errors.MPG123_BAD_HANDLE); unsafe { - var outHandle = MpgNative._out123Handle; + var outHandle = basolia._out123Handle; IntPtr bufferPtr = Marshal.AllocHGlobal(Marshal.SizeOf() * buffer.Length); Marshal.Copy(buffer, 0, bufferPtr, buffer.Length); var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_play)); diff --git a/BassBoom.Basolia/Radio/RadioTools.cs b/BassBoom.Basolia/Radio/RadioTools.cs index a9afaf7..e4cb799 100644 --- a/BassBoom.Basolia/Radio/RadioTools.cs +++ b/BassBoom.Basolia/Radio/RadioTools.cs @@ -18,6 +18,7 @@ // using BassBoom.Basolia.Exceptions; +using SpecProbe.Software.Platform; using System; using System.Linq; using System.Net.Http; @@ -54,7 +55,7 @@ public static class RadioTools var uri = new Uri(radioUrl); // Check to see if the radio station exists - if (RuntimeInformation.FrameworkDescription.Contains("Framework")) + if (PlatformHelper.IsDotNetFx()) client = new(); client.DefaultRequestHeaders.Add("Icy-MetaData", "1"); var reply = await client.GetAsync(radioUrl, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); diff --git a/BassBoom.Cli/BassBoomCli.cs b/BassBoom.Cli/BassBoomCli.cs index 084b459..c220449 100644 --- a/BassBoom.Cli/BassBoomCli.cs +++ b/BassBoom.Cli/BassBoomCli.cs @@ -36,6 +36,7 @@ internal class BassBoomCli private static readonly Version? version = Assembly.GetAssembly(typeof(InitBasolia))?.GetName().Version; internal static Version? mpgVer; internal static Version? outVer; + internal static BasoliaMedia? basolia; internal static Color white = new(ConsoleColors.White); static int Main(string[] args) @@ -63,7 +64,7 @@ static int Main(string[] args) } // Initialize Basolia - InitBasolia.Init(); + basolia = new(); // Initialize versions mpgVer = InitBasolia.MpgLibVersion; diff --git a/BassBoom.Cli/CliBase/Common.cs b/BassBoom.Cli/CliBase/Common.cs index 097b034..9b01115 100644 --- a/BassBoom.Cli/CliBase/Common.cs +++ b/BassBoom.Cli/CliBase/Common.cs @@ -57,7 +57,7 @@ internal static void RaiseVolume() volume += 0.05; if (volume > 1) volume = 1; - PlaybackTools.SetVolume(volume); + PlaybackTools.SetVolume(BassBoomCli.basolia, volume); } internal static void LowerVolume() @@ -65,7 +65,7 @@ internal static void LowerVolume() volume -= 0.05; if (volume < 0) volume = 0; - PlaybackTools.SetVolume(volume); + PlaybackTools.SetVolume(BassBoomCli.basolia, volume); } internal static void Exit() @@ -73,26 +73,26 @@ internal static void Exit() exiting = true; advance = false; if (FileTools.IsOpened) - PlaybackTools.Stop(); + PlaybackTools.Stop(BassBoomCli.basolia); } internal static void Switch(string musicPath) { if (FileTools.IsOpened) - FileTools.CloseFile(); + FileTools.CloseFile(BassBoomCli.basolia); if (isRadioMode) - FileTools.OpenUrl(musicPath); + FileTools.OpenUrl(BassBoomCli.basolia, musicPath); else - FileTools.OpenFile(musicPath); + FileTools.OpenFile(BassBoomCli.basolia, musicPath); } internal static void ShowDeviceDriver() { var builder = new StringBuilder(); var currentBuilder = new StringBuilder(); - if (PlaybackTools.Playing) + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) { - var (driver, device) = DeviceTools.GetCurrent(); + var (driver, device) = DeviceTools.GetCurrent(BassBoomCli.basolia); var cached = DeviceTools.GetCurrentCached(); currentBuilder.AppendLine( $$""" @@ -105,14 +105,14 @@ internal static void ShowDeviceDriver() } else currentBuilder.AppendLine("Can't query current devices while not playing."); - var drivers = DeviceTools.GetDrivers(); + var drivers = DeviceTools.GetDrivers(BassBoomCli.basolia); string activeDevice = ""; foreach (var driver in drivers) { try { builder.AppendLine($"- {driver.Key}: {driver.Value}"); - var devices = DeviceTools.GetDevices(driver.Key, ref activeDevice); + var devices = DeviceTools.GetDevices(BassBoomCli.basolia, driver.Key, ref activeDevice); foreach (var device in devices) builder.AppendLine($" - {device.Key}: {device.Value}"); } @@ -262,21 +262,21 @@ internal static void HandleKeypressCommon(ConsoleKeyInfo keystroke, Screen playe case ConsoleKey.D: if (keystroke.Modifiers == ConsoleModifiers.Control) { - var drivers = DeviceTools.GetDrivers().Select((kvp) => new InputChoiceInfo(kvp.Key, kvp.Value)).ToArray(); + var drivers = DeviceTools.GetDrivers(BassBoomCli.basolia).Select((kvp) => new InputChoiceInfo(kvp.Key, kvp.Value)).ToArray(); int driverIdx = InfoBoxSelectionColor.WriteInfoBoxSelection(drivers, "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(); + var devices = DeviceTools.GetDevices(BassBoomCli.basolia, driver.ChoiceName, ref active).Select((kvp) => new InputChoiceInfo(kvp.Key, kvp.Value)).ToArray(); int deviceIdx = InfoBoxSelectionColor.WriteInfoBoxSelection(devices, $"Select a device. Current driver is {active}. ESC to quit."); playerScreen.RequireRefresh(); if (deviceIdx < 0) return; var device = devices[deviceIdx]; - DeviceTools.SetActiveDriver(driver.ChoiceName); - DeviceTools.SetActiveDevice(driver.ChoiceName, device.ChoiceName); + DeviceTools.SetActiveDriver(BassBoomCli.basolia, driver.ChoiceName); + DeviceTools.SetActiveDevice(BassBoomCli.basolia, driver.ChoiceName, device.ChoiceName); } else if (keystroke.Modifiers == ConsoleModifiers.Shift) DeviceTools.Reset(); diff --git a/BassBoom.Cli/CliBase/EqualizerControls.cs b/BassBoom.Cli/CliBase/EqualizerControls.cs index 9273140..4c63816 100644 --- a/BassBoom.Cli/CliBase/EqualizerControls.cs +++ b/BassBoom.Cli/CliBase/EqualizerControls.cs @@ -32,17 +32,17 @@ internal static double GetCachedEqualizer(int band) => bands[band]; internal static double GetEqualizer(int band) => - PlaybackTools.GetEqualizer(PlaybackChannels.Both, band); + PlaybackTools.GetEqualizer(BassBoomCli.basolia, PlaybackChannels.Both, band); internal static void SetEqualizer(int band, double value) { - PlaybackTools.SetEqualizer(PlaybackChannels.Both, band, value); + PlaybackTools.SetEqualizer(BassBoomCli.basolia, PlaybackChannels.Both, band, value); UpdateEqualizers(); } internal static void ResetEqualizers() { - PlaybackTools.ResetEqualizer(); + PlaybackTools.ResetEqualizer(BassBoomCli.basolia); UpdateEqualizers(); } diff --git a/BassBoom.Cli/CliBase/Player.cs b/BassBoom.Cli/CliBase/Player.cs index 768212c..787b7a2 100644 --- a/BassBoom.Cli/CliBase/Player.cs +++ b/BassBoom.Cli/CliBase/Player.cs @@ -46,7 +46,7 @@ internal static class Player public static void PlayerLoop() { - Common.volume = PlaybackTools.GetVolume().baseLinear; + Common.volume = PlaybackTools.GetVolume(BassBoomCli.basolia).baseLinear; // Populate the screen Screen playerScreen = new(); @@ -65,10 +65,10 @@ public static void PlayerLoop() if (Common.CurrentCachedInfo is null) return ""; var buffer = new StringBuilder(); - position = FileTools.IsOpened ? PlaybackPositioningTools.GetCurrentDuration() : 0; - var posSpan = FileTools.IsOpened ? PlaybackPositioningTools.GetCurrentDurationSpan() : new(); - var disco = PlaybackTools.Playing && Common.enableDisco ? new Color($"hsl:{hue};50;50") : BassBoomCli.white; - if (PlaybackTools.Playing) + position = FileTools.IsOpened ? PlaybackPositioningTools.GetCurrentDuration(BassBoomCli.basolia) : 0; + var posSpan = FileTools.IsOpened ? PlaybackPositioningTools.GetCurrentDurationSpan(BassBoomCli.basolia) : new(); + var disco = PlaybackTools.IsPlaying(BassBoomCli.basolia) && Common.enableDisco ? new Color($"hsl:{hue};50;50") : BassBoomCli.white; + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) { hue++; if (hue >= 360) @@ -77,13 +77,13 @@ public static void PlayerLoop() string indicator = $"╣ Seek: {PlayerControls.seekRate:0.00} | " + $"Volume: {Common.volume:0.00} ╠"; - string lyric = Common.CurrentCachedInfo.LyricInstance is not null ? Common.CurrentCachedInfo.LyricInstance.GetLastLineCurrent() : ""; + string lyric = Common.CurrentCachedInfo.LyricInstance is not null ? Common.CurrentCachedInfo.LyricInstance.GetLastLineCurrent(BassBoomCli.basolia) : ""; 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) + CenteredTextColor.RenderCentered(ConsoleWrapper.WindowHeight - 6, Common.CurrentCachedInfo.LyricInstance is not null && PlaybackTools.IsPlaying(BassBoomCli.basolia) ? $"╣ {finalLyric} ╠" : "", disco) ); return buffer.ToString(); }); @@ -106,7 +106,7 @@ public static void PlayerLoop() if (ConsoleWrapper.KeyAvailable) { var keystroke = Input.ReadKey(); - if (PlaybackTools.Playing) + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) HandleKeypressPlayMode(keystroke, playerScreen); else HandleKeypressIdleMode(keystroke, playerScreen); @@ -114,22 +114,22 @@ public static void PlayerLoop() } catch (BasoliaException bex) { - if (PlaybackTools.Playing) - PlaybackTools.Stop(); + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) + PlaybackTools.Stop(BassBoomCli.basolia); InfoBoxColor.WriteInfoBox("There's an error with Basolia when trying to process the music file.\n\n" + bex.Message); playerScreen.RequireRefresh(); } catch (BasoliaOutException bex) { - if (PlaybackTools.Playing) - PlaybackTools.Stop(); + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) + PlaybackTools.Stop(BassBoomCli.basolia); InfoBoxColor.WriteInfoBox("There's an error with Basolia output when trying to process the music file.\n\n" + bex.Message); playerScreen.RequireRefresh(); } catch (Exception ex) { - if (PlaybackTools.Playing) - PlaybackTools.Stop(); + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) + PlaybackTools.Stop(BassBoomCli.basolia); InfoBoxColor.WriteInfoBox("There's an unknown error when trying to process the music file.\n\n" + ex.Message); playerScreen.RequireRefresh(); } @@ -137,7 +137,7 @@ public static void PlayerLoop() // Close the file if open if (FileTools.IsOpened) - FileTools.CloseFile(); + FileTools.CloseFile(BassBoomCli.basolia); // Restore state ConsoleWrapper.CursorVisible = true; @@ -188,7 +188,7 @@ private static void HandleKeypressIdleMode(ConsoleKeyInfo keystroke, Screen play if (keystroke.Modifiers == ConsoleModifiers.Shift) PlayerControls.SeekTo(Common.CurrentCachedInfo.RepeatCheckpoint); else - Common.CurrentCachedInfo.RepeatCheckpoint = PlaybackPositioningTools.GetCurrentDurationSpan(); + Common.CurrentCachedInfo.RepeatCheckpoint = PlaybackPositioningTools.GetCurrentDurationSpan(BassBoomCli.basolia); break; default: Common.HandleKeypressCommon(keystroke, playerScreen, false); @@ -274,7 +274,7 @@ private static void HandleKeypressPlayMode(ConsoleKeyInfo keystroke, Screen play if (keystroke.Modifiers == ConsoleModifiers.Shift) PlayerControls.SeekTo(Common.CurrentCachedInfo.RepeatCheckpoint); else - Common.CurrentCachedInfo.RepeatCheckpoint = PlaybackPositioningTools.GetCurrentDurationSpan(); + Common.CurrentCachedInfo.RepeatCheckpoint = PlaybackPositioningTools.GetCurrentDurationSpan(BassBoomCli.basolia); break; default: Common.HandleKeypressCommon(keystroke, playerScreen, false); @@ -298,9 +298,9 @@ private static void HandlePlay() if (Common.paused) { Common.paused = false; - PlaybackPositioningTools.SeekToFrame(position); + PlaybackPositioningTools.SeekToFrame(BassBoomCli.basolia, position); } - PlaybackTools.Play(); + PlaybackTools.Play(BassBoomCli.basolia); } } catch (Exception ex) diff --git a/BassBoom.Cli/CliBase/PlayerControls.cs b/BassBoom.Cli/CliBase/PlayerControls.cs index c7f6497..016e06a 100644 --- a/BassBoom.Cli/CliBase/PlayerControls.cs +++ b/BassBoom.Cli/CliBase/PlayerControls.cs @@ -53,7 +53,7 @@ internal static void SeekForward() Player.position += (int)(Common.CurrentCachedInfo.FormatInfo.rate * seekRate); if (Player.position > Common.CurrentCachedInfo.Duration) Player.position = Common.CurrentCachedInfo.Duration; - PlaybackPositioningTools.SeekToFrame(Player.position); + PlaybackPositioningTools.SeekToFrame(BassBoomCli.basolia, Player.position); } internal static void SeekBackward() @@ -67,7 +67,7 @@ internal static void SeekBackward() Player.position -= (int)(Common.CurrentCachedInfo.FormatInfo.rate * seekRate); if (Player.position < 0) Player.position = 0; - PlaybackPositioningTools.SeekToFrame(Player.position); + PlaybackPositioningTools.SeekToFrame(BassBoomCli.basolia, Player.position); } internal static void SeekBeginning() @@ -76,7 +76,7 @@ internal static void SeekBeginning() if (Common.cachedInfos.Count == 0) return; - PlaybackPositioningTools.SeekToTheBeginning(); + PlaybackPositioningTools.SeekToTheBeginning(BassBoomCli.basolia); Player.position = 0; } @@ -90,11 +90,11 @@ internal static void SeekPreviousLyric() if (Common.CurrentCachedInfo.LyricInstance is null) return; - var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesCurrent(); + var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesCurrent(BassBoomCli.basolia); if (lyrics.Length == 0) return; var lyric = lyrics.Length == 1 ? lyrics[0] : lyrics[lyrics.Length - 2]; - PlaybackPositioningTools.SeekLyric(lyric); + PlaybackPositioningTools.SeekLyric(BassBoomCli.basolia, lyric); } internal static void SeekCurrentLyric() @@ -107,11 +107,11 @@ internal static void SeekCurrentLyric() if (Common.CurrentCachedInfo.LyricInstance is null) return; - var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesCurrent(); + var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesCurrent(BassBoomCli.basolia); if (lyrics.Length == 0) return; var lyric = lyrics[lyrics.Length - 1]; - PlaybackPositioningTools.SeekLyric(lyric); + PlaybackPositioningTools.SeekLyric(BassBoomCli.basolia, lyric); } internal static void SeekNextLyric() @@ -124,14 +124,14 @@ internal static void SeekNextLyric() if (Common.CurrentCachedInfo.LyricInstance is null) return; - var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesUpcoming(); + var lyrics = Common.CurrentCachedInfo.LyricInstance.GetLinesUpcoming(BassBoomCli.basolia); if (lyrics.Length == 0) { SeekCurrentLyric(); return; } var lyric = lyrics[0]; - PlaybackPositioningTools.SeekLyric(lyric); + PlaybackPositioningTools.SeekLyric(BassBoomCli.basolia, lyric); } internal static void SeekWhichLyric() @@ -150,7 +150,7 @@ internal static void SeekWhichLyric() if (index == -1) return; var lyric = lyrics[index]; - PlaybackPositioningTools.SeekLyric(lyric); + PlaybackPositioningTools.SeekLyric(BassBoomCli.basolia, lyric); } internal static void SeekTo(TimeSpan target) @@ -164,7 +164,7 @@ internal static void SeekTo(TimeSpan target) Player.position = (int)(target.TotalSeconds * Common.CurrentCachedInfo.FormatInfo.rate); if (Player.position > Common.CurrentCachedInfo.Duration) Player.position = 0; - PlaybackPositioningTools.SeekToFrame(Player.position); + PlaybackPositioningTools.SeekToFrame(BassBoomCli.basolia, Player.position); } internal static void Play() @@ -175,12 +175,12 @@ internal static void Play() if (Player.playerThread is null) return; - if (PlaybackTools.State == PlaybackState.Stopped) + if (PlaybackTools.GetState(BassBoomCli.basolia) == PlaybackState.Stopped) // There could be a chance that the music has fully stopped without any user interaction. - PlaybackPositioningTools.SeekToTheBeginning(); + PlaybackPositioningTools.SeekToTheBeginning(BassBoomCli.basolia); Common.advance = true; Player.playerThread.Start(); - SpinWait.SpinUntil(() => PlaybackTools.Playing || Common.failedToPlay); + SpinWait.SpinUntil(() => PlaybackTools.IsPlaying(BassBoomCli.basolia) || Common.failedToPlay); Common.failedToPlay = false; } @@ -188,7 +188,7 @@ internal static void Pause() { Common.advance = false; Common.paused = true; - PlaybackTools.Pause(); + PlaybackTools.Pause(BassBoomCli.basolia); } internal static void Stop(bool resetCurrentSong = true) @@ -197,7 +197,7 @@ internal static void Stop(bool resetCurrentSong = true) Common.paused = false; if (resetCurrentSong) Common.currentPos = 1; - PlaybackTools.Stop(); + PlaybackTools.Stop(BassBoomCli.basolia); } internal static void NextSong() @@ -233,7 +233,7 @@ internal static void PromptForAddSong() PopulateMusicFileInfo(path); Common.populate = true; PopulateMusicFileInfo(Common.CurrentCachedInfo?.MusicPath ?? ""); - PlaybackPositioningTools.SeekToFrame(currentPos); + PlaybackPositioningTools.SeekToFrame(BassBoomCli.basolia, currentPos); } else InfoBoxColor.WriteInfoBox($"File \"{path}\" doesn't exist."); @@ -256,7 +256,7 @@ internal static void PromptForAddDirectory() } Common.populate = true; PopulateMusicFileInfo(Common.CurrentCachedInfo?.MusicPath ?? ""); - PlaybackPositioningTools.SeekToFrame(currentPos); + PlaybackPositioningTools.SeekToFrame(BassBoomCli.basolia, currentPos); } } else @@ -266,7 +266,7 @@ internal static void PromptForAddDirectory() internal static void PopulateMusicFileInfo(string musicPath) { // Try to open the file after loading the library - if (PlaybackTools.Playing || !Common.populate) + if (PlaybackTools.IsPlaying(BassBoomCli.basolia) || !Common.populate) return; Common.populate = false; Common.Switch(musicPath); @@ -274,10 +274,10 @@ internal static void PopulateMusicFileInfo(string musicPath) { ScreenTools.CurrentScreen?.RequireRefresh(); InfoBoxColor.WriteInfoBox($"Loading BassBoom to open {musicPath}...", false); - var total = AudioInfoTools.GetDuration(true); - var formatInfo = FormatTools.GetFormatInfo(); - var frameInfo = AudioInfoTools.GetFrameInfo(); - AudioInfoTools.GetId3Metadata(out var managedV1, out var managedV2); + var total = AudioInfoTools.GetDuration(BassBoomCli.basolia, true); + var formatInfo = FormatTools.GetFormatInfo(BassBoomCli.basolia); + var frameInfo = AudioInfoTools.GetFrameInfo(BassBoomCli.basolia); + AudioInfoTools.GetId3Metadata(BassBoomCli.basolia, out var managedV1, out var managedV2); // Try to open the lyrics var lyric = OpenLyrics(musicPath); @@ -401,7 +401,7 @@ internal static void PromptSeek() Player.position = (int)(Common.CurrentCachedInfo.FormatInfo.rate * duration.TotalSeconds); if (Player.position > Common.CurrentCachedInfo.Duration) Player.position = Common.CurrentCachedInfo.Duration; - PlaybackPositioningTools.SeekToFrame(Player.position); + PlaybackPositioningTools.SeekToFrame(BassBoomCli.basolia, Player.position); } } @@ -447,13 +447,13 @@ Layer info Native State ============ - Accurate rendering: {{PlaybackTools.GetNativeState(PlaybackStateType.Accurate)}} - Buffer fill: {{PlaybackTools.GetNativeState(PlaybackStateType.BufferFill)}} - Decoding delay: {{PlaybackTools.GetNativeState(PlaybackStateType.DecodeDelay)}} - Encoding delay: {{PlaybackTools.GetNativeState(PlaybackStateType.EncodeDelay)}} - Encoding padding: {{PlaybackTools.GetNativeState(PlaybackStateType.EncodePadding)}} - Frankenstein stream: {{PlaybackTools.GetNativeState(PlaybackStateType.Frankenstein)}} - Fresh decoder: {{PlaybackTools.GetNativeState(PlaybackStateType.FreshDecoder)}} + Accurate rendering: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.Accurate)}} + Buffer fill: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.BufferFill)}} + Decoding delay: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.DecodeDelay)}} + Encoding delay: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.EncodeDelay)}} + Encoding padding: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.EncodePadding)}} + Frankenstein stream: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.Frankenstein)}} + Fresh decoder: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.FreshDecoder)}} Texts and Extras ================ diff --git a/BassBoom.Cli/CliBase/Radio.cs b/BassBoom.Cli/CliBase/Radio.cs index 9845195..5838563 100644 --- a/BassBoom.Cli/CliBase/Radio.cs +++ b/BassBoom.Cli/CliBase/Radio.cs @@ -45,7 +45,7 @@ internal static class Radio public static void RadioLoop() { - Common.volume = PlaybackTools.GetVolume().baseLinear; + Common.volume = PlaybackTools.GetVolume(BassBoomCli.basolia).baseLinear; Common.isRadioMode = true; // Populate the screen @@ -66,8 +66,8 @@ public static void RadioLoop() return ""; var buffer = new StringBuilder(); string indicator = $"╣ Volume: {Common.volume:0.00} ╠"; - var disco = PlaybackTools.Playing && Common.enableDisco ? new Color($"hsl:{hue};50;50") : BassBoomCli.white; - if (PlaybackTools.Playing) + var disco = PlaybackTools.IsPlaying(BassBoomCli.basolia) && Common.enableDisco ? new Color($"hsl:{hue};50;50") : BassBoomCli.white; + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) { hue++; if (hue >= 360) @@ -98,7 +98,7 @@ public static void RadioLoop() if (ConsoleWrapper.KeyAvailable) { var keystroke = Input.ReadKey(); - if (PlaybackTools.Playing) + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) HandleKeypressPlayMode(keystroke, radioScreen); else HandleKeypressIdleMode(keystroke, radioScreen); @@ -106,22 +106,22 @@ public static void RadioLoop() } catch (BasoliaException bex) { - if (PlaybackTools.Playing) - PlaybackTools.Stop(); + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) + PlaybackTools.Stop(BassBoomCli.basolia); InfoBoxColor.WriteInfoBox("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(); + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) + PlaybackTools.Stop(BassBoomCli.basolia); InfoBoxColor.WriteInfoBox("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(); + if (PlaybackTools.IsPlaying(BassBoomCli.basolia)) + PlaybackTools.Stop(BassBoomCli.basolia); InfoBoxColor.WriteInfoBox("There's an unknown error when trying to process the music file.\n\n" + ex.Message); radioScreen.RequireRefresh(); } @@ -129,7 +129,7 @@ public static void RadioLoop() // Close the file if open if (FileTools.IsOpened) - FileTools.CloseFile(); + FileTools.CloseFile(BassBoomCli.basolia); // Restore state ConsoleWrapper.CursorVisible = true; @@ -240,7 +240,7 @@ private static void HandlePlay() TextWriterRaw.WritePlain(RadioControls.RenderStationName(), false); if (Common.paused) Common.paused = false; - PlaybackTools.Play(); + PlaybackTools.Play(BassBoomCli.basolia); } } catch (Exception ex) diff --git a/BassBoom.Cli/CliBase/RadioControls.cs b/BassBoom.Cli/CliBase/RadioControls.cs index ce0895c..143b773 100644 --- a/BassBoom.Cli/CliBase/RadioControls.cs +++ b/BassBoom.Cli/CliBase/RadioControls.cs @@ -47,11 +47,11 @@ internal static void Play() // 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(); + if (PlaybackTools.GetState(BassBoomCli.basolia) == PlaybackState.Stopped) + PlaybackPositioningTools.Drop(BassBoomCli.basolia); Common.advance = true; Radio.playerThread.Start(); - SpinWait.SpinUntil(() => PlaybackTools.Playing || Common.failedToPlay); + SpinWait.SpinUntil(() => PlaybackTools.IsPlaying(BassBoomCli.basolia) || Common.failedToPlay); Common.failedToPlay = false; } @@ -59,7 +59,7 @@ internal static void Pause() { Common.advance = false; Common.paused = true; - PlaybackTools.Pause(); + PlaybackTools.Pause(BassBoomCli.basolia); } internal static void Stop(bool resetCurrentStation = true) @@ -68,7 +68,7 @@ internal static void Stop(bool resetCurrentStation = true) Common.paused = false; if (resetCurrentStation) Common.currentPos = 1; - PlaybackTools.Stop(); + PlaybackTools.Stop(BassBoomCli.basolia); } internal static void NextStation() @@ -77,7 +77,7 @@ internal static void NextStation() if (Common.cachedInfos.Count == 0) return; - PlaybackTools.Stop(); + PlaybackTools.Stop(BassBoomCli.basolia); Common.currentPos++; if (Common.currentPos > Common.cachedInfos.Count) Common.currentPos = 1; @@ -89,7 +89,7 @@ internal static void PreviousStation() if (Common.cachedInfos.Count == 0) return; - PlaybackTools.Stop(); + PlaybackTools.Stop(BassBoomCli.basolia); Common.currentPos--; if (Common.currentPos <= 0) Common.currentPos = Common.cachedInfos.Count; @@ -108,15 +108,15 @@ internal static void PromptForAddStation() internal static void PopulateRadioStationInfo(string musicPath) { // Try to open the file after loading the library - if (PlaybackTools.Playing || !Common.populate) + if (PlaybackTools.IsPlaying(BassBoomCli.basolia) || !Common.populate) return; Common.populate = false; Common.Switch(musicPath); if (!Common.cachedInfos.Any((csi) => csi.MusicPath == musicPath)) { InfoBoxColor.WriteInfoBox($"Loading BassBoom to open {musicPath}...", false); - var formatInfo = FormatTools.GetFormatInfo(); - var frameInfo = AudioInfoTools.GetFrameInfo(); + var formatInfo = FormatTools.GetFormatInfo(BassBoomCli.basolia); + var frameInfo = AudioInfoTools.GetFrameInfo(BassBoomCli.basolia); // Try to open the lyrics var instance = new CachedSongInfo(musicPath, null, null, -1, formatInfo, frameInfo, null, FileTools.CurrentFile?.StationName ?? "", true); @@ -127,7 +127,7 @@ internal static void PopulateRadioStationInfo(string musicPath) internal static string RenderStationName() { // Render the station name - string icy = PlaybackTools.RadioNowPlaying; + string icy = PlaybackTools.GetRadioNowPlaying(BassBoomCli.basolia); // Print the music name return @@ -175,7 +175,7 @@ Station info Radio station URL: {{Common.CurrentCachedInfo.MusicPath}} Radio station name: {{Common.CurrentCachedInfo.StationName}} - Radio station current song: {{PlaybackTools.RadioNowPlaying}} + Radio station current song: {{PlaybackTools.GetRadioNowPlaying(BassBoomCli.basolia)}} Layer info ========== @@ -195,13 +195,13 @@ Layer info Native State ============ - Accurate rendering: {{PlaybackTools.GetNativeState(PlaybackStateType.Accurate)}} - Buffer fill: {{PlaybackTools.GetNativeState(PlaybackStateType.BufferFill)}} - Decoding delay: {{PlaybackTools.GetNativeState(PlaybackStateType.DecodeDelay)}} - Encoding delay: {{PlaybackTools.GetNativeState(PlaybackStateType.EncodeDelay)}} - Encoding padding: {{PlaybackTools.GetNativeState(PlaybackStateType.EncodePadding)}} - Frankenstein stream: {{PlaybackTools.GetNativeState(PlaybackStateType.Frankenstein)}} - Fresh decoder: {{PlaybackTools.GetNativeState(PlaybackStateType.FreshDecoder)}} + Accurate rendering: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.Accurate)}} + Buffer fill: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.BufferFill)}} + Decoding delay: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.DecodeDelay)}} + Encoding delay: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.EncodeDelay)}} + Encoding padding: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.EncodePadding)}} + Frankenstein stream: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.Frankenstein)}} + Fresh decoder: {{PlaybackTools.GetNativeState(BassBoomCli.basolia, PlaybackStateType.FreshDecoder)}} """ ); } diff --git a/BassBoom.Native/MpgNative.cs b/BassBoom.Native/MpgNative.cs index d5dbc81..c7296c0 100644 --- a/BassBoom.Native/MpgNative.cs +++ b/BassBoom.Native/MpgNative.cs @@ -38,9 +38,6 @@ internal static unsafe class MpgNative internal static string mpg123LibPath = GetLibPath("mpg123"); internal static string out123LibPath = GetLibPath("out123"); - internal static mpg123_handle* _mpg123Handle; - internal static out123_handle* _out123Handle; - internal static LibraryManager? libManagerMpg; internal static LibraryManager? libManagerOut; @@ -117,51 +114,32 @@ internal static void InitializeLibrary(string libPath, string libPathOut) mpg123LibPath = libPath; out123LibPath = libPathOut; - // Start the libraries up - var architecture = PlatformHelper.GetArchitecture(); - if (architecture == Architecture.X86 || architecture == Architecture.Arm) - throw new BasoliaNativeLibraryException("32-bit platforms are no longer supported."); - libManagerMpg = new LibraryManager(new LibraryFile(mpg123LibPath)); - libManagerOut = new LibraryManager(new LibraryFile(out123LibPath)); - libManagerMpg.LoadNativeLibrary(); - libManagerOut.LoadNativeLibrary(); - - // Tell the library the path for the modules - string libPluginsPath = Path.GetDirectoryName(mpg123LibPath) + "/plugins/"; - int result = -1; - if (PlatformHelper.IsOnWindows()) - result = NativeInit._putenv_s("MPG123_MODDIR", libPluginsPath); - else - result = NativeInit.setenv("MPG123_MODDIR", libPluginsPath, 1); - if (result != 0) - throw new BasoliaNativeLibraryException("Can't set environment variable MPG123_MODDIR"); - - // Verify that we've actually loaded the library! try { - var @delegate = GetDelegate(libManagerMpg, nameof(NativeInit.mpg123_new)); - var handle = @delegate.Invoke(null, null); - Debug.WriteLine($"Verifying mpg123 version: {MpgLibVersion}"); - _mpg123Handle = handle; + // Start the libraries up + var architecture = PlatformHelper.GetArchitecture(); + if (architecture == Architecture.X86 || architecture == Architecture.Arm) + throw new BasoliaNativeLibraryException("32-bit platforms are no longer supported."); + libManagerMpg = new LibraryManager(new LibraryFile(mpg123LibPath)); + libManagerOut = new LibraryManager(new LibraryFile(out123LibPath)); + libManagerMpg.LoadNativeLibrary(); + libManagerOut.LoadNativeLibrary(); + + // Tell the library the path for the modules + string libPluginsPath = Path.GetDirectoryName(mpg123LibPath) + "/plugins/"; + int result = -1; + if (PlatformHelper.IsOnWindows()) + result = NativeInit._putenv_s("MPG123_MODDIR", libPluginsPath); + else + result = NativeInit.setenv("MPG123_MODDIR", libPluginsPath, 1); + if (result != 0) + throw new BasoliaNativeLibraryException("Can't set environment variable MPG123_MODDIR"); } catch (Exception ex) { mpg123LibPath = oldLibPath; - throw new BasoliaNativeLibraryException($"mpg123 library path {libPath} doesn't contain a valid mpg123 library. out123_distversion() was called. {ex.Message}"); - } - - // Do the same for the out123 library! - try - { - var @delegate = GetDelegate(libManagerOut, nameof(NativeOutputLib.out123_new)); - var handle = @delegate.Invoke(); - Debug.WriteLine($"Verifying out123 version: {OutLibVersion}"); - _out123Handle = handle; - } - catch (Exception ex) - { out123LibPath = oldLibPathOut; - throw new BasoliaNativeLibraryException($"out123 library path {libPathOut} doesn't contain a valid out123 library. out123_distversion() was called. {ex.Message}"); + throw new BasoliaNativeLibraryException($"Failed to load libraries. {mpg123LibPath}. {ex.Message}"); } }