@arcblock/terminal 3.1.11 → 3.1.13
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/lib/Player.js +178 -101
- package/package.json +4 -4
- package/src/Player.jsx +227 -107
package/lib/Player.js
CHANGED
|
@@ -1,129 +1,206 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useRef as
|
|
1
|
+
import { jsxs as F, jsx as a } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as P, useState as _, useReducer as U, useEffect as q, useCallback as g } from "react";
|
|
3
3
|
import o from "prop-types";
|
|
4
|
-
import { useSize as
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { PlayerRoot as
|
|
9
|
-
import { defaultOptions as
|
|
10
|
-
const
|
|
11
|
-
function
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
cols:
|
|
16
|
-
rows:
|
|
17
|
-
cursorStyle:
|
|
18
|
-
fontFamily:
|
|
19
|
-
fontSize:
|
|
20
|
-
lineHeight:
|
|
21
|
-
letterSpacing:
|
|
4
|
+
import { useSize as Y } from "ahooks";
|
|
5
|
+
import x from "lodash/isUndefined";
|
|
6
|
+
import b from "lodash/noop";
|
|
7
|
+
import X from "./Terminal.js";
|
|
8
|
+
import { PlayerRoot as $ } from "./styles.js";
|
|
9
|
+
import { defaultOptions as G, formatFrames as K, defaultState as Q, findFrameAt as M, getPlayerClass as V, getFrameClass as Z, formatTime as ee } from "./util.js";
|
|
10
|
+
const me = 24;
|
|
11
|
+
function te({ ...j }) {
|
|
12
|
+
const s = Object.assign({}, j);
|
|
13
|
+
x(s.onComplete) && (s.onComplete = b), x(s.onStart) && (s.onStart = b), x(s.onStop) && (s.onStop = b), x(s.onPause) && (s.onPause = b), x(s.onTick) && (s.onTick = b), x(s.onJump) && (s.onJump = b);
|
|
14
|
+
const e = Object.assign({}, G, s.options), { frames: c, totalDuration: w } = K(s.frames, e), D = {
|
|
15
|
+
cols: e.cols,
|
|
16
|
+
rows: e.rows,
|
|
17
|
+
cursorStyle: e.cursorStyle,
|
|
18
|
+
fontFamily: e.fontFamily,
|
|
19
|
+
fontSize: e.fontSize,
|
|
20
|
+
lineHeight: e.lineHeight,
|
|
21
|
+
letterSpacing: e.letterSpacing,
|
|
22
22
|
allowTransparency: !0,
|
|
23
23
|
scrollback: 0,
|
|
24
|
-
theme:
|
|
25
|
-
},
|
|
26
|
-
switch (
|
|
24
|
+
theme: e.theme || {}
|
|
25
|
+
}, E = (t, r) => {
|
|
26
|
+
switch (r.type) {
|
|
27
27
|
case "jump":
|
|
28
|
-
return { ...
|
|
28
|
+
return { ...t, ...r.payload };
|
|
29
29
|
case "start":
|
|
30
|
-
return { ...
|
|
30
|
+
return { ...t, isStarted: !0, lastTickTime: Date.now() };
|
|
31
31
|
case "play":
|
|
32
|
-
return { ...
|
|
32
|
+
return { ...t, isPlaying: !0, lastTickTime: Date.now(), ...r.payload };
|
|
33
33
|
case "pause":
|
|
34
|
-
return { ...
|
|
34
|
+
return { ...t, isPlaying: !1, ...r.payload };
|
|
35
35
|
case "tickStart":
|
|
36
|
-
return { ...
|
|
36
|
+
return { ...t, isRendering: !0, lastTickTime: Date.now(), ...r.payload };
|
|
37
37
|
case "tickEnd":
|
|
38
|
-
return { ...
|
|
38
|
+
return { ...t, isRendering: !1, lastTickTime: Date.now(), ...r.payload };
|
|
39
39
|
case "reset":
|
|
40
|
-
return { ...
|
|
40
|
+
return { ...t, currentFrame: -1, currentTime: 0, ...r.payload };
|
|
41
41
|
default:
|
|
42
|
-
return { ...
|
|
42
|
+
return { ...t, lastTickTime: Date.now(), ...r.payload };
|
|
43
43
|
}
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
if (
|
|
44
|
+
}, l = P(null), R = P(null), S = P(null), p = P(null), d = P(null), [z, J] = _(0), L = Y(document.body), [n, u] = U(E, Q), O = L?.width || 0;
|
|
45
|
+
q(() => {
|
|
46
|
+
if (l.current)
|
|
47
47
|
try {
|
|
48
|
-
const
|
|
49
|
-
let
|
|
50
|
-
if (
|
|
51
|
-
const
|
|
52
|
-
|
|
48
|
+
const t = 8.03305785123967, r = S.current.getBoundingClientRect();
|
|
49
|
+
let i = r.x < 0 ? r.width + r.x : r.width;
|
|
50
|
+
if (S.current.parentNode) {
|
|
51
|
+
const T = S.current.parentNode.getBoundingClientRect();
|
|
52
|
+
r.width > T.width && (i = T.width);
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
54
|
+
e.controls && (i -= 12);
|
|
55
|
+
const f = Math.ceil(i / t), y = Math.min(Math.max(f, 40), e.cols);
|
|
56
|
+
l.current.resize(y, e.rows);
|
|
57
57
|
} catch {
|
|
58
58
|
}
|
|
59
|
-
}, [
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
59
|
+
}, [O, z]);
|
|
60
|
+
const k = g(
|
|
61
|
+
(t) => new Promise((r) => {
|
|
62
|
+
const i = c[t];
|
|
63
|
+
i && i.content && l.current ? (n.requireReset && l.current.reset(), l.current.write(i.content, () => {
|
|
64
|
+
r();
|
|
65
|
+
})) : r();
|
|
66
|
+
}),
|
|
67
|
+
[c, n.requireReset]
|
|
68
|
+
), m = g(
|
|
69
|
+
(t) => {
|
|
70
|
+
typeof s[t] == "function" && s[t]({ state: n, frames: c, options: e });
|
|
71
|
+
},
|
|
72
|
+
[s, n, c, e]
|
|
73
|
+
), C = g(
|
|
74
|
+
async (t) => {
|
|
75
|
+
if (!l.current) return;
|
|
76
|
+
l.current.reset();
|
|
77
|
+
const r = M(c, t);
|
|
78
|
+
if (r >= 0)
|
|
79
|
+
for (let i = 0; i <= r; i++)
|
|
80
|
+
await k(i);
|
|
81
|
+
},
|
|
82
|
+
[c, k]
|
|
83
|
+
), I = g(
|
|
84
|
+
(t) => {
|
|
85
|
+
if (!R.current || !l.current || !n.isStarted)
|
|
86
|
+
return !1;
|
|
87
|
+
const r = R.current.getBoundingClientRect().width, i = t.nativeEvent.offsetX, f = Math.floor(w * i / r), y = M(c, f), T = n.isPlaying;
|
|
88
|
+
return u({
|
|
89
|
+
type: "jump",
|
|
90
|
+
payload: {
|
|
91
|
+
currentTime: f,
|
|
92
|
+
currentFrame: y,
|
|
93
|
+
isPlaying: T
|
|
94
|
+
// Keep the same playing state
|
|
95
|
+
}
|
|
96
|
+
}), d.current = null, C(f), m("onJump"), !1;
|
|
97
|
+
},
|
|
98
|
+
[n.isStarted, n.isPlaying, w, c, C, m]
|
|
99
|
+
), N = g(() => (n.isStarted === !1 && (u({ type: "start" }), l.current && l.current.reset()), d.current = null, u({ type: "play", payload: { currentFrame: -1, currentTime: 0 } }), m("onStart"), !1), [n.isStarted, m]), W = g(() => (u({ type: "pause" }), m("onPause"), !1), [m]), H = g(() => (n.currentFrame >= c.length - 1 ? (u({ type: "reset", payload: { currentFrame: -1, currentTime: 0 } }), l.current && l.current.reset(), d.current = null, u({ type: "play", payload: { currentFrame: -1, currentTime: 0 } })) : (d.current = null, u({ type: "play" })), m("onPlay"), !1), [n.currentFrame, c.length, m]);
|
|
100
|
+
q(() => {
|
|
101
|
+
if (l.current) {
|
|
102
|
+
if (S.current)
|
|
81
103
|
try {
|
|
82
|
-
|
|
104
|
+
J(S.current.getBoundingClientRect().width);
|
|
83
105
|
} catch {
|
|
84
106
|
}
|
|
85
|
-
|
|
107
|
+
e.autoplay ? N() : C(Math.min(Math.abs(e.thumbnailTime), w));
|
|
86
108
|
}
|
|
87
|
-
}, [])
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
}, []);
|
|
110
|
+
const v = P(n);
|
|
111
|
+
v.current = n;
|
|
112
|
+
const B = g(async () => {
|
|
113
|
+
const t = v.current;
|
|
114
|
+
if (!t.isPlaying || t.isRendering)
|
|
115
|
+
return;
|
|
116
|
+
const r = performance.now();
|
|
117
|
+
if (!d.current) {
|
|
118
|
+
const h = t.currentFrame >= 0 && c[t.currentFrame]?.startTime || 0;
|
|
119
|
+
d.current = r - h;
|
|
120
|
+
}
|
|
121
|
+
const i = r - d.current, { currentFrame: f } = t, y = f + 1;
|
|
122
|
+
if (f >= c.length - 1) {
|
|
123
|
+
if (m("onComplete"), e.repeat) {
|
|
124
|
+
d.current = r, u({ type: "reset", payload: { currentTime: 0, currentFrame: -1 } });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const h = {
|
|
128
|
+
currentTime: w,
|
|
129
|
+
currentFrame: c.length - 1,
|
|
130
|
+
requireReset: !0,
|
|
131
|
+
isStarted: !1
|
|
132
|
+
};
|
|
133
|
+
u({ type: "pause", payload: h }), d.current = null;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const T = c[y];
|
|
137
|
+
if (!T) {
|
|
138
|
+
n.isPlaying && (p.current = requestAnimationFrame(B));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const A = T.startTime || 0;
|
|
142
|
+
if (i >= A) {
|
|
143
|
+
u({
|
|
144
|
+
type: "tickStart",
|
|
145
|
+
payload: {
|
|
146
|
+
currentTime: A,
|
|
147
|
+
currentFrame: y
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
try {
|
|
151
|
+
await k(y);
|
|
152
|
+
const h = {
|
|
153
|
+
currentTime: A,
|
|
154
|
+
currentFrame: y
|
|
155
|
+
};
|
|
156
|
+
t.requireReset && (h.requireReset = !1), u({ type: "tickEnd", payload: h }), m("onTick");
|
|
157
|
+
} catch (h) {
|
|
158
|
+
console.error("Frame rendering error:", h);
|
|
159
|
+
}
|
|
160
|
+
} else
|
|
161
|
+
u({ type: "tick", payload: { currentTime: i } });
|
|
162
|
+
}, [c, k, m, e.repeat, w]);
|
|
163
|
+
return q(() => {
|
|
164
|
+
let t = !0;
|
|
165
|
+
const r = async () => {
|
|
166
|
+
for (; t && v.current.isPlaying; )
|
|
167
|
+
await new Promise((i) => {
|
|
168
|
+
p.current = requestAnimationFrame(i);
|
|
169
|
+
}), t && v.current.isPlaying && await B();
|
|
170
|
+
};
|
|
171
|
+
return n.isPlaying ? r() : (p.current && (cancelAnimationFrame(p.current), p.current = null), d.current = null), () => {
|
|
172
|
+
t = !1, p.current && (cancelAnimationFrame(p.current), p.current = null);
|
|
173
|
+
};
|
|
174
|
+
}, [n.isPlaying, B]), e.controls && (e.frameBox.title = null, e.frameBox.type = null, e.frameBox.style = {}, e.theme?.background === "transparent" ? e.frameBox.style.background = "black" : e.theme?.background && (e.frameBox.style.background = e.theme.background), e.frameBox.style.padding = "10px", e.frameBox.style.paddingBottom = "40px"), /* @__PURE__ */ F($, { className: V(e, n), ref: S, children: [
|
|
175
|
+
/* @__PURE__ */ a("div", { className: "cover", onClick: N }),
|
|
176
|
+
/* @__PURE__ */ a("div", { className: "start", onClick: N, children: /* @__PURE__ */ F("svg", { style: { enableBackground: "new 0 0 30 30" }, viewBox: "0 0 30 30", children: [
|
|
177
|
+
/* @__PURE__ */ a("polygon", { points: "6.583,3.186 5,4.004 5,15 26,15 26.483,14.128 " }),
|
|
178
|
+
/* @__PURE__ */ a("polygon", { points: "6.583,26.814 5,25.996 5,15 26,15 26.483,15.872 " }),
|
|
179
|
+
/* @__PURE__ */ a("circle", { cx: "26", cy: "15", r: "1" }),
|
|
180
|
+
/* @__PURE__ */ a("circle", { cx: "6", cy: "4", r: "1" }),
|
|
181
|
+
/* @__PURE__ */ a("circle", { cx: "6", cy: "26", r: "1" })
|
|
105
182
|
] }) }),
|
|
106
|
-
/* @__PURE__ */
|
|
107
|
-
/* @__PURE__ */
|
|
108
|
-
/* @__PURE__ */
|
|
109
|
-
/* @__PURE__ */
|
|
110
|
-
/* @__PURE__ */
|
|
111
|
-
/* @__PURE__ */
|
|
183
|
+
/* @__PURE__ */ a("div", { className: "terminal", children: /* @__PURE__ */ F("div", { className: Z(e), style: e.frameBox.style || {}, children: [
|
|
184
|
+
/* @__PURE__ */ F("div", { className: "terminal-titlebar", children: [
|
|
185
|
+
/* @__PURE__ */ F("div", { className: "buttons", children: [
|
|
186
|
+
/* @__PURE__ */ a("div", { className: "close-button" }),
|
|
187
|
+
/* @__PURE__ */ a("div", { className: "minimize-button" }),
|
|
188
|
+
/* @__PURE__ */ a("div", { className: "maximize-button" })
|
|
112
189
|
] }),
|
|
113
|
-
/* @__PURE__ */
|
|
190
|
+
/* @__PURE__ */ a("div", { className: "title", children: e.frameBox.title || "" })
|
|
114
191
|
] }),
|
|
115
|
-
/* @__PURE__ */
|
|
192
|
+
/* @__PURE__ */ a("div", { className: "terminal-body", children: /* @__PURE__ */ a(X, { ref: l, options: D }) })
|
|
116
193
|
] }) }),
|
|
117
|
-
/* @__PURE__ */
|
|
118
|
-
|
|
119
|
-
!
|
|
120
|
-
!
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
-
/* @__PURE__ */
|
|
194
|
+
/* @__PURE__ */ F("div", { className: "controller", children: [
|
|
195
|
+
n.isPlaying && /* @__PURE__ */ a("div", { className: "pause", onClick: W, title: "Pause", children: /* @__PURE__ */ a("span", { className: "icon" }) }),
|
|
196
|
+
!n.isPlaying && n.isStarted && /* @__PURE__ */ a("div", { className: "play", onClick: H, title: "Play", children: /* @__PURE__ */ a("span", { className: "icon" }) }),
|
|
197
|
+
!n.isPlaying && !n.isStarted && /* @__PURE__ */ a("div", { className: "play", onClick: N, title: "Start", children: /* @__PURE__ */ a("span", { className: "icon" }) }),
|
|
198
|
+
/* @__PURE__ */ a("div", { className: "timer", children: ee(n.currentTime) }),
|
|
199
|
+
/* @__PURE__ */ a("div", { className: "progressbar-wrapper", children: /* @__PURE__ */ a("div", { className: "progressbar", ref: R, onClick: I, children: /* @__PURE__ */ a("div", { className: "progress", style: { width: `${n.currentTime / w * 100}%` } }) }) })
|
|
123
200
|
] })
|
|
124
201
|
] });
|
|
125
202
|
}
|
|
126
|
-
|
|
203
|
+
te.propTypes = {
|
|
127
204
|
frames: o.array.isRequired,
|
|
128
205
|
options: o.shape({
|
|
129
206
|
autoplay: o.bool,
|
|
@@ -142,6 +219,6 @@ Q.propTypes = {
|
|
|
142
219
|
onJump: o.func
|
|
143
220
|
};
|
|
144
221
|
export {
|
|
145
|
-
|
|
146
|
-
|
|
222
|
+
me as PLAYER_FRAME_DELAY,
|
|
223
|
+
te as default
|
|
147
224
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcblock/terminal",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.13",
|
|
4
4
|
"description": "A react wrapper for xterm allowing you to easily render a terminal in the browser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": "^19.0.0"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "d571a921f32b979346e3d2bc64ecd7ab6d01a2ad",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@arcblock/react-hooks": "3.1.
|
|
46
|
-
"@arcblock/ux": "3.1.
|
|
45
|
+
"@arcblock/react-hooks": "3.1.13",
|
|
46
|
+
"@arcblock/ux": "3.1.13",
|
|
47
47
|
"@emotion/react": "^11.14.0",
|
|
48
48
|
"@emotion/styled": "^11.14.0",
|
|
49
49
|
"ahooks": "^3.8.5",
|
package/src/Player.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/* eslint-disable react/no-unused-prop-types */
|
|
2
|
-
import { useReducer, useState, useRef, useEffect } from 'react';
|
|
1
|
+
/* eslint-disable react/no-unused-prop-types, no-console */
|
|
2
|
+
import { useReducer, useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
|
-
import { useSize
|
|
4
|
+
import { useSize } from 'ahooks';
|
|
5
5
|
import isUndefined from 'lodash/isUndefined';
|
|
6
6
|
import noop from 'lodash/noop';
|
|
7
7
|
|
|
@@ -13,14 +13,13 @@ import {
|
|
|
13
13
|
formatFrames,
|
|
14
14
|
formatTime,
|
|
15
15
|
findFrameAt,
|
|
16
|
-
isFrameAt,
|
|
17
16
|
getFrameClass,
|
|
18
17
|
getPlayerClass,
|
|
19
18
|
defaultOptions,
|
|
20
19
|
defaultState,
|
|
21
20
|
} from './util';
|
|
22
21
|
|
|
23
|
-
export const PLAYER_FRAME_DELAY =
|
|
22
|
+
export const PLAYER_FRAME_DELAY = 24;
|
|
24
23
|
|
|
25
24
|
export default function Player({ ...rawProps }) {
|
|
26
25
|
const props = Object.assign({}, rawProps);
|
|
@@ -63,7 +62,7 @@ export default function Player({ ...rawProps }) {
|
|
|
63
62
|
// console.log(`dispatch.${action.type}`, action.payload);
|
|
64
63
|
switch (action.type) {
|
|
65
64
|
case 'jump':
|
|
66
|
-
return { ...state,
|
|
65
|
+
return { ...state, ...action.payload };
|
|
67
66
|
case 'start':
|
|
68
67
|
return { ...state, isStarted: true, lastTickTime: Date.now() };
|
|
69
68
|
case 'play':
|
|
@@ -75,7 +74,7 @@ export default function Player({ ...rawProps }) {
|
|
|
75
74
|
case 'tickEnd':
|
|
76
75
|
return { ...state, isRendering: false, lastTickTime: Date.now(), ...action.payload };
|
|
77
76
|
case 'reset':
|
|
78
|
-
return { ...state, currentFrame:
|
|
77
|
+
return { ...state, currentFrame: -1, currentTime: 0, ...action.payload };
|
|
79
78
|
default:
|
|
80
79
|
return { ...state, lastTickTime: Date.now(), ...action.payload };
|
|
81
80
|
}
|
|
@@ -84,6 +83,8 @@ export default function Player({ ...rawProps }) {
|
|
|
84
83
|
const terminal = useRef(null);
|
|
85
84
|
const progress = useRef(null);
|
|
86
85
|
const container = useRef(null);
|
|
86
|
+
const animationRef = useRef(null);
|
|
87
|
+
const startTimeRef = useRef(null);
|
|
87
88
|
const [maxWidth, setMaxWidth] = useState(0);
|
|
88
89
|
const size = useSize(document.body);
|
|
89
90
|
const [state, dispatch] = useReducer(stateReducer, defaultState);
|
|
@@ -118,83 +119,133 @@ export default function Player({ ...rawProps }) {
|
|
|
118
119
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
120
|
}, [width, maxWidth]);
|
|
120
121
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
// Render a frame with Promise-based approach
|
|
123
|
+
const renderFrame = useCallback(
|
|
124
|
+
(frameIndex) => {
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
const frame = frames[frameIndex];
|
|
127
|
+
if (frame && frame.content && terminal.current) {
|
|
128
|
+
if (state.requireReset) {
|
|
129
|
+
terminal.current.reset();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
terminal.current.write(frame.content, () => {
|
|
133
|
+
resolve();
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
resolve();
|
|
133
137
|
}
|
|
134
138
|
});
|
|
135
|
-
}
|
|
136
|
-
|
|
139
|
+
},
|
|
140
|
+
[frames, state.requireReset]
|
|
141
|
+
);
|
|
137
142
|
|
|
138
143
|
// Emit a event
|
|
139
|
-
const emitEvent = (
|
|
140
|
-
|
|
141
|
-
props[name]
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
const emitEvent = useCallback(
|
|
145
|
+
(name) => {
|
|
146
|
+
if (typeof props[name] === 'function') {
|
|
147
|
+
props[name]({ state, frames, options });
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[props, state, frames, options]
|
|
151
|
+
);
|
|
144
152
|
|
|
145
|
-
const doJump = (
|
|
146
|
-
|
|
153
|
+
const doJump = useCallback(
|
|
154
|
+
async (time) => {
|
|
155
|
+
if (!terminal.current) return;
|
|
147
156
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
renderFrame(i);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
157
|
+
terminal.current.reset();
|
|
158
|
+
const toFrameIndex = findFrameAt(frames, time);
|
|
153
159
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
if (toFrameIndex >= 0) {
|
|
161
|
+
// Render all frames up to the target frame sequentially
|
|
162
|
+
for (let i = 0; i <= toFrameIndex; i++) {
|
|
163
|
+
// eslint-disable-next-line no-await-in-loop
|
|
164
|
+
await renderFrame(i);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
[frames, renderFrame]
|
|
169
|
+
);
|
|
158
170
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
const onJump = useCallback(
|
|
172
|
+
(e) => {
|
|
173
|
+
if (!progress.current || !terminal.current || !state.isStarted) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
162
176
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
doJump(currentTime);
|
|
166
|
-
emitEvent('onJump');
|
|
177
|
+
const length = progress.current.getBoundingClientRect().width;
|
|
178
|
+
const position = e.nativeEvent.offsetX;
|
|
167
179
|
|
|
168
|
-
|
|
169
|
-
|
|
180
|
+
const currentTime = Math.floor((totalDuration * position) / length);
|
|
181
|
+
const targetFrameIndex = findFrameAt(frames, currentTime);
|
|
182
|
+
|
|
183
|
+
// Preserve the current playing state
|
|
184
|
+
const isCurrentlyPlaying = state.isPlaying;
|
|
185
|
+
|
|
186
|
+
// Update state to reflect the jump while preserving play state
|
|
187
|
+
dispatch({
|
|
188
|
+
type: 'jump',
|
|
189
|
+
payload: {
|
|
190
|
+
currentTime,
|
|
191
|
+
currentFrame: targetFrameIndex,
|
|
192
|
+
isPlaying: isCurrentlyPlaying, // Keep the same playing state
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Reset the timing for accurate playback after jump
|
|
197
|
+
startTimeRef.current = null;
|
|
170
198
|
|
|
171
|
-
|
|
199
|
+
// Perform the jump
|
|
200
|
+
doJump(currentTime);
|
|
201
|
+
emitEvent('onJump');
|
|
202
|
+
|
|
203
|
+
return false;
|
|
204
|
+
},
|
|
205
|
+
[state.isStarted, state.isPlaying, totalDuration, frames, doJump, emitEvent]
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const onStart = useCallback(() => {
|
|
172
209
|
if (state.isStarted === false) {
|
|
173
210
|
dispatch({ type: 'start' });
|
|
174
|
-
terminal.current
|
|
211
|
+
if (terminal.current) {
|
|
212
|
+
terminal.current.reset();
|
|
213
|
+
}
|
|
175
214
|
}
|
|
176
215
|
|
|
177
|
-
|
|
216
|
+
// Reset start time and frame for accurate timing
|
|
217
|
+
startTimeRef.current = null;
|
|
218
|
+
dispatch({ type: 'play', payload: { currentFrame: -1, currentTime: 0 } });
|
|
178
219
|
emitEvent('onStart');
|
|
179
220
|
|
|
180
221
|
return false;
|
|
181
|
-
};
|
|
222
|
+
}, [state.isStarted, emitEvent]);
|
|
182
223
|
|
|
183
|
-
const onPause = () => {
|
|
224
|
+
const onPause = useCallback(() => {
|
|
184
225
|
dispatch({ type: 'pause' });
|
|
185
226
|
emitEvent('onPause');
|
|
186
227
|
return false;
|
|
187
|
-
};
|
|
228
|
+
}, [emitEvent]);
|
|
188
229
|
|
|
189
|
-
const onPlay = () => {
|
|
190
|
-
if (state.currentFrame
|
|
191
|
-
|
|
192
|
-
|
|
230
|
+
const onPlay = useCallback(() => {
|
|
231
|
+
if (state.currentFrame >= frames.length - 1) {
|
|
232
|
+
// Reset to beginning if at the end
|
|
233
|
+
dispatch({ type: 'reset', payload: { currentFrame: -1, currentTime: 0 } });
|
|
234
|
+
if (terminal.current) {
|
|
235
|
+
terminal.current.reset();
|
|
236
|
+
}
|
|
237
|
+
startTimeRef.current = null;
|
|
238
|
+
// Start from beginning
|
|
239
|
+
dispatch({ type: 'play', payload: { currentFrame: -1, currentTime: 0 } });
|
|
240
|
+
} else {
|
|
241
|
+
// Continue from current position
|
|
242
|
+
startTimeRef.current = null; // Reset timing for accurate playback
|
|
243
|
+
dispatch({ type: 'play' });
|
|
193
244
|
}
|
|
194
245
|
|
|
195
246
|
emitEvent('onPlay');
|
|
196
|
-
return
|
|
197
|
-
};
|
|
247
|
+
return false;
|
|
248
|
+
}, [state.currentFrame, frames.length, emitEvent]);
|
|
198
249
|
|
|
199
250
|
// Render thumbnailTime
|
|
200
251
|
useEffect(() => {
|
|
@@ -216,68 +267,137 @@ export default function Player({ ...rawProps }) {
|
|
|
216
267
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
217
268
|
}, []);
|
|
218
269
|
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (state.isRendering) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
270
|
+
// Use ref to store the latest state and avoid recreating the loop function
|
|
271
|
+
const stateRef = useRef(state);
|
|
272
|
+
stateRef.current = state;
|
|
225
273
|
|
|
226
|
-
|
|
227
|
-
|
|
274
|
+
// Animation loop - frame-based sequential rendering
|
|
275
|
+
const animationLoop = useCallback(async () => {
|
|
276
|
+
const currentState = stateRef.current;
|
|
228
277
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
278
|
+
if (!currentState.isPlaying || currentState.isRendering) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const now = performance.now();
|
|
283
|
+
if (!startTimeRef.current) {
|
|
284
|
+
// Calculate start time based on current frame position
|
|
285
|
+
const currentFrameTime = currentState.currentFrame >= 0 ? frames[currentState.currentFrame]?.startTime || 0 : 0;
|
|
286
|
+
startTimeRef.current = now - currentFrameTime;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const elapsed = now - startTimeRef.current;
|
|
290
|
+
|
|
291
|
+
// Check if it's time to render the next frame
|
|
292
|
+
const { currentFrame } = currentState;
|
|
293
|
+
const nextFrameIndex = currentFrame + 1;
|
|
294
|
+
|
|
295
|
+
// If we've rendered all frames, we're done
|
|
296
|
+
if (currentFrame >= frames.length - 1) {
|
|
297
|
+
emitEvent('onComplete');
|
|
298
|
+
|
|
299
|
+
if (options.repeat) {
|
|
300
|
+
// Reset for repeat
|
|
301
|
+
startTimeRef.current = now;
|
|
302
|
+
dispatch({ type: 'reset', payload: { currentTime: 0, currentFrame: -1 } });
|
|
303
|
+
return;
|
|
234
304
|
}
|
|
305
|
+
// Stop playing
|
|
306
|
+
const endState = {
|
|
307
|
+
currentTime: totalDuration,
|
|
308
|
+
currentFrame: frames.length - 1,
|
|
309
|
+
requireReset: true,
|
|
310
|
+
isStarted: false,
|
|
311
|
+
};
|
|
312
|
+
dispatch({ type: 'pause', payload: endState });
|
|
313
|
+
startTimeRef.current = null;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
235
316
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
317
|
+
// Calculate when the next frame should be rendered
|
|
318
|
+
const nextFrame = frames[nextFrameIndex];
|
|
319
|
+
if (!nextFrame) {
|
|
320
|
+
// No more frames, schedule next animation frame
|
|
321
|
+
if (state.isPlaying) {
|
|
322
|
+
animationRef.current = requestAnimationFrame(animationLoop);
|
|
239
323
|
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
240
326
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
327
|
+
// Check if enough time has passed for the next frame
|
|
328
|
+
const frameStartTime = nextFrame.startTime || 0;
|
|
329
|
+
if (elapsed >= frameStartTime) {
|
|
330
|
+
// Time to render the next frame
|
|
331
|
+
dispatch({
|
|
332
|
+
type: 'tickStart',
|
|
333
|
+
payload: {
|
|
334
|
+
currentTime: frameStartTime,
|
|
335
|
+
currentFrame: nextFrameIndex,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
244
338
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
339
|
+
try {
|
|
340
|
+
await renderFrame(nextFrameIndex);
|
|
341
|
+
|
|
342
|
+
const finalState = {
|
|
343
|
+
currentTime: frameStartTime,
|
|
344
|
+
currentFrame: nextFrameIndex,
|
|
345
|
+
};
|
|
346
|
+
if (currentState.requireReset) {
|
|
347
|
+
finalState.requireReset = false;
|
|
248
348
|
}
|
|
249
349
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
newState.isStarted = false;
|
|
255
|
-
return dispatch({ type: 'pause', payload: newState });
|
|
350
|
+
dispatch({ type: 'tickEnd', payload: finalState });
|
|
351
|
+
emitEvent('onTick');
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('Frame rendering error:', error);
|
|
256
354
|
}
|
|
355
|
+
} else {
|
|
356
|
+
// Just update time without rendering
|
|
357
|
+
dispatch({ type: 'tick', payload: { currentTime: elapsed } });
|
|
358
|
+
}
|
|
257
359
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
state?.currentFrame != null &&
|
|
262
|
-
isFrameAt(newState.currentTime, state.currentFrame + 1)
|
|
263
|
-
) {
|
|
264
|
-
newState.currentFrame = state.currentFrame + 1;
|
|
265
|
-
} else {
|
|
266
|
-
newState.currentFrame = findFrameAt(frames, newState.currentTime);
|
|
267
|
-
}
|
|
360
|
+
// Don't schedule here - let useEffect handle it
|
|
361
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
362
|
+
}, [frames, renderFrame, emitEvent, options.repeat, totalDuration]);
|
|
268
363
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
364
|
+
// Start/stop animation loop based on playing state
|
|
365
|
+
useEffect(() => {
|
|
366
|
+
let isActive = true;
|
|
367
|
+
|
|
368
|
+
const runAnimationLoop = async () => {
|
|
369
|
+
// eslint-disable-next-line no-await-in-loop
|
|
370
|
+
while (isActive && stateRef.current.isPlaying) {
|
|
371
|
+
// eslint-disable-next-line no-await-in-loop
|
|
372
|
+
await new Promise((resolve) => {
|
|
373
|
+
animationRef.current = requestAnimationFrame(resolve);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
if (isActive && stateRef.current.isPlaying) {
|
|
377
|
+
// eslint-disable-next-line no-await-in-loop
|
|
378
|
+
await animationLoop();
|
|
274
379
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
if (state.isPlaying) {
|
|
384
|
+
runAnimationLoop();
|
|
385
|
+
} else {
|
|
386
|
+
if (animationRef.current) {
|
|
387
|
+
cancelAnimationFrame(animationRef.current);
|
|
388
|
+
animationRef.current = null;
|
|
389
|
+
}
|
|
390
|
+
startTimeRef.current = null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return () => {
|
|
394
|
+
isActive = false;
|
|
395
|
+
if (animationRef.current) {
|
|
396
|
+
cancelAnimationFrame(animationRef.current);
|
|
397
|
+
animationRef.current = null;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}, [state.isPlaying, animationLoop]);
|
|
281
401
|
|
|
282
402
|
// If controls are enabled, we need to disable frameBox
|
|
283
403
|
if (options.controls) {
|