I'm using plugin NightlyRecorderService to record voice on Xamarin Forms Android and iOS, and both seems to work as expected until you need to play the file recorded by iOS on Android(inverse works properly). I'm using SimpleAudioPlayer to play the recorded audio.
Recording code:
//initializing the plugin using Mp4Aar and Samplig rate of 24k
NightlyRecorderService Rec = new NightlyRecorderService(new RecorderSettings
{
DroidRecorderSettings = new DroidMp4Aar() { SamplingRate = 24000 },
IosRecorderSettings = new IosMp4Aar() { SampleRate = 24000 },
});
// assuming we have mic/storage permissions(I have it on my real project), let's record an audio of 5 secs
Stream audioStream = await Rec.RecordAsync();
//After 5 secs
Rec.Stop();
//save the audio stream to a temp file, then upload it to file server:
string audioTemp = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
string localPath = Path.Combine(audioTemp, "audioTemp");
using (FileStream writeStream = new FileStream(localPath, FileMode.CreateNew, FileAccess.Write))
{
try
{
ReadWriteStream(audioStream, writeStream);
}
finally
{
if (File.Exists(localPath))
await Task.Run(() => UploadFile("upload.php", localPath));
writeStream.Dispose();
}
}
private void ReadWriteStream(Stream readStream, Stream writeStream)
{
int Length = 256;
Byte[] buffer = new Byte[Length];
readStream.Seek(0, SeekOrigin.Begin);
int bytesRead = readStream.Read(buffer, 0, Length);
// write the required bytes
while (bytesRead > 0)
{
writeStream.Write(buffer, 0, bytesRead);
bytesRead = readStream.Read(buffer, 0, Length);
}
readStream.Close();
writeStream.Close();
}
Playing code:
//assuming the upload was a success and we have the url to the file at fileserver:
try
{
Stream AudioStream = await Task.Run(() =>
{
System.Net.WebRequest webRequest = System.Net.WebRequest.Create("https://....audio.mp4");
System.Net.WebRequest.DefaultCachePolicy = new RequestCachePolicy(RequestCacheLevel.CacheIfAvailable);
return webRequest.GetResponse().GetResponseStream();
});
if (AudioStream != null)
{
Plugin.SimpleAudioPlayer.ISimpleAudioPlayer player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.CreateSimpleAudioPlayer();
if (await player.Load(AudioStream))
player.Play();
}
}
catch (Exception e)
{
Console.WriteLine("Erro ao tentar carregar audio da internet ===>" + e.Message);
// #1 we are falling here
}
Exception/message by player.Load():
#1 Exception message: "Prepare failed.: status=0x1"
Console output:
[MediaPlayer] getDuration_l
[MediaPlayer] Attempt to call getDuration in wrong state: mPlayer=0x70e0901680, mCurrentState=0
[MediaPlayer] message received msg=100, ext1=-38, ext2=0
[MediaPlayer] error (-38, 0)
[MediaPlayer] callback application
[MediaPlayer] back from callback
[MediaPlayer-JNI] getDuration: 0 (msec)
Tested on Android 7, Android 11, iOS 14.4.1
Current behavior:
If recorded by Android -> Plays on Android and iOS
If recorded by iOS -> Only plays on iOS
Two samples of recorded audio by android and ios. Not even quicktime is able to play audio recorded by iOS.
I solved the problem on iOS, changing NightlyRecorderService by Plugin.AudioRecorder. When the audio is recorded, I convert it to m4u, what allows to achieve a lightweight audio file and be reproduced in any device.
As this solution uses iOS specific code, I opted by to create an interface:
IConvertAudioFile.cs in Shared Project:
public interface IConvertAudioFile
{
System.Threading.Tasks.Task<string> ConvertAudioFile(Plugin.AudioRecorder.AudioRecorderService gravadorIos);
}
RecordAudio.cs in Shared Project:
// instantiate the recorder according to your needs:
static AudioRecorderService gravadorIos = new AudioRecorderService()
{
StopRecordingOnSilence = false,
SilenceThreshold = 0,
PreferredSampleRate = 24000
};
// the code that is called when you click to stop recording:
if (Device.RuntimePlatform == Device.iOS)
{
await gravadorIos.StopRecording();
_ = Task.Run(async () =>
{
try
{
string filePath2Upload = await DependencyService.Get<IConvertAudioFile>().ConvertAudioFile(gravadorIos);
ShareAudio(filePath2Upload);
}
catch (Exception err)
{
#if DEBUG
Console.WriteLine("++++++++ ERROR WHILE EXPORTING " + err.Message);
#endif
}
});
}
IConvertAudioFile in iOS Project:
using System;
using System.IO;
using System.Threading.Tasks;
using ThisApp.iOS;
using UIKit;
using Xamarin.Forms;
[assembly: Dependency(typeof(ConvertAudioService))]
namespace ThisApp.iOS
{
public class ConvertAudioService : IConvertAudioFile
{
public async Task<string> ConvertAudioFile(Plugin.AudioRecorder.AudioRecorderService gravadorIos)
{
var asset = AVFoundation.AVAsset.FromUrl(Foundation.NSUrl.FromFilename(gravadorIos.GetAudioFilePath()));
var export = new AVFoundation.AVAssetExportSession(asset, AVFoundation.AVAssetExportSession.PresetAppleM4A);
string u = Path.Combine(Path.GetTempPath(), "temp_last_audio.m4a");
if (File.Exists(u))
File.Delete(u);
export.OutputUrl = Foundation.NSUrl.FromFilename(u);
export.OutputFileType = AVFoundation.AVFileType.AppleM4A;
export.ShouldOptimizeForNetworkUse = true;
string returnString="";
await export.ExportTaskAsync();
if (export.Error != null)
{
#if DEBUG
Console.WriteLine("++++++++ ERROR WHILE EXPORTING WAV TO MP4: LETS USE WAV INSTEAD");
#endif
returnString = gravadorIos.GetAudioFilePath();
}
else
{
#if DEBUG
Console.WriteLine("++++++++ SUCCESS EXPORTED WAV TO MP4");
#endif
returnString = u;
}
return returnString;
}
}
}