@gjsify/webaudio 0.4.0 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/gst-player.ts DELETED
@@ -1,185 +0,0 @@
1
- // GStreamer playback pipeline: AudioBuffer PCM → audio output
2
- //
3
- // Pipeline: appsrc(F32LE) → audioconvert → volume → autoaudiosink
4
- // Each GstPlayer instance is single-use (matches W3C AudioBufferSourceNode).
5
- //
6
- // Reference: GStreamer 1.0 via gi://Gst
7
-
8
- import GLib from 'gi://GLib?version=2.0';
9
- import { ensureGstInit, Gst } from './gst-init.js';
10
- import type { AudioBuffer } from './audio-buffer.js';
11
-
12
- // Force GstApp typelib load
13
- import GstApp from 'gi://GstApp?version=1.0';
14
- void GstApp;
15
-
16
- export interface GstPlayerOptions {
17
- audioBuffer: AudioBuffer;
18
- volume: number;
19
- loop: boolean;
20
- offset: number; // start offset in seconds
21
- duration?: number; // play duration in seconds (undefined = full)
22
- playbackRate: number;
23
- onEnded: () => void;
24
- }
25
-
26
- /**
27
- * Manages a single GStreamer playback pipeline for one AudioBufferSourceNode.start() call.
28
- */
29
- export class GstPlayer {
30
- private _pipeline: any = null;
31
- private _volumeElement: any = null;
32
- private _busWatchId: number | null = null;
33
- private _ended = false;
34
- private _loop: boolean;
35
- private _onEnded: () => void;
36
- private _audioBuffer: AudioBuffer;
37
-
38
- constructor(options: GstPlayerOptions) {
39
- ensureGstInit();
40
- this._loop = options.loop;
41
- this._onEnded = options.onEnded;
42
- this._audioBuffer = options.audioBuffer;
43
-
44
- const { audioBuffer, volume, offset, duration, playbackRate } = options;
45
- const sr = audioBuffer.sampleRate;
46
- const ch = audioBuffer.numberOfChannels;
47
-
48
- // Build interleaved PCM data
49
- const pcmData = this._interleave(audioBuffer, offset, duration);
50
- if (pcmData.length === 0) {
51
- // Empty buffer — fire ended immediately
52
- this._fireEnded();
53
- return;
54
- }
55
-
56
- // Build pipeline — format=3 (TIME) ensures downstream gets TIME-based
57
- // segments, preventing gst_segment_to_stream_time assertion failures.
58
- const capsStr = `audio/x-raw,format=F32LE,rate=${sr},channels=${ch},layout=interleaved`;
59
- const desc = `appsrc name=src caps="${capsStr}" format=3 ! audioconvert ! volume name=vol ! autoaudiosink`;
60
- this._pipeline = Gst.parse_launch(desc);
61
- this._volumeElement = this._pipeline.get_by_name('vol');
62
- const appsrc = this._pipeline.get_by_name('src')!;
63
-
64
- // Set volume
65
- this._volumeElement.set_property('volume', Math.max(0, Math.min(volume, 10)));
66
-
67
- // Set up bus watch for EOS/ERROR
68
- const bus = this._pipeline.get_bus();
69
- this._busWatchId = bus.add_watch(0, (_bus: any, msg: any) => {
70
- if (msg.type === Gst.MessageType.EOS) {
71
- if (this._loop && !this._ended) {
72
- // Restart: push data again
73
- this._restartPlayback(appsrc, pcmData);
74
- } else {
75
- this._fireEnded();
76
- }
77
- } else if (msg.type === Gst.MessageType.ERROR) {
78
- this._fireEnded();
79
- }
80
- return true; // keep watching
81
- });
82
-
83
- // Push PCM data with proper timestamps for TIME-format segments
84
- const gstBuf = Gst.Buffer.new_wrapped(pcmData);
85
- const totalFrames = pcmData.length / (4 * ch);
86
- gstBuf.pts = 0;
87
- gstBuf.duration = Math.floor((totalFrames / sr) * Number(Gst.SECOND));
88
- appsrc.push_buffer(gstBuf);
89
- appsrc.end_of_stream();
90
-
91
- // Apply playback rate if not 1.0
92
- if (playbackRate !== 1.0) {
93
- this._pipeline.set_state(Gst.State.PAUSED);
94
- // Seek with rate change
95
- this._pipeline.seek(
96
- playbackRate,
97
- Gst.Format.TIME,
98
- Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE,
99
- Gst.SeekType.SET, 0,
100
- Gst.SeekType.NONE, -1
101
- );
102
- }
103
-
104
- this._pipeline.set_state(Gst.State.PLAYING);
105
- }
106
-
107
- /** Update volume on a running pipeline */
108
- setVolume(value: number): void {
109
- if (this._volumeElement && !this._ended) {
110
- this._volumeElement.set_property('volume', Math.max(0, Math.min(value, 10)));
111
- }
112
- }
113
-
114
- /** Update loop flag */
115
- setLoop(value: boolean): void {
116
- this._loop = value;
117
- }
118
-
119
- /** Stop playback and clean up */
120
- stop(): void {
121
- if (this._ended) return;
122
- this._fireEnded();
123
- }
124
-
125
- /** Whether playback has ended */
126
- get ended(): boolean {
127
- return this._ended;
128
- }
129
-
130
- private _restartPlayback(appsrc: any, pcmData: Uint8Array): void {
131
- // For looping: seek pipeline to start
132
- if (this._pipeline) {
133
- this._pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, 0);
134
- }
135
- }
136
-
137
- private _fireEnded(): void {
138
- if (this._ended) return;
139
- this._ended = true;
140
- this._cleanup();
141
- this._onEnded();
142
- }
143
-
144
- private _cleanup(): void {
145
- const pipeline = this._pipeline;
146
- this._pipeline = null;
147
- this._volumeElement = null;
148
- this._busWatchId = null;
149
-
150
- if (pipeline) {
151
- // Defer NULL-state transition to a low-priority idle so this method does not
152
- // block the GLib main loop when called from within the bus-watch callback (EOS/ERROR).
153
- // autoaudiosink teardown flushes the audio device, which can take several ms and
154
- // causes frame drops when SFX fire frequently during gameplay.
155
- GLib.idle_add(GLib.PRIORITY_LOW, () => {
156
- pipeline.set_state(Gst.State.NULL);
157
- return GLib.SOURCE_REMOVE;
158
- });
159
- }
160
- }
161
-
162
- /**
163
- * Interleave AudioBuffer's per-channel Float32Arrays into a single Uint8Array.
164
- * Applies offset (seconds) and optional duration (seconds).
165
- */
166
- private _interleave(buf: AudioBuffer, offsetSec: number, durationSec?: number): Uint8Array {
167
- const ch = buf.numberOfChannels;
168
- const startFrame = Math.min(Math.floor(offsetSec * buf.sampleRate), buf.length);
169
- const maxFrames = buf.length - startFrame;
170
- const frames = durationSec !== undefined
171
- ? Math.min(Math.floor(durationSec * buf.sampleRate), maxFrames)
172
- : maxFrames;
173
-
174
- if (frames <= 0) return new Uint8Array(0);
175
-
176
- const interleaved = new Float32Array(frames * ch);
177
- for (let frame = 0; frame < frames; frame++) {
178
- for (let c = 0; c < ch; c++) {
179
- interleaved[frame * ch + c] = buf._channelData[c][startFrame + frame];
180
- }
181
- }
182
-
183
- return new Uint8Array(interleaved.buffer);
184
- }
185
- }
@@ -1,76 +0,0 @@
1
- // HTMLAudioElement — format detection + basic playback via GStreamer playbin.
2
- // Used by Excalibur.js for canPlayType() format sniffing.
3
- //
4
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement
5
-
6
- import { ensureGstInit, Gst } from './gst-init.js';
7
-
8
- // GStreamer-supported MIME types (common on GNOME systems)
9
- const SUPPORTED_TYPES = new Set([
10
- 'audio/mpeg',
11
- 'audio/mp3',
12
- 'audio/wav',
13
- 'audio/x-wav',
14
- 'audio/ogg',
15
- 'audio/webm',
16
- 'audio/flac',
17
- 'audio/x-flac',
18
- 'audio/aac',
19
- 'audio/mp4',
20
- ]);
21
-
22
- export class HTMLAudioElement {
23
- src = '';
24
- volume = 1;
25
- loop = false;
26
- paused = true;
27
- currentTime = 0;
28
- duration = 0;
29
- readyState = 0;
30
-
31
- private _pipeline: any = null;
32
-
33
- canPlayType(type: string): CanPlayTypeResult {
34
- // Strip codecs parameter: "audio/ogg; codecs=vorbis" → "audio/ogg"
35
- const mime = type.split(';')[0].trim().toLowerCase();
36
- return SUPPORTED_TYPES.has(mime) ? 'maybe' : '';
37
- }
38
-
39
- play(): Promise<void> {
40
- if (!this.src) return Promise.resolve();
41
-
42
- ensureGstInit();
43
- this._cleanup();
44
-
45
- this._pipeline = Gst.ElementFactory.make('playbin', 'player');
46
- if (!this._pipeline) return Promise.resolve();
47
-
48
- this._pipeline.set_property('uri', this.src);
49
- this._pipeline.set_property('volume', this.volume);
50
- this._pipeline.set_state(Gst.State.PLAYING);
51
- this.paused = false;
52
-
53
- return Promise.resolve();
54
- }
55
-
56
- pause(): void {
57
- if (this._pipeline) {
58
- this._pipeline.set_state(Gst.State.PAUSED);
59
- this.paused = true;
60
- }
61
- }
62
-
63
- load(): void {
64
- this._cleanup();
65
- }
66
-
67
- addEventListener(_type: string, _listener: any): void {}
68
- removeEventListener(_type: string, _listener: any): void {}
69
-
70
- private _cleanup(): void {
71
- if (this._pipeline) {
72
- this._pipeline.set_state(Gst.State.NULL);
73
- this._pipeline = null;
74
- }
75
- }
76
- }
package/src/index.ts DELETED
@@ -1,14 +0,0 @@
1
- // Web Audio API for GJS — backed by GStreamer 1.0.
2
- //
3
- // This module has no side effects. Importing @gjsify/webaudio gives
4
- // named access to Web Audio classes but does NOT register globals.
5
- // Use @gjsify/webaudio/register to set globalThis.AudioContext, etc.
6
-
7
- export { AudioContext } from './audio-context.js';
8
- export { AudioBuffer } from './audio-buffer.js';
9
- export { AudioNode } from './audio-node.js';
10
- export { AudioDestinationNode } from './audio-destination-node.js';
11
- export { AudioBufferSourceNode } from './audio-buffer-source-node.js';
12
- export { GainNode } from './gain-node.js';
13
- export { AudioParam } from './audio-param.js';
14
- export { HTMLAudioElement } from './html-audio-element.js';
package/src/register.ts DELETED
@@ -1,17 +0,0 @@
1
- // Side-effect module: registers Web Audio API globals on GJS.
2
- // On Node.js the alias layer routes this to @gjsify/empty.
3
-
4
- import { AudioContext, HTMLAudioElement } from './index.js';
5
-
6
- if (typeof (globalThis as any).AudioContext === 'undefined') {
7
- (globalThis as any).AudioContext = AudioContext;
8
- }
9
- if (typeof (globalThis as any).webkitAudioContext === 'undefined') {
10
- (globalThis as any).webkitAudioContext = AudioContext;
11
- }
12
- if (typeof (globalThis as any).Audio === 'undefined') {
13
- (globalThis as any).Audio = HTMLAudioElement;
14
- }
15
- if (typeof (globalThis as any).HTMLAudioElement === 'undefined') {
16
- (globalThis as any).HTMLAudioElement = HTMLAudioElement;
17
- }
package/src/test.mts DELETED
@@ -1,5 +0,0 @@
1
- import webaudioSpec from './webaudio.spec.js';
2
-
3
- const results = {
4
- webaudio: await webaudioSpec(),
5
- };
@@ -1,351 +0,0 @@
1
- // Web Audio API tests — GJS only (requires GStreamer)
2
- //
3
- // Ported from W3C Web Audio API spec behavior.
4
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API
5
-
6
- import { describe, it, expect } from '@gjsify/unit';
7
- import { AudioContext } from './audio-context.js';
8
- import { AudioBuffer } from './audio-buffer.js';
9
- import { AudioParam } from './audio-param.js';
10
- import { AudioNode } from './audio-node.js';
11
- import { GainNode } from './gain-node.js';
12
- import { AudioBufferSourceNode } from './audio-buffer-source-node.js';
13
- import { AudioDestinationNode } from './audio-destination-node.js';
14
- import { HTMLAudioElement } from './html-audio-element.js';
15
-
16
- /** Generate a minimal WAV ArrayBuffer (mono, 16-bit PCM, 440Hz sine) */
17
- function createTestWav(durationSec = 0.1, sampleRate = 44100): ArrayBuffer {
18
- const numSamples = Math.floor(durationSec * sampleRate);
19
- const bitsPerSample = 16;
20
- const numChannels = 1;
21
- const dataSize = numSamples * numChannels * (bitsPerSample / 8);
22
- const buf = new ArrayBuffer(44 + dataSize);
23
- const view = new DataView(buf);
24
- const writeStr = (offset: number, str: string) => {
25
- for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));
26
- };
27
- writeStr(0, 'RIFF');
28
- view.setUint32(4, 36 + dataSize, true);
29
- writeStr(8, 'WAVE');
30
- writeStr(12, 'fmt ');
31
- view.setUint32(16, 16, true);
32
- view.setUint16(20, 1, true);
33
- view.setUint16(22, numChannels, true);
34
- view.setUint32(24, sampleRate, true);
35
- view.setUint32(28, sampleRate * numChannels * bitsPerSample / 8, true);
36
- view.setUint16(32, numChannels * bitsPerSample / 8, true);
37
- view.setUint16(34, bitsPerSample, true);
38
- writeStr(36, 'data');
39
- view.setUint32(40, dataSize, true);
40
- for (let i = 0; i < numSamples; i++) {
41
- const sample = Math.sin(2 * Math.PI * 440 * i / sampleRate);
42
- view.setInt16(44 + i * 2, Math.round(sample * 32767), true);
43
- }
44
- return buf;
45
- }
46
-
47
- export default async () => {
48
- await describe('AudioContext', async () => {
49
- await it('should start in suspended state', async () => {
50
- const ctx = new AudioContext();
51
- expect(ctx.state).toBe('suspended');
52
- });
53
-
54
- await it('should transition to running on resume', async () => {
55
- const ctx = new AudioContext();
56
- await ctx.resume();
57
- expect(ctx.state).toBe('running');
58
- });
59
-
60
- await it('should transition to suspended on suspend', async () => {
61
- const ctx = new AudioContext();
62
- await ctx.resume();
63
- await ctx.suspend();
64
- expect(ctx.state).toBe('suspended');
65
- });
66
-
67
- await it('should transition to closed on close', async () => {
68
- const ctx = new AudioContext();
69
- await ctx.close();
70
- expect(ctx.state).toBe('closed');
71
- });
72
-
73
- await it('should have sampleRate of 44100', async () => {
74
- const ctx = new AudioContext();
75
- expect(ctx.sampleRate).toBe(44100);
76
- });
77
-
78
- await it('should have a destination node', async () => {
79
- const ctx = new AudioContext();
80
- expect(ctx.destination).toBeDefined();
81
- expect(ctx.destination instanceof AudioDestinationNode).toBe(true);
82
- });
83
- });
84
-
85
- await describe('AudioContext.currentTime', async () => {
86
- await it('should be a number >= 0', async () => {
87
- const ctx = new AudioContext();
88
- expect(typeof ctx.currentTime).toBe('number');
89
- expect(ctx.currentTime >= 0).toBe(true);
90
- });
91
-
92
- await it('should increase monotonically', async () => {
93
- const ctx = new AudioContext();
94
- const t1 = ctx.currentTime;
95
- // Small busy-wait to ensure time passes
96
- const start = Date.now();
97
- while (Date.now() - start < 5) { /* busy wait */ }
98
- const t2 = ctx.currentTime;
99
- expect(t2 > t1).toBe(true);
100
- });
101
- });
102
-
103
- await describe('AudioContext.createBuffer', async () => {
104
- await it('should create a buffer with correct properties', async () => {
105
- const ctx = new AudioContext();
106
- const buf = ctx.createBuffer(2, 44100, 44100);
107
- expect(buf.numberOfChannels).toBe(2);
108
- expect(buf.length).toBe(44100);
109
- expect(buf.sampleRate).toBe(44100);
110
- expect(buf.duration).toBe(1);
111
- });
112
- });
113
-
114
- await describe('AudioBuffer', async () => {
115
- await it('should return Float32Array from getChannelData', async () => {
116
- const buf = new AudioBuffer({ numberOfChannels: 2, length: 100, sampleRate: 44100 });
117
- const ch0 = buf.getChannelData(0);
118
- expect(ch0 instanceof Float32Array).toBe(true);
119
- expect(ch0.length).toBe(100);
120
- });
121
-
122
- await it('should throw on invalid channel index', async () => {
123
- const buf = new AudioBuffer({ numberOfChannels: 1, length: 10, sampleRate: 44100 });
124
- expect(() => buf.getChannelData(1)).toThrow();
125
- });
126
-
127
- await it('should support copyFromChannel/copyToChannel round-trip', async () => {
128
- const buf = new AudioBuffer({ numberOfChannels: 1, length: 4, sampleRate: 44100 });
129
- const src = new Float32Array([0.1, 0.2, 0.3, 0.4]);
130
- buf.copyToChannel(src, 0);
131
- const dst = new Float32Array(4);
132
- buf.copyFromChannel(dst, 0);
133
- expect(dst[0]).toBe(src[0]);
134
- expect(dst[3]).toBe(src[3]);
135
- });
136
-
137
- await it('should calculate duration correctly', async () => {
138
- const buf = new AudioBuffer({ numberOfChannels: 1, length: 22050, sampleRate: 44100 });
139
- expect(buf.duration).toBe(0.5);
140
- });
141
- });
142
-
143
- await describe('AudioContext.decodeAudioData', async () => {
144
- await it('should decode a WAV file', async () => {
145
- const ctx = new AudioContext();
146
- const wav = createTestWav(0.1, 44100);
147
- const buf = await ctx.decodeAudioData(wav);
148
- expect(buf instanceof AudioBuffer).toBe(true);
149
- expect(buf.sampleRate).toBe(44100);
150
- expect(buf.numberOfChannels).toBe(1);
151
- // Duration should be approximately 0.1s (allow some tolerance for decoder)
152
- expect(buf.duration > 0.09).toBe(true);
153
- expect(buf.duration < 0.15).toBe(true);
154
- });
155
-
156
- await it('should produce non-zero PCM data', async () => {
157
- const ctx = new AudioContext();
158
- const wav = createTestWav(0.1, 44100);
159
- const buf = await ctx.decodeAudioData(wav);
160
- const data = buf.getChannelData(0);
161
- let nonZero = 0;
162
- for (let i = 0; i < data.length; i++) {
163
- if (data[i] !== 0) nonZero++;
164
- }
165
- expect(nonZero > data.length * 0.9).toBe(true);
166
- });
167
-
168
- await it('should call success callback', async () => {
169
- const ctx = new AudioContext();
170
- const wav = createTestWav(0.05);
171
- let callbackResult: AudioBuffer | null = null;
172
- await ctx.decodeAudioData(wav, (buf) => { callbackResult = buf; });
173
- expect(callbackResult).toBeDefined();
174
- expect(callbackResult!.numberOfChannels).toBe(1);
175
- });
176
-
177
- await it('should reject invalid data', async () => {
178
- const ctx = new AudioContext();
179
- const invalid = new ArrayBuffer(10);
180
- let rejected = false;
181
- try {
182
- await ctx.decodeAudioData(invalid);
183
- } catch {
184
- rejected = true;
185
- }
186
- expect(rejected).toBe(true);
187
- });
188
- });
189
-
190
- await describe('AudioNode', async () => {
191
- await it('should connect and disconnect', async () => {
192
- const a = new AudioNode();
193
- const b = new AudioNode();
194
- a.connect(b);
195
- expect(a._outputs.has(b)).toBe(true);
196
- expect(b._inputs.has(a)).toBe(true);
197
- a.disconnect(b);
198
- expect(a._outputs.has(b)).toBe(false);
199
- expect(b._inputs.has(a)).toBe(false);
200
- });
201
-
202
- await it('should return destination from connect()', async () => {
203
- const a = new AudioNode();
204
- const b = new AudioNode();
205
- const result = a.connect(b);
206
- expect(result).toBe(b);
207
- });
208
-
209
- await it('should disconnect all on disconnect()', async () => {
210
- const a = new AudioNode();
211
- const b = new AudioNode();
212
- const c = new AudioNode();
213
- a.connect(b);
214
- a.connect(c);
215
- a.disconnect();
216
- expect(a._outputs.size).toBe(0);
217
- });
218
- });
219
-
220
- await describe('GainNode', async () => {
221
- await it('should have gain AudioParam with default 1', async () => {
222
- const gain = new GainNode();
223
- expect(gain.gain instanceof AudioParam).toBe(true);
224
- expect(gain.gain.value).toBe(1);
225
- });
226
-
227
- await it('should allow setting gain value', async () => {
228
- const gain = new GainNode();
229
- gain.gain.value = 0.5;
230
- expect(gain.gain.value).toBe(0.5);
231
- });
232
- });
233
-
234
- await describe('AudioParam', async () => {
235
- await it('should have correct default value', async () => {
236
- const param = new AudioParam(0.75);
237
- expect(param.value).toBe(0.75);
238
- expect(param.defaultValue).toBe(0.75);
239
- });
240
-
241
- await it('should clamp to min/max', async () => {
242
- const param = new AudioParam(0, 0, 1);
243
- param.value = 2;
244
- expect(param.value).toBe(1);
245
- param.value = -1;
246
- expect(param.value).toBe(0);
247
- });
248
-
249
- await it('should call onChange callback', async () => {
250
- const param = new AudioParam(0);
251
- let called = false;
252
- param._onChange = () => { called = true; };
253
- param.value = 0.5;
254
- expect(called).toBe(true);
255
- });
256
- });
257
-
258
- await describe('AudioBufferSourceNode', async () => {
259
- await it('should have default properties', async () => {
260
- const node = new AudioBufferSourceNode();
261
- expect(node.buffer).toBeNull();
262
- expect(node.loop).toBe(false);
263
- expect(node.playbackRate.value).toBe(1);
264
- });
265
-
266
- await it('should throw if started twice', async () => {
267
- const node = new AudioBufferSourceNode();
268
- node.buffer = new AudioBuffer({ numberOfChannels: 1, length: 100, sampleRate: 44100 });
269
- const gain = new GainNode();
270
- const dest = new AudioDestinationNode();
271
- node.connect(gain).connect(dest);
272
- node.start();
273
- expect(() => node.start()).toThrow();
274
- });
275
-
276
- await it('should fire onended after playback completes', async () => {
277
- const ctx = new AudioContext();
278
- const wav = createTestWav(0.05, 44100); // 50ms
279
- const buf = await ctx.decodeAudioData(wav);
280
-
281
- const source = ctx.createBufferSource();
282
- const gain = ctx.createGain();
283
- gain.gain.value = 0; // silent
284
- source.buffer = buf;
285
- source.connect(gain).connect(ctx.destination);
286
-
287
- let ended = false;
288
- source.onended = () => { ended = true; };
289
- source.start();
290
-
291
- // Wait for playback to finish (50ms buffer + margin)
292
- await new Promise<void>(resolve => setTimeout(resolve, 500));
293
- expect(ended).toBe(true);
294
- });
295
-
296
- await it('should not fire onended while looping', async () => {
297
- const ctx = new AudioContext();
298
- const wav = createTestWav(0.05, 44100); // 50ms
299
- const buf = await ctx.decodeAudioData(wav);
300
-
301
- const source = ctx.createBufferSource();
302
- const gain = ctx.createGain();
303
- gain.gain.value = 0; // silent
304
- source.buffer = buf;
305
- source.loop = true;
306
- source.connect(gain).connect(ctx.destination);
307
-
308
- let ended = false;
309
- source.onended = () => { ended = true; };
310
- source.start();
311
-
312
- // Wait longer than one full play cycle
313
- await new Promise<void>(resolve => setTimeout(resolve, 300));
314
- expect(ended).toBe(false);
315
-
316
- // Explicitly stop — should fire onended
317
- source.stop();
318
- expect(ended).toBe(true);
319
- });
320
-
321
- await it('should support connect chain source→gain→destination', async () => {
322
- const ctx = new AudioContext();
323
- const source = ctx.createBufferSource();
324
- const gain = ctx.createGain();
325
- const result = source.connect(gain).connect(ctx.destination);
326
- expect(result).toBe(ctx.destination);
327
- expect(source._outputs.has(gain)).toBe(true);
328
- expect(gain._outputs.has(ctx.destination)).toBe(true);
329
- });
330
- });
331
-
332
- await describe('HTMLAudioElement', async () => {
333
- await it('should return maybe for supported types', async () => {
334
- const audio = new HTMLAudioElement();
335
- expect(audio.canPlayType('audio/mpeg')).toBe('maybe');
336
- expect(audio.canPlayType('audio/wav')).toBe('maybe');
337
- expect(audio.canPlayType('audio/ogg')).toBe('maybe');
338
- });
339
-
340
- await it('should return empty for unsupported types', async () => {
341
- const audio = new HTMLAudioElement();
342
- expect(audio.canPlayType('audio/unknown')).toBe('');
343
- expect(audio.canPlayType('video/mp4')).toBe('');
344
- });
345
-
346
- await it('should handle codecs parameter', async () => {
347
- const audio = new HTMLAudioElement();
348
- expect(audio.canPlayType('audio/ogg; codecs=vorbis')).toBe('maybe');
349
- });
350
- });
351
- };
package/tsconfig.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "ESNext",
4
- "types": [
5
- "node",
6
- "@girs/gjs",
7
- "@girs/gst-1.0",
8
- "@girs/gstapp-1.0",
9
- "@girs/gstaudio-1.0",
10
- "@girs/gstbase-1.0",
11
- "@girs/glib-2.0"
12
- ],
13
- "target": "ESNext",
14
- "experimentalDecorators": true,
15
- "emitDeclarationOnly": true,
16
- "declaration": true,
17
- "outDir": "lib",
18
- "rootDir": "src",
19
- "declarationDir": "lib/types",
20
- "composite": true,
21
- "moduleResolution": "bundler",
22
- "allowImportingTsExtensions": true,
23
- "skipLibCheck": true,
24
- "allowJs": true,
25
- "checkJs": false,
26
- "strict": false
27
- },
28
- "reflection": false,
29
- "include": [
30
- "src/**/*.ts"
31
- ],
32
- "exclude": [
33
- "src/test.ts",
34
- "src/test.mts"
35
- ]
36
- }