@nodetool-ai/game-renderer 0.8.3 → 0.8.4-a13.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.
Files changed (105) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/audio/music.d.ts +38 -0
  3. package/dist/audio/music.d.ts.map +1 -0
  4. package/dist/audio/music.js +186 -0
  5. package/dist/audio/music.js.map +1 -0
  6. package/dist/audio/player.d.ts +15 -1
  7. package/dist/audio/player.d.ts.map +1 -1
  8. package/dist/audio/player.js +185 -19
  9. package/dist/audio/player.js.map +1 -1
  10. package/dist/build-tauri.d.ts.map +1 -1
  11. package/dist/build-tauri.js +3 -4
  12. package/dist/build-tauri.js.map +1 -1
  13. package/dist/build3d.d.ts +1 -1
  14. package/dist/build3d.d.ts.map +1 -1
  15. package/dist/build3d.js +13 -1
  16. package/dist/build3d.js.map +1 -1
  17. package/dist/game3d-capture-page.js +328 -262
  18. package/dist/game3d-player.js +415 -311
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/node3d.d.ts +3 -0
  24. package/dist/node3d.d.ts.map +1 -1
  25. package/dist/node3d.js +27 -1
  26. package/dist/node3d.js.map +1 -1
  27. package/dist/particles/index.d.ts +1 -0
  28. package/dist/particles/index.d.ts.map +1 -1
  29. package/dist/particles/index.js +1 -0
  30. package/dist/particles/index.js.map +1 -1
  31. package/dist/particles/particles3d.d.ts +89 -0
  32. package/dist/particles/particles3d.d.ts.map +1 -0
  33. package/dist/particles/particles3d.js +176 -0
  34. package/dist/particles/particles3d.js.map +1 -0
  35. package/dist/particles/pass3d.d.ts +63 -0
  36. package/dist/particles/pass3d.d.ts.map +1 -0
  37. package/dist/particles/pass3d.js +394 -0
  38. package/dist/particles/pass3d.js.map +1 -0
  39. package/dist/particles/simulator.d.ts +4 -0
  40. package/dist/particles/simulator.d.ts.map +1 -1
  41. package/dist/particles/simulator.js +9 -1
  42. package/dist/particles/simulator.js.map +1 -1
  43. package/dist/player-v1.js +412 -353
  44. package/dist/preparation3d.d.ts +2 -0
  45. package/dist/preparation3d.d.ts.map +1 -1
  46. package/dist/preparation3d.js +1 -0
  47. package/dist/preparation3d.js.map +1 -1
  48. package/dist/renderer3d/assets/image-cache.d.ts +20 -0
  49. package/dist/renderer3d/assets/image-cache.d.ts.map +1 -0
  50. package/dist/renderer3d/assets/image-cache.js +140 -0
  51. package/dist/renderer3d/assets/image-cache.js.map +1 -0
  52. package/dist/renderer3d/assets/image-contract.d.ts +14 -0
  53. package/dist/renderer3d/assets/image-contract.d.ts.map +1 -0
  54. package/dist/renderer3d/assets/image-contract.js +2 -0
  55. package/dist/renderer3d/assets/image-contract.js.map +1 -0
  56. package/dist/renderer3d/assets/image-preparation.d.ts +18 -0
  57. package/dist/renderer3d/assets/image-preparation.d.ts.map +1 -0
  58. package/dist/renderer3d/assets/image-preparation.js +167 -0
  59. package/dist/renderer3d/assets/image-preparation.js.map +1 -0
  60. package/dist/renderer3d/capture-page.d.ts +3 -0
  61. package/dist/renderer3d/capture-page.d.ts.map +1 -1
  62. package/dist/renderer3d/capture-page.js +17 -4
  63. package/dist/renderer3d/capture-page.js.map +1 -1
  64. package/dist/renderer3d/hud/paint.d.ts +1 -0
  65. package/dist/renderer3d/hud/paint.d.ts.map +1 -1
  66. package/dist/renderer3d/hud/paint.js +7 -0
  67. package/dist/renderer3d/hud/paint.js.map +1 -1
  68. package/dist/renderer3d/index.d.ts +13 -2
  69. package/dist/renderer3d/index.d.ts.map +1 -1
  70. package/dist/renderer3d/index.js +26 -4
  71. package/dist/renderer3d/index.js.map +1 -1
  72. package/dist/renderer3d/passes/editor.d.ts +59 -0
  73. package/dist/renderer3d/passes/editor.d.ts.map +1 -0
  74. package/dist/renderer3d/passes/editor.js +290 -0
  75. package/dist/renderer3d/passes/editor.js.map +1 -0
  76. package/dist/renderer3d/passes/post-processing.d.ts +2 -0
  77. package/dist/renderer3d/passes/post-processing.d.ts.map +1 -1
  78. package/dist/renderer3d/passes/post-processing.js +3 -1
  79. package/dist/renderer3d/passes/post-processing.js.map +1 -1
  80. package/dist/renderer3d/preparation.d.ts.map +1 -1
  81. package/dist/renderer3d/preparation.js +2 -0
  82. package/dist/renderer3d/preparation.js.map +1 -1
  83. package/dist/save-storage.d.ts +19 -0
  84. package/dist/save-storage.d.ts.map +1 -0
  85. package/dist/save-storage.js +135 -0
  86. package/dist/save-storage.js.map +1 -0
  87. package/dist/smoke.d.ts.map +1 -1
  88. package/dist/smoke.js +12 -2
  89. package/dist/smoke.js.map +1 -1
  90. package/dist/standalone-player.d.ts +3 -0
  91. package/dist/standalone-player.d.ts.map +1 -1
  92. package/dist/standalone-player.js +40 -30
  93. package/dist/standalone-player.js.map +1 -1
  94. package/dist/standalone-player3d.d.ts +3 -0
  95. package/dist/standalone-player3d.d.ts.map +1 -1
  96. package/dist/standalone-player3d.js +47 -13
  97. package/dist/standalone-player3d.js.map +1 -1
  98. package/dist/ui/controller.d.ts.map +1 -1
  99. package/dist/ui/controller.js +2 -1
  100. package/dist/ui/controller.js.map +1 -1
  101. package/dist/ui/hud3d.d.ts +5 -0
  102. package/dist/ui/hud3d.d.ts.map +1 -0
  103. package/dist/ui/hud3d.js +5 -0
  104. package/dist/ui/hud3d.js.map +1 -0
  105. package/package.json +14 -6
