waa-play 0.1.0 → 0.2.0
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 +60 -95
- package/dist/adapters.cjs +6 -6
- package/dist/adapters.d.cts +14 -2
- package/dist/adapters.d.ts +14 -2
- package/dist/adapters.js +1 -1
- package/dist/{chunk-V2QX5K42.js → chunk-2FFORBOP.js} +24 -4
- package/dist/chunk-2FFORBOP.js.map +1 -0
- package/dist/chunk-7S5KWTZ6.cjs +93 -0
- package/dist/chunk-7S5KWTZ6.cjs.map +1 -0
- package/dist/{chunk-PZE6HTZR.cjs → chunk-CTUCTTIE.cjs} +13 -4
- package/dist/chunk-CTUCTTIE.cjs.map +1 -0
- package/dist/chunk-IMNRPYBM.js +146 -0
- package/dist/chunk-IMNRPYBM.js.map +1 -0
- package/dist/{chunk-TULV7V5M.cjs → chunk-SIMLANWE.cjs} +24 -4
- package/dist/chunk-SIMLANWE.cjs.map +1 -0
- package/dist/chunk-VKT7YCWK.js +87 -0
- package/dist/chunk-VKT7YCWK.js.map +1 -0
- package/dist/{chunk-RWJ4EWJT.js → chunk-X4IFO7U7.js} +13 -4
- package/dist/chunk-X4IFO7U7.js.map +1 -0
- package/dist/chunk-XZBMBZA3.cjs +148 -0
- package/dist/chunk-XZBMBZA3.cjs.map +1 -0
- package/dist/engine-QUMYW73L.cjs +13 -0
- package/dist/{engine-5JK2FCNL.cjs.map → engine-QUMYW73L.cjs.map} +1 -1
- package/dist/engine-TYI7OX7O.js +4 -0
- package/dist/{engine-M2U4LE3F.js.map → engine-TYI7OX7O.js.map} +1 -1
- package/dist/index.cjs +13 -8
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -2
- package/dist/play.cjs +2 -2
- package/dist/play.js +1 -1
- package/dist/player.cjs +22 -0
- package/dist/player.cjs.map +1 -0
- package/dist/player.d.cts +64 -0
- package/dist/player.d.ts +64 -0
- package/dist/player.js +13 -0
- package/dist/player.js.map +1 -0
- package/dist/stretcher.cjs +2 -2
- package/dist/stretcher.d.cts +2 -0
- package/dist/stretcher.d.ts +2 -0
- package/dist/stretcher.js +1 -1
- package/package.json +13 -4
- package/dist/chunk-AGP2IRC6.js +0 -63
- package/dist/chunk-AGP2IRC6.js.map +0 -1
- package/dist/chunk-HTGOHC73.cjs +0 -69
- package/dist/chunk-HTGOHC73.cjs.map +0 -1
- package/dist/chunk-PZE6HTZR.cjs.map +0 -1
- package/dist/chunk-RWJ4EWJT.js.map +0 -1
- package/dist/chunk-TULV7V5M.cjs.map +0 -1
- package/dist/chunk-V2QX5K42.js.map +0 -1
- package/dist/engine-5JK2FCNL.cjs +0 -13
- package/dist/engine-M2U4LE3F.js +0 -4
package/dist/stretcher.d.cts
CHANGED
|
@@ -83,6 +83,7 @@ interface StretcherEvents {
|
|
|
83
83
|
};
|
|
84
84
|
complete: void;
|
|
85
85
|
ended: void;
|
|
86
|
+
loop: void;
|
|
86
87
|
error: {
|
|
87
88
|
message: string;
|
|
88
89
|
chunkIndex?: number;
|
|
@@ -97,6 +98,7 @@ interface StretcherEngine {
|
|
|
97
98
|
seek(position: number): void;
|
|
98
99
|
stop(): void;
|
|
99
100
|
setTempo(tempo: number): void;
|
|
101
|
+
setLoop(loop: boolean): void;
|
|
100
102
|
getCurrentPosition(): number;
|
|
101
103
|
getStatus(): StretcherStatus;
|
|
102
104
|
getSnapshot(): StretcherSnapshotExtension;
|
package/dist/stretcher.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ interface StretcherEvents {
|
|
|
83
83
|
};
|
|
84
84
|
complete: void;
|
|
85
85
|
ended: void;
|
|
86
|
+
loop: void;
|
|
86
87
|
error: {
|
|
87
88
|
message: string;
|
|
88
89
|
chunkIndex?: number;
|
|
@@ -97,6 +98,7 @@ interface StretcherEngine {
|
|
|
97
98
|
seek(position: number): void;
|
|
98
99
|
stop(): void;
|
|
99
100
|
setTempo(tempo: number): void;
|
|
101
|
+
setLoop(loop: boolean): void;
|
|
100
102
|
getCurrentPosition(): number;
|
|
101
103
|
getStatus(): StretcherStatus;
|
|
102
104
|
getSnapshot(): StretcherSnapshotExtension;
|
package/dist/stretcher.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "waa-play",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Convenient composable building blocks for Web Audio API. Time-stretch, streaming playback, waveform extraction, BYO AudioContext, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -82,6 +82,12 @@
|
|
|
82
82
|
"types": "./dist/stretcher.d.ts",
|
|
83
83
|
"default": "./dist/stretcher.js"
|
|
84
84
|
}
|
|
85
|
+
},
|
|
86
|
+
"./player": {
|
|
87
|
+
"import": {
|
|
88
|
+
"types": "./dist/player.d.ts",
|
|
89
|
+
"default": "./dist/player.js"
|
|
90
|
+
}
|
|
85
91
|
}
|
|
86
92
|
},
|
|
87
93
|
"sideEffects": false,
|
|
@@ -100,11 +106,14 @@
|
|
|
100
106
|
"keywords": [
|
|
101
107
|
"web-audio",
|
|
102
108
|
"audio",
|
|
109
|
+
"audio-player",
|
|
103
110
|
"sound",
|
|
104
111
|
"webaudio",
|
|
105
112
|
"audiocontext",
|
|
106
|
-
"
|
|
107
|
-
"
|
|
113
|
+
"time-stretch",
|
|
114
|
+
"wsola",
|
|
115
|
+
"playback",
|
|
116
|
+
"pitch-preserving"
|
|
108
117
|
],
|
|
109
118
|
"license": "MIT",
|
|
110
119
|
"author": "ivgtr",
|
package/dist/chunk-AGP2IRC6.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// src/adapters.ts
|
|
2
|
-
function getSnapshot(playback) {
|
|
3
|
-
const base = {
|
|
4
|
-
state: playback.getState(),
|
|
5
|
-
position: playback.getCurrentTime(),
|
|
6
|
-
duration: playback.getDuration(),
|
|
7
|
-
progress: playback.getProgress()
|
|
8
|
-
};
|
|
9
|
-
const getter = playback["_getStretcherSnapshot"];
|
|
10
|
-
if (typeof getter === "function") {
|
|
11
|
-
const stretcher = getter();
|
|
12
|
-
if (stretcher) {
|
|
13
|
-
base.stretcher = stretcher;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return base;
|
|
17
|
-
}
|
|
18
|
-
function subscribeSnapshot(playback, callback) {
|
|
19
|
-
const unsubs = [];
|
|
20
|
-
unsubs.push(playback.on("statechange", callback));
|
|
21
|
-
unsubs.push(playback.on("timeupdate", callback));
|
|
22
|
-
unsubs.push(playback.on("seek", callback));
|
|
23
|
-
unsubs.push(playback.on("ended", callback));
|
|
24
|
-
return () => {
|
|
25
|
-
for (const unsub of unsubs) unsub();
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
function onFrame(playback, callback) {
|
|
29
|
-
let rafId = null;
|
|
30
|
-
function tick() {
|
|
31
|
-
callback(getSnapshot(playback));
|
|
32
|
-
rafId = requestAnimationFrame(tick);
|
|
33
|
-
}
|
|
34
|
-
rafId = requestAnimationFrame(tick);
|
|
35
|
-
return () => {
|
|
36
|
-
if (rafId !== null) {
|
|
37
|
-
cancelAnimationFrame(rafId);
|
|
38
|
-
rafId = null;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function whenEnded(playback) {
|
|
43
|
-
return new Promise((resolve) => {
|
|
44
|
-
const unsub = playback.on("ended", () => {
|
|
45
|
-
unsub();
|
|
46
|
-
resolve();
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
function whenPosition(playback, position) {
|
|
51
|
-
return new Promise((resolve) => {
|
|
52
|
-
const unsub = playback.on("timeupdate", ({ position: current }) => {
|
|
53
|
-
if (current >= position) {
|
|
54
|
-
unsub();
|
|
55
|
-
resolve();
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export { getSnapshot, onFrame, subscribeSnapshot, whenEnded, whenPosition };
|
|
62
|
-
//# sourceMappingURL=chunk-AGP2IRC6.js.map
|
|
63
|
-
//# sourceMappingURL=chunk-AGP2IRC6.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters.ts"],"names":[],"mappings":";AAUO,SAAS,YAAY,QAAA,EAAsC;AAChE,EAAA,MAAM,IAAA,GAAyB;AAAA,IAC7B,KAAA,EAAO,SAAS,QAAA,EAAS;AAAA,IACzB,QAAA,EAAU,SAAS,cAAA,EAAe;AAAA,IAClC,QAAA,EAAU,SAAS,WAAA,EAAY;AAAA,IAC/B,QAAA,EAAU,SAAS,WAAA;AAAY,GACjC;AAGA,EAAA,MAAM,MAAA,GAAU,SAAgD,uBAAuB,CAAA;AACvF,EAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAChC,IAAA,MAAM,YAAa,MAAA,EAA+C;AAClE,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,IACnB;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAiBO,SAAS,iBAAA,CACd,UACA,QAAA,EACY;AACZ,EAAA,MAAM,SAA4B,EAAC;AAEnC,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,aAAA,EAAe,QAAQ,CAAC,CAAA;AAChD,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,YAAA,EAAc,QAAQ,CAAC,CAAA;AAC/C,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,MAAA,EAAQ,QAAQ,CAAC,CAAA;AACzC,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,OAAA,EAAS,QAAQ,CAAC,CAAA;AAE1C,EAAA,OAAO,MAAM;AACX,IAAA,KAAA,MAAW,KAAA,IAAS,QAAQ,KAAA,EAAM;AAAA,EACpC,CAAA;AACF;AAQO,SAAS,OAAA,CACd,UACA,QAAA,EACY;AACZ,EAAA,IAAI,KAAA,GAAuB,IAAA;AAE3B,EAAA,SAAS,IAAA,GAAO;AACd,IAAA,QAAA,CAAS,WAAA,CAAY,QAAQ,CAAC,CAAA;AAC9B,IAAA,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAAA,EACpC;AAEA,EAAA,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAElC,EAAA,OAAO,MAAM;AACX,IAAA,IAAI,UAAU,IAAA,EAAM;AAClB,MAAA,oBAAA,CAAqB,KAAK,CAAA;AAC1B,MAAA,KAAA,GAAQ,IAAA;AAAA,IACV;AAAA,EACF,CAAA;AACF;AAMO,SAAS,UAAU,QAAA,EAAmC;AAC3D,EAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AACpC,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,EAAA,CAAG,OAAA,EAAS,MAAM;AACvC,MAAA,KAAA,EAAM;AACN,MAAA,OAAA,EAAQ;AAAA,IACV,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;AAMO,SAAS,YAAA,CACd,UACA,QAAA,EACe;AACf,EAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AACpC,IAAA,MAAM,KAAA,GAAQ,SAAS,EAAA,CAAG,YAAA,EAAc,CAAC,EAAE,QAAA,EAAU,SAAQ,KAAM;AACjE,MAAA,IAAI,WAAW,QAAA,EAAU;AACvB,QAAA,KAAA,EAAM;AACN,QAAA,OAAA,EAAQ;AAAA,MACV;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH","file":"chunk-AGP2IRC6.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M10: Framework adapters\n// ---------------------------------------------------------------------------\n\nimport type { Playback, PlaybackSnapshot } from \"./types.js\";\n\n/**\n * Get an immutable snapshot of the current playback state.\n * Designed for use with React's `useSyncExternalStore` or similar patterns.\n */\nexport function getSnapshot(playback: Playback): PlaybackSnapshot {\n const base: PlaybackSnapshot = {\n state: playback.getState(),\n position: playback.getCurrentTime(),\n duration: playback.getDuration(),\n progress: playback.getProgress(),\n };\n\n // Include stretcher snapshot if available (no static import required)\n const getter = (playback as unknown as Record<string, unknown>)[\"_getStretcherSnapshot\"];\n if (typeof getter === \"function\") {\n const stretcher = (getter as () => PlaybackSnapshot[\"stretcher\"])();\n if (stretcher) {\n base.stretcher = stretcher;\n }\n }\n\n return base;\n}\n\n/**\n * Subscribe to playback state changes, calling `callback` with a fresh\n * snapshot whenever the state updates.\n *\n * Returns an unsubscribe function. Works as the `subscribe` parameter for\n * React's `useSyncExternalStore`.\n *\n * ```ts\n * // React example:\n * const snap = useSyncExternalStore(\n * (cb) => subscribeSnapshot(playback, cb),\n * () => getSnapshot(playback),\n * );\n * ```\n */\nexport function subscribeSnapshot(\n playback: Playback,\n callback: () => void,\n): () => void {\n const unsubs: Array<() => void> = [];\n\n unsubs.push(playback.on(\"statechange\", callback));\n unsubs.push(playback.on(\"timeupdate\", callback));\n unsubs.push(playback.on(\"seek\", callback));\n unsubs.push(playback.on(\"ended\", callback));\n\n return () => {\n for (const unsub of unsubs) unsub();\n };\n}\n\n/**\n * Call `callback` on every animation frame with the current playback snapshot.\n * Useful for smooth UI animations (waveform cursors, progress bars, etc.).\n *\n * Returns a `stop` function that cancels the loop.\n */\nexport function onFrame(\n playback: Playback,\n callback: (snapshot: PlaybackSnapshot) => void,\n): () => void {\n let rafId: number | null = null;\n\n function tick() {\n callback(getSnapshot(playback));\n rafId = requestAnimationFrame(tick);\n }\n\n rafId = requestAnimationFrame(tick);\n\n return () => {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n };\n}\n\n/**\n * Return a `Promise` that resolves when the playback reaches the `\"stopped\"`\n * state via the `ended` event (natural end, not manual stop).\n */\nexport function whenEnded(playback: Playback): Promise<void> {\n return new Promise<void>((resolve) => {\n const unsub = playback.on(\"ended\", () => {\n unsub();\n resolve();\n });\n });\n}\n\n/**\n * Return a `Promise` that resolves when the playback position reaches or\n * exceeds `position` seconds.\n */\nexport function whenPosition(\n playback: Playback,\n position: number,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n const unsub = playback.on(\"timeupdate\", ({ position: current }) => {\n if (current >= position) {\n unsub();\n resolve();\n }\n });\n });\n}\n"]}
|
package/dist/chunk-HTGOHC73.cjs
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/adapters.ts
|
|
4
|
-
function getSnapshot(playback) {
|
|
5
|
-
const base = {
|
|
6
|
-
state: playback.getState(),
|
|
7
|
-
position: playback.getCurrentTime(),
|
|
8
|
-
duration: playback.getDuration(),
|
|
9
|
-
progress: playback.getProgress()
|
|
10
|
-
};
|
|
11
|
-
const getter = playback["_getStretcherSnapshot"];
|
|
12
|
-
if (typeof getter === "function") {
|
|
13
|
-
const stretcher = getter();
|
|
14
|
-
if (stretcher) {
|
|
15
|
-
base.stretcher = stretcher;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return base;
|
|
19
|
-
}
|
|
20
|
-
function subscribeSnapshot(playback, callback) {
|
|
21
|
-
const unsubs = [];
|
|
22
|
-
unsubs.push(playback.on("statechange", callback));
|
|
23
|
-
unsubs.push(playback.on("timeupdate", callback));
|
|
24
|
-
unsubs.push(playback.on("seek", callback));
|
|
25
|
-
unsubs.push(playback.on("ended", callback));
|
|
26
|
-
return () => {
|
|
27
|
-
for (const unsub of unsubs) unsub();
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function onFrame(playback, callback) {
|
|
31
|
-
let rafId = null;
|
|
32
|
-
function tick() {
|
|
33
|
-
callback(getSnapshot(playback));
|
|
34
|
-
rafId = requestAnimationFrame(tick);
|
|
35
|
-
}
|
|
36
|
-
rafId = requestAnimationFrame(tick);
|
|
37
|
-
return () => {
|
|
38
|
-
if (rafId !== null) {
|
|
39
|
-
cancelAnimationFrame(rafId);
|
|
40
|
-
rafId = null;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function whenEnded(playback) {
|
|
45
|
-
return new Promise((resolve) => {
|
|
46
|
-
const unsub = playback.on("ended", () => {
|
|
47
|
-
unsub();
|
|
48
|
-
resolve();
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
function whenPosition(playback, position) {
|
|
53
|
-
return new Promise((resolve) => {
|
|
54
|
-
const unsub = playback.on("timeupdate", ({ position: current }) => {
|
|
55
|
-
if (current >= position) {
|
|
56
|
-
unsub();
|
|
57
|
-
resolve();
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
exports.getSnapshot = getSnapshot;
|
|
64
|
-
exports.onFrame = onFrame;
|
|
65
|
-
exports.subscribeSnapshot = subscribeSnapshot;
|
|
66
|
-
exports.whenEnded = whenEnded;
|
|
67
|
-
exports.whenPosition = whenPosition;
|
|
68
|
-
//# sourceMappingURL=chunk-HTGOHC73.cjs.map
|
|
69
|
-
//# sourceMappingURL=chunk-HTGOHC73.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters.ts"],"names":[],"mappings":";;;AAUO,SAAS,YAAY,QAAA,EAAsC;AAChE,EAAA,MAAM,IAAA,GAAyB;AAAA,IAC7B,KAAA,EAAO,SAAS,QAAA,EAAS;AAAA,IACzB,QAAA,EAAU,SAAS,cAAA,EAAe;AAAA,IAClC,QAAA,EAAU,SAAS,WAAA,EAAY;AAAA,IAC/B,QAAA,EAAU,SAAS,WAAA;AAAY,GACjC;AAGA,EAAA,MAAM,MAAA,GAAU,SAAgD,uBAAuB,CAAA;AACvF,EAAA,IAAI,OAAO,WAAW,UAAA,EAAY;AAChC,IAAA,MAAM,YAAa,MAAA,EAA+C;AAClE,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,IACnB;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAiBO,SAAS,iBAAA,CACd,UACA,QAAA,EACY;AACZ,EAAA,MAAM,SAA4B,EAAC;AAEnC,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,aAAA,EAAe,QAAQ,CAAC,CAAA;AAChD,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,YAAA,EAAc,QAAQ,CAAC,CAAA;AAC/C,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,MAAA,EAAQ,QAAQ,CAAC,CAAA;AACzC,EAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,EAAA,CAAG,OAAA,EAAS,QAAQ,CAAC,CAAA;AAE1C,EAAA,OAAO,MAAM;AACX,IAAA,KAAA,MAAW,KAAA,IAAS,QAAQ,KAAA,EAAM;AAAA,EACpC,CAAA;AACF;AAQO,SAAS,OAAA,CACd,UACA,QAAA,EACY;AACZ,EAAA,IAAI,KAAA,GAAuB,IAAA;AAE3B,EAAA,SAAS,IAAA,GAAO;AACd,IAAA,QAAA,CAAS,WAAA,CAAY,QAAQ,CAAC,CAAA;AAC9B,IAAA,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAAA,EACpC;AAEA,EAAA,KAAA,GAAQ,sBAAsB,IAAI,CAAA;AAElC,EAAA,OAAO,MAAM;AACX,IAAA,IAAI,UAAU,IAAA,EAAM;AAClB,MAAA,oBAAA,CAAqB,KAAK,CAAA;AAC1B,MAAA,KAAA,GAAQ,IAAA;AAAA,IACV;AAAA,EACF,CAAA;AACF;AAMO,SAAS,UAAU,QAAA,EAAmC;AAC3D,EAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AACpC,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,EAAA,CAAG,OAAA,EAAS,MAAM;AACvC,MAAA,KAAA,EAAM;AACN,MAAA,OAAA,EAAQ;AAAA,IACV,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;AAMO,SAAS,YAAA,CACd,UACA,QAAA,EACe;AACf,EAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AACpC,IAAA,MAAM,KAAA,GAAQ,SAAS,EAAA,CAAG,YAAA,EAAc,CAAC,EAAE,QAAA,EAAU,SAAQ,KAAM;AACjE,MAAA,IAAI,WAAW,QAAA,EAAU;AACvB,QAAA,KAAA,EAAM;AACN,QAAA,OAAA,EAAQ;AAAA,MACV;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH","file":"chunk-HTGOHC73.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M10: Framework adapters\n// ---------------------------------------------------------------------------\n\nimport type { Playback, PlaybackSnapshot } from \"./types.js\";\n\n/**\n * Get an immutable snapshot of the current playback state.\n * Designed for use with React's `useSyncExternalStore` or similar patterns.\n */\nexport function getSnapshot(playback: Playback): PlaybackSnapshot {\n const base: PlaybackSnapshot = {\n state: playback.getState(),\n position: playback.getCurrentTime(),\n duration: playback.getDuration(),\n progress: playback.getProgress(),\n };\n\n // Include stretcher snapshot if available (no static import required)\n const getter = (playback as unknown as Record<string, unknown>)[\"_getStretcherSnapshot\"];\n if (typeof getter === \"function\") {\n const stretcher = (getter as () => PlaybackSnapshot[\"stretcher\"])();\n if (stretcher) {\n base.stretcher = stretcher;\n }\n }\n\n return base;\n}\n\n/**\n * Subscribe to playback state changes, calling `callback` with a fresh\n * snapshot whenever the state updates.\n *\n * Returns an unsubscribe function. Works as the `subscribe` parameter for\n * React's `useSyncExternalStore`.\n *\n * ```ts\n * // React example:\n * const snap = useSyncExternalStore(\n * (cb) => subscribeSnapshot(playback, cb),\n * () => getSnapshot(playback),\n * );\n * ```\n */\nexport function subscribeSnapshot(\n playback: Playback,\n callback: () => void,\n): () => void {\n const unsubs: Array<() => void> = [];\n\n unsubs.push(playback.on(\"statechange\", callback));\n unsubs.push(playback.on(\"timeupdate\", callback));\n unsubs.push(playback.on(\"seek\", callback));\n unsubs.push(playback.on(\"ended\", callback));\n\n return () => {\n for (const unsub of unsubs) unsub();\n };\n}\n\n/**\n * Call `callback` on every animation frame with the current playback snapshot.\n * Useful for smooth UI animations (waveform cursors, progress bars, etc.).\n *\n * Returns a `stop` function that cancels the loop.\n */\nexport function onFrame(\n playback: Playback,\n callback: (snapshot: PlaybackSnapshot) => void,\n): () => void {\n let rafId: number | null = null;\n\n function tick() {\n callback(getSnapshot(playback));\n rafId = requestAnimationFrame(tick);\n }\n\n rafId = requestAnimationFrame(tick);\n\n return () => {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n };\n}\n\n/**\n * Return a `Promise` that resolves when the playback reaches the `\"stopped\"`\n * state via the `ended` event (natural end, not manual stop).\n */\nexport function whenEnded(playback: Playback): Promise<void> {\n return new Promise<void>((resolve) => {\n const unsub = playback.on(\"ended\", () => {\n unsub();\n resolve();\n });\n });\n}\n\n/**\n * Return a `Promise` that resolves when the playback position reaches or\n * exceeds `position` seconds.\n */\nexport function whenPosition(\n playback: Playback,\n position: number,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n const unsub = playback.on(\"timeupdate\", ({ position: current }) => {\n if (current >= position) {\n unsub();\n resolve();\n }\n });\n });\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/play.ts"],"names":["createEmitter"],"mappings":"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","file":"chunk-PZE6HTZR.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// M3: Playback engine\n// ---------------------------------------------------------------------------\n\nimport { createEmitter } from \"./emitter.js\";\nimport type {\n Playback,\n PlaybackEventMap,\n PlaybackState,\n PlayOptions,\n StretcherSnapshotExtension,\n} from \"./types.js\";\n\n/**\n * Play an `AudioBuffer` through an `AudioContext` and return a controllable\n * `Playback` handle.\n *\n * ```ts\n * const pb = play(ctx, buffer, { loop: true });\n * pb.on(\"timeupdate\", ({ position }) => console.log(position));\n * pb.pause();\n * pb.resume();\n * pb.seek(10);\n * pb.stop();\n * pb.dispose();\n * ```\n */\nexport function play(\n ctx: AudioContext,\n buffer: AudioBuffer,\n options?: PlayOptions,\n): Playback {\n const { preservePitch = true } = options ?? {};\n\n // ----- Pitch-preserving mode (WSOLA-based time-stretch) -----\n if (preservePitch) {\n return createStretchedPlayback(ctx, buffer, options ?? {});\n }\n\n const {\n offset: initialOffset = 0,\n loop = false,\n loopStart,\n loopEnd,\n playbackRate: initialRate = 1,\n through = [],\n destination = ctx.destination,\n timeupdateInterval = 50,\n } = options ?? {};\n\n const emitter = createEmitter<PlaybackEventMap>();\n const duration = buffer.duration;\n\n // ----- mutable internal state -----\n let state: PlaybackState = \"stopped\";\n let sourceNode: AudioBufferSourceNode | null = null;\n let startedAt = 0; // ctx.currentTime when playback last started/resumed\n let pausedAt = initialOffset; // position in the buffer (seconds)\n let currentRate = initialRate;\n let isLooping = loop;\n let timerId: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n\n // ----- helpers -----\n\n function createSource(): AudioBufferSourceNode {\n const src = ctx.createBufferSource();\n src.buffer = buffer;\n src.playbackRate.value = currentRate;\n src.loop = isLooping;\n if (loopStart !== undefined) src.loopStart = loopStart;\n if (loopEnd !== undefined) src.loopEnd = loopEnd;\n\n // Connect through the node chain (or directly to destination).\n if (through.length > 0) {\n src.connect(through[0]!);\n for (let i = 0; i < through.length - 1; i++) {\n through[i]!.connect(through[i + 1]!);\n }\n through[through.length - 1]!.connect(destination);\n } else {\n src.connect(destination);\n }\n\n src.onended = handleEnded;\n return src;\n }\n\n function startSource(positionInBuffer: number) {\n sourceNode = createSource();\n sourceNode.start(0, positionInBuffer);\n startedAt = ctx.currentTime - positionInBuffer / currentRate;\n }\n\n function stopSource() {\n if (sourceNode) {\n sourceNode.onended = null;\n try {\n sourceNode.stop();\n } catch {\n // Already stopped — safe to ignore.\n }\n sourceNode.disconnect();\n sourceNode = null;\n }\n }\n\n function handleEnded() {\n // If we manually stopped / paused, the handler was already removed.\n if (state !== \"playing\") return;\n if (isLooping) {\n emitter.emit(\"loop\", undefined as never);\n return;\n }\n setState(\"stopped\");\n pausedAt = 0;\n stopTimer();\n emitter.emit(\"ended\", undefined as never);\n }\n\n function setState(next: PlaybackState) {\n if (state === next) return;\n state = next;\n emitter.emit(\"statechange\", { state: next });\n }\n\n function startTimer() {\n if (timerId !== null) return;\n timerId = setInterval(() => {\n if (state !== \"playing\") return;\n emitter.emit(\"timeupdate\", {\n position: getCurrentTime(),\n duration,\n });\n }, timeupdateInterval);\n }\n\n function stopTimer() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n }\n\n // ----- public API -----\n\n function getCurrentTime(): number {\n if (state === \"playing\") {\n const elapsed = (ctx.currentTime - startedAt) * currentRate;\n if (isLooping) {\n const loopDur =\n (loopEnd ?? duration) - (loopStart ?? 0);\n return ((elapsed - (loopStart ?? 0)) % loopDur) + (loopStart ?? 0);\n }\n return Math.min(elapsed, duration);\n }\n if (state === \"paused\") return pausedAt;\n return 0;\n }\n\n function pause() {\n if (state !== \"playing\" || disposed) return;\n pausedAt = getCurrentTime();\n stopSource();\n stopTimer();\n setState(\"paused\");\n emitter.emit(\"pause\", undefined as never);\n }\n\n function resume() {\n if (state !== \"paused\" || disposed) return;\n startSource(pausedAt);\n setState(\"playing\");\n startTimer();\n emitter.emit(\"resume\", undefined as never);\n }\n\n function togglePlayPause() {\n if (state === \"playing\") pause();\n else if (state === \"paused\") resume();\n }\n\n function seek(position: number) {\n if (disposed) return;\n const clamped = Math.max(0, Math.min(position, duration));\n const wasPlaying = state === \"playing\";\n\n stopSource();\n stopTimer();\n\n pausedAt = clamped;\n\n if (wasPlaying) {\n startSource(clamped);\n startTimer();\n }\n\n emitter.emit(\"seek\", { position: clamped });\n }\n\n function stop() {\n if (state === \"stopped\" || disposed) return;\n stopSource();\n stopTimer();\n pausedAt = 0;\n setState(\"stopped\");\n emitter.emit(\"stop\", undefined as never);\n }\n\n function setPlaybackRate(rate: number) {\n const position = getCurrentTime();\n currentRate = rate;\n if (sourceNode) {\n sourceNode.playbackRate.value = rate;\n startedAt = ctx.currentTime - position / rate;\n }\n }\n\n function setLoop(value: boolean) {\n isLooping = value;\n if (sourceNode) {\n sourceNode.loop = value;\n }\n }\n\n function dispose() {\n if (disposed) return;\n disposed = true;\n stopSource();\n stopTimer();\n emitter.clear();\n }\n\n // ----- kick off initial playback -----\n\n startSource(initialOffset);\n setState(\"playing\");\n startTimer();\n emitter.emit(\"play\", undefined as never);\n\n return {\n getState: () => state,\n getCurrentTime,\n getDuration: () => duration,\n getProgress: () => (duration > 0 ? getCurrentTime() / duration : 0),\n pause,\n resume,\n togglePlayPause,\n seek,\n stop,\n setPlaybackRate,\n setLoop,\n on: emitter.on.bind(emitter) as Playback[\"on\"],\n off: emitter.off.bind(emitter) as Playback[\"off\"],\n dispose,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Stretched playback (preservePitch: true)\n// ---------------------------------------------------------------------------\n\nfunction createStretchedPlayback(\n ctx: AudioContext,\n buffer: AudioBuffer,\n options: PlayOptions,\n): Playback {\n const {\n offset: initialOffset = 0,\n playbackRate: initialRate = 1,\n through = [],\n destination = ctx.destination,\n timeupdateInterval = 50,\n } = options;\n\n const emitter = createEmitter<PlaybackEventMap>();\n const duration = buffer.duration;\n\n let state: PlaybackState = \"playing\";\n let engineInstance: import(\"./stretcher/types.js\").StretcherEngine | null =\n null;\n let timerId: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n let currentRate = initialRate;\n let pendingSeek: number | null = null;\n\n // Emit initial play event\n emitter.emit(\"statechange\", { state: \"playing\" });\n emitter.emit(\"play\", undefined as never);\n\n // Fire-and-forget dynamic import of the stretcher engine\n import(\"./stretcher/engine.js\").then(({ createStretcherEngine }) => {\n if (disposed) return;\n\n engineInstance = createStretcherEngine(ctx, buffer, {\n tempo: currentRate,\n offset: initialOffset,\n through,\n destination,\n timeupdateInterval,\n });\n\n // Wire stretcher events to playback events\n engineInstance.on(\"buffering\", (data) => {\n if (disposed) return;\n emitter.emit(\"buffering\", data);\n });\n\n engineInstance.on(\"buffered\", (data) => {\n if (disposed) return;\n emitter.emit(\"buffered\", data);\n });\n\n engineInstance.on(\"ended\", () => {\n if (disposed) return;\n state = \"stopped\";\n stopTimer();\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"ended\", undefined as never);\n });\n\n engineInstance.on(\"error\", (data) => {\n if (disposed) return;\n if (data.fatal) {\n state = \"stopped\";\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"ended\", undefined as never);\n }\n });\n\n // Start engine and timeupdate timer\n engineInstance.start();\n startTimer();\n\n // Apply pending seek if any\n if (pendingSeek !== null) {\n engineInstance.seek(pendingSeek);\n pendingSeek = null;\n }\n\n // If we were paused before the engine loaded, pause it\n if (state === \"paused\") {\n engineInstance.pause();\n } else if (state === \"stopped\") {\n engineInstance.stop();\n }\n });\n\n function startTimer() {\n if (timerId !== null) return;\n timerId = setInterval(() => {\n if (state !== \"playing\" || disposed) return;\n emitter.emit(\"timeupdate\", {\n position: getCurrentTime(),\n duration,\n });\n }, timeupdateInterval);\n }\n\n function stopTimer() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n }\n\n function getCurrentTime(): number {\n if (pendingSeek !== null) {\n return pendingSeek;\n }\n if (engineInstance) {\n return engineInstance.getCurrentPosition();\n }\n return initialOffset;\n }\n\n function pause() {\n if (state !== \"playing\" || disposed) return;\n state = \"paused\";\n engineInstance?.pause();\n stopTimer();\n emitter.emit(\"statechange\", { state: \"paused\" });\n emitter.emit(\"pause\", undefined as never);\n }\n\n function resume() {\n if (state !== \"paused\" || disposed) return;\n state = \"playing\";\n engineInstance?.resume();\n startTimer();\n emitter.emit(\"statechange\", { state: \"playing\" });\n emitter.emit(\"resume\", undefined as never);\n }\n\n function togglePlayPause() {\n if (state === \"playing\") pause();\n else if (state === \"paused\") resume();\n }\n\n function seek(position: number) {\n if (disposed) return;\n const clamped = Math.max(0, Math.min(position, duration));\n if (engineInstance) {\n engineInstance.seek(clamped);\n } else {\n pendingSeek = clamped;\n }\n emitter.emit(\"seek\", { position: clamped });\n }\n\n function stop() {\n if (state === \"stopped\" || disposed) return;\n state = \"stopped\";\n engineInstance?.stop();\n stopTimer();\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"stop\", undefined as never);\n }\n\n function setPlaybackRate(rate: number) {\n currentRate = rate;\n if (engineInstance) {\n engineInstance.setTempo(rate);\n }\n }\n\n function setLoop(_value: boolean) {\n // Loop is not supported in stretcher mode\n }\n\n function dispose() {\n if (disposed) return;\n disposed = true;\n stopTimer();\n engineInstance?.dispose();\n emitter.clear();\n }\n\n function _getStretcherSnapshot(): StretcherSnapshotExtension | null {\n if (!engineInstance) return null;\n return engineInstance.getSnapshot();\n }\n\n const playback: Playback & {\n _getStretcherSnapshot: typeof _getStretcherSnapshot;\n } = {\n getState: () => state,\n getCurrentTime,\n getDuration: () => duration,\n getProgress: () => (duration > 0 ? getCurrentTime() / duration : 0),\n pause,\n resume,\n togglePlayPause,\n seek,\n stop,\n setPlaybackRate,\n setLoop,\n on: emitter.on.bind(emitter) as Playback[\"on\"],\n off: emitter.off.bind(emitter) as Playback[\"off\"],\n dispose,\n _getStretcherSnapshot,\n };\n\n return playback;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/play.ts"],"names":[],"mappings":";;;AA2BO,SAAS,IAAA,CACd,GAAA,EACA,MAAA,EACA,OAAA,EACU;AACV,EAAA,MAAM,EAAE,aAAA,GAAgB,IAAA,EAAK,GAAI,WAAW,EAAC;AAG7C,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,OAAO,uBAAA,CAAwB,GAAA,EAAK,MAAA,EAAQ,OAAA,IAAW,EAAE,CAAA;AAAA,EAC3D;AAEA,EAAA,MAAM;AAAA,IACJ,QAAQ,aAAA,GAAgB,CAAA;AAAA,IACxB,IAAA,GAAO,KAAA;AAAA,IACP,SAAA;AAAA,IACA,OAAA;AAAA,IACA,cAAc,WAAA,GAAc,CAAA;AAAA,IAC5B,UAAU,EAAC;AAAA,IACX,cAAc,GAAA,CAAI,WAAA;AAAA,IAClB,kBAAA,GAAqB;AAAA,GACvB,GAAI,WAAW,EAAC;AAEhB,EAAA,MAAM,UAAU,aAAA,EAAgC;AAChD,EAAA,MAAM,WAAW,MAAA,CAAO,QAAA;AAGxB,EAAA,IAAI,KAAA,GAAuB,SAAA;AAC3B,EAAA,IAAI,UAAA,GAA2C,IAAA;AAC/C,EAAA,IAAI,SAAA,GAAY,CAAA;AAChB,EAAA,IAAI,QAAA,GAAW,aAAA;AACf,EAAA,IAAI,WAAA,GAAc,WAAA;AAClB,EAAA,IAAI,SAAA,GAAY,IAAA;AAChB,EAAA,IAAI,OAAA,GAAiD,IAAA;AACrD,EAAA,IAAI,QAAA,GAAW,KAAA;AAIf,EAAA,SAAS,YAAA,GAAsC;AAC7C,IAAA,MAAM,GAAA,GAAM,IAAI,kBAAA,EAAmB;AACnC,IAAA,GAAA,CAAI,MAAA,GAAS,MAAA;AACb,IAAA,GAAA,CAAI,aAAa,KAAA,GAAQ,WAAA;AACzB,IAAA,GAAA,CAAI,IAAA,GAAO,SAAA;AACX,IAAA,IAAI,SAAA,KAAc,MAAA,EAAW,GAAA,CAAI,SAAA,GAAY,SAAA;AAC7C,IAAA,IAAI,OAAA,KAAY,MAAA,EAAW,GAAA,CAAI,OAAA,GAAU,OAAA;AAGzC,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,MAAA,GAAA,CAAI,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAE,CAAA;AACvB,MAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,MAAA,GAAS,GAAG,CAAA,EAAA,EAAK;AAC3C,QAAA,OAAA,CAAQ,CAAC,CAAA,CAAG,OAAA,CAAQ,OAAA,CAAQ,CAAA,GAAI,CAAC,CAAE,CAAA;AAAA,MACrC;AACA,MAAA,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA,CAAG,QAAQ,WAAW,CAAA;AAAA,IAClD,CAAA,MAAO;AACL,MAAA,GAAA,CAAI,QAAQ,WAAW,CAAA;AAAA,IACzB;AAEA,IAAA,GAAA,CAAI,OAAA,GAAU,WAAA;AACd,IAAA,OAAO,GAAA;AAAA,EACT;AAEA,EAAA,SAAS,YAAY,gBAAA,EAA0B;AAC7C,IAAA,UAAA,GAAa,YAAA,EAAa;AAC1B,IAAA,UAAA,CAAW,KAAA,CAAM,GAAG,gBAAgB,CAAA;AACpC,IAAA,SAAA,GAAY,GAAA,CAAI,cAAc,gBAAA,GAAmB,WAAA;AAAA,EACnD;AAEA,EAAA,SAAS,UAAA,GAAa;AACpB,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AACrB,MAAA,IAAI;AACF,QAAA,UAAA,CAAW,IAAA,EAAK;AAAA,MAClB,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,UAAA,CAAW,UAAA,EAAW;AACtB,MAAA,UAAA,GAAa,IAAA;AAAA,IACf;AAAA,EACF;AAEA,EAAA,SAAS,WAAA,GAAc;AAErB,IAAA,IAAI,UAAU,SAAA,EAAW;AACzB,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,OAAA,CAAQ,IAAA,CAAK,QAAQ,MAAkB,CAAA;AACvC,MAAA;AAAA,IACF;AACA,IAAA,QAAA,CAAS,SAAS,CAAA;AAClB,IAAA,QAAA,GAAW,CAAA;AACX,IAAA,SAAA,EAAU;AACV,IAAA,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAkB,CAAA;AAAA,EAC1C;AAEA,EAAA,SAAS,SAAS,IAAA,EAAqB;AACrC,IAAA,IAAI,UAAU,IAAA,EAAM;AACpB,IAAA,KAAA,GAAQ,IAAA;AACR,IAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,EAAE,KAAA,EAAO,MAAM,CAAA;AAAA,EAC7C;AAEA,EAAA,SAAS,UAAA,GAAa;AACpB,IAAA,IAAI,YAAY,IAAA,EAAM;AACtB,IAAA,OAAA,GAAU,YAAY,MAAM;AAC1B,MAAA,IAAI,UAAU,SAAA,EAAW;AACzB,MAAA,OAAA,CAAQ,KAAK,YAAA,EAAc;AAAA,QACzB,UAAU,cAAA,EAAe;AAAA,QACzB;AAAA,OACD,CAAA;AAAA,IACH,GAAG,kBAAkB,CAAA;AAAA,EACvB;AAEA,EAAA,SAAS,SAAA,GAAY;AACnB,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,aAAA,CAAc,OAAO,CAAA;AACrB,MAAA,OAAA,GAAU,IAAA;AAAA,IACZ;AAAA,EACF;AAIA,EAAA,SAAS,cAAA,GAAyB;AAChC,IAAA,IAAI,UAAU,SAAA,EAAW;AACvB,MAAA,MAAM,OAAA,GAAA,CAAW,GAAA,CAAI,WAAA,GAAc,SAAA,IAAa,WAAA;AAChD,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,MAAM,OAAA,GAAA,CACH,OAAA,IAAW,QAAA,KAAa,SAAA,IAAa,CAAA,CAAA;AACxC,QAAA,OAAA,CAAS,OAAA,IAAW,SAAA,IAAa,CAAA,CAAA,IAAM,OAAA,IAAY,SAAA,IAAa,CAAA,CAAA;AAAA,MAClE;AACA,MAAA,OAAO,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,QAAQ,CAAA;AAAA,IACnC;AACA,IAAA,IAAI,KAAA,KAAU,UAAU,OAAO,QAAA;AAC/B,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,SAAS,KAAA,GAAQ;AACf,IAAA,IAAI,KAAA,KAAU,aAAa,QAAA,EAAU;AACrC,IAAA,QAAA,GAAW,cAAA,EAAe;AAC1B,IAAA,UAAA,EAAW;AACX,IAAA,SAAA,EAAU;AACV,IAAA,QAAA,CAAS,QAAQ,CAAA;AACjB,IAAA,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAkB,CAAA;AAAA,EAC1C;AAEA,EAAA,SAAS,MAAA,GAAS;AAChB,IAAA,IAAI,KAAA,KAAU,YAAY,QAAA,EAAU;AACpC,IAAA,WAAA,CAAY,QAAQ,CAAA;AACpB,IAAA,QAAA,CAAS,SAAS,CAAA;AAClB,IAAA,UAAA,EAAW;AACX,IAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,MAAkB,CAAA;AAAA,EAC3C;AAEA,EAAA,SAAS,eAAA,GAAkB;AACzB,IAAA,IAAI,KAAA,KAAU,WAAW,KAAA,EAAM;AAAA,SAAA,IACtB,KAAA,KAAU,UAAU,MAAA,EAAO;AAAA,EACtC;AAEA,EAAA,SAAS,KAAK,QAAA,EAAkB;AAC9B,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,MAAM,OAAA,GAAU,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,QAAA,EAAU,QAAQ,CAAC,CAAA;AACxD,IAAA,MAAM,aAAa,KAAA,KAAU,SAAA;AAE7B,IAAA,UAAA,EAAW;AACX,IAAA,SAAA,EAAU;AAEV,IAAA,QAAA,GAAW,OAAA;AAEX,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,WAAA,CAAY,OAAO,CAAA;AACnB,MAAA,UAAA,EAAW;AAAA,IACb;AAEA,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,EAAE,QAAA,EAAU,SAAS,CAAA;AAAA,EAC5C;AAEA,EAAA,SAAS,IAAA,GAAO;AACd,IAAA,IAAI,KAAA,KAAU,aAAa,QAAA,EAAU;AACrC,IAAA,UAAA,EAAW;AACX,IAAA,SAAA,EAAU;AACV,IAAA,QAAA,GAAW,CAAA;AACX,IAAA,QAAA,CAAS,SAAS,CAAA;AAClB,IAAA,OAAA,CAAQ,IAAA,CAAK,QAAQ,MAAkB,CAAA;AAAA,EACzC;AAEA,EAAA,SAAS,gBAAgB,IAAA,EAAc;AACrC,IAAA,MAAM,WAAW,cAAA,EAAe;AAChC,IAAA,WAAA,GAAc,IAAA;AACd,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,UAAA,CAAW,aAAa,KAAA,GAAQ,IAAA;AAChC,MAAA,SAAA,GAAY,GAAA,CAAI,cAAc,QAAA,GAAW,IAAA;AAAA,IAC3C;AAAA,EACF;AAEA,EAAA,SAAS,QAAQ,KAAA,EAAgB;AAC/B,IAAA,SAAA,GAAY,KAAA;AACZ,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,UAAA,CAAW,IAAA,GAAO,KAAA;AAAA,IACpB;AAAA,EACF;AAEA,EAAA,SAAS,OAAA,GAAU;AACjB,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,QAAA,GAAW,IAAA;AACX,IAAA,UAAA,EAAW;AACX,IAAA,SAAA,EAAU;AACV,IAAA,OAAA,CAAQ,KAAA,EAAM;AAAA,EAChB;AAIA,EAAA,WAAA,CAAY,aAAa,CAAA;AACzB,EAAA,QAAA,CAAS,SAAS,CAAA;AAClB,EAAA,UAAA,EAAW;AACX,EAAA,OAAA,CAAQ,IAAA,CAAK,QAAQ,MAAkB,CAAA;AAEvC,EAAA,OAAO;AAAA,IACL,UAAU,MAAM,KAAA;AAAA,IAChB,cAAA;AAAA,IACA,aAAa,MAAM,QAAA;AAAA,IACnB,aAAa,MAAO,QAAA,GAAW,CAAA,GAAI,cAAA,KAAmB,QAAA,GAAW,CAAA;AAAA,IACjE,KAAA;AAAA,IACA,MAAA;AAAA,IACA,eAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA;AAAA,IACA,eAAA;AAAA,IACA,OAAA;AAAA,IACA,EAAA,EAAI,OAAA,CAAQ,EAAA,CAAG,IAAA,CAAK,OAAO,CAAA;AAAA,IAC3B,GAAA,EAAK,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA;AAAA,IAC7B;AAAA,GACF;AACF;AAMA,SAAS,uBAAA,CACP,GAAA,EACA,MAAA,EACA,OAAA,EACU;AACV,EAAA,MAAM;AAAA,IACJ,QAAQ,aAAA,GAAgB,CAAA;AAAA,IACxB,cAAc,WAAA,GAAc,CAAA;AAAA,IAC5B,UAAU,EAAC;AAAA,IACX,cAAc,GAAA,CAAI,WAAA;AAAA,IAClB,kBAAA,GAAqB;AAAA,GACvB,GAAI,OAAA;AAEJ,EAAA,MAAM,UAAU,aAAA,EAAgC;AAChD,EAAA,MAAM,WAAW,MAAA,CAAO,QAAA;AAExB,EAAA,IAAI,KAAA,GAAuB,SAAA;AAC3B,EAAA,IAAI,cAAA,GACF,IAAA;AACF,EAAA,IAAI,OAAA,GAAiD,IAAA;AACrD,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,IAAI,WAAA,GAAc,WAAA;AAClB,EAAA,IAAI,WAAA,GAA6B,IAAA;AAGjC,EAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,EAAE,KAAA,EAAO,WAAW,CAAA;AAChD,EAAA,OAAA,CAAQ,IAAA,CAAK,QAAQ,MAAkB,CAAA;AAGvC,EAAA,OAAO,sBAAuB,CAAA,CAAE,IAAA,CAAK,CAAC,EAAE,uBAAsB,KAAM;AAClE,IAAA,IAAI,QAAA,EAAU;AAEd,IAAA,cAAA,GAAiB,qBAAA,CAAsB,KAAK,MAAA,EAAQ;AAAA,MAClD,KAAA,EAAO,WAAA;AAAA,MACP,MAAA,EAAQ,aAAA;AAAA,MACR,OAAA;AAAA,MACA,WAAA;AAAA,MACA;AAAA,KACD,CAAA;AAGD,IAAA,cAAA,CAAe,EAAA,CAAG,WAAA,EAAa,CAAC,IAAA,KAAS;AACvC,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,OAAA,CAAQ,IAAA,CAAK,aAAa,IAAI,CAAA;AAAA,IAChC,CAAC,CAAA;AAED,IAAA,cAAA,CAAe,EAAA,CAAG,UAAA,EAAY,CAAC,IAAA,KAAS;AACtC,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,OAAA,CAAQ,IAAA,CAAK,YAAY,IAAI,CAAA;AAAA,IAC/B,CAAC,CAAA;AAED,IAAA,cAAA,CAAe,EAAA,CAAG,SAAS,MAAM;AAC/B,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,KAAA,GAAQ,SAAA;AACR,MAAA,SAAA,EAAU;AACV,MAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,EAAE,KAAA,EAAO,WAAW,CAAA;AAChD,MAAA,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAkB,CAAA;AAAA,IAC1C,CAAC,CAAA;AAED,IAAA,cAAA,CAAe,EAAA,CAAG,OAAA,EAAS,CAAC,IAAA,KAAS;AACnC,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,IAAI,KAAK,KAAA,EAAO;AACd,QAAA,KAAA,GAAQ,SAAA;AACR,QAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,EAAE,KAAA,EAAO,WAAW,CAAA;AAChD,QAAA,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAkB,CAAA;AAAA,MAC1C;AAAA,IACF,CAAC,CAAA;AAGD,IAAA,cAAA,CAAe,KAAA,EAAM;AACrB,IAAA,UAAA,EAAW;AAGX,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,cAAA,CAAe,KAAK,WAAW,CAAA;AAC/B,MAAA,WAAA,GAAc,IAAA;AAAA,IAChB;AAGA,IAAA,IAAI,UAAU,QAAA,EAAU;AACtB,MAAA,cAAA,CAAe,KAAA,EAAM;AAAA,IACvB,CAAA,MAAA,IAAW,UAAU,SAAA,EAAW;AAC9B,MAAA,cAAA,CAAe,IAAA,EAAK;AAAA,IACtB;AAAA,EACF,CAAC,CAAA;AAED,EAAA,SAAS,UAAA,GAAa;AACpB,IAAA,IAAI,YAAY,IAAA,EAAM;AACtB,IAAA,OAAA,GAAU,YAAY,MAAM;AAC1B,MAAA,IAAI,KAAA,KAAU,aAAa,QAAA,EAAU;AACrC,MAAA,OAAA,CAAQ,KAAK,YAAA,EAAc;AAAA,QACzB,UAAU,cAAA,EAAe;AAAA,QACzB;AAAA,OACD,CAAA;AAAA,IACH,GAAG,kBAAkB,CAAA;AAAA,EACvB;AAEA,EAAA,SAAS,SAAA,GAAY;AACnB,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,aAAA,CAAc,OAAO,CAAA;AACrB,MAAA,OAAA,GAAU,IAAA;AAAA,IACZ;AAAA,EACF;AAEA,EAAA,SAAS,cAAA,GAAyB;AAChC,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,OAAO,WAAA;AAAA,IACT;AACA,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,OAAO,eAAe,kBAAA,EAAmB;AAAA,IAC3C;AACA,IAAA,OAAO,aAAA;AAAA,EACT;AAEA,EAAA,SAAS,KAAA,GAAQ;AACf,IAAA,IAAI,KAAA,KAAU,aAAa,QAAA,EAAU;AACrC,IAAA,KAAA,GAAQ,QAAA;AACR,IAAA,cAAA,EAAgB,KAAA,EAAM;AACtB,IAAA,SAAA,EAAU;AACV,IAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,EAAE,KAAA,EAAO,UAAU,CAAA;AAC/C,IAAA,OAAA,CAAQ,IAAA,CAAK,SAAS,MAAkB,CAAA;AAAA,EAC1C;AAEA,EAAA,SAAS,MAAA,GAAS;AAChB,IAAA,IAAI,KAAA,KAAU,YAAY,QAAA,EAAU;AACpC,IAAA,KAAA,GAAQ,SAAA;AACR,IAAA,cAAA,EAAgB,MAAA,EAAO;AACvB,IAAA,UAAA,EAAW;AACX,IAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,EAAE,KAAA,EAAO,WAAW,CAAA;AAChD,IAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,MAAkB,CAAA;AAAA,EAC3C;AAEA,EAAA,SAAS,eAAA,GAAkB;AACzB,IAAA,IAAI,KAAA,KAAU,WAAW,KAAA,EAAM;AAAA,SAAA,IACtB,KAAA,KAAU,UAAU,MAAA,EAAO;AAAA,EACtC;AAEA,EAAA,SAAS,KAAK,QAAA,EAAkB;AAC9B,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,MAAM,OAAA,GAAU,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,QAAA,EAAU,QAAQ,CAAC,CAAA;AACxD,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,cAAA,CAAe,KAAK,OAAO,CAAA;AAAA,IAC7B,CAAA,MAAO;AACL,MAAA,WAAA,GAAc,OAAA;AAAA,IAChB;AACA,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,EAAE,QAAA,EAAU,SAAS,CAAA;AAAA,EAC5C;AAEA,EAAA,SAAS,IAAA,GAAO;AACd,IAAA,IAAI,KAAA,KAAU,aAAa,QAAA,EAAU;AACrC,IAAA,KAAA,GAAQ,SAAA;AACR,IAAA,cAAA,EAAgB,IAAA,EAAK;AACrB,IAAA,SAAA,EAAU;AACV,IAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,EAAe,EAAE,KAAA,EAAO,WAAW,CAAA;AAChD,IAAA,OAAA,CAAQ,IAAA,CAAK,QAAQ,MAAkB,CAAA;AAAA,EACzC;AAEA,EAAA,SAAS,gBAAgB,IAAA,EAAc;AACrC,IAAA,WAAA,GAAc,IAAA;AACd,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,cAAA,CAAe,SAAS,IAAI,CAAA;AAAA,IAC9B;AAAA,EACF;AAEA,EAAA,SAAS,QAAQ,MAAA,EAAiB;AAAA,EAElC;AAEA,EAAA,SAAS,OAAA,GAAU;AACjB,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,QAAA,GAAW,IAAA;AACX,IAAA,SAAA,EAAU;AACV,IAAA,cAAA,EAAgB,OAAA,EAAQ;AACxB,IAAA,OAAA,CAAQ,KAAA,EAAM;AAAA,EAChB;AAEA,EAAA,SAAS,qBAAA,GAA2D;AAClE,IAAA,IAAI,CAAC,gBAAgB,OAAO,IAAA;AAC5B,IAAA,OAAO,eAAe,WAAA,EAAY;AAAA,EACpC;AAEA,EAAA,MAAM,QAAA,GAEF;AAAA,IACF,UAAU,MAAM,KAAA;AAAA,IAChB,cAAA;AAAA,IACA,aAAa,MAAM,QAAA;AAAA,IACnB,aAAa,MAAO,QAAA,GAAW,CAAA,GAAI,cAAA,KAAmB,QAAA,GAAW,CAAA;AAAA,IACjE,KAAA;AAAA,IACA,MAAA;AAAA,IACA,eAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA;AAAA,IACA,eAAA;AAAA,IACA,OAAA;AAAA,IACA,EAAA,EAAI,OAAA,CAAQ,EAAA,CAAG,IAAA,CAAK,OAAO,CAAA;AAAA,IAC3B,GAAA,EAAK,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA;AAAA,IAC7B,OAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAO,QAAA;AACT","file":"chunk-RWJ4EWJT.js","sourcesContent":["// ---------------------------------------------------------------------------\n// M3: Playback engine\n// ---------------------------------------------------------------------------\n\nimport { createEmitter } from \"./emitter.js\";\nimport type {\n Playback,\n PlaybackEventMap,\n PlaybackState,\n PlayOptions,\n StretcherSnapshotExtension,\n} from \"./types.js\";\n\n/**\n * Play an `AudioBuffer` through an `AudioContext` and return a controllable\n * `Playback` handle.\n *\n * ```ts\n * const pb = play(ctx, buffer, { loop: true });\n * pb.on(\"timeupdate\", ({ position }) => console.log(position));\n * pb.pause();\n * pb.resume();\n * pb.seek(10);\n * pb.stop();\n * pb.dispose();\n * ```\n */\nexport function play(\n ctx: AudioContext,\n buffer: AudioBuffer,\n options?: PlayOptions,\n): Playback {\n const { preservePitch = true } = options ?? {};\n\n // ----- Pitch-preserving mode (WSOLA-based time-stretch) -----\n if (preservePitch) {\n return createStretchedPlayback(ctx, buffer, options ?? {});\n }\n\n const {\n offset: initialOffset = 0,\n loop = false,\n loopStart,\n loopEnd,\n playbackRate: initialRate = 1,\n through = [],\n destination = ctx.destination,\n timeupdateInterval = 50,\n } = options ?? {};\n\n const emitter = createEmitter<PlaybackEventMap>();\n const duration = buffer.duration;\n\n // ----- mutable internal state -----\n let state: PlaybackState = \"stopped\";\n let sourceNode: AudioBufferSourceNode | null = null;\n let startedAt = 0; // ctx.currentTime when playback last started/resumed\n let pausedAt = initialOffset; // position in the buffer (seconds)\n let currentRate = initialRate;\n let isLooping = loop;\n let timerId: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n\n // ----- helpers -----\n\n function createSource(): AudioBufferSourceNode {\n const src = ctx.createBufferSource();\n src.buffer = buffer;\n src.playbackRate.value = currentRate;\n src.loop = isLooping;\n if (loopStart !== undefined) src.loopStart = loopStart;\n if (loopEnd !== undefined) src.loopEnd = loopEnd;\n\n // Connect through the node chain (or directly to destination).\n if (through.length > 0) {\n src.connect(through[0]!);\n for (let i = 0; i < through.length - 1; i++) {\n through[i]!.connect(through[i + 1]!);\n }\n through[through.length - 1]!.connect(destination);\n } else {\n src.connect(destination);\n }\n\n src.onended = handleEnded;\n return src;\n }\n\n function startSource(positionInBuffer: number) {\n sourceNode = createSource();\n sourceNode.start(0, positionInBuffer);\n startedAt = ctx.currentTime - positionInBuffer / currentRate;\n }\n\n function stopSource() {\n if (sourceNode) {\n sourceNode.onended = null;\n try {\n sourceNode.stop();\n } catch {\n // Already stopped — safe to ignore.\n }\n sourceNode.disconnect();\n sourceNode = null;\n }\n }\n\n function handleEnded() {\n // If we manually stopped / paused, the handler was already removed.\n if (state !== \"playing\") return;\n if (isLooping) {\n emitter.emit(\"loop\", undefined as never);\n return;\n }\n setState(\"stopped\");\n pausedAt = 0;\n stopTimer();\n emitter.emit(\"ended\", undefined as never);\n }\n\n function setState(next: PlaybackState) {\n if (state === next) return;\n state = next;\n emitter.emit(\"statechange\", { state: next });\n }\n\n function startTimer() {\n if (timerId !== null) return;\n timerId = setInterval(() => {\n if (state !== \"playing\") return;\n emitter.emit(\"timeupdate\", {\n position: getCurrentTime(),\n duration,\n });\n }, timeupdateInterval);\n }\n\n function stopTimer() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n }\n\n // ----- public API -----\n\n function getCurrentTime(): number {\n if (state === \"playing\") {\n const elapsed = (ctx.currentTime - startedAt) * currentRate;\n if (isLooping) {\n const loopDur =\n (loopEnd ?? duration) - (loopStart ?? 0);\n return ((elapsed - (loopStart ?? 0)) % loopDur) + (loopStart ?? 0);\n }\n return Math.min(elapsed, duration);\n }\n if (state === \"paused\") return pausedAt;\n return 0;\n }\n\n function pause() {\n if (state !== \"playing\" || disposed) return;\n pausedAt = getCurrentTime();\n stopSource();\n stopTimer();\n setState(\"paused\");\n emitter.emit(\"pause\", undefined as never);\n }\n\n function resume() {\n if (state !== \"paused\" || disposed) return;\n startSource(pausedAt);\n setState(\"playing\");\n startTimer();\n emitter.emit(\"resume\", undefined as never);\n }\n\n function togglePlayPause() {\n if (state === \"playing\") pause();\n else if (state === \"paused\") resume();\n }\n\n function seek(position: number) {\n if (disposed) return;\n const clamped = Math.max(0, Math.min(position, duration));\n const wasPlaying = state === \"playing\";\n\n stopSource();\n stopTimer();\n\n pausedAt = clamped;\n\n if (wasPlaying) {\n startSource(clamped);\n startTimer();\n }\n\n emitter.emit(\"seek\", { position: clamped });\n }\n\n function stop() {\n if (state === \"stopped\" || disposed) return;\n stopSource();\n stopTimer();\n pausedAt = 0;\n setState(\"stopped\");\n emitter.emit(\"stop\", undefined as never);\n }\n\n function setPlaybackRate(rate: number) {\n const position = getCurrentTime();\n currentRate = rate;\n if (sourceNode) {\n sourceNode.playbackRate.value = rate;\n startedAt = ctx.currentTime - position / rate;\n }\n }\n\n function setLoop(value: boolean) {\n isLooping = value;\n if (sourceNode) {\n sourceNode.loop = value;\n }\n }\n\n function dispose() {\n if (disposed) return;\n disposed = true;\n stopSource();\n stopTimer();\n emitter.clear();\n }\n\n // ----- kick off initial playback -----\n\n startSource(initialOffset);\n setState(\"playing\");\n startTimer();\n emitter.emit(\"play\", undefined as never);\n\n return {\n getState: () => state,\n getCurrentTime,\n getDuration: () => duration,\n getProgress: () => (duration > 0 ? getCurrentTime() / duration : 0),\n pause,\n resume,\n togglePlayPause,\n seek,\n stop,\n setPlaybackRate,\n setLoop,\n on: emitter.on.bind(emitter) as Playback[\"on\"],\n off: emitter.off.bind(emitter) as Playback[\"off\"],\n dispose,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Stretched playback (preservePitch: true)\n// ---------------------------------------------------------------------------\n\nfunction createStretchedPlayback(\n ctx: AudioContext,\n buffer: AudioBuffer,\n options: PlayOptions,\n): Playback {\n const {\n offset: initialOffset = 0,\n playbackRate: initialRate = 1,\n through = [],\n destination = ctx.destination,\n timeupdateInterval = 50,\n } = options;\n\n const emitter = createEmitter<PlaybackEventMap>();\n const duration = buffer.duration;\n\n let state: PlaybackState = \"playing\";\n let engineInstance: import(\"./stretcher/types.js\").StretcherEngine | null =\n null;\n let timerId: ReturnType<typeof setInterval> | null = null;\n let disposed = false;\n let currentRate = initialRate;\n let pendingSeek: number | null = null;\n\n // Emit initial play event\n emitter.emit(\"statechange\", { state: \"playing\" });\n emitter.emit(\"play\", undefined as never);\n\n // Fire-and-forget dynamic import of the stretcher engine\n import(\"./stretcher/engine.js\").then(({ createStretcherEngine }) => {\n if (disposed) return;\n\n engineInstance = createStretcherEngine(ctx, buffer, {\n tempo: currentRate,\n offset: initialOffset,\n through,\n destination,\n timeupdateInterval,\n });\n\n // Wire stretcher events to playback events\n engineInstance.on(\"buffering\", (data) => {\n if (disposed) return;\n emitter.emit(\"buffering\", data);\n });\n\n engineInstance.on(\"buffered\", (data) => {\n if (disposed) return;\n emitter.emit(\"buffered\", data);\n });\n\n engineInstance.on(\"ended\", () => {\n if (disposed) return;\n state = \"stopped\";\n stopTimer();\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"ended\", undefined as never);\n });\n\n engineInstance.on(\"error\", (data) => {\n if (disposed) return;\n if (data.fatal) {\n state = \"stopped\";\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"ended\", undefined as never);\n }\n });\n\n // Start engine and timeupdate timer\n engineInstance.start();\n startTimer();\n\n // Apply pending seek if any\n if (pendingSeek !== null) {\n engineInstance.seek(pendingSeek);\n pendingSeek = null;\n }\n\n // If we were paused before the engine loaded, pause it\n if (state === \"paused\") {\n engineInstance.pause();\n } else if (state === \"stopped\") {\n engineInstance.stop();\n }\n });\n\n function startTimer() {\n if (timerId !== null) return;\n timerId = setInterval(() => {\n if (state !== \"playing\" || disposed) return;\n emitter.emit(\"timeupdate\", {\n position: getCurrentTime(),\n duration,\n });\n }, timeupdateInterval);\n }\n\n function stopTimer() {\n if (timerId !== null) {\n clearInterval(timerId);\n timerId = null;\n }\n }\n\n function getCurrentTime(): number {\n if (pendingSeek !== null) {\n return pendingSeek;\n }\n if (engineInstance) {\n return engineInstance.getCurrentPosition();\n }\n return initialOffset;\n }\n\n function pause() {\n if (state !== \"playing\" || disposed) return;\n state = \"paused\";\n engineInstance?.pause();\n stopTimer();\n emitter.emit(\"statechange\", { state: \"paused\" });\n emitter.emit(\"pause\", undefined as never);\n }\n\n function resume() {\n if (state !== \"paused\" || disposed) return;\n state = \"playing\";\n engineInstance?.resume();\n startTimer();\n emitter.emit(\"statechange\", { state: \"playing\" });\n emitter.emit(\"resume\", undefined as never);\n }\n\n function togglePlayPause() {\n if (state === \"playing\") pause();\n else if (state === \"paused\") resume();\n }\n\n function seek(position: number) {\n if (disposed) return;\n const clamped = Math.max(0, Math.min(position, duration));\n if (engineInstance) {\n engineInstance.seek(clamped);\n } else {\n pendingSeek = clamped;\n }\n emitter.emit(\"seek\", { position: clamped });\n }\n\n function stop() {\n if (state === \"stopped\" || disposed) return;\n state = \"stopped\";\n engineInstance?.stop();\n stopTimer();\n emitter.emit(\"statechange\", { state: \"stopped\" });\n emitter.emit(\"stop\", undefined as never);\n }\n\n function setPlaybackRate(rate: number) {\n currentRate = rate;\n if (engineInstance) {\n engineInstance.setTempo(rate);\n }\n }\n\n function setLoop(_value: boolean) {\n // Loop is not supported in stretcher mode\n }\n\n function dispose() {\n if (disposed) return;\n disposed = true;\n stopTimer();\n engineInstance?.dispose();\n emitter.clear();\n }\n\n function _getStretcherSnapshot(): StretcherSnapshotExtension | null {\n if (!engineInstance) return null;\n return engineInstance.getSnapshot();\n }\n\n const playback: Playback & {\n _getStretcherSnapshot: typeof _getStretcherSnapshot;\n } = {\n getState: () => state,\n getCurrentTime,\n getDuration: () => duration,\n getProgress: () => (duration > 0 ? getCurrentTime() / duration : 0),\n pause,\n resume,\n togglePlayPause,\n seek,\n stop,\n setPlaybackRate,\n setLoop,\n on: emitter.on.bind(emitter) as Playback[\"on\"],\n off: emitter.off.bind(emitter) as Playback[\"off\"],\n dispose,\n _getStretcherSnapshot,\n };\n\n return playback;\n}\n"]}
|