MAUI 中创建录音机和播放器应用

前言

在本博客中,你将了解如何在 .NET MAUI 中开发录音机和播放器。音频播放器将录制和播放音频文件。
此应用程序可以在Android和iOS上部署和使用。

预览

以下是该录音机和播放录音的应用程序屏幕截图。

MAUI 中创建录音机和播放器应用_第1张图片

先决条件

IDE: VisualStudio 2022
支持的平台:Android 和 IOS
支持的操作系统:Android(7.0 及以上)和 iOS(v12 及以上)

步骤1:在两个平台中添加所需的权限。

要录制音频并将其保存在设备中,应用程序必须访问设备的音频输入和存储。为此,需要授予以下权限:

  • RECORD_AUDIO

  • READ_EXTERNAL_STORAGE

  • WRITE_EXTERNAL_STORAGE

注意:在iOS中,您无法添加存储权限。在选中和请求时,它将始终返回"已授予"。
在 Android 中,将以下代码添加到 AndroidManifest.xml 文件中。




在 iOS 中,将以下代码添加到 Info.plist 文件中。

NSMicrophoneUsageDescription
The audio recorder app wants to use your microphone to record audio.

步骤2:创建用于录制和播放音频的服务。

NET MAUI 没有能够直接使用的录制和播放音频功能。因此,必须在本机平台中创建用于录制和播放音频文件的服务。
在创建服务类之前,请创建用于调用本机方法的接口。

请参考以下代码。

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 的录音机服务:

  • 创建 MediaRecorder 类的实例,该实例将用于录制音频.

  • SetAudioSource(): 指定用于捕获音频输入的硬件设备.

  • SetOutputFile(): 指定输出音频文件的名称.

  • Prepare(): 初始化录音机.

  • Start(): 开始录制音频.

  • Reset(): 丢弃录制的音频并重置录音机.

  • Pause(): 将录制暂停在当前运行位置.

  • Resume(): 从暂停位置恢复录制.

  • Stop(): 停止录音.
    请参考以下代码。

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
}

适用于 iOS 的录音机服务

现在,使用AVAudioRecorder为iOS平台创建录音机服务:

  • 在尝试录制之前初始化音频会话。

  • 指定录制文件格式和保存录制内容的位置。记录格式被指定为 NSDictionary 中的条目,其中包含两个包含格式键和值的 NSObject 数组。

  • 准备好开始录制音频时调用 Record 方法。

  • 完成录制后,在录制器上调用 Stop() 方法。
    请参考以下代码。

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;
  }
}

现在,来实现一个音频播放器服务,用于在两个平台上播放录制的音频。

适用于安卓的音频播放器服务

请按照以下步骤创建适用于 Android 的音频播放服务:

  • 创建 MediaPlayer 类的实例以播放音频文件。

  • 通过 SetDataSource 方法将音频文件的文件路径提供给媒体播放器实例。

  • 设置数据源后,通过调用 Prepare 方法准备媒体播放器。

  • 准备好媒体播放器后,使用 Start 方法开始播放音频。
    请参考以下代码。

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
}

适用于 iOS 的音频播放器服务

使用 AVPlayer 类在 iOS 中创建音频播放服务:

  • 将音频文件配置为通过 AVPlayerItem 内置类播放。

  • 调用 Play 方法以开始播放音频。
    请参考以下代码。

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();
   }
}

步骤3:创建模型。

创建一个模型类以在列表中显示录制的音频文件。请参考以下代码。

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
}

在代码中,属性可以显示播放音频时间。

步骤4:创建 UI。

这里将创建一个简单的UI,用于显示录制的音频文件和录制音频。为此,这里使用 Syncfusion 的 ListView for .NET MAUI。安装 .NET MAUI 列表视图 NuGet 包,然后将其包含在应用程序中。

以下 XAML 代码将在 Syncfusion 的 .NET MAUI 列表视图控件中显示录制的音频。


     
       
         
           
                 
                    
                    
                    
                    
                
                

以下 XAML 代码用于设计用于录制音频的 UI。



     
   


    
         
         
         
    
         
    
    
       
     
       
    
    
        
        
       

步骤 5:注册依赖项注入以访问构造函数中的对象。

