From 84f6c699e738e37c59ec8bb55c791f4ed1d0f483 Mon Sep 17 00:00:00 2001 From: Aptivi CEO Date: Wed, 4 Sep 2024 19:17:40 +0300 Subject: [PATCH] add - Added full nullability support --- You can now use BassBoom in null-aware applications and libraries. --- Type: add Breaking: False Doc Required: False Backport Required: False Part: 1/1 --- BassBoom.Basolia/Devices/DeviceTools.cs | 13 +-- .../Exceptions/BasoliaException.cs | 6 +- .../Exceptions/BasoliaOutException.cs | 6 +- BassBoom.Basolia/File/FileTools.cs | 10 +- BassBoom.Basolia/File/FileType.cs | 10 +- BassBoom.Basolia/Format/AudioInfoTools.cs | 28 +++--- BassBoom.Basolia/Format/DecodeTools.cs | 12 +-- BassBoom.Basolia/Format/FormatTools.cs | 4 +- BassBoom.Basolia/Format/Id3V2Metadata.cs | 8 +- .../Playback/PlaybackPositioningTools.cs | 8 +- BassBoom.Basolia/Playback/PlaybackTools.cs | 38 +++++--- BassBoom.Basolia/Radio/IcecastServer.cs | 11 ++- BassBoom.Basolia/Radio/RadioTools.cs | 6 +- BassBoom.Basolia/Radio/ShoutcastServer.cs | 22 +++-- BassBoom.Basolia/Radio/StreamInfo.cs | 91 ++++++++++--------- BassBoom.Cli/BassBoomCli.cs | 8 +- BassBoom.Cli/CliBase/Common.cs | 2 +- BassBoom.Cli/CliBase/Equalizer.cs | 2 +- BassBoom.Cli/CliBase/Player.cs | 12 ++- BassBoom.Cli/CliBase/PlayerControls.cs | 86 +++++++++++------- BassBoom.Cli/CliBase/Radio.cs | 11 ++- BassBoom.Cli/CliBase/RadioControls.cs | 73 +++++++++------ BassBoom.Cli/Tools/CachedSongInfo.cs | 8 +- BassBoom.Native/Interop/Init/NativeInit.cs | 2 +- .../Interop/Output/NativeOutputLib.cs | 2 +- BassBoom.Native/MpgNative.cs | 21 +++-- Directory.Build.props | 1 + 27 files changed, 287 insertions(+), 214 deletions(-) diff --git a/BassBoom.Basolia/Devices/DeviceTools.cs b/BassBoom.Basolia/Devices/DeviceTools.cs index c520f6b..f96e935 100644 --- a/BassBoom.Basolia/Devices/DeviceTools.cs +++ b/BassBoom.Basolia/Devices/DeviceTools.cs @@ -34,8 +34,8 @@ namespace BassBoom.Basolia.Devices /// public static class DeviceTools { - internal static string activeDriver; - internal static string activeDevice; + internal static string? activeDriver; + internal static string? activeDevice; /// /// Gets a read only dictionary that lists all the drivers @@ -55,7 +55,7 @@ public static ReadOnlyDictionary GetDrivers() { // Query the drivers var handle = MpgNative._out123Handle; - var @delegate = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_drivers)); + 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) throw new BasoliaException("Can't query the drivers", mpg123_errors.MPG123_ERR); @@ -96,7 +96,7 @@ public static ReadOnlyDictionary GetDevices(string driver, ref s { // Query the devices var handle = MpgNative._out123Handle; - var @delegate = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_devices)); + 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) throw new BasoliaException("Can't query the devices", mpg123_errors.MPG123_ERR); @@ -132,7 +132,7 @@ public static (string driver, string device) GetCurrent() var handle = MpgNative._out123Handle; IntPtr driverPtr = IntPtr.Zero; IntPtr devicePtr = IntPtr.Zero; - var @delegate = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_driver_info)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_driver_info)); int devicesStatus = @delegate.Invoke(handle, ref driverPtr, ref devicePtr); if (devicesStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't query the devices", mpg123_errors.MPG123_ERR); @@ -147,7 +147,7 @@ public static (string driver, string device) GetCurrent() /// /// Current cached device and driver /// - public static (string driver, string device) GetCurrentCached() + public static (string? driver, string? device) GetCurrentCached() { InitBasolia.CheckInited(); return (activeDriver, activeDevice); @@ -174,6 +174,7 @@ public static void SetActiveDriver(string driver) /// public static void SetActiveDevice(string driver, string device) { + activeDevice = ""; var deviceList = GetDevices(driver, ref activeDevice); if (string.IsNullOrEmpty(device)) return; diff --git a/BassBoom.Basolia/Exceptions/BasoliaException.cs b/BassBoom.Basolia/Exceptions/BasoliaException.cs index 45475ad..f3c5710 100644 --- a/BassBoom.Basolia/Exceptions/BasoliaException.cs +++ b/BassBoom.Basolia/Exceptions/BasoliaException.cs @@ -35,7 +35,7 @@ public class BasoliaException : Exception /// An MPG123 error value to use. internal BasoliaException(mpg123_errors error) : base($"General Basolia error\n" + - $"MPG123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeError.mpg123_plain_strerror)).Invoke((int)error))}]") + $"MPG123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeError.mpg123_plain_strerror)).Invoke((int)error))}]") { } /// @@ -45,7 +45,7 @@ internal BasoliaException(mpg123_errors error) : /// An MPG123 error value to use. internal BasoliaException(string message, mpg123_errors error) : base($"{message}\n" + - $"MPG123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeError.mpg123_plain_strerror)).Invoke((int)error))}]") + $"MPG123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeError.mpg123_plain_strerror)).Invoke((int)error))}]") { } /// @@ -56,7 +56,7 @@ internal BasoliaException(string message, mpg123_errors error) : /// An MPG123 error value to use. internal BasoliaException(string message, Exception innerException, mpg123_errors error) : base($"{message}\n" + - $"MPG123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeError.mpg123_plain_strerror)).Invoke((int)error))}]", innerException) + $"MPG123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeError.mpg123_plain_strerror)).Invoke((int)error))}]", innerException) { } } } diff --git a/BassBoom.Basolia/Exceptions/BasoliaOutException.cs b/BassBoom.Basolia/Exceptions/BasoliaOutException.cs index 258dffc..9c9f7c8 100644 --- a/BassBoom.Basolia/Exceptions/BasoliaOutException.cs +++ b/BassBoom.Basolia/Exceptions/BasoliaOutException.cs @@ -35,7 +35,7 @@ public class BasoliaOutException : Exception /// An OUT123 error value to use. internal BasoliaOutException(out123_error error) : base($"General Basolia output system error\n" + - $"OUT123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_plain_strerror)).Invoke((int)error))}]") + $"OUT123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_plain_strerror)).Invoke((int)error))}]") { } /// @@ -45,7 +45,7 @@ internal BasoliaOutException(out123_error error) : /// An OUT123 error value to use. internal BasoliaOutException(string message, out123_error error) : base($"{message}\n" + - $"OUT123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_plain_strerror)).Invoke((int)error))}]") + $"OUT123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_plain_strerror)).Invoke((int)error))}]") { } /// @@ -56,7 +56,7 @@ internal BasoliaOutException(string message, out123_error error) : /// An OUT123 error value to use. internal BasoliaOutException(string message, Exception innerException, out123_error error) : base($"{message}\n" + - $"OUT123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_plain_strerror)).Invoke((int)error))}]", innerException) + $"OUT123 returned the following error: [{error} - {Marshal.PtrToStringAnsi(MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_plain_strerror)).Invoke((int)error))}]", innerException) { } } } diff --git a/BassBoom.Basolia/File/FileTools.cs b/BassBoom.Basolia/File/FileTools.cs index 1c9042a..eabb6c5 100644 --- a/BassBoom.Basolia/File/FileTools.cs +++ b/BassBoom.Basolia/File/FileTools.cs @@ -37,7 +37,7 @@ public static class FileTools { private static bool isOpened = false; private static bool isRadioStation = false; - private static FileType currentFile; + private static FileType? currentFile; private static readonly string[] supportedExts = [ ".mp3", @@ -68,7 +68,7 @@ public static class FileTools /// /// Current file /// - public static FileType CurrentFile => + public static FileType? CurrentFile => currentFile; /// @@ -95,7 +95,7 @@ public static void OpenFile(string path) { // Open the file var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeInput.mpg123_open)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInput.mpg123_open)); int openStatus = @delegate.Invoke(handle, path); if (openStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't open file", mpg123_errors.MPG123_ERR); @@ -146,7 +146,7 @@ public static async Task OpenUrlAsync(string path) { // Open the radio station var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeInput.mpg123_open_feed)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInput.mpg123_open_feed)); int openStatus = @delegate.Invoke(handle); if (openStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't open radio station", mpg123_errors.MPG123_ERR); @@ -179,7 +179,7 @@ public static void CloseFile() { // Close the file var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeInput.mpg123_close)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInput.mpg123_close)); int closeStatus = @delegate.Invoke(handle); if (closeStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't close file", mpg123_errors.MPG123_ERR); diff --git a/BassBoom.Basolia/File/FileType.cs b/BassBoom.Basolia/File/FileType.cs index e8c0d17..19a3131 100644 --- a/BassBoom.Basolia/File/FileType.cs +++ b/BassBoom.Basolia/File/FileType.cs @@ -30,8 +30,8 @@ public class FileType { private bool isLink; private string path; - private Stream stream; - private HttpResponseHeaders headers; + private Stream? stream; + private HttpResponseHeaders? headers; private string stationName; /// @@ -49,13 +49,13 @@ public class FileType /// /// Radio station stream /// - public Stream Stream => + public Stream? Stream => stream; /// /// Radio station ICY headers /// - public HttpResponseHeaders Headers => + public HttpResponseHeaders? Headers => headers; /// @@ -64,7 +64,7 @@ public class FileType public string StationName => stationName; - internal FileType(bool isLink, string path, Stream stream, HttpResponseHeaders headers, string stationName) + internal FileType(bool isLink, string path, Stream? stream, HttpResponseHeaders? headers, string stationName) { this.isLink = isLink; this.path = path ?? throw new ArgumentNullException(nameof(path)); diff --git a/BassBoom.Basolia/Format/AudioInfoTools.cs b/BassBoom.Basolia/Format/AudioInfoTools.cs index 847694f..d107b44 100644 --- a/BassBoom.Basolia/Format/AudioInfoTools.cs +++ b/BassBoom.Basolia/Format/AudioInfoTools.cs @@ -71,7 +71,7 @@ public static int GetDuration(bool scan) lock (PlaybackPositioningTools.PositionLock) { // We need to scan the file to get accurate duration - var delegate2 = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_scan)); + var delegate2 = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_scan)); int scanStatus = delegate2.Invoke(handle); if (scanStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't scan file for length information", mpg123_errors.MPG123_ERR); @@ -79,7 +79,7 @@ public static int GetDuration(bool scan) } // Get the actual length - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_length)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_length)); length = @delegate.Invoke(handle); if (length == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't determine the length of the file", mpg123_errors.MPG123_ERR); @@ -151,7 +151,7 @@ public static int GetFrameSize() var outHandle = MpgNative._out123Handle; // Get the output format to get the frame size - var @delegate = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_getformat)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_getformat)); int getStatus = @delegate.Invoke(outHandle, null, null, null, out frameSize); if (getStatus != (int)out123_error.OUT123_OK) throw new BasoliaOutException($"Can't get the output.", (out123_error)getStatus); @@ -179,7 +179,7 @@ public static int GetFrameLength() var handle = MpgNative._mpg123Handle; // Get the frame length - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_framelength)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_framelength)); getStatus = @delegate.Invoke(handle); if (getStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException($"Can't get the frame length.", mpg123_errors.MPG123_ERR); @@ -207,7 +207,7 @@ public static int GetSamplesPerFrame() var handle = MpgNative._mpg123Handle; // Get the samples per frame - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_spf)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_spf)); getStatus = @delegate.Invoke(handle); if (getStatus < 0) throw new BasoliaException($"Can't get the samples per frame.", mpg123_errors.MPG123_ERR); @@ -235,7 +235,7 @@ public static int GetBufferSize() var handle = MpgNative._mpg123Handle; // Now, buffer the entire music file and create an empty array based on its size - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeLowIo.mpg123_outblock)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeLowIo.mpg123_outblock)); bufferSize = @delegate.Invoke(handle); Debug.WriteLine($"Buffer size is {bufferSize}"); } @@ -269,14 +269,14 @@ public static void GetId3Metadata(out Id3V1Metadata managedV1, out Id3V2Metadata // We need to scan the file to get accurate info if (!FileTools.IsRadioStation) { - var delegate2 = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_scan)); + var delegate2 = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_scan)); int scanStatus = delegate2.Invoke(handle); if (scanStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't scan file for frame information", mpg123_errors.MPG123_ERR); } // Now, get the metadata info. - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeMetadata.mpg123_id3)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeMetadata.mpg123_id3)); int getStatus = @delegate.Invoke(handle, ref v1, ref v2); if (getStatus != (int)mpg123_errors.MPG123_OK) throw new BasoliaException("Can't get metadata information", (mpg123_errors)getStatus); @@ -424,14 +424,14 @@ public static string GetIcyMetadata() // We need to scan the file to get accurate info if (!FileTools.IsRadioStation) { - var delegate2 = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_scan)); + var delegate2 = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_scan)); int scanStatus = delegate2.Invoke(handle); if (scanStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't scan file for frame information", mpg123_errors.MPG123_ERR); } // Now, get the metadata info. - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeMetadata.mpg123_icy)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeMetadata.mpg123_icy)); int getStatus = @delegate.Invoke(handle, ref icy); if (getStatus != (int)mpg123_errors.MPG123_OK) throw new BasoliaException("Can't get metadata information", (mpg123_errors)getStatus); @@ -480,14 +480,14 @@ public static FrameInfo GetFrameInfo() // We need to scan the file to get accurate info, but it only works with files if (!FileTools.IsRadioStation) { - var delegate2 = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_scan)); + var delegate2 = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_scan)); int scanStatus = delegate2.Invoke(handle); if (scanStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't scan file for frame information", mpg123_errors.MPG123_ERR); } // Now, get the frame info. - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_info)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_info)); int getStatus = @delegate.Invoke(handle, ref frameInfo); if (getStatus != (int)mpg123_errors.MPG123_OK) throw new BasoliaException("Can't get frame information", (mpg123_errors)getStatus); @@ -516,14 +516,14 @@ public static FrameInfo GetFrameInfo() // We need to scan the file to get accurate info if (!FileTools.IsRadioStation) { - var delegate2 = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_scan)); + var delegate2 = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_scan)); int scanStatus = delegate2.Invoke(handle); if (scanStatus == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't scan file for frame information", mpg123_errors.MPG123_ERR); } // Now, get the frame info. - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_info)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeStatus.mpg123_info)); int getStatus = @delegate.Invoke(handle, ref frameInfo); if (getStatus != (int)mpg123_errors.MPG123_OK) throw new BasoliaException("Can't get frame information", (mpg123_errors)getStatus); diff --git a/BassBoom.Basolia/Format/DecodeTools.cs b/BassBoom.Basolia/Format/DecodeTools.cs index 1561557..c1dbe56 100644 --- a/BassBoom.Basolia/Format/DecodeTools.cs +++ b/BassBoom.Basolia/Format/DecodeTools.cs @@ -50,7 +50,7 @@ public static string Decoder /// 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(ref int num, ref byte[]? audio, ref int bytes) { InitBasolia.CheckInited(); @@ -67,7 +67,7 @@ public static int DecodeFrame(ref int num, ref byte[] audio, ref int bytes) IntPtr numPtr, bytesPtr, audioPtr = IntPtr.Zero; numPtr = new IntPtr(num); bytesPtr = new IntPtr(bytes); - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeInput.mpg123_decode_frame)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInput.mpg123_decode_frame)); int decodeStatus = @delegate.Invoke(handle, ref numPtr, ref audioPtr, ref bytesPtr); num = numPtr.ToInt32(); bytes = bytesPtr.ToInt32(); @@ -96,8 +96,8 @@ public static string[] GetDecoders(bool onlySupported) // Try to set the equalizer value unsafe { - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeDecoder.mpg123_supported_decoders)); - var delegate2 = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeDecoder.mpg123_decoders)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeDecoder.mpg123_supported_decoders)); + var delegate2 = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeDecoder.mpg123_decoders)); IntPtr decodersPtr = onlySupported ? @delegate.Invoke() : delegate2.Invoke(); string[] decoders = ArrayVariantLength.GetStringsUnknownLength(decodersPtr); return decoders; @@ -112,7 +112,7 @@ private static string GetCurrentDecoder() unsafe { var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeDecoder.mpg123_current_decoder)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeDecoder.mpg123_current_decoder)); IntPtr decoderPtr = @delegate.Invoke(handle); return Marshal.PtrToStringAnsi(decoderPtr); } @@ -132,7 +132,7 @@ private static void SetCurrentDecoder(string decoderName) if (!supportedDecoders.Contains(decoderName)) throw new BasoliaException($"Decoder {decoderName} not supported by your device", mpg123_errors.MPG123_BAD_DECODER); var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeDecoder.mpg123_decoder)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeDecoder.mpg123_decoder)); int status = @delegate.Invoke(handle, decoderName); if (status != (int)mpg123_errors.MPG123_OK) throw new BasoliaException($"Can't set decoder to {decoderName}", (mpg123_errors)status); diff --git a/BassBoom.Basolia/Format/FormatTools.cs b/BassBoom.Basolia/Format/FormatTools.cs index 2122f8f..e460768 100644 --- a/BassBoom.Basolia/Format/FormatTools.cs +++ b/BassBoom.Basolia/Format/FormatTools.cs @@ -49,7 +49,7 @@ public static (long rate, int channels, int encoding) GetFormatInfo() var handle = MpgNative._mpg123Handle; // Get the rate, the number of channels, and encoding - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeOutput.mpg123_getformat)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeOutput.mpg123_getformat)); int length = @delegate.Invoke(handle, out fileRate, out fileChannel, out fileEncoding); if (length != (int)mpg123_errors.MPG123_OK) throw new BasoliaException("Can't determine the format of the file", mpg123_errors.MPG123_ERR); @@ -74,7 +74,7 @@ public static FormatInfo[] GetFormats() var outHandle = MpgNative._out123Handle; // Get the list of supported formats - var @delegate = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_formats)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_formats)); getStatus = @delegate.Invoke(outHandle, IntPtr.Zero, 0, 0, 0, ref fmtlist); if (getStatus == (int)out123_error.OUT123_ERR) throw new BasoliaOutException("Can't get format information", (out123_error)getStatus); diff --git a/BassBoom.Basolia/Format/Id3V2Metadata.cs b/BassBoom.Basolia/Format/Id3V2Metadata.cs index 5ab1c35..53ee78a 100644 --- a/BassBoom.Basolia/Format/Id3V2Metadata.cs +++ b/BassBoom.Basolia/Format/Id3V2Metadata.cs @@ -30,10 +30,10 @@ public class Id3V2Metadata private readonly string year = ""; private readonly string comment = ""; private readonly string genre = ""; - private readonly (string, string)[] comments; - private readonly (string, string)[] texts; - private readonly (string, string)[] extras; - private readonly (string, string)[] pictures; + private readonly (string, string)[] comments = []; + private readonly (string, string)[] texts = []; + private readonly (string, string)[] extras = []; + private readonly (string, string)[] pictures = []; /// /// Title of the song (usually the song name) diff --git a/BassBoom.Basolia/Playback/PlaybackPositioningTools.cs b/BassBoom.Basolia/Playback/PlaybackPositioningTools.cs index f6ddf02..daa9889 100644 --- a/BassBoom.Basolia/Playback/PlaybackPositioningTools.cs +++ b/BassBoom.Basolia/Playback/PlaybackPositioningTools.cs @@ -55,7 +55,7 @@ public static int GetCurrentDuration() var handle = MpgNative._mpg123Handle; // Get the length - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativePositioning.mpg123_tell)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativePositioning.mpg123_tell)); length = @delegate.Invoke(handle); if (length == (int)mpg123_errors.MPG123_ERR) throw new BasoliaException("Can't determine the current duration of the file", mpg123_errors.MPG123_ERR); @@ -105,7 +105,7 @@ public static void SeekToTheBeginning() while (PlaybackTools.bufferPlaying) Thread.Sleep(1); Drop(); - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativePositioning.mpg123_seek)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativePositioning.mpg123_seek)); int status = @delegate.Invoke(handle, 0, 0); PlaybackTools.holding = false; if (status == (int)mpg123_errors.MPG123_ERR) @@ -139,7 +139,7 @@ public static void SeekToFrame(int frame) while (PlaybackTools.bufferPlaying) Thread.Sleep(1); Drop(); - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativePositioning.mpg123_seek)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativePositioning.mpg123_seek)); int status = @delegate.Invoke(handle, frame, 0); PlaybackTools.holding = false; if (status == (int)mpg123_errors.MPG123_ERR) @@ -190,7 +190,7 @@ public static void Drop() unsafe { var outHandle = MpgNative._out123Handle; - var @delegate = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_drop)); + 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 31b2d11..53b3e36 100644 --- a/BassBoom.Basolia/Playback/PlaybackTools.cs +++ b/BassBoom.Basolia/Playback/PlaybackTools.cs @@ -102,26 +102,26 @@ public static void Play() // First, get formats and reset them var (rate, channels, encoding) = FormatTools.GetFormatInfo(); - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeOutput.mpg123_format_none)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeOutput.mpg123_format_none)); int resetStatus = @delegate.Invoke(handle); if (resetStatus != (int)mpg123_errors.MPG123_OK) throw new BasoliaException($"Can't reset output encoding", (mpg123_errors)resetStatus); // Set the format - var delegate2 = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeOutput.mpg123_format)); + var delegate2 = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeOutput.mpg123_format)); int formatStatus = delegate2.Invoke(handle, rate, channels, encoding); if (formatStatus != (int)mpg123_errors.MPG123_OK) throw new BasoliaException($"Can't set output encoding", (mpg123_errors)formatStatus); Debug.WriteLine($"Format {rate}, {channels}, {encoding}"); // Try to open output to device - var delegate3 = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_open)); + var delegate3 = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_open)); int openStatus = delegate3.Invoke(outHandle, DeviceTools.activeDriver, DeviceTools.activeDevice); if (openStatus != (int)out123_error.OUT123_OK) throw new BasoliaOutException($"Can't open output to device {DeviceTools.activeDevice} on driver {DeviceTools.activeDriver}", (out123_error)openStatus); // Start the output - var delegate4 = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_start)); + var delegate4 = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_start)); int startStatus = delegate4.Invoke(outHandle, rate, channels, encoding); if (startStatus != (int)out123_error.OUT123_OK) throw new BasoliaOutException($"Can't start the output.", (out123_error)startStatus); @@ -135,7 +135,7 @@ public static void Play() { int num = 0; int audioBytes = 0; - byte[] audio = null; + byte[]? audio = null; // First, let Basolia "hold on" until hold is released while (holding) @@ -217,7 +217,7 @@ public static void SetVolume(double volume) unsafe { var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeVolume.mpg123_volume)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_volume)); int status = @delegate.Invoke(handle, volume); if (status != (int)out123_error.OUT123_OK) throw new BasoliaOutException($"Can't set volume to {volume}", (out123_error)status); @@ -241,7 +241,7 @@ public static (double baseLinear, double actualLinear, double decibelsRva) GetVo unsafe { var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeVolume.mpg123_getvolume)); + 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) throw new BasoliaOutException($"Can't get volume (base, really, and decibels)", (out123_error)status); @@ -266,7 +266,7 @@ public static void SetEqualizer(PlaybackChannels channels, int bandIdx, double v unsafe { var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeVolume.mpg123_eq)); + 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) throw new BasoliaException($"Can't set equalizer band {bandIdx + 1}/32 to {value} under {channels}", (mpg123_errors)status); @@ -289,7 +289,7 @@ public static void SetEqualizerRange(PlaybackChannels channels, int bandIdxStart unsafe { var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeVolume.mpg123_eq_bands)); + 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) throw new BasoliaException($"Can't set equalizer bands {bandIdxStart + 1}/32 -> {bandIdxEnd + 1}/32 to {value} under {channels}", (mpg123_errors)status); @@ -310,7 +310,7 @@ public static double GetEqualizer(PlaybackChannels channels, int bandIdx) unsafe { var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeVolume.mpg123_geteq)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_geteq)); double eq = @delegate.Invoke(handle, (mpg123_channels)channels, bandIdx); return eq; } @@ -328,7 +328,7 @@ public static void ResetEqualizer() unsafe { var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeVolume.mpg123_reset_eq)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeVolume.mpg123_reset_eq)); int status = @delegate.Invoke(handle); if (status != (int)mpg123_errors.MPG123_OK) throw new BasoliaException("Can't reset equalizer bands to their initial values!", (mpg123_errors)status); @@ -351,7 +351,7 @@ public static (long, double) GetNativeState(PlaybackStateType state) long stateInt = 0; double stateDouble = 0; var handle = MpgNative._mpg123Handle; - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeStatus.mpg123_getstate)); + 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) throw new BasoliaException($"Can't get native state of {state}!", (mpg123_errors)status); @@ -363,6 +363,12 @@ internal static void FeedRadio() { if (!FileTools.IsOpened || !FileTools.IsRadioStation) return; + if (FileTools.CurrentFile is null) + return; + if (FileTools.CurrentFile.Headers is null) + return; + if (FileTools.CurrentFile.Stream is null) + return; unsafe { @@ -397,21 +403,23 @@ internal static void FeedRadio() // Copy the data to MPG123 IntPtr data = Marshal.AllocHGlobal(buffer.Length); Marshal.Copy(buffer, 0, data, buffer.Length); - var @delegate = MpgNative.libManagerMpg.GetNativeMethodDelegate(nameof(NativeInput.mpg123_feed)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerMpg, nameof(NativeInput.mpg123_feed)); int feedResult = @delegate.Invoke(handle, data, buffer.Length); if (feedResult != (int)mpg123_errors.MPG123_OK) throw new BasoliaException("Can't feed.", mpg123_errors.MPG123_ERR); } } - internal static int PlayBuffer(byte[] buffer) + internal static int PlayBuffer(byte[]? buffer) { + if (buffer is null) + return 0; unsafe { var outHandle = MpgNative._out123Handle; IntPtr bufferPtr = Marshal.AllocHGlobal(Marshal.SizeOf() * buffer.Length); Marshal.Copy(buffer, 0, bufferPtr, buffer.Length); - var @delegate = MpgNative.libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_play)); + var @delegate = MpgNative.GetDelegate(MpgNative.libManagerOut, nameof(NativeOutputLib.out123_play)); int size = @delegate.Invoke(outHandle, bufferPtr, buffer.Length); Marshal.FreeHGlobal(bufferPtr); return size; diff --git a/BassBoom.Basolia/Radio/IcecastServer.cs b/BassBoom.Basolia/Radio/IcecastServer.cs index b3bee49..aa0ac2e 100644 --- a/BassBoom.Basolia/Radio/IcecastServer.cs +++ b/BassBoom.Basolia/Radio/IcecastServer.cs @@ -39,7 +39,7 @@ public class IcecastServer : IRadioServer private int currentListeners; private int peakListeners; private readonly List streams = []; - internal JToken streamToken; + internal JToken? streamToken; internal HtmlDocument streamHtmlToken = new(); /// @@ -151,13 +151,16 @@ internal void FinalizeIcecast() { // Use all the keys in the first object except the "streams" and "version", where we'd later use the former in StreamInfo to install // all the streams into the new class instance. - var sources = (JArray)streamToken["source"]; + if (streamToken is null) + throw new BasoliaMiscException("Stream token is null."); + var sources = (JArray?)streamToken["source"] ?? + throw new BasoliaMiscException("There are no sources."); var inactive = sources.Where((token) => token["server_name"] is null); var active = sources.Except(inactive); totalStreams = sources.Count; activeStreams = active.Count(); - currentListeners = active.Max((token) => (int)token["listeners"]); - peakListeners = active.Max((token) => (int)token["listener_peak"]); + currentListeners = active.Max((token) => (int?)token["listeners"] ?? 0); + peakListeners = active.Max((token) => (int?)token["listener_peak"] ?? 0); // Now, deal with the stream settings. streams.Clear(); diff --git a/BassBoom.Basolia/Radio/RadioTools.cs b/BassBoom.Basolia/Radio/RadioTools.cs index 40461f4..a9afaf7 100644 --- a/BassBoom.Basolia/Radio/RadioTools.cs +++ b/BassBoom.Basolia/Radio/RadioTools.cs @@ -38,7 +38,7 @@ public static class RadioTools /// /// Radio station URL /// An instance of . if type can't be determined. if this radio station server uses Shoutcast, and if it uses Icecast. - public static IRadioServer GetRadioInfo(string radioUrl) => + public static IRadioServer? GetRadioInfo(string radioUrl) => Task.Run(() => GetRadioInfoAsync(radioUrl)).GetAwaiter().GetResult(); /// @@ -46,7 +46,7 @@ public static IRadioServer GetRadioInfo(string radioUrl) => /// /// Radio station URL /// An instance of . if type can't be determined. if this radio station server uses Shoutcast, and if it uses Icecast. - public static async Task GetRadioInfoAsync(string radioUrl) + public static async Task GetRadioInfoAsync(string radioUrl) { // Check to see if we provided a path if (string.IsNullOrEmpty(radioUrl)) @@ -72,7 +72,7 @@ public static async Task GetRadioInfoAsync(string radioUrl) throw new BasoliaMiscException("Can't determine radio station server type."); // Return the appropriate parsed server stats instance - IRadioServer stats = type switch + IRadioServer? stats = type switch { RadioServerType.Shoutcast => new ShoutcastServer(uri.Host, uri.Port, uri.Scheme == "https"), diff --git a/BassBoom.Basolia/Radio/ShoutcastServer.cs b/BassBoom.Basolia/Radio/ShoutcastServer.cs index f9e2e85..2866c09 100644 --- a/BassBoom.Basolia/Radio/ShoutcastServer.cs +++ b/BassBoom.Basolia/Radio/ShoutcastServer.cs @@ -42,7 +42,7 @@ public class ShoutcastServer : IRadioServer private int uniqueListeners; private int averageTime; private readonly List streams = []; - internal JToken streamToken; + internal JToken? streamToken; internal HtmlDocument streamHtmlToken = new(); /// @@ -226,17 +226,21 @@ internal void FinalizeShoutcastV2() // Shoutcast version v2.x, so use the JToken. // Use all the keys in the first object except the "streams" and "version", where we'd later use the former in StreamInfo to install // all the streams into the new class instance. - totalStreams = (int)streamToken["totalstreams"]; - activeStreams = (int)streamToken["activestreams"]; - currentListeners = (int)streamToken["currentlisteners"]; - peakListeners = (int)streamToken["peaklisteners"]; - maxListeners = (int)streamToken["maxlisteners"]; - uniqueListeners = (int)streamToken["uniquelisteners"]; - averageTime = (int)streamToken["averagetime"]; + if (streamToken is null) + throw new BasoliaMiscException("Shoutcast v2.x stream token is null"); + totalStreams = (int?)streamToken["totalstreams"] ?? 0; + activeStreams = (int?)streamToken["activestreams"] ?? 0; + currentListeners = (int?)streamToken["currentlisteners"] ?? 0; + peakListeners = (int?)streamToken["peaklisteners"] ?? 0; + maxListeners = (int?)streamToken["maxlisteners"] ?? 0; + uniqueListeners = (int?)streamToken["uniquelisteners"] ?? 0; + averageTime = (int?)streamToken["averagetime"] ?? 0; // Now, deal with the stream settings. streams.Clear(); - foreach (JToken stream in streamToken["streams"]) + var listStreams = streamToken["streams"] ?? + throw new BasoliaMiscException("There are no streams."); + foreach (JToken stream in listStreams) { StreamInfo streamInfo = new(this, stream); streams.Add(streamInfo); diff --git a/BassBoom.Basolia/Radio/StreamInfo.cs b/BassBoom.Basolia/Radio/StreamInfo.cs index f375aa1..0765143 100644 --- a/BassBoom.Basolia/Radio/StreamInfo.cs +++ b/BassBoom.Basolia/Radio/StreamInfo.cs @@ -36,23 +36,23 @@ public class StreamInfo private readonly int uniqueListeners; private readonly int averageTime; private readonly int streamId; - private readonly string streamGenre; - private readonly string streamGenre2; - private readonly string streamGenre3; - private readonly string streamGenre4; - private readonly string streamGenre5; - private readonly string streamHomepage; - private readonly string streamTitle; - private readonly string songTitle; + private readonly string streamGenre = ""; + private readonly string streamGenre2 = ""; + private readonly string streamGenre3 = ""; + private readonly string streamGenre4 = ""; + private readonly string streamGenre5 = ""; + private readonly string streamHomepage = ""; + private readonly string streamTitle = ""; + private readonly string songTitle = ""; private readonly long streamHits; private readonly int streamStatus; private readonly int backupStatus; private readonly bool streamListed; - private readonly string streamPath; + private readonly string streamPath = ""; private readonly long streamUptime; private readonly int bitRate; private readonly int sampleRate; - private readonly string mimeInfo; + private readonly string mimeInfo = ""; /// /// Stream ID starting from number one (1) @@ -160,7 +160,7 @@ public class StreamInfo /// /// Radio server class instance /// Individual stream token - internal StreamInfo(IRadioServer server, JToken stream) + internal StreamInfo(IRadioServer server, JToken? stream) { try { @@ -182,44 +182,49 @@ internal StreamInfo(IRadioServer server, JToken stream) else { // Shoutcast version v2.x, so use the JToken. - streamId = (int)stream["id"]; - currentListeners = (int)stream["currentlisteners"]; - peakListeners = (int)stream["peaklisteners"]; - maxListeners = (int)stream["maxlisteners"]; - uniqueListeners = (int)stream["uniquelisteners"]; - averageTime = (int)stream["averagetime"]; - streamGenre = (string)stream["servergenre"]; - streamGenre2 = (string)stream["servergenre2"]; - streamGenre3 = (string)stream["servergenre3"]; - streamGenre4 = (string)stream["servergenre4"]; - streamGenre5 = (string)stream["servergenre5"]; - streamHomepage = (string)stream["serverurl"]; - streamTitle = (string)stream["servertitle"]; - songTitle = (string)stream["songtitle"]; - streamHits = (int)stream["streamhits"]; - streamStatus = (int)stream["streamstatus"]; - backupStatus = (int)stream["backupstatus"]; - streamListed = (bool)stream["streamlisted"]; - streamPath = (string)stream["streampath"]; - streamUptime = (int)stream["streamuptime"]; - bitRate = (int)stream["bitrate"]; - sampleRate = (int)stream["samplerate"]; - mimeInfo = (string)stream["content"]; + if (stream is null) + throw new BasoliaMiscException("There is no Shoutcast v2.x stream."); + streamId = (int?)stream["id"] ?? 0; + currentListeners = (int?)stream["currentlisteners"] ?? 0; + peakListeners = (int?)stream["peaklisteners"] ?? 0; + maxListeners = (int?)stream["maxlisteners"] ?? 0; + uniqueListeners = (int?)stream["uniquelisteners"] ?? 0; + averageTime = (int?)stream["averagetime"] ?? 0; + streamGenre = (string?)stream["servergenre"] ?? ""; + streamGenre2 = (string?)stream["servergenre2"] ?? ""; + streamGenre3 = (string?)stream["servergenre3"] ?? ""; + streamGenre4 = (string?)stream["servergenre4"] ?? ""; + streamGenre5 = (string?)stream["servergenre5"] ?? ""; + streamHomepage = (string?)stream["serverurl"] ?? ""; + streamTitle = (string?)stream["servertitle"] ?? ""; + songTitle = (string?)stream["songtitle"] ?? ""; + streamHits = (int?)stream["streamhits"] ?? 0; + streamStatus = (int?)stream["streamstatus"] ?? 0; + backupStatus = (int?)stream["backupstatus"] ?? 0; + streamListed = (bool?)stream["streamlisted"] ?? false; + streamPath = (string?)stream["streampath"] ?? ""; + streamUptime = (int?)stream["streamuptime"] ?? 0; + bitRate = (int?)stream["bitrate"] ?? 0; + sampleRate = (int?)stream["samplerate"] ?? 0; + mimeInfo = (string?)stream["content"] ?? ""; } } else if (server is IcecastServer icecastServer) { + if (stream is null) + throw new BasoliaMiscException("There is no Icecast stream."); + // Icecast server, so use the JToken. - currentListeners = (int)stream["listeners"]; - peakListeners = (int)stream["listener_peak"]; - streamGenre = (string)stream["genre"]; - streamHomepage = (string)stream["server_url"]; - streamTitle = (string)stream["server_name"]; - songTitle = (string)stream["title"]; + currentListeners = (int?)stream["listeners"] ?? 0; + peakListeners = (int?)stream["listener_peak"] ?? 0; + streamGenre = (string?)stream["genre"] ?? ""; + streamHomepage = (string?)stream["server_url"] ?? ""; + streamTitle = (string?)stream["server_name"] ?? ""; + songTitle = (string?)stream["title"] ?? ""; streamListed = true; - streamPath = (string)stream["listenurl"]; - bitRate = (int)stream["bitrate"]; - mimeInfo = (string)stream["server_type"]; + streamPath = (string?)stream["listenurl"] ?? ""; + bitRate = (int?)stream["bitrate"] ?? 0; + mimeInfo = (string?)stream["server_type"] ?? ""; } } catch (Exception ex) diff --git a/BassBoom.Cli/BassBoomCli.cs b/BassBoom.Cli/BassBoomCli.cs index 6e0364d..084b459 100644 --- a/BassBoom.Cli/BassBoomCli.cs +++ b/BassBoom.Cli/BassBoomCli.cs @@ -33,16 +33,16 @@ namespace BassBoom.Cli { internal class BassBoomCli { - private static readonly Version version = Assembly.GetAssembly(typeof(InitBasolia)).GetName().Version; - internal static Version mpgVer; - internal static Version outVer; + private static readonly Version? version = Assembly.GetAssembly(typeof(InitBasolia))?.GetName().Version; + internal static Version? mpgVer; + internal static Version? outVer; internal static Color white = new(ConsoleColors.White); static int Main(string[] args) { try { - ConsoleMisc.SetTitle($"BassBoom CLI - Basolia v{version.ToString(3)} - Beta {version.Minor}"); + ConsoleMisc.SetTitle($"BassBoom CLI - Basolia v{version?.ToString(3)} - Beta {version?.Minor}"); // First, prompt for the music path if no arguments are provided. string[] arguments = args.Where((arg) => !arg.StartsWith("-")).ToArray(); diff --git a/BassBoom.Cli/CliBase/Common.cs b/BassBoom.Cli/CliBase/Common.cs index e101cca..097b034 100644 --- a/BassBoom.Cli/CliBase/Common.cs +++ b/BassBoom.Cli/CliBase/Common.cs @@ -49,7 +49,7 @@ internal static class Common internal static bool isRadioMode = false; internal static readonly List cachedInfos = []; - internal static CachedSongInfo CurrentCachedInfo => + internal static CachedSongInfo? CurrentCachedInfo => cachedInfos.Count > 0 ? cachedInfos[currentPos - 1] : null; internal static void RaiseVolume() diff --git a/BassBoom.Cli/CliBase/Equalizer.cs b/BassBoom.Cli/CliBase/Equalizer.cs index a8d89f8..9e4dbcc 100644 --- a/BassBoom.Cli/CliBase/Equalizer.cs +++ b/BassBoom.Cli/CliBase/Equalizer.cs @@ -145,7 +145,7 @@ private static string HandleDraw() if (Common.isRadioMode) drawn.Append(RadioControls.RenderStationName()); else - drawn.Append(PlayerControls.RenderSongName(Common.CurrentCachedInfo.MusicPath)); + drawn.Append(PlayerControls.RenderSongName(Common.CurrentCachedInfo?.MusicPath ?? "")); } else drawn.Append( diff --git a/BassBoom.Cli/CliBase/Player.cs b/BassBoom.Cli/CliBase/Player.cs index 5c29373..768212c 100644 --- a/BassBoom.Cli/CliBase/Player.cs +++ b/BassBoom.Cli/CliBase/Player.cs @@ -31,7 +31,6 @@ 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 BassBoom.Basolia.Exceptions; @@ -41,7 +40,7 @@ namespace BassBoom.Cli.CliBase { internal static class Player { - internal static Thread playerThread; + internal static Thread? playerThread; internal static int position = 0; internal static readonly List passedMusicPaths = []; @@ -353,9 +352,12 @@ private static string HandleDraw() } // Populate music file info, as necessary - if (Common.populate) - PlayerControls.PopulateMusicFileInfo(Common.CurrentCachedInfo.MusicPath); - drawn.Append(PlayerControls.RenderSongName(Common.CurrentCachedInfo.MusicPath)); + if (Common.CurrentCachedInfo is not null) + { + if (Common.populate) + PlayerControls.PopulateMusicFileInfo(Common.CurrentCachedInfo.MusicPath); + drawn.Append(PlayerControls.RenderSongName(Common.CurrentCachedInfo.MusicPath)); + } // Now, print the list of songs. var choices = new List(); diff --git a/BassBoom.Cli/CliBase/PlayerControls.cs b/BassBoom.Cli/CliBase/PlayerControls.cs index aaaa355..c7f6497 100644 --- a/BassBoom.Cli/CliBase/PlayerControls.cs +++ b/BassBoom.Cli/CliBase/PlayerControls.cs @@ -47,6 +47,8 @@ internal static void SeekForward() // In case we have no songs in the playlist... if (Common.cachedInfos.Count == 0) return; + if (Common.CurrentCachedInfo is null) + return; Player.position += (int)(Common.CurrentCachedInfo.FormatInfo.rate * seekRate); if (Player.position > Common.CurrentCachedInfo.Duration) @@ -59,6 +61,8 @@ internal static void SeekBackward() // In case we have no songs in the playlist... if (Common.cachedInfos.Count == 0) return; + if (Common.CurrentCachedInfo is null) + return; Player.position -= (int)(Common.CurrentCachedInfo.FormatInfo.rate * seekRate); if (Player.position < 0) @@ -81,6 +85,8 @@ 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 is null) + return; if (Common.CurrentCachedInfo.LyricInstance is null) return; @@ -96,6 +102,8 @@ 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 is null) + return; if (Common.CurrentCachedInfo.LyricInstance is null) return; @@ -111,6 +119,8 @@ 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 is null) + return; if (Common.CurrentCachedInfo.LyricInstance is null) return; @@ -129,6 +139,8 @@ 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 is null) + return; if (Common.CurrentCachedInfo.LyricInstance is null) return; @@ -146,6 +158,8 @@ internal static void SeekTo(TimeSpan target) // In case we have no songs in the playlist... if (Common.cachedInfos.Count == 0) return; + if (Common.CurrentCachedInfo is null) + return; Player.position = (int)(target.TotalSeconds * Common.CurrentCachedInfo.FormatInfo.rate); if (Player.position > Common.CurrentCachedInfo.Duration) @@ -158,6 +172,8 @@ internal static void Play() // In case we have no songs in the playlist... if (Common.cachedInfos.Count == 0) return; + if (Player.playerThread is null) + return; if (PlaybackTools.State == PlaybackState.Stopped) // There could be a chance that the music has fully stopped without any user interaction. @@ -209,14 +225,14 @@ internal static void PreviousSong() internal static void PromptForAddSong() { string path = InfoBoxInputColor.WriteInfoBoxInput("Enter a path to the music file"); - ScreenTools.CurrentScreen.RequireRefresh(); + ScreenTools.CurrentScreen?.RequireRefresh(); if (File.Exists(path)) { int currentPos = Player.position; Common.populate = true; PopulateMusicFileInfo(path); Common.populate = true; - PopulateMusicFileInfo(Common.CurrentCachedInfo.MusicPath); + PopulateMusicFileInfo(Common.CurrentCachedInfo?.MusicPath ?? ""); PlaybackPositioningTools.SeekToFrame(currentPos); } else @@ -226,7 +242,7 @@ internal static void PromptForAddSong() internal static void PromptForAddDirectory() { string path = InfoBoxInputColor.WriteInfoBoxInput("Enter a path to the music library directory"); - ScreenTools.CurrentScreen.RequireRefresh(); + ScreenTools.CurrentScreen?.RequireRefresh(); if (Directory.Exists(path)) { int currentPos = Player.position; @@ -239,7 +255,7 @@ internal static void PromptForAddDirectory() PopulateMusicFileInfo(musicFile); } Common.populate = true; - PopulateMusicFileInfo(Common.CurrentCachedInfo.MusicPath); + PopulateMusicFileInfo(Common.CurrentCachedInfo?.MusicPath ?? ""); PlaybackPositioningTools.SeekToFrame(currentPos); } } @@ -256,7 +272,7 @@ internal static void PopulateMusicFileInfo(string musicPath) Common.Switch(musicPath); if (!Common.cachedInfos.Any((csi) => csi.MusicPath == musicPath)) { - ScreenTools.CurrentScreen.RequireRefresh(); + ScreenTools.CurrentScreen?.RequireRefresh(); InfoBoxColor.WriteInfoBox($"Loading BassBoom to open {musicPath}...", false); var total = AudioInfoTools.GetDuration(true); var formatInfo = FormatTools.GetFormatInfo(); @@ -283,20 +299,22 @@ internal static string RenderSongName(string musicPath) internal static (string musicName, string musicArtist, string musicGenre) GetMusicNameArtistGenre(string musicPath) { + if (Common.CurrentCachedInfo is null) + return ("", "", ""); var metadatav2 = Common.CurrentCachedInfo.MetadataV2; var metadatav1 = Common.CurrentCachedInfo.MetadataV1; string musicName = - !string.IsNullOrEmpty(metadatav2.Title) ? metadatav2.Title : - !string.IsNullOrEmpty(metadatav1.Title) ? metadatav1.Title : - Path.GetFileNameWithoutExtension(musicPath); + (!string.IsNullOrEmpty(metadatav2?.Title) ? metadatav2?.Title : + !string.IsNullOrEmpty(metadatav1?.Title) ? metadatav1?.Title : + Path.GetFileNameWithoutExtension(musicPath)) ?? ""; string musicArtist = - !string.IsNullOrEmpty(metadatav2.Artist) ? metadatav2.Artist : - !string.IsNullOrEmpty(metadatav1.Artist) ? metadatav1.Artist : - "Unknown Artist"; + (!string.IsNullOrEmpty(metadatav2?.Artist) ? metadatav2?.Artist : + !string.IsNullOrEmpty(metadatav1?.Artist) ? metadatav1?.Artist : + "Unknown Artist") ?? ""; string musicGenre = - !string.IsNullOrEmpty(metadatav2.Genre) ? metadatav2.Genre : - metadatav1.GenreIndex >= 0 ? $"{metadatav1.Genre} [{metadatav1.GenreIndex}]" : - "Unknown Genre"; + (!string.IsNullOrEmpty(metadatav2?.Genre) ? metadatav2?.Genre : + metadatav1?.GenreIndex >= 0 ? $"{metadatav1.Genre} [{metadatav1.GenreIndex}]" : + "Unknown Genre") ?? ""; return (musicName, musicArtist, musicGenre); } @@ -307,21 +325,21 @@ internal static (string musicName, string musicArtist, string musicGenre) GetMus var metadatav1 = cachedInfo.MetadataV1; var path = cachedInfo.MusicPath; string musicName = - !string.IsNullOrEmpty(metadatav2.Title) ? metadatav2.Title : - !string.IsNullOrEmpty(metadatav1.Title) ? metadatav1.Title : - Path.GetFileNameWithoutExtension(path); + (!string.IsNullOrEmpty(metadatav2?.Title) ? metadatav2?.Title : + !string.IsNullOrEmpty(metadatav1?.Title) ? metadatav1?.Title : + Path.GetFileNameWithoutExtension(path)) ?? ""; string musicArtist = - !string.IsNullOrEmpty(metadatav2.Artist) ? metadatav2.Artist : - !string.IsNullOrEmpty(metadatav1.Artist) ? metadatav1.Artist : - "Unknown Artist"; + (!string.IsNullOrEmpty(metadatav2?.Artist) ? metadatav2?.Artist : + !string.IsNullOrEmpty(metadatav1?.Artist) ? metadatav1?.Artist : + "Unknown Artist") ?? ""; string musicGenre = - !string.IsNullOrEmpty(metadatav2.Genre) ? metadatav2.Genre : - metadatav1.GenreIndex >= 0 ? $"{metadatav1.Genre} [{metadatav1.GenreIndex}]" : - "Unknown Genre"; + (!string.IsNullOrEmpty(metadatav2?.Genre) ? metadatav2?.Genre : + metadatav1?.GenreIndex >= 0 ? $"{metadatav1.Genre} [{metadatav1.GenreIndex}]" : + "Unknown Genre") ?? ""; return (musicName, musicArtist, musicGenre); } - internal static Lyric OpenLyrics(string musicPath) + internal static Lyric? OpenLyrics(string musicPath) { string lyricsPath = Path.GetDirectoryName(musicPath) + "/" + Path.GetFileNameWithoutExtension(musicPath) + ".lrc"; try @@ -344,6 +362,8 @@ internal static void RemoveCurrentSong() // In case we have no songs in the playlist... if (Common.cachedInfos.Count == 0) return; + if (Common.CurrentCachedInfo is null) + return; Common.cachedInfos.RemoveAt(Common.currentPos - 1); if (Common.cachedInfos.Count > 0) @@ -371,6 +391,8 @@ internal static void PromptSeek() // In case we have no songs in the playlist... if (Common.cachedInfos.Count == 0) return; + if (Common.CurrentCachedInfo is null) + return; // Prompt the user to set the current position to the specified time string time = InfoBoxInputColor.WriteInfoBoxInput("Write the target position in this format: HH:MM:SS"); @@ -385,23 +407,25 @@ internal static void PromptSeek() internal static void ShowSongInfo() { + if (Common.CurrentCachedInfo is null) + return; var textsBuilder = new StringBuilder(); var idv2 = Common.CurrentCachedInfo.MetadataV2; var idv1 = Common.CurrentCachedInfo.MetadataV1; - foreach (var text in idv2.Texts) + foreach (var text in idv2?.Texts ?? []) textsBuilder.AppendLine($"T - {text.Item1}: {text.Item2}"); - foreach (var text in idv2.Extras) + foreach (var text in idv2?.Extras ?? []) textsBuilder.AppendLine($"E - {text.Item1}: {text.Item2}"); InfoBoxColor.WriteInfoBox( $$""" Song info ========= - Artist: {{(!string.IsNullOrEmpty(idv2.Artist) ? idv2.Artist : !string.IsNullOrEmpty(idv1.Artist) ? idv1.Artist : "Unknown")}} - Title: {{(!string.IsNullOrEmpty(idv2.Title) ? idv2.Title : !string.IsNullOrEmpty(idv1.Title) ? idv1.Title : "")}} - Album: {{(!string.IsNullOrEmpty(idv2.Album) ? idv2.Album : !string.IsNullOrEmpty(idv1.Album) ? idv1.Album : "")}} - Genre: {{(!string.IsNullOrEmpty(idv2.Genre) ? idv2.Genre : !string.IsNullOrEmpty(idv1.Genre.ToString()) ? idv1.Genre.ToString() : "")}} - Comment: {{(!string.IsNullOrEmpty(idv2.Comment) ? idv2.Comment : !string.IsNullOrEmpty(idv1.Comment) ? idv1.Comment : "")}} + Artist: {{(!string.IsNullOrEmpty(idv2?.Artist) ? idv2?.Artist : !string.IsNullOrEmpty(idv1?.Artist) ? idv1?.Artist : "Unknown")}} + Title: {{(!string.IsNullOrEmpty(idv2?.Title) ? idv2?.Title : !string.IsNullOrEmpty(idv1?.Title) ? idv1?.Title : "")}} + Album: {{(!string.IsNullOrEmpty(idv2?.Album) ? idv2?.Album : !string.IsNullOrEmpty(idv1?.Album) ? idv1?.Album : "")}} + Genre: {{(!string.IsNullOrEmpty(idv2?.Genre) ? idv2?.Genre : !string.IsNullOrEmpty(idv1?.Genre.ToString()) ? idv1?.Genre.ToString() : "")}} + Comment: {{(!string.IsNullOrEmpty(idv2?.Comment) ? idv2?.Comment : !string.IsNullOrEmpty(idv1?.Comment) ? idv1?.Comment : "")}} Duration: {{Common.CurrentCachedInfo.DurationSpan}} Lyrics: {{(Common.CurrentCachedInfo.LyricInstance is not null ? $"{Common.CurrentCachedInfo.LyricInstance.Lines.Count} lines" : "No lyrics")}} diff --git a/BassBoom.Cli/CliBase/Radio.cs b/BassBoom.Cli/CliBase/Radio.cs index 756eb92..9845195 100644 --- a/BassBoom.Cli/CliBase/Radio.cs +++ b/BassBoom.Cli/CliBase/Radio.cs @@ -41,7 +41,7 @@ namespace BassBoom.Cli.CliBase { internal static class Radio { - internal static Thread playerThread; + internal static Thread? playerThread; public static void RadioLoop() { @@ -280,9 +280,12 @@ private static string HandleDraw() } // Populate music file info, as necessary - if (Common.populate) - RadioControls.PopulateRadioStationInfo(Common.CurrentCachedInfo.MusicPath); - drawn.Append(RadioControls.RenderStationName()); + if (Common.CurrentCachedInfo is not null) + { + if (Common.populate) + RadioControls.PopulateRadioStationInfo(Common.CurrentCachedInfo.MusicPath); + drawn.Append(RadioControls.RenderStationName()); + } // Now, print the list of stations. var choices = new List(); diff --git a/BassBoom.Cli/CliBase/RadioControls.cs b/BassBoom.Cli/CliBase/RadioControls.cs index c01320f..ce0895c 100644 --- a/BassBoom.Cli/CliBase/RadioControls.cs +++ b/BassBoom.Cli/CliBase/RadioControls.cs @@ -42,6 +42,8 @@ internal static void Play() // In case we have no stations in the playlist... if (Common.cachedInfos.Count == 0) return; + if (Radio.playerThread is null) + 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. @@ -96,11 +98,11 @@ internal static void PreviousStation() internal static void PromptForAddStation() { string path = InfoBoxInputColor.WriteInfoBoxInput("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(); + ScreenTools.CurrentScreen?.RequireRefresh(); Common.populate = true; PopulateRadioStationInfo(path); Common.populate = true; - PopulateRadioStationInfo(Common.CurrentCachedInfo.MusicPath); + PopulateRadioStationInfo(Common.CurrentCachedInfo?.MusicPath ?? ""); } internal static void PopulateRadioStationInfo(string musicPath) @@ -117,7 +119,7 @@ internal static void PopulateRadioStationInfo(string musicPath) var frameInfo = AudioInfoTools.GetFrameInfo(); // Try to open the lyrics - var instance = new CachedSongInfo(musicPath, null, null, -1, formatInfo, frameInfo, null, FileTools.CurrentFile.StationName, true); + var instance = new CachedSongInfo(musicPath, null, null, -1, formatInfo, frameInfo, null, FileTools.CurrentFile?.StationName ?? "", true); Common.cachedInfos.Add(instance); } } @@ -138,6 +140,8 @@ internal static void RemoveCurrentStation() // In case we have no stations in the playlist... if (Common.cachedInfos.Count == 0) return; + if (Common.CurrentCachedInfo is null) + return; Common.cachedInfos.RemoveAt(Common.currentPos - 1); if (Common.cachedInfos.Count > 0) @@ -162,6 +166,8 @@ internal static void RemoveAllStations() internal static void ShowStationInfo() { + if (Common.CurrentCachedInfo is null) + return; InfoBoxColor.WriteInfoBox( $$""" Station info @@ -202,38 +208,45 @@ Native State internal static void ShowExtendedStationInfo() { + if (Common.CurrentCachedInfo is null) + return; var station = RadioTools.GetRadioInfo(Common.CurrentCachedInfo.MusicPath); var streamBuilder = new StringBuilder(); - foreach (var stream in station.Streams) + if (station is not null) { - streamBuilder.AppendLine($"Name: {stream.StreamTitle}"); - streamBuilder.AppendLine($"Home page: {stream.StreamHomepage}"); - streamBuilder.AppendLine($"Genre: {stream.StreamGenre}"); - streamBuilder.AppendLine($"Now playing: {stream.SongTitle}"); - streamBuilder.AppendLine($"Stream path: {stream.StreamPath}"); - streamBuilder.AppendLine($"Listeners: {stream.CurrentListeners} with {stream.PeakListeners} at peak"); - streamBuilder.AppendLine($"Bit rate: {stream.BitRate} kbps"); - streamBuilder.AppendLine($"Media type: {stream.MimeInfo}"); - streamBuilder.AppendLine("==============================="); - } - InfoBoxColor.WriteInfoBox( - $$""" - Radio server info - ================= - - Radio station URL: {{station.ServerHostFull}} - Radio station uses HTTPS: {{station.ServerHttps}} - Radio station server type: {{station.ServerType}} - Radio station streams: {{station.TotalStreams}} with {{station.ActiveStreams}} active - Radio station listeners: {{station.CurrentListeners}} with {{station.PeakListeners}} at peak + foreach (var stream in station.Streams) + { + streamBuilder.AppendLine($"Name: {stream.StreamTitle}"); + streamBuilder.AppendLine($"Home page: {stream.StreamHomepage}"); + streamBuilder.AppendLine($"Genre: {stream.StreamGenre}"); + streamBuilder.AppendLine($"Now playing: {stream.SongTitle}"); + streamBuilder.AppendLine($"Stream path: {stream.StreamPath}"); + streamBuilder.AppendLine($"Listeners: {stream.CurrentListeners} with {stream.PeakListeners} at peak"); + streamBuilder.AppendLine($"Bit rate: {stream.BitRate} kbps"); + streamBuilder.AppendLine($"Media type: {stream.MimeInfo}"); + streamBuilder.AppendLine("==============================="); + } + InfoBoxColor.WriteInfoBox( + $$""" + Radio server info + ================= + + Radio station URL: {{station.ServerHostFull}} + Radio station uses HTTPS: {{station.ServerHttps}} + Radio station server type: {{station.ServerType}} + Radio station streams: {{station.TotalStreams}} with {{station.ActiveStreams}} active + Radio station listeners: {{station.CurrentListeners}} with {{station.PeakListeners}} at peak - Stream info - =========== + Stream info + =========== - =============================== - {{streamBuilder}} - """ - ); + =============================== + {{streamBuilder}} + """ + ); + } + else + InfoBoxColor.WriteInfoBox($"Unable to get extended radio station info for {Common.CurrentCachedInfo.MusicPath}"); } } } diff --git a/BassBoom.Cli/Tools/CachedSongInfo.cs b/BassBoom.Cli/Tools/CachedSongInfo.cs index b599606..05ee1f4 100644 --- a/BassBoom.Cli/Tools/CachedSongInfo.cs +++ b/BassBoom.Cli/Tools/CachedSongInfo.cs @@ -35,11 +35,11 @@ internal class CachedSongInfo /// /// ID3v1 metadata /// - public Id3V1Metadata MetadataV1 { get; private set; } + public Id3V1Metadata? MetadataV1 { get; private set; } /// /// ID3v2 metadata /// - public Id3V2Metadata MetadataV2 { get; private set; } + public Id3V2Metadata? MetadataV2 { get; private set; } /// /// Radio station name /// @@ -64,7 +64,7 @@ internal class CachedSongInfo /// /// An instance of the music lyrics (if any) /// - public Lyric LyricInstance { get; private set; } + public Lyric? LyricInstance { get; private set; } /// /// Checks to see if this cached song info instance is a radio station or not /// @@ -86,7 +86,7 @@ internal class CachedSongInfo /// 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) + 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; diff --git a/BassBoom.Native/Interop/Init/NativeInit.cs b/BassBoom.Native/Interop/Init/NativeInit.cs index a01f097..3e71f2c 100644 --- a/BassBoom.Native/Interop/Init/NativeInit.cs +++ b/BassBoom.Native/Interop/Init/NativeInit.cs @@ -133,7 +133,7 @@ internal static unsafe class NativeInit /// /// MPG123_EXPORT mpg123_handle* mpg123_new (const char* decoder, int* error) /// - internal delegate mpg123_handle* mpg123_new([MarshalAs(UnmanagedType.LPStr)] string decoder, int* error); + internal delegate mpg123_handle* mpg123_new([MarshalAs(UnmanagedType.LPStr)] string? decoder, int* error); /// /// MPG123_EXPORT void mpg123_delete(mpg123_handle* mh) diff --git a/BassBoom.Native/Interop/Output/NativeOutputLib.cs b/BassBoom.Native/Interop/Output/NativeOutputLib.cs index da97aa4..f9b80d7 100644 --- a/BassBoom.Native/Interop/Output/NativeOutputLib.cs +++ b/BassBoom.Native/Interop/Output/NativeOutputLib.cs @@ -175,7 +175,7 @@ internal delegate int out123_devices(out123_handle* ao, /// /// MPG123_EXPORT int out123_open(out123_handle *ao, const char* driver, const char* device); /// - internal delegate int out123_open(out123_handle* ao, string driver, string device); + internal delegate int out123_open(out123_handle* ao, string? driver, string? device); /// /// MPG123_EXPORT int out123_driver_info(out123_handle *ao, char **driver, char **device); diff --git a/BassBoom.Native/MpgNative.cs b/BassBoom.Native/MpgNative.cs index 5a6aaba..d5dbc81 100644 --- a/BassBoom.Native/MpgNative.cs +++ b/BassBoom.Native/MpgNative.cs @@ -41,8 +41,8 @@ internal static unsafe class MpgNative internal static mpg123_handle* _mpg123Handle; internal static out123_handle* _out123Handle; - internal static LibraryManager libManagerMpg; - internal static LibraryManager libManagerOut; + internal static LibraryManager? libManagerMpg; + internal static LibraryManager? libManagerOut; internal const string LibcName = "libc"; internal const string LibraryName = "mpg123"; @@ -68,7 +68,7 @@ internal static Version MpgLibVersion get { uint major = 0, minor = 0, patch = 0; - var @delegate = libManagerMpg.GetNativeMethodDelegate(nameof(NativeInit.mpg123_distversion)); + var @delegate = GetDelegate(libManagerMpg, nameof(NativeInit.mpg123_distversion)); var versionHandle = @delegate.Invoke(ref major, ref minor, ref patch); string version = Marshal.PtrToStringAnsi(versionHandle); Debug.WriteLine($"mpg123 version: {version}"); @@ -84,7 +84,7 @@ internal static Version OutLibVersion get { uint major = 0, minor = 0, patch = 0; - var @delegate = libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_distversion)); + var @delegate = GetDelegate(libManagerOut, nameof(NativeOutputLib.out123_distversion)); var versionHandle = @delegate.Invoke(ref major, ref minor, ref patch); string version = Marshal.PtrToStringAnsi(versionHandle); Debug.WriteLine($"out123 version: {version}"); @@ -139,7 +139,7 @@ internal static void InitializeLibrary(string libPath, string libPathOut) // Verify that we've actually loaded the library! try { - var @delegate = libManagerMpg.GetNativeMethodDelegate(nameof(NativeInit.mpg123_new)); + var @delegate = GetDelegate(libManagerMpg, nameof(NativeInit.mpg123_new)); var handle = @delegate.Invoke(null, null); Debug.WriteLine($"Verifying mpg123 version: {MpgLibVersion}"); _mpg123Handle = handle; @@ -153,7 +153,7 @@ internal static void InitializeLibrary(string libPath, string libPathOut) // Do the same for the out123 library! try { - var @delegate = libManagerOut.GetNativeMethodDelegate(nameof(NativeOutputLib.out123_new)); + var @delegate = GetDelegate(libManagerOut, nameof(NativeOutputLib.out123_new)); var handle = @delegate.Invoke(); Debug.WriteLine($"Verifying out123 version: {OutLibVersion}"); _out123Handle = handle; @@ -182,5 +182,14 @@ internal static string GetLibPath(string root, string libName) runtimesPath += $"runtimes/freebsd-{lowerArch}/native/lib{libName}.so"; return runtimesPath; } + + internal static TDelegate GetDelegate(LibraryManager? libraryManager, string function) + where TDelegate : Delegate + { + if (libraryManager is null) + throw new BasoliaNativeLibraryException($"Can't get delegate for {function} without initializing the library first"); + return libraryManager.GetNativeMethodDelegate(function) ?? + throw new BasoliaNativeLibraryException($"Can't get delegate for {function}"); + } } } diff --git a/Directory.Build.props b/Directory.Build.props index 25a0e29..97e45a0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,6 +7,7 @@ latest portable True + enable CS8002,nullable $(MSBuildThisFileDirectory) $(RootPath)\aptivi_snk.snk