From 48c2702701f2df4ff24837dd4c11394cbded1990 Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Tue, 18 Apr 2023 22:20:30 +0200 Subject: [PATCH 1/8] add screen recording using recordRTC --- package-lock.json | 454 ++++++++++++++++++++++++++++ package.json | 3 + src/components/Recorder.tsx | 170 +++++++++++ src/components/VideoUploadModal.tsx | 126 ++++---- 4 files changed, 693 insertions(+), 60 deletions(-) create mode 100644 src/components/Recorder.tsx diff --git a/package-lock.json b/package-lock.json index beabac6..0af3176 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 9e33111..11919e5 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,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", @@ -38,8 +39,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" diff --git a/src/components/Recorder.tsx b/src/components/Recorder.tsx new file mode 100644 index 0000000..36e7007 --- /dev/null +++ b/src/components/Recorder.tsx @@ -0,0 +1,170 @@ +import React, { useState, useRef, Fragment } from "react"; +import RecordRTC, { invokeSaveAsDialog } from "recordrtc"; +import { Listbox, Transition } from "@headlessui/react"; +import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid"; + +const people = [ + { name: "Wade Cooper" }, + { name: "Arlene Mccoy" }, + { name: "Devon Webb" }, + { name: "Tom Cook" }, + { name: "Tanya Fox" }, + { name: "Hellen Schmidt" }, +]; + +export default function Recorder() { + const [steam, setStream] = useState(null); + const [blob, setBlob] = useState(null); + const refVideo = useRef(null); + const recorderRef = useRef(null); + const [pause, setPause] = useState(false); + const [selected, setSelected] = useState<{ name: string }>(people[0]); + + const handleRecording = async () => { + const mediaStream = await navigator.mediaDevices.getDisplayMedia({ + video: { + width: 1920, + height: 1080, + frameRate: 30, + }, + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 44100, + }, + }); + + setStream(mediaStream); + recorderRef.current = new RecordRTC(mediaStream, { type: "video" }); + recorderRef.current.startRecording(); + }; + + const handleStop = () => { + if (recorderRef.current === null) return; + recorderRef.current.stopRecording(() => { + if (recorderRef.current) { + setBlob(recorderRef.current.getBlob()); + 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); + } + }; + + const handleSave = () => { + if (blob) { + invokeSaveAsDialog(blob); + } + }; + + return ( +
+
+ + + + +
+ +
+ + {selected.name} + + + + + + {people.map((person, personIdx) => ( + + `relative cursor-default select-none py-2 pl-10 pr-4 ${ + active + ? "bg-amber-100 text-amber-900" + : "text-gray-900" + }` + } + value={person} + > + {({ selected }) => ( + <> + + {person.name} + + {selected ? ( + + + ) : null} + + )} + + ))} + + +
+
+
+ {blob && ( +
+
+ ); +} diff --git a/src/components/VideoUploadModal.tsx b/src/components/VideoUploadModal.tsx index 7180f8c..b87b4d0 100644 --- a/src/components/VideoUploadModal.tsx +++ b/src/components/VideoUploadModal.tsx @@ -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(); @@ -81,76 +83,80 @@ export default function VideoUploadModal() { leaveTo="opacity-0 scale-95" > -
-
+ )}
From 44e78b16d95c1d67528fd9138ed63f3324ce4f84 Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Tue, 18 Apr 2023 23:05:05 +0200 Subject: [PATCH 2/8] add microphone recording and allow user to select audio device --- src/components/Recorder.tsx | 61 +++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/components/Recorder.tsx b/src/components/Recorder.tsx index 36e7007..e3b269b 100644 --- a/src/components/Recorder.tsx +++ b/src/components/Recorder.tsx @@ -1,27 +1,21 @@ -import React, { useState, useRef, Fragment } from "react"; +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"; -const people = [ - { name: "Wade Cooper" }, - { name: "Arlene Mccoy" }, - { name: "Devon Webb" }, - { name: "Tom Cook" }, - { name: "Tanya Fox" }, - { name: "Hellen Schmidt" }, -]; - export default function Recorder() { const [steam, setStream] = useState(null); const [blob, setBlob] = useState(null); const refVideo = useRef(null); const recorderRef = useRef(null); const [pause, setPause] = useState(false); - const [selected, setSelected] = useState<{ name: string }>(people[0]); + const [audioDevices, setAudioDevices] = useState([]); + const [selectedDevice, setSelectedDevice] = useState( + null + ); const handleRecording = async () => { - const mediaStream = await navigator.mediaDevices.getDisplayMedia({ + const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: { width: 1920, height: 1080, @@ -34,6 +28,16 @@ export default function Recorder() { }, }); + 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(); @@ -61,6 +65,23 @@ export default function Recorder() { } }; + 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) { invokeSaveAsDialog(blob); @@ -98,11 +119,13 @@ export default function Recorder() { > save -
- +
+
- {selected.name} + + {selectedDevice?.label ?? "No device selected"} + - {people.map((person, personIdx) => ( + {audioDevices.map((audioDevice, i) => ( `relative cursor-default select-none py-2 pl-10 pr-4 ${ active @@ -127,7 +150,7 @@ export default function Recorder() { : "text-gray-900" }` } - value={person} + value={audioDevice} > {({ selected }) => ( <> @@ -136,7 +159,7 @@ export default function Recorder() { selected ? "font-medium" : "font-normal" }`} > - {person.name} + {audioDevice.label} {selected ? ( From e757f7cfd82f636862280b0b8e3be8164231f10c Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Wed, 19 Apr 2023 00:09:41 +0200 Subject: [PATCH 3/8] add in-recording step buttons --- src/components/Recorder.tsx | 121 +++++++++++++++++----------- src/components/VideoUploadModal.tsx | 8 +- 2 files changed, 80 insertions(+), 49 deletions(-) diff --git a/src/components/Recorder.tsx b/src/components/Recorder.tsx index e3b269b..5a28dc1 100644 --- a/src/components/Recorder.tsx +++ b/src/components/Recorder.tsx @@ -2,6 +2,9 @@ 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 { TrashIcon } from "@radix-ui/react-icons"; +import { StopIcon } from "@heroicons/react/24/solid"; export default function Recorder() { const [steam, setStream] = useState(null); @@ -13,6 +16,7 @@ export default function Recorder() { const [selectedDevice, setSelectedDevice] = useState( null ); + const [step, setStep] = useState<"pre" | "in" | "post">("pre"); const handleRecording = async () => { const screenStream = await navigator.mediaDevices.getDisplayMedia({ @@ -41,6 +45,8 @@ export default function Recorder() { setStream(mediaStream); recorderRef.current = new RecordRTC(mediaStream, { type: "video" }); recorderRef.current.startRecording(); + + setStep("in"); }; const handleStop = () => { @@ -53,6 +59,14 @@ export default function Recorder() { }); }; + 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); @@ -89,40 +103,16 @@ export default function Recorder() { }; return ( -
-
- - - - -
+
+ {step === "pre" ? ( +
- + + @@ -144,10 +137,8 @@ export default function Recorder() { - `relative cursor-default select-none py-2 pl-10 pr-4 ${ - active - ? "bg-amber-100 text-amber-900" - : "text-gray-900" + `relative cursor-default select-none py-2 pl-10 pr-4 text-gray-900 ${ + active ? "bg-gray-200" : "" }` } value={audioDevice} @@ -177,17 +168,53 @@ export default function Recorder() {
+
- {blob && ( -
+ ) : null} + {step === "in" ? ( +
+
+
+
+
+
+
+
+
+ ) : null} + {step === "post" ? ( +
+ post recording + {blob && ( +
+ ) : null}
); } diff --git a/src/components/VideoUploadModal.tsx b/src/components/VideoUploadModal.tsx index b87b4d0..0257b43 100644 --- a/src/components/VideoUploadModal.tsx +++ b/src/components/VideoUploadModal.tsx @@ -58,7 +58,11 @@ export default function VideoUploadModal() {
- + void closeModal} + > - + {1 + 1 === 2 ? ( ) : ( From bb5be1dcca96be52bf10aa47f2a9020b688009ee Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Wed, 19 Apr 2023 00:35:57 +0200 Subject: [PATCH 4/8] add recording duration calculator --- src/components/Recorder.tsx | 3 ++- src/components/StopTime.tsx | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/components/StopTime.tsx diff --git a/src/components/Recorder.tsx b/src/components/Recorder.tsx index 5a28dc1..322a294 100644 --- a/src/components/Recorder.tsx +++ b/src/components/Recorder.tsx @@ -5,6 +5,7 @@ import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid"; import { MicrophoneIcon, PauseIcon } from "@heroicons/react/24/outline"; import { TrashIcon } from "@radix-ui/react-icons"; import { StopIcon } from "@heroicons/react/24/solid"; +import StopTime from "~/components/StopTime"; export default function Recorder() { const [steam, setStream] = useState(null); @@ -184,7 +185,7 @@ export default function Recorder() { className="flex cursor-pointer flex-row items-center justify-center rounded pr-2 text-lg hover:bg-gray-200" >
(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 {calculateTimeDuration(secs)}; +} From 4b46ed7b4bd500a231a1468c781035e1ddff271b Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Wed, 19 Apr 2023 00:39:09 +0200 Subject: [PATCH 5/8] add resume icon while paused --- src/components/Recorder.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Recorder.tsx b/src/components/Recorder.tsx index 322a294..0ffbfcb 100644 --- a/src/components/Recorder.tsx +++ b/src/components/Recorder.tsx @@ -3,7 +3,7 @@ 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 { TrashIcon } from "@radix-ui/react-icons"; +import { ResumeIcon, TrashIcon } from "@radix-ui/react-icons"; import { StopIcon } from "@heroicons/react/24/solid"; import StopTime from "~/components/StopTime"; @@ -192,7 +192,14 @@ export default function Recorder() { onClick={handlePause} className="cursor-pointer rounded p-1 hover:bg-gray-200" > -
Date: Wed, 19 Apr 2023 00:46:23 +0200 Subject: [PATCH 6/8] prevent the counter from wiggling when the time changes --- src/components/StopTime.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/StopTime.tsx b/src/components/StopTime.tsx index a44f51e..a0a523a 100644 --- a/src/components/StopTime.tsx +++ b/src/components/StopTime.tsx @@ -31,5 +31,9 @@ export default function StopTime({ running }: { running?: boolean }) { return () => clearInterval(interval); }, [running]); - return {calculateTimeDuration(secs)}; + return ( +
+ {calculateTimeDuration(secs)} +
+ ); } From fff2e44a0fb60b4745bf677cd5e0f5efc193ce4f Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:20:57 +0200 Subject: [PATCH 7/8] move to post-recording step after stopping recording --- src/components/Recorder.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Recorder.tsx b/src/components/Recorder.tsx index 0ffbfcb..ff2ad4f 100644 --- a/src/components/Recorder.tsx +++ b/src/components/Recorder.tsx @@ -58,6 +58,8 @@ export default function Recorder() { steam?.getTracks().map((track) => track.stop()); } }); + + setStep("post"); }; const handleDelete = () => { From 58739430a5288e471f060b286295843cc2475f56 Mon Sep 17 00:00:00 2001 From: MarconLP <13001502+MarconLP@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:32:49 +0200 Subject: [PATCH 8/8] add ability to download and upload recorded video --- src/components/Recorder.tsx | 46 +++++++++++++++++++++++++++-- src/components/VideoUploadModal.tsx | 12 -------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/components/Recorder.tsx b/src/components/Recorder.tsx index ff2ad4f..2163a50 100644 --- a/src/components/Recorder.tsx +++ b/src/components/Recorder.tsx @@ -6,6 +6,10 @@ 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); @@ -18,6 +22,10 @@ export default function Recorder() { null ); const [step, setStep] = useState<"pre" | "in" | "post">("pre"); + const router = useRouter(); + const [submitting, setSubmitting] = useState(false); + const apiUtils = api.useContext(); + const getSignedUrl = api.video.getUploadUrl.useMutation(); const handleRecording = async () => { const screenStream = await navigator.mediaDevices.getDisplayMedia({ @@ -101,10 +109,33 @@ export default function Recorder() { const handleSave = () => { if (blob) { - invokeSaveAsDialog(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 (
{step === "pre" ? ( @@ -213,7 +244,6 @@ export default function Recorder() { ) : null} {step === "post" ? (
- post recording {blob && (
) : null}
diff --git a/src/components/VideoUploadModal.tsx b/src/components/VideoUploadModal.tsx index 0257b43..8dc0450 100644 --- a/src/components/VideoUploadModal.tsx +++ b/src/components/VideoUploadModal.tsx @@ -63,18 +63,6 @@ export default function VideoUploadModal() { className="relative z-10" onClose={() => void closeModal} > - -
- -