Files
discord.js/packages/voice/__tests__/AudioResource.test.ts
Jiralite 77e767277b build: Upgrade dependencies and pnpm (#11420)
* build: upgrade dependencies

* build: upgrade pnpm

* test: fix voice

* build: regenerate file

* fix: reports by ESLint

* fix: docs errors

* build: downgrades

* build: no upstream bump

* build: discord-api-types 0.38.40

* build: pnpm 10.30.1

* fix: ignore @typescript-eslint/no-duplicate-type-constituents

* fix: jsdoc lint in api-extractor

* build: update template for ESLint

Co-authored-by: Almeida <github@almeidx.dev>

* chore: explicit TODO

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* chore: revert typings lint change

---------

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>
Co-authored-by: Almeida <github@almeidx.dev>
2026-02-20 17:44:38 +00:00

136 lines
4.9 KiB
TypeScript

import { Buffer } from 'node:buffer';
import process from 'node:process';
import { PassThrough, Readable } from 'node:stream';
import { opus, VolumeTransformer } from 'prism-media';
import { describe, test, expect, vitest, type MockedFunction, beforeAll, beforeEach } from 'vitest';
import { SILENCE_FRAME } from '../src/audio/AudioPlayer';
import { AudioResource, createAudioResource, NO_CONSTRAINT, VOLUME_CONSTRAINT } from '../src/audio/AudioResource';
import { findPipeline as _findPipeline, StreamType, TransformerType, type Edge } from '../src/audio/TransformerGraph';
vitest.mock('../src/audio/TransformerGraph');
async function wait() {
// eslint-disable-next-line no-promise-executor-return
return new Promise((resolve) => process.nextTick(resolve));
}
async function started(resource: AudioResource) {
while (!resource.started) {
await wait();
}
return resource;
}
const findPipeline = _findPipeline as unknown as MockedFunction<typeof _findPipeline>;
beforeAll(() => {
// @ts-expect-error: No type
findPipeline.mockImplementation((from: StreamType, constraint: (path: Edge[]) => boolean) => {
const base = [
{
cost: 1,
transformer: () => new PassThrough(),
type: TransformerType.FFmpegPCM,
},
];
if (constraint === VOLUME_CONSTRAINT) {
base.push({
cost: 1,
// Transformer type shouldn't matter: we are not testing prism-media, but rather the expectation that the stream is VolumeTransformer
transformer: () => new VolumeTransformer({ type: 's16le' } as any),
type: TransformerType.InlineVolume,
});
}
return base as any[];
});
});
beforeEach(() => {
findPipeline.mockClear();
});
describe('createAudioResource', () => {
test('Creates a resource from string path', () => {
const resource = createAudioResource('mypath.mp3');
expect(findPipeline).toHaveBeenCalledWith(StreamType.Arbitrary, NO_CONSTRAINT);
expect(resource.volume).toBeUndefined();
});
test('Creates a resource from string path (volume)', () => {
const resource = createAudioResource('mypath.mp3', { inlineVolume: true });
expect(findPipeline).toHaveBeenCalledWith(StreamType.Arbitrary, VOLUME_CONSTRAINT);
expect(resource.volume).toBeInstanceOf(VolumeTransformer);
});
test('Only infers type if not explicitly given', () => {
const resource = createAudioResource(new opus.Encoder({ rate: 48_000, channels: 2, frameSize: 960 }), {
inputType: StreamType.Arbitrary,
});
expect(findPipeline).toHaveBeenCalledWith(StreamType.Arbitrary, NO_CONSTRAINT);
expect(resource.volume).toBeUndefined();
});
test('Infers from opus.Encoder', () => {
const resource = createAudioResource(new opus.Encoder({ rate: 48_000, channels: 2, frameSize: 960 }), {
inlineVolume: true,
});
expect(findPipeline).toHaveBeenCalledWith(StreamType.Opus, VOLUME_CONSTRAINT);
expect(resource.volume).toBeInstanceOf(VolumeTransformer);
expect(resource.encoder).toBeInstanceOf(opus.Encoder);
});
test('Infers from opus.OggDemuxer', () => {
const resource = createAudioResource(new opus.OggDemuxer());
expect(findPipeline).toHaveBeenCalledWith(StreamType.Opus, NO_CONSTRAINT);
expect(resource.volume).toBeUndefined();
expect(resource.encoder).toBeUndefined();
});
test('Infers from opus.WebmDemuxer', () => {
const resource = createAudioResource(new opus.WebmDemuxer());
expect(findPipeline).toHaveBeenCalledWith(StreamType.Opus, NO_CONSTRAINT);
expect(resource.volume).toBeUndefined();
});
test('Infers from opus.Decoder', () => {
const resource = createAudioResource(new opus.Decoder({ rate: 48_000, channels: 2, frameSize: 960 }));
expect(findPipeline).toHaveBeenCalledWith(StreamType.Raw, NO_CONSTRAINT);
expect(resource.volume).toBeUndefined();
});
test('Infers from VolumeTransformer', () => {
// Transformer type shouldn't matter: we are not testing prism-media, but rather the expectation that the stream is VolumeTransformer
const stream = new VolumeTransformer({ type: 's16le' } as any);
const resource = createAudioResource(stream, { inlineVolume: true });
expect(findPipeline).toHaveBeenCalledWith(StreamType.Raw, NO_CONSTRAINT);
expect(resource.volume).toEqual(stream);
});
test('Falls back to Arbitrary for unknown stream type', () => {
const resource = createAudioResource(new PassThrough());
expect(findPipeline).toHaveBeenCalledWith(StreamType.Arbitrary, NO_CONSTRAINT);
expect(resource.volume).toBeUndefined();
});
test('Appends silence frames when ended', async () => {
const stream = Readable.from(Buffer.from([1]));
const resource = new AudioResource([], [stream], null, 5);
await started(resource);
expect(resource.readable).toEqual(true);
expect(resource.read()).toEqual(Buffer.from([1]));
for (let index = 0; index < 5; index++) {
await wait();
expect(resource.readable).toEqual(true);
expect(resource.read()).toEqual(SILENCE_FRAME);
}
await wait();
expect(resource.readable).toEqual(false);
expect(resource.read()).toEqual(null);
});
});