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 CHANGED
@@ -1,34 +1,34 @@
1
1
  {
2
2
  "name": "screen-recorder-react",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "scripts": {
5
- "predev": "lerna run build --scope screen-recorder-base",
6
- "prebuild": "lerna run build --scope screen-recorder-base",
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.1",
21
- "react": "^17.0.0",
22
- "react-dom": "^17.0.0",
23
- "screen-recorder-base": "^0.0.6",
24
- "use-draggable-hook": "^0.1.2"
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": "^17.0.0",
28
- "@types/react-dom": "^17.0.0",
29
- "@vitejs/plugin-react": "^1.0.0",
30
- "antd": "^4.16.13",
31
- "typescript": "^4.3.2",
32
- "vite": "^2.6.4"
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 'react'
2
- import { Card, Switch, Row, Col, Select, Input } from 'antd';
3
- import ScreenRecorder, { Video, safeCallback } from 'screen-recorder-react';
4
- import 'antd/dist/antd.css';
5
- import './App.css';
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: 'Alt+Shift+W',
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: '#48bfa7' },
29
- endBtnText: '🛑 结束',
30
- endBtnStyle: { color: '#FF0000' },
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: '100%',
51
- display: 'flex',
52
- justifyContent: 'space-around',
53
- alignItems: 'center'
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: '50%',
58
- display: 'flex',
59
- flexDirection: 'column',
60
- justifyContent: 'center'
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 style={{ width: '100%', height: '1px', background: '#eee', margin: '30px auto', ...style }}></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<{ style?: React.CSSProperties }> = ({ style, children }) =>
67
- <span style={{ fontSize: 12, color: '#999', ...style }}>{children}</span>
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> = (props) => {
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 title={title} bordered={bordered} style={{ width: '90%', margin: '20px auto', ...style }}>
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: '20px' }}>
90
- <Col md={12}>预览<GraySmallFont>(preview)</GraySmallFont></Col>
91
- <Col md={12}><Switch checked={state.preview} onChange={onPreviewChange} /></Col>
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: '20px' }}>
94
- <Col md={12}>快捷键<GraySmallFont>(shortKey)</GraySmallFont></Col>
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 value={state.shortKey} style={{ width: '100%' }} onChange={onShortKeyChange}>
97
- <Select.Option value="Alt+Shift+W">Alt+Shift+W</Select.Option>
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: '20px' }}>
110
- <Col md={12}>开始按钮文本<GraySmallFont>(startBtnText)</GraySmallFont></Col>
111
- <Col md={12}><Input value={state.startBtnText as string} onChange={onStartBtnTextChange} /></Col>
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
- state.startBtnStyle && (
115
- <Row gutter={{ md: 24 }} style={{ marginBottom: '20px' }}>
116
- <Col md={12}>开始按钮文本颜色<GraySmallFont>(startBtnStyle.color)</GraySmallFont></Col>
117
- <Col md={12}><input type="color" value={state.startBtnStyle?.color} onChange={onStartBtnStyleChange} /></Col>
118
- </Row>
119
- )
120
- }
121
- <Row gutter={{ md: 24 }} style={{ marginBottom: '20px' }}>
122
- <Col md={12}>结束按钮文本<GraySmallFont>(endBtnText)</GraySmallFont></Col>
123
- <Col md={12}><Input value={state.endBtnText as string} onChange={onEndBtnTextChange} /></Col>
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
- state.endBtnStyle && (
127
- <Row gutter={{ md: 24 }} style={{ marginBottom: '20px' }}>
128
- <Col md={12}>结束按钮文本颜色<GraySmallFont>(startBtnStyle.color)</GraySmallFont></Col>
129
- <Col md={12}><input type="color" value={state.endBtnStyle?.color} onChange={onEndBtnStyleChange} /></Col>
130
- </Row>
131
- )
132
- }
133
- <Row gutter={{ md: 24 }} style={{ marginBottom: '20px' }}>
134
- <Col md={12}>视频FPS设置<GraySmallFont>(videoOptions.frameRate)</GraySmallFont></Col>
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
- typeof state.videoOptions?.frameRate === 'number' && !isNaN(state.videoOptions?.frameRate)
140
- ? String(state.videoOptions?.frameRate)
141
- : '60'
142
- }
143
- style={{ width: '100%' }} onChange={onVideoOptionsFrameRateChange}>
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: '20px' }}>
151
- <Col md={12}>视频分辨率设置<GraySmallFont>(videoOptions.width/height)</GraySmallFont></Col>
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: '100%' }} onChange={onVideoOptionsAspectRatioChange}>
157
- <Select.Option value={'1920×1080'}>1920×1080</Select.Option>
158
- <Select.Option value={'1280×720'}>1280×720</Select.Option>
159
- <Select.Option value={'720×480'}>720×480</Select.Option>
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 | { type: TActionType, payload: Partial<IAppStates> | React.CSSProperties | MediaTrackConstraints }
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 === 'string' ? action : action.type
212
- const payload = typeof action === 'string' ? undefined : action.payload
213
- const res: IAppStates | undefined = safeCallback<any, IAppStates>(handleAction[actionType], state, payload)
214
- return res ?? state
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 [state, dispatch] = React.useReducer(reducer, initState)
220
- const setState = (payload: Partial<IAppStates>) => dispatch({
221
- type: 'setState',
222
- payload
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 } }) => setState({ startBtnText })}
238
- onEndBtnTextChange={({ target: { value: endBtnText } }) => setState({ endBtnText })}
239
- onStartBtnStyleChange={({ target: { value: color } }) => dispatch({ type: 'setStartBtnStyle', payload: { color } })}
240
- onEndBtnStyleChange={({ target: { value: color } }) => dispatch({ type: 'setEndBtnStyle', payload: { color } })}
241
- onVideoOptionsFrameRateChange={(frameRate) => dispatch({
242
- type: 'setVideoOptions',
243
- payload: { frameRate: Number(frameRate) }
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('×').map(Number)
324
+ const [width = 1920, height = 1080] = aspectRatio.split("×").map(
325
+ Number,
326
+ );
247
327
  dispatch({
248
- type: 'setVideoOptions',
249
- payload: { width, height }
250
- })
328
+ type: "setVideoOptions",
329
+ payload: { width, height },
330
+ });
251
331
  }}
