@revideo/player-react 0.10.0 → 0.10.1-alpha.1116

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