screen-recorder-react 0.0.5 → 0.0.7
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/package.json +19 -19
- package/src/App.tsx +315 -200
- package/src/components/ScreenRecorder.tsx +161 -140
- package/src/main.tsx +6 -7
- package/tsconfig.json +13 -3
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screen-recorder-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"predev": "
|
|
6
|
-
"prebuild": "
|
|
7
|
-
"dev": "vite",
|
|
8
|
-
"build": "tsc && vite build",
|
|
5
|
+
"predev": "pnpm --filter screen-recorder-base build",
|
|
6
|
+
"prebuild": "pnpm --filter screen-recorder-base build",
|
|
7
|
+
"dev": "vite --host",
|
|
8
|
+
"build": "tsc --skipLibCheck && vite build",
|
|
9
9
|
"serve": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"types": "./src/index.ts",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
+
"types": "./src/index.ts",
|
|
14
15
|
"import": "./src/index.ts",
|
|
15
|
-
"require": "./src/index.ts"
|
|
16
|
-
"types": "./src/index.ts"
|
|
16
|
+
"require": "./src/index.ts"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@w-xuefeng/bindkey": "^1.0.
|
|
21
|
-
"react": "^
|
|
22
|
-
"react-dom": "^
|
|
23
|
-
"screen-recorder-base": "^0.0.
|
|
24
|
-
"use-draggable-hook": "^
|
|
20
|
+
"@w-xuefeng/bindkey": "^1.0.3",
|
|
21
|
+
"react": "^19.2.3",
|
|
22
|
+
"react-dom": "^19.2.3",
|
|
23
|
+
"screen-recorder-base": "^0.0.8",
|
|
24
|
+
"use-draggable-hook": "^1.1.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/react": "^
|
|
28
|
-
"@types/react-dom": "^
|
|
29
|
-
"@vitejs/plugin-react": "^1.
|
|
30
|
-
"antd": "^
|
|
31
|
-
"typescript": "^
|
|
32
|
-
"vite": "^
|
|
27
|
+
"@types/react": "^19.2.9",
|
|
28
|
+
"@types/react-dom": "^19.2.3",
|
|
29
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
30
|
+
"antd": "^6.2.1",
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"vite": "^7.3.1"
|
|
33
33
|
}
|
|
34
|
-
}
|
|
34
|
+
}
|
package/src/App.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Card,
|
|
3
|
-
import ScreenRecorder, {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Card, Col, Input, Row, Select, Switch } from "antd";
|
|
3
|
+
import ScreenRecorder, { safeCallback, Video } from ".";
|
|
4
|
+
import "antd/dist/antd.css";
|
|
5
|
+
import "./App.css";
|
|
6
6
|
|
|
7
7
|
interface IAppStates {
|
|
8
8
|
shortKey?: string;
|
|
@@ -12,61 +12,74 @@ interface IAppStates {
|
|
|
12
12
|
startBtnStyle?: React.CSSProperties;
|
|
13
13
|
endBtnText?: React.ReactNode;
|
|
14
14
|
endBtnStyle?: React.CSSProperties;
|
|
15
|
-
recording?: boolean
|
|
16
|
-
url?: string
|
|
15
|
+
recording?: boolean;
|
|
16
|
+
url?: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const initState: IAppStates = {
|
|
20
|
-
shortKey:
|
|
20
|
+
shortKey: "Alt+Shift+D",
|
|
21
21
|
preview: true,
|
|
22
22
|
videoOptions: {
|
|
23
23
|
frameRate: 60,
|
|
24
24
|
width: 1920,
|
|
25
|
-
height: 1080
|
|
25
|
+
height: 1080,
|
|
26
26
|
},
|
|
27
|
-
startBtnText:
|
|
28
|
-
startBtnStyle: { color:
|
|
29
|
-
endBtnText:
|
|
30
|
-
endBtnStyle: { color:
|
|
31
|
-
recording: false
|
|
32
|
-
}
|
|
27
|
+
startBtnText: "🛫 开始",
|
|
28
|
+
startBtnStyle: { color: "#48bfa7" },
|
|
29
|
+
endBtnText: "🛑 结束",
|
|
30
|
+
endBtnStyle: { color: "#FF0000" },
|
|
31
|
+
recording: false,
|
|
32
|
+
};
|
|
33
33
|
|
|
34
34
|
interface IBlockProps {
|
|
35
|
-
title?: React.ReactNode
|
|
36
|
-
style?: React.CSSProperties
|
|
37
|
-
bordered?: boolean
|
|
38
|
-
state: IAppStates
|
|
39
|
-
onPreviewChange?: (checked: boolean) => void
|
|
40
|
-
onShortKeyChange?: (key: string) => void
|
|
41
|
-
onStartBtnTextChange?: React.ChangeEventHandler<HTMLInputElement
|
|
42
|
-
onEndBtnTextChange?: React.ChangeEventHandler<HTMLInputElement
|
|
43
|
-
onStartBtnStyleChange?: React.ChangeEventHandler<HTMLInputElement
|
|
44
|
-
onEndBtnStyleChange?: React.ChangeEventHandler<HTMLInputElement
|
|
45
|
-
onVideoOptionsFrameRateChange?: (frameRate: string) => void
|
|
46
|
-
onVideoOptionsAspectRatioChange?: (aspectRatio: string) => void
|
|
35
|
+
title?: React.ReactNode;
|
|
36
|
+
style?: React.CSSProperties;
|
|
37
|
+
bordered?: boolean;
|
|
38
|
+
state: IAppStates;
|
|
39
|
+
onPreviewChange?: (checked: boolean) => void;
|
|
40
|
+
onShortKeyChange?: (key: string) => void;
|
|
41
|
+
onStartBtnTextChange?: React.ChangeEventHandler<HTMLInputElement>;
|
|
42
|
+
onEndBtnTextChange?: React.ChangeEventHandler<HTMLInputElement>;
|
|
43
|
+
onStartBtnStyleChange?: React.ChangeEventHandler<HTMLInputElement>;
|
|
44
|
+
onEndBtnStyleChange?: React.ChangeEventHandler<HTMLInputElement>;
|
|
45
|
+
onVideoOptionsFrameRateChange?: (frameRate: string) => void;
|
|
46
|
+
onVideoOptionsAspectRatioChange?: (aspectRatio: string) => void;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const staticStyle: React.CSSProperties = {
|
|
50
|
-
width:
|
|
51
|
-
display:
|
|
52
|
-
justifyContent:
|
|
53
|
-
alignItems:
|
|
54
|
-
}
|
|
50
|
+
width: "100%",
|
|
51
|
+
display: "flex",
|
|
52
|
+
justifyContent: "space-around",
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
};
|
|
55
55
|
|
|
56
56
|
const fromStyle: React.CSSProperties = {
|
|
57
|
-
width:
|
|
58
|
-
display:
|
|
59
|
-
flexDirection:
|
|
60
|
-
justifyContent:
|
|
61
|
-
}
|
|
57
|
+
width: "50%",
|
|
58
|
+
display: "flex",
|
|
59
|
+
flexDirection: "column",
|
|
60
|
+
justifyContent: "center",
|
|
61
|
+
};
|
|
62
62
|
|
|
63
|
-
const Line: React.FC<{ style?: React.CSSProperties }> = ({ style }) =>
|
|
64
|
-
<div
|
|
63
|
+
const Line: React.FC<{ style?: React.CSSProperties }> = ({ style }) => (
|
|
64
|
+
<div
|
|
65
|
+
style={{
|
|
66
|
+
width: "100%",
|
|
67
|
+
height: "1px",
|
|
68
|
+
background: "#eee",
|
|
69
|
+
margin: "30px auto",
|
|
70
|
+
...style,
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
65
75
|
|
|
66
|
-
const GraySmallFont: React.FC<
|
|
67
|
-
<
|
|
76
|
+
const GraySmallFont: React.FC<
|
|
77
|
+
React.PropsWithChildren<{ style?: React.CSSProperties }>
|
|
78
|
+
> = (
|
|
79
|
+
{ style, children },
|
|
80
|
+
) => <span style={{ fontSize: 12, color: "#999", ...style }}>{children}</span>;
|
|
68
81
|
|
|
69
|
-
const Block: React.FC<IBlockProps
|
|
82
|
+
const Block: React.FC<React.PropsWithChildren<IBlockProps>> = (props) => {
|
|
70
83
|
const {
|
|
71
84
|
title,
|
|
72
85
|
style,
|
|
@@ -79,22 +92,36 @@ const Block: React.FC<IBlockProps> = (props) => {
|
|
|
79
92
|
onStartBtnStyleChange,
|
|
80
93
|
onEndBtnStyleChange,
|
|
81
94
|
onVideoOptionsFrameRateChange,
|
|
82
|
-
onVideoOptionsAspectRatioChange
|
|
83
|
-
} = props
|
|
95
|
+
onVideoOptionsAspectRatioChange,
|
|
96
|
+
} = props;
|
|
84
97
|
|
|
85
98
|
return (
|
|
86
|
-
<Card
|
|
99
|
+
<Card
|
|
100
|
+
title={title}
|
|
101
|
+
variant={bordered ? "outlined" : "borderless"}
|
|
102
|
+
style={{ width: "90%", margin: "20px auto", ...style }}
|
|
103
|
+
>
|
|
87
104
|
<div style={staticStyle}>
|
|
88
105
|
<div style={fromStyle}>
|
|
89
|
-
<Row gutter={{ md: 24 }} style={{ marginBottom:
|
|
90
|
-
<Col md={12}
|
|
91
|
-
|
|
106
|
+
<Row gutter={{ md: 24 }} style={{ marginBottom: "20px" }}>
|
|
107
|
+
<Col md={12}>
|
|
108
|
+
预览<GraySmallFont>(preview)</GraySmallFont>
|
|
109
|
+
</Col>
|
|
110
|
+
<Col md={12}>
|
|
111
|
+
<Switch checked={state.preview} onChange={onPreviewChange} />
|
|
112
|
+
</Col>
|
|
92
113
|
</Row>
|
|
93
|
-
<Row gutter={{ md: 24 }} style={{ marginBottom:
|
|
94
|
-
<Col md={12}
|
|
114
|
+
<Row gutter={{ md: 24 }} style={{ marginBottom: "20px" }}>
|
|
115
|
+
<Col md={12}>
|
|
116
|
+
快捷键<GraySmallFont>(shortKey)</GraySmallFont>
|
|
117
|
+
</Col>
|
|
95
118
|
<Col md={12}>
|
|
96
|
-
<Select
|
|
97
|
-
|
|
119
|
+
<Select
|
|
120
|
+
value={state.shortKey}
|
|
121
|
+
style={{ width: "100%" }}
|
|
122
|
+
onChange={onShortKeyChange}
|
|
123
|
+
>
|
|
124
|
+
<Select.Option value="Alt+Shift+D">Alt+Shift+D</Select.Option>
|
|
98
125
|
<Select.Option value="Alt+Shift+Q">Alt+Shift+Q</Select.Option>
|
|
99
126
|
<Select.Option value="Alt+1">Alt+1</Select.Option>
|
|
100
127
|
<Select.Option value="Alt+2">Alt+2</Select.Option>
|
|
@@ -106,57 +133,98 @@ const Block: React.FC<IBlockProps> = (props) => {
|
|
|
106
133
|
</Select>
|
|
107
134
|
</Col>
|
|
108
135
|
</Row>
|
|
109
|
-
<Row gutter={{ md: 24 }} style={{ marginBottom:
|
|
110
|
-
<Col md={12}
|
|
111
|
-
|
|
136
|
+
<Row gutter={{ md: 24 }} style={{ marginBottom: "20px" }}>
|
|
137
|
+
<Col md={12}>
|
|
138
|
+
开始按钮文本<GraySmallFont>(startBtnText)</GraySmallFont>
|
|
139
|
+
</Col>
|
|
140
|
+
<Col md={12}>
|
|
141
|
+
<Input
|
|
142
|
+
value={state.startBtnText as string}
|
|
143
|
+
onChange={onStartBtnTextChange}
|
|
144
|
+
/>
|
|
145
|
+
</Col>
|
|
112
146
|
</Row>
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
147
|
+
{state.startBtnStyle && (
|
|
148
|
+
<Row gutter={{ md: 24 }} style={{ marginBottom: "20px" }}>
|
|
149
|
+
<Col md={12}>
|
|
150
|
+
开始按钮文本颜色<GraySmallFont>
|
|
151
|
+
(startBtnStyle.color)
|
|
152
|
+
</GraySmallFont>
|
|
153
|
+
</Col>
|
|
154
|
+
<Col md={12}>
|
|
155
|
+
<input
|
|
156
|
+
type="color"
|
|
157
|
+
value={state.startBtnStyle?.color}
|
|
158
|
+
onChange={onStartBtnStyleChange}
|
|
159
|
+
/>
|
|
160
|
+
</Col>
|
|
161
|
+
</Row>
|
|
162
|
+
)}
|
|
163
|
+
<Row gutter={{ md: 24 }} style={{ marginBottom: "20px" }}>
|
|
164
|
+
<Col md={12}>
|
|
165
|
+
结束按钮文本<GraySmallFont>(endBtnText)</GraySmallFont>
|
|
166
|
+
</Col>
|
|
167
|
+
<Col md={12}>
|
|
168
|
+
<Input
|
|
169
|
+
value={state.endBtnText as string}
|
|
170
|
+
onChange={onEndBtnTextChange}
|
|
171
|
+
/>
|
|
172
|
+
</Col>
|
|
124
173
|
</Row>
|
|
125
|
-
{
|
|
126
|
-
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
174
|
+
{state.endBtnStyle && (
|
|
175
|
+
<Row gutter={{ md: 24 }} style={{ marginBottom: "20px" }}>
|
|
176
|
+
<Col md={12}>
|
|
177
|
+
结束按钮文本颜色<GraySmallFont>
|
|
178
|
+
(startBtnStyle.color)
|
|
179
|
+
</GraySmallFont>
|
|
180
|
+
</Col>
|
|
181
|
+
<Col md={12}>
|
|
182
|
+
<input
|
|
183
|
+
type="color"
|
|
184
|
+
value={state.endBtnStyle?.color}
|
|
185
|
+
onChange={onEndBtnStyleChange}
|
|
186
|
+
/>
|
|
187
|
+
</Col>
|
|
188
|
+
</Row>
|
|
189
|
+
)}
|
|
190
|
+
<Row gutter={{ md: 24 }} style={{ marginBottom: "20px" }}>
|
|
191
|
+
<Col md={12}>
|
|
192
|
+
视频FPS设置<GraySmallFont>
|
|
193
|
+
(videoOptions.frameRate)
|
|
194
|
+
</GraySmallFont>
|
|
195
|
+
</Col>
|
|
135
196
|
<Col md={12}>
|
|
136
197
|
<Select
|
|
137
198
|
disabled={state.recording}
|
|
138
|
-
value={
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
199
|
+
value={typeof state.videoOptions?.frameRate === "number" &&
|
|
200
|
+
!isNaN(state.videoOptions?.frameRate)
|
|
201
|
+
? String(state.videoOptions?.frameRate)
|
|
202
|
+
: "60"}
|
|
203
|
+
style={{ width: "100%" }}
|
|
204
|
+
onChange={onVideoOptionsFrameRateChange}
|
|
205
|
+
>
|
|
144
206
|
<Select.Option value={60}>60</Select.Option>
|
|
145
207
|
<Select.Option value={30}>30</Select.Option>
|
|
146
208
|
<Select.Option value={24}>24</Select.Option>
|
|
147
209
|
</Select>
|
|
148
210
|
</Col>
|
|
149
211
|
</Row>
|
|
150
|
-
<Row gutter={{ md: 24 }} style={{ marginBottom:
|
|
151
|
-
<Col md={12}
|
|
212
|
+
<Row gutter={{ md: 24 }} style={{ marginBottom: "20px" }}>
|
|
213
|
+
<Col md={12}>
|
|
214
|
+
视频分辨率设置<GraySmallFont>
|
|
215
|
+
(videoOptions.width/height)
|
|
216
|
+
</GraySmallFont>
|
|
217
|
+
</Col>
|
|
152
218
|
<Col md={12}>
|
|
153
219
|
<Select
|
|
154
220
|
disabled={state.recording}
|
|
155
221
|
value={`${state.videoOptions?.width}×${state.videoOptions?.height}`}
|
|
156
|
-
style={{ width:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
<Select.Option value={
|
|
222
|
+
style={{ width: "100%" }}
|
|
223
|
+
onChange={onVideoOptionsAspectRatioChange}
|
|
224
|
+
>
|
|
225
|
+
<Select.Option value={"1920×1080"}>1920×1080</Select.Option>
|
|
226
|
+
<Select.Option value={"1280×720"}>1280×720</Select.Option>
|
|
227
|
+
<Select.Option value={"720×480"}>720×480</Select.Option>
|
|
160
228
|
</Select>
|
|
161
229
|
</Col>
|
|
162
230
|
</Row>
|
|
@@ -164,16 +232,15 @@ const Block: React.FC<IBlockProps> = (props) => {
|
|
|
164
232
|
{props.children}
|
|
165
233
|
</div>
|
|
166
234
|
</Card>
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
|
|
235
|
+
);
|
|
236
|
+
};
|
|
170
237
|
|
|
171
238
|
const handleAction = {
|
|
172
239
|
setState: (state: IAppStates, payload?: Partial<IAppStates>) => {
|
|
173
240
|
return {
|
|
174
241
|
...state,
|
|
175
|
-
...payload
|
|
176
|
-
}
|
|
242
|
+
...payload,
|
|
243
|
+
};
|
|
177
244
|
},
|
|
178
245
|
setStartBtnStyle: (state: IAppStates, payload?: React.CSSProperties) => {
|
|
179
246
|
return {
|
|
@@ -181,8 +248,8 @@ const handleAction = {
|
|
|
181
248
|
startBtnStyle: {
|
|
182
249
|
...state.startBtnStyle,
|
|
183
250
|
...payload,
|
|
184
|
-
}
|
|
185
|
-
}
|
|
251
|
+
},
|
|
252
|
+
};
|
|
186
253
|
},
|
|
187
254
|
setEndBtnStyle: (state: IAppStates, payload?: React.CSSProperties) => {
|
|
188
255
|
return {
|
|
@@ -190,8 +257,8 @@ const handleAction = {
|
|
|
190
257
|
endBtnStyle: {
|
|
191
258
|
...state.endBtnStyle,
|
|
192
259
|
...payload,
|
|
193
|
-
}
|
|
194
|
-
}
|
|
260
|
+
},
|
|
261
|
+
};
|
|
195
262
|
},
|
|
196
263
|
setVideoOptions: (state: IAppStates, payload?: MediaTrackConstraints) => {
|
|
197
264
|
return {
|
|
@@ -199,33 +266,39 @@ const handleAction = {
|
|
|
199
266
|
videoOptions: {
|
|
200
267
|
...state.videoOptions,
|
|
201
268
|
...payload,
|
|
202
|
-
}
|
|
203
|
-
}
|
|
269
|
+
},
|
|
270
|
+
};
|
|
204
271
|
},
|
|
205
|
-
}
|
|
272
|
+
};
|
|
206
273
|
|
|
207
|
-
type TActionType = keyof typeof handleAction
|
|
208
|
-
type TAction = TActionType | {
|
|
274
|
+
type TActionType = keyof typeof handleAction;
|
|
275
|
+
type TAction = TActionType | {
|
|
276
|
+
type: TActionType;
|
|
277
|
+
payload: Partial<IAppStates> | React.CSSProperties | MediaTrackConstraints;
|
|
278
|
+
};
|
|
209
279
|
|
|
210
280
|
const reducer = (state: IAppStates, action: TAction): IAppStates => {
|
|
211
|
-
const actionType = typeof action ===
|
|
212
|
-
const payload = typeof action ===
|
|
213
|
-
const res: IAppStates | undefined = safeCallback<any, IAppStates>(
|
|
214
|
-
|
|
215
|
-
|
|
281
|
+
const actionType = typeof action === "string" ? action : action.type;
|
|
282
|
+
const payload = typeof action === "string" ? undefined : action.payload;
|
|
283
|
+
const res: IAppStates | undefined = safeCallback<any, IAppStates>(
|
|
284
|
+
handleAction[actionType],
|
|
285
|
+
state,
|
|
286
|
+
payload,
|
|
287
|
+
);
|
|
288
|
+
return res ?? state;
|
|
289
|
+
};
|
|
216
290
|
|
|
217
291
|
const SimpUse: React.FC<{}> = (props) => {
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
292
|
+
const [state, dispatch] = React.useReducer(reducer, initState);
|
|
293
|
+
const setState = (payload: Partial<IAppStates>) =>
|
|
294
|
+
dispatch({
|
|
295
|
+
type: "setState",
|
|
296
|
+
payload,
|
|
297
|
+
});
|
|
225
298
|
|
|
226
299
|
const recordingEnd = (url: string) => {
|
|
227
300
|
setState({ recording: false, url });
|
|
228
|
-
}
|
|
301
|
+
};
|
|
229
302
|
|
|
230
303
|
return (
|
|
231
304
|
<Block
|
|
@@ -234,29 +307,37 @@ const SimpUse: React.FC<{}> = (props) => {
|
|
|
234
307
|
state={state}
|
|
235
308
|
onPreviewChange={(preview) => setState({ preview })}
|
|
236
309
|
onShortKeyChange={(shortKey) => setState({ shortKey })}
|
|
237
|
-
onStartBtnTextChange={({ target: { value: startBtnText } }) =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
type:
|
|
243
|
-
|
|
244
|
-
|
|
310
|
+
onStartBtnTextChange={({ target: { value: startBtnText } }) =>
|
|
311
|
+
setState({ startBtnText })}
|
|
312
|
+
onEndBtnTextChange={({ target: { value: endBtnText } }) =>
|
|
313
|
+
setState({ endBtnText })}
|
|
314
|
+
onStartBtnStyleChange={({ target: { value: color } }) =>
|
|
315
|
+
dispatch({ type: "setStartBtnStyle", payload: { color } })}
|
|
316
|
+
onEndBtnStyleChange={({ target: { value: color } }) =>
|
|
317
|
+
dispatch({ type: "setEndBtnStyle", payload: { color } })}
|
|
318
|
+
onVideoOptionsFrameRateChange={(frameRate) =>
|
|
319
|
+
dispatch({
|
|
320
|
+
type: "setVideoOptions",
|
|
321
|
+
payload: { frameRate: Number(frameRate) },
|
|
322
|
+
})}
|
|
245
323
|
onVideoOptionsAspectRatioChange={(aspectRatio) => {
|
|
246
|
-
const [width = 1920, height = 1080] = aspectRatio.split(
|
|
324
|
+
const [width = 1920, height = 1080] = aspectRatio.split("×").map(
|
|
325
|
+
Number,
|
|
326
|
+
);
|
|
247
327
|
dispatch({
|
|
248
|
-
type:
|
|
249
|
-
payload: { width, height }
|
|
250
|
-
})
|
|
328
|
+
type: "setVideoOptions",
|
|
329
|
+
payload: { width, height },
|
|
330
|
+
});
|
|
251
331
|
}}
|
|
252
332
|
>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
333
|
+
<div
|
|
334
|
+
style={{
|
|
335
|
+
display: "flex",
|
|
336
|
+
flexDirection: "column",
|
|
337
|
+
justifyContent: "center",
|
|
338
|
+
alignItems: "center",
|
|
339
|
+
}}
|
|
340
|
+
>
|
|
260
341
|
<ScreenRecorder
|
|
261
342
|
preview={state.preview}
|
|
262
343
|
shortKey={state.shortKey}
|
|
@@ -268,27 +349,25 @@ const SimpUse: React.FC<{}> = (props) => {
|
|
|
268
349
|
videoOptions={state.videoOptions}
|
|
269
350
|
onRecordingStart={() => setState({ recording: true })}
|
|
270
351
|
/>
|
|
271
|
-
{
|
|
272
|
-
state.url &&
|
|
352
|
+
{state.url &&
|
|
273
353
|
!state.recording &&
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
354
|
+
(
|
|
355
|
+
<video
|
|
356
|
+
width="500"
|
|
357
|
+
muted
|
|
358
|
+
autoPlay
|
|
359
|
+
controls
|
|
360
|
+
src={state.url}
|
|
361
|
+
style={{ marginTop: "30px", border: "1px solid #000" }}
|
|
362
|
+
>
|
|
363
|
+
</video>
|
|
364
|
+
)}
|
|
283
365
|
</div>
|
|
284
|
-
|
|
285
366
|
</Block>
|
|
286
|
-
)
|
|
287
|
-
}
|
|
288
|
-
|
|
367
|
+
);
|
|
368
|
+
};
|
|
289
369
|
|
|
290
370
|
const CustomContent: React.FC<{}> = (props) => {
|
|
291
|
-
|
|
292
371
|
const [recording, setRecording] = React.useState(false);
|
|
293
372
|
const [url, setUrl] = React.useState<string | undefined>();
|
|
294
373
|
|
|
@@ -299,10 +378,11 @@ const CustomContent: React.FC<{}> = (props) => {
|
|
|
299
378
|
|
|
300
379
|
const customRecordingEnd = (url: string) => {
|
|
301
380
|
setRecording(false);
|
|
302
|
-
setUrl(url)
|
|
303
|
-
}
|
|
381
|
+
setUrl(url);
|
|
382
|
+
};
|
|
304
383
|
|
|
305
|
-
const startContent = (startEvent: Function) =>
|
|
384
|
+
const startContent = (startEvent: Function) =>
|
|
385
|
+
!recording &&
|
|
306
386
|
(
|
|
307
387
|
<div
|
|
308
388
|
title="开始录屏"
|
|
@@ -316,54 +396,88 @@ const CustomContent: React.FC<{}> = (props) => {
|
|
|
316
396
|
p-id="887"
|
|
317
397
|
width="50"
|
|
318
398
|
height="50"
|
|
319
|
-
|
|
399
|
+
>
|
|
400
|
+
<path
|
|
401
|
+
d="M325.43 185.5c-127.62 0-177.25 49.63-177.25 177.25v298.52c0 85.82 46.64 177.24 177.25 177.24h223.89c127.62 0 177.24-49.63 177.24-177.24V362.74c0-127.62-49.63-177.25-177.24-177.25H325.43z"
|
|
402
|
+
fill="#A4D4FF"
|
|
403
|
+
p-id="888"
|
|
404
|
+
>
|
|
405
|
+
</path>
|
|
406
|
+
<path
|
|
407
|
+
d="M493.31 488.87c-38.74 0-70.15-31.41-70.15-70.15s31.41-70.15 70.15-70.15 70.15 31.41 70.15 70.15-31.41 70.15-70.15 70.15z"
|
|
408
|
+
fill="#2B8CF7"
|
|
409
|
+
p-id="889"
|
|
410
|
+
>
|
|
411
|
+
</path>
|
|
412
|
+
<path
|
|
413
|
+
d="M781.06 308.27l-55.23 38.81c0.37 5.22 0.75 10.07 0.75 15.67v298.52c0 5.6-0.75 10.45-0.75 15.67l55.23 38.81c23.14 16.42 43.29 21.64 59.33 21.64 13.81 0 24.63-3.73 31.72-7.46 15.3-7.84 41.05-29.11 41.05-82.47V376.92c0-53.36-25.75-74.63-41.05-82.47-15.3-7.83-47.39-16.78-91.05 13.82z"
|
|
414
|
+
fill="#2B8CF7"
|
|
415
|
+
p-id="890"
|
|
416
|
+
>
|
|
417
|
+
</path>
|
|
320
418
|
</svg>
|
|
321
419
|
</div>
|
|
322
|
-
)
|
|
420
|
+
);
|
|
323
421
|
|
|
324
|
-
const endContent = (endEvent: Function) =>
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
<svg
|
|
331
|
-
viewBox="0 0 1024 1024"
|
|
332
|
-
version="1.1"
|
|
333
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
334
|
-
p-id="1050"
|
|
335
|
-
width="50"
|
|
336
|
-
height="50"
|
|
422
|
+
const endContent = (endEvent: Function) =>
|
|
423
|
+
recording && (
|
|
424
|
+
<div
|
|
425
|
+
title="结束录屏"
|
|
426
|
+
onClick={() => endEvent()}
|
|
427
|
+
className="icon"
|
|
337
428
|
>
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
429
|
+
<svg
|
|
430
|
+
viewBox="0 0 1024 1024"
|
|
431
|
+
version="1.1"
|
|
432
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
433
|
+
p-id="1050"
|
|
434
|
+
width="50"
|
|
435
|
+
height="50"
|
|
436
|
+
>
|
|
437
|
+
<path
|
|
438
|
+
d="M510.91 885.15c-206.08 0-373.15-167.06-373.15-373.15s167.06-373.15 373.15-373.15S884.05 305.92 884.05 512 716.99 885.15 510.91 885.15z"
|
|
439
|
+
fill="#A4D4FF"
|
|
440
|
+
p-id="1051"
|
|
441
|
+
>
|
|
442
|
+
</path>
|
|
443
|
+
<path
|
|
444
|
+
d="M557.88 669.84c61.94 0 111.94-50 111.94-111.94v-91.8c0-61.94-50-111.94-111.94-111.94h-91.79c-61.94 0-111.94 50-111.94 111.94v91.79c0 61.94 50 111.94 111.94 111.94h91.79z"
|
|
445
|
+
fill="#2B8CF7"
|
|
446
|
+
p-id="1052"
|
|
447
|
+
>
|
|
448
|
+
</path>
|
|
449
|
+
</svg>
|
|
450
|
+
</div>
|
|
451
|
+
);
|
|
342
452
|
|
|
343
|
-
const previewContent = (mediaStream: MediaStream) =>
|
|
453
|
+
const previewContent = (mediaStream: MediaStream) => (
|
|
344
454
|
<Video
|
|
345
455
|
muted
|
|
346
456
|
autoPlay
|
|
347
|
-
style={{ display:
|
|
457
|
+
style={{ display: "block" }}
|
|
348
458
|
width={500}
|
|
349
459
|
srcObject={mediaStream}
|
|
350
460
|
/>
|
|
461
|
+
);
|
|
351
462
|
|
|
352
463
|
return (
|
|
353
464
|
<Card
|
|
354
465
|
title="2.自定义视图"
|
|
355
466
|
bordered
|
|
356
467
|
style={{
|
|
357
|
-
width:
|
|
358
|
-
margin:
|
|
359
|
-
minHeight:
|
|
360
|
-
}}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
468
|
+
width: "90%",
|
|
469
|
+
margin: "20px auto",
|
|
470
|
+
minHeight: "500px",
|
|
471
|
+
}}
|
|
472
|
+
>
|
|
473
|
+
<div
|
|
474
|
+
style={{
|
|
475
|
+
display: "flex",
|
|
476
|
+
flexDirection: "column",
|
|
477
|
+
justifyContent: "center",
|
|
478
|
+
alignItems: "center",
|
|
479
|
+
}}
|
|
480
|
+
>
|
|
367
481
|
<ScreenRecorder
|
|
368
482
|
preview
|
|
369
483
|
onRecordingEnd={customRecordingEnd}
|
|
@@ -371,28 +485,29 @@ const CustomContent: React.FC<{}> = (props) => {
|
|
|
371
485
|
endContent={endContent}
|
|
372
486
|
previewContent={previewContent}
|
|
373
487
|
/>
|
|
374
|
-
{
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
488
|
+
{url && !recording &&
|
|
489
|
+
(
|
|
490
|
+
<video
|
|
491
|
+
width="500"
|
|
492
|
+
muted
|
|
493
|
+
autoPlay
|
|
494
|
+
controls
|
|
495
|
+
src={url}
|
|
496
|
+
style={{ marginTop: "30px", border: "1px solid #000" }}
|
|
497
|
+
>
|
|
498
|
+
</video>
|
|
499
|
+
)}
|
|
385
500
|
</div>
|
|
386
501
|
</Card>
|
|
387
|
-
)
|
|
388
|
-
}
|
|
502
|
+
);
|
|
503
|
+
};
|
|
389
504
|
|
|
390
505
|
export default () => {
|
|
391
506
|
return (
|
|
392
507
|
<>
|
|
393
508
|
<SimpUse />
|
|
394
|
-
<Line style={{ width:
|
|
509
|
+
<Line style={{ width: "90%" }} />
|
|
395
510
|
<CustomContent />
|
|
396
511
|
</>
|
|
397
|
-
)
|
|
398
|
-
}
|
|
512
|
+
);
|
|
513
|
+
};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
IScreenRecorderOptions,
|
|
4
|
+
safeCallback,
|
|
5
|
+
ScreenRecorder,
|
|
6
|
+
} from "screen-recorder-base";
|
|
7
|
+
import { bindkey } from "@w-xuefeng/bindkey";
|
|
8
|
+
import Video from "./Video";
|
|
5
9
|
|
|
6
10
|
export interface IScreenRecorderComponentProps {
|
|
7
11
|
shortKey?: string;
|
|
@@ -11,96 +15,114 @@ export interface IScreenRecorderComponentProps {
|
|
|
11
15
|
startBtnStyle?: React.CSSProperties;
|
|
12
16
|
endBtnText?: React.ReactNode;
|
|
13
17
|
endBtnStyle?: React.CSSProperties;
|
|
14
|
-
startContent?: (startEvent: Function, endEvent: Function) => React.ReactNode
|
|
15
|
-
endContent?: (endEvent: Function, startEvent: Function) => React.ReactNode
|
|
16
|
-
previewContent?: (mediaStream: MediaStream) => React.ReactNode
|
|
17
|
-
onRecordingStart?: (mediaStream: MediaStream) => void
|
|
18
|
-
onRecordingEnd?: (blobUrl: string, fixedBlob: Blob) => void
|
|
19
|
-
onRecordingUnsupport?: () => void
|
|
20
|
-
onRecordingError?: (err: unknown) => void
|
|
21
|
-
}
|
|
18
|
+
startContent?: (startEvent: Function, endEvent: Function) => React.ReactNode;
|
|
19
|
+
endContent?: (endEvent: Function, startEvent: Function) => React.ReactNode;
|
|
20
|
+
previewContent?: (mediaStream: MediaStream) => React.ReactNode;
|
|
21
|
+
onRecordingStart?: (mediaStream: MediaStream) => void;
|
|
22
|
+
onRecordingEnd?: (blobUrl: string, fixedBlob: Blob) => void;
|
|
23
|
+
onRecordingUnsupport?: () => void;
|
|
24
|
+
onRecordingError?: (err: unknown) => void;
|
|
25
|
+
}
|
|
22
26
|
|
|
23
27
|
export interface IScreenRecorderComponentStates {
|
|
24
|
-
error: boolean
|
|
25
|
-
unsupported: boolean
|
|
26
|
-
recording: boolean
|
|
27
|
-
previewMediaStream: null | MediaStream
|
|
28
|
+
error: boolean;
|
|
29
|
+
unsupported: boolean;
|
|
30
|
+
recording: boolean;
|
|
31
|
+
previewMediaStream: null | MediaStream;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
const defaultBtnStyle: React.CSSProperties = {
|
|
31
|
-
margin:
|
|
32
|
-
cursor:
|
|
33
|
-
boxShadow:
|
|
34
|
-
fontSize:
|
|
35
|
-
lineHeight:
|
|
36
|
-
fontFamily:
|
|
37
|
-
height:
|
|
38
|
-
display:
|
|
39
|
-
alignItems:
|
|
40
|
-
justifyContent:
|
|
41
|
-
userSelect:
|
|
42
|
-
border:
|
|
43
|
-
borderRadius:
|
|
44
|
-
padding:
|
|
35
|
+
margin: "0 16px",
|
|
36
|
+
cursor: "pointer",
|
|
37
|
+
boxShadow: "none",
|
|
38
|
+
fontSize: "14px",
|
|
39
|
+
lineHeight: "20px",
|
|
40
|
+
fontFamily: "Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif",
|
|
41
|
+
height: "32px",
|
|
42
|
+
display: "inline-flex",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
justifyContent: "center",
|
|
45
|
+
userSelect: "none",
|
|
46
|
+
border: "0 solid transparent",
|
|
47
|
+
borderRadius: "2px",
|
|
48
|
+
padding: "6px 12px",
|
|
45
49
|
fontWeight: 600,
|
|
46
|
-
outline:
|
|
47
|
-
verticalAlign:
|
|
48
|
-
whiteSpace:
|
|
49
|
-
color:
|
|
50
|
-
}
|
|
50
|
+
outline: "none",
|
|
51
|
+
verticalAlign: "middle",
|
|
52
|
+
whiteSpace: "nowrap",
|
|
53
|
+
color: "rgb(80, 86, 94)",
|
|
54
|
+
};
|
|
51
55
|
|
|
52
56
|
const defaultPreviewStyle: React.CSSProperties = {
|
|
53
|
-
position:
|
|
54
|
-
border:
|
|
57
|
+
position: "fixed",
|
|
58
|
+
border: "1px solid #666",
|
|
55
59
|
zIndex: 9999,
|
|
56
|
-
cursor:
|
|
60
|
+
cursor: "move",
|
|
57
61
|
top: 5,
|
|
58
|
-
right: 5
|
|
59
|
-
}
|
|
62
|
+
right: 5,
|
|
63
|
+
};
|
|
60
64
|
|
|
61
|
-
const rShow = (
|
|
65
|
+
const rShow = (
|
|
66
|
+
c: boolean,
|
|
67
|
+
styles?: React.CSSProperties,
|
|
68
|
+
): React.CSSProperties => (
|
|
62
69
|
{
|
|
63
70
|
...styles,
|
|
64
|
-
...(c ? {} : { display:
|
|
71
|
+
...(c ? {} : { display: "none" }),
|
|
65
72
|
}
|
|
66
|
-
)
|
|
73
|
+
);
|
|
67
74
|
|
|
68
|
-
const AorB = (c: boolean, A: React.ReactNode, B: React.ReactNode = null) =>
|
|
75
|
+
const AorB = (c: boolean, A: React.ReactNode, B: React.ReactNode = null) =>
|
|
76
|
+
c ? A : B;
|
|
69
77
|
|
|
70
78
|
const initState: IScreenRecorderComponentStates = {
|
|
71
79
|
error: false,
|
|
72
80
|
unsupported: false,
|
|
73
81
|
recording: false,
|
|
74
82
|
previewMediaStream: null,
|
|
75
|
-
}
|
|
83
|
+
};
|
|
76
84
|
|
|
77
85
|
const handleAction = {
|
|
78
|
-
setState: (
|
|
86
|
+
setState: (
|
|
87
|
+
state: IScreenRecorderComponentStates,
|
|
88
|
+
payload?: Partial<IScreenRecorderComponentStates>,
|
|
89
|
+
) => {
|
|
79
90
|
return {
|
|
80
91
|
...state,
|
|
81
|
-
...payload
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
92
|
+
...payload,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
};
|
|
85
96
|
|
|
86
|
-
type TActionType = keyof typeof handleAction
|
|
87
|
-
type TAction = TActionType | {
|
|
97
|
+
type TActionType = keyof typeof handleAction;
|
|
98
|
+
type TAction = TActionType | {
|
|
99
|
+
type: TActionType;
|
|
100
|
+
payload: Partial<IScreenRecorderComponentStates>;
|
|
101
|
+
};
|
|
88
102
|
|
|
89
|
-
const reducer = (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
103
|
+
const reducer = (
|
|
104
|
+
state: IScreenRecorderComponentStates,
|
|
105
|
+
action: TAction,
|
|
106
|
+
): IScreenRecorderComponentStates => {
|
|
107
|
+
const actionType = typeof action === "string" ? action : action.type;
|
|
108
|
+
const payload = typeof action === "string" ? undefined : action.payload;
|
|
109
|
+
const res: IScreenRecorderComponentStates | undefined = safeCallback<
|
|
110
|
+
any,
|
|
111
|
+
IScreenRecorderComponentStates
|
|
112
|
+
>(handleAction[actionType], state, payload);
|
|
113
|
+
return res ?? state;
|
|
114
|
+
};
|
|
95
115
|
|
|
96
|
-
const ScreenRecorderComponent: React.FC<IScreenRecorderComponentProps> = (
|
|
116
|
+
const ScreenRecorderComponent: React.FC<IScreenRecorderComponentProps> = (
|
|
117
|
+
props,
|
|
118
|
+
) => {
|
|
97
119
|
const {
|
|
98
120
|
shortKey,
|
|
99
121
|
preview = false,
|
|
100
122
|
videoOptions,
|
|
101
|
-
startBtnText =
|
|
123
|
+
startBtnText = "开始录屏",
|
|
102
124
|
startBtnStyle,
|
|
103
|
-
endBtnText =
|
|
125
|
+
endBtnText = "结束录屏",
|
|
104
126
|
endBtnStyle,
|
|
105
127
|
startContent,
|
|
106
128
|
endContent,
|
|
@@ -109,85 +131,86 @@ const ScreenRecorderComponent: React.FC<IScreenRecorderComponentProps> = (props)
|
|
|
109
131
|
onRecordingEnd,
|
|
110
132
|
onRecordingUnsupport,
|
|
111
133
|
onRecordingError,
|
|
112
|
-
} = props
|
|
134
|
+
} = props;
|
|
113
135
|
|
|
114
|
-
|
|
115
|
-
const [state, dispatch] = React.useReducer(reducer, initState)
|
|
136
|
+
const [state, dispatch] = React.useReducer(reducer, initState);
|
|
116
137
|
|
|
117
138
|
const previewDefaultWidth = 300;
|
|
118
139
|
|
|
119
|
-
const setState = (payload: Partial<IScreenRecorderComponentStates>) =>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
140
|
+
const setState = (payload: Partial<IScreenRecorderComponentStates>) =>
|
|
141
|
+
dispatch({
|
|
142
|
+
type: "setState",
|
|
143
|
+
payload,
|
|
144
|
+
});
|
|
123
145
|
|
|
124
|
-
const initPreview = (mediaStream: MediaStream) =>
|
|
146
|
+
const initPreview = (mediaStream: MediaStream) =>
|
|
147
|
+
setState({ previewMediaStream: mediaStream });
|
|
125
148
|
|
|
126
149
|
const options: IScreenRecorderOptions = {
|
|
127
150
|
onUnsupported: () => {
|
|
128
151
|
setState({
|
|
129
152
|
unsupported: true,
|
|
130
153
|
recording: false,
|
|
131
|
-
error: false
|
|
132
|
-
})
|
|
154
|
+
error: false,
|
|
155
|
+
});
|
|
133
156
|
safeCallback(onRecordingUnsupport);
|
|
134
157
|
},
|
|
135
158
|
onRecordStart: (mediaStream: MediaStream) => {
|
|
136
159
|
setState({
|
|
137
160
|
recording: true,
|
|
138
|
-
error: false
|
|
139
|
-
})
|
|
161
|
+
error: false,
|
|
162
|
+
});
|
|
140
163
|
initPreview(mediaStream);
|
|
141
164
|
safeCallback(onRecordingStart, mediaStream);
|
|
142
165
|
},
|
|
143
166
|
onError: (err) => {
|
|
144
167
|
setState({
|
|
145
168
|
error: true,
|
|
146
|
-
recording: false
|
|
147
|
-
})
|
|
169
|
+
recording: false,
|
|
170
|
+
});
|
|
148
171
|
safeCallback(onRecordingError, err);
|
|
149
172
|
},
|
|
150
173
|
onRecordEnd: (blobUrl: string, fixedBlob: Blob) => {
|
|
151
174
|
setState({
|
|
152
175
|
recording: false,
|
|
153
|
-
error: false
|
|
154
|
-
})
|
|
176
|
+
error: false,
|
|
177
|
+
});
|
|
155
178
|
safeCallback(onRecordingEnd, blobUrl, fixedBlob);
|
|
156
179
|
},
|
|
157
180
|
timeSlice: 1000,
|
|
158
181
|
videoOptions,
|
|
159
182
|
};
|
|
160
183
|
|
|
161
|
-
const screenRecorder = React.useRef(ScreenRecorder.createSR(options))
|
|
184
|
+
const screenRecorder = React.useRef(ScreenRecorder.createSR(options));
|
|
162
185
|
|
|
163
186
|
const bindShortKey = () => {
|
|
164
|
-
if (shortKey && shortKey.toUpperCase() !==
|
|
187
|
+
if (shortKey && shortKey.toUpperCase() !== "ESC") {
|
|
165
188
|
bindkey.add(shortKey, start, { target: globalThis });
|
|
166
|
-
bindkey.add(
|
|
167
|
-
console.info(`[BindKey] ${shortKey} to start`)
|
|
168
|
-
console.info(`[BindKey] ESC to end`)
|
|
189
|
+
bindkey.add("ESC", end, { target: globalThis });
|
|
190
|
+
console.info(`[BindKey] ${shortKey} to start`);
|
|
191
|
+
console.info(`[BindKey] ESC to end`);
|
|
169
192
|
}
|
|
170
|
-
}
|
|
193
|
+
};
|
|
171
194
|
|
|
172
195
|
const removeShortKey = () => {
|
|
173
|
-
shortKey && (bindkey.remove(shortKey), bindkey.remove(
|
|
174
|
-
console.info(`[RemoveKey] ${shortKey}`)
|
|
175
|
-
console.info(`[RemoveKey] ESC`)
|
|
176
|
-
}
|
|
196
|
+
shortKey && (bindkey.remove(shortKey), bindkey.remove("ESC"));
|
|
197
|
+
console.info(`[RemoveKey] ${shortKey}`);
|
|
198
|
+
console.info(`[RemoveKey] ESC`);
|
|
199
|
+
};
|
|
177
200
|
|
|
178
201
|
React.useEffect(() => {
|
|
179
202
|
state.recording &&
|
|
180
203
|
screenRecorder.current &&
|
|
181
204
|
screenRecorder.current.mediaStream &&
|
|
182
|
-
initPreview(screenRecorder.current.mediaStream)
|
|
183
|
-
}, [preview])
|
|
205
|
+
initPreview(screenRecorder.current.mediaStream);
|
|
206
|
+
}, [preview]);
|
|
184
207
|
|
|
185
208
|
React.useEffect(() => {
|
|
186
|
-
bindShortKey()
|
|
209
|
+
bindShortKey();
|
|
187
210
|
return () => {
|
|
188
|
-
removeShortKey()
|
|
189
|
-
}
|
|
190
|
-
}, [shortKey])
|
|
211
|
+
removeShortKey();
|
|
212
|
+
};
|
|
213
|
+
}, [shortKey]);
|
|
191
214
|
|
|
192
215
|
const start = () => {
|
|
193
216
|
screenRecorder.current?.startRecording();
|
|
@@ -199,57 +222,55 @@ const ScreenRecorderComponent: React.FC<IScreenRecorderComponentProps> = (props)
|
|
|
199
222
|
|
|
200
223
|
return (
|
|
201
224
|
<div>
|
|
202
|
-
{
|
|
225
|
+
{AorB(
|
|
226
|
+
!startContent && !state.recording,
|
|
227
|
+
<button
|
|
228
|
+
onClick={start}
|
|
229
|
+
style={{ ...defaultBtnStyle, ...startBtnStyle }}
|
|
230
|
+
>
|
|
231
|
+
{startBtnText}
|
|
232
|
+
</button>,
|
|
203
233
|
AorB(
|
|
204
|
-
!
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
{
|
|
234
|
+
!state.recording,
|
|
235
|
+
safeCallback(startContent, start, end),
|
|
236
|
+
),
|
|
237
|
+
)}
|
|
238
|
+
{AorB(
|
|
239
|
+
!endContent && state.recording,
|
|
240
|
+
<button
|
|
241
|
+
onClick={end}
|
|
242
|
+
style={{ ...defaultBtnStyle, ...endBtnStyle }}
|
|
243
|
+
>
|
|
244
|
+
{endBtnText}
|
|
245
|
+
</button>,
|
|
218
246
|
AorB(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
247
|
+
state.recording,
|
|
248
|
+
safeCallback(endContent, end, start),
|
|
249
|
+
),
|
|
250
|
+
)}
|
|
251
|
+
{AorB(
|
|
252
|
+
preview && !previewContent,
|
|
253
|
+
<Video
|
|
254
|
+
draggable
|
|
255
|
+
muted
|
|
256
|
+
autoPlay
|
|
257
|
+
width={previewDefaultWidth}
|
|
258
|
+
srcObject={state.previewMediaStream}
|
|
259
|
+
dragWrapStyle={rShow(state.recording, { ...defaultPreviewStyle })}
|
|
260
|
+
>
|
|
261
|
+
</Video>,
|
|
233
262
|
AorB(
|
|
234
|
-
preview &&
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
!!(preview && state.recording && screenRecorder.current),
|
|
245
|
-
screenRecorder.current
|
|
246
|
-
? safeCallback<[MediaStream], React.ReactNode>(previewContent, screenRecorder.current?.mediaStream!)
|
|
247
|
-
: null
|
|
248
|
-
)
|
|
249
|
-
)
|
|
250
|
-
}
|
|
251
|
-
</div >
|
|
263
|
+
!!(preview && state.recording && screenRecorder.current),
|
|
264
|
+
screenRecorder.current
|
|
265
|
+
? safeCallback<[MediaStream], React.ReactNode>(
|
|
266
|
+
previewContent,
|
|
267
|
+
screenRecorder.current?.mediaStream!,
|
|
268
|
+
)
|
|
269
|
+
: null,
|
|
270
|
+
),
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
252
273
|
);
|
|
253
|
-
}
|
|
274
|
+
};
|
|
254
275
|
|
|
255
276
|
export default ScreenRecorderComponent;
|
package/src/main.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import "./index.css";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
createRoot(document.getElementById("root")!).render(
|
|
7
7
|
<React.StrictMode>
|
|
8
8
|
<App />
|
|
9
9
|
</React.StrictMode>,
|
|
10
|
-
|
|
11
|
-
)
|
|
10
|
+
);
|
package/tsconfig.json
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ESNext",
|
|
4
4
|
"useDefineForClassFields": true,
|
|
5
|
-
"lib": [
|
|
5
|
+
"lib": [
|
|
6
|
+
"DOM",
|
|
7
|
+
"DOM.Iterable",
|
|
8
|
+
"ESNext"
|
|
9
|
+
],
|
|
6
10
|
"allowJs": false,
|
|
7
11
|
"skipLibCheck": false,
|
|
8
12
|
"esModuleInterop": false,
|
|
@@ -16,5 +20,11 @@
|
|
|
16
20
|
"noEmit": true,
|
|
17
21
|
"jsx": "react-jsx"
|
|
18
22
|
},
|
|
19
|
-
"include": [
|
|
20
|
-
|
|
23
|
+
"include": [
|
|
24
|
+
"./src"
|
|
25
|
+
],
|
|
26
|
+
"exclude": [
|
|
27
|
+
"node_modules",
|
|
28
|
+
"**/node_modules/*"
|
|
29
|
+
]
|
|
30
|
+
}
|