I am recording audio in Expo React Native using expo-av and expo-file-system, saving the recorded file as an MP3, and then uploading it to the server. When I test it using Postman, everything works fine. However, when I try to upload the file in my code, the API returns a (400) Bad Request error. Here is my code:
const [recording, setRecording] = useState(null);
const [voice, setVoice] = useState('uk');
const [isRecording, setIsRecording] = useState(false);
const startRecording = async () => {
try {
const { status } = await Audio.requestPermissionsAsync();
if (status !== 'granted') {
console.error('Permission to access audio was denied');
return;
}
const recording = new Audio.Recording();
await recording.prepareToRecordAsync(Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY);
await recording.startAsync();
setRecording(recording);
setIsRecording(true);
} catch (error) {
console.error('Error starting recording: ', error);
}
};
const stopRecording = async () => {
try {
await recording.stopAndUnloadAsync();
setIsRecording(false);
const fileDirectory = FileSystem.documentDirectory || FileSystem.cacheDirectory;
const fileName = 'recording.mp3';
const filePath = `${fileDirectory}${fileName}`;
await FileSystem.moveAsync({
from: recording.getURI(),
to: filePath,
});
const formData = new FormData();
formData.append('file', {
uri: filePath,
type: 'audio/mpeg',
name: 'recording.mp3',
});
formData.append('expectedText', 'Hello');
formData.append('extension', 'mp3');
const response = await axios.post(
`http://192.168.10.16/erp-pronounciation/pronunciation/file/uk`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
console.log('Upload successful', response.data);
} catch (error) {
console.error('Error stopping recording or saving file: ', error);
}
};
backend code using .Net core:
using System.Diagnostics;
using System.Dynamic;
using System.Net;
using System.Text;
using Navi.SundayQ.Pronunciation.Extensions;
using Navi.SundayQ.Pronunciation.Models.Pronunciation;
using Newtonsoft.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var configuration = builder.Configuration;
const string outputAudioExt = ".wav";
app.Use(async (context, next) =>
{
if (context.Request.Method == "OPTIONS")
{
context.Response.StatusCode = 200;
await WriteCorsAsync(context);
await context.Response.WriteAsync("");
}
else
{
context.Response.OnStarting(WriteCorsAsync, context);
await next(context);
}
static Task WriteCorsAsync(object state)
{
var context = (HttpContext)state;
context.Response.Headers.Add("Access-Control-Allow-Origin", context.Request.Headers.Origin);
context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
context.Response.Headers.Add("Access-Control-Allow-Methods",
string.Join(", ",
new[] { context.Request.Method }.Union(
context.Request.Headers.AccessControlRequestMethod.Select(v => v))));
context.Response.Headers.Add("Access-Control-Allow-Headers",
string.Join(", ",
context.Request.Headers.Select(p => p.Key)
.Union(context.Request.Headers.AccessControlRequestHeaders.Select(v => v))));
return Task.CompletedTask;
}
});
app.MapPost("/pronunciation/file/{accent:regex(^u(s|k)$)}",
(HttpContext context, ILogger<Program> logger, string accent) =>
{
string expectedText = context.Request.Form["expectedText"];
if (string.IsNullOrEmpty(expectedText))
expectedText = context.Request.Query["expectedText"];
if (string.IsNullOrEmpty(expectedText))
return Results.BadRequest();
var file = context.Request.Form.Files["file"];
if (file == null)
return Results.BadRequest();
var extension = Path.GetExtension(file.FileName)?.ToLower();
if (!new[] { ".wav", ".mp3", ".ogg" }.Contains(extension))
return Results.BadRequest();
string audioBase64 = null;
try
{
if (extension.Equals(outputAudioExt))
{
using var ms = new MemoryStream();
file.CopyTo(ms);
audioBase64 = Convert.ToBase64String(ms.ToArray());
}
else
{
var dir = Path.Combine(Directory.GetCurrentDirectory(), "Shared", "Temp");
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
var fileInput = Path.Combine(dir, Guid.NewGuid() + extension);
using var s = File.Create(fileInput);
file.CopyTo(s);
s.Close();
s.Dispose();
audioBase64 = ConvertAudiAndGetBase64(fileInput);
}
}
catch (Exception e)
{
logger.LogError(e, "ERROR");
}
if (string.IsNullOrEmpty(audioBase64))
return Results.Problem();
try
{
var result = GetPronunciationResult(accent, expectedText, audioBase64);
return Results.Json(result);
}
catch (Exception e)
{
logger.LogError(e, "ERROR");
}
return Results.StatusCode(500);
});
string ConvertAudiAndGetBase64(string fileInput)
{
var fileOutput = fileInput + outputAudioExt;
var cmdFfmpeg = configuration.GetValue<string>("FfmpegCmd");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = cmdFfmpeg,
Arguments = $"-y -i \"{fileInput}\" \"{fileOutput}\"",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
process.Start();
process.WaitForExit();
string audioBase64 = null;
if (File.Exists(fileOutput))
{
audioBase64 = Convert.ToBase64String(File.ReadAllBytes(fileOutput));
File.Delete(fileOutput);
}
if (File.Exists(fileInput))
File.Delete(fileInput);
return audioBase64;
}
ExpandoObject GetPronunciationResult(string accent, string expectedText, string base64)
{
using var webClient = new WebClient();
webClient.Encoding = Encoding.UTF8;
webClient.Headers.Add("x-blobr-key", configuration.GetValue<string>("PronunciationApiKey"));
webClient.Headers.Add("Content-Type", "application/json");
var json =
webClient.UploadString(configuration.GetValue<string>("PronunciationApiUrl").Replace("{accent}", accent),
JsonConvert.SerializeObject(new
{
audio_base64 = base64,
audio_format = "wav",
expected_text = expectedText
}));
return JsonConvert.DeserializeObject<ExpandoObject>(json);
}
app.Run();