├── .github
└── workflows
│ └── publish.yaml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── index.js
├── package-lock.json
├── package.json
├── src
└── index.ts
└── tsconfig.json
/.github/workflows/publish.yaml:
--------------------------------------------------------------------------------
1 | name: Publish Package to npmjs
2 | on:
3 | release:
4 | types: [published]
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | contents: read
10 | id-token: write
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: actions/setup-node@v4
14 | with:
15 | node-version: '20.x'
16 | registry-url: 'https://registry.npmjs.org'
17 | - run: npm ci
18 | - run: npm publish --provenance --access public
19 | env:
20 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # build directories
61 | lib
62 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Giridharan GM
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-media-recorder :o2: :video_camera: :microphone: :computer:
2 |
3 | `react-media-recorder` is a fully typed react component with render prop, or a react hook, that can be used to:
4 |
5 | - Record audio/video
6 | - Record screen
7 |
8 | using [MediaRecorder API](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder).
9 |
10 | ## Installation
11 |
12 | ```
13 | npm i react-media-recorder
14 | ```
15 |
16 | or
17 |
18 | ```
19 | yarn add react-media-recorder
20 | ```
21 |
22 | ## Usage
23 |
24 | ```javascript
25 | import { ReactMediaRecorder } from "react-media-recorder";
26 |
27 | const RecordView = () => (
28 |
29 |
(
32 |
33 |
{status}
34 |
35 |
36 |
37 |
38 | )}
39 | />
40 |
41 | );
42 | ```
43 |
44 | Since `react-media-recording` uses render prop, you can define what to render in the view. Just don't forget to wire the `startRecording`, `stopRecording` and `mediaBlobUrl` to your component.
45 |
46 | ## Usage with react hooks
47 |
48 | ```javascript
49 | import { useReactMediaRecorder } from "react-media-recorder";
50 |
51 | const RecordView = () => {
52 | const { status, startRecording, stopRecording, mediaBlobUrl } =
53 | useReactMediaRecorder({ video: true });
54 |
55 | return (
56 |
57 |
{status}
58 |
59 |
60 |
61 |
62 | );
63 | };
64 | ```
65 |
66 | The hook receives an object as argument with the same ReactMediaRecorder options / props (except the `render` function).
67 |
68 | ### Options / Props
69 |
70 | #### audio
71 |
72 | Can be either a boolean value or a [MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints) object.
73 |
74 | type: `boolean` or `object`
75 | default: `true`
76 |
77 | #### blobPropertyBag
78 |
79 | [From MDN](https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob):
80 | An optional `BlobPropertyBag` dictionary which may specify the following two attributes (for the `mediaBlob`):
81 |
82 | - `type`, that represents the MIME type of the content of the array that will be put in the blob.
83 | - `endings`, with a default value of "transparent", that specifies how strings containing the line ending character \n are to be written out. It is one of the two values: "native", meaning that line ending characters are changed to match host OS filesystem convention, or "transparent", meaning that endings are stored in the blob without change
84 |
85 | type: `object`
86 | default:
87 | if `video` is enabled,
88 |
89 | ```
90 | {
91 | type: "video/mp4"
92 | }
93 | ```
94 |
95 | if there's only `audio` is enabled,
96 |
97 | ```
98 | {
99 | type: "audio/wav"
100 | }
101 | ```
102 |
103 | #### customMediaStream
104 |
105 | A media stream object itself (optional)
106 |
107 | #### mediaRecorderOptions
108 |
109 | An optional options object that will be passed to `MediaRecorder`. Please note that if you specify the MIME type via either `audio` or `video` prop _and_ through this `mediaRecorderOptions`, the `mediaRecorderOptions` have higher precedence.
110 |
111 | type: `object`
112 | default: `{}`
113 |
114 | #### onStart
115 |
116 | A `function` that would get invoked when the MediaRecorder starts.
117 |
118 | type: `function()`
119 | default: `() => null`
120 |
121 | #### onStop
122 |
123 | A `function` that would get invoked when the MediaRecorder stops. It'll provide the blob and the blob url as its params.
124 |
125 | type: `function(blobUrl: string, blob: Blob)`
126 | default: `() => null`
127 |
128 | #### stopStreamsOnStop
129 |
130 | Whether to stop all streams on stop. By default, its `true`
131 |
132 | #### render
133 |
134 | A `function` which accepts an object containing fields: `status`, `startRecording`, `stopRecording` and`mediaBlob`. This function would return a react element/component.
135 |
136 | type: `function`
137 | default: `() => null`
138 |
139 | #### screen
140 |
141 | A `boolean` value. Lets you to record your current screen. Not all browsers would support this. Please [check here](https://caniuse.com/#search=getDisplayMedia) for the availability. Please note that at the moment, the MediaRecorder won't record two alike streams at a time, if you provide both `screen` and `video` prop, the **screen capturing will take precedence** than the video capturing. But, you can provide the `video` prop (_as the MediaTrackConstraints_) which will then utilized by screen capture (for example, `height`, `width` etc..)
142 |
143 | #### video
144 |
145 | Can be either a boolean value or a [MediaTrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints) object.
146 |
147 | type: `boolean` or `object`
148 | default: `false`
149 |
150 | #### askPermissionOnMount
151 |
152 | A boolean value. If set to `true`, will ask media permission on mounting.
153 |
154 | type: `boolean`
155 | default: `false`
156 |
157 | ### Props available in the `render` function
158 |
159 | #### error
160 |
161 | A string enum. Possible values:
162 |
163 | - `media_aborted`
164 | - `permission_denied`
165 | - `no_specified_media_found`
166 | - `media_in_use`
167 | - `invalid_media_constraints`
168 | - `no_constraints`
169 | - `recorder_error`
170 |
171 | #### status
172 |
173 | A string `enum`. Possible values:
174 |
175 | - `media_aborted`
176 | - `permission_denied`
177 | - `no_specified_media_found`
178 | - `media_in_use`
179 | - `invalid_media_constraints`
180 | - `no_constraints`
181 | - `recorder_error`
182 | - `idle`
183 | - `acquiring_media`
184 | - `recording`
185 | - `stopping`
186 | - `stopped`
187 |
188 | #### startRecording
189 |
190 | A `function`, which starts recording when invoked.
191 |
192 | #### pauseRecording
193 |
194 | A `function`, which pauses the recording when invoked.
195 |
196 | #### resumeRecording
197 |
198 | A `function`, which resumes the recording when invoked.
199 |
200 | #### stopRecording
201 |
202 | A `function`, which stops recording when invoked.
203 |
204 | #### muteAudio
205 |
206 | A `function`, which mutes the audio tracks when invoked.
207 |
208 | #### unmuteAudio
209 |
210 | A `function` which unmutes the audio tracks when invoked.
211 |
212 | #### mediaBlobUrl
213 |
214 | A `blob` url that can be wired to an ``, `` or an `` element.
215 |
216 | #### clearBlobUrl
217 |
218 | A `function` which clears the existing generated blob url (if any) and resets the workflow to its initial `idle` state.
219 |
220 | #### isMuted
221 |
222 | A boolean prop that tells whether the audio is muted or not.
223 |
224 | #### previewStream
225 |
226 | If you want to create a live-preview of the video to the user, you can use this _stream_ and attach it to a `` element. Please note that this is a **muted stream**. This is by design to get rid of internal microphone feedbacks on machines like laptop.
227 |
228 | For example:
229 |
230 | ```tsx
231 | const VideoPreview = ({ stream }: { stream: MediaStream | null }) => {
232 | const videoRef = useRef(null);
233 |
234 | useEffect(() => {
235 | if (videoRef.current && stream) {
236 | videoRef.current.srcObject = stream;
237 | }
238 | }, [stream]);
239 | if (!stream) {
240 | return null;
241 | }
242 | return ;
243 | };
244 |
245 | const App = () => (
246 | {
249 | return ;
250 | }}
251 | />
252 | );
253 | ```
254 |
255 | #### previewAudioStream
256 |
257 | If you want access to the live audio stream for use in sound visualisations, you can use this _stream_ as your audio source and extract data from it using the [AudioContext](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext) and [AnalyzerNode](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode) features of the Web Audio API. Some javascript examples of how to do this can be found [here](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API).
258 |
259 | ## Contributing
260 |
261 | Feel free to submit a PR if you found a bug (I might've missed many! :grinning:) or if you want to enhance it further.
262 |
263 | Thanks!. Happy Recording!
264 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require("./lib");
2 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-media-recorder",
3 | "version": "1.7.1",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "react-media-recorder",
9 | "version": "1.7.1",
10 | "license": "MIT",
11 | "dependencies": {
12 | "extendable-media-recorder": "^6.6.5",
13 | "extendable-media-recorder-wav-encoder": "^7.0.68"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^16.9.11",
17 | "jsmin": "^1.0.1",
18 | "typescript": "^4.4.3"
19 | }
20 | },
21 | "node_modules/@babel/runtime": {
22 | "version": "7.25.6",
23 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
24 | "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
25 | "dependencies": {
26 | "regenerator-runtime": "^0.14.0"
27 | },
28 | "engines": {
29 | "node": ">=6.9.0"
30 | }
31 | },
32 | "node_modules/@types/prop-types": {
33 | "version": "15.7.4",
34 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
35 | "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==",
36 | "dev": true
37 | },
38 | "node_modules/@types/react": {
39 | "version": "16.14.15",
40 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.15.tgz",
41 | "integrity": "sha512-jOxlBV9RGZhphdeqJTCv35VZOkjY+XIEY2owwSk84BNDdDv2xS6Csj6fhi+B/q30SR9Tz8lDNt/F2Z5RF3TrRg==",
42 | "dev": true,
43 | "dependencies": {
44 | "@types/prop-types": "*",
45 | "@types/scheduler": "*",
46 | "csstype": "^3.0.2"
47 | }
48 | },
49 | "node_modules/@types/scheduler": {
50 | "version": "0.16.2",
51 | "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
52 | "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
53 | "dev": true
54 | },
55 | "node_modules/automation-events": {
56 | "version": "7.0.9",
57 | "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-7.0.9.tgz",
58 | "integrity": "sha512-BvN5ynKILdG5UoONshTQu+9W1LXXtBR//OHvAjOe1XfQ1Y4muFyApjcG71alVIyVwsJLBjbh1jqbTrU22FuZEA==",
59 | "dependencies": {
60 | "@babel/runtime": "^7.25.6",
61 | "tslib": "^2.7.0"
62 | },
63 | "engines": {
64 | "node": ">=18.2.0"
65 | }
66 | },
67 | "node_modules/broker-factory": {
68 | "version": "3.0.102",
69 | "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.0.102.tgz",
70 | "integrity": "sha512-nVJJRSba3otuE7PjsWvtEqdKaKScTD7P5z4EQICB93XNl9jgJXjkCGb+5f1WE4S+g1amBXVVE7vmPDPowpN2Qw==",
71 | "dependencies": {
72 | "@babel/runtime": "^7.25.6",
73 | "fast-unique-numbers": "^9.0.9",
74 | "tslib": "^2.7.0",
75 | "worker-factory": "^7.0.29"
76 | }
77 | },
78 | "node_modules/compilerr": {
79 | "version": "10.0.2",
80 | "resolved": "https://registry.npmjs.org/compilerr/-/compilerr-10.0.2.tgz",
81 | "integrity": "sha512-CFwUXxJ9OuWsSvnLSbefxi+GLsZ0YnuJh40ry5QdmZ1FWK59OG+QB8XSj6t7Kq+/c5DSS7en+cML6GlzHKH58A==",
82 | "dependencies": {
83 | "@babel/runtime": "^7.21.0",
84 | "dashify": "^2.0.0",
85 | "indefinite-article": "0.0.2",
86 | "tslib": "^2.5.0"
87 | },
88 | "engines": {
89 | "node": ">=14.15.4"
90 | }
91 | },
92 | "node_modules/csstype": {
93 | "version": "3.0.9",
94 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz",
95 | "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==",
96 | "dev": true
97 | },
98 | "node_modules/dashify": {
99 | "version": "2.0.0",
100 | "resolved": "https://registry.npmjs.org/dashify/-/dashify-2.0.0.tgz",
101 | "integrity": "sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A==",
102 | "engines": {
103 | "node": ">=4"
104 | }
105 | },
106 | "node_modules/extendable-media-recorder": {
107 | "version": "6.6.10",
108 | "resolved": "https://registry.npmjs.org/extendable-media-recorder/-/extendable-media-recorder-6.6.10.tgz",
109 | "integrity": "sha512-gnSmLqDFq40ZdbGfuarnMLNqYPLCPpPr0p21V+g67wG4Pv2oCc/ga8sfsZrEM5GywEi7FcpyRm3z99JWZ/0aPw==",
110 | "dependencies": {
111 | "@babel/runtime": "^7.18.9",
112 | "media-encoder-host": "^8.0.76",
113 | "multi-buffer-data-view": "^3.0.20",
114 | "recorder-audio-worklet": "^5.1.26",
115 | "standardized-audio-context": "^25.3.29",
116 | "subscribable-things": "^2.1.6",
117 | "tslib": "^2.4.0"
118 | }
119 | },
120 | "node_modules/extendable-media-recorder-wav-encoder": {
121 | "version": "7.0.115",
122 | "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder/-/extendable-media-recorder-wav-encoder-7.0.115.tgz",
123 | "integrity": "sha512-HGEBmuVVniVfJXSXBsibiOmH/zzVggBvqMyEcJagb5Tvj/rxCsSYV9GsrX2YU7kcv0ymOBdhV/Pbvnmy9QOj5g==",
124 | "dependencies": {
125 | "@babel/runtime": "^7.25.6",
126 | "extendable-media-recorder-wav-encoder-broker": "^7.0.106",
127 | "extendable-media-recorder-wav-encoder-worker": "^8.0.103",
128 | "tslib": "^2.7.0"
129 | }
130 | },
131 | "node_modules/extendable-media-recorder-wav-encoder-broker": {
132 | "version": "7.0.106",
133 | "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-broker/-/extendable-media-recorder-wav-encoder-broker-7.0.106.tgz",
134 | "integrity": "sha512-kLtXD3rebhBc/IW8IBnr0SzCBFnZWk8dWqEsPt10+PC6BlYGJ1wKvEP5/q3MW6YDBBGMmN+VYb/qOLqeOhxsIQ==",
135 | "dependencies": {
136 | "@babel/runtime": "^7.25.6",
137 | "broker-factory": "^3.0.102",
138 | "extendable-media-recorder-wav-encoder-worker": "^8.0.103",
139 | "tslib": "^2.7.0"
140 | }
141 | },
142 | "node_modules/extendable-media-recorder-wav-encoder-worker": {
143 | "version": "8.0.103",
144 | "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-worker/-/extendable-media-recorder-wav-encoder-worker-8.0.103.tgz",
145 | "integrity": "sha512-p2+yUArjGQCIbqaDtDDDeC/IGDeUsRYC2twRjp/0pA3mEuXX6jVVAhm6RFSeKtxkCwjkWWJFnO1Oe0eGKt6rnA==",
146 | "dependencies": {
147 | "@babel/runtime": "^7.25.6",
148 | "tslib": "^2.7.0",
149 | "worker-factory": "^7.0.29"
150 | }
151 | },
152 | "node_modules/fast-unique-numbers": {
153 | "version": "9.0.9",
154 | "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.9.tgz",
155 | "integrity": "sha512-XVGu/UFAjpclSMX6LD/GGiyRu+weUFb8kUxP09yiDmWA1ORxiO5gpOl13ZBqjeb+gg05JA2H7d37UvbSHfW+7w==",
156 | "dependencies": {
157 | "@babel/runtime": "^7.25.6",
158 | "tslib": "^2.7.0"
159 | },
160 | "engines": {
161 | "node": ">=18.2.0"
162 | }
163 | },
164 | "node_modules/indefinite-article": {
165 | "version": "0.0.2",
166 | "resolved": "https://registry.npmjs.org/indefinite-article/-/indefinite-article-0.0.2.tgz",
167 | "integrity": "sha512-Au/2XzRkvxq2J6w5uvSSbBKPZ5kzINx5F2wb0SF8xpRL8BP9Lav81TnRbfPp6p+SYjYxwaaLn4EUwI3/MmYKSw=="
168 | },
169 | "node_modules/jsmin": {
170 | "version": "1.0.1",
171 | "resolved": "https://registry.npmjs.org/jsmin/-/jsmin-1.0.1.tgz",
172 | "integrity": "sha1-570NzWSWw79IYyNb9GGj2YqjuYw=",
173 | "dev": true,
174 | "bin": {
175 | "jsmin": "bin/jsmin"
176 | },
177 | "engines": {
178 | "node": ">=0.1.93"
179 | }
180 | },
181 | "node_modules/media-encoder-host": {
182 | "version": "8.1.0",
183 | "resolved": "https://registry.npmjs.org/media-encoder-host/-/media-encoder-host-8.1.0.tgz",
184 | "integrity": "sha512-VwX3ex48ltl+K1ObGEq3IcZp/XqpNTWemd9brC9ovo89rYmCRKTZAp1FCyfAY86RdvSMrUs26lbo45DIDVyERg==",
185 | "dependencies": {
186 | "@babel/runtime": "^7.24.4",
187 | "media-encoder-host-broker": "^7.1.0",
188 | "media-encoder-host-worker": "^9.2.0",
189 | "tslib": "^2.6.2"
190 | }
191 | },
192 | "node_modules/media-encoder-host-broker": {
193 | "version": "7.1.0",
194 | "resolved": "https://registry.npmjs.org/media-encoder-host-broker/-/media-encoder-host-broker-7.1.0.tgz",
195 | "integrity": "sha512-Emu3f45Wbf6AoRJxfvZ8e5nh8fRVviBfkABgYNvVUsVBgJ7+l137gn324g/JmNVQhhVQ89fjmGT1kHIJ9JG5Nw==",
196 | "dependencies": {
197 | "@babel/runtime": "^7.24.4",
198 | "broker-factory": "^3.0.97",
199 | "fast-unique-numbers": "^9.0.4",
200 | "media-encoder-host-worker": "^9.2.0",
201 | "tslib": "^2.6.2"
202 | }
203 | },
204 | "node_modules/media-encoder-host-worker": {
205 | "version": "9.2.0",
206 | "resolved": "https://registry.npmjs.org/media-encoder-host-worker/-/media-encoder-host-worker-9.2.0.tgz",
207 | "integrity": "sha512-LrJJgNBDZH2y1PYBLaiYQw9uFU5i3yPvDkDxdko+L3Z4qzhKq9+4eYxKDqlwO4EdOlaiggvMpkgZl3roOniz2A==",
208 | "dependencies": {
209 | "@babel/runtime": "^7.24.4",
210 | "extendable-media-recorder-wav-encoder-broker": "^7.0.100",
211 | "tslib": "^2.6.2",
212 | "worker-factory": "^7.0.24"
213 | }
214 | },
215 | "node_modules/multi-buffer-data-view": {
216 | "version": "3.0.24",
217 | "resolved": "https://registry.npmjs.org/multi-buffer-data-view/-/multi-buffer-data-view-3.0.24.tgz",
218 | "integrity": "sha512-jm7Ycplx37ExXyQmqhwl7zfQmAj81y5LLzVx0XyWea4omP9W/xJhLEHs/5b+WojGyYSRt8BHiXZVcYzu68Ma0Q==",
219 | "dependencies": {
220 | "@babel/runtime": "^7.20.6",
221 | "tslib": "^2.4.1"
222 | },
223 | "engines": {
224 | "node": ">=12.20.1"
225 | }
226 | },
227 | "node_modules/recorder-audio-worklet": {
228 | "version": "5.1.39",
229 | "resolved": "https://registry.npmjs.org/recorder-audio-worklet/-/recorder-audio-worklet-5.1.39.tgz",
230 | "integrity": "sha512-w/RazoBwZnkFnEPRsJYNThOHznLQC98/IzWRrutpJQVvCcL0nbLsVSLDaRrnrqVpRUI11VgiXRh30HaHiSdVhQ==",
231 | "dependencies": {
232 | "@babel/runtime": "^7.21.0",
233 | "broker-factory": "^3.0.75",
234 | "fast-unique-numbers": "^7.0.2",
235 | "recorder-audio-worklet-processor": "^4.2.21",
236 | "standardized-audio-context": "^25.3.41",
237 | "subscribable-things": "^2.1.14",
238 | "tslib": "^2.5.0",
239 | "worker-factory": "^6.0.76"
240 | }
241 | },
242 | "node_modules/recorder-audio-worklet-processor": {
243 | "version": "4.2.21",
244 | "resolved": "https://registry.npmjs.org/recorder-audio-worklet-processor/-/recorder-audio-worklet-processor-4.2.21.tgz",
245 | "integrity": "sha512-oiiS2sp6eMxkvjt13yetSYUJvnAxBZk60mIxz0Vf/2lDWa/4svCyMLHIDzYKbHahkISd0UYyqLS9dI7xDlUOCA==",
246 | "dependencies": {
247 | "@babel/runtime": "^7.21.0",
248 | "tslib": "^2.5.0"
249 | }
250 | },
251 | "node_modules/recorder-audio-worklet/node_modules/fast-unique-numbers": {
252 | "version": "7.0.2",
253 | "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-7.0.2.tgz",
254 | "integrity": "sha512-xnqpsnu889bHbq5cbDMwCJ2BPf6kjFPMu+RHfqKvisRxeEbTOVxY5aW/ZNsZ/r8OlwatxmjdFEVQog2xAhLkvg==",
255 | "dependencies": {
256 | "@babel/runtime": "^7.21.0",
257 | "tslib": "^2.5.0"
258 | },
259 | "engines": {
260 | "node": ">=14.15.4"
261 | }
262 | },
263 | "node_modules/recorder-audio-worklet/node_modules/worker-factory": {
264 | "version": "6.0.76",
265 | "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-6.0.76.tgz",
266 | "integrity": "sha512-W1iBNPmE9p0asU4aFmYJYCnMxhkvk4qlKc660GlHxWgmflY64NxxTbmKqipu4K5p9LiKKPjqXfcQme6153BZEQ==",
267 | "dependencies": {
268 | "@babel/runtime": "^7.21.0",
269 | "compilerr": "^10.0.2",
270 | "fast-unique-numbers": "^7.0.2",
271 | "tslib": "^2.5.0"
272 | }
273 | },
274 | "node_modules/regenerator-runtime": {
275 | "version": "0.14.1",
276 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
277 | "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
278 | },
279 | "node_modules/rxjs-interop": {
280 | "version": "2.0.0",
281 | "resolved": "https://registry.npmjs.org/rxjs-interop/-/rxjs-interop-2.0.0.tgz",
282 | "integrity": "sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw=="
283 | },
284 | "node_modules/standardized-audio-context": {
285 | "version": "25.3.77",
286 | "resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.77.tgz",
287 | "integrity": "sha512-Ki9zNz6pKcC5Pi+QPjPyVsD9GwJIJWgryji0XL9cAJXMGyn+dPOf6Qik1AHei0+UNVcc4BOCa0hWLBzlwqsW/A==",
288 | "dependencies": {
289 | "@babel/runtime": "^7.25.6",
290 | "automation-events": "^7.0.9",
291 | "tslib": "^2.7.0"
292 | }
293 | },
294 | "node_modules/subscribable-things": {
295 | "version": "2.1.40",
296 | "resolved": "https://registry.npmjs.org/subscribable-things/-/subscribable-things-2.1.40.tgz",
297 | "integrity": "sha512-nWw3aCsTsF4b1HY3vU2iweWGleLpM7hjJsmR1SFabVLcxizWo+zHDVYG/P3nEcdOkYgjCMC4vIaX7vFOrT7RGw==",
298 | "dependencies": {
299 | "@babel/runtime": "^7.25.6",
300 | "rxjs-interop": "^2.0.0",
301 | "tslib": "^2.7.0"
302 | }
303 | },
304 | "node_modules/tslib": {
305 | "version": "2.7.0",
306 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
307 | "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
308 | },
309 | "node_modules/typescript": {
310 | "version": "4.4.3",
311 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
312 | "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
313 | "dev": true,
314 | "bin": {
315 | "tsc": "bin/tsc",
316 | "tsserver": "bin/tsserver"
317 | },
318 | "engines": {
319 | "node": ">=4.2.0"
320 | }
321 | },
322 | "node_modules/worker-factory": {
323 | "version": "7.0.29",
324 | "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.29.tgz",
325 | "integrity": "sha512-Nuv/6/Nr70aeBKtiukggJp+o+ewGvgqfjdlJjzlrSi7faPh+5kJ3hFyP3Px5/oEeTUt2ZZ/hgYg1jJFRXi4hAw==",
326 | "dependencies": {
327 | "@babel/runtime": "^7.25.6",
328 | "fast-unique-numbers": "^9.0.9",
329 | "tslib": "^2.7.0"
330 | }
331 | }
332 | },
333 | "dependencies": {
334 | "@babel/runtime": {
335 | "version": "7.25.6",
336 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
337 | "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
338 | "requires": {
339 | "regenerator-runtime": "^0.14.0"
340 | }
341 | },
342 | "@types/prop-types": {
343 | "version": "15.7.4",
344 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
345 | "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==",
346 | "dev": true
347 | },
348 | "@types/react": {
349 | "version": "16.14.15",
350 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.15.tgz",
351 | "integrity": "sha512-jOxlBV9RGZhphdeqJTCv35VZOkjY+XIEY2owwSk84BNDdDv2xS6Csj6fhi+B/q30SR9Tz8lDNt/F2Z5RF3TrRg==",
352 | "dev": true,
353 | "requires": {
354 | "@types/prop-types": "*",
355 | "@types/scheduler": "*",
356 | "csstype": "^3.0.2"
357 | }
358 | },
359 | "@types/scheduler": {
360 | "version": "0.16.2",
361 | "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
362 | "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
363 | "dev": true
364 | },
365 | "automation-events": {
366 | "version": "7.0.9",
367 | "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-7.0.9.tgz",
368 | "integrity": "sha512-BvN5ynKILdG5UoONshTQu+9W1LXXtBR//OHvAjOe1XfQ1Y4muFyApjcG71alVIyVwsJLBjbh1jqbTrU22FuZEA==",
369 | "requires": {
370 | "@babel/runtime": "^7.25.6",
371 | "tslib": "^2.7.0"
372 | }
373 | },
374 | "broker-factory": {
375 | "version": "3.0.102",
376 | "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.0.102.tgz",
377 | "integrity": "sha512-nVJJRSba3otuE7PjsWvtEqdKaKScTD7P5z4EQICB93XNl9jgJXjkCGb+5f1WE4S+g1amBXVVE7vmPDPowpN2Qw==",
378 | "requires": {
379 | "@babel/runtime": "^7.25.6",
380 | "fast-unique-numbers": "^9.0.9",
381 | "tslib": "^2.7.0",
382 | "worker-factory": "^7.0.29"
383 | }
384 | },
385 | "compilerr": {
386 | "version": "10.0.2",
387 | "resolved": "https://registry.npmjs.org/compilerr/-/compilerr-10.0.2.tgz",
388 | "integrity": "sha512-CFwUXxJ9OuWsSvnLSbefxi+GLsZ0YnuJh40ry5QdmZ1FWK59OG+QB8XSj6t7Kq+/c5DSS7en+cML6GlzHKH58A==",
389 | "requires": {
390 | "@babel/runtime": "^7.21.0",
391 | "dashify": "^2.0.0",
392 | "indefinite-article": "0.0.2",
393 | "tslib": "^2.5.0"
394 | }
395 | },
396 | "csstype": {
397 | "version": "3.0.9",
398 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz",
399 | "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==",
400 | "dev": true
401 | },
402 | "dashify": {
403 | "version": "2.0.0",
404 | "resolved": "https://registry.npmjs.org/dashify/-/dashify-2.0.0.tgz",
405 | "integrity": "sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A=="
406 | },
407 | "extendable-media-recorder": {
408 | "version": "6.6.10",
409 | "resolved": "https://registry.npmjs.org/extendable-media-recorder/-/extendable-media-recorder-6.6.10.tgz",
410 | "integrity": "sha512-gnSmLqDFq40ZdbGfuarnMLNqYPLCPpPr0p21V+g67wG4Pv2oCc/ga8sfsZrEM5GywEi7FcpyRm3z99JWZ/0aPw==",
411 | "requires": {
412 | "@babel/runtime": "^7.18.9",
413 | "media-encoder-host": "^8.0.76",
414 | "multi-buffer-data-view": "^3.0.20",
415 | "recorder-audio-worklet": "^5.1.26",
416 | "standardized-audio-context": "^25.3.29",
417 | "subscribable-things": "^2.1.6",
418 | "tslib": "^2.4.0"
419 | }
420 | },
421 | "extendable-media-recorder-wav-encoder": {
422 | "version": "7.0.115",
423 | "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder/-/extendable-media-recorder-wav-encoder-7.0.115.tgz",
424 | "integrity": "sha512-HGEBmuVVniVfJXSXBsibiOmH/zzVggBvqMyEcJagb5Tvj/rxCsSYV9GsrX2YU7kcv0ymOBdhV/Pbvnmy9QOj5g==",
425 | "requires": {
426 | "@babel/runtime": "^7.25.6",
427 | "extendable-media-recorder-wav-encoder-broker": "^7.0.106",
428 | "extendable-media-recorder-wav-encoder-worker": "^8.0.103",
429 | "tslib": "^2.7.0"
430 | }
431 | },
432 | "extendable-media-recorder-wav-encoder-broker": {
433 | "version": "7.0.106",
434 | "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-broker/-/extendable-media-recorder-wav-encoder-broker-7.0.106.tgz",
435 | "integrity": "sha512-kLtXD3rebhBc/IW8IBnr0SzCBFnZWk8dWqEsPt10+PC6BlYGJ1wKvEP5/q3MW6YDBBGMmN+VYb/qOLqeOhxsIQ==",
436 | "requires": {
437 | "@babel/runtime": "^7.25.6",
438 | "broker-factory": "^3.0.102",
439 | "extendable-media-recorder-wav-encoder-worker": "^8.0.103",
440 | "tslib": "^2.7.0"
441 | }
442 | },
443 | "extendable-media-recorder-wav-encoder-worker": {
444 | "version": "8.0.103",
445 | "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-worker/-/extendable-media-recorder-wav-encoder-worker-8.0.103.tgz",
446 | "integrity": "sha512-p2+yUArjGQCIbqaDtDDDeC/IGDeUsRYC2twRjp/0pA3mEuXX6jVVAhm6RFSeKtxkCwjkWWJFnO1Oe0eGKt6rnA==",
447 | "requires": {
448 | "@babel/runtime": "^7.25.6",
449 | "tslib": "^2.7.0",
450 | "worker-factory": "^7.0.29"
451 | }
452 | },
453 | "fast-unique-numbers": {
454 | "version": "9.0.9",
455 | "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.9.tgz",
456 | "integrity": "sha512-XVGu/UFAjpclSMX6LD/GGiyRu+weUFb8kUxP09yiDmWA1ORxiO5gpOl13ZBqjeb+gg05JA2H7d37UvbSHfW+7w==",
457 | "requires": {
458 | "@babel/runtime": "^7.25.6",
459 | "tslib": "^2.7.0"
460 | }
461 | },
462 | "indefinite-article": {
463 | "version": "0.0.2",
464 | "resolved": "https://registry.npmjs.org/indefinite-article/-/indefinite-article-0.0.2.tgz",
465 | "integrity": "sha512-Au/2XzRkvxq2J6w5uvSSbBKPZ5kzINx5F2wb0SF8xpRL8BP9Lav81TnRbfPp6p+SYjYxwaaLn4EUwI3/MmYKSw=="
466 | },
467 | "jsmin": {
468 | "version": "1.0.1",
469 | "resolved": "https://registry.npmjs.org/jsmin/-/jsmin-1.0.1.tgz",
470 | "integrity": "sha1-570NzWSWw79IYyNb9GGj2YqjuYw=",
471 | "dev": true
472 | },
473 | "media-encoder-host": {
474 | "version": "8.1.0",
475 | "resolved": "https://registry.npmjs.org/media-encoder-host/-/media-encoder-host-8.1.0.tgz",
476 | "integrity": "sha512-VwX3ex48ltl+K1ObGEq3IcZp/XqpNTWemd9brC9ovo89rYmCRKTZAp1FCyfAY86RdvSMrUs26lbo45DIDVyERg==",
477 | "requires": {
478 | "@babel/runtime": "^7.24.4",
479 | "media-encoder-host-broker": "^7.1.0",
480 | "media-encoder-host-worker": "^9.2.0",
481 | "tslib": "^2.6.2"
482 | }
483 | },
484 | "media-encoder-host-broker": {
485 | "version": "7.1.0",
486 | "resolved": "https://registry.npmjs.org/media-encoder-host-broker/-/media-encoder-host-broker-7.1.0.tgz",
487 | "integrity": "sha512-Emu3f45Wbf6AoRJxfvZ8e5nh8fRVviBfkABgYNvVUsVBgJ7+l137gn324g/JmNVQhhVQ89fjmGT1kHIJ9JG5Nw==",
488 | "requires": {
489 | "@babel/runtime": "^7.24.4",
490 | "broker-factory": "^3.0.97",
491 | "fast-unique-numbers": "^9.0.4",
492 | "media-encoder-host-worker": "^9.2.0",
493 | "tslib": "^2.6.2"
494 | }
495 | },
496 | "media-encoder-host-worker": {
497 | "version": "9.2.0",
498 | "resolved": "https://registry.npmjs.org/media-encoder-host-worker/-/media-encoder-host-worker-9.2.0.tgz",
499 | "integrity": "sha512-LrJJgNBDZH2y1PYBLaiYQw9uFU5i3yPvDkDxdko+L3Z4qzhKq9+4eYxKDqlwO4EdOlaiggvMpkgZl3roOniz2A==",
500 | "requires": {
501 | "@babel/runtime": "^7.24.4",
502 | "extendable-media-recorder-wav-encoder-broker": "^7.0.100",
503 | "tslib": "^2.6.2",
504 | "worker-factory": "^7.0.24"
505 | }
506 | },
507 | "multi-buffer-data-view": {
508 | "version": "3.0.24",
509 | "resolved": "https://registry.npmjs.org/multi-buffer-data-view/-/multi-buffer-data-view-3.0.24.tgz",
510 | "integrity": "sha512-jm7Ycplx37ExXyQmqhwl7zfQmAj81y5LLzVx0XyWea4omP9W/xJhLEHs/5b+WojGyYSRt8BHiXZVcYzu68Ma0Q==",
511 | "requires": {
512 | "@babel/runtime": "^7.20.6",
513 | "tslib": "^2.4.1"
514 | }
515 | },
516 | "recorder-audio-worklet": {
517 | "version": "5.1.39",
518 | "resolved": "https://registry.npmjs.org/recorder-audio-worklet/-/recorder-audio-worklet-5.1.39.tgz",
519 | "integrity": "sha512-w/RazoBwZnkFnEPRsJYNThOHznLQC98/IzWRrutpJQVvCcL0nbLsVSLDaRrnrqVpRUI11VgiXRh30HaHiSdVhQ==",
520 | "requires": {
521 | "@babel/runtime": "^7.21.0",
522 | "broker-factory": "^3.0.75",
523 | "fast-unique-numbers": "^7.0.2",
524 | "recorder-audio-worklet-processor": "^4.2.21",
525 | "standardized-audio-context": "^25.3.41",
526 | "subscribable-things": "^2.1.14",
527 | "tslib": "^2.5.0",
528 | "worker-factory": "^6.0.76"
529 | },
530 | "dependencies": {
531 | "fast-unique-numbers": {
532 | "version": "7.0.2",
533 | "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-7.0.2.tgz",
534 | "integrity": "sha512-xnqpsnu889bHbq5cbDMwCJ2BPf6kjFPMu+RHfqKvisRxeEbTOVxY5aW/ZNsZ/r8OlwatxmjdFEVQog2xAhLkvg==",
535 | "requires": {
536 | "@babel/runtime": "^7.21.0",
537 | "tslib": "^2.5.0"
538 | }
539 | },
540 | "worker-factory": {
541 | "version": "6.0.76",
542 | "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-6.0.76.tgz",
543 | "integrity": "sha512-W1iBNPmE9p0asU4aFmYJYCnMxhkvk4qlKc660GlHxWgmflY64NxxTbmKqipu4K5p9LiKKPjqXfcQme6153BZEQ==",
544 | "requires": {
545 | "@babel/runtime": "^7.21.0",
546 | "compilerr": "^10.0.2",
547 | "fast-unique-numbers": "^7.0.2",
548 | "tslib": "^2.5.0"
549 | }
550 | }
551 | }
552 | },
553 | "recorder-audio-worklet-processor": {
554 | "version": "4.2.21",
555 | "resolved": "https://registry.npmjs.org/recorder-audio-worklet-processor/-/recorder-audio-worklet-processor-4.2.21.tgz",
556 | "integrity": "sha512-oiiS2sp6eMxkvjt13yetSYUJvnAxBZk60mIxz0Vf/2lDWa/4svCyMLHIDzYKbHahkISd0UYyqLS9dI7xDlUOCA==",
557 | "requires": {
558 | "@babel/runtime": "^7.21.0",
559 | "tslib": "^2.5.0"
560 | }
561 | },
562 | "regenerator-runtime": {
563 | "version": "0.14.1",
564 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
565 | "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
566 | },
567 | "rxjs-interop": {
568 | "version": "2.0.0",
569 | "resolved": "https://registry.npmjs.org/rxjs-interop/-/rxjs-interop-2.0.0.tgz",
570 | "integrity": "sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw=="
571 | },
572 | "standardized-audio-context": {
573 | "version": "25.3.77",
574 | "resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.77.tgz",
575 | "integrity": "sha512-Ki9zNz6pKcC5Pi+QPjPyVsD9GwJIJWgryji0XL9cAJXMGyn+dPOf6Qik1AHei0+UNVcc4BOCa0hWLBzlwqsW/A==",
576 | "requires": {
577 | "@babel/runtime": "^7.25.6",
578 | "automation-events": "^7.0.9",
579 | "tslib": "^2.7.0"
580 | }
581 | },
582 | "subscribable-things": {
583 | "version": "2.1.40",
584 | "resolved": "https://registry.npmjs.org/subscribable-things/-/subscribable-things-2.1.40.tgz",
585 | "integrity": "sha512-nWw3aCsTsF4b1HY3vU2iweWGleLpM7hjJsmR1SFabVLcxizWo+zHDVYG/P3nEcdOkYgjCMC4vIaX7vFOrT7RGw==",
586 | "requires": {
587 | "@babel/runtime": "^7.25.6",
588 | "rxjs-interop": "^2.0.0",
589 | "tslib": "^2.7.0"
590 | }
591 | },
592 | "tslib": {
593 | "version": "2.7.0",
594 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
595 | "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
596 | },
597 | "typescript": {
598 | "version": "4.4.3",
599 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
600 | "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
601 | "dev": true
602 | },
603 | "worker-factory": {
604 | "version": "7.0.29",
605 | "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.29.tgz",
606 | "integrity": "sha512-Nuv/6/Nr70aeBKtiukggJp+o+ewGvgqfjdlJjzlrSi7faPh+5kJ3hFyP3Px5/oEeTUt2ZZ/hgYg1jJFRXi4hAw==",
607 | "requires": {
608 | "@babel/runtime": "^7.25.6",
609 | "fast-unique-numbers": "^9.0.9",
610 | "tslib": "^2.7.0"
611 | }
612 | }
613 | }
614 | }
615 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-media-recorder",
3 | "version": "1.7.1",
4 | "description": "A React component based on MediaRecorder() API to record audio/video streams",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "tsc && jsmin -o ./lib/index.js ./lib/index.js",
8 | "prepare": "npm run build"
9 | },
10 | "files": [
11 | "lib"
12 | ],
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/giridharangm/react-media-recorder.git"
16 | },
17 | "keywords": [
18 | "react",
19 | "recorder",
20 | "voice recording",
21 | "video recording",
22 | "media recording",
23 | "getusermedia",
24 | "MediaRecorder",
25 | "getDisplayMedia",
26 | "screen recorder",
27 | "video recorder",
28 | "audio recorder"
29 | ],
30 | "author": "Giridharan GM",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/giridharangm/react-media-recorder/issues"
34 | },
35 | "homepage": "https://github.com/giridharangm/react-media-recorder#readme",
36 | "devDependencies": {
37 | "@types/react": "^16.9.11",
38 | "jsmin": "^1.0.1",
39 | "typescript": "^4.4.3"
40 | },
41 | "types": "./lib/index.d.ts",
42 | "dependencies": {
43 | "extendable-media-recorder": "^6.6.5",
44 | "extendable-media-recorder-wav-encoder": "^7.0.68"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | register,
3 | MediaRecorder as ExtendableMediaRecorder,
4 | IMediaRecorder,
5 | } from "extendable-media-recorder";
6 | import { ReactElement, useCallback, useEffect, useRef, useState } from "react";
7 | import { connect } from "extendable-media-recorder-wav-encoder";
8 |
9 | export type ReactMediaRecorderRenderProps = {
10 | error: string;
11 | muteAudio: () => void;
12 | unMuteAudio: () => void;
13 | startRecording: () => void;
14 | pauseRecording: () => void;
15 | resumeRecording: () => void;
16 | stopRecording: () => void;
17 | mediaBlobUrl: undefined | string;
18 | status: StatusMessages;
19 | isAudioMuted: boolean;
20 | previewStream: MediaStream | null;
21 | previewAudioStream: MediaStream | null;
22 | clearBlobUrl: () => void;
23 | };
24 |
25 | export type ReactMediaRecorderHookProps = {
26 | audio?: boolean | MediaTrackConstraints;
27 | video?: boolean | MediaTrackConstraints;
28 | screen?: boolean;
29 | selfBrowserSurface?: SelfBrowserSurface;
30 | onStop?: (blobUrl: string, blob: Blob) => void;
31 | onStart?: () => void;
32 | blobPropertyBag?: BlobPropertyBag;
33 | mediaRecorderOptions?: MediaRecorderOptions | undefined;
34 | customMediaStream?: MediaStream | null;
35 | stopStreamsOnStop?: boolean;
36 | askPermissionOnMount?: boolean;
37 | };
38 | export type ReactMediaRecorderProps = ReactMediaRecorderHookProps & {
39 | render: (props: ReactMediaRecorderRenderProps) => ReactElement;
40 | };
41 |
42 | /**
43 | * Experimental (optional).
44 | * An enumerated value specifying whether the browser should allow the user to select the current tab for capture.
45 | * This helps to avoid the "infinite hall of mirrors" effect experienced when a video conferencing app inadvertently shares its own display.
46 | * Possible values are include, which hints that the browser should include the current tab in the choices offered for capture,
47 | * and exclude, which hints that it should be excluded.
48 | * A default value is not mandated by the spec.
49 | * See specs at: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#selfbrowsersurface
50 | */
51 | export type SelfBrowserSurface = undefined | 'include' | 'exclude';
52 |
53 | export type StatusMessages =
54 | | "media_aborted"
55 | | "permission_denied"
56 | | "no_specified_media_found"
57 | | "media_in_use"
58 | | "invalid_media_constraints"
59 | | "no_constraints"
60 | | "recorder_error"
61 | | "idle"
62 | | "acquiring_media"
63 | | "delayed_start"
64 | | "recording"
65 | | "stopping"
66 | | "stopped"
67 | | "paused";
68 |
69 | export enum RecorderErrors {
70 | AbortError = "media_aborted",
71 | NotAllowedError = "permission_denied",
72 | NotFoundError = "no_specified_media_found",
73 | NotReadableError = "media_in_use",
74 | OverconstrainedError = "invalid_media_constraints",
75 | TypeError = "no_constraints",
76 | NONE = "",
77 | NO_RECORDER = "recorder_error",
78 | }
79 |
80 | export function useReactMediaRecorder({
81 | audio = true,
82 | video = false,
83 | selfBrowserSurface = undefined,
84 | onStop = () => null,
85 | onStart = () => null,
86 | blobPropertyBag,
87 | screen = false,
88 | mediaRecorderOptions = undefined,
89 | customMediaStream = null,
90 | stopStreamsOnStop = true,
91 | askPermissionOnMount = false,
92 | }: ReactMediaRecorderHookProps): ReactMediaRecorderRenderProps {
93 | const mediaRecorder = useRef(null);
94 | const mediaChunks = useRef([]);
95 | const mediaStream = useRef(null);
96 | const [status, setStatus] = useState("idle");
97 | const [isAudioMuted, setIsAudioMuted] = useState(false);
98 | const [mediaBlobUrl, setMediaBlobUrl] = useState(
99 | undefined
100 | );
101 | const [error, setError] = useState("NONE");
102 | const [init, setInit] = useState(false);
103 |
104 | useEffect(() => {
105 | // avoid re-registering the encoder
106 | if (init) {
107 | return;
108 | }
109 |
110 | const setup = async () => {
111 | try {
112 | await register(await connect());
113 | } catch (e) {
114 | //
115 | }
116 | };
117 |
118 | setup();
119 | setInit(true);
120 | }, []);
121 |
122 | const getMediaStream = useCallback(async () => {
123 | setStatus("acquiring_media");
124 | const requiredMedia: MediaStreamConstraints = {
125 | audio: typeof audio === "boolean" ? !!audio : audio,
126 | video: typeof video === "boolean" ? !!video : video,
127 | };
128 | try {
129 | if (customMediaStream) {
130 | mediaStream.current = customMediaStream;
131 | } else if (screen) {
132 | const stream = (await window.navigator.mediaDevices.getDisplayMedia({
133 | video: video || true,
134 | // @ts-ignore experimental feature, useful for Chrome
135 | selfBrowserSurface,
136 | })) as MediaStream;
137 | stream.getVideoTracks()[0].addEventListener("ended", () => {
138 | stopRecording();
139 | });
140 | if (audio) {
141 | const audioStream = await window.navigator.mediaDevices.getUserMedia({
142 | audio,
143 | });
144 |
145 | audioStream
146 | .getAudioTracks()
147 | .forEach((audioTrack) => stream.addTrack(audioTrack));
148 | }
149 | mediaStream.current = stream;
150 | } else {
151 | const stream = await window.navigator.mediaDevices.getUserMedia(
152 | requiredMedia
153 | );
154 | mediaStream.current = stream;
155 | }
156 | setStatus("idle");
157 | } catch (error: any) {
158 | setError(error.name);
159 | setStatus("idle");
160 | }
161 | }, [audio, video, screen]);
162 |
163 | useEffect(() => {
164 | if (!window.MediaRecorder) {
165 | throw new Error("Unsupported Browser");
166 | }
167 |
168 | if (screen) {
169 | if (!window.navigator.mediaDevices.getDisplayMedia) {
170 | throw new Error("This browser doesn't support screen capturing");
171 | }
172 | }
173 |
174 | const checkConstraints = (mediaType: MediaTrackConstraints) => {
175 | const supportedMediaConstraints =
176 | navigator.mediaDevices.getSupportedConstraints();
177 | const unSupportedConstraints = Object.keys(mediaType).filter(
178 | (constraint) =>
179 | !(supportedMediaConstraints as { [key: string]: any })[constraint]
180 | );
181 |
182 | if (unSupportedConstraints.length > 0) {
183 | console.error(
184 | `The constraints ${unSupportedConstraints.join(
185 | ","
186 | )} doesn't support on this browser. Please check your ReactMediaRecorder component.`
187 | );
188 | }
189 | };
190 |
191 | if (typeof audio === "object") {
192 | checkConstraints(audio);
193 | }
194 | if (typeof video === "object") {
195 | checkConstraints(video);
196 | }
197 |
198 | if (mediaRecorderOptions && mediaRecorderOptions.mimeType) {
199 | if (!MediaRecorder.isTypeSupported(mediaRecorderOptions.mimeType)) {
200 | console.error(
201 | `The specified MIME type you supplied for MediaRecorder doesn't support this browser`
202 | );
203 | }
204 | }
205 |
206 | if (!mediaStream.current && askPermissionOnMount) {
207 | getMediaStream();
208 | }
209 |
210 | return () => {
211 | if (mediaStream.current) {
212 | const tracks = mediaStream.current.getTracks();
213 | tracks.forEach((track) => track.clone().stop());
214 | }
215 | };
216 | }, [
217 | audio,
218 | screen,
219 | video,
220 | getMediaStream,
221 | mediaRecorderOptions,
222 | askPermissionOnMount,
223 | ]);
224 |
225 | // Media Recorder Handlers
226 |
227 | const startRecording = async () => {
228 | setError("NONE");
229 | if (!mediaStream.current) {
230 | await getMediaStream();
231 | }
232 | if (mediaStream.current) {
233 | const isStreamEnded = mediaStream.current
234 | .getTracks()
235 | .some((track) => track.readyState === "ended");
236 | if (isStreamEnded) {
237 | await getMediaStream();
238 | }
239 |
240 | // User blocked the permissions (getMediaStream errored out)
241 | if (!mediaStream.current.active) {
242 | return;
243 | }
244 | mediaRecorder.current = new ExtendableMediaRecorder(
245 | mediaStream.current,
246 | mediaRecorderOptions || undefined
247 | );
248 | mediaRecorder.current.ondataavailable = onRecordingActive;
249 | mediaRecorder.current.onstop = onRecordingStop;
250 | mediaRecorder.current.onstart = onRecordingStart;
251 | mediaRecorder.current.onerror = () => {
252 | setError("NO_RECORDER");
253 | setStatus("idle");
254 | };
255 | mediaRecorder.current.start();
256 | setStatus("recording");
257 | }
258 | };
259 |
260 | const onRecordingActive = ({ data }: BlobEvent) => {
261 | mediaChunks.current.push(data);
262 | };
263 |
264 | const onRecordingStart = () => {
265 | onStart();
266 | };
267 |
268 | const onRecordingStop = () => {
269 | const [chunk] = mediaChunks.current;
270 | const blobProperty: BlobPropertyBag = Object.assign(
271 | { type: chunk.type },
272 | blobPropertyBag || (video ? { type: "video/mp4" } : { type: "audio/wav" })
273 | );
274 | const blob = new Blob(mediaChunks.current, blobProperty);
275 | const url = URL.createObjectURL(blob);
276 | setStatus("stopped");
277 | setMediaBlobUrl(url);
278 | onStop(url, blob);
279 | };
280 |
281 | const muteAudio = (mute: boolean) => {
282 | setIsAudioMuted(mute);
283 | if (mediaStream.current) {
284 | mediaStream.current
285 | .getAudioTracks()
286 | .forEach((audioTrack) => (audioTrack.enabled = !mute));
287 | }
288 | };
289 |
290 | const pauseRecording = () => {
291 | if (mediaRecorder.current && mediaRecorder.current.state === "recording") {
292 | setStatus("paused");
293 | mediaRecorder.current.pause();
294 | }
295 | };
296 | const resumeRecording = () => {
297 | if (mediaRecorder.current && mediaRecorder.current.state === "paused") {
298 | setStatus("recording");
299 | mediaRecorder.current.resume();
300 | }
301 | };
302 |
303 | const stopRecording = () => {
304 | if (mediaRecorder.current) {
305 | if (mediaRecorder.current.state !== "inactive") {
306 | setStatus("stopping");
307 | mediaRecorder.current.stop();
308 | if (stopStreamsOnStop) {
309 | mediaStream.current &&
310 | mediaStream.current.getTracks().forEach((track) => track.stop());
311 | }
312 | mediaChunks.current = [];
313 | }
314 | }
315 | };
316 |
317 | return {
318 | error: RecorderErrors[error],
319 | muteAudio: () => muteAudio(true),
320 | unMuteAudio: () => muteAudio(false),
321 | startRecording,
322 | pauseRecording,
323 | resumeRecording,
324 | stopRecording,
325 | mediaBlobUrl,
326 | status,
327 | isAudioMuted,
328 | previewStream: mediaStream.current
329 | ? new MediaStream(mediaStream.current.getVideoTracks())
330 | : null,
331 | previewAudioStream: mediaStream.current
332 | ? new MediaStream(mediaStream.current.getAudioTracks())
333 | : null,
334 | clearBlobUrl: () => {
335 | if (mediaBlobUrl) {
336 | URL.revokeObjectURL(mediaBlobUrl);
337 | }
338 | setMediaBlobUrl(undefined);
339 | setStatus("idle");
340 | },
341 | };
342 | }
343 |
344 | export const ReactMediaRecorder = (props: ReactMediaRecorderProps) =>
345 | props.render(useReactMediaRecorder(props));
346 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | // "incremental": true, /* Enable incremental compilation */
5 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
6 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
7 | "lib": [
8 | "ES2015",
9 | "DOM"
10 | ] /* Specify library files to be included in the compilation. */,
11 | // "allowJs": true, /* Allow javascript files to be compiled. */
12 | // "checkJs": true, /* Report errors in .js files. */
13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
14 | "declaration": true /* Generates corresponding '.d.ts' file. */,
15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
16 | // "sourceMap": true, /* Generates corresponding '.map' file. */
17 | // "outFile": "./", /* Concatenate and emit output to single file. */
18 | "outDir": "./lib" /* Redirect output structure to the directory. */,
19 | "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
20 | // "composite": true, /* Enable project compilation */
21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
22 | // "removeComments": true, /* Do not emit comments to output. */
23 | // "noEmit": true, /* Do not emit outputs. */
24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
27 |
28 | /* Strict Type-Checking Options */
29 | "strict": true /* Enable all strict type-checking options. */,
30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
31 | // "strictNullChecks": true, /* Enable strict null checks. */
32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
37 |
38 | /* Additional Checks */
39 | // "noUnusedLocals": true, /* Report errors on unused locals. */
40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
43 |
44 | /* Module Resolution Options */
45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
49 | // "typeRoots": [], /* List of folders to include type definitions from. */
50 | // "types": [], /* Type declaration files to be included in compilation. */
51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
52 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
55 |
56 | /* Source Map Options */
57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
61 |
62 | /* Experimental Options */
63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
65 |
66 | /* Advanced Options */
67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
68 | }
69 | }
70 |
--------------------------------------------------------------------------------