@@ -1 +1 @@
1
- 2026-10-12T12:30:00.497Z
1
+ 2026-10-15T22:44:25.338Z
@@ -0,0 +1,38 @@
1
+ import type { GameScene } from "@nodetool-ai/protocol";
2
+ type StreamingSettings = NonNullable<NonNullable<GameScene["music"]>["streaming"]>;
3
+ /** Owns a streamed track and its media lifecycle. Media time never enters simulation. */
4
+ export declare class GameStreamingMusicVoice {
5
+ private readonly context;
6
+ private readonly url;
7
+ private readonly settings;
8
+ private readonly volume;
9
+ private readonly fadeSeconds;
10
+ private readonly offset;
11
+ private readonly status;
12
+ private readonly ended;
13
+ private readonly playing;
14
+ private readonly media;
15
+ private readonly source;
16
+ private readonly gain;
17
+ private readonly controller;
18
+ private timer;
19
+ private disposed;
20
+ private paused;
21
+ private started;
22
+ private playGeneration;
23
+ private fadeStart;
24
+ private fadeOut;
25
+ private end;
26
+ constructor(context: AudioContext, url: string, destination: AudioNode, settings: StreamingSettings, volume: number, fadeSeconds: number, offset: () => number, status: (message: string) => void, ended: () => void, playing?: () => void);
27
+ load(): void;
28
+ private ready;
29
+ private play;
30
+ private fail;
31
+ pause(): void;
32
+ resume(): void;
33
+ stop(fadeSeconds: number): void;
34
+ private scheduleFadeOut;
35
+ dispose(): void;
36
+ }
37
+ export {};
38
+ //# sourceMappingURL=music.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"music.d.ts","sourceRoot":"","sources":["../../src/audio/music.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,KAAK,iBAAiB,GAAG,WAAW,CAAC,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;AAOnF,yFAAyF;AACzF,qBAAa,uBAAuB;IAehC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAEpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAvB1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,KAAK,CAA4C;IACzD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,OAAO,CAAiE;IAChF,OAAO,CAAC,GAAG,CAAK;IAEhB,YACmB,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,MAAM,EAC5B,WAAW,EAAE,SAAS,EACL,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,MAAM,EACpB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACjC,KAAK,EAAE,MAAM,IAAI,EACjB,OAAO,GAAE,MAAM,IAAsB,EAsBvD;IAED,IAAI,IAAI,IAAI,CAKX;IAED,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,IAAI;IAuBZ,OAAO,CAAC,IAAI;IAKZ,KAAK,IAAI,IAAI,CAeZ;IAED,MAAM,IAAI,IAAI,CAeb;IAED,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAU9B;IAED,OAAO,CAAC,eAAe;IAUvB,OAAO,IAAI,IAAI,CAYd;CACF"}
@@ -0,0 +1,186 @@
1
+ /** Logical music time includes the intro once, then repeats the authored loop. */
2
+ function loopOffset(seconds, start, end) {
3
+ return seconds < end ? seconds : start + (seconds - start) % (end - start);
4
+ }
5
+ /** Owns a streamed track and its media lifecycle. Media time never enters simulation. */
6
+ export class GameStreamingMusicVoice {
7
+ context;
8
+ url;
9
+ settings;
10
+ volume;
11
+ fadeSeconds;
12
+ offset;
13
+ status;
14
+ ended;
15
+ playing;
16
+ media = new Audio();
17
+ source;
18
+ gain;
19
+ controller = new AbortController();
20
+ timer;
21
+ disposed = false;
22
+ paused = false;
23
+ started = false;
24
+ playGeneration = 0;
25
+ fadeStart = 0;
26
+ fadeOut;
27
+ end = 0;
28
+ constructor(context, url, destination, settings, volume, fadeSeconds, offset, status, ended, playing = () => undefined) {
29
+ this.context = context;
30
+ this.url = url;
31
+ this.settings = settings;
32
+ this.volume = volume;
33
+ this.fadeSeconds = fadeSeconds;
34
+ this.offset = offset;
35
+ this.status = status;
36
+ this.ended = ended;
37
+ this.playing = playing;
38
+ this.media.crossOrigin = "anonymous";
39
+ this.media.preload = "metadata";
40
+ this.source = context.createMediaElementSource(this.media);
41
+ this.gain = context.createGain();
42
+ this.source.connect(this.gain).connect(destination);
43
+ this.gain.gain.setValueAtTime(0, context.currentTime);
44
+ const signal = this.controller.signal;
45
+ this.media.addEventListener("loadedmetadata", () => this.ready(), { signal });
46
+ this.media.addEventListener("timeupdate", () => {
47
+ if (!this.paused && this.end > 0 && this.media.currentTime >= this.end) {
48
+ this.media.currentTime = loopOffset(this.media.currentTime, settings.loopStartSeconds, this.end);
49
+ }
50
+ }, { signal });
51
+ this.media.addEventListener("ended", () => {
52
+ if (!this.paused && !this.disposed && this.end > 0) {
53
+ this.media.currentTime = settings.loopStartSeconds;
54
+ this.play();
55
+ }
56
+ }, { signal });
57
+ this.media.addEventListener("error", () => this.fail("the media could not be loaded"), { signal });
58
+ }
59
+ load() {
60
+ if (this.disposed)
61
+ return;
62
+ this.media.src = this.url;
63
+ this.media.load();
64
+ if (this.media.readyState >= 1)
65
+ this.ready();
66
+ }
67
+ ready() {
68
+ if (this.disposed)
69
+ return;
70
+ const duration = this.media.duration;
71
+ const end = this.settings.loopEndSeconds ?? duration;
72
+ if (!Number.isFinite(duration) || duration <= 0 || this.settings.loopStartSeconds >= end || end > duration) {
73
+ this.fail("loop points must fit inside the track duration");
74
+ return;
75
+ }
76
+ this.end = end;
77
+ this.media.loop = this.settings.loopStartSeconds === 0 && this.settings.loopEndSeconds === undefined;
78
+ if (!this.paused)
79
+ this.resume();
80
+ }
81
+ play() {
82
+ const generation = ++this.playGeneration;
83
+ void this.media.play().then(() => {
84
+ if (generation !== this.playGeneration) {
85
+ return;
86
+ }
87
+ if (this.disposed || this.paused) {
88
+ this.media.pause();
89
+ }
90
+ else {
91
+ this.media.currentTime = loopOffset(Math.max(0, this.offset()), this.settings.loopStartSeconds, this.end);
92
+ if (!this.started) {
93
+ this.started = true;
94
+ this.playing();
95
+ this.fadeStart = this.context.currentTime;
96
+ this.gain.gain.setValueAtTime(this.fadeSeconds > 0 ? 0 : this.volume, this.fadeStart);
97
+ if (this.fadeSeconds > 0)
98
+ this.gain.gain.linearRampToValueAtTime(this.volume, this.fadeStart + this.fadeSeconds);
99
+ }
100
+ }
101
+ }).catch((error) => {
102
+ if (!this.disposed && !this.paused && generation === this.playGeneration) {
103
+ this.fail(error instanceof Error ? error.message : String(error));
104
+ }
105
+ });
106
+ }
107
+ fail(message) {
108
+ this.status(`Streaming music could not play: ${message}`);
109
+ this.dispose();
110
+ }
111
+ pause() {
112
+ if (this.paused)
113
+ return;
114
+ if (this.fadeOut) {
115
+ const now = this.context.currentTime;
116
+ const elapsed = Math.max(0, now - this.fadeOut.start);
117
+ const seconds = Math.max(0, this.fadeOut.seconds - elapsed);
118
+ const volume = this.fadeOut.volume * Math.max(0, 1 - elapsed / this.fadeOut.seconds);
119
+ this.fadeOut = { start: now, seconds, volume };
120
+ this.gain.gain.cancelScheduledValues(now);
121
+ this.gain.gain.setValueAtTime(volume, now);
122
+ if (this.timer !== undefined)
123
+ clearTimeout(this.timer);
124
+ }
125
+ this.paused = true;
126
+ this.playGeneration += 1;
127
+ this.media.pause();
128
+ }
129
+ resume() {
130
+ if (this.disposed)
131
+ return;
132
+ const wasPaused = this.paused;
133
+ this.paused = false;
134
+ if (this.end === 0)
135
+ return;
136
+ if (wasPaused && this.fadeOut) {
137
+ if (this.fadeOut.seconds === 0) {
138
+ this.dispose();
139
+ return;
140
+ }
141
+ this.fadeOut.start = this.context.currentTime;
142
+ this.scheduleFadeOut();
143
+ }
144
+ this.media.currentTime = loopOffset(Math.max(0, this.offset()), this.settings.loopStartSeconds, this.end);
145
+ this.play();
146
+ }
147
+ stop(fadeSeconds) {
148
+ if (this.disposed)
149
+ return;
150
+ if (fadeSeconds === 0 || this.paused || !this.started) {
151
+ this.dispose();
152
+ return;
153
+ }
154
+ const now = this.context.currentTime;
155
+ const current = this.fadeSeconds > 0 ? this.volume * Math.min(1, Math.max(0, (now - this.fadeStart) / this.fadeSeconds)) : this.volume;
156
+ this.fadeOut = { start: now, seconds: fadeSeconds, volume: current };
157
+ this.scheduleFadeOut();
158
+ }
159
+ scheduleFadeOut() {
160
+ const fade = this.fadeOut;
161
+ if (!fade)
162
+ return;
163
+ if (this.timer !== undefined)
164
+ clearTimeout(this.timer);
165
+ this.gain.gain.cancelScheduledValues(fade.start);
166
+ this.gain.gain.setValueAtTime(fade.volume, fade.start);
167
+ this.gain.gain.linearRampToValueAtTime(0, fade.start + fade.seconds);
168
+ this.timer = setTimeout(() => this.dispose(), fade.seconds * 1000);
169
+ }
170
+ dispose() {
171
+ if (this.disposed)
172
+ return;
173
+ this.disposed = true;
174
+ if (this.timer !== undefined)
175
+ clearTimeout(this.timer);
176
+ this.controller.abort();
177
+ this.fadeOut = undefined;
178
+ this.pause();
179
+ this.media.removeAttribute("src");
180
+ this.media.load();
181
+ this.source.disconnect();
182
+ this.gain.disconnect();
183
+ this.ended();
184
+ }
185
+ }
186
+ //# sourceMappingURL=music.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"music.js","sourceRoot":"","sources":["../../src/audio/music.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
- import { type GameAssetBinding, type GameAssetBinding3D, type GameAudioMixerInput, type GameEvent, type GameEvent3D, type GameSnapshot } from "@nodetool-ai/protocol";
1
+ import { type GameAssetBinding, type GameAssetBinding3D, type GameAudioMixerInput, type GameEvent, type GameEvent3D, type GameSnapshot, type GameScene } from "@nodetool-ai/protocol";
2
2
  import { type GameAudioMixerState } from "./mixer.js";
