@effing/annie-player 0.34.0 → 0.35.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/dist/{chunk-AFGBXFMF.js → chunk-XXS5ARJX.js} +40 -18
- package/dist/chunk-XXS5ARJX.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1 -1
- package/dist/react/index.d.ts +20 -0
- package/dist/react/index.js +129 -14
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AFGBXFMF.js.map +0 -1
|
@@ -48,6 +48,7 @@ var AnniePlayerCore = class {
|
|
|
48
48
|
isLoading: this._isLoading,
|
|
49
49
|
isPlaying: this._isPlaying,
|
|
50
50
|
frameCount: this.frames.length,
|
|
51
|
+
currentFrame: this.currentFrameIndex,
|
|
51
52
|
dimensions: this._dimensions
|
|
52
53
|
};
|
|
53
54
|
}
|
|
@@ -155,18 +156,18 @@ var AnniePlayerCore = class {
|
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
/**
|
|
158
|
-
* Start playing the animation.
|
|
159
|
+
* Start playing the animation from the current frame.
|
|
159
160
|
*/
|
|
160
161
|
play() {
|
|
161
162
|
if (this._isPlaying || this.frames.length === 0) return;
|
|
162
163
|
this._isPlaying = true;
|
|
163
|
-
this.currentFrameIndex = 0;
|
|
164
164
|
this.lastFrameTime = 0;
|
|
165
|
+
this.drawFrame(this.currentFrameIndex);
|
|
165
166
|
this.setStatus("Playing...");
|
|
166
|
-
this.
|
|
167
|
+
this.animationFrameId = requestAnimationFrame(this.animate);
|
|
167
168
|
}
|
|
168
169
|
/**
|
|
169
|
-
* Pause the animation.
|
|
170
|
+
* Pause the animation at the current frame.
|
|
170
171
|
*/
|
|
171
172
|
pause() {
|
|
172
173
|
if (!this._isPlaying) return;
|
|
@@ -178,7 +179,7 @@ var AnniePlayerCore = class {
|
|
|
178
179
|
this.setStatus("Paused.");
|
|
179
180
|
}
|
|
180
181
|
/**
|
|
181
|
-
* Stop the animation and reset to the
|
|
182
|
+
* Stop the animation and reset to the first frame.
|
|
182
183
|
*/
|
|
183
184
|
stop() {
|
|
184
185
|
this._isPlaying = false;
|
|
@@ -189,6 +190,37 @@ var AnniePlayerCore = class {
|
|
|
189
190
|
this.currentFrameIndex = 0;
|
|
190
191
|
this.lastFrameTime = 0;
|
|
191
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Seek to a specific frame. The frame is drawn immediately. Playback state
|
|
195
|
+
* is preserved — if playing, playback continues forward from the new frame.
|
|
196
|
+
*/
|
|
197
|
+
seek(frameIndex) {
|
|
198
|
+
if (this.frames.length === 0) return;
|
|
199
|
+
const clamped = Math.max(
|
|
200
|
+
0,
|
|
201
|
+
Math.min(Math.floor(frameIndex), this.frames.length - 1)
|
|
202
|
+
);
|
|
203
|
+
this.lastFrameTime = 0;
|
|
204
|
+
this.drawFrame(clamped);
|
|
205
|
+
}
|
|
206
|
+
drawFrame(index) {
|
|
207
|
+
const indexChanged = index !== this.currentFrameIndex;
|
|
208
|
+
if (!this.canvas || !this.context) {
|
|
209
|
+
if (indexChanged) {
|
|
210
|
+
this.currentFrameIndex = index;
|
|
211
|
+
this.emitStateChange();
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const frame = this.frames[index];
|
|
216
|
+
if (!frame) return;
|
|
217
|
+
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
218
|
+
this.context.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);
|
|
219
|
+
if (indexChanged) {
|
|
220
|
+
this.currentFrameIndex = index;
|
|
221
|
+
this.emitStateChange();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
192
224
|
animate = (timestamp) => {
|
|
193
225
|
if (!this._isPlaying || this.frames.length === 0) {
|
|
194
226
|
this.animationFrameId = null;
|
|
@@ -204,18 +236,8 @@ var AnniePlayerCore = class {
|
|
|
204
236
|
const elapsed = timestamp - this.lastFrameTime;
|
|
205
237
|
if (elapsed >= this.frameInterval) {
|
|
206
238
|
this.lastFrameTime = timestamp - elapsed % this.frameInterval;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (frameToDraw) {
|
|
210
|
-
this.context.drawImage(
|
|
211
|
-
frameToDraw,
|
|
212
|
-
0,
|
|
213
|
-
0,
|
|
214
|
-
this.canvas.width,
|
|
215
|
-
this.canvas.height
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
this.currentFrameIndex = (this.currentFrameIndex + 1) % this.frames.length;
|
|
239
|
+
const nextIndex = (this.currentFrameIndex + 1) % this.frames.length;
|
|
240
|
+
this.drawFrame(nextIndex);
|
|
219
241
|
}
|
|
220
242
|
this.animationFrameId = requestAnimationFrame(this.animate);
|
|
221
243
|
};
|
|
@@ -244,4 +266,4 @@ var AnniePlayerCore = class {
|
|
|
244
266
|
export {
|
|
245
267
|
AnniePlayerCore
|
|
246
268
|
};
|
|
247
|
-
//# sourceMappingURL=chunk-
|
|
269
|
+
//# sourceMappingURL=chunk-XXS5ARJX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core.ts"],"sourcesContent":["import { untar } from \"@andrewbranch/untar.js\";\n\nexport type AnniePlayerState = {\n status: string;\n error: string | null;\n isLoading: boolean;\n isPlaying: boolean;\n frameCount: number;\n currentFrame: number;\n dimensions: { width: number; height: number } | null;\n};\n\nexport type AnniePlayerEvents = {\n statechange: AnniePlayerState;\n load: { frameCount: number; dimensions: { width: number; height: number } };\n error: Error;\n};\n\nexport type AnniePlayerOptions = {\n src: string;\n fps?: number;\n autoPlay?: boolean;\n};\n\ntype EventCallback<T> = (data: T) => void;\n\n/**\n * Framework-agnostic Annie animation player.\n * Handles loading TAR archives of image frames and playing them on a canvas.\n */\nexport class AnniePlayerCore {\n private src: string;\n private fps: number;\n private autoPlay: boolean;\n private frameInterval: number;\n\n private frames: HTMLImageElement[] = [];\n private canvas: HTMLCanvasElement | null = null;\n private context: CanvasRenderingContext2D | null = null;\n\n private animationFrameId: number | null = null;\n private currentFrameIndex = 0;\n private lastFrameTime = 0;\n\n private _isLoading = false;\n private _isPlaying = false;\n private _error: string | null = null;\n private _status = \"Ready to load animation.\";\n private _dimensions: { width: number; height: number } | null = null;\n\n private listeners: Map<\n keyof AnniePlayerEvents,\n Set<EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>>\n > = new Map();\n\n constructor(options: AnniePlayerOptions) {\n this.src = options.src;\n this.fps = options.fps ?? 30;\n this.autoPlay = options.autoPlay ?? false;\n this.frameInterval = 1000 / this.fps;\n }\n\n /**\n * Attach a canvas element to render the animation on.\n */\n attachCanvas(canvas: HTMLCanvasElement): void {\n this.canvas = canvas;\n this.context = canvas.getContext(\"2d\");\n }\n\n /**\n * Detach the canvas and stop playback.\n */\n detachCanvas(): void {\n this.stop();\n this.canvas = null;\n this.context = null;\n }\n\n /**\n * Get the current state of the player.\n */\n getState(): AnniePlayerState {\n return {\n status: this._status,\n error: this._error,\n isLoading: this._isLoading,\n isPlaying: this._isPlaying,\n frameCount: this.frames.length,\n currentFrame: this.currentFrameIndex,\n dimensions: this._dimensions,\n };\n }\n\n /**\n * Subscribe to player events.\n */\n on<K extends keyof AnniePlayerEvents>(\n event: K,\n callback: EventCallback<AnniePlayerEvents[K]>,\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const callbacks = this.listeners.get(event)!;\n callbacks.add(\n callback as EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>,\n );\n\n return () => {\n callbacks.delete(\n callback as EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>,\n );\n };\n }\n\n private emit<K extends keyof AnniePlayerEvents>(\n event: K,\n data: AnniePlayerEvents[K],\n ): void {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.forEach((cb) => cb(data));\n }\n }\n\n private emitStateChange(): void {\n this.emit(\"statechange\", this.getState());\n }\n\n private setStatus(status: string): void {\n this._status = status;\n this.emitStateChange();\n }\n\n private setError(error: string | null): void {\n this._error = error;\n if (error) {\n this.emit(\"error\", new Error(error));\n }\n this.emitStateChange();\n }\n\n /**\n * Load the animation from the source URL.\n */\n async load(): Promise<void> {\n if (this._isLoading) return;\n\n this.stop();\n this._isLoading = true;\n this._error = null;\n this.setStatus(\"Loading Annie file...\");\n this.cleanup();\n\n const imageFiles: { name: string; image: HTMLImageElement }[] = [];\n const imageLoadPromises: Promise<void>[] = [];\n\n try {\n const response = await fetch(this.src);\n if (!response.ok) {\n throw new Error(`Failed to fetch animation: ${response.statusText}`);\n }\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n\n this.setStatus(\"Extracting frames...\");\n\n untar(await response.arrayBuffer()).forEach((file) => {\n if (!file.name || file.typeflag !== \"0\") return;\n if (!file.fileData) return;\n\n const blob = new Blob([new Uint8Array(file.fileData)]);\n\n const promise = new Promise<void>((resolve, reject) => {\n const img = new Image();\n img.onload = () => {\n imageFiles.push({ name: file.name!, image: img });\n if (imageFiles.length === 1) {\n this._dimensions = {\n width: img.naturalWidth,\n height: img.naturalHeight,\n };\n }\n resolve();\n };\n img.onerror = (err) => {\n reject(new Error(`Could not load image: ${file.name} (${err})`));\n };\n img.src = URL.createObjectURL(blob);\n });\n imageLoadPromises.push(promise);\n });\n\n await Promise.all(imageLoadPromises);\n\n if (imageFiles.length === 0) {\n throw new Error(\"No frames found in the TAR file.\");\n }\n\n imageFiles.sort((a, b) => a.name.localeCompare(b.name));\n this.frames = imageFiles.map((f) => f.image);\n this._isLoading = false;\n this.setStatus(`Loaded ${this.frames.length} frames. Ready to play.`);\n\n this.emit(\"load\", {\n frameCount: this.frames.length,\n dimensions: this._dimensions!,\n });\n\n if (this.autoPlay && this.frames.length > 0) {\n this.play();\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Unknown error\";\n this._isLoading = false;\n this.setError(errorMessage);\n this.setStatus(`Error: ${errorMessage}`);\n }\n }\n\n /**\n * Start playing the animation from the current frame.\n */\n play(): void {\n if (this._isPlaying || this.frames.length === 0) return;\n\n this._isPlaying = true;\n this.lastFrameTime = 0;\n this.drawFrame(this.currentFrameIndex);\n this.setStatus(\"Playing...\");\n this.animationFrameId = requestAnimationFrame(this.animate);\n }\n\n /**\n * Pause the animation at the current frame.\n */\n pause(): void {\n if (!this._isPlaying) return;\n\n this._isPlaying = false;\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n this.setStatus(\"Paused.\");\n }\n\n /**\n * Stop the animation and reset to the first frame.\n */\n stop(): void {\n this._isPlaying = false;\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n this.currentFrameIndex = 0;\n this.lastFrameTime = 0;\n }\n\n /**\n * Seek to a specific frame. The frame is drawn immediately. Playback state\n * is preserved — if playing, playback continues forward from the new frame.\n */\n seek(frameIndex: number): void {\n if (this.frames.length === 0) return;\n const clamped = Math.max(\n 0,\n Math.min(Math.floor(frameIndex), this.frames.length - 1),\n );\n this.lastFrameTime = 0;\n this.drawFrame(clamped);\n }\n\n private drawFrame(index: number): void {\n const indexChanged = index !== this.currentFrameIndex;\n if (!this.canvas || !this.context) {\n if (indexChanged) {\n this.currentFrameIndex = index;\n this.emitStateChange();\n }\n return;\n }\n const frame = this.frames[index];\n if (!frame) return;\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n if (indexChanged) {\n this.currentFrameIndex = index;\n this.emitStateChange();\n }\n }\n\n private animate = (timestamp: number): void => {\n if (!this._isPlaying || this.frames.length === 0) {\n this.animationFrameId = null;\n return;\n }\n\n if (!this.canvas || !this.context) {\n this.animationFrameId = null;\n return;\n }\n\n if (this.lastFrameTime === 0) {\n this.lastFrameTime = timestamp;\n }\n\n const elapsed = timestamp - this.lastFrameTime;\n\n if (elapsed >= this.frameInterval) {\n this.lastFrameTime = timestamp - (elapsed % this.frameInterval);\n const nextIndex = (this.currentFrameIndex + 1) % this.frames.length;\n this.drawFrame(nextIndex);\n }\n\n this.animationFrameId = requestAnimationFrame(this.animate);\n };\n\n /**\n * Clean up resources (revoke blob URLs).\n */\n cleanup(): void {\n this.frames.forEach((img) => {\n if (img.src.startsWith(\"blob:\")) {\n URL.revokeObjectURL(img.src);\n }\n });\n this.frames = [];\n }\n\n /**\n * Destroy the player and clean up all resources.\n */\n destroy(): void {\n this.stop();\n this.cleanup();\n this.detachCanvas();\n this.listeners.clear();\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ type AnniePlayerState = {
|
|
|
4
4
|
isLoading: boolean;
|
|
5
5
|
isPlaying: boolean;
|
|
6
6
|
frameCount: number;
|
|
7
|
+
currentFrame: number;
|
|
7
8
|
dimensions: {
|
|
8
9
|
width: number;
|
|
9
10
|
height: number;
|
|
@@ -73,17 +74,23 @@ declare class AnniePlayerCore {
|
|
|
73
74
|
*/
|
|
74
75
|
load(): Promise<void>;
|
|
75
76
|
/**
|
|
76
|
-
* Start playing the animation.
|
|
77
|
+
* Start playing the animation from the current frame.
|
|
77
78
|
*/
|
|
78
79
|
play(): void;
|
|
79
80
|
/**
|
|
80
|
-
* Pause the animation.
|
|
81
|
+
* Pause the animation at the current frame.
|
|
81
82
|
*/
|
|
82
83
|
pause(): void;
|
|
83
84
|
/**
|
|
84
|
-
* Stop the animation and reset to the
|
|
85
|
+
* Stop the animation and reset to the first frame.
|
|
85
86
|
*/
|
|
86
87
|
stop(): void;
|
|
88
|
+
/**
|
|
89
|
+
* Seek to a specific frame. The frame is drawn immediately. Playback state
|
|
90
|
+
* is preserved — if playing, playback continues forward from the new frame.
|
|
91
|
+
*/
|
|
92
|
+
seek(frameIndex: number): void;
|
|
93
|
+
private drawFrame;
|
|
87
94
|
private animate;
|
|
88
95
|
/**
|
|
89
96
|
* Clean up resources (revoke blob URLs).
|
package/dist/index.js
CHANGED
package/dist/react/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ type AnniePlayerContextValue = {
|
|
|
7
7
|
load: () => void;
|
|
8
8
|
play: () => void;
|
|
9
9
|
pause: () => void;
|
|
10
|
+
seek: (frameIndex: number) => void;
|
|
10
11
|
};
|
|
11
12
|
canvasRef: React.RefObject<HTMLCanvasElement | null>;
|
|
12
13
|
canvasDimensions: {
|
|
@@ -66,6 +67,23 @@ type AnniePlayerPauseButtonProps = {
|
|
|
66
67
|
style?: React.CSSProperties;
|
|
67
68
|
};
|
|
68
69
|
declare function AnniePlayerPauseButton({ children, className, style, }: AnniePlayerPauseButtonProps): react_jsx_runtime.JSX.Element;
|
|
70
|
+
type AnniePlayerScrubberProps = {
|
|
71
|
+
className?: string;
|
|
72
|
+
style?: React.CSSProperties;
|
|
73
|
+
/** If true, pause playback while the user is interacting with the scrubber. */
|
|
74
|
+
pauseWhileScrubbing?: boolean;
|
|
75
|
+
};
|
|
76
|
+
declare function AnniePlayerScrubber({ className, style, pauseWhileScrubbing, }: AnniePlayerScrubberProps): react_jsx_runtime.JSX.Element;
|
|
77
|
+
type AnniePlayerFrameCounterProps = {
|
|
78
|
+
className?: string;
|
|
79
|
+
style?: React.CSSProperties;
|
|
80
|
+
/** Render the counter contents. Defaults to "current / total". */
|
|
81
|
+
children?: (frame: {
|
|
82
|
+
current: number;
|
|
83
|
+
total: number;
|
|
84
|
+
}) => React.ReactNode;
|
|
85
|
+
};
|
|
86
|
+
declare function AnniePlayerFrameCounter({ className, style, children, }: AnniePlayerFrameCounterProps): react_jsx_runtime.JSX.Element;
|
|
69
87
|
type AnniePlayerStatusProps = {
|
|
70
88
|
children?: React.ReactNode;
|
|
71
89
|
className?: string;
|
|
@@ -101,6 +119,8 @@ declare const AnniePlayer: typeof AnniePlayerSimple & {
|
|
|
101
119
|
LoadButton: typeof AnniePlayerLoadButton;
|
|
102
120
|
PlayButton: typeof AnniePlayerPlayButton;
|
|
103
121
|
PauseButton: typeof AnniePlayerPauseButton;
|
|
122
|
+
Scrubber: typeof AnniePlayerScrubber;
|
|
123
|
+
FrameCounter: typeof AnniePlayerFrameCounter;
|
|
104
124
|
Status: typeof AnniePlayerStatus;
|
|
105
125
|
};
|
|
106
126
|
|
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AnniePlayerCore
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-XXS5ARJX.js";
|
|
4
4
|
|
|
5
5
|
// src/react/index.tsx
|
|
6
6
|
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
|
@@ -34,6 +34,7 @@ function AnniePlayerRoot({
|
|
|
34
34
|
isLoading: false,
|
|
35
35
|
isPlaying: false,
|
|
36
36
|
frameCount: 0,
|
|
37
|
+
currentFrame: 0,
|
|
37
38
|
dimensions: null
|
|
38
39
|
});
|
|
39
40
|
const [canvasDimensions, setCanvasDimensions] = useState({
|
|
@@ -74,7 +75,8 @@ function AnniePlayerRoot({
|
|
|
74
75
|
const actions = {
|
|
75
76
|
load: () => playerRef.current?.load(),
|
|
76
77
|
play: () => playerRef.current?.play(),
|
|
77
|
-
pause: () => playerRef.current?.pause()
|
|
78
|
+
pause: () => playerRef.current?.pause(),
|
|
79
|
+
seek: (frameIndex) => playerRef.current?.seek(frameIndex)
|
|
78
80
|
};
|
|
79
81
|
const value = {
|
|
80
82
|
state,
|
|
@@ -210,6 +212,63 @@ function AnniePlayerPauseButton({
|
|
|
210
212
|
}
|
|
211
213
|
);
|
|
212
214
|
}
|
|
215
|
+
function AnniePlayerScrubber({
|
|
216
|
+
className,
|
|
217
|
+
style,
|
|
218
|
+
pauseWhileScrubbing = false
|
|
219
|
+
}) {
|
|
220
|
+
const { state, actions } = useAnniePlayerContext();
|
|
221
|
+
const wasPlayingRef = useRef(false);
|
|
222
|
+
const disabled = state.frameCount === 0;
|
|
223
|
+
const max = Math.max(0, state.frameCount - 1);
|
|
224
|
+
const handlePointerDown = () => {
|
|
225
|
+
if (pauseWhileScrubbing && state.isPlaying) {
|
|
226
|
+
wasPlayingRef.current = true;
|
|
227
|
+
actions.pause();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const handlePointerUp = () => {
|
|
231
|
+
if (pauseWhileScrubbing && wasPlayingRef.current) {
|
|
232
|
+
wasPlayingRef.current = false;
|
|
233
|
+
actions.play();
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
return /* @__PURE__ */ jsx(
|
|
237
|
+
"input",
|
|
238
|
+
{
|
|
239
|
+
type: "range",
|
|
240
|
+
min: 0,
|
|
241
|
+
max,
|
|
242
|
+
step: 1,
|
|
243
|
+
value: state.currentFrame,
|
|
244
|
+
disabled,
|
|
245
|
+
"data-disabled": disabled,
|
|
246
|
+
onChange: (e) => actions.seek(Number(e.target.value)),
|
|
247
|
+
onPointerDown: handlePointerDown,
|
|
248
|
+
onPointerUp: handlePointerUp,
|
|
249
|
+
onPointerCancel: handlePointerUp,
|
|
250
|
+
className,
|
|
251
|
+
style: { width: "100%", ...style }
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
function AnniePlayerFrameCounter({
|
|
256
|
+
className,
|
|
257
|
+
style,
|
|
258
|
+
children
|
|
259
|
+
}) {
|
|
260
|
+
const { state } = useAnniePlayerContext();
|
|
261
|
+
const current = state.currentFrame;
|
|
262
|
+
const total = Math.max(0, state.frameCount - 1);
|
|
263
|
+
return /* @__PURE__ */ jsx(
|
|
264
|
+
"span",
|
|
265
|
+
{
|
|
266
|
+
className,
|
|
267
|
+
style: { fontVariantNumeric: "tabular-nums", ...style },
|
|
268
|
+
children: children ? children({ current, total }) : `${current} / ${total}`
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
}
|
|
213
272
|
function AnniePlayerStatus({
|
|
214
273
|
children,
|
|
215
274
|
className,
|
|
@@ -268,8 +327,9 @@ function AnniePlayerSimple({
|
|
|
268
327
|
const loadDisabled = state.isLoading;
|
|
269
328
|
const playDisabled = state.isLoading || state.isPlaying || state.frameCount === 0;
|
|
270
329
|
const pauseDisabled = !state.isPlaying;
|
|
271
|
-
const
|
|
272
|
-
const
|
|
330
|
+
const scrubberDisabled = state.frameCount === 0;
|
|
331
|
+
const overlayVisible = !state.isPlaying || isHovering;
|
|
332
|
+
const totalFrames = Math.max(0, state.frameCount - 1);
|
|
273
333
|
const buttonStyle = (disabled) => ({
|
|
274
334
|
backgroundColor: "#222",
|
|
275
335
|
color: "white",
|
|
@@ -310,7 +370,7 @@ function AnniePlayerSimple({
|
|
|
310
370
|
gap: 4,
|
|
311
371
|
zIndex: 10,
|
|
312
372
|
transition: "opacity 0.3s ease",
|
|
313
|
-
opacity:
|
|
373
|
+
opacity: overlayVisible ? 1 : 0
|
|
314
374
|
},
|
|
315
375
|
children: [
|
|
316
376
|
/* @__PURE__ */ jsx(
|
|
@@ -352,26 +412,79 @@ function AnniePlayerSimple({
|
|
|
352
412
|
style: {
|
|
353
413
|
position: "absolute",
|
|
354
414
|
bottom: 16,
|
|
415
|
+
left: 16,
|
|
416
|
+
right: 16,
|
|
417
|
+
zIndex: 10,
|
|
418
|
+
display: "flex",
|
|
419
|
+
alignItems: "center",
|
|
420
|
+
gap: 12,
|
|
421
|
+
padding: "8px 12px",
|
|
422
|
+
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
423
|
+
color: "white",
|
|
424
|
+
borderRadius: 4,
|
|
425
|
+
fontSize: 13,
|
|
426
|
+
transition: "opacity 0.3s ease",
|
|
427
|
+
opacity: overlayVisible ? 1 : 0,
|
|
428
|
+
pointerEvents: overlayVisible ? "auto" : "none"
|
|
429
|
+
},
|
|
430
|
+
children: [
|
|
431
|
+
/* @__PURE__ */ jsx(
|
|
432
|
+
"input",
|
|
433
|
+
{
|
|
434
|
+
type: "range",
|
|
435
|
+
min: 0,
|
|
436
|
+
max: totalFrames,
|
|
437
|
+
step: 1,
|
|
438
|
+
value: state.currentFrame,
|
|
439
|
+
disabled: scrubberDisabled,
|
|
440
|
+
onChange: (e) => actions.seek(Number(e.target.value)),
|
|
441
|
+
style: {
|
|
442
|
+
flex: 1,
|
|
443
|
+
accentColor: "#fff",
|
|
444
|
+
cursor: scrubberDisabled ? "not-allowed" : "pointer"
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
),
|
|
448
|
+
/* @__PURE__ */ jsxs(
|
|
449
|
+
"span",
|
|
450
|
+
{
|
|
451
|
+
style: {
|
|
452
|
+
fontVariantNumeric: "tabular-nums",
|
|
453
|
+
whiteSpace: "nowrap",
|
|
454
|
+
minWidth: 64,
|
|
455
|
+
textAlign: "right"
|
|
456
|
+
},
|
|
457
|
+
children: [
|
|
458
|
+
state.currentFrame,
|
|
459
|
+
" / ",
|
|
460
|
+
totalFrames
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
)
|
|
464
|
+
]
|
|
465
|
+
}
|
|
466
|
+
),
|
|
467
|
+
state.error && /* @__PURE__ */ jsxs(
|
|
468
|
+
"div",
|
|
469
|
+
{
|
|
470
|
+
style: {
|
|
471
|
+
position: "absolute",
|
|
472
|
+
top: 64,
|
|
355
473
|
left: 0,
|
|
356
474
|
right: 0,
|
|
357
475
|
margin: "0 auto",
|
|
358
476
|
width: "fit-content",
|
|
359
477
|
maxWidth: "90%",
|
|
360
478
|
zIndex: 10,
|
|
361
|
-
backgroundColor: "rgba(
|
|
479
|
+
backgroundColor: "rgba(180, 0, 0, 0.85)",
|
|
362
480
|
color: "white",
|
|
363
481
|
padding: 8,
|
|
364
482
|
borderRadius: 4,
|
|
365
|
-
fontSize: 14
|
|
366
|
-
transition: "opacity 0.3s ease",
|
|
367
|
-
opacity: statusVisible ? 1 : 0
|
|
483
|
+
fontSize: 14
|
|
368
484
|
},
|
|
369
485
|
children: [
|
|
370
|
-
|
|
371
|
-
state.error
|
|
372
|
-
" Error: ",
|
|
373
|
-
state.error
|
|
374
|
-
] })
|
|
486
|
+
"Error: ",
|
|
487
|
+
state.error
|
|
375
488
|
]
|
|
376
489
|
}
|
|
377
490
|
),
|
|
@@ -403,6 +516,8 @@ var AnniePlayer = Object.assign(AnniePlayerSimple, {
|
|
|
403
516
|
LoadButton: AnniePlayerLoadButton,
|
|
404
517
|
PlayButton: AnniePlayerPlayButton,
|
|
405
518
|
PauseButton: AnniePlayerPauseButton,
|
|
519
|
+
Scrubber: AnniePlayerScrubber,
|
|
520
|
+
FrameCounter: AnniePlayerFrameCounter,
|
|
406
521
|
Status: AnniePlayerStatus
|
|
407
522
|
});
|
|
408
523
|
export {
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.tsx"],"sourcesContent":["import { createContext, useContext, useEffect, useRef, useState } from \"react\";\nimport { AnniePlayerCore, type AnniePlayerState } from \"../core\";\n\n// ============ Context ============\n\ntype AnniePlayerContextValue = {\n state: AnniePlayerState;\n actions: {\n load: () => void;\n play: () => void;\n pause: () => void;\n };\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n canvasDimensions: { width: number; height: number };\n isHovering: boolean;\n setIsHovering: (v: boolean) => void;\n};\n\nconst AnniePlayerContext = createContext<AnniePlayerContextValue | null>(null);\n\nfunction useAnniePlayerContext() {\n const ctx = useContext(AnniePlayerContext);\n if (!ctx) {\n throw new Error(\n \"AnniePlayer compound components must be used within AnniePlayer.Root\",\n );\n }\n return ctx;\n}\n\n// ============ Root Component ============\n\nexport type AnniePlayerRootProps = {\n src: string;\n height: number;\n defaultWidth?: number;\n autoLoad?: boolean;\n autoPlay?: boolean;\n fps?: number;\n /** Style for the root container */\n style?: React.CSSProperties;\n /** Class name for the root container */\n className?: string;\n /** Children can be React nodes or a render function for headless usage */\n children?:\n | React.ReactNode\n | ((value: AnniePlayerContextValue) => React.ReactNode);\n};\n\nfunction AnniePlayerRoot({\n src,\n height,\n defaultWidth,\n autoLoad = false,\n autoPlay = false,\n fps = 30,\n style,\n className,\n children,\n}: AnniePlayerRootProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const playerRef = useRef<AnniePlayerCore | null>(null);\n const [state, setState] = useState<AnniePlayerState>({\n status: \"Ready to load animation.\",\n error: null,\n isLoading: false,\n isPlaying: false,\n frameCount: 0,\n dimensions: null,\n });\n const [canvasDimensions, setCanvasDimensions] = useState({\n width: defaultWidth ?? height,\n height,\n });\n const [isHovering, setIsHovering] = useState(false);\n\n // Initialize the core player\n useEffect(() => {\n const player = new AnniePlayerCore({ src, fps, autoPlay });\n playerRef.current = player;\n\n const unsubscribeState = player.on(\"statechange\", (newState) => {\n setState(newState);\n });\n\n const unsubscribeLoad = player.on(\"load\", ({ dimensions }) => {\n setCanvasDimensions({\n width: (dimensions.width / dimensions.height) * height,\n height,\n });\n });\n\n return () => {\n unsubscribeState();\n unsubscribeLoad();\n player.destroy();\n };\n }, [src, fps, autoPlay, height]);\n\n // Attach canvas to player when ready\n useEffect(() => {\n const player = playerRef.current;\n const canvas = canvasRef.current;\n if (player && canvas) {\n player.attachCanvas(canvas);\n }\n }, [canvasDimensions]);\n\n // Auto-load if enabled\n useEffect(() => {\n if (autoLoad && playerRef.current) {\n playerRef.current.load();\n }\n }, [autoLoad]);\n\n const actions = {\n load: () => playerRef.current?.load(),\n play: () => playerRef.current?.play(),\n pause: () => playerRef.current?.pause(),\n };\n\n const value: AnniePlayerContextValue = {\n state,\n actions,\n canvasRef,\n canvasDimensions,\n isHovering,\n setIsHovering,\n };\n\n // Render function pattern for headless usage\n if (typeof children === \"function\") {\n return <>{children(value)}</>;\n }\n\n return (\n <AnniePlayerContext.Provider value={value}>\n <div\n style={{ display: \"inline-block\", width: \"fit-content\", ...style }}\n className={className}\n >\n {children}\n </div>\n </AnniePlayerContext.Provider>\n );\n}\n\n// ============ Wrapper Component ============\n\ntype AnniePlayerWrapperProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerWrapper({\n children,\n className,\n style,\n}: AnniePlayerWrapperProps) {\n const { isHovering, setIsHovering, state } = useAnniePlayerContext();\n return (\n <div\n style={{\n position: \"relative\",\n display: \"inline-block\",\n width: \"fit-content\",\n ...style,\n }}\n className={className}\n onMouseEnter={() => setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n data-hovering={isHovering}\n data-playing={state.isPlaying}\n >\n {children}\n </div>\n );\n}\n\n// ============ Canvas Component ============\n\ntype AnniePlayerCanvasProps = {\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerCanvas({ className, style }: AnniePlayerCanvasProps) {\n const { canvasRef, canvasDimensions } = useAnniePlayerContext();\n return (\n <canvas\n ref={canvasRef}\n width={canvasDimensions.width}\n height={canvasDimensions.height}\n className={className}\n style={{ display: \"block\", ...style }}\n />\n );\n}\n\n// ============ Controls Component ============\n\ntype AnniePlayerControlsProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerControls({\n children,\n className,\n style,\n}: AnniePlayerControlsProps) {\n const { state, isHovering } = useAnniePlayerContext();\n const visible = !state.isPlaying || isHovering;\n return (\n <div\n className={className}\n style={{\n transition: \"opacity 0.3s ease\",\n opacity: visible ? 1 : 0,\n ...style,\n }}\n >\n {children}\n </div>\n );\n}\n\n// ============ Button Components ============\n\ntype AnniePlayerLoadButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerLoadButton({\n children,\n className,\n style,\n}: AnniePlayerLoadButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = state.isLoading;\n return (\n <button\n onClick={actions.load}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? (state.isLoading ? \"Loading...\" : \"Load\")}\n </button>\n );\n}\n\ntype AnniePlayerPlayButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerPlayButton({\n children,\n className,\n style,\n}: AnniePlayerPlayButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = state.isLoading || state.isPlaying || state.frameCount === 0;\n return (\n <button\n onClick={actions.play}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? \"Play\"}\n </button>\n );\n}\n\ntype AnniePlayerPauseButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerPauseButton({\n children,\n className,\n style,\n}: AnniePlayerPauseButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = !state.isPlaying;\n return (\n <button\n onClick={actions.pause}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? \"Pause\"}\n </button>\n );\n}\n\n// ============ Status Component ============\n\ntype AnniePlayerStatusProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerStatus({\n children,\n className,\n style,\n}: AnniePlayerStatusProps) {\n const { state, isHovering } = useAnniePlayerContext();\n const visible = isHovering;\n return (\n <div\n className={className}\n style={{\n transition: \"opacity 0.3s ease\",\n opacity: visible ? 1 : 0,\n ...style,\n }}\n >\n {children ?? (\n <>\n {state.status}\n {state.error && <span> Error: {state.error}</span>}\n </>\n )}\n </div>\n );\n}\n\n// ============ Simple Component ============\n\nexport type AnniePlayerProps = {\n src: string;\n height: number;\n defaultWidth?: number;\n autoLoad?: boolean;\n autoPlay?: boolean;\n fps?: number;\n /** Style for the root container */\n style?: React.CSSProperties;\n /** Class name for the root container */\n className?: string;\n};\n\nfunction AnniePlayerSimple({\n src,\n height,\n defaultWidth,\n autoLoad = false,\n autoPlay = false,\n fps = 30,\n style,\n className,\n}: AnniePlayerProps) {\n return (\n <AnniePlayerRoot\n src={src}\n height={height}\n defaultWidth={defaultWidth}\n autoLoad={autoLoad}\n autoPlay={autoPlay}\n fps={fps}\n style={style}\n className={className}\n >\n {({\n state,\n actions,\n canvasRef,\n canvasDimensions,\n isHovering,\n setIsHovering,\n }) => {\n const loadDisabled = state.isLoading;\n const playDisabled =\n state.isLoading || state.isPlaying || state.frameCount === 0;\n const pauseDisabled = !state.isPlaying;\n const buttonContainerVisible = !state.isPlaying || isHovering;\n const statusVisible = isHovering;\n\n const buttonStyle = (disabled: boolean): React.CSSProperties => ({\n backgroundColor: \"#222\",\n color: \"white\",\n border: \"none\",\n borderRadius: 4,\n padding: \"0.5rem 1rem\",\n cursor: \"pointer\",\n margin: \"0 4px\",\n fontSize: \"14px\",\n fontWeight: 500,\n opacity: disabled ? 0.6 : 1,\n });\n\n return (\n <div\n style={{\n position: \"relative\",\n display: \"inline-block\",\n width: \"fit-content\",\n }}\n data-hovering={isHovering}\n data-playing={state.isPlaying}\n onMouseEnter={() => setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n >\n <div\n style={{\n position: \"absolute\",\n width: \"90%\",\n top: 16,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n display: \"flex\",\n flexWrap: \"wrap\",\n justifyContent: \"center\",\n gap: 4,\n zIndex: 10,\n transition: \"opacity 0.3s ease\",\n opacity: buttonContainerVisible ? 1 : 0,\n }}\n >\n <button\n onClick={actions.load}\n disabled={loadDisabled}\n data-disabled={loadDisabled}\n style={buttonStyle(loadDisabled)}\n >\n {state.isLoading ? \"Loading...\" : \"Load\"}\n </button>\n <button\n onClick={actions.play}\n disabled={playDisabled}\n data-disabled={playDisabled}\n style={buttonStyle(playDisabled)}\n >\n Play\n </button>\n <button\n onClick={actions.pause}\n disabled={pauseDisabled}\n data-disabled={pauseDisabled}\n style={buttonStyle(pauseDisabled)}\n >\n Pause\n </button>\n </div>\n\n <div\n style={{\n position: \"absolute\",\n bottom: 16,\n left: 0,\n right: 0,\n margin: \"0 auto\",\n width: \"fit-content\",\n maxWidth: \"90%\",\n zIndex: 10,\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n color: \"white\",\n padding: 8,\n borderRadius: 4,\n fontSize: 14,\n transition: \"opacity 0.3s ease\",\n opacity: statusVisible ? 1 : 0,\n }}\n >\n {state.status}\n {state.error && <span> Error: {state.error}</span>}\n </div>\n\n <canvas\n ref={canvasRef}\n width={canvasDimensions.width}\n height={canvasDimensions.height}\n style={{\n display: \"block\",\n border: \"1px solid #ccc\",\n backgroundColor: \"#f5f5f5\",\n }}\n />\n </div>\n );\n }}\n </AnniePlayerRoot>\n );\n}\n\n/**\n * AnniePlayer - Play TAR archives of PNG/JPEG frames in the browser.\n *\n * Usage:\n * - Simple: `<AnniePlayer src={url} height={540} />`\n * - Compound: `<AnniePlayer.Root src={url} height={540}>...</AnniePlayer.Root>`\n * - Headless: `<AnniePlayer.Root src={url} height={540}>{({ state, actions }) => ...}</AnniePlayer.Root>`\n */\nexport const AnniePlayer = Object.assign(AnniePlayerSimple, {\n Root: AnniePlayerRoot,\n Wrapper: AnniePlayerWrapper,\n Canvas: AnniePlayerCanvas,\n Controls: AnniePlayerControls,\n LoadButton: AnniePlayerLoadButton,\n PlayButton: AnniePlayerPlayButton,\n PauseButton: AnniePlayerPauseButton,\n Status: AnniePlayerStatus,\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.tsx"],"sourcesContent":["import { createContext, useContext, useEffect, useRef, useState } from \"react\";\nimport { AnniePlayerCore, type AnniePlayerState } from \"../core\";\n\n// ============ Context ============\n\ntype AnniePlayerContextValue = {\n state: AnniePlayerState;\n actions: {\n load: () => void;\n play: () => void;\n pause: () => void;\n seek: (frameIndex: number) => void;\n };\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n canvasDimensions: { width: number; height: number };\n isHovering: boolean;\n setIsHovering: (v: boolean) => void;\n};\n\nconst AnniePlayerContext = createContext<AnniePlayerContextValue | null>(null);\n\nfunction useAnniePlayerContext() {\n const ctx = useContext(AnniePlayerContext);\n if (!ctx) {\n throw new Error(\n \"AnniePlayer compound components must be used within AnniePlayer.Root\",\n );\n }\n return ctx;\n}\n\n// ============ Root Component ============\n\nexport type AnniePlayerRootProps = {\n src: string;\n height: number;\n defaultWidth?: number;\n autoLoad?: boolean;\n autoPlay?: boolean;\n fps?: number;\n /** Style for the root container */\n style?: React.CSSProperties;\n /** Class name for the root container */\n className?: string;\n /** Children can be React nodes or a render function for headless usage */\n children?:\n | React.ReactNode\n | ((value: AnniePlayerContextValue) => React.ReactNode);\n};\n\nfunction AnniePlayerRoot({\n src,\n height,\n defaultWidth,\n autoLoad = false,\n autoPlay = false,\n fps = 30,\n style,\n className,\n children,\n}: AnniePlayerRootProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const playerRef = useRef<AnniePlayerCore | null>(null);\n const [state, setState] = useState<AnniePlayerState>({\n status: \"Ready to load animation.\",\n error: null,\n isLoading: false,\n isPlaying: false,\n frameCount: 0,\n currentFrame: 0,\n dimensions: null,\n });\n const [canvasDimensions, setCanvasDimensions] = useState({\n width: defaultWidth ?? height,\n height,\n });\n const [isHovering, setIsHovering] = useState(false);\n\n // Initialize the core player\n useEffect(() => {\n const player = new AnniePlayerCore({ src, fps, autoPlay });\n playerRef.current = player;\n\n const unsubscribeState = player.on(\"statechange\", (newState) => {\n setState(newState);\n });\n\n const unsubscribeLoad = player.on(\"load\", ({ dimensions }) => {\n setCanvasDimensions({\n width: (dimensions.width / dimensions.height) * height,\n height,\n });\n });\n\n return () => {\n unsubscribeState();\n unsubscribeLoad();\n player.destroy();\n };\n }, [src, fps, autoPlay, height]);\n\n // Attach canvas to player when ready\n useEffect(() => {\n const player = playerRef.current;\n const canvas = canvasRef.current;\n if (player && canvas) {\n player.attachCanvas(canvas);\n }\n }, [canvasDimensions]);\n\n // Auto-load if enabled\n useEffect(() => {\n if (autoLoad && playerRef.current) {\n playerRef.current.load();\n }\n }, [autoLoad]);\n\n const actions = {\n load: () => playerRef.current?.load(),\n play: () => playerRef.current?.play(),\n pause: () => playerRef.current?.pause(),\n seek: (frameIndex: number) => playerRef.current?.seek(frameIndex),\n };\n\n const value: AnniePlayerContextValue = {\n state,\n actions,\n canvasRef,\n canvasDimensions,\n isHovering,\n setIsHovering,\n };\n\n // Render function pattern for headless usage\n if (typeof children === \"function\") {\n return <>{children(value)}</>;\n }\n\n return (\n <AnniePlayerContext.Provider value={value}>\n <div\n style={{ display: \"inline-block\", width: \"fit-content\", ...style }}\n className={className}\n >\n {children}\n </div>\n </AnniePlayerContext.Provider>\n );\n}\n\n// ============ Wrapper Component ============\n\ntype AnniePlayerWrapperProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerWrapper({\n children,\n className,\n style,\n}: AnniePlayerWrapperProps) {\n const { isHovering, setIsHovering, state } = useAnniePlayerContext();\n return (\n <div\n style={{\n position: \"relative\",\n display: \"inline-block\",\n width: \"fit-content\",\n ...style,\n }}\n className={className}\n onMouseEnter={() => setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n data-hovering={isHovering}\n data-playing={state.isPlaying}\n >\n {children}\n </div>\n );\n}\n\n// ============ Canvas Component ============\n\ntype AnniePlayerCanvasProps = {\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerCanvas({ className, style }: AnniePlayerCanvasProps) {\n const { canvasRef, canvasDimensions } = useAnniePlayerContext();\n return (\n <canvas\n ref={canvasRef}\n width={canvasDimensions.width}\n height={canvasDimensions.height}\n className={className}\n style={{ display: \"block\", ...style }}\n />\n );\n}\n\n// ============ Controls Component ============\n\ntype AnniePlayerControlsProps = {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerControls({\n children,\n className,\n style,\n}: AnniePlayerControlsProps) {\n const { state, isHovering } = useAnniePlayerContext();\n const visible = !state.isPlaying || isHovering;\n return (\n <div\n className={className}\n style={{\n transition: \"opacity 0.3s ease\",\n opacity: visible ? 1 : 0,\n ...style,\n }}\n >\n {children}\n </div>\n );\n}\n\n// ============ Button Components ============\n\ntype AnniePlayerLoadButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerLoadButton({\n children,\n className,\n style,\n}: AnniePlayerLoadButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = state.isLoading;\n return (\n <button\n onClick={actions.load}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? (state.isLoading ? \"Loading...\" : \"Load\")}\n </button>\n );\n}\n\ntype AnniePlayerPlayButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerPlayButton({\n children,\n className,\n style,\n}: AnniePlayerPlayButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = state.isLoading || state.isPlaying || state.frameCount === 0;\n return (\n <button\n onClick={actions.play}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? \"Play\"}\n </button>\n );\n}\n\ntype AnniePlayerPauseButtonProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerPauseButton({\n children,\n className,\n style,\n}: AnniePlayerPauseButtonProps) {\n const { state, actions } = useAnniePlayerContext();\n const disabled = !state.isPlaying;\n return (\n <button\n onClick={actions.pause}\n disabled={disabled}\n data-disabled={disabled}\n className={className}\n style={style}\n >\n {children ?? \"Pause\"}\n </button>\n );\n}\n\n// ============ Scrubber Component ============\n\ntype AnniePlayerScrubberProps = {\n className?: string;\n style?: React.CSSProperties;\n /** If true, pause playback while the user is interacting with the scrubber. */\n pauseWhileScrubbing?: boolean;\n};\n\nfunction AnniePlayerScrubber({\n className,\n style,\n pauseWhileScrubbing = false,\n}: AnniePlayerScrubberProps) {\n const { state, actions } = useAnniePlayerContext();\n const wasPlayingRef = useRef(false);\n const disabled = state.frameCount === 0;\n const max = Math.max(0, state.frameCount - 1);\n\n const handlePointerDown = () => {\n if (pauseWhileScrubbing && state.isPlaying) {\n wasPlayingRef.current = true;\n actions.pause();\n }\n };\n\n const handlePointerUp = () => {\n if (pauseWhileScrubbing && wasPlayingRef.current) {\n wasPlayingRef.current = false;\n actions.play();\n }\n };\n\n return (\n <input\n type=\"range\"\n min={0}\n max={max}\n step={1}\n value={state.currentFrame}\n disabled={disabled}\n data-disabled={disabled}\n onChange={(e) => actions.seek(Number(e.target.value))}\n onPointerDown={handlePointerDown}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n className={className}\n style={{ width: \"100%\", ...style }}\n />\n );\n}\n\n// ============ Frame Counter Component ============\n\ntype AnniePlayerFrameCounterProps = {\n className?: string;\n style?: React.CSSProperties;\n /** Render the counter contents. Defaults to \"current / total\". */\n children?: (frame: { current: number; total: number }) => React.ReactNode;\n};\n\nfunction AnniePlayerFrameCounter({\n className,\n style,\n children,\n}: AnniePlayerFrameCounterProps) {\n const { state } = useAnniePlayerContext();\n const current = state.currentFrame;\n const total = Math.max(0, state.frameCount - 1);\n return (\n <span\n className={className}\n style={{ fontVariantNumeric: \"tabular-nums\", ...style }}\n >\n {children ? children({ current, total }) : `${current} / ${total}`}\n </span>\n );\n}\n\n// ============ Status Component ============\n\ntype AnniePlayerStatusProps = {\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n};\n\nfunction AnniePlayerStatus({\n children,\n className,\n style,\n}: AnniePlayerStatusProps) {\n const { state, isHovering } = useAnniePlayerContext();\n const visible = isHovering;\n return (\n <div\n className={className}\n style={{\n transition: \"opacity 0.3s ease\",\n opacity: visible ? 1 : 0,\n ...style,\n }}\n >\n {children ?? (\n <>\n {state.status}\n {state.error && <span> Error: {state.error}</span>}\n </>\n )}\n </div>\n );\n}\n\n// ============ Simple Component ============\n\nexport type AnniePlayerProps = {\n src: string;\n height: number;\n defaultWidth?: number;\n autoLoad?: boolean;\n autoPlay?: boolean;\n fps?: number;\n /** Style for the root container */\n style?: React.CSSProperties;\n /** Class name for the root container */\n className?: string;\n};\n\nfunction AnniePlayerSimple({\n src,\n height,\n defaultWidth,\n autoLoad = false,\n autoPlay = false,\n fps = 30,\n style,\n className,\n}: AnniePlayerProps) {\n return (\n <AnniePlayerRoot\n src={src}\n height={height}\n defaultWidth={defaultWidth}\n autoLoad={autoLoad}\n autoPlay={autoPlay}\n fps={fps}\n style={style}\n className={className}\n >\n {({\n state,\n actions,\n canvasRef,\n canvasDimensions,\n isHovering,\n setIsHovering,\n }) => {\n const loadDisabled = state.isLoading;\n const playDisabled =\n state.isLoading || state.isPlaying || state.frameCount === 0;\n const pauseDisabled = !state.isPlaying;\n const scrubberDisabled = state.frameCount === 0;\n const overlayVisible = !state.isPlaying || isHovering;\n const totalFrames = Math.max(0, state.frameCount - 1);\n\n const buttonStyle = (disabled: boolean): React.CSSProperties => ({\n backgroundColor: \"#222\",\n color: \"white\",\n border: \"none\",\n borderRadius: 4,\n padding: \"0.5rem 1rem\",\n cursor: \"pointer\",\n margin: \"0 4px\",\n fontSize: \"14px\",\n fontWeight: 500,\n opacity: disabled ? 0.6 : 1,\n });\n\n return (\n <div\n style={{\n position: \"relative\",\n display: \"inline-block\",\n width: \"fit-content\",\n }}\n data-hovering={isHovering}\n data-playing={state.isPlaying}\n onMouseEnter={() => setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n >\n <div\n style={{\n position: \"absolute\",\n width: \"90%\",\n top: 16,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n display: \"flex\",\n flexWrap: \"wrap\",\n justifyContent: \"center\",\n gap: 4,\n zIndex: 10,\n transition: \"opacity 0.3s ease\",\n opacity: overlayVisible ? 1 : 0,\n }}\n >\n <button\n onClick={actions.load}\n disabled={loadDisabled}\n data-disabled={loadDisabled}\n style={buttonStyle(loadDisabled)}\n >\n {state.isLoading ? \"Loading...\" : \"Load\"}\n </button>\n <button\n onClick={actions.play}\n disabled={playDisabled}\n data-disabled={playDisabled}\n style={buttonStyle(playDisabled)}\n >\n Play\n </button>\n <button\n onClick={actions.pause}\n disabled={pauseDisabled}\n data-disabled={pauseDisabled}\n style={buttonStyle(pauseDisabled)}\n >\n Pause\n </button>\n </div>\n\n <div\n style={{\n position: \"absolute\",\n bottom: 16,\n left: 16,\n right: 16,\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n gap: 12,\n padding: \"8px 12px\",\n backgroundColor: \"rgba(0, 0, 0, 0.7)\",\n color: \"white\",\n borderRadius: 4,\n fontSize: 13,\n transition: \"opacity 0.3s ease\",\n opacity: overlayVisible ? 1 : 0,\n pointerEvents: overlayVisible ? \"auto\" : \"none\",\n }}\n >\n <input\n type=\"range\"\n min={0}\n max={totalFrames}\n step={1}\n value={state.currentFrame}\n disabled={scrubberDisabled}\n onChange={(e) => actions.seek(Number(e.target.value))}\n style={{\n flex: 1,\n accentColor: \"#fff\",\n cursor: scrubberDisabled ? \"not-allowed\" : \"pointer\",\n }}\n />\n <span\n style={{\n fontVariantNumeric: \"tabular-nums\",\n whiteSpace: \"nowrap\",\n minWidth: 64,\n textAlign: \"right\",\n }}\n >\n {state.currentFrame} / {totalFrames}\n </span>\n </div>\n\n {state.error && (\n <div\n style={{\n position: \"absolute\",\n top: 64,\n left: 0,\n right: 0,\n margin: \"0 auto\",\n width: \"fit-content\",\n maxWidth: \"90%\",\n zIndex: 10,\n backgroundColor: \"rgba(180, 0, 0, 0.85)\",\n color: \"white\",\n padding: 8,\n borderRadius: 4,\n fontSize: 14,\n }}\n >\n Error: {state.error}\n </div>\n )}\n\n <canvas\n ref={canvasRef}\n width={canvasDimensions.width}\n height={canvasDimensions.height}\n style={{\n display: \"block\",\n border: \"1px solid #ccc\",\n backgroundColor: \"#f5f5f5\",\n }}\n />\n </div>\n );\n }}\n </AnniePlayerRoot>\n );\n}\n\n/**\n * AnniePlayer - Play TAR archives of PNG/JPEG frames in the browser.\n *\n * Usage:\n * - Simple: `<AnniePlayer src={url} height={540} />`\n * - Compound: `<AnniePlayer.Root src={url} height={540}>...</AnniePlayer.Root>`\n * - Headless: `<AnniePlayer.Root src={url} height={540}>{({ state, actions }) => ...}</AnniePlayer.Root>`\n */\nexport const AnniePlayer = Object.assign(AnniePlayerSimple, {\n Root: AnniePlayerRoot,\n Wrapper: AnniePlayerWrapper,\n Canvas: AnniePlayerCanvas,\n Controls: AnniePlayerControls,\n LoadButton: AnniePlayerLoadButton,\n PlayButton: AnniePlayerPlayButton,\n PauseButton: AnniePlayerPauseButton,\n Scrubber: AnniePlayerScrubber,\n FrameCounter: AnniePlayerFrameCounter,\n Status: AnniePlayerStatus,\n});\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core.ts"],"sourcesContent":["import { untar } from \"@andrewbranch/untar.js\";\n\nexport type AnniePlayerState = {\n status: string;\n error: string | null;\n isLoading: boolean;\n isPlaying: boolean;\n frameCount: number;\n dimensions: { width: number; height: number } | null;\n};\n\nexport type AnniePlayerEvents = {\n statechange: AnniePlayerState;\n load: { frameCount: number; dimensions: { width: number; height: number } };\n error: Error;\n};\n\nexport type AnniePlayerOptions = {\n src: string;\n fps?: number;\n autoPlay?: boolean;\n};\n\ntype EventCallback<T> = (data: T) => void;\n\n/**\n * Framework-agnostic Annie animation player.\n * Handles loading TAR archives of image frames and playing them on a canvas.\n */\nexport class AnniePlayerCore {\n private src: string;\n private fps: number;\n private autoPlay: boolean;\n private frameInterval: number;\n\n private frames: HTMLImageElement[] = [];\n private canvas: HTMLCanvasElement | null = null;\n private context: CanvasRenderingContext2D | null = null;\n\n private animationFrameId: number | null = null;\n private currentFrameIndex = 0;\n private lastFrameTime = 0;\n\n private _isLoading = false;\n private _isPlaying = false;\n private _error: string | null = null;\n private _status = \"Ready to load animation.\";\n private _dimensions: { width: number; height: number } | null = null;\n\n private listeners: Map<\n keyof AnniePlayerEvents,\n Set<EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>>\n > = new Map();\n\n constructor(options: AnniePlayerOptions) {\n this.src = options.src;\n this.fps = options.fps ?? 30;\n this.autoPlay = options.autoPlay ?? false;\n this.frameInterval = 1000 / this.fps;\n }\n\n /**\n * Attach a canvas element to render the animation on.\n */\n attachCanvas(canvas: HTMLCanvasElement): void {\n this.canvas = canvas;\n this.context = canvas.getContext(\"2d\");\n }\n\n /**\n * Detach the canvas and stop playback.\n */\n detachCanvas(): void {\n this.stop();\n this.canvas = null;\n this.context = null;\n }\n\n /**\n * Get the current state of the player.\n */\n getState(): AnniePlayerState {\n return {\n status: this._status,\n error: this._error,\n isLoading: this._isLoading,\n isPlaying: this._isPlaying,\n frameCount: this.frames.length,\n dimensions: this._dimensions,\n };\n }\n\n /**\n * Subscribe to player events.\n */\n on<K extends keyof AnniePlayerEvents>(\n event: K,\n callback: EventCallback<AnniePlayerEvents[K]>,\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const callbacks = this.listeners.get(event)!;\n callbacks.add(\n callback as EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>,\n );\n\n return () => {\n callbacks.delete(\n callback as EventCallback<AnniePlayerEvents[keyof AnniePlayerEvents]>,\n );\n };\n }\n\n private emit<K extends keyof AnniePlayerEvents>(\n event: K,\n data: AnniePlayerEvents[K],\n ): void {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.forEach((cb) => cb(data));\n }\n }\n\n private emitStateChange(): void {\n this.emit(\"statechange\", this.getState());\n }\n\n private setStatus(status: string): void {\n this._status = status;\n this.emitStateChange();\n }\n\n private setError(error: string | null): void {\n this._error = error;\n if (error) {\n this.emit(\"error\", new Error(error));\n }\n this.emitStateChange();\n }\n\n /**\n * Load the animation from the source URL.\n */\n async load(): Promise<void> {\n if (this._isLoading) return;\n\n this.stop();\n this._isLoading = true;\n this._error = null;\n this.setStatus(\"Loading Annie file...\");\n this.cleanup();\n\n const imageFiles: { name: string; image: HTMLImageElement }[] = [];\n const imageLoadPromises: Promise<void>[] = [];\n\n try {\n const response = await fetch(this.src);\n if (!response.ok) {\n throw new Error(`Failed to fetch animation: ${response.statusText}`);\n }\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n\n this.setStatus(\"Extracting frames...\");\n\n untar(await response.arrayBuffer()).forEach((file) => {\n if (!file.name || file.typeflag !== \"0\") return;\n if (!file.fileData) return;\n\n const blob = new Blob([new Uint8Array(file.fileData)]);\n\n const promise = new Promise<void>((resolve, reject) => {\n const img = new Image();\n img.onload = () => {\n imageFiles.push({ name: file.name!, image: img });\n if (imageFiles.length === 1) {\n this._dimensions = {\n width: img.naturalWidth,\n height: img.naturalHeight,\n };\n }\n resolve();\n };\n img.onerror = (err) => {\n reject(new Error(`Could not load image: ${file.name} (${err})`));\n };\n img.src = URL.createObjectURL(blob);\n });\n imageLoadPromises.push(promise);\n });\n\n await Promise.all(imageLoadPromises);\n\n if (imageFiles.length === 0) {\n throw new Error(\"No frames found in the TAR file.\");\n }\n\n imageFiles.sort((a, b) => a.name.localeCompare(b.name));\n this.frames = imageFiles.map((f) => f.image);\n this._isLoading = false;\n this.setStatus(`Loaded ${this.frames.length} frames. Ready to play.`);\n\n this.emit(\"load\", {\n frameCount: this.frames.length,\n dimensions: this._dimensions!,\n });\n\n if (this.autoPlay && this.frames.length > 0) {\n this.play();\n }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Unknown error\";\n this._isLoading = false;\n this.setError(errorMessage);\n this.setStatus(`Error: ${errorMessage}`);\n }\n }\n\n /**\n * Start playing the animation.\n */\n play(): void {\n if (this._isPlaying || this.frames.length === 0) return;\n\n this._isPlaying = true;\n this.currentFrameIndex = 0;\n this.lastFrameTime = 0;\n this.setStatus(\"Playing...\");\n this.animate(performance.now());\n }\n\n /**\n * Pause the animation.\n */\n pause(): void {\n if (!this._isPlaying) return;\n\n this._isPlaying = false;\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n this.setStatus(\"Paused.\");\n }\n\n /**\n * Stop the animation and reset to the beginning.\n */\n stop(): void {\n this._isPlaying = false;\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n this.currentFrameIndex = 0;\n this.lastFrameTime = 0;\n }\n\n private animate = (timestamp: number): void => {\n if (!this._isPlaying || this.frames.length === 0) {\n this.animationFrameId = null;\n return;\n }\n\n if (!this.canvas || !this.context) {\n this.animationFrameId = null;\n return;\n }\n\n if (this.lastFrameTime === 0) {\n this.lastFrameTime = timestamp;\n }\n\n const elapsed = timestamp - this.lastFrameTime;\n\n if (elapsed >= this.frameInterval) {\n this.lastFrameTime = timestamp - (elapsed % this.frameInterval);\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n const frameToDraw = this.frames[this.currentFrameIndex];\n if (frameToDraw) {\n this.context.drawImage(\n frameToDraw,\n 0,\n 0,\n this.canvas.width,\n this.canvas.height,\n );\n }\n this.currentFrameIndex =\n (this.currentFrameIndex + 1) % this.frames.length;\n }\n\n this.animationFrameId = requestAnimationFrame(this.animate);\n };\n\n /**\n * Clean up resources (revoke blob URLs).\n */\n cleanup(): void {\n this.frames.forEach((img) => {\n if (img.src.startsWith(\"blob:\")) {\n URL.revokeObjectURL(img.src);\n }\n });\n this.frames = [];\n }\n\n /**\n * Destroy the player and clean up all resources.\n */\n destroy(): void {\n this.stop();\n this.cleanup();\n this.detachCanvas();\n this.listeners.clear();\n }\n}\n"],"mappings":"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","names":[]}
|