@combos-fun/plugin-sound 0.0.6 → 0.0.7
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/README.md +25 -1
- package/agent-skill.md +122 -0
- package/combos-plugin.json +11 -0
- package/package.json +17 -3
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
1
|
# @combos-fun/plugin-sound
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
@combos-fun/plugin-sound — part of the Combos Fun engine monorepo.
|
|
4
|
+
|
|
5
|
+
Keywords: `sound`, `audio`, `playback`, `volume`, `web-audio`, `sfx`, `bgm`.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
- Per-package agent / developer notes: [`agent-skill.md`](./agent-skill.md)
|
|
10
|
+
- Machine-readable manifest: [`combos-plugin.json`](./combos-plugin.json)
|
|
11
|
+
(validated against `schemas/combos-plugin.schema.json` in the repo
|
|
12
|
+
root)
|
|
13
|
+
- Entry skill (single source of truth for AI agents working with this
|
|
14
|
+
monorepo): `skills/combos-engine-development/SKILL.md`
|
|
15
|
+
|
|
16
|
+
When consumed from npm, the manifest and agent notes are exposed as
|
|
17
|
+
stable subpaths:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import manifest from '@combos-fun/plugin-sound/plugin-manifest';
|
|
21
|
+
// or fetch the markdown directly:
|
|
22
|
+
// require.resolve('@combos-fun/plugin-sound/agent-skill')
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
Internal workspace package, part of the Combos Fun engine monorepo.
|
package/agent-skill.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# `@combos-fun/plugin-sound` — Agent notes
|
|
2
|
+
|
|
3
|
+
Audio playback for Combos Fun. Web Audio internally
|
|
4
|
+
(`AudioContext` + `AudioBufferSourceNode` + `GainNode`). Shared between 2D
|
|
5
|
+
and 3D — there is no spatial / 3D-positional audio in this package.
|
|
6
|
+
|
|
7
|
+
## When to read
|
|
8
|
+
|
|
9
|
+
Read for any audio task: SFX, BGM, autoplay, loop, mute, volume, pause /
|
|
10
|
+
resume integration with `Game.pause()` / `Game.resume()`, touch-to-unlock
|
|
11
|
+
on mobile.
|
|
12
|
+
|
|
13
|
+
## Public API
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Sound, SoundSystem } from '@combos-fun/plugin-sound';
|
|
17
|
+
import { resource, RESOURCE_TYPE } from '@combos-fun/engine';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### `Sound` Component params
|
|
21
|
+
|
|
22
|
+
| Field | Type | Notes |
|
|
23
|
+
|-------|------|-------|
|
|
24
|
+
| `resource` | `string` | Engine resource name registered with `RESOURCE_TYPE.AUDIO` |
|
|
25
|
+
| `autoplay` / `loop` / `muted` | `boolean?` | |
|
|
26
|
+
| `volume` | `number?` | `0` – `1` |
|
|
27
|
+
| `seek` / `duration` | `number?` | |
|
|
28
|
+
| `onEnd` | `() => void?` | Fires when playback ends naturally |
|
|
29
|
+
|
|
30
|
+
Methods: `play()`, `pause()`, `stop()`. Read-only fields: `playing`,
|
|
31
|
+
`state` (`'unloaded' | 'loading' | 'loaded'`), `muted`, `volume`.
|
|
32
|
+
|
|
33
|
+
### `SoundSystemParams`
|
|
34
|
+
|
|
35
|
+
| Field | Type | Notes |
|
|
36
|
+
|-------|------|-------|
|
|
37
|
+
| `onError` | `(error: unknown) => void` | **Required** — TypeScript will fail without it |
|
|
38
|
+
| `autoPauseAndStart` | `boolean?` | Pause / resume audio along with `Game` |
|
|
39
|
+
|
|
40
|
+
System API: `resumeAll()`, `pauseAll()`, `stopAll()`, `muted`, `volume`,
|
|
41
|
+
`audioLocked` (true until first user gesture). Decoded `AudioBuffer`s are
|
|
42
|
+
cached per resource.
|
|
43
|
+
|
|
44
|
+
### Audio resource registration
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
resource.addResource([
|
|
48
|
+
{ name: 'bgm', src: { audio: { type: 'audio', url: '/bgm.mp3' } }, preload: true },
|
|
49
|
+
]);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The loader configures `mp3` / `wav` / `aac` / `ogg` as `arrayBuffer`. The
|
|
53
|
+
audio buffer is decoded once and reused.
|
|
54
|
+
|
|
55
|
+
## Required setup
|
|
56
|
+
|
|
57
|
+
- Add `SoundSystem` after the renderer system. Order does not affect
|
|
58
|
+
audio correctness, but keeping it consistent helps debugging.
|
|
59
|
+
- Always pass `onError` — this is enforced by TypeScript.
|
|
60
|
+
- Audio is locked until the first user gesture (browser autoplay policy).
|
|
61
|
+
Either start audio inside an event-driven Component (e.g. on tap) or
|
|
62
|
+
show a "tap to start" UI on first paint.
|
|
63
|
+
|
|
64
|
+
## Runtime behaviour
|
|
65
|
+
|
|
66
|
+
- `Sound.play()` resolves to a fresh `AudioBufferSourceNode` per call;
|
|
67
|
+
re-`play()` after `stop()` works.
|
|
68
|
+
- `SoundSystem` listens to the unlock gesture once and sets
|
|
69
|
+
`audioLocked = false`. Subsequent plays succeed without user gesture.
|
|
70
|
+
- `autoPauseAndStart: true` calls `pauseAll()` on `Game.pause()` and
|
|
71
|
+
`resumeAll()` on `Game.resume()`.
|
|
72
|
+
|
|
73
|
+
## Common pitfalls
|
|
74
|
+
|
|
75
|
+
| Symptom | Fix |
|
|
76
|
+
|---------|-----|
|
|
77
|
+
| TS: `onError` missing | `new SoundSystem({ onError: (e) => console.warn(e) })` |
|
|
78
|
+
| No sound on page load | Browser autoplay policy — call `play()` from a user-gesture Component |
|
|
79
|
+
| `state === 'unloaded'` | Resource not registered, or component `resource` name doesn't match |
|
|
80
|
+
| `state === 'loading'` forever | Resource URL 404 or CORS — check network tab |
|
|
81
|
+
| Stuck loud after pause | `volume` is per Sound; use `SoundSystem.volume` for global mute |
|
|
82
|
+
|
|
83
|
+
## Minimal example
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { Game, GameObject, resource } from '@combos-fun/engine';
|
|
87
|
+
import { RendererSystem } from '@combos-fun/plugin-renderer';
|
|
88
|
+
import { Sound, SoundSystem } from '@combos-fun/plugin-sound';
|
|
89
|
+
|
|
90
|
+
resource.addResource([
|
|
91
|
+
{ name: 'tap-sfx', src: { audio: { type: 'audio', url: '/tap.mp3' } }, preload: true },
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
new Game({
|
|
95
|
+
systems: [
|
|
96
|
+
new RendererSystem({ canvas, width: 750, height: 1334 }),
|
|
97
|
+
new SoundSystem({ onError: (e) => console.warn(e) }),
|
|
98
|
+
],
|
|
99
|
+
onSystemsBootstrapComplete: (game) => {
|
|
100
|
+
const audio = new GameObject('tap-audio');
|
|
101
|
+
audio.addComponent(new Sound({ resource: 'tap-sfx' }));
|
|
102
|
+
game.getScene().addChild(audio);
|
|
103
|
+
|
|
104
|
+
// Trigger from a tap Component:
|
|
105
|
+
// gameObject.getComponent(Sound)?.play()
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Video
|
|
111
|
+
|
|
112
|
+
`RESOURCE_TYPE.VIDEO` is supported by `@combos-fun/engine`'s loader for
|
|
113
|
+
`{ video: { type: 'mp4', url: '...' } }`, but on-canvas video playback is
|
|
114
|
+
app- or plugin-specific. After `getResource(name)`, use the resolved
|
|
115
|
+
`instance` in your video plugin or a DOM `<video>` element.
|
|
116
|
+
|
|
117
|
+
## Verification
|
|
118
|
+
|
|
119
|
+
- `pnpm --filter @combos-fun/plugin-sound run build`
|
|
120
|
+
- Run an example with audio on touch / click: verify autoplay-locked state
|
|
121
|
+
unlocks after the first user gesture and the buffer is cached for
|
|
122
|
+
subsequent plays.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@combos-fun/plugin-sound",
|
|
3
|
+
"pluginId": "sound",
|
|
4
|
+
"category": "audio",
|
|
5
|
+
"dimension": "shared",
|
|
6
|
+
"isCore": false,
|
|
7
|
+
"keywords": ["sound", "audio", "playback", "volume", "web-audio", "sfx", "bgm"],
|
|
8
|
+
"agentSkill": "./agent-skill.md",
|
|
9
|
+
"requires": ["@combos-fun/engine"],
|
|
10
|
+
"exports": ["Sound", "SoundSystem"]
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@combos-fun/plugin-sound",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "@combos-fun/plugin-sound",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "dist/plugin-sound.esm.js",
|
|
@@ -8,8 +8,22 @@
|
|
|
8
8
|
"unpkg": "dist/CombosFun.plugin.sound.min.js",
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
|
-
"dist"
|
|
11
|
+
"dist",
|
|
12
|
+
"agent-skill.md",
|
|
13
|
+
"combos-plugin.json"
|
|
12
14
|
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./dist/plugin-sound.esm.js",
|
|
18
|
+
"require": "./index.js",
|
|
19
|
+
"types": "./dist/plugin-sound.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./plugin-manifest": "./combos-plugin.json",
|
|
22
|
+
"./agent-skill": "./agent-skill.md"
|
|
23
|
+
},
|
|
24
|
+
"combos": {
|
|
25
|
+
"pluginManifest": "./combos-plugin.json"
|
|
26
|
+
},
|
|
13
27
|
"types": "dist/plugin-sound.d.ts",
|
|
14
28
|
"keywords": [
|
|
15
29
|
"combos-fun",
|
|
@@ -18,7 +32,7 @@
|
|
|
18
32
|
"author": "sun668 <q947692259@gmail.com>",
|
|
19
33
|
"dependencies": {
|
|
20
34
|
"eventemitter3": "^5.0.4",
|
|
21
|
-
"@combos-fun/engine": "0.0.
|
|
35
|
+
"@combos-fun/engine": "0.0.7"
|
|
22
36
|
},
|
|
23
37
|
"scripts": {
|
|
24
38
|
"build": "node ../../scripts/build-package.mjs"
|