依赖关系注入是对象(客户端)接收依赖于它的其他对象(服务)的一种方式。若要了解有关在 .NET MAUI 中使用依赖项注入的详细信息.
请参阅以下代码以注册依赖项注入服务。首先,必须添加必要的服务。然后,可以直接访问所需类构造函数中的对象。因此才可以在视图模型中访问 AudioPlayerService 和 RecordAudioService 对象。

public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder
         .UseMauiApp()
         .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();
        builder.Services.AddTransient();
     #endif
        builder.Services.AddTransient();
        builder.Services.AddTransient();
        builder.ConfigureSyncfusionListView();
        return builder.Build();
}

步骤6:创建视图模型。

这里遵循 MVVM(模型-视图-视图模型)结构来开发该应用程序。
因此,需要创建一个用于录制和播放音频的视图模型(MainPageViewModel.cs)文件。
在 ViewModel 中,生成一个音频集合来绑定录制音频的数据。

ViewModel 类中的属性
recordTime,playTimer:播放或录制音频时UI中使用的计时器属性。
IsRecordingAudio: 控制重置和停止选项的可见性。
IsPauseButtonVisible:控制 UI 中“暂停”按钮的可见性。
IsRecordButtonVisible:控制“录制”按钮的可见性。
IsResumeButtonVisible:控制“恢复”按钮的可见性。
audios:要在列表视图中显示的所有录制音频文件的集合。
recordAudioService,audioPlayerService:这些接口属性调用特定于本机平台的代码。
下面的代码示例演示录音机。

使用以下代码启动录制器。必须调用依赖服务方法 recordAudio.StartRecord() 来启动记录器。

只有授予权限,才能启动记录器。

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();
   }
}

使用 recordAudio.PauseRecord() 平台特定的方法暂停录制器。

private void PauseRecording()
{
    isRecord = false;
    IsPauseButtonVisible = false;
    IsResumeButtonVisible = true;
    recordAudio.PauseRecord();
}

以下方法用于从暂停位置继续录制。

private void ResumeRecording()
{
   recordAudio.StartRecord();
   IsResumeButtonVisible = false;
   IsPauseButtonVisible = true;
   isRecord = true;
}

使用以下代码重置记录器并从初始位置启动它。使用特定于平台的代码 recordAudio.ResetRecord() 重置录制器。

private void ResetRecording()
{
   recordAudio.ResetRecord();
   timerValue = new TimeSpan();
   TimerLabel = string.Format("{0:mm\\:ss}", timerValue);
   IsRecordingAudio = false;
   IsPauseButtonVisible = false;
   IsResumeButtonVisible = false;
   StartRecording();
}

若要停止录制器,请使用特定于平台的代码 recordAudio.StopRecord()。

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 RequestandCheckPermission()
{
    PermissionStatus status = await Permissions.CheckStatusAsync();
    if (status != PermissionStatus.Granted)
        await Permissions.RequestAsync();
  
    status = await Permissions.CheckStatusAsync();
    if (status != PermissionStatus.Granted)
        await Permissions.RequestAsync();
              PermissionStatus storagePermission = await Permissions.CheckStatusAsync();
              PermissionStatus microPhonePermission = await Permissions.CheckStatusAsync();
              if (storagePermission == PermissionStatus.Granted && microPhonePermission == PermissionStatus.Granted) 
              {
                  return PermissionStatus.Granted;
              }
    return PermissionStatus.Denied;
}

下面的代码示例演示音频播放器。
调用特定于平台的方法 audioPlayer.PlayAudio(audioFilePath) 来播放音频。

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();
   }
}

使用以下方法通过特定于平台的方法 audioPlayer.Pause() 暂停音频。

private void PauseAudio(object obj)
{
   if (obj is Audio)
   {
      var audiophile = (Audio)obj;
      audioFile.IsPlayVisible = true;
      audioPlayer.Pause();
   }
}

使用方法audioPlayer.Stop() 停止以下代码中的音频。

public void StopAudio()
{
   if (AudioFile != null)
   {
      audioPlayer.Stop();
      playTimer.Stop();
   }
}

使用以下代码获取音频的当前位置并将其显示在 UI 中。

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();
}

你可能感兴趣的:(c#,xamarin,android,ios)