Create recorder and player application in. NET MAUI (transfer)
preface
preview
precondition
Step 1: Add the required permissions in the two platforms.
-
RECORD_AUDIO -
READ_EXTERNAL_STORAGE -
WRITE_EXTERNAL_STORAGE
Note: You cannot add storage permissions in iOS. When selected and requested, it will always return Granted.
In Android, add the following code to the AndroidManifest.xml file.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<key>NSMicrophoneUsageDescription</key> <string>The audio recorder app wants to use your microphone to record audio.</ string>
Step 2: Create a service for recording and playing audio.
public interface IAudioPlayer { void PlayAudio(string filePath); void Pause(); void Stop(); string GetCurrentPlayTime(); bool CheckFinishedPlayingAudio(); } public interface IRecordAudio { void StartRecord(); string StopRecord(); void PauseRecord(); void ResetRecord(); }
Android recorder service
-
Create an instance of the MediaRecorder class, which will be used to record audio -
SetAudioSource(): Specifies the hardware device used to capture audio input -
SetOutputFile(): Specifies the name of the output audio file -
Prepare(): initialize the recorder -
Start(): Start recording audio -
Reset(): Discard the recorded audio and reset the recorder -
Pause(): Pause recording at the current running position -
Resume(): Resume recording from the paused position -
Stop(): Stop recording
Refer to the following code.
public class RecordAudio : IRecordAudio { #region Fields private MediaRecorder mediaRecorder; private string storagePath; private bool isRecordStarted = false; #endregion #region Methods public void StartRecord() { if (mediaRecorder == null) { SetAudioFilePath(); mediaRecorder = new MediaRecorder(); mediaRecorder.Reset(); mediaRecorder.SetAudioSource(AudioSource. Mic); mediaRecorder.SetOutputFormat(OutputFormat. AacAdts); mediaRecorder.SetAudioEncoder(AudioEncoder. Aac); mediaRecorder.SetOutputFile(storagePath); mediaRecorder.Prepare(); mediaRecorder.Start(); } else { mediaRecorder.Resume(); } isRecordStarted = true; } public void PauseRecord() { if (mediaRecorder == null) { return; } mediaRecorder.Pause(); isRecordStarted = false; } public void ResetRecord() { if (mediaRecorder != null) { mediaRecorder.Resume(); mediaRecorder.Reset(); } mediaRecorder = null; isRecordStarted = false; } public string StopRecord() { if (mediaRecorder == null) { return string.Empty; } mediaRecorder.Resume(); mediaRecorder.Stop(); mediaRecorder = null; isRecordStarted = false; return storagePath; } private void SetAudioFilePath() { string fileName = "/Record_" + DateTime.UtcNow.ToString("ddMMM_hhmmss") + ".mp3"; var path = Environment.GetFolderPath(System. Environment.SpecialFolder.MyDocuments); storagePath = path + fileName; Directory.CreateDirectory(path); } #endregion }
Recorder service for iOS
-
Initialize the audio session before attempting to record. -
Specify the recording file format and where to save the recording. The record format is specified as an entry in the NSDictionary, which contains two NSObject arrays containing format keys and values. -
Call the Record method when you are ready to start recording audio. -
After recording, call the Stop() method on the recorder.
Refer to the following code.
public class RecordAudio : IRecordAudio { AVAudioRecorder recorder; NSUrl url; NSError error; NSDictionary settings; string audioFilePath; public RecordAudio() { InitializeAudioSession(); } private bool InitializeAudioSession() { var audioSession = AVAudioSession.SharedInstance(); var err = audioSession.SetCategory(AVAudioSessionCategory. PlayAndRecord); if (err != null) { Console.WriteLine("audioSession: {0}", err); return false; } err = audioSession.SetActive(true); if (err != null) { Console.WriteLine("audioSession: {0}", err); return false; } return false; } public void PauseRecord() { recorder.Pause(); } public void ResetRecord() { recorder.Dispose(); recorder = null; } public void StartRecord() { if (recorder == null) { string fileName = "/Record_" + DateTime.UtcNow.ToString("ddMMM_hhmmss") + ".wav"; var docuFolder = Environment.GetFolderPath(Environment. SpecialFolder.MyDocuments); audioFilePath = docuFolder + fileName; url = NSUrl.FromFilename(audioFilePath); NSObject[] values = new NSObject[] { NSNumber.FromFloat(44100.0f), NSNumber.FromInt32((int)AudioToolbox. AudioFormatType.LinearPCM), NSNumber.FromInt32(2), NSNumber.FromInt32(16), NSNumber.FromBoolean(false), NSNumber.FromBoolean(false) }; NSObject[] key = new NSObject[] { AVAudioSettings.AVSampleRateKey, AVAudioSettings.AVFormatIDKey, AVAudioSettings.AVNumberOfChannelsKey, AVAudioSettings.AVLinearPCMBitDepthKey, AVAudioSettings.AVLinearPCMIsBigEndianKey, AVAudioSettings.AVLinearPCMIsFloatKey }; settings = NSDictionary.FromObjectsAndKeys(values, key); recorder = AVAudioRecorder.Create(url, new AudioSettings(settings), out error); recorder.PrepareToRecord(); recorder.Record(); } else { recorder.Record(); } } public string StopRecord() { if (recorder == null) { return string.Empty; } recorder.Stop(); recorder = null; return audioFilePath; } }
Audio player service for Android
-
Create an instance of the MediaPlayer class to play the audio file. -
The file path of the audio file is provided to the media player instance through the SetDataSource method. -
After setting the data source, prepare the media player by calling the Prepare method. -
After the media player is ready, use the Start method to start playing audio.
Refer to the following code.
public class AudioPlayer : IAudioPlayer { #region Fields private MediaPlayer _mediaPlayer; private int currentPositionLength = 0; private bool isPrepared; private bool isCompleted; #endregion #region Methods public void PlayAudio(string filePath) { if (_mediaPlayer != null && !_mediaPlayer.IsPlaying) { _mediaPlayer. SeekTo(currentPositionLength); currentPositionLength = 0; _mediaPlayer. Start(); } else if (_mediaPlayer == null || !_ mediaPlayer.IsPlaying) { try { isCompleted = false; _mediaPlayer = new MediaPlayer(); _mediaPlayer. SetDataSource(filePath); _mediaPlayer. SetAudioStreamType(Stream. Music); _mediaPlayer. PrepareAsync(); _mediaPlayer. Prepared += (sender, args) => { isPrepared = true; _mediaPlayer. Start(); }; _mediaPlayer. Completion += (sender, args) => { isCompleted = true; }; } catch (Exception e) { _mediaPlayer = null; } } } public void Pause() { if (_mediaPlayer != null && _mediaPlayer.IsPlaying) { _mediaPlayer. Pause(); currentPositionLength = _mediaPlayer.CurrentPosition; } } public void Stop() { if (_mediaPlayer != null) { if (isPrepared) { _mediaPlayer. Stop(); _mediaPlayer. Release(); isPrepared = false; } isCompleted = false; _mediaPlayer = null; } } public string GetCurrentPlayTime() { if (_mediaPlayer != null) { var positionTimeSeconds = double.Parse(_mediaPlayer. CurrentPosition.ToString()); positionTimeSeconds = positionTimeSeconds / 1000; TimeSpan currentTime = TimeSpan.FromSeconds(positionTimeSeconds); string currentPlayTime = string.Format("{0:mm\\:ss}", currentTime); return currentPlayTime; } return null; } public bool CheckFinishedPlayingAudio() { return isCompleted; } #endregion }
Audio player service for iOS
-
Configure the audio file to play through the AVPlayerItem built-in class. -
Call the Play method to start playing audio.
Refer to the following code.
public class AudioPlayer : IAudioPlayer { AVPlayer _player; NSObject notificationHandle; NSUrl url; private bool isFinishedPlaying; private bool isPlaying; public bool IsPlaying { get { return isPlaying; } set { if (_player. Rate == 1 && _player.Error == null) isPlaying = true; else isPlaying = false; } } public AudioPlayer() { RegisterNotification(); } ~AudioPlayer() { UnregisterNotification(); } public void PlayAudio(string filePath) { isFinishedPlaying = false; if (_player == null) { url = NSUrl.FromString(filePath); AVPlayerItem avPlayerItem = new AVPlayerItem(URL); _player = new AVPlayer(avPlayerItem); _player. AutomaticallyWaitsToMinimizeStalling = false; _player. Volume = 1; _player. Play(); IsPlaying = true; isFinishedPlaying = false; } else if (_player != null && !IsPlaying) { _player. Play(); IsPlaying = true; isFinishedPlaying = false; } } public void Pause() { if (_player != null && IsPlaying) { _player. Pause(); IsPlaying = false; } } public void Stop() { if (_player != null) { _player. Dispose(); IsPlaying = false; _player = null; } } public string GetCurrentPlayTime() { if (_player != null) { var positionTimeSeconds = _player.CurrentTime.Seconds; TimeSpan currentTime = TimeSpan.FromSeconds(positionTimeSeconds); string currentPlayTime = string.Format("{0:mm\\:ss}", currentTime); return currentPlayTime; } return null; } public bool CheckFinishedPlayingAudio() { return isFinishedPlaying; } private void RegisterNotification() { notificationHandle = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem. DidPlayToEndTimeNotification, HandleNotification); } private void UnregisterNotification() { NSNotificationCenter.DefaultCenter.RemoveObserver(notificationHandle); } private void HandleNotification(NSNotification notification) { isFinishedPlaying = true; Stop(); } }
Step 3: Create the model.
public class Audio : INotifyPropertyChanged { #region Private private bool isPlayVisible; private bool isPauseVisible; private string currentAudioPostion; #endregion #region Constructor public Audio() { IsPlayVisible = true; } #endregion #region Properties public string AudioName { get; set; } public string AudioURL { get; set; } public string Caption { get; set; } public bool IsPlayVisible { get { return isPlayVisible; } set { isPlayVisible = value; OnPropertyChanged(); IsPauseVisble = !value; } } public bool IsPauseVisble { get { return isPauseVisible; } set { isPauseVisible = value; OnPropertyChanged(); } } public string CurrentAudioPosition { get { return currentAudioPostion; } set { if (string.IsNullOrEmpty(currentAudioPostion)) { currentAudioPostion = string.Format("{0:mm\\:ss}", new TimeSpan()); } else { currentAudioPostion = value; } OnPropertyChanged(); } } #endregion }
Step 4: Create the UI.
<syncfusion:SfListView x:Name="AudioList" Grid.Row="0" Grid.ColumnSpan="2" Margin="0,8" IsVisible="true" ItemsSource="{Binding Audios}" SelectionMode="None"> <syncfusion:SfListView. ItemTemplate> <DataTemplate> <ViewCell> <Grid x:Name="PlayAudioGrid" Margin="0,4,0,12" BackgroundColor="Transparent" HeightRequest="60"> <Grid. ColumnDefinitions> <ColumnDefinition Width="50" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="80" /> </Grid.ColumnDefinitions> <Button Grid.Column="0" Padding="0" BackgroundColor="Transparent" Command="{Binding Path=BindingContext.PlayAudioCommand, Source={x:Reference mainPage}}" CommandParameter="{Binding .}" FontFamily="AudioIconFonts" FontSize="22" IsVisible="{Binding IsPlayVisible}" Text=" " TextColor="Black" /> <Button Grid.Column="0" Padding="0" BackgroundColor="Transparent" BorderColor="LightGray" Command="{Binding Path=BindingContext.PauseAudioCommand, Source={x:Reference mainPage}}" CommandParameter="{Binding .}" FontFamily="AudioIconFonts" FontSize="22" IsVisible="{Binding IsPauseVisble}" Text=" " TextColor="Black" /> <Label Grid.Column="1" FontSize="14" Text="{Binding AudioName}" TextColor="Black" VerticalTextAlignment="Center" /> <Label Grid.Column="2" Margin="0,0,12,0" FontSize="14" IsVisible="{Binding IsPauseVisble}" Text="{Binding CurrentAudioPosition}" TextColor="Black" VerticalTextAlignment="Center" /> <Button Grid.Column="3" BackgroundColor="Transparent" Command="{Binding Path=BindingContext.DeleteCommand, Source={x:Reference mainPage}}" CommandParameter="{Binding}" FontFamily="AudioIconFonts" FontSize="20" Text="" TextColor="Red" /> </Grid> </ViewCell> </DataTemplate> </syncfusion:SfListView. ItemTemplate> </syncfusion:SfListView>
<!-- Timer Label --> <StackLayout Grid.Row="2" Grid.ColumnSpan="2" Margin="0,0,0,32" VerticalOptions="End"> <Label FontSize="14" HorizontalTextAlignment="Center" IsVisible="{Binding IsRecordingAudio}" Text="Recording…" TextColor="#7D898F" /> <Label FontSize="60" HorizontalTextAlignment="Center" IsVisible="{Binding IsRecordingAudio}" Text="{Binding TimerLabel}" TextColor="Black" /> </StackLayout> <!-- Button Setup --> <Grid Grid.Row="3" Grid.ColumnSpan="2" ColumnSpacing="60"> <Grid. ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!-- Retry --> <Grid Grid.Column="0" RowDefinitions="auto,auto"> <Button Grid.Row="0" BackgroundColor="LightGray" BorderColor="#5F49FF" BorderWidth="1" Command="{Binding ResetCommand}" CornerRadius="25" FontFamily="AudioIconFonts" FontSize="22" HeightRequest="50" IsEnabled="{Binding IsRecordingAudio}" Text=" " TextColor="#5F49FF" WidthRequest="50"> <Button. Triggers> <DataTrigger Binding="{Binding IsRecordingAudio}" TargetType="Button" Value="False"> <Setter Property="TextColor" Value="Gray" /> <Setter Property="BorderColor" Value="Gray" /> </DataTrigger> </Button. Triggers> </Button> <Label Grid.Row="1" HorizontalOptions="Center" Text="Retry" /> </Grid> <!-- Play --> <Grid Grid.Column="1" HorizontalOptions="CenterAndExpand" RowDefinitions="auto,auto"> <!-- Record Button --> <Button Grid.Row="0" BackgroundColor="Red" BorderColor="Red" BorderWidth="1" Command="{Binding RecordCommand}" CornerRadius="25" FontFamily="AudioIconFonts" FontSize="22" HeightRequest="50" IsVisible="{Binding IsRecordButtonVisible}" Text=" " TextColor="White" WidthRequest="50" /> <Label Grid.Row="1" HorizontalOptions="Center" IsVisible="{Binding IsRecordButtonVisible}" Text="Record" /> <!-- Pause Button --> <Button Grid.Row="0" BackgroundColor="Green" BorderColor="Green" BorderWidth="1" Command="{Binding PauseCommand}" CornerRadius="25" FontFamily="AudioIconFonts" FontSize="22" HeightRequest="50" IsVisible="{Binding IsPauseButtonVisible}" Text=" " TextColor="White" WidthRequest="50" /> <Label Grid.Row="1" HorizontalOptions="Center" IsVisible="{Binding IsPauseButtonVisible}" Text="Pause" /> <!-- Resume Button --> <Button Grid.Row="0" BackgroundColor="Red" BorderColor="Red" BorderWidth="1" Command="{Binding RecordCommand}" CornerRadius="25" FontFamily="AudioIconFonts" FontSize="22" HeightRequest="50" IsVisible="{Binding IsResumeButtonVisible}" Text=" " TextColor="White" WidthRequest="50" /> <Label Grid.Row="1" HorizontalOptions="Center" IsVisible="{Binding IsResumeButtonVisible}" Text="Resume" /> </Grid> <!-- Stop --> <Grid Grid.Column="2" RowDefinitions="auto,auto"> <Button Grid.Row="0" BackgroundColor="LightGray" BorderColor="#5F49FF" BorderWidth="1" Command="{Binding StopCommand}" CornerRadius="25" FontFamily="AudioIconFonts" FontSize="22" HeightRequest="50" IsEnabled="{Binding IsRecordingAudio}" Text=" " TextColor="#5F49FF" WidthRequest="50"> <Button. Triggers> <DataTrigger Binding="{Binding IsRecordingAudio}" TargetType="Button" Value="False"> <Setter Property="TextColor" Value="Gray" /> <Setter Property="BorderColor" Value="Gray" /> </DataTrigger> </Button. Triggers> </Button> <Label Grid.Row="1" HorizontalOptions="Center" Text="Stop" /> </Grid> </Grid>
Step 5: Register dependency injection to access the object in the constructor.
public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); fonts.AddFont("AudioIconFonts.ttf", "AudioIconFonts"); }); #if ANDROID || IOS builder.Services.AddTransient<IAudioPlayerService, AudioPlayerService>(); builder.Services.AddTransient<IRecordAudioService, RecordAudioService>(); #endif builder.Services.AddTransient<MainPage>(); builder.Services.AddTransient<AppShell>(); builder.ConfigureSyncfusionListView(); return builder.Build(); }
Step 6: Create the view model.
You must grant permission to start the logger.
private async void StartRecording() { if (! IsRecordingAudio) { var permissionStatus = await RequestandCheckPermission(); if (permissionStatus == PermissionStatus. Granted) { IsRecordingAudio = true; IsPauseButtonVisible = true; recordAudio.StartRecord(); IsRecordButtonVisible = false; isRecord = true; timerValue = new TimeSpan(0, 0, -1); recordTimer.Start(); } else { IsRecordingAudio = false; IsPauseButtonVisible = false; } } else { ResumeRecording(); } }
private void PauseRecording() { isRecord = false; IsPauseButtonVisible = false; IsResumeButtonVisible = true; recordAudio.PauseRecord(); }
private void ResumeRecording() { recordAudio.StartRecord(); IsResumeButtonVisible = false; IsPauseButtonVisible = true; isRecord = true; }
private void ResetRecording() { recordAudio.ResetRecord(); timerValue = new TimeSpan(); TimerLabel = string.Format("{0:mm\\:ss}", timerValue); IsRecordingAudio = false; IsPauseButtonVisible = false; IsResumeButtonVisible = false; StartRecording(); }
private async void StopRecording() { IsPauseButtonVisible = false; IsResumeButtonVisible = false; IsRecordingAudio = false; IsRecordButtonVisible = true; timerValue = new TimeSpan(); recordTimer.Stop(); RecentAudioFilePath = recordAudio.StopRecord(); await App.Current.MainPage.DisplayAlert("Alert", "Audio has been recorded", "Ok"); TimerLabel = string.Format("{0:mm\\:ss}", timerValue); SendRecording(); } private void SendRecording() { Audio recordedFile = new Audio() { AudioURL = RecentAudioFilePath }; if (recordedFile != null) { recordedFile.AudioName = Path.GetFileName(RecentAudioFilePath); Audios.Insert(0, recordedFile); } }
public async Task<PermissionStatus> RequestandCheckPermission() { PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.StorageWrite>(); if (status != PermissionStatus.Granted) await Permissions.RequestAsync<Permissions.StorageWrite>(); status = await Permissions.CheckStatusAsync<Permissions.Microphone>(); if (status != PermissionStatus.Granted) await Permissions.RequestAsync<Permissions.Microphone>(); PermissionStatus storagePermission = await Permissions.CheckStatusAsync<Permissions.StorageWrite>(); PermissionStatus microPhonePermission = await Permissions.CheckStatusAsync<Permissions.Microphone>(); if (storagePermission == PermissionStatus. Granted && microPhonePermission == PermissionStatus.Granted) { return PermissionStatus.Granted; } return PermissionStatus.Denied; }
private void StartPlayingAudio(object obj) { if (audioFile != null && audioFile != (Audio)obj) { AudioFile.IsPlayVisible = true; StopAudio(); } if (obj is Audio) { audioFile = (Audio)obj; audioFile.IsPlayVisible = false; string audioFilePath = AudioFile.AudioURL; audioPlayer.PlayAudio(audioFilePath); SetCurrentAudioPosition(); } }
private void PauseAudio(object obj) { if (obj is Audio) { var audiophile = (Audio)obj; audioFile.IsPlayVisible = true; audioPlayer.Pause(); } }
public void StopAudio() { if (AudioFile != null) { audioPlayer.Stop(); playTimer.Stop(); } }
private void SetCurrentAudioPosition() { playTimer.Interval = new TimeSpan(0, 0, 0, 0, 250); playTimer.Tick += (s, e) => { if (AudioFile != null) { AudioFile.CurrentAudioPosition = audioPlayer.GetCurrentPlayTime(); bool isAudioCompleted = audioPlayer.CheckFinishedPlayingAudio(); if (isAudioCompleted) { AudioFile.IsPlayVisible = true; playTimer.Stop(); } } }; playTimer.Start(); }
reference resources
resources