I need to record some audio, maybe even video, using Media API in JS, example for Blazor. Then, I would like to pass recorded Blob
content from JS to Blazor. As far as this is audio or video content, it can be pretty big.
What I have tried so far
Encoding data as ANSI string or passing an array of integers. This results in InvalidDataException
, SignalR disconnect, timeout, after a minute or so SignalR gets back to life and C# receives null
Encoding data as base 64 or passing UInt8Array
. Outcome is the same.
Passing Blob
, ArrayBuffer
, or FormData
directly from JS. This results in empty object ValueKind: {}
in C#.
The source of JS call
recorder.stop();
recorder.exportWAV(async blob => {
const content = await (new Response(blob).arrayBuffer());
const contentNums = new Uint8Array(content);
const contentCodes = new TextDecoder('windows-1252').decode(contentNums);
const data = new FormData();
data.append('file', blob, 'Demo');
//success(window.URL.createObjectURL(blob));
console.log(blob)
console.log(content)
console.log(contentNums)
console.log(contentCodes)
success(Array.from(contentNums));
})
The source of C# interop call
private IJSRuntime _scriptRuntime = null;
public async Task<dynamic> GetMedia<dynamic>()
{
return await _scriptRuntime.InvokeAsync<dynamic>('AudioFunctions.GetMediaFile');
}
Is there a way to pass large byte arrays or at least strings from JS to Blazor.NET?
Appears to be, the hack with ANSI encoding works fine, I just needed to increase the size of SignalR
messages.
Startup
public void ConfigureServices(IServiceCollection services)
{
// ... some other services
services.AddSignalR(o =>
{
o.EnableDetailedErrors = true;
o.MaximumReceiveMessageSize = long.MaxValue;
});
}
JS interop
window.InteropFunctions = window.InteropFunctions || {
GetMediaFile: async (classInstance, cssClass) => {
return await new Promise((success, error) => {
const chunks = [];
const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
// Event handlers for play and stop to detect when data is available
recorder.addEventListener('stop', {
// Encode data as ANSI string - Windows 1252
const response = new Blob(chunks, { type: 'audio/webm' });
const content = await (new Response(response).arrayBuffer());
const contentNums = new Uint8Array(content);
const contentCodes = new TextDecoder('windows-1252').decode(contentNums);
const audioControl = document.querySelector('.' + cssClass + ' audio');
// Play audio in HTML 5 control
if (audioControl) {
audioControl.src = URL.createObjectURL(response);
}
// Resolve the promise and send data to Blazor.NET
success(contentCodes);
});
// Grab recorded data
recorder.addEventListener('dataavailable', {
if (e.data.size > 0) {
chunks.push(e.data);
}
});
// Record for 5 seconds
recorder.start();
setTimeout(() => recorder.stop(), 5000);
});
}
}
C# Interop
private IJSRuntime _scriptRuntime = null;
public async Task<string> GetMediaFile<string>(params object[] inputs)
{
return await _scriptRuntime.InvokeAsync<string>("InteropFunctions.GetMediaFile", inputs);
}
Initiate interop call from .NET
var instance = DotNetObjectReference.Create(this);
var source = await _audioCommandInstance.GetMediaFile(instance, "audio-container");
if (string.IsNullOrEmpty(source) == false)
{
var audioBytes = Encoder.GetBytes(source);
await File.WriteAllBytesAsync("audio.wav", audioBytes);
}