3
3
  import { type GameAudioSpatialQuality, type GameAudioSpatialView } from "./spatial.js";
4
4
  export interface GameAudioOptions {
5
5
  readonly assets: Readonly<Record<string, GameAssetBinding | GameAssetBinding3D>>;
6
6
  readonly tickRate: number;
7
+ /** Scene music settings are presentation configuration, separate from snapshots. */
8
+ readonly scenes?: ReadonlyArray<Pick<GameScene, "id" | "music">>;
7
9
  readonly resolveAsset: (binding: GameAssetBinding | GameAssetBinding3D) => Promise<string | null>;
8
10
  readonly status: (message: string) => void;
9
11
  readonly context?: AudioContext;
@@ -17,6 +19,11 @@ export declare class GameAudioPlayer {
17
19
  private options;
18
20
  private readonly context;
19
21
  private readonly mixer;
22
+ private readonly failedStreams;
23
+ private readonly streamed;
24
+ private readonly deferredDecodedFades;
25
+ private readonly deferredFades;
26
+ private readonly streamingFades;
20
27
  private readonly buffers;
21
28
  private readonly voices;
22
29
  private readonly fading;
@@ -37,6 +44,8 @@ export declare class GameAudioPlayer {
37
44
  private mixerSettings;
38
45
  /** Applies edited mixer settings. Playing voices keep playing on their buses. */
39
46
  updateMixer(settings: GameAudioMixerInput | undefined): void;
47
+ /** Updates scene presentation settings without replacing the audio context. */
48
+ updateScenes(scenes: GameAudioOptions["scenes"]): void;
40
49
  /** Player-facing bus volume, for example from a settings screen. */
41
50
  setBusVolume(busId: string, volume: number): void;
42
51
  setBusMuted(busId: string, muted: boolean): void;
@@ -50,6 +59,10 @@ export declare class GameAudioPlayer {
50
59
  resume(): void;
51
60
  sync(snapshot: Pick<GameSnapshot, "tick" | "music" | "sceneId">): void;
52
61
  private startDesiredMusic;
62
+ private streamingSettings;
63
+ private voiceCount;
64
+ private makeVoiceRoom;
65
+ private startStream;
53
66
  /**
54
67
  * Moves the listener and every spatial voice to a rendered frame. Players call it once per rendered frame with the view
55
68
  * built from the interpolated frame, so positions follow what is on screen. Emitters missing from the view keep their last position.
@@ -60,6 +73,7 @@ export declare class GameAudioPlayer {
60
73
  /** Plays audio events and lets other simulation events drive mixer snapshot transitions. */
61
74
  handle(event: GameEvent | GameEvent3D): void;
62
75
  private start;
76
+ private finishDeferredFades;
63
77
  private stop;
64
78
  reset(snapshot: Pick<GameSnapshot, "tick" | "music" | "sceneId">): void;
65
79
  dispose(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/audio/player.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/audio/player.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { gameAudioMixer } from "@nodetool-ai/protocol";
2
+ import { GameStreamingMusicVoice } from "./music.js";
2
3
  import { GameAudioMixer } from "./mixer.js";
3
4
  import { createGameAudioPanner, gameAudioDopplerRate, gameAudioVelocity, placeGameAudioListener, placeGameAudioPanner, setGameAudioDopplerRate } from "./spatial.js";
4
5
  import { playBuiltinGameVoice, stopGameVoice } from "./voices.js";
@@ -8,6 +9,11 @@ export class GameAudioPlayer {
8
9
  options;
9
10
  context;
10
11
  mixer;
12
+ failedStreams = new Set();
13
+ streamed = new Map();
14
+ deferredDecodedFades = new Map();
15
+ deferredFades = new Map();
16
+ streamingFades = new Map();
11
17
  buffers = new Map();
12
18
  voices = new Map();
13
19
  fading = new Set();
@@ -43,6 +49,21 @@ export class GameAudioPlayer {
43
49
  return;
44
50
  this.mixer.update(this.mixerSettings(settings));
45
51
  }
52
+ /** Updates scene presentation settings without replacing the audio context. */
53
+ updateScenes(scenes) {
54
+ if (this.disposed)
55
+ return;
56
+ const before = this.streamingSettings();
57
+ this.options = { ...this.options, scenes };
58
+ for (const scene of scenes ?? []) {
59
+ if (scene.music?.streaming)
60
+ this.buffers.delete(scene.music.assetId);
61
+ }
62
+ if (JSON.stringify(before) !== JSON.stringify(this.streamingSettings()) && this.desiredMusic) {
63
+ this.stop(this.desiredMusic.voiceId, 0);
64
+ this.startDesiredMusic();
65
+ }
66
+ }
46
67
  /** Player-facing bus volume, for example from a settings screen. */
47
68
  setBusVolume(busId, volume) {
48
69
  if (!this.disposed)
@@ -103,13 +124,24 @@ export class GameAudioPlayer {
103
124
  this.fading.delete(voice);
104
125
  }
105
126
  }
127
+ for (const [id, stream] of this.streamed) {
128
+ if (changed.has(stream.assetId))
129
+ this.stop(id, 0);
130
+ }
131
+ for (const [voice, assetId] of this.streamingFades) {
132
+ if (changed.has(assetId))
133
+ voice.dispose();
134
+ }
135
+ if (this.desiredMusic && changed.has(this.desiredMusic.assetId))
136
+ this.failedStreams.delete(this.desiredMusic.voiceId);
106
137
  this.options = { ...this.options, assets };
107
138
  this.preload();
108
139
  this.startDesiredMusic();
109
140
  }
110
141
  preload() {
142
+ const streamedSlots = new Set((this.options.scenes ?? []).flatMap((scene) => scene.music?.streaming ? [scene.music.assetId] : []));
111
143
  for (const [id, binding] of Object.entries(this.options.assets)) {
112
- if (binding.mediaKind === "audio" && !binding.assetId.startsWith("builtin:"))
144
+ if (binding.mediaKind === "audio" && !binding.assetId.startsWith("builtin:") && !streamedSlots.has(id))
113
145
  void this.buffer(id);
114
146
  }
115
147
  }
@@ -117,6 +149,7 @@ export class GameAudioPlayer {
117
149
  if (this.disposed || this.paused)
118
150
  return;
119
151
  try {
152
+ this.failedStreams.clear();
120
153
  await this.context.resume();
121
154
  this.startDesiredMusic();
122
155
  }
@@ -128,11 +161,22 @@ export class GameAudioPlayer {
128
161
  if (this.disposed || this.paused)
129
162
  return;
130
163
  this.paused = true;
164
+ for (const stream of this.streamed.values())
165
+ stream.voice.pause();
166
+ for (const voice of this.streamingFades.keys())
167
+ voice.pause();
131
168
  void this.context.suspend().catch(() => this.options.status("Audio could not pause"));
132
169
  }
133
170
  resume() {
134
171
  this.paused = false;
135
- void this.unlock();
172
+ void this.unlock().then(() => {
173
+ if (!this.paused && !this.disposed && this.context.state === "running") {
174
+ for (const stream of this.streamed.values())
175
+ stream.voice.resume();
176
+ for (const voice of this.streamingFades.keys())
177
+ voice.resume();
178
+ }
179
+ });
136
180
  }
137
181
  sync(snapshot) {
138
182
  if (this.disposed)
@@ -154,23 +198,115 @@ export class GameAudioPlayer {
154
198
  if (this.desiredMusic?.voiceId !== next?.voiceId || this.desiredMusic?.startTick !== next?.startTick ||
155
199
  this.desiredMusic?.assetId !== next?.assetId || this.desiredMusic?.volume !== next?.volume ||
156
200
  this.desiredMusic?.fadeInTicks !== next?.fadeInTicks || this.desiredMusic?.fadeOutTicks !== next?.fadeOutTicks) {
157
- if (this.desiredMusic)
158
- this.stop(this.desiredMusic.voiceId, this.desiredMusic.fadeOutTicks);
201
+ if (this.desiredMusic) {
202
+ const outgoing = this.streamed.get(this.desiredMusic.voiceId);
203
+ const decoded = this.voices.get(this.desiredMusic.voiceId);
204
+ const incomingStream = next && this.options.scenes?.find(scene => scene.id === snapshot.sceneId)?.music?.streaming;
205
+ if (incomingStream && (outgoing || decoded)) {
206
+ if (decoded) {
207
+ this.voices.delete(decoded.id);
208
+ this.fading.add(decoded);
209
+ this.deferredDecodedFades.set(decoded, this.desiredMusic.fadeOutTicks);
210
+ }
211
+ this.pending.delete(this.desiredMusic.voiceId);
212
+ if (outgoing) {
213
+ this.streamed.delete(this.desiredMusic.voiceId);
214
+ this.failedStreams.delete(this.desiredMusic.voiceId);
215
+ this.streamingFades.set(outgoing.voice, outgoing.assetId);
216
+ this.deferredFades.set(outgoing.voice, this.desiredMusic.fadeOutTicks);
217
+ }
218
+ }
219
+ else {
220
+ this.stop(this.desiredMusic.voiceId, this.desiredMusic.fadeOutTicks);
221
+ }
222
+ }
223
+ if (!next) {
224
+ this.finishDeferredFades();
225
+ }
159
226
  this.desiredMusic = next;
160
227
  }
161
228
  this.startDesiredMusic();
162
229
  }
163
230
  startDesiredMusic() {
164
231
  const music = this.desiredMusic;
165
- if (!music || !this.options.assets[music.assetId] || this.paused || this.context.state !== "running" || this.voices.has(music.voiceId) || this.pending.has(music.voiceId))
232
+ if (!music || !this.options.assets[music.assetId] || this.paused || this.context.state !== "running" || this.voices.has(music.voiceId) || this.streamed.has(music.voiceId) || this.failedStreams.has(music.voiceId) || this.pending.has(music.voiceId))
166
233
  return;
234
+ const streaming = this.streamingSettings() !== undefined;
167
235
  const token = Symbol();
168
236
  this.pending.set(music.voiceId, { token, assetId: music.assetId });
169
237
  void this.start(music.voiceId, music.assetId, true, music.volume, music.fadeInTicks, music.fadeOutTicks, token, music.startTick)
170
- .catch((error) => this.options.status(`Music could not start: ${error instanceof Error ? error.message : String(error)}`))
238
+ .catch((error) => {
239
+ if (this.pending.get(music.voiceId)?.token !== token)
240
+ return;
241
+ if (streaming)
242
+ this.failedStreams.add(music.voiceId);
243
+ this.options.status(`Music could not start: ${error instanceof Error ? error.message : String(error)}`);
244
+ })
171
245
  .finally(() => { if (this.pending.get(music.voiceId)?.token === token)
172
246
  this.pending.delete(music.voiceId); });
173
247
  }
248
+ streamingSettings() {
249
+ const music = this.options.scenes?.find((scene) => scene.id === this.sceneId)?.music;
250
+ return music?.assetId === this.desiredMusic?.assetId ? music?.streaming : undefined;
251
+ }
252
+ voiceCount() {
253
+ return this.voices.size + this.fading.size + this.streamed.size + this.streamingFades.size;
254
+ }
255
+ makeVoiceRoom() {
256
+ if (this.voiceCount() < MAX_VOICES)
257
+ return true;
258
+ const stream = this.streamingFades.keys().next().value;
259
+ if (stream)
260
+ stream.dispose();
261
+ else {
262
+ const fade = this.fading.values().next().value;
263
+ if (fade) {
264
+ fade.source.stop();
265
+ this.fading.delete(fade);
266
+ }
267
+ else {
268
+ const effect = [...this.voices.values()].find((voice) => !voice.loop);
269
+ if (!effect)
270
+ return false;
271
+ this.stop(effect.id, 0);
272
+ }
273
+ }
274
+ return this.voiceCount() < MAX_VOICES;
275
+ }
276
+ async startStream(id, assetId, volume, fadeInTicks, logicalStartTick, token, settings) {
277
+ const generation = this.generation;
278
+ const binding = this.options.assets[assetId];
279
+ if (!binding)
280
+ return;
281
+ const url = await this.options.resolveAsset(binding);
282
+ if (this.disposed || this.paused || generation !== this.generation || this.context.state !== "running" ||
283
+ this.pending.get(id)?.token !== token || this.streamed.has(id))
284
+ return;
285
+ if (!url)
286
+ throw new Error("asset URL is unavailable");
287
+ if (!this.makeVoiceRoom())
288
+ return;
289
+ const bus = this.mixer.busFor(assetId, true);
290
+ const voice = new GameStreamingMusicVoice(this.context, url, this.mixer.input(bus), settings, volume, fadeInTicks / this.options.tickRate, () => Math.max(0, this.tick - logicalStartTick) / this.options.tickRate, (message) => {
291
+ if (this.streamed.get(id)?.voice === voice) {
292
+ this.failedStreams.add(id);
293
+ }
294
+ this.options.status(message);
295
+ }, () => {
296
+ this.mixer.voiceEnded(bus);
297
+ if (this.streamed.get(id)?.voice === voice)
298
+ this.streamed.delete(id);
299
+ this.streamingFades.delete(voice);
300
+ this.deferredFades.delete(voice);
301
+ }, () => {
302
+ if (this.streamed.get(id)?.voice === voice) {
303
+ this.finishDeferredFades();
304
+ }
305
+ });
306
+ this.streamed.set(id, { voice, assetId });
307
+ this.mixer.voiceStarted(bus);
308
+ voice.load();
309
+ }
174
310
  /**
175
311
  * Moves the listener and every spatial voice to a rendered frame. Players call it once per rendered frame with the view
176
312
  * built from the interpolated frame, so positions follow what is on screen. Emitters missing from the view keep their last position.
@@ -251,23 +387,17 @@ export class GameAudioPlayer {
251
387
  }
252
388
  return;
253
389
  }
390
+ const streaming = logicalStartTick === undefined ? undefined : this.streamingSettings();
391
+ if (streaming && logicalStartTick !== undefined) {
392
+ await this.startStream(id, assetId, volume, fadeInTicks, logicalStartTick, token, streaming);
393
+ return;
394
+ }
254
395
  const buffer = await this.buffer(assetId);
255
396
  if (!buffer || this.disposed || this.paused || this.context.state !== "running" || generation !== this.generation ||
256
397
  this.pending.get(id)?.token !== token || this.voices.has(id))
257
398
  return;
258
- if (this.voices.size + this.fading.size >= MAX_VOICES && this.fading.size > 0) {
259
- const fading = this.fading.values().next().value;
260
- if (fading) {
261
- fading.source.stop();
262
- this.fading.delete(fading);
263
- }
264
- }
265
- if (this.voices.size + this.fading.size >= MAX_VOICES) {
266
- const oldestEffect = [...this.voices.values()].find((voice) => !voice.loop);
267
- if (!oldestEffect)
268
- return;
269
- this.stop(oldestEffect.id, 0);
270
- }
399
+ if (!this.makeVoiceRoom())
400
+ return;
271
401
  const source = this.context.createBufferSource();
272
402
  const gain = this.context.createGain();
273
403
  source.buffer = buffer;
@@ -291,15 +421,39 @@ export class GameAudioPlayer {
291
421
  if (this.voices.get(id) === voice)
292
422
  this.voices.delete(id);
293
423
  this.fading.delete(voice);
424
+ this.deferredDecodedFades.delete(voice);
294
425
  source.disconnect();
295
426
  gain.disconnect();
296
427
  output.release();
297
428
  };
298
429
  const offset = logicalStartTick === undefined ? 0 : Math.max(0, this.tick - logicalStartTick) / this.options.tickRate;
299
430
  source.start(0, loop && buffer.duration > 0 ? offset % buffer.duration : 0);
431
+ if (logicalStartTick !== undefined) {
432
+ this.finishDeferredFades();
433
+ }
434
+ }
435
+ finishDeferredFades() {
436
+ const outgoing = [...this.deferredFades];
437
+ this.deferredFades.clear();
438
+ for (const [voice, ticks] of outgoing) {
439
+ voice.stop(ticks / this.options.tickRate);
440
+ }
441
+ for (const [voice, ticks] of this.deferredDecodedFades) {
442
+ if (this.fading.has(voice)) {
443
+ stopGameVoice(voice, this.context, ticks, this.options.tickRate, this.fading);
444
+ }
445
+ }
446
+ this.deferredDecodedFades.clear();
300
447
  }
301
448
  stop(id, fadeTicks) {
302
449
  this.pending.delete(id);
450
+ this.failedStreams.delete(id);
451
+ const stream = this.streamed.get(id);
452
+ if (stream) {
453
+ this.streamed.delete(id);
454
+ this.streamingFades.set(stream.voice, stream.assetId);
455
+ stream.voice.stop(fadeTicks / this.options.tickRate);
456
+ }
303
457
  const voice = this.voices.get(id);
304
458
  if (!voice)
305
459
  return;
@@ -309,11 +463,17 @@ export class GameAudioPlayer {
309
463
  reset(snapshot) {
310
464
  this.generation += 1;
311
465
  this.pending.clear();
466
+ this.failedStreams.clear();
467
+ for (const id of this.streamed.keys())
468
+ this.stop(id, 0);
469
+ for (const voice of this.streamingFades.keys())
470
+ voice.dispose();
312
471
  for (const id of this.voices.keys())
313
472
  this.stop(id, 0);
314
473
  for (const voice of this.fading)
315
474
  voice.source.stop();
316
475
  this.fading.clear();
476
+ this.deferredDecodedFades.clear();
317
477
  this.desiredMusic = null;
318
478
  this.mixer.reset(snapshot.sceneId);
319
479
  this.sync(snapshot);
@@ -324,11 +484,17 @@ export class GameAudioPlayer {
324
484
  this.disposed = true;
325
485
  this.generation += 1;
326
486
  this.pending.clear();
487
+ this.failedStreams.clear();
488
+ for (const id of this.streamed.keys())
489
+ this.stop(id, 0);
490
+ for (const voice of this.streamingFades.keys())
491
+ voice.dispose();
327
492
  for (const id of this.voices.keys())
328
493
  this.stop(id, 0);
329
494
  for (const voice of this.fading)
330
495
  voice.source.stop();
331
496
  this.fading.clear();
497
+ this.deferredDecodedFades.clear();
332
498
  this.buffers.clear();
333
499
  this.mixer.dispose();
334
500
  void this.context.close();