Merge pull request #1 from MarconLP/feature/screen-recording

Add in-browser recording
This commit is contained in:
Marcus Hof 2023-04-19 20:33:18 +02:00 committed by GitHub
commit d89e9a41b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 840 additions and 74 deletions

454
package-lock.json generated
View file

@ -22,6 +22,7 @@
"@trpc/next": "^10.18.0",
"@trpc/react-query": "^10.18.0",
"@trpc/server": "^10.18.0",
"@types/recordrtc": "^5.6.11",
"@upstash/qstash": "^0.3.6",
"axios": "^1.3.5",
"dayjs": "^1.11.7",
@ -32,8 +33,10 @@
"next-auth": "^4.21.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-media-recorder": "^1.6.6",
"react-player": "^2.12.0",
"react-popper": "^2.3.0",
"recordrtc": "^5.6.2",
"stripe": "^12.1.1",
"superjson": "1.12.2",
"zod": "^3.21.4"
@ -2404,6 +2407,11 @@
"@types/react": "*"
}
},
"node_modules/@types/recordrtc": {
"version": "5.6.11",
"resolved": "https://registry.npmjs.org/@types/recordrtc/-/recordrtc-5.6.11.tgz",
"integrity": "sha512-X4XD5nltz0cjmyzsPNegQReOPF+C5ARTfSPAPhqnKV7SsfRta/M4FBJ5AtSInCaEveL71FLLSVQE9mg8Uuo++w=="
},
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
@ -2814,6 +2822,18 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/automation-events": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/automation-events/-/automation-events-5.0.3.tgz",
"integrity": "sha512-ZZWTNYJTkGjcJUOBX5P0MHZrArJOkcrQsbyGWwlzJpZs961Y5YvKUw5MsAf8xLlvh7+1B8SO/VTvjMmVXFkD3w==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.15.4"
}
},
"node_modules/autoprefixer": {
"version": "10.4.14",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
@ -2929,6 +2949,17 @@
"node": ">=8"
}
},
"node_modules/broker-factory": {
"version": "3.0.75",
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.0.75.tgz",
"integrity": "sha512-VMC2GBMaoKXdVPC0yH/Z1NbDieYRuKlGPT6PbrrReDwvHSZH2Cl5dJVO3tPTkA9Q+xXOmnRYgcjNktZD/Oz21w==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"fast-unique-numbers": "^7.0.2",
"tslib": "^2.5.0",
"worker-factory": "^6.0.76"
}
},
"node_modules/browserslist": {
"version": "4.21.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
@ -3129,6 +3160,20 @@
"node": ">= 6"
}
},
"node_modules/compilerr": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/compilerr/-/compilerr-10.0.2.tgz",
"integrity": "sha512-CFwUXxJ9OuWsSvnLSbefxi+GLsZ0YnuJh40ry5QdmZ1FWK59OG+QB8XSj6t7Kq+/c5DSS7en+cML6GlzHKH58A==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"dashify": "^2.0.0",
"indefinite-article": "0.0.2",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.15.4"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -3203,6 +3248,14 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
"node_modules/dashify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dashify/-/dashify-2.0.0.tgz",
"integrity": "sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A==",
"engines": {
"node": ">=4"
}
},
"node_modules/dayjs": {
"version": "1.11.7",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
@ -4004,6 +4057,52 @@
"node": ">=0.10.0"
}
},
"node_modules/extendable-media-recorder": {
"version": "6.6.10",
"resolved": "https://registry.npmjs.org/extendable-media-recorder/-/extendable-media-recorder-6.6.10.tgz",
"integrity": "sha512-gnSmLqDFq40ZdbGfuarnMLNqYPLCPpPr0p21V+g67wG4Pv2oCc/ga8sfsZrEM5GywEi7FcpyRm3z99JWZ/0aPw==",
"dependencies": {
"@babel/runtime": "^7.18.9",
"media-encoder-host": "^8.0.76",
"multi-buffer-data-view": "^3.0.20",
"recorder-audio-worklet": "^5.1.26",
"standardized-audio-context": "^25.3.29",
"subscribable-things": "^2.1.6",
"tslib": "^2.4.0"
}
},
"node_modules/extendable-media-recorder-wav-encoder": {
"version": "7.0.85",
"resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder/-/extendable-media-recorder-wav-encoder-7.0.85.tgz",
"integrity": "sha512-M96Y6twJ/OBordRltfyf8Kl2P2C0e176ogVTpGYquHApEyRnGpnm6LMmNqiRkQC8me0WOOhKLVvKCGmEF3lmvQ==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"extendable-media-recorder-wav-encoder-broker": "^7.0.77",
"extendable-media-recorder-wav-encoder-worker": "^8.0.76",
"tslib": "^2.5.0"
}
},
"node_modules/extendable-media-recorder-wav-encoder-broker": {
"version": "7.0.77",
"resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-broker/-/extendable-media-recorder-wav-encoder-broker-7.0.77.tgz",
"integrity": "sha512-HNUcsUMtKw8hUx1pHM/78+joa3L7FpCh+R3jX9RRuSrHMUw/ldq29wi/evBs4lMoasyMZLHD3yI1Phz0hYH7pg==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"broker-factory": "^3.0.75",
"extendable-media-recorder-wav-encoder-worker": "^8.0.76",
"tslib": "^2.5.0"
}
},
"node_modules/extendable-media-recorder-wav-encoder-worker": {
"version": "8.0.76",
"resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-worker/-/extendable-media-recorder-wav-encoder-worker-8.0.76.tgz",
"integrity": "sha512-m/pafu/XodKM05r3wkdY6D+gvprD6VCe1rLKnmF/Ooj4VGQ3DglsOhxWmEXRHVtzFWHIxIpjq6Z14VYaj1sXUg==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"tslib": "^2.5.0",
"worker-factory": "^6.0.76"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -4050,6 +4149,18 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"node_modules/fast-unique-numbers": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-7.0.2.tgz",
"integrity": "sha512-xnqpsnu889bHbq5cbDMwCJ2BPf6kjFPMu+RHfqKvisRxeEbTOVxY5aW/ZNsZ/r8OlwatxmjdFEVQog2xAhLkvg==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.15.4"
}
},
"node_modules/fast-xml-parser": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz",
@ -4539,6 +4650,11 @@
"node": ">=0.8.19"
}
},
"node_modules/indefinite-article": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/indefinite-article/-/indefinite-article-0.0.2.tgz",
"integrity": "sha512-Au/2XzRkvxq2J6w5uvSSbBKPZ5kzINx5F2wb0SF8xpRL8BP9Lav81TnRbfPp6p+SYjYxwaaLn4EUwI3/MmYKSw=="
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -5094,6 +5210,40 @@
"node": ">=10"
}
},
"node_modules/media-encoder-host": {
"version": "8.0.88",
"resolved": "https://registry.npmjs.org/media-encoder-host/-/media-encoder-host-8.0.88.tgz",
"integrity": "sha512-15pWY8GFLsm3w01JuSQdEIkCh1+KUKqNQg5F6s5SXhAwbj0DuTceFbyuqWFMq+XzV8zZrPt8LtYAAj5ld5IEOw==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"media-encoder-host-broker": "^7.0.78",
"media-encoder-host-worker": "^9.1.0",
"tslib": "^2.5.0"
}
},
"node_modules/media-encoder-host-broker": {
"version": "7.0.78",
"resolved": "https://registry.npmjs.org/media-encoder-host-broker/-/media-encoder-host-broker-7.0.78.tgz",
"integrity": "sha512-5IZEZwN4qyCymkP6FCELS6rRCE2xhNFiE3FWr+8R3ySRn1iwPWd/U/YFB4PdvjqFw3MtQ9rHFErfc/YjUf8j9g==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"broker-factory": "^3.0.75",
"fast-unique-numbers": "^7.0.2",
"media-encoder-host-worker": "^9.1.0",
"tslib": "^2.5.0"
}
},
"node_modules/media-encoder-host-worker": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/media-encoder-host-worker/-/media-encoder-host-worker-9.1.0.tgz",
"integrity": "sha512-dVvb65RH/5Yt5PBxBVGw+BcCFkq8A15/fw3HxEKugtaFJocTcwQsS1IeDUuf1i831Fk/FuCipnXFz8VsGRCZyg==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"extendable-media-recorder-wav-encoder-broker": "^7.0.77",
"tslib": "^2.5.0",
"worker-factory": "^6.0.76"
}
},
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@ -5196,6 +5346,18 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/multi-buffer-data-view": {
"version": "3.0.24",
"resolved": "https://registry.npmjs.org/multi-buffer-data-view/-/multi-buffer-data-view-3.0.24.tgz",
"integrity": "sha512-jm7Ycplx37ExXyQmqhwl7zfQmAj81y5LLzVx0XyWea4omP9W/xJhLEHs/5b+WojGyYSRt8BHiXZVcYzu68Ma0Q==",
"dependencies": {
"@babel/runtime": "^7.20.6",
"tslib": "^2.4.1"
},
"engines": {
"node": ">=12.20.1"
}
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@ -6046,6 +6208,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-media-recorder": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/react-media-recorder/-/react-media-recorder-1.6.6.tgz",
"integrity": "sha512-VdC4bUINMWJyqOAHw1DaZ8HZhdCyVBK85zJ4cHMo9tsrekui3wq5ZxNtBmNe6nbAFQBTNj/pRnLEsiVrCW+TNQ==",
"dependencies": {
"extendable-media-recorder": "^6.6.5",
"extendable-media-recorder-wav-encoder": "^7.0.68"
}
},
"node_modules/react-player": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/react-player/-/react-player-2.12.0.tgz",
@ -6104,6 +6275,35 @@
"node": ">=8.10.0"
}
},
"node_modules/recorder-audio-worklet": {
"version": "5.1.39",
"resolved": "https://registry.npmjs.org/recorder-audio-worklet/-/recorder-audio-worklet-5.1.39.tgz",
"integrity": "sha512-w/RazoBwZnkFnEPRsJYNThOHznLQC98/IzWRrutpJQVvCcL0nbLsVSLDaRrnrqVpRUI11VgiXRh30HaHiSdVhQ==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"broker-factory": "^3.0.75",
"fast-unique-numbers": "^7.0.2",
"recorder-audio-worklet-processor": "^4.2.21",
"standardized-audio-context": "^25.3.41",
"subscribable-things": "^2.1.14",
"tslib": "^2.5.0",
"worker-factory": "^6.0.76"
}
},
"node_modules/recorder-audio-worklet-processor": {
"version": "4.2.21",
"resolved": "https://registry.npmjs.org/recorder-audio-worklet-processor/-/recorder-audio-worklet-processor-4.2.21.tgz",
"integrity": "sha512-oiiS2sp6eMxkvjt13yetSYUJvnAxBZk60mIxz0Vf/2lDWa/4svCyMLHIDzYKbHahkISd0UYyqLS9dI7xDlUOCA==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"tslib": "^2.5.0"
}
},
"node_modules/recordrtc": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/recordrtc/-/recordrtc-5.6.2.tgz",
"integrity": "sha512-1QNKKNtl7+KcwD1lyOgP3ZlbiJ1d0HtXnypUy7yq49xEERxk31PHvE9RCciDrulPCY7WJ+oz0R9hpNxgsIurGQ=="
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -6200,6 +6400,11 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs-interop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/rxjs-interop/-/rxjs-interop-2.0.0.tgz",
"integrity": "sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw=="
},
"node_modules/safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@ -6317,6 +6522,16 @@
"source-map": "^0.6.0"
}
},
"node_modules/standardized-audio-context": {
"version": "25.3.43",
"resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.43.tgz",
"integrity": "sha512-Wh/6rZCScDh3ZAsRy0kgeGF+g3mGO4E6UiWj8k79uCHKJjqiBJOtbwEFS1MBFfVXMHN3L3U3kn1aVrkCHtlkvw==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"automation-events": "^5.0.3",
"tslib": "^2.5.0"
}
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@ -6481,6 +6696,16 @@
}
}
},
"node_modules/subscribable-things": {
"version": "2.1.14",
"resolved": "https://registry.npmjs.org/subscribable-things/-/subscribable-things-2.1.14.tgz",
"integrity": "sha512-+0e6wGpFVa7snrwzY/xwiiQhlpBglvPAhsYK9EhFAcboP5z0eN1W3CQoROYyeY3psiCbwcleb9vaXasKAcvISw==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"rxjs-interop": "^2.0.0",
"tslib": "^2.5.0"
}
},
"node_modules/sucrase": {
"version": "3.32.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
@ -6995,6 +7220,17 @@
"node": ">=0.10.0"
}
},
"node_modules/worker-factory": {
"version": "6.0.76",
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-6.0.76.tgz",
"integrity": "sha512-W1iBNPmE9p0asU4aFmYJYCnMxhkvk4qlKc660GlHxWgmflY64NxxTbmKqipu4K5p9LiKKPjqXfcQme6153BZEQ==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"compilerr": "^10.0.2",
"fast-unique-numbers": "^7.0.2",
"tslib": "^2.5.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@ -8759,6 +8995,11 @@
"@types/react": "*"
}
},
"@types/recordrtc": {
"version": "5.6.11",
"resolved": "https://registry.npmjs.org/@types/recordrtc/-/recordrtc-5.6.11.tgz",
"integrity": "sha512-X4XD5nltz0cjmyzsPNegQReOPF+C5ARTfSPAPhqnKV7SsfRta/M4FBJ5AtSInCaEveL71FLLSVQE9mg8Uuo++w=="
},
"@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
@ -9032,6 +9273,15 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"automation-events": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/automation-events/-/automation-events-5.0.3.tgz",
"integrity": "sha512-ZZWTNYJTkGjcJUOBX5P0MHZrArJOkcrQsbyGWwlzJpZs961Y5YvKUw5MsAf8xLlvh7+1B8SO/VTvjMmVXFkD3w==",
"requires": {
"@babel/runtime": "^7.21.0",
"tslib": "^2.5.0"
}
},
"autoprefixer": {
"version": "10.4.14",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
@ -9113,6 +9363,17 @@
"fill-range": "^7.0.1"
}
},
"broker-factory": {
"version": "3.0.75",
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.0.75.tgz",
"integrity": "sha512-VMC2GBMaoKXdVPC0yH/Z1NbDieYRuKlGPT6PbrrReDwvHSZH2Cl5dJVO3tPTkA9Q+xXOmnRYgcjNktZD/Oz21w==",
"requires": {
"@babel/runtime": "^7.21.0",
"fast-unique-numbers": "^7.0.2",
"tslib": "^2.5.0",
"worker-factory": "^6.0.76"
}
},
"browserslist": {
"version": "4.21.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
@ -9241,6 +9502,17 @@
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true
},
"compilerr": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/compilerr/-/compilerr-10.0.2.tgz",
"integrity": "sha512-CFwUXxJ9OuWsSvnLSbefxi+GLsZ0YnuJh40ry5QdmZ1FWK59OG+QB8XSj6t7Kq+/c5DSS7en+cML6GlzHKH58A==",
"requires": {
"@babel/runtime": "^7.21.0",
"dashify": "^2.0.0",
"indefinite-article": "0.0.2",
"tslib": "^2.5.0"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -9294,6 +9566,11 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
"dashify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dashify/-/dashify-2.0.0.tgz",
"integrity": "sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A=="
},
"dayjs": {
"version": "1.11.7",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
@ -9914,6 +10191,52 @@
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"extendable-media-recorder": {
"version": "6.6.10",
"resolved": "https://registry.npmjs.org/extendable-media-recorder/-/extendable-media-recorder-6.6.10.tgz",
"integrity": "sha512-gnSmLqDFq40ZdbGfuarnMLNqYPLCPpPr0p21V+g67wG4Pv2oCc/ga8sfsZrEM5GywEi7FcpyRm3z99JWZ/0aPw==",
"requires": {
"@babel/runtime": "^7.18.9",
"media-encoder-host": "^8.0.76",
"multi-buffer-data-view": "^3.0.20",
"recorder-audio-worklet": "^5.1.26",
"standardized-audio-context": "^25.3.29",
"subscribable-things": "^2.1.6",
"tslib": "^2.4.0"
}
},
"extendable-media-recorder-wav-encoder": {
"version": "7.0.85",
"resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder/-/extendable-media-recorder-wav-encoder-7.0.85.tgz",
"integrity": "sha512-M96Y6twJ/OBordRltfyf8Kl2P2C0e176ogVTpGYquHApEyRnGpnm6LMmNqiRkQC8me0WOOhKLVvKCGmEF3lmvQ==",
"requires": {
"@babel/runtime": "^7.21.0",
"extendable-media-recorder-wav-encoder-broker": "^7.0.77",
"extendable-media-recorder-wav-encoder-worker": "^8.0.76",
"tslib": "^2.5.0"
}
},
"extendable-media-recorder-wav-encoder-broker": {
"version": "7.0.77",
"resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-broker/-/extendable-media-recorder-wav-encoder-broker-7.0.77.tgz",
"integrity": "sha512-HNUcsUMtKw8hUx1pHM/78+joa3L7FpCh+R3jX9RRuSrHMUw/ldq29wi/evBs4lMoasyMZLHD3yI1Phz0hYH7pg==",
"requires": {
"@babel/runtime": "^7.21.0",
"broker-factory": "^3.0.75",
"extendable-media-recorder-wav-encoder-worker": "^8.0.76",
"tslib": "^2.5.0"
}
},
"extendable-media-recorder-wav-encoder-worker": {
"version": "8.0.76",
"resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-worker/-/extendable-media-recorder-wav-encoder-worker-8.0.76.tgz",
"integrity": "sha512-m/pafu/XodKM05r3wkdY6D+gvprD6VCe1rLKnmF/Ooj4VGQ3DglsOhxWmEXRHVtzFWHIxIpjq6Z14VYaj1sXUg==",
"requires": {
"@babel/runtime": "^7.21.0",
"tslib": "^2.5.0",
"worker-factory": "^6.0.76"
}
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -9956,6 +10279,15 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"fast-unique-numbers": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-7.0.2.tgz",
"integrity": "sha512-xnqpsnu889bHbq5cbDMwCJ2BPf6kjFPMu+RHfqKvisRxeEbTOVxY5aW/ZNsZ/r8OlwatxmjdFEVQog2xAhLkvg==",
"requires": {
"@babel/runtime": "^7.21.0",
"tslib": "^2.5.0"
}
},
"fast-xml-parser": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.2.tgz",
@ -10293,6 +10625,11 @@
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true
},
"indefinite-article": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/indefinite-article/-/indefinite-article-0.0.2.tgz",
"integrity": "sha512-Au/2XzRkvxq2J6w5uvSSbBKPZ5kzINx5F2wb0SF8xpRL8BP9Lav81TnRbfPp6p+SYjYxwaaLn4EUwI3/MmYKSw=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -10688,6 +11025,40 @@
"yallist": "^4.0.0"
}
},
"media-encoder-host": {
"version": "8.0.88",
"resolved": "https://registry.npmjs.org/media-encoder-host/-/media-encoder-host-8.0.88.tgz",
"integrity": "sha512-15pWY8GFLsm3w01JuSQdEIkCh1+KUKqNQg5F6s5SXhAwbj0DuTceFbyuqWFMq+XzV8zZrPt8LtYAAj5ld5IEOw==",
"requires": {
"@babel/runtime": "^7.21.0",
"media-encoder-host-broker": "^7.0.78",
"media-encoder-host-worker": "^9.1.0",
"tslib": "^2.5.0"
}
},
"media-encoder-host-broker": {
"version": "7.0.78",
"resolved": "https://registry.npmjs.org/media-encoder-host-broker/-/media-encoder-host-broker-7.0.78.tgz",
"integrity": "sha512-5IZEZwN4qyCymkP6FCELS6rRCE2xhNFiE3FWr+8R3ySRn1iwPWd/U/YFB4PdvjqFw3MtQ9rHFErfc/YjUf8j9g==",
"requires": {
"@babel/runtime": "^7.21.0",
"broker-factory": "^3.0.75",
"fast-unique-numbers": "^7.0.2",
"media-encoder-host-worker": "^9.1.0",
"tslib": "^2.5.0"
}
},
"media-encoder-host-worker": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/media-encoder-host-worker/-/media-encoder-host-worker-9.1.0.tgz",
"integrity": "sha512-dVvb65RH/5Yt5PBxBVGw+BcCFkq8A15/fw3HxEKugtaFJocTcwQsS1IeDUuf1i831Fk/FuCipnXFz8VsGRCZyg==",
"requires": {
"@babel/runtime": "^7.21.0",
"extendable-media-recorder-wav-encoder-broker": "^7.0.77",
"tslib": "^2.5.0",
"worker-factory": "^6.0.76"
}
},
"memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@ -10765,6 +11136,15 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"multi-buffer-data-view": {
"version": "3.0.24",
"resolved": "https://registry.npmjs.org/multi-buffer-data-view/-/multi-buffer-data-view-3.0.24.tgz",
"integrity": "sha512-jm7Ycplx37ExXyQmqhwl7zfQmAj81y5LLzVx0XyWea4omP9W/xJhLEHs/5b+WojGyYSRt8BHiXZVcYzu68Ma0Q==",
"requires": {
"@babel/runtime": "^7.20.6",
"tslib": "^2.4.1"
}
},
"mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@ -11269,6 +11649,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-media-recorder": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/react-media-recorder/-/react-media-recorder-1.6.6.tgz",
"integrity": "sha512-VdC4bUINMWJyqOAHw1DaZ8HZhdCyVBK85zJ4cHMo9tsrekui3wq5ZxNtBmNe6nbAFQBTNj/pRnLEsiVrCW+TNQ==",
"requires": {
"extendable-media-recorder": "^6.6.5",
"extendable-media-recorder-wav-encoder": "^7.0.68"
}
},
"react-player": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/react-player/-/react-player-2.12.0.tgz",
@ -11314,6 +11703,35 @@
"picomatch": "^2.2.1"
}
},
"recorder-audio-worklet": {
"version": "5.1.39",
"resolved": "https://registry.npmjs.org/recorder-audio-worklet/-/recorder-audio-worklet-5.1.39.tgz",
"integrity": "sha512-w/RazoBwZnkFnEPRsJYNThOHznLQC98/IzWRrutpJQVvCcL0nbLsVSLDaRrnrqVpRUI11VgiXRh30HaHiSdVhQ==",
"requires": {
"@babel/runtime": "^7.21.0",
"broker-factory": "^3.0.75",
"fast-unique-numbers": "^7.0.2",
"recorder-audio-worklet-processor": "^4.2.21",
"standardized-audio-context": "^25.3.41",
"subscribable-things": "^2.1.14",
"tslib": "^2.5.0",
"worker-factory": "^6.0.76"
}
},
"recorder-audio-worklet-processor": {
"version": "4.2.21",
"resolved": "https://registry.npmjs.org/recorder-audio-worklet-processor/-/recorder-audio-worklet-processor-4.2.21.tgz",
"integrity": "sha512-oiiS2sp6eMxkvjt13yetSYUJvnAxBZk60mIxz0Vf/2lDWa/4svCyMLHIDzYKbHahkISd0UYyqLS9dI7xDlUOCA==",
"requires": {
"@babel/runtime": "^7.21.0",
"tslib": "^2.5.0"
}
},
"recordrtc": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/recordrtc/-/recordrtc-5.6.2.tgz",
"integrity": "sha512-1QNKKNtl7+KcwD1lyOgP3ZlbiJ1d0HtXnypUy7yq49xEERxk31PHvE9RCciDrulPCY7WJ+oz0R9hpNxgsIurGQ=="
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -11371,6 +11789,11 @@
"queue-microtask": "^1.2.2"
}
},
"rxjs-interop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/rxjs-interop/-/rxjs-interop-2.0.0.tgz",
"integrity": "sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw=="
},
"safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@ -11461,6 +11884,16 @@
"source-map": "^0.6.0"
}
},
"standardized-audio-context": {
"version": "25.3.43",
"resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.43.tgz",
"integrity": "sha512-Wh/6rZCScDh3ZAsRy0kgeGF+g3mGO4E6UiWj8k79uCHKJjqiBJOtbwEFS1MBFfVXMHN3L3U3kn1aVrkCHtlkvw==",
"requires": {
"@babel/runtime": "^7.21.0",
"automation-events": "^5.0.3",
"tslib": "^2.5.0"
}
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@ -11572,6 +12005,16 @@
"client-only": "0.0.1"
}
},
"subscribable-things": {
"version": "2.1.14",
"resolved": "https://registry.npmjs.org/subscribable-things/-/subscribable-things-2.1.14.tgz",
"integrity": "sha512-+0e6wGpFVa7snrwzY/xwiiQhlpBglvPAhsYK9EhFAcboP5z0eN1W3CQoROYyeY3psiCbwcleb9vaXasKAcvISw==",
"requires": {
"@babel/runtime": "^7.21.0",
"rxjs-interop": "^2.0.0",
"tslib": "^2.5.0"
}
},
"sucrase": {
"version": "3.32.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
@ -11939,6 +12382,17 @@
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"dev": true
},
"worker-factory": {
"version": "6.0.76",
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-6.0.76.tgz",
"integrity": "sha512-W1iBNPmE9p0asU4aFmYJYCnMxhkvk4qlKc660GlHxWgmflY64NxxTbmKqipu4K5p9LiKKPjqXfcQme6153BZEQ==",
"requires": {
"@babel/runtime": "^7.21.0",
"compilerr": "^10.0.2",
"fast-unique-numbers": "^7.0.2",
"tslib": "^2.5.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View file

@ -29,6 +29,7 @@
"@trpc/next": "^10.18.0",
"@trpc/react-query": "^10.18.0",
"@trpc/server": "^10.18.0",
"@types/recordrtc": "^5.6.11",
"@upstash/qstash": "^0.3.6",
"axios": "^1.3.5",
"dayjs": "^1.11.7",
@ -39,8 +40,10 @@
"next-auth": "^4.21.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-media-recorder": "^1.6.6",
"react-player": "^2.12.0",
"react-popper": "^2.3.0",
"recordrtc": "^5.6.2",
"stripe": "^12.1.1",
"superjson": "1.12.2",
"zod": "^3.21.4"

View file

@ -0,0 +1,272 @@
import React, { useState, useRef, Fragment, useEffect } from "react";
import RecordRTC, { invokeSaveAsDialog } from "recordrtc";
import { Listbox, Transition } from "@headlessui/react";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid";
import { MicrophoneIcon, PauseIcon } from "@heroicons/react/24/outline";
import { ResumeIcon, TrashIcon } from "@radix-ui/react-icons";
import { StopIcon } from "@heroicons/react/24/solid";
import StopTime from "~/components/StopTime";
import axios from "axios";
import dayjs from "dayjs";
import { useRouter } from "next/router";
import { api } from "~/utils/api";
export default function Recorder() {
const [steam, setStream] = useState<null | MediaStream>(null);
const [blob, setBlob] = useState<null | Blob>(null);
const refVideo = useRef<null | HTMLVideoElement>(null);
const recorderRef = useRef<null | RecordRTC>(null);
const [pause, setPause] = useState<boolean>(false);
const [audioDevices, setAudioDevices] = useState<MediaDeviceInfo[]>([]);
const [selectedDevice, setSelectedDevice] = useState<MediaDeviceInfo | null>(
null
);
const [step, setStep] = useState<"pre" | "in" | "post">("pre");
const router = useRouter();
const [submitting, setSubmitting] = useState<boolean>(false);
const apiUtils = api.useContext();
const getSignedUrl = api.video.getUploadUrl.useMutation();
const handleRecording = async () => {
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: {
width: 1920,
height: 1080,
frameRate: 30,
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100,
},
});
const micStream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: selectedDevice?.deviceId },
});
const mediaStream = new MediaStream();
micStream.getAudioTracks().forEach((track) => mediaStream.addTrack(track));
screenStream
.getVideoTracks()
.forEach((track) => mediaStream.addTrack(track));
setStream(mediaStream);
recorderRef.current = new RecordRTC(mediaStream, { type: "video" });
recorderRef.current.startRecording();
setStep("in");
};
const handleStop = () => {
if (recorderRef.current === null) return;
recorderRef.current.stopRecording(() => {
if (recorderRef.current) {
setBlob(recorderRef.current.getBlob());
steam?.getTracks().map((track) => track.stop());
}
});
setStep("post");
};
const handleDelete = () => {
if (recorderRef.current === null) return;
setBlob(null);
recorderRef.current.stopRecording(() => {
steam?.getTracks().map((track) => track.stop());
});
};
const handlePause = () => {
if (recorderRef.current) {
console.log(recorderRef.current?.state);
if (pause) {
recorderRef.current?.resumeRecording();
} else {
recorderRef.current.pauseRecording();
}
setPause(!pause);
}
};
useEffect(() => {
async function getAudioDevices() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevices = devices.filter(
(device) => device.kind === "audioinput"
);
setAudioDevices(audioDevices);
if (audioDevices[0]) setSelectedDevice(audioDevices[0]);
} catch (error) {
console.error(error);
}
}
void getAudioDevices();
}, []);
const handleSave = () => {
if (blob) {
const dateString =
"Recording - " + dayjs().format("D MMM YYYY") + ".webm";
invokeSaveAsDialog(blob, dateString);
}
};
const handleUpload = async () => {
if (!blob) return;
const dateString = "Recording - " + dayjs().format("D MMM YYYY") + ".webm";
setSubmitting(true);
const { signedUrl, id } = await getSignedUrl.mutateAsync({
key: dateString,
});
await axios
.put(signedUrl, blob.slice(), {
headers: { "Content-Type": "video/webm" },
})
.then(() => {
void router.push("share/" + id);
})
.catch((err) => {
console.error(err);
});
setSubmitting(false);
void apiUtils.video.getAll.invalidate();
};
return (
<div>
{step === "pre" ? (
<div className="w-full">
<Listbox value={selectedDevice} onChange={setSelectedDevice}>
<div className="relative mt-1">
<Listbox.Button className="relative flex w-full cursor-default flex-row items-center justify-start rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 sm:text-sm">
<MicrophoneIcon
className="mr-2 h-5 w-5 text-gray-400"
aria-hidden="true"
/>
<span className="block truncate">
{selectedDevice?.label ?? "No device selected"}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</Listbox.Button>
<Transition
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{audioDevices.map((audioDevice, i) => (
<Listbox.Option
key={i}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 text-gray-900 ${
active ? "bg-gray-200" : ""
}`
}
value={audioDevice}
>
{({ selected }) => (
<>
<span
className={`block truncate ${
selected ? "font-medium" : "font-normal"
}`}
>
{audioDevice.label}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">
<CheckIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
<button
type="button"
className="mt-4 flex inline-flex w-full items-center items-center justify-center rounded-md bg-indigo-500 px-4 py-2 text-sm font-semibold leading-6 text-white shadow transition duration-150 ease-in-out hover:bg-indigo-400 disabled:cursor-not-allowed"
onClick={() => void handleRecording()}
>
<span>Start recording</span>
</button>
</div>
) : null}
{step === "in" ? (
<div className="flex flex-row items-center justify-center">
<div
onClick={handleStop}
className="flex cursor-pointer flex-row items-center justify-center rounded pr-2 text-lg hover:bg-gray-200"
>
<StopIcon className="h-8 w-8 text-[#ff623f]" aria-hidden="true" />
<StopTime running={!pause} />
</div>
<div className="mx-2 h-6 w-px bg-[#E7E9EB]"></div>
<div
onClick={handlePause}
className="cursor-pointer rounded p-1 hover:bg-gray-200"
>
{pause ? (
<ResumeIcon
className="h-6 w-6 text-gray-400"
aria-hidden="true"
/>
) : (
<PauseIcon className="h-6 w-6 text-gray-400" aria-hidden="true" />
)}
</div>
<div
onClick={handleDelete}
className="ml-1 cursor-pointer rounded p-1 hover:bg-gray-200"
>
<TrashIcon className="h-6 w-6 text-gray-400" aria-hidden="true" />
</div>
</div>
) : null}
{step === "post" ? (
<div>
{blob && (
<video
src={URL.createObjectURL(blob)}
controls
autoPlay
ref={refVideo}
style={{ width: "700px", margin: "1em" }}
/>
)}
<button
onClick={handleSave}
className="mt-2 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Download
</button>
<button
onClick={() => void handleUpload()}
className="ml-2 mt-2 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
>
Upload
</button>
</div>
) : null}
</div>
);
}

View file

@ -0,0 +1,39 @@
import React, { useEffect, useState } from "react";
export default function StopTime({ running }: { running?: boolean }) {
const [secs, setSecs] = useState<number>(0);
const calculateTimeDuration = (secs: number): string => {
const hr = Math.floor(secs / 3600).toString();
let min = Math.floor((secs - parseInt(hr) * 3600) / 60).toString();
let sec = Math.floor(
secs - parseInt(hr) * 3600 - parseInt(min) * 60
).toString();
if (parseInt(min) < 10) {
min = "0" + min;
}
if (parseInt(sec) < 10) {
sec = "0" + sec;
}
if (parseInt(hr) <= 0) {
return min + ":" + sec;
}
return hr + ":" + min + ":" + sec;
};
useEffect(() => {
if (!running) return;
const interval = setInterval(() => setSecs((sec) => sec + 1), 1000);
return () => clearInterval(interval);
}, [running]);
return (
<div className="ml-1 flex w-10 items-center justify-center">
<span className="text-sm">{calculateTimeDuration(secs)}</span>
</div>
);
}

View file

@ -3,6 +3,8 @@ import { Dialog, Transition } from "@headlessui/react";
import { api } from "~/utils/api";
import axios from "axios";
import { useRouter } from "next/router";
const Recorder = dynamic(() => import("~/components/Recorder"), { ssr: false });
import dynamic from "next/dynamic";
export default function VideoUploadModal() {
const router = useRouter();
@ -56,19 +58,11 @@ export default function VideoUploadModal() {
</span>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<Dialog
as="div"
className="relative z-10"
onClose={() => void closeModal}
>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
@ -80,77 +74,81 @@ export default function VideoUploadModal() {
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-lg bg-white p-6 text-left align-middle shadow-xl transition-all">
<div className="flex flex-col items-center gap-2">
<label className="flex h-32 w-full min-w-[300px] cursor-pointer appearance-none justify-center rounded-md border-2 border-dashed border-gray-300 px-4 transition hover:border-gray-400 focus:outline-none">
<span className="flex items-center space-x-2 text-[#292D34]">
{file ? (
<span className="font-medium">{file.name}</span>
) : (
<Dialog.Panel className="w-full max-w-md transform rounded-lg bg-white p-6 text-left align-middle shadow-xl transition-all">
{1 + 1 === 2 ? (
<Recorder />
) : (
<div className="flex flex-col items-center gap-2">
<label className="flex h-32 w-full min-w-[300px] cursor-pointer appearance-none justify-center rounded-md border-2 border-dashed border-gray-300 px-4 transition hover:border-gray-400 focus:outline-none">
<span className="flex items-center space-x-2 text-[#292D34]">
{file ? (
<span className="font-medium">{file.name}</span>
) : (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
</svg>
<span className="font-medium">
{"Drop files to Attach, or browse"}
</span>
</>
)}
</span>
<input
accept="video/mp4,video/webm"
onChange={handleFileChange}
type="file"
name="file_upload"
className="hidden"
/>
</label>
<button
type="button"
className="mt-4 inline-flex items-center rounded-md bg-indigo-500 px-4 py-2 text-sm font-semibold leading-6 text-white shadow transition duration-150 ease-in-out hover:bg-indigo-400 disabled:cursor-not-allowed"
disabled={submitting}
onClick={() => void handleSubmit()}
>
{submitting ? (
<>
<svg
className="-ml-1 mr-3 h-5 w-5 animate-spin text-white"
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/>
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<span className="font-medium">
{"Drop files to Attach, or browse"}
</span>
Uploading...
</>
) : (
<>Upload</>
)}
</span>
<input
accept="video/mp4,video/webm"
onChange={handleFileChange}
type="file"
name="file_upload"
className="hidden"
/>
</label>
<button
type="button"
className="mt-4 inline-flex items-center rounded-md bg-indigo-500 px-4 py-2 text-sm font-semibold leading-6 text-white shadow transition duration-150 ease-in-out hover:bg-indigo-400 disabled:cursor-not-allowed"
disabled={submitting}
onClick={() => void handleSubmit()}
>
{submitting ? (
<>
<svg
className="-ml-1 mr-3 h-5 w-5 animate-spin text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Uploading...
</>
) : (
<>Upload</>
)}
</button>
</div>
</button>
</div>
)}
</Dialog.Panel>
</Transition.Child>
</div>