|
@@ -0,0 +1,120 @@
|
|
|
+using CSCore;
|
|
|
+using System.Runtime.InteropServices.JavaScript;
|
|
|
+using System.Runtime.Versioning;
|
|
|
+using CSCore.Codecs.FLAC;
|
|
|
+using Newtonsoft.Json;
|
|
|
+using Microsoft.AspNetCore.Components.WebAssembly.Http;
|
|
|
+
|
|
|
+namespace FNZCM.BlazorWasm.Utility
|
|
|
+{
|
|
|
+ [SupportedOSPlatform("browser")]
|
|
|
+ public static partial class WavePlayerModuleCsCore
|
|
|
+ {
|
|
|
+ public static int MsPerChunk { get; set; } = 150;
|
|
|
+ public static bool IsInit { get; private set; }
|
|
|
+
|
|
|
+ private static IWaveSource _waveSource;
|
|
|
+ private static ISampleSource _sampleSource;
|
|
|
+
|
|
|
+ public static async Task InitFlacAsync(string url)
|
|
|
+ {
|
|
|
+ Console.WriteLine("create wave source with uri");
|
|
|
+ Console.WriteLine("create http client");
|
|
|
+ var client = new HttpClient();
|
|
|
+ Console.WriteLine("build request message");
|
|
|
+ var req = new HttpRequestMessage(HttpMethod.Get, url);
|
|
|
+ req.SetBrowserResponseStreamingEnabled(true);
|
|
|
+ Console.WriteLine("send request");
|
|
|
+ var rsp = await client.SendAsync(req);
|
|
|
+ if (!rsp.IsSuccessStatusCode)
|
|
|
+ {
|
|
|
+ Console.WriteLine($"fail, {rsp.StatusCode}({(int)rsp.StatusCode}) , {rsp.ReasonPhrase}");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Console.WriteLine("get stream");
|
|
|
+ var flacStream = await rsp.Content.ReadAsStreamAsync();
|
|
|
+ _waveSource = new FlacFile(flacStream);
|
|
|
+
|
|
|
+ Console.WriteLine("create sample source");
|
|
|
+ _sampleSource = _waveSource.ToSampleSource();
|
|
|
+ Console.WriteLine("format:" + _sampleSource.WaveFormat);
|
|
|
+ Console.WriteLine("json:" + JsonConvert.SerializeObject(_sampleSource.WaveFormat));
|
|
|
+ Console.WriteLine("init wave player");
|
|
|
+ await InitAsync(_sampleSource);
|
|
|
+ Play();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static async Task InitAsync(ISampleSource sampleSource)
|
|
|
+ {
|
|
|
+ if (!IsInit)
|
|
|
+ {
|
|
|
+ await JSHost.ImportAsync("WavePlayer", "/lib/fnz/fnz-wave-player-module.js");
|
|
|
+ IsInit = true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ _sampleSource = sampleSource;
|
|
|
+ JsInit(_sampleSource.WaveFormat.Channels, _sampleSource.WaveFormat.SampleRate,nameof(WavePlayerModuleCsCore));
|
|
|
+ }
|
|
|
+
|
|
|
+ [JSImport("play", "WavePlayer")]
|
|
|
+ public static partial void Play();
|
|
|
+
|
|
|
+ [JSImport("stop", "WavePlayer")]
|
|
|
+ public static partial void Stop();
|
|
|
+
|
|
|
+ [JSExport]
|
|
|
+ public static double[] TakeChunk()
|
|
|
+ {
|
|
|
+ var sampleCountPerChannel = (int)(_sampleSource.WaveFormat.SampleRate * MsPerChunk / 1000f);
|
|
|
+
|
|
|
+ float[] sampleBuffer;
|
|
|
+ {
|
|
|
+ var remainSamples = sampleCountPerChannel * _sampleSource.WaveFormat.Channels;
|
|
|
+ sampleBuffer = new float[remainSamples];
|
|
|
+ var readIndex = 0;
|
|
|
+ while (remainSamples > 0)
|
|
|
+ {
|
|
|
+ var r = _sampleSource.Read(sampleBuffer, readIndex, sampleBuffer.Length - readIndex);
|
|
|
+ if (r == 0)
|
|
|
+ {
|
|
|
+ Array.Resize(ref sampleBuffer, readIndex);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ remainSamples -= r;
|
|
|
+ readIndex += r;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //de-multiplex
|
|
|
+ //re-order buffer
|
|
|
+ // 0 1 2 3 4 5
|
|
|
+ // L R L R L R
|
|
|
+ // → L L L R R R
|
|
|
+ // 0 2 4 1 3 5
|
|
|
+ //convert float to double for marshal
|
|
|
+
|
|
|
+ var marshalBuf = new double[sampleBuffer.Length];
|
|
|
+ var sampleIndex = 0;
|
|
|
+ for (var i = 0; i < marshalBuf.Length; i += _sampleSource.WaveFormat.Channels)
|
|
|
+ {
|
|
|
+ for (var c = 0; c < _sampleSource.WaveFormat.Channels; c++)
|
|
|
+ {
|
|
|
+ var srcIdx = i + c;
|
|
|
+ var dstIdx = sampleCountPerChannel * c + sampleIndex;
|
|
|
+ marshalBuf[dstIdx] = sampleBuffer[srcIdx];
|
|
|
+ }
|
|
|
+ sampleIndex++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return marshalBuf;
|
|
|
+ }
|
|
|
+
|
|
|
+ [JSImport("init", "WavePlayer")]
|
|
|
+ private static partial void JsInit(int channels, int sampleRate,string className);
|
|
|
+
|
|
|
+ [JSImport("feedChunk", "WavePlayer")]
|
|
|
+ private static partial void JsFeedChunk(double[] channel0, double[] channel2);
|
|
|
+ }
|
|
|
+}
|