diff --git a/samples/Plugin.Maui.Audio.Sample/ViewModels/AudioRecorderPageViewModel.cs b/samples/Plugin.Maui.Audio.Sample/ViewModels/AudioRecorderPageViewModel.cs index 3db2bb1..2ec8c4e 100644 --- a/samples/Plugin.Maui.Audio.Sample/ViewModels/AudioRecorderPageViewModel.cs +++ b/samples/Plugin.Maui.Audio.Sample/ViewModels/AudioRecorderPageViewModel.cs @@ -111,7 +111,7 @@ public int SelectedSampleRate async void PlayAudio() { - if (audioSource != null) + if (audioSource is not null) { audioPlayer = this.audioManager.CreateAsyncPlayer(((FileAudioSource)audioSource).GetAudioStream()); diff --git a/samples/Plugin.Maui.Audio.Sample/ViewModels/MusicPlayerPageViewModel.cs b/samples/Plugin.Maui.Audio.Sample/ViewModels/MusicPlayerPageViewModel.cs index 66202d8..46bea99 100644 --- a/samples/Plugin.Maui.Audio.Sample/ViewModels/MusicPlayerPageViewModel.cs +++ b/samples/Plugin.Maui.Audio.Sample/ViewModels/MusicPlayerPageViewModel.cs @@ -130,7 +130,7 @@ public double Volume get => audioPlayer?.Volume ?? 1; set { - if (audioPlayer != null) + if (audioPlayer is not null) { audioPlayer.Volume = value; } @@ -142,7 +142,7 @@ public double Balance get => audioPlayer?.Balance ?? 0; set { - if (audioPlayer != null) + if (audioPlayer is not null) { audioPlayer.Balance = value; } diff --git a/src/Plugin.Maui.Audio/AudioMixer/AudioMixer.cs b/src/Plugin.Maui.Audio/AudioMixer/AudioMixer.cs index 267ce98..92e2355 100644 --- a/src/Plugin.Maui.Audio/AudioMixer/AudioMixer.cs +++ b/src/Plugin.Maui.Audio/AudioMixer/AudioMixer.cs @@ -45,11 +45,15 @@ public class AudioMixer : IDisposable /// The number of audio channels to manage. public AudioMixer(IAudioManager audioManager, int numberOfChannels) { - if (audioManager == null) + if (audioManager is null) + { throw new ArgumentNullException(nameof(audioManager)); + } if (numberOfChannels <= 0) + { throw new ArgumentOutOfRangeException(nameof(numberOfChannels), "Number of channels must be positive."); + } this.audioManager = audioManager; ChannelCount = numberOfChannels; @@ -94,8 +98,10 @@ public void Play(int channelIndex, bool loop = false) public void Play(int channelIndex, IAudioSource audioClip, bool loop = false) { ValidateChannelIndex(channelIndex); - if (audioClip == null) + if (audioClip is null) + { throw new ArgumentNullException(nameof(audioClip)); + } var player = channels[channelIndex]; player.Stop(); @@ -177,8 +183,10 @@ public void PlayAll() public void SetSource(int channelIndex, IAudioSource audioClip) { ValidateChannelIndex(channelIndex); - if (audioClip == null) + if (audioClip is null) + { throw new ArgumentNullException(nameof(audioClip)); + } var player = channels[channelIndex]; player.Stop(); @@ -244,7 +252,9 @@ public void SetVolume(int channelIndex, float volume) void ValidateChannelIndex(int channelIndex) { if (channelIndex < 0 || channelIndex >= ChannelCount) + { throw new ArgumentOutOfRangeException(nameof(channelIndex), $"Channel index must be between 0 and {ChannelCount - 1}."); + } } /// @@ -307,7 +317,9 @@ public float GetDistanceDecay(float dist) protected virtual void Dispose(bool disposing) { if (IsDisposed) + { return; + } foreach (var player in channels) { diff --git a/src/Plugin.Maui.Audio/AudioPlayer/AudioPlayer.android.cs b/src/Plugin.Maui.Audio/AudioPlayer/AudioPlayer.android.cs index 0fafc9f..e773acc 100644 --- a/src/Plugin.Maui.Audio/AudioPlayer/AudioPlayer.android.cs +++ b/src/Plugin.Maui.Audio/AudioPlayer/AudioPlayer.android.cs @@ -142,12 +142,12 @@ public bool Loop void PrepareAudioSource() { - if (audioBytes == null && string.IsNullOrWhiteSpace(file)) + if (audioBytes is null && string.IsNullOrWhiteSpace(file)) { throw new ArgumentException("audio source is not set"); } - if (audioBytes != null && OperatingSystem.IsAndroidVersionAtLeast(23)) + if (audioBytes is not null && OperatingSystem.IsAndroidVersionAtLeast(23)) { stream = new MemoryStream(audioBytes); var mediaSource = new StreamMediaDataSource(stream); @@ -172,6 +172,11 @@ void PrepareAudioSource() } else { + if (file is null) + { + throw new FailedToLoadAudioException("Provided file was null"); + } + AssetFileDescriptor afd = Android.App.Application.Context.Assets?.OpenFd(file) ?? throw new FailedToLoadAudioException("Unable to create AssetFileDescriptor."); diff --git a/src/Plugin.Maui.Audio/AudioPlayer/AudioPlayer.macios.cs b/src/Plugin.Maui.Audio/AudioPlayer/AudioPlayer.macios.cs index 1b3c755..cb93389 100644 --- a/src/Plugin.Maui.Audio/AudioPlayer/AudioPlayer.macios.cs +++ b/src/Plugin.Maui.Audio/AudioPlayer/AudioPlayer.macios.cs @@ -104,7 +104,7 @@ public bool Loop internal AudioPlayer(AudioPlayerOptions audioPlayerOptions) { - if (emptySource == null) + if (emptySource is null) { byte[] empty = new byte[16]; int sampleRate = 44100; @@ -127,7 +127,7 @@ internal AudioPlayer(AudioPlayerOptions audioPlayerOptions) /// Thrown when the audio stream cannot be loaded. public void SetSource(Stream audioStream) { - if (player != null) + if (player is not null) { player.FinishedPlaying -= OnPlayerFinishedPlaying; player.DecoderError -= OnPlayerError; diff --git a/src/Plugin.Maui.Audio/AudioRecorder/AudioRecorder.android.cs b/src/Plugin.Maui.Audio/AudioRecorder/AudioRecorder.android.cs index 6fa5fde..a312615 100644 --- a/src/Plugin.Maui.Audio/AudioRecorder/AudioRecorder.android.cs +++ b/src/Plugin.Maui.Audio/AudioRecorder/AudioRecorder.android.cs @@ -39,7 +39,7 @@ public AudioRecorder(AudioRecorderOptions options) var packageManager = Android.App.Application.Context.PackageManager; CanRecordAudio = packageManager?.HasSystemFeature(Android.Content.PM.PackageManager.FeatureMicrophone) ?? false; - this.audioRecorderOptions = options; + audioRecorderOptions = options; } public Task StartAsync(AudioRecorderOptions? options = null) => StartAsync(GetTempFilePath(), options); @@ -57,7 +57,7 @@ public Task StartAsync(string filePath, AudioRecorderOptions? recordingOptions = if (recordingOptions is not null) { - this.audioRecorderOptions = recordingOptions; + audioRecorderOptions = recordingOptions; } audioFilePath = filePath; @@ -65,9 +65,9 @@ public Task StartAsync(string filePath, AudioRecorderOptions? recordingOptions = // solve some parameters needed for AudioRecord/MediaRecorder ChannelIn channelIn = SharedChannelTypesToAndroidChannelTypes(audioRecorderOptions.Channels, audioRecorderOptions.ThrowIfNotSupported); - this.sampleRate = audioRecorderOptions.SampleRate; - this.bitDepth = (int)audioRecorderOptions.BitDepth; - this.channels = channelIn == ChannelIn.Mono ? 1 : 2; + sampleRate = audioRecorderOptions.SampleRate; + bitDepth = (int)audioRecorderOptions.BitDepth; + channels = channelIn == ChannelIn.Mono ? 1 : 2; int bitRate = audioRecorderOptions.BitRate; int numChannels = (int)audioRecorderOptions.Channels; @@ -124,10 +124,22 @@ public Task StartAsync(string filePath, AudioRecorderOptions? recordingOptions = } // Create MediaRecorder - mediaRecorder = - new MediaRecorder(Platform.CurrentActivity - .ApplicationContext); //needs context, obsoleted without context https://stackoverflow.com/questions/73598179/deprecated-mediarecorder-new-mediarecorder#73598440 - + if (OperatingSystem.IsAndroidVersionAtLeast(31)) + { + if (Platform.CurrentActivity?.ApplicationContext is null) + { + throw new FailedToStartRecordingException("Android ApplicationContext is null"); + } + + //needs context, obsoleted without context https://stackoverflow.com/questions/73598179/deprecated-mediarecorder-new-mediarecorder#73598440 + mediaRecorder = + new MediaRecorder(Platform.CurrentActivity.ApplicationContext); + } + else + { + mediaRecorder = new MediaRecorder(); + } + mediaRecorder.Reset(); mediaRecorder.SetAudioSource(AudioSource.Mic); mediaRecorder.SetOutputFormat(outputFormat); diff --git a/src/Plugin.Maui.Audio/AudioRecorder/AudioRecorder.windows.cs b/src/Plugin.Maui.Audio/AudioRecorder/AudioRecorder.windows.cs index 799c23c..4a58812 100644 --- a/src/Plugin.Maui.Audio/AudioRecorder/AudioRecorder.windows.cs +++ b/src/Plugin.Maui.Audio/AudioRecorder/AudioRecorder.windows.cs @@ -11,7 +11,7 @@ partial class AudioRecorder : IAudioRecorder string audioFilePath = string.Empty; public bool CanRecordAudio { get; private set; } = true; - public bool IsRecording => mediaCapture != null; + public bool IsRecording => mediaCapture is not null; AudioRecorderOptions audioRecorderOptions; static readonly AudioRecorderOptions defaultOptions = new AudioRecorderOptions(); @@ -56,7 +56,7 @@ public async Task StartAsync(string filePath, AudioRecorderOptions? options = nu CanRecordAudio = false; DeleteMediaCapture(); - if (ex.InnerException != null && ex.InnerException.GetType() == typeof(UnauthorizedAccessException)) + if (ex.InnerException is not null && ex.InnerException.GetType() == typeof(UnauthorizedAccessException)) { throw ex.InnerException; } @@ -152,7 +152,7 @@ async Task InitMediaCapture(MediaCaptureInitializationSettings settings) public async Task StopAsync() { - if (mediaCapture == null) + if (mediaCapture is null) { throw new InvalidOperationException("No recording in progress"); } diff --git a/src/Plugin.Maui.Audio/AudioStreamer/AudioStream.macios.cs b/src/Plugin.Maui.Audio/AudioStreamer/AudioStream.macios.cs index 72add02..d14c53c 100644 --- a/src/Plugin.Maui.Audio/AudioStreamer/AudioStream.macios.cs +++ b/src/Plugin.Maui.Audio/AudioStreamer/AudioStream.macios.cs @@ -46,7 +46,7 @@ public Task Start() BufferOperation(() => audioQueue.AllocateBuffer(bufferByteSize, out bufferPtr), () => { - BufferOperation(() => audioQueue.EnqueueBuffer(bufferPtr, bufferByteSize, null), () => Debug.WriteLine("AudioQueue buffer enqueued :: {0} of {1}", index + 1, countAudioBuffers)); + BufferOperation(() => audioQueue.EnqueueBuffer(bufferPtr, bufferByteSize, null!), () => Debug.WriteLine("AudioQueue buffer enqueued :: {0} of {1}", index + 1, countAudioBuffers)); }); } @@ -150,11 +150,12 @@ void QueueInputCompleted(object? sender, InputCompletedEventArgs e) // check if active again, because the auto stop logic may stop the audio queue from within this handler! if (Active) { - BufferOperation(() => audioQueue.EnqueueBuffer(e.IntPtrBuffer, null), null, status => - { - Debug.WriteLine("AudioStream.QueueInputCompleted() :: audioQueue.EnqueueBuffer returned non-Ok status :: {0}", status); - OnException?.Invoke(this, new Exception($"audioQueue.EnqueueBuffer returned non-Ok status :: {status}")); - }); + BufferOperation(() => audioQueue?.EnqueueBuffer(e.IntPtrBuffer, null!) ?? AudioQueueStatus.InvalidBuffer, + null, status => + { + Debug.WriteLine("AudioStream.QueueInputCompleted() :: audioQueue.EnqueueBuffer returned non-Ok status :: {0}", status); + OnException?.Invoke(this, new Exception($"audioQueue.EnqueueBuffer returned non-Ok status :: {status}")); + }); } } catch (Exception ex) diff --git a/src/Plugin.Maui.Audio/AudioStreamer/AudioStreamer.net.cs b/src/Plugin.Maui.Audio/AudioStreamer/AudioStreamer.net.cs index c9ad170..7165fc8 100644 --- a/src/Plugin.Maui.Audio/AudioStreamer/AudioStreamer.net.cs +++ b/src/Plugin.Maui.Audio/AudioStreamer/AudioStreamer.net.cs @@ -8,38 +8,43 @@ public partial class AudioStreamer : IAudioStreamer /// /// Gets whether the device is capable of streaming audio. /// - public bool CanStreamAudio => false; + public bool CanStreamAudio => throw new PlatformNotSupportedException(); /// /// Gets whether the streamer is currently streaming audio. /// - public bool IsStreaming => false; + public bool IsStreaming => throw new PlatformNotSupportedException(); /// /// Gets the audio streaming options. /// - public AudioStreamOptions Options { get; } = AudioManager.Current.DefaultStreamerOptions; + public AudioStreamOptions Options => throw new PlatformNotSupportedException(); /// /// Captured linear PCM audio (raw WAV audio) /// - public event EventHandler? OnAudioCaptured; + public event EventHandler? OnAudioCaptured + { + add + { + throw new PlatformNotSupportedException(); + } + + remove + { + throw new PlatformNotSupportedException(); + } + } /// /// Start streaming audio to . /// /// A task representing the asynchronous operation. - public Task StartAsync() - { - return Task.CompletedTask; - } + public Task StartAsync() => throw new PlatformNotSupportedException(); /// /// Stop streaming. /// /// A task representing the asynchronous operation. - public Task StopAsync() - { - return Task.CompletedTask; - } + public Task StopAsync() => throw new PlatformNotSupportedException(); } \ No newline at end of file diff --git a/src/Plugin.Maui.Audio/Plugin.Maui.Audio.csproj b/src/Plugin.Maui.Audio/Plugin.Maui.Audio.csproj index 2dd8a41..769bbe4 100644 --- a/src/Plugin.Maui.Audio/Plugin.Maui.Audio.csproj +++ b/src/Plugin.Maui.Audio/Plugin.Maui.Audio.csproj @@ -70,7 +70,7 @@ - + diff --git a/src/Plugin.Maui.Audio/RawAudioSource.cs b/src/Plugin.Maui.Audio/RawAudioSource.cs index 805a0ed..d4e3e77 100644 --- a/src/Plugin.Maui.Audio/RawAudioSource.cs +++ b/src/Plugin.Maui.Audio/RawAudioSource.cs @@ -26,12 +26,12 @@ public enum BitsPerSample /// public class RawAudioSource : IAudioSource { - readonly byte[] _soundData; - readonly int _sampleRate; - readonly int _nbOfChannels; - readonly BitsPerSample _bitsPerSample; + readonly byte[] soundData; + readonly int sampleRate; + readonly int nbOfChannels; + readonly BitsPerSample bitsPerSample; - byte[] _withHeader; + byte[]? withHeader; /// /// Initializes a new instance of the class with 8-bit samples. @@ -54,27 +54,34 @@ public RawAudioSource(ReadOnlySpan soundData, int sampleRate, int nbOfChan /// Bits per sample (e.g., 8 or 16). public RawAudioSource(byte[] soundData, int sampleRate, int nbOfChannels = 1, BitsPerSample bitsPerSample = BitsPerSample.Bit8) { - if (soundData == null) - throw new ArgumentNullException(nameof(soundData)); + ArgumentNullException.ThrowIfNull(soundData); if (sampleRate <= 0) + { throw new ArgumentOutOfRangeException(nameof(sampleRate), "Sample rate must be positive."); + } if (nbOfChannels <= 0) + { throw new ArgumentOutOfRangeException(nameof(nbOfChannels), "Number of channels must be positive."); + } if (bitsPerSample != BitsPerSample.Bit8 && bitsPerSample != BitsPerSample.Bit16) + { throw new NotSupportedException($"Unsupported BitsPerSample: {bitsPerSample}. Only 8-bit and 16-bit are supported."); + } // Validate sound data length based on bits per sample and number of channels int bytesPerSample = bitsPerSample == BitsPerSample.Bit8 ? 1 : 2; if (soundData.Length % (nbOfChannels * bytesPerSample) != 0) + { throw new ArgumentException("Sound data length is not aligned with the specified number of channels and bits per sample.", nameof(soundData)); + } - _soundData = soundData; - _sampleRate = sampleRate; - _nbOfChannels = nbOfChannels; - _bitsPerSample = bitsPerSample; + this.soundData = soundData; + this.sampleRate = sampleRate; + this.nbOfChannels = nbOfChannels; + this.bitsPerSample = bitsPerSample; } /// @@ -93,12 +100,9 @@ public byte[] Bytes { get { - if (_withHeader == null) - { - _withHeader = BuildWavFile(); - } + withHeader ??= BuildWavFile(); - return _withHeader; + return withHeader; } } @@ -108,12 +112,12 @@ public byte[] Bytes /// Byte array containing the complete WAV file. byte[] BuildWavFile() { - int dataSize = _soundData.Length; - int bytesPerSample = _bitsPerSample == BitsPerSample.Bit8 ? 1 : 2; - int byteRate = _sampleRate * _nbOfChannels * bytesPerSample; - short blockAlign = (short)(_nbOfChannels * bytesPerSample); + int dataSize = soundData.Length; + int bytesPerSample = this.bitsPerSample == BitsPerSample.Bit8 ? 1 : 2; + int byteRate = sampleRate * nbOfChannels * bytesPerSample; + short blockAlign = (short)(nbOfChannels * bytesPerSample); short audioFormat = 1; // PCM - short bitsPerSample = (short)_bitsPerSample; + short bitsPerSample = (short)this.bitsPerSample; int fileSize = dataSize + 44 - 8; // Total file size minus "RIFF" and size field itself byte[] wavHeader = new byte[44]; @@ -125,8 +129,8 @@ byte[] BuildWavFile() Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes("fmt "), 0, wavHeader, 12, 4); Buffer.BlockCopy(BitConverter.GetBytes(16), 0, wavHeader, 16, 4); Buffer.BlockCopy(BitConverter.GetBytes(audioFormat), 0, wavHeader, 20, 2); - Buffer.BlockCopy(BitConverter.GetBytes((short)_nbOfChannels), 0, wavHeader, 22, 2); - Buffer.BlockCopy(BitConverter.GetBytes(_sampleRate), 0, wavHeader, 24, 4); + Buffer.BlockCopy(BitConverter.GetBytes((short)nbOfChannels), 0, wavHeader, 22, 2); + Buffer.BlockCopy(BitConverter.GetBytes(sampleRate), 0, wavHeader, 24, 4); Buffer.BlockCopy(BitConverter.GetBytes(byteRate), 0, wavHeader, 28, 4); Buffer.BlockCopy(BitConverter.GetBytes(blockAlign), 0, wavHeader, 32, 2); Buffer.BlockCopy(BitConverter.GetBytes(bitsPerSample), 0, wavHeader, 34, 2); @@ -136,9 +140,8 @@ byte[] BuildWavFile() byte[] wavSoundData = new byte[wavHeader.Length + dataSize]; Buffer.BlockCopy(wavHeader, 0, wavSoundData, 0, wavHeader.Length); - Buffer.BlockCopy(_soundData, 0, wavSoundData, wavHeader.Length, dataSize); + Buffer.BlockCopy(soundData, 0, wavSoundData, wavHeader.Length, dataSize); return wavSoundData; } } -