@acpfx/audio-player 0.3.0 → 0.3.1
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/LICENSE +15 -0
- package/README.md +36 -0
- package/dist/index.js +337 -0
- package/dist/manifest.json +1 -0
- package/manifest.yaml +33 -0
- package/package.json +3 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 acpfx contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @acpfx/audio-player
|
|
2
|
+
|
|
3
|
+
Audio mixer with SFX support. Plays speech audio through the system speaker, with optional sound effects for agent thinking and tool use.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
This package is a pipeline node for [@acpfx/cli](../orchestrator/README.md). See the CLI package for installation and usage.
|
|
8
|
+
|
|
9
|
+
## Manifest
|
|
10
|
+
|
|
11
|
+
- **Consumes:** `audio.chunk`, `agent.thinking`, `agent.tool_start`, `agent.tool_done`, `agent.delta`, `agent.complete`, `control.interrupt`
|
|
12
|
+
- **Emits:** `audio.chunk`, `player.status`, `lifecycle.ready`, `lifecycle.done`
|
|
13
|
+
|
|
14
|
+
## Settings
|
|
15
|
+
|
|
16
|
+
| Name | Type | Default | Description |
|
|
17
|
+
|------|------|---------|-------------|
|
|
18
|
+
| `speechSource` | string | | Node name whose `audio.chunk` events are speech |
|
|
19
|
+
| `sampleRate` | number | `16000` | Audio sample rate in Hz |
|
|
20
|
+
| `thinkingClip` | string | | Path to thinking SFX audio clip |
|
|
21
|
+
| `toolClip` | string | | Path to tool-use SFX audio clip |
|
|
22
|
+
| `sfxVolume` | number | `0.3` | SFX volume multiplier (0-1) |
|
|
23
|
+
|
|
24
|
+
## Pipeline Example
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
nodes:
|
|
28
|
+
player:
|
|
29
|
+
use: "@acpfx/audio-player"
|
|
30
|
+
settings: { speechSource: tts, sampleRate: 16000 }
|
|
31
|
+
outputs: [mic] # reference audio for AEC
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
ISC
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { readFileSync as readFileSync2, existsSync } from "node:fs";
|
|
3
|
+
import { join as join2, dirname as dirname2 } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
// ../node-sdk/src/index.ts
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
|
|
9
|
+
// ../core/src/config.ts
|
|
10
|
+
import { parse as parseYaml } from "yaml";
|
|
11
|
+
|
|
12
|
+
// ../core/src/manifest.ts
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { join, dirname } from "node:path";
|
|
15
|
+
import { z as z2 } from "zod";
|
|
16
|
+
|
|
17
|
+
// ../core/src/acpfx-flags.ts
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var SetupCheckResponseSchema = z.object({
|
|
20
|
+
needed: z.boolean(),
|
|
21
|
+
description: z.string().optional()
|
|
22
|
+
});
|
|
23
|
+
var SetupProgressSchema = z.discriminatedUnion("type", [
|
|
24
|
+
z.object({
|
|
25
|
+
type: z.literal("progress"),
|
|
26
|
+
message: z.string(),
|
|
27
|
+
pct: z.number().optional()
|
|
28
|
+
}),
|
|
29
|
+
z.object({ type: z.literal("complete"), message: z.string() }),
|
|
30
|
+
z.object({ type: z.literal("error"), message: z.string() })
|
|
31
|
+
]);
|
|
32
|
+
var UnsupportedFlagResponseSchema = z.object({
|
|
33
|
+
unsupported: z.boolean(),
|
|
34
|
+
flag: z.string()
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ../core/src/manifest.ts
|
|
38
|
+
var ArgumentTypeSchema = z2.enum(["string", "number", "boolean"]);
|
|
39
|
+
var ManifestArgumentSchema = z2.object({
|
|
40
|
+
type: ArgumentTypeSchema,
|
|
41
|
+
default: z2.unknown().optional(),
|
|
42
|
+
description: z2.string().optional(),
|
|
43
|
+
required: z2.boolean().optional(),
|
|
44
|
+
enum: z2.array(z2.unknown()).optional()
|
|
45
|
+
});
|
|
46
|
+
var ManifestEnvFieldSchema = z2.object({
|
|
47
|
+
required: z2.boolean().optional(),
|
|
48
|
+
description: z2.string().optional()
|
|
49
|
+
});
|
|
50
|
+
var NodeManifestSchema = z2.object({
|
|
51
|
+
name: z2.string(),
|
|
52
|
+
description: z2.string().optional(),
|
|
53
|
+
consumes: z2.array(z2.string()),
|
|
54
|
+
emits: z2.array(z2.string()),
|
|
55
|
+
arguments: z2.record(z2.string(), ManifestArgumentSchema).optional(),
|
|
56
|
+
additional_arguments: z2.boolean().optional(),
|
|
57
|
+
env: z2.record(z2.string(), ManifestEnvFieldSchema).optional()
|
|
58
|
+
});
|
|
59
|
+
function handleAcpfxFlags(manifestPath) {
|
|
60
|
+
const acpfxFlag = process.argv.find((a) => a.startsWith("--acpfx-"));
|
|
61
|
+
const legacyManifest = process.argv.includes("--manifest");
|
|
62
|
+
if (!acpfxFlag && !legacyManifest) return;
|
|
63
|
+
const flag = acpfxFlag ?? "--acpfx-manifest";
|
|
64
|
+
switch (flag) {
|
|
65
|
+
case "--acpfx-manifest":
|
|
66
|
+
printManifest(manifestPath);
|
|
67
|
+
break;
|
|
68
|
+
case "--acpfx-setup-check":
|
|
69
|
+
process.stdout.write(JSON.stringify({ needed: false }) + "\n");
|
|
70
|
+
process.exit(0);
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
process.stdout.write(
|
|
74
|
+
JSON.stringify({ unsupported: true, flag }) + "\n"
|
|
75
|
+
);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function handleManifestFlag(manifestPath) {
|
|
80
|
+
handleAcpfxFlags(manifestPath);
|
|
81
|
+
}
|
|
82
|
+
function printManifest(manifestPath) {
|
|
83
|
+
if (!manifestPath) {
|
|
84
|
+
const script = process.argv[1];
|
|
85
|
+
const scriptDir = dirname(script);
|
|
86
|
+
const scriptBase = script.replace(/\.[^.]+$/, "");
|
|
87
|
+
const colocated = `${scriptBase}.manifest.json`;
|
|
88
|
+
try {
|
|
89
|
+
readFileSync(colocated);
|
|
90
|
+
manifestPath = colocated;
|
|
91
|
+
} catch {
|
|
92
|
+
manifestPath = join(scriptDir, "manifest.json");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const content = readFileSync(manifestPath, "utf8");
|
|
97
|
+
process.stdout.write(content.trim() + "\n");
|
|
98
|
+
process.exit(0);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
process.stderr.write(`Failed to read manifest: ${err}
|
|
101
|
+
`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ../node-sdk/src/index.ts
|
|
107
|
+
var NODE_NAME = process.env.ACPFX_NODE_NAME ?? "unknown";
|
|
108
|
+
function emit(event) {
|
|
109
|
+
process.stdout.write(JSON.stringify(event) + "\n");
|
|
110
|
+
}
|
|
111
|
+
function log(level, message) {
|
|
112
|
+
emit({ type: "log", level, component: NODE_NAME, message });
|
|
113
|
+
}
|
|
114
|
+
log.info = (message) => log("info", message);
|
|
115
|
+
log.warn = (message) => log("warn", message);
|
|
116
|
+
log.error = (message) => log("error", message);
|
|
117
|
+
log.debug = (message) => log("debug", message);
|
|
118
|
+
function onEvent(handler) {
|
|
119
|
+
const rl = createInterface({ input: process.stdin });
|
|
120
|
+
rl.on("line", (line) => {
|
|
121
|
+
if (!line.trim()) return;
|
|
122
|
+
try {
|
|
123
|
+
const event = JSON.parse(line);
|
|
124
|
+
handler(event);
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return rl;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/index.ts
|
|
132
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
133
|
+
handleManifestFlag();
|
|
134
|
+
var settings = JSON.parse(process.env.ACPFX_SETTINGS || "{}");
|
|
135
|
+
var SPEECH_SOURCE = settings.speechSource ?? "tts";
|
|
136
|
+
var SAMPLE_RATE = settings.sampleRate ?? 16e3;
|
|
137
|
+
var SFX_VOLUME = settings.sfxVolume ?? 0.3;
|
|
138
|
+
var BYTES_PER_SAMPLE = 2;
|
|
139
|
+
function findBundledSound(filename) {
|
|
140
|
+
const candidates = [
|
|
141
|
+
join2(__dirname, "..", "sounds", filename),
|
|
142
|
+
// dev: src/../sounds/
|
|
143
|
+
join2(__dirname, "sounds", filename),
|
|
144
|
+
// dist: next to bundled .js
|
|
145
|
+
join2(__dirname, "..", "..", "sounds", filename)
|
|
146
|
+
// npm package layout
|
|
147
|
+
];
|
|
148
|
+
for (const p of candidates) {
|
|
149
|
+
if (existsSync(p)) return p;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function applyGain(pcm, gain) {
|
|
154
|
+
if (gain === 1) return pcm;
|
|
155
|
+
const out = Buffer.alloc(pcm.length);
|
|
156
|
+
for (let i = 0; i < pcm.length; i += 2) {
|
|
157
|
+
const sample = Math.max(-32768, Math.min(32767, Math.round(pcm.readInt16LE(i) * gain)));
|
|
158
|
+
out.writeInt16LE(sample, i);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
function loadWavPcm(filePath) {
|
|
163
|
+
try {
|
|
164
|
+
const raw = readFileSync2(filePath);
|
|
165
|
+
return raw.subarray(44);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
log.error(`Failed to load WAV: ${filePath}: ${err instanceof Error ? err.message : err}`);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
var agentState = "idle";
|
|
172
|
+
var playingKind = null;
|
|
173
|
+
var thinkingPcm = null;
|
|
174
|
+
var toolPcm = null;
|
|
175
|
+
var sfxLoopTimer = null;
|
|
176
|
+
var sfxActive = false;
|
|
177
|
+
var sfxClipOffset = 0;
|
|
178
|
+
var sfxCurrentClip = null;
|
|
179
|
+
var SFX_CHUNK_MS = 100;
|
|
180
|
+
var SFX_CHUNK_BYTES = Math.floor(SAMPLE_RATE * BYTES_PER_SAMPLE * SFX_CHUNK_MS / 1e3);
|
|
181
|
+
function emitStatus() {
|
|
182
|
+
emit({
|
|
183
|
+
type: "player.status",
|
|
184
|
+
playing: playingKind,
|
|
185
|
+
agentState,
|
|
186
|
+
sfxActive
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function emitChunk(pcm, kind) {
|
|
190
|
+
const durationMs = Math.round(pcm.length / (SAMPLE_RATE * BYTES_PER_SAMPLE) * 1e3);
|
|
191
|
+
emit({
|
|
192
|
+
type: "audio.chunk",
|
|
193
|
+
trackId: "player",
|
|
194
|
+
format: "pcm_s16le",
|
|
195
|
+
sampleRate: SAMPLE_RATE,
|
|
196
|
+
channels: 1,
|
|
197
|
+
data: pcm.toString("base64"),
|
|
198
|
+
durationMs,
|
|
199
|
+
kind
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function startSfxLoop() {
|
|
203
|
+
if (sfxActive) return;
|
|
204
|
+
const clip = agentState === "thinking" ? thinkingPcm : agentState === "tool" ? toolPcm : null;
|
|
205
|
+
if (!clip) return;
|
|
206
|
+
sfxActive = true;
|
|
207
|
+
playingKind = "sfx";
|
|
208
|
+
sfxCurrentClip = applyGain(clip, SFX_VOLUME);
|
|
209
|
+
sfxClipOffset = 0;
|
|
210
|
+
log.info(`Starting SFX loop (${agentState})`);
|
|
211
|
+
writeSfxChunk();
|
|
212
|
+
sfxLoopTimer = setInterval(writeSfxChunk, SFX_CHUNK_MS);
|
|
213
|
+
}
|
|
214
|
+
function writeSfxChunk() {
|
|
215
|
+
if (!sfxActive || !sfxCurrentClip) return;
|
|
216
|
+
const remaining = sfxCurrentClip.length - sfxClipOffset;
|
|
217
|
+
if (remaining <= 0) {
|
|
218
|
+
sfxClipOffset = 0;
|
|
219
|
+
}
|
|
220
|
+
const bytesToWrite = Math.min(SFX_CHUNK_BYTES, sfxCurrentClip.length - sfxClipOffset);
|
|
221
|
+
const chunk = sfxCurrentClip.subarray(sfxClipOffset, sfxClipOffset + bytesToWrite);
|
|
222
|
+
sfxClipOffset += bytesToWrite;
|
|
223
|
+
emitChunk(chunk, "sfx");
|
|
224
|
+
}
|
|
225
|
+
function stopSfxLoop() {
|
|
226
|
+
if (sfxLoopTimer) {
|
|
227
|
+
clearInterval(sfxLoopTimer);
|
|
228
|
+
sfxLoopTimer = null;
|
|
229
|
+
}
|
|
230
|
+
if (sfxActive) {
|
|
231
|
+
sfxActive = false;
|
|
232
|
+
log.info("Stopped SFX loop");
|
|
233
|
+
}
|
|
234
|
+
if (playingKind === "sfx") {
|
|
235
|
+
playingKind = null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
var THINKING_DELAY_MS = 500;
|
|
239
|
+
var sfxDelayTimer = null;
|
|
240
|
+
function cancelSfxDelay() {
|
|
241
|
+
if (sfxDelayTimer) {
|
|
242
|
+
clearTimeout(sfxDelayTimer);
|
|
243
|
+
sfxDelayTimer = null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function flushSfxForSpeech() {
|
|
247
|
+
stopSfxLoop();
|
|
248
|
+
}
|
|
249
|
+
function handleEvent(event) {
|
|
250
|
+
const type = event.type;
|
|
251
|
+
const from = event._from;
|
|
252
|
+
if (type === "audio.chunk") {
|
|
253
|
+
if (from !== SPEECH_SOURCE) {
|
|
254
|
+
log.debug(`Ignoring audio.chunk from "${from}" (expected "${SPEECH_SOURCE}")`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
cancelSfxDelay();
|
|
258
|
+
if (sfxActive) {
|
|
259
|
+
flushSfxForSpeech();
|
|
260
|
+
}
|
|
261
|
+
playingKind = "speech";
|
|
262
|
+
const pcm = Buffer.from(event.data, "base64");
|
|
263
|
+
emitChunk(pcm, "speech");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (type === "agent.thinking") {
|
|
267
|
+
agentState = "thinking";
|
|
268
|
+
cancelSfxDelay();
|
|
269
|
+
sfxDelayTimer = setTimeout(() => {
|
|
270
|
+
sfxDelayTimer = null;
|
|
271
|
+
if (agentState === "thinking") startSfxLoop();
|
|
272
|
+
}, THINKING_DELAY_MS);
|
|
273
|
+
emitStatus();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (type === "agent.tool_start") {
|
|
277
|
+
agentState = "tool";
|
|
278
|
+
cancelSfxDelay();
|
|
279
|
+
stopSfxLoop();
|
|
280
|
+
startSfxLoop();
|
|
281
|
+
emitStatus();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (type === "agent.tool_done") {
|
|
285
|
+
agentState = "idle";
|
|
286
|
+
cancelSfxDelay();
|
|
287
|
+
stopSfxLoop();
|
|
288
|
+
emitStatus();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (type === "agent.delta") {
|
|
292
|
+
if (agentState !== "idle") {
|
|
293
|
+
agentState = "idle";
|
|
294
|
+
cancelSfxDelay();
|
|
295
|
+
emitStatus();
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (type === "agent.complete") {
|
|
300
|
+
agentState = "idle";
|
|
301
|
+
cancelSfxDelay();
|
|
302
|
+
stopSfxLoop();
|
|
303
|
+
emitStatus();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (type === "control.interrupt") {
|
|
307
|
+
agentState = "idle";
|
|
308
|
+
cancelSfxDelay();
|
|
309
|
+
stopSfxLoop();
|
|
310
|
+
playingKind = null;
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function main() {
|
|
315
|
+
const thinkingPath = settings.thinkingClip ?? findBundledSound("thinking.wav");
|
|
316
|
+
const toolPath = settings.toolClip ?? findBundledSound("typing.wav");
|
|
317
|
+
if (thinkingPath) {
|
|
318
|
+
thinkingPcm = loadWavPcm(thinkingPath);
|
|
319
|
+
if (thinkingPcm) log.info(`Loaded thinking clip: ${thinkingPath} (${thinkingPcm.length} bytes)`);
|
|
320
|
+
}
|
|
321
|
+
if (toolPath) {
|
|
322
|
+
toolPcm = loadWavPcm(toolPath);
|
|
323
|
+
if (toolPcm) log.info(`Loaded tool clip: ${toolPath} (${toolPcm.length} bytes)`);
|
|
324
|
+
}
|
|
325
|
+
emit({ type: "lifecycle.ready", component: "audio-player" });
|
|
326
|
+
const rl = onEvent(handleEvent);
|
|
327
|
+
rl.on("close", () => {
|
|
328
|
+
stopSfxLoop();
|
|
329
|
+
emit({ type: "lifecycle.done", component: "audio-player" });
|
|
330
|
+
process.exit(0);
|
|
331
|
+
});
|
|
332
|
+
process.on("SIGTERM", () => {
|
|
333
|
+
stopSfxLoop();
|
|
334
|
+
process.exit(0);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"audio-player","description":"Audio mixer with SFX — emits audio.chunk for downstream playback","consumes":["audio.chunk","agent.thinking","agent.tool_start","agent.tool_done","agent.delta","agent.complete","control.interrupt"],"emits":["audio.chunk","player.status","lifecycle.ready","lifecycle.done"],"arguments":{"speechSource":{"type":"string","description":"Node name whose audio.chunk events are speech (for filtering)"},"sampleRate":{"type":"number","default":16000,"description":"Audio sample rate in Hz"},"thinkingClip":{"type":"string","description":"Path to thinking SFX audio clip"},"toolClip":{"type":"string","description":"Path to tool-use SFX audio clip"},"sfxVolume":{"type":"number","default":0.3,"description":"SFX volume multiplier (0-1)"}}}
|
package/manifest.yaml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: audio-player
|
|
2
|
+
description: Audio mixer with SFX — emits audio.chunk for downstream playback
|
|
3
|
+
consumes:
|
|
4
|
+
- audio.chunk
|
|
5
|
+
- agent.thinking
|
|
6
|
+
- agent.tool_start
|
|
7
|
+
- agent.tool_done
|
|
8
|
+
- agent.delta
|
|
9
|
+
- agent.complete
|
|
10
|
+
- control.interrupt
|
|
11
|
+
emits:
|
|
12
|
+
- audio.chunk
|
|
13
|
+
- player.status
|
|
14
|
+
- lifecycle.ready
|
|
15
|
+
- lifecycle.done
|
|
16
|
+
arguments:
|
|
17
|
+
speechSource:
|
|
18
|
+
type: string
|
|
19
|
+
description: "Node name whose audio.chunk events are speech (for filtering)"
|
|
20
|
+
sampleRate:
|
|
21
|
+
type: number
|
|
22
|
+
default: 16000
|
|
23
|
+
description: "Audio sample rate in Hz"
|
|
24
|
+
thinkingClip:
|
|
25
|
+
type: string
|
|
26
|
+
description: "Path to thinking SFX audio clip"
|
|
27
|
+
toolClip:
|
|
28
|
+
type: string
|
|
29
|
+
description: "Path to tool-use SFX audio clip"
|
|
30
|
+
sfxVolume:
|
|
31
|
+
type: number
|
|
32
|
+
default: 0.3
|
|
33
|
+
description: "SFX volume multiplier (0-1)"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acpfx/audio-player",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"acpfx-audio-player": "./dist/index.js"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
+
"manifest.yaml",
|
|
11
12
|
"sounds"
|
|
12
13
|
],
|
|
13
14
|
"dependencies": {
|
|
@@ -16,6 +17,6 @@
|
|
|
16
17
|
"@acpfx/node-sdk": "0.3.0"
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
19
|
-
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --packages=external"
|
|
20
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --packages=external && node ../../scripts/copy-manifest.js"
|
|
20
21
|
}
|
|
21
22
|
}
|