implementacao de interrupcao em audio streming
This commit is contained in:
204
tests/js/pcm-player.test.mjs
Normal file
204
tests/js/pcm-player.test.mjs
Normal file
@@ -0,0 +1,204 @@
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { PCMPlayer } from
|
||||
"../../src/voice_assistant/audio_gateway/web/pcm-player.js";
|
||||
|
||||
class FakeAudioSource {
|
||||
constructor(context) {
|
||||
this.context = context;
|
||||
this.buffer = null;
|
||||
this.onended = null;
|
||||
this.startedAt = null;
|
||||
this.stopped = false;
|
||||
}
|
||||
|
||||
connect() {}
|
||||
|
||||
disconnect() {}
|
||||
|
||||
start(time) {
|
||||
this.startedAt = time;
|
||||
this.context.sources.push(this);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.stopped = true;
|
||||
}
|
||||
|
||||
end() {
|
||||
this.onended?.();
|
||||
}
|
||||
}
|
||||
|
||||
class FakeAudioContext {
|
||||
constructor() {
|
||||
this.currentTime = 0;
|
||||
this.destination = {};
|
||||
this.sources = [];
|
||||
}
|
||||
|
||||
async resume() {}
|
||||
|
||||
createAnalyser() {
|
||||
return {
|
||||
fftSize: 1024,
|
||||
smoothingTimeConstant: 0.65,
|
||||
|
||||
connect() {},
|
||||
disconnect() {},
|
||||
|
||||
getFloatTimeDomainData(array) {
|
||||
array.fill(0);
|
||||
},
|
||||
|
||||
getByteFrequencyData(array) {
|
||||
array.fill(0);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
createBuffer(channels, length, sampleRate) {
|
||||
return {
|
||||
duration: length / sampleRate,
|
||||
copyToChannel(samples) {
|
||||
this.samples = samples;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
createBufferSource() {
|
||||
return new FakeAudioSource(this);
|
||||
}
|
||||
}
|
||||
|
||||
function pcm(values) {
|
||||
const buffer = new ArrayBuffer(values.length * 2);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
values.forEach((value, index) => {
|
||||
view.setInt16(index * 2, value, true);
|
||||
});
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
test("decodes signed PCM16 correctly", () => {
|
||||
const player = new PCMPlayer({
|
||||
audioContext: new FakeAudioContext(),
|
||||
});
|
||||
|
||||
const samples = player.decodePCM(
|
||||
pcm([-32768, 0, 16384, 32767])
|
||||
);
|
||||
|
||||
assert.equal(samples[0], -1);
|
||||
assert.equal(samples[1], 0);
|
||||
assert.equal(samples[2], 0.5);
|
||||
assert.ok(samples[3] > 0.99);
|
||||
});
|
||||
|
||||
test("schedules chunks in order", async () => {
|
||||
const context = new FakeAudioContext();
|
||||
const player = new PCMPlayer({
|
||||
audioContext: context,
|
||||
});
|
||||
|
||||
await player.enqueue(pcm([100, 200, 300, 400]));
|
||||
await player.enqueue(pcm([500, 600, 700, 800]));
|
||||
|
||||
assert.equal(context.sources.length, 2);
|
||||
|
||||
const first = context.sources[0];
|
||||
const second = context.sources[1];
|
||||
|
||||
assert.ok(second.startedAt >=
|
||||
first.startedAt + first.buffer.duration);
|
||||
});
|
||||
|
||||
test("waits for actual playback completion", async () => {
|
||||
const context = new FakeAudioContext();
|
||||
const player = new PCMPlayer({
|
||||
audioContext: context,
|
||||
});
|
||||
|
||||
await player.enqueue(pcm([100, 200]));
|
||||
|
||||
player.finish();
|
||||
|
||||
let completed = false;
|
||||
|
||||
const waiting = player.waitForCompletion().then(() => {
|
||||
completed = true;
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
assert.equal(completed, false);
|
||||
|
||||
context.sources[0].end();
|
||||
|
||||
await waiting;
|
||||
|
||||
assert.equal(completed, true);
|
||||
});
|
||||
|
||||
test("stop interrupts scheduled playback", async () => {
|
||||
const context = new FakeAudioContext();
|
||||
const player = new PCMPlayer({
|
||||
audioContext: context,
|
||||
});
|
||||
|
||||
await player.enqueue(pcm([100, 200]));
|
||||
|
||||
player.stop();
|
||||
|
||||
assert.equal(context.sources[0].stopped, true);
|
||||
assert.equal(player.sources.size, 0);
|
||||
assert.equal(player.playing, false);
|
||||
});
|
||||
|
||||
test("reset allows a new playback turn", async () => {
|
||||
const context = new FakeAudioContext();
|
||||
const player = new PCMPlayer({
|
||||
audioContext: context,
|
||||
});
|
||||
|
||||
await player.enqueue(pcm([100, 200]));
|
||||
player.finish();
|
||||
context.sources[0].end();
|
||||
|
||||
await player.waitForCompletion();
|
||||
|
||||
player.reset();
|
||||
|
||||
await player.enqueue(pcm([300, 400]));
|
||||
|
||||
assert.equal(context.sources.length, 2);
|
||||
assert.equal(player.finished, false);
|
||||
});
|
||||
|
||||
test("rejects invalid PCM buffers", () => {
|
||||
const player = new PCMPlayer({
|
||||
audioContext: new FakeAudioContext(),
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => player.decodePCM(new ArrayBuffer(3)),
|
||||
/Invalid PCM byte length/
|
||||
);
|
||||
});
|
||||
|
||||
test("exposes playback analyser", () => {
|
||||
const context = new FakeAudioContext();
|
||||
|
||||
const player = new PCMPlayer({
|
||||
audioContext: context,
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
player.getAnalyser(),
|
||||
player.analyser
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user