252
332
  >
253
-
254
- <div style={{
255
- display: 'flex',
256
- flexDirection: 'column',
257
- justifyContent: 'center',
258
- alignItems: 'center'
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
- <video
275
- width="500"
276
- muted
277
- autoPlay
278
- controls
279
- src={state.url}
280
- style={{ marginTop: '30px', border: '1px solid #000' }}
281
- ></video>
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) => !recording &&
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
- ><path 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" fill="#A4D4FF" p-id="888"></path><path 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" fill="#2B8CF7" p-id="889"></path><path 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" fill="#2B8CF7" p-id="890"></path>
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) => recording && (
325
- <div
326
- title="结束录屏"
327
- onClick={() => endEvent()}
328
- className="icon"
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
- <path 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" fill="#A4D4FF" p-id="1051"></path><path 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" fill="#2B8CF7" p-id="1052"></path>
339
- </svg>
340
- </div>
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: 'block' }}
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: '90%',
358
- margin: '20px auto',
359
- minHeight: '500px'
360
- }}>
361
- <div style={{
362
- display: 'flex',
363
- flexDirection: 'column',
364
- justifyContent: 'center',
365
- alignItems: 'center'
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
- url && !recording &&
376
- <video
377
- width="500"
378
- muted
379
- autoPlay
380
- controls
381
- src={url}
382
- style={{ marginTop: '30px', border: '1px solid #000' }}
383
- ></video>
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: '90%' }} />
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 { ScreenRecorder, IScreenRecorderOptions, safeCallback } from 'screen-recorder-base';
3
- import bindkey from '@w-xuefeng/bindkey';
4
- import Video from './Video';
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: '0 16px',
32
- cursor: 'pointer',
33
- boxShadow: 'none',
34
- fontSize: '14px',
35
- lineHeight: '20px',
36
- fontFamily: 'Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif',
37
- height: '32px',
38
- display: 'inline-flex',
39
- alignItems: 'center',
40
- justifyContent: 'center',
41
- userSelect: 'none',
42
- border: '0 solid transparent',
43
- borderRadius: '2px',
44
- padding: '6px 12px',
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: 'none',
47
- verticalAlign: 'middle',
48
- whiteSpace: 'nowrap',
49
- color: 'rgb(80, 86, 94)',
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: 'fixed',
54
- border: '1px solid #666',
57
+ position: "fixed",
58
+ border: "1px solid #666",
55
59
  zIndex: 9999,
56
- cursor: 'move',
60
+ cursor: "move",
57
61
  top: 5,
58
- right: 5
59
- }
62
+ right: 5,
63
+ };
60
64
 
61
- const rShow = (c: boolean, styles?: React.CSSProperties): React.CSSProperties => (
65
+ const rShow = (
66
+ c: boolean,
67
+ styles?: React.CSSProperties,
68
+ ): React.CSSProperties => (
62
69
  {
63
70
  ...styles,
64
- ...(c ? {} : { display: 'none' })
71
+ ...(c ? {} : { display: "none" }),
65
72
  }
66
- )
73
+ );
67
74
 
68
- const AorB = (c: boolean, A: React.ReactNode, B: React.ReactNode = null) => c ? A : B
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: (state: IScreenRecorderComponentStates, payload?: Partial<IScreenRecorderComponentStates>) => {
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 | { type: TActionType, payload: Partial<IScreenRecorderComponentStates> }
97
+ type TActionType = keyof typeof handleAction;
98
+ type TAction = TActionType | {
99
+ type: TActionType;
100
+ payload: Partial<IScreenRecorderComponentStates>;
101
+ };
88
102
 
89
- const reducer = (state: IScreenRecorderComponentStates, action: TAction): IScreenRecorderComponentStates => {
90
- const actionType = typeof action === 'string' ? action : action.type
91
- const payload = typeof action === 'string' ? undefined : action.payload
92
- const res: IScreenRecorderComponentStates | undefined = safeCallback<any, IScreenRecorderComponentStates>(handleAction[actionType], state, payload)
93
- return res ?? state
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> = (props) => {
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>) => dispatch({
120
- type: 'setState',
121
- payload
122
- })
140
+ const setState = (payload: Partial<IScreenRecorderComponentStates>) =>
141
+ dispatch({
142
+ type: "setState",
143
+ payload,
144
+ });
123
145
 
124
- const initPreview = (mediaStream: MediaStream) => setState({ previewMediaStream: 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() !== 'ESC') {
187
+ if (shortKey && shortKey.toUpperCase() !== "ESC") {
165
188
  bindkey.add(shortKey, start, { target: globalThis });
166
- bindkey.add('ESC', end, { target: globalThis });
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('ESC'))
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
- !startContent && !state.recording,
205
- <button
206
- onClick={start}
207
- style={{ ...defaultBtnStyle, ...startBtnStyle }}
208
- >
209
- {startBtnText}
210
- </button>,
211
- AorB(
212
- !state.recording,
213
- safeCallback(startContent, start, end)
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
- !endContent && state.recording,
220
- <button
221
- onClick={end}
222
- style={{ ...defaultBtnStyle, ...endBtnStyle }}
223
- >
224
- {endBtnText}
225
- </button>,
226
- AorB(
227
- state.recording,
228
- safeCallback(endContent, end, start)
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 && !previewContent,
235
- <Video
236
- draggable
237
- muted
238
- autoPlay
239
- width={previewDefaultWidth}
240
- srcObject={state.previewMediaStream}
241
- dragWrapStyle={rShow(state.recording, { ...defaultPreviewStyle })}
242
- ></Video>,
243
- AorB(
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 'react'
2
- import ReactDOM from 'react-dom'
3
- import './index.css'
4
- import App from './App'
1
+ import React from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import App from "./App";
4
+ import "./index.css";
5
5
 
6
- ReactDOM.render(
6
+ createRoot(document.getElementById("root")!).render(
7
7
  <React.StrictMode>
8
8
  <App />
9
9
  </React.StrictMode>,
10
- document.getElementById('root')
11
- )
10
+ );
package/tsconfig.json CHANGED
@@ -2,7 +2,11 @@
2
2
  "compilerOptions": {
3
3
  "target": "ESNext",
4
4
  "useDefineForClassFields": true,
5
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
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": ["./src"]
20
- }
23
+ "include": [
24
+ "./src"
25
+ ],
26
+ "exclude": [
27
+ "node_modules",
28
+ "**/node_modules/*"
29
+ ]
30
+ }