@twick/player-react 0.14.21 → 0.15.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/index.cjs ADDED
@@ -0,0 +1,729 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/internal.ts
24
+ var internal_exports = {};
25
+ var import_core, import_core2, stylesNew, TEMPLATE, ID, TwickPlayer;
26
+ var init_internal = __esm({
27
+ "src/internal.ts"() {
28
+ import_core = require("@twick/core");
29
+ import_core2 = require("@twick/core");
30
+ stylesNew = `
31
+ .overlay {
32
+ position: absolute;
33
+ left: 0;
34
+ right: 0;
35
+ top: 0;
36
+ bottom: 0;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ opacity: 0;
41
+ transition: opacity 0.1s;
42
+ z-index: 0;
43
+ }
44
+ .canvas {
45
+ width: 100%;
46
+ display: block;
47
+ opacity: 1;
48
+ transition: opacity 0.1s;
49
+ }
50
+ `;
51
+ TEMPLATE = `<style>${stylesNew}</style><div class="overlay"></div>`;
52
+ ID = "twick-player";
53
+ TwickPlayer = class extends HTMLElement {
54
+ static get observedAttributes() {
55
+ return [
56
+ "playing",
57
+ "variables",
58
+ "looping",
59
+ "fps",
60
+ "quality",
61
+ "width",
62
+ "height",
63
+ "volume"
64
+ ];
65
+ }
66
+ get fps() {
67
+ const attr = this.getAttribute("fps");
68
+ return attr ? parseFloat(attr) : this.defaultSettings?.fps ?? 60;
69
+ }
70
+ get quality() {
71
+ const attr = this.getAttribute("quality");
72
+ return attr ? parseFloat(attr) : this.defaultSettings?.resolutionScale ?? 1;
73
+ }
74
+ get width() {
75
+ const attr = this.getAttribute("width");
76
+ return attr ? parseFloat(attr) : this.defaultSettings?.size.width ?? 0;
77
+ }
78
+ get height() {
79
+ const attr = this.getAttribute("height");
80
+ return attr ? parseFloat(attr) : this.defaultSettings?.size.height ?? 0;
81
+ }
82
+ get variables() {
83
+ try {
84
+ const attr = this.getAttribute("variables");
85
+ return attr ? JSON.parse(attr) : {};
86
+ } catch {
87
+ this.project?.logger.warn(`Project variables could not be parsed.`);
88
+ return {};
89
+ }
90
+ }
91
+ root;
92
+ canvas;
93
+ overlay;
94
+ state = "initial" /* Initial */;
95
+ project = null;
96
+ player = null;
97
+ defaultSettings;
98
+ abortController = null;
99
+ playing = false;
100
+ stage = new import_core.Stage();
101
+ time = 0;
102
+ duration = 0;
103
+ // in frames
104
+ looping = true;
105
+ volume = 1;
106
+ volumeChangeRequested = true;
107
+ constructor() {
108
+ super();
109
+ this.root = this.attachShadow({ mode: "open" });
110
+ this.root.innerHTML = TEMPLATE;
111
+ this.overlay = this.root.querySelector(".overlay");
112
+ this.canvas = this.stage.finalBuffer;
113
+ this.canvas.classList.add("canvas");
114
+ this.root.prepend(this.canvas);
115
+ this.setState("initial" /* Initial */);
116
+ }
117
+ setProject(project) {
118
+ this.updateProject(project);
119
+ }
120
+ setState(state) {
121
+ this.state = state;
122
+ this.setPlaying(this.playing);
123
+ }
124
+ setPlaying(value) {
125
+ if (this.state === "ready" /* Ready */ && value) {
126
+ this.player?.togglePlayback(true);
127
+ this.playing = true;
128
+ } else {
129
+ this.player?.togglePlayback(false);
130
+ this.playing = false;
131
+ }
132
+ }
133
+ async updateProject(project) {
134
+ const playing = this.playing;
135
+ this.setState("initial" /* Initial */);
136
+ this.abortController?.abort();
137
+ this.abortController = new AbortController();
138
+ this.project = project;
139
+ console.log(project);
140
+ this.defaultSettings = (0, import_core.getFullPreviewSettings)(this.project);
141
+ const player = new import_core.Player(this.project);
142
+ player.setVariables(this.variables);
143
+ player.toggleLoop(this.looping);
144
+ this.player?.onRender.unsubscribe(this.render);
145
+ this.player?.onFrameChanged.unsubscribe(this.handleFrameChanged);
146
+ this.player?.togglePlayback(false);
147
+ this.player?.deactivate();
148
+ this.player = player;
149
+ this.updateSettings();
150
+ this.setState("ready" /* Ready */);
151
+ this.dispatchEvent(new CustomEvent("playerready", { detail: this.player }));
152
+ this.setPlaying(playing);
153
+ this.player.onRender.subscribe(this.render);
154
+ this.player.onFrameChanged.subscribe(this.handleFrameChanged);
155
+ }
156
+ attributeChangedCallback(name, _, newValue) {
157
+ switch (name) {
158
+ case "playing":
159
+ this.setPlaying(newValue === "true");
160
+ break;
161
+ case "variables":
162
+ this.player?.setVariables(this.variables);
163
+ this.player?.requestSeek(this.player.playback.frame);
164
+ this.player?.playback.reload();
165
+ break;
166
+ case "looping":
167
+ this.looping = newValue === "true";
168
+ this.player?.toggleLoop(newValue === "true");
169
+ break;
170
+ case "fps":
171
+ case "quality":
172
+ case "width":
173
+ case "height":
174
+ this.updateSettings();
175
+ break;
176
+ case "volume":
177
+ this.volume = newValue;
178
+ this.volumeChangeRequested = true;
179
+ }
180
+ }
181
+ /**
182
+ * Runs when the element is removed from the DOM.
183
+ */
184
+ disconnectedCallback() {
185
+ this.player?.deactivate();
186
+ this.player?.onRender.unsubscribe(this.render);
187
+ this.removeEventListener("seekto", this.handleSeekTo);
188
+ this.removeEventListener("volumechange", this.handleVolumeChange);
189
+ }
190
+ /**
191
+ * Runs when the element is added to the DOM.
192
+ */
193
+ connectedCallback() {
194
+ this.player?.activate();
195
+ this.player?.onRender.subscribe(this.render);
196
+ this.addEventListener("seekto", this.handleSeekTo);
197
+ this.addEventListener("volumechange", this.handleVolumeChange);
198
+ }
199
+ /**
200
+ * Triggered by the timeline.
201
+ */
202
+ handleSeekTo = (event) => {
203
+ if (!this.project) {
204
+ return;
205
+ }
206
+ const e = event;
207
+ this.time = e.detail;
208
+ this.player?.requestSeek(e.detail * this.player.playback.fps);
209
+ this.volumeChangeRequested = true;
210
+ };
211
+ handleVolumeChange = (event) => {
212
+ if (!this.project) {
213
+ return;
214
+ }
215
+ const e = event;
216
+ this.volume = e.detail;
217
+ this.player?.playback.currentScene.adjustVolume(this.volume);
218
+ };
219
+ /**
220
+ * Triggered by the player.
221
+ */
222
+ handleFrameChanged = (frame) => {
223
+ if (!this.project || !this.player) {
224
+ return;
225
+ }
226
+ this.time = frame / this.player.playback.fps;
227
+ if (this.volumeChangeRequested || frame === 0) {
228
+ this.player?.playback.currentScene.adjustVolume(this.volume);
229
+ this.volumeChangeRequested = false;
230
+ }
231
+ };
232
+ /**
233
+ * Called on every frame.
234
+ */
235
+ render = async () => {
236
+ if (this.player && this.project) {
237
+ await this.stage.render(
238
+ this.player.playback.currentScene,
239
+ this.player.playback.previousScene
240
+ );
241
+ this.dispatchEvent(new CustomEvent("timeupdate", { detail: this.time }));
242
+ const durationInFrames = this.player.playback.duration;
243
+ if (durationInFrames === this.duration) {
244
+ return;
245
+ }
246
+ this.duration = durationInFrames;
247
+ const durationInSeconds = durationInFrames / this.player.playback.fps;
248
+ this.dispatchEvent(
249
+ new CustomEvent("duration", { detail: durationInSeconds })
250
+ );
251
+ }
252
+ };
253
+ updateSettings() {
254
+ if (!this.defaultSettings) {
255
+ return;
256
+ }
257
+ const settings = {
258
+ ...this.defaultSettings,
259
+ size: new import_core2.Vector2(this.width, this.height),
260
+ resolutionScale: this.quality,
261
+ fps: this.fps
262
+ };
263
+ this.stage.configure(settings);
264
+ this.player?.configure(settings);
265
+ }
266
+ };
267
+ if (!customElements.get(ID)) {
268
+ customElements.define(ID, TwickPlayer);
269
+ }
270
+ }
271
+ });
272
+
273
+ // src/index.tsx
274
+ var index_exports = {};
275
+ __export(index_exports, {
276
+ Player: () => Player2
277
+ });
278
+ module.exports = __toCommonJS(index_exports);
279
+ var import_react2 = require("react");
280
+
281
+ // src/controls.tsx
282
+ var import_react = require("react");
283
+
284
+ // src/icons.tsx
285
+ var import_jsx_runtime = require("react/jsx-runtime");
286
+ function PlayButton() {
287
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
288
+ "svg",
289
+ {
290
+ xmlns: "http://www.w3.org/2000/svg",
291
+ viewBox: "0 0 24 24",
292
+ fill: "currentColor",
293
+ className: "w-6 h-6",
294
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
295
+ "path",
296
+ {
297
+ fillRule: "evenodd",
298
+ d: "M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z",
299
+ clipRule: "evenodd"
300
+ }
301
+ )
302
+ }
303
+ );
304
+ }
305
+ function PauseButton() {
306
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
307
+ "svg",
308
+ {
309
+ xmlns: "http://www.w3.org/2000/svg",
310
+ viewBox: "0 0 24 24",
311
+ fill: "currentColor",
312
+ className: "w-6 h-6",
313
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
314
+ "path",
315
+ {
316
+ fillRule: "evenodd",
317
+ d: "M6.75 5.25a.75.75 0 0 1 .75-.75H9a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H7.5a.75.75 0 0 1-.75-.75V5.25Zm7.5 0A.75.75 0 0 1 15 4.5h1.5a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H15a.75.75 0 0 1-.75-.75V5.25Z",
318
+ clipRule: "evenodd"
319
+ }
320
+ )
321
+ }
322
+ );
323
+ }
324
+ function SoundIcon() {
325
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
326
+ "svg",
327
+ {
328
+ xmlns: "http://www.w3.org/2000/svg",
329
+ viewBox: "0 0 24 24",
330
+ fill: "currentColor",
331
+ className: "p-w-6 p-h-6",
332
+ children: [
333
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18.36,19.36a1,1,0,0,1-.7-.29,1,1,0,0,1,0-1.41,8,8,0,0,0,0-11.32,1,1,0,0,1,1.41-1.41,10,10,0,0,1,0,14.14A1,1,0,0,1,18.36,19.36Z" }),
334
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15.54,16.54a1,1,0,0,1-.71-.3,1,1,0,0,1,0-1.41,4,4,0,0,0,0-5.66,1,1,0,0,1,1.41-1.41,6,6,0,0,1,0,8.48A1,1,0,0,1,15.54,16.54Z" }),
335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M11.38,4.08a1,1,0,0,0-1.09.21L6.59,8H4a2,2,0,0,0-2,2v4a2,2,0,0,0,2,2H6.59l3.7,3.71A1,1,0,0,0,11,20a.84.84,0,0,0,.38-.08A1,1,0,0,0,12,19V5A1,1,0,0,0,11.38,4.08Z" })
336
+ ]
337
+ }
338
+ );
339
+ }
340
+ function MutedSoundIcon() {
341
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
342
+ "svg",
343
+ {
344
+ xmlns: "http://www.w3.org/2000/svg",
345
+ viewBox: "0 0 24 24",
346
+ fill: "currentColor",
347
+ className: "p-w-6 p-h-6",
348
+ children: [
349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M11.38,4.08a1,1,0,0,0-1.09.21L6.59,8H4a2,2,0,0,0-2,2v4a2,2,0,0,0,2,2H6.59l3.7,3.71A1,1,0,0,0,11,20a.84.84,0,0,0,.38-.08A1,1,0,0,0,12,19V5A1,1,0,0,0,11.38,4.08Z" }),
350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M16,15.5a1,1,0,0,1-.71-.29,1,1,0,0,1,0-1.42l5-5a1,1,0,0,1,1.42,1.42l-5,5A1,1,0,0,1,16,15.5Z" }),
351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21,15.5a1,1,0,0,1-.71-.29l-5-5a1,1,0,0,1,1.42-1.42l5,5a1,1,0,0,1,0,1.42A1,1,0,0,1,21,15.5Z" })
352
+ ]
353
+ }
354
+ );
355
+ }
356
+
357
+ // src/utils.ts
358
+ function getFormattedTime(timeInSeconds, absoluteTimeInSeconds, timeDisplayFormat) {
359
+ function toFormattedTime(timeInSeconds2) {
360
+ const minutes = Math.floor(timeInSeconds2 / 60);
361
+ const seconds = Math.floor(timeInSeconds2 % 60).toString().padStart(2, "0");
362
+ const milliseconds = Math.floor(timeInSeconds2 % 1 * 1e3).toString().padStart(3, "0");
363
+ if (timeDisplayFormat === "MM:SS") {
364
+ return `${minutes}:${seconds}`;
365
+ }
366
+ if (timeDisplayFormat === "MM:SS.m") {
367
+ return `${minutes}:${seconds}.${milliseconds[0]}`;
368
+ }
369
+ if (timeDisplayFormat === "MM:SS.mm") {
370
+ return `${minutes}:${seconds}.${milliseconds.slice(0, 2)}`;
371
+ }
372
+ }
373
+ return `${toFormattedTime(timeInSeconds)} / ${toFormattedTime(absoluteTimeInSeconds)}`;
374
+ }
375
+ function shouldShowControls(playing, isMouseOver, areControlsDisabled) {
376
+ if (areControlsDisabled) {
377
+ return false;
378
+ }
379
+ return !playing || isMouseOver;
380
+ }
381
+
382
+ // src/controls.tsx
383
+ var import_jsx_runtime2 = require("react/jsx-runtime");
384
+ function PlayPause({
385
+ playing,
386
+ setPlaying
387
+ }) {
388
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "p-1", onClick: () => setPlaying(!playing), children: playing ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PauseButton, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PlayButton, {}) });
389
+ }
390
+ function VolumeSlider({
391
+ volume,
392
+ setVolume
393
+ }) {
394
+ const [isHovering, setIsHovering] = (0, import_react.useState)(false);
395
+ const [isInteracting, setIsInteracting] = (0, import_react.useState)(false);
396
+ const [previousVolume, setPreviousVolume] = (0, import_react.useState)(1);
397
+ const handleIconClick = () => {
398
+ if (volume > 0) {
399
+ setPreviousVolume(volume);
400
+ setVolume(0);
401
+ } else {
402
+ setVolume(previousVolume);
403
+ }
404
+ };
405
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
406
+ "div",
407
+ {
408
+ className: "flex items-center space-x-2 relative",
409
+ onMouseEnter: () => setIsHovering(true),
410
+ onMouseLeave: () => {
411
+ if (!isInteracting) {
412
+ setIsHovering(false);
413
+ }
414
+ },
415
+ children: [
416
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
417
+ "div",
418
+ {
419
+ className: "w-6 h-6 flex items-center justify-center cursor-pointer",
420
+ onClick: handleIconClick,
421
+ children: volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MutedSoundIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SoundIcon, {})
422
+ }
423
+ ),
424
+ (isHovering || isInteracting) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center h-1.5 whitespace-nowrap", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-20 h-1.5 bg-gray-300 rounded-full", children: [
425
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
426
+ "div",
427
+ {
428
+ className: "absolute top-0 left-0 h-full bg-gray-100 rounded-full",
429
+ style: { width: `${volume * 100}%` }
430
+ }
431
+ ),
432
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
433
+ "input",
434
+ {
435
+ type: "range",
436
+ min: 0,
437
+ max: 1,
438
+ step: 0.01,
439
+ value: volume,
440
+ onChange: (e) => {
441
+ const newVolume = Number(e.target.value);
442
+ setVolume(newVolume);
443
+ if (newVolume > 0) {
444
+ setPreviousVolume(newVolume);
445
+ }
446
+ },
447
+ onMouseDown: () => setIsInteracting(true),
448
+ onMouseUp: () => setIsInteracting(false),
449
+ onMouseLeave: () => setIsInteracting(false),
450
+ className: "absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
451
+ }
452
+ )
453
+ ] }) })
454
+ ]
455
+ }
456
+ );
457
+ }
458
+ function Timeline({
459
+ currentTime,
460
+ duration,
461
+ setCurrentTime
462
+ }) {
463
+ const progressPercentage = currentTime / duration * 100;
464
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative flex-1 w-full h-1.5 bg-gray-300 rounded-full overflow-hidden", children: [
465
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
466
+ "div",
467
+ {
468
+ className: "absolute top-0 left-0 h-full bg-gray-100",
469
+ style: { width: `${progressPercentage}%` }
470
+ }
471
+ ),
472
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
473
+ "input",
474
+ {
475
+ type: "range",
476
+ value: currentTime,
477
+ min: 0,
478
+ max: duration,
479
+ step: 0.01,
480
+ className: "absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer",
481
+ onChange: (event) => setCurrentTime(Number(event.target.value))
482
+ }
483
+ )
484
+ ] });
485
+ }
486
+ function Controls({
487
+ duration,
488
+ playing,
489
+ setPlaying,
490
+ currentTime,
491
+ setForcedTime,
492
+ timeDisplayFormat,
493
+ volume,
494
+ setVolume
495
+ }) {
496
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-white p-4 flex-col space-y-2 bg-gradient-to-t from-gray-500 to-transparent", children: [
497
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center space-x-2", children: [
498
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PlayPause, { playing, setPlaying }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center space-x-2", children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VolumeSlider, { volume, setVolume }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: getFormattedTime(currentTime, duration, timeDisplayFormat) }) })
502
+ ] }),
503
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-grow" })
504
+ ] }),
505
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
506
+ Timeline,
507
+ {
508
+ currentTime,
509
+ duration,
510
+ setCurrentTime: setForcedTime
511
+ }
512
+ )
513
+ ] });
514
+ }
515
+
516
+ // src/index.tsx
517
+ var import_jsx_runtime3 = require("react/jsx-runtime");
518
+ function Player2({
519
+ project,
520
+ controls = true,
521
+ variables = {},
522
+ playing = false,
523
+ currentTime = 0,
524
+ volume = 1,
525
+ looping = true,
526
+ fps = 30,
527
+ width = void 0,
528
+ height = void 0,
529
+ quality = void 0,
530
+ timeDisplayFormat = "MM:SS",
531
+ onDurationChange = () => {
532
+ },
533
+ onTimeUpdate = () => {
534
+ },
535
+ onPlayerReady = () => {
536
+ },
537
+ onPlayerResize = () => {
538
+ }
539
+ }) {
540
+ const [playingState, setPlaying] = (0, import_react2.useState)(playing);
541
+ const [isMouseOver, setIsMouseOver] = (0, import_react2.useState)(false);
542
+ const [currentTimeState, setCurrentTime] = (0, import_react2.useState)(currentTime);
543
+ const [volumeState, setVolumeState] = (0, import_react2.useState)(volume);
544
+ const [duration, setDuration] = (0, import_react2.useState)(-1);
545
+ const focus = (0, import_react2.useRef)(false);
546
+ const playerRef = (0, import_react2.useRef)(null);
547
+ const wrapperRef = (0, import_react2.useRef)(null);
548
+ const lastRect = (0, import_react2.useRef)(null);
549
+ const onClickHandler = controls ? () => setPlaying((prev) => !prev) : void 0;
550
+ (0, import_react2.useEffect)(() => {
551
+ setPlaying(playing);
552
+ }, [playing]);
553
+ (0, import_react2.useEffect)(() => {
554
+ const diff = Math.abs(currentTime - currentTimeState);
555
+ if (diff > 0.05) {
556
+ setForcedTime(currentTime);
557
+ }
558
+ }, [currentTime]);
559
+ (0, import_react2.useEffect)(() => {
560
+ setForcedVolume(volume);
561
+ }, [volume]);
562
+ const onTimeUpdateRef = (0, import_react2.useRef)(onTimeUpdate);
563
+ const onDurationChangeRef = (0, import_react2.useRef)(onDurationChange);
564
+ (0, import_react2.useEffect)(() => {
565
+ onTimeUpdateRef.current = onTimeUpdate;
566
+ }, [onTimeUpdate]);
567
+ (0, import_react2.useEffect)(() => {
568
+ onDurationChangeRef.current = onDurationChange;
569
+ }, [onDurationChange]);
570
+ const handleTimeUpdate = (0, import_react2.useCallback)((event) => {
571
+ const e = event;
572
+ setCurrentTime(e.detail);
573
+ onTimeUpdateRef.current(e.detail);
574
+ }, []);
575
+ const handleDurationUpdate = (0, import_react2.useCallback)((event) => {
576
+ const e = event;
577
+ setDuration(e.detail);
578
+ onDurationChangeRef.current(e.detail);
579
+ }, []);
580
+ const handleKeyDown = (0, import_react2.useCallback)((event) => {
581
+ if (event.code === "Space" && focus.current) {
582
+ event.preventDefault();
583
+ setPlaying((prev) => !prev);
584
+ }
585
+ }, []);
586
+ const onPlayerReadyRef = (0, import_react2.useRef)(onPlayerReady);
587
+ (0, import_react2.useEffect)(() => {
588
+ onPlayerReadyRef.current = onPlayerReady;
589
+ }, [onPlayerReady]);
590
+ const handlePlayerReady = (0, import_react2.useCallback)((event) => {
591
+ const player = event.detail;
592
+ if (player) {
593
+ onPlayerReadyRef.current(player);
594
+ }
595
+ const playerElement = playerRef.current;
596
+ if (playerElement) {
597
+ playerElement.removeEventListener("timeupdate", handleTimeUpdate);
598
+ playerElement.removeEventListener("duration", handleDurationUpdate);
599
+ playerElement.addEventListener("timeupdate", handleTimeUpdate);
600
+ playerElement.addEventListener("duration", handleDurationUpdate);
601
+ }
602
+ }, [handleTimeUpdate, handleDurationUpdate]);
603
+ const handlePlayerResize = (0, import_react2.useCallback)(
604
+ (entries) => {
605
+ const [firstEntry] = entries;
606
+ if (!firstEntry || !wrapperRef.current) {
607
+ return;
608
+ }
609
+ const newRect = wrapperRef.current.getBoundingClientRect();
610
+ if (!lastRect.current || newRect.width !== lastRect.current.width || newRect.height !== lastRect.current.height || newRect.x !== lastRect.current.x || newRect.y !== lastRect.current.y) {
611
+ lastRect.current = newRect;
612
+ onPlayerResize(newRect);
613
+ }
614
+ },
615
+ [onPlayerResize]
616
+ );
617
+ (0, import_react2.useEffect)(() => {
618
+ if (!wrapperRef.current) return;
619
+ const resizeObserver = new ResizeObserver(handlePlayerResize);
620
+ resizeObserver.observe(wrapperRef.current);
621
+ return () => {
622
+ resizeObserver.disconnect();
623
+ };
624
+ }, [handlePlayerResize]);
625
+ (0, import_react2.useEffect)(() => {
626
+ let cleanup = null;
627
+ const setupListeners = () => {
628
+ const player = playerRef.current;
629
+ if (!player) return;
630
+ player.removeEventListener("timeupdate", handleTimeUpdate);
631
+ player.removeEventListener("duration", handleDurationUpdate);
632
+ player.removeEventListener("playerready", handlePlayerReady);
633
+ player.addEventListener("timeupdate", handleTimeUpdate);
634
+ player.addEventListener("duration", handleDurationUpdate);
635
+ player.addEventListener("playerready", handlePlayerReady);
636
+ document.addEventListener("keydown", handleKeyDown);
637
+ cleanup = () => {
638
+ player.removeEventListener("timeupdate", handleTimeUpdate);
639
+ player.removeEventListener("duration", handleDurationUpdate);
640
+ player.removeEventListener("playerready", handlePlayerReady);
641
+ document.removeEventListener("keydown", handleKeyDown);
642
+ };
643
+ };
644
+ Promise.resolve().then(() => (init_internal(), internal_exports)).then(() => {
645
+ requestAnimationFrame(() => {
646
+ if (playerRef.current) {
647
+ playerRef.current.setProject(project);
648
+ setupListeners();
649
+ }
650
+ });
651
+ });
652
+ if (playerRef.current) {
653
+ setupListeners();
654
+ }
655
+ return () => {
656
+ if (cleanup) {
657
+ cleanup();
658
+ }
659
+ };
660
+ }, [project, handleTimeUpdate, handleDurationUpdate, handlePlayerReady, handleKeyDown]);
661
+ function setForcedTime(forcedTime) {
662
+ if (playerRef.current) {
663
+ playerRef.current.dispatchEvent(
664
+ new CustomEvent("seekto", { detail: forcedTime })
665
+ );
666
+ }
667
+ }
668
+ function setForcedVolume(volume2) {
669
+ setVolumeState(volume2);
670
+ if (playerRef.current) {
671
+ playerRef.current.dispatchEvent(
672
+ new CustomEvent("volumechange", { detail: volume2 })
673
+ );
674
+ }
675
+ }
676
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "twick-player-root", style: { display: "contents" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
677
+ "div",
678
+ {
679
+ ref: wrapperRef,
680
+ className: "relative cursor-default focus:outline-none",
681
+ onFocus: () => focus.current = true,
682
+ onBlur: () => focus.current = false,
683
+ tabIndex: 0,
684
+ onMouseEnter: () => setIsMouseOver(true),
685
+ onMouseLeave: () => setIsMouseOver(false),
686
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
688
+ "twick-player",
689
+ {
690
+ ref: playerRef,
691
+ playing: String(playingState),
692
+ onClick: onClickHandler,
693
+ variables: JSON.stringify(variables),
694
+ looping: looping ? "true" : "false",
695
+ width,
696
+ height,
697
+ quality,
698
+ fps,
699
+ volume: volumeState
700
+ }
701
+ ),
702
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
703
+ "div",
704
+ {
705
+ className: `absolute bottom-0 w-full transition-opacity duration-200 ${shouldShowControls(playingState, isMouseOver, !controls) ? "opacity-100" : "opacity-0"}`,
706
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
707
+ Controls,
708
+ {
709
+ duration,
710
+ playing: playingState,
711
+ setPlaying,
712
+ currentTime: currentTimeState,
713
+ setForcedTime,
714
+ timeDisplayFormat,
715
+ volume: volumeState,
716
+ setVolume: setForcedVolume
717
+ }
718
+ )
719
+ }
720
+ )
721
+ ] })
722
+ }
723
+ ) });
724
+ }
725
+ // Annotate the CommonJS export names for ESM import in node:
726
+ 0 && (module.exports = {
727
+ Player
728
+ });
729
+ //# sourceMappingURL=index.cjs.map