using Amphora; using System; using System.Runtime.InteropServices.JavaScript; namespace Susurrus.WebAudio { /// /// A streamed sound that hands the browser a quarter second at a time, which it schedules back to back. Every /// chunk is decoded into the same array. /// public class WebAudioStreamedSound : StreamedSound { /// /// The number of chunks kept scheduled ahead, so a pause of up to a second between updates is covered. /// public const int ChunksAhead = 4; private readonly byte[] chunk; private readonly int bytesPerFrame; /// /// Gets how many frames each chunk holds, which is about a quarter of a second. /// public int ChunkFrames { get; } public WebAudioStreamedSound(ISoundStream stream) : base(stream) { bytesPerFrame = ChannelCount * StreamFeed.BytesPerSample(stream.Format); ChunkFrames = Math.Max(1, stream.SampleRate / 4); chunk = new byte[ChunkFrames * bytesPerFrame]; } /// /// Schedules chunks on the browser voice in until are /// waiting, or the stream has nothing more to give. A failure, including a chunk the browser refuses, ends the /// feeding. /// public Status Refill(JSObject engine, int slot) { int queued = WebAudioInterop.StreamQueued(engine, slot); while (!IsFed && queued < ChunksAhead) { StatusOr filled = Feed(new ByteView(chunk)); if (!filled.IsOk) { return filled.Status; } if (filled.Value == 0) { break; } try { WebAudioInterop.StreamQueue(engine, slot, chunk, filled.Value * bytesPerFrame, Stream.SampleRate, ChannelCount, isFloat: Stream.Format == SampleFormat.Float32); } catch (JSException exception) { IsFed = true; return WebAudioFailures.BufferRejected.Fail(exception.Message); } ++queued; } return Status.Ok; } } }