Estoy creando un Blazor v0.5.1 proyecto que está utilizando una biblioteca .NET estándar a proporcionado toda la lógica de negocio. Esta biblioteca tiene varios archivos WAV almacenados como recursos incrustados.
El consumo de uno de los recursos en una tecnología .NET típica (WinForms, WPF, etc.) que hace referencia a esta biblioteca se puede hacer con esto:
var assemName = "MyLibName";
var assembly = AppDomain
.CurrentDomain
.GetAssemblies()
.First(a => a.GetName().Name == assemName);
//memory stream
var stream = assembly.GetManifestResourceStream($"{assemName}.mysound.wav");
//Can play the file at some point later
var player = new SoundPlayer(stream)
player.Play();
Me gustaría hacer el equivalente en Blazor. En este momento, tengo sonidos trabajando en la aplicación al copiar / pegar los archivos wav en la carpeta wwwroot\sounds
del proyecto para que se sirva como contenido HTML estático. Luego, en JavaScript, puedo jugar uno así:
const audio = new Audio"\sounds\mysound.wave"]);
audio.currentTime = 0;
audio.play();
Pero lo que realmente me gustaría hacer es evitar copiar y pegar y de alguna manera servir los archivos dinámicamente como puntos finales para que sea transparente para JS.
Bueno, pude conseguirlo. No estoy seguro si es de la mejor manera pero parece funcionar bien.
Tuve que añadir la extensión de almacenamiento blazor:
https://github.com/BlazorExtensions/Storage
que actúa como un proxy para el SessionStorage de JavaScript y LocalStorage de .NET. Una vez cargado, agregué cada archivo wav incrustado desde .NET de esta manera:
foreach (var kvp in SoundStreamDictionary)
{
await sessionStorage.SetItem(
kvp.Key.ToString().ToLower() //Key is the name of the sound
, kvp.Value.ToBase64() //Value is a Stream object
);
}
El ToBase64
es bastante estándar para la manipulación de secuencias .NET:
public static string ToBase64(this Stream stream)
{
byte[] bytes;
using (var memoryStream = new MemoryStream())
{
stream.Position = 0;
stream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
}
return Convert.ToBase64String(bytes);
}
Ahora todos los datos se almacenan en JavaScript SessionStorage como cadenas. El truco ahora es decodificar eso para audio. Con la ayuda de este método de ayuda JS (gracias a esta publicación de StackOverflow ):
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: contentType });
}
Ponga eso y esto en el archivo JsInterop.js de BlazorComponent:
const soundAudios = [];
window.JsSound = {
loadSounds: function (sounds) {
sounds.forEach(sound => {
//Blazor.Storage extension adds extra double quotes so trim
const str64 = sessionStorage.getItem(sound.path).slice(1, -1);
const blob = b64toBlob(str64, "audio/wav");
const blobUrl = URL.createObjectURL(blob);
const audio = new Audio(blobUrl);
soundAudios[sound.id] = audio;
});
return true;
},
play: function (id) {
const audio = soundAudios[id];
audio.currentTime = 0;
audio.play();
return true;
},
};
Y FINALMENTE los sonidos pueden ser invocados por nombre desde .Net:
private IDictionary<string, int> soundDict = new Dictionary<string, int>();
public Task<string> LoadSounds(IEnumerable<string> fileNames)
{
var sounds = fileNames
.Select(name =>
{
var snd = new
{
id = soundDict.Count,
path = name
};
soundDict.Add(name, snd.id);
return snd;
})
.ToList();
return JSRuntime.Current.InvokeAsync<string>(
"JsSound.loadSounds"
, sounds
);
}
public Task<string> Play(string name)
{
return JSRuntime.Current.InvokeAsync<string>(
"JsSound.play"
, soundDict[name]
);
}