add video duration metadata to blob
This commit is contained in:
parent
3aec440f1e
commit
09286a6ed8
4 changed files with 79 additions and 20 deletions
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -27,6 +27,7 @@
|
|||
"axios": "^1.3.5",
|
||||
"dayjs": "^1.11.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"fix-webm-duration": "^1.0.5",
|
||||
"micro": "^10.0.1",
|
||||
"micro-cors": "^0.1.1",
|
||||
"next": "^13.3.0",
|
||||
|
|
@ -4230,6 +4231,11 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/fix-webm-duration": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/fix-webm-duration/-/fix-webm-duration-1.0.5.tgz",
|
||||
"integrity": "sha512-b6oula3OfSknx0aWoLsxvp4DVIYbwsf+UAkr6EDAK3iuMYk/OSNKzmeSI61GXK0MmFTEuzle19BPvTxMIKjkZg=="
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
|
||||
|
|
@ -10338,6 +10344,11 @@
|
|||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"fix-webm-duration": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/fix-webm-duration/-/fix-webm-duration-1.0.5.tgz",
|
||||
"integrity": "sha512-b6oula3OfSknx0aWoLsxvp4DVIYbwsf+UAkr6EDAK3iuMYk/OSNKzmeSI61GXK0MmFTEuzle19BPvTxMIKjkZg=="
|
||||
},
|
||||
"flat-cache": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"axios": "^1.3.5",
|
||||
"dayjs": "^1.11.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"fix-webm-duration": "^1.0.5",
|
||||
"micro": "^10.0.1",
|
||||
"micro-cors": "^0.1.1",
|
||||
"next": "^13.3.0",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import axios from "axios";
|
|||
import dayjs from "dayjs";
|
||||
import { useRouter } from "next/router";
|
||||
import { api } from "~/utils/api";
|
||||
import fixWebmDuration from "fix-webm-duration";
|
||||
|
||||
export default function Recorder() {
|
||||
const [steam, setStream] = useState<null | MediaStream>(null);
|
||||
|
|
@ -26,6 +27,7 @@ export default function Recorder() {
|
|||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
const apiUtils = api.useContext();
|
||||
const getSignedUrl = api.video.getUploadUrl.useMutation();
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
|
||||
const handleRecording = async () => {
|
||||
const screenStream = await navigator.mediaDevices.getDisplayMedia({
|
||||
|
|
@ -62,7 +64,13 @@ export default function Recorder() {
|
|||
if (recorderRef.current === null) return;
|
||||
recorderRef.current.stopRecording(() => {
|
||||
if (recorderRef.current) {
|
||||
setBlob(recorderRef.current.getBlob());
|
||||
fixWebmDuration(
|
||||
recorderRef.current.getBlob(),
|
||||
duration * 1000,
|
||||
(seekableBlob) => {
|
||||
setBlob(seekableBlob);
|
||||
}
|
||||
);
|
||||
steam?.getTracks().map((track) => track.stop());
|
||||
}
|
||||
});
|
||||
|
|
@ -218,7 +226,11 @@ export default function Recorder() {
|
|||
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} />
|
||||
<StopTime
|
||||
running={!pause}
|
||||
duration={duration}
|
||||
setDuration={setDuration}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx-2 h-6 w-px bg-[#E7E9EB]"></div>
|
||||
<div
|
||||
|
|
@ -253,18 +265,49 @@ export default function Recorder() {
|
|||
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 className="flex items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
className="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"
|
||||
onClick={() => void handleSave()}
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-2 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 handleUpload()}
|
||||
>
|
||||
{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>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
export default function StopTime({ running }: { running?: boolean }) {
|
||||
const [secs, setSecs] = useState<number>(0);
|
||||
interface Props {
|
||||
running: boolean;
|
||||
duration: number;
|
||||
setDuration: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export default function StopTime({ running, duration, setDuration }: Props) {
|
||||
const calculateTimeDuration = (secs: number): string => {
|
||||
const hr = Math.floor(secs / 3600).toString();
|
||||
let min = Math.floor((secs - parseInt(hr) * 3600) / 60).toString();
|
||||
|
|
@ -27,13 +31,13 @@ export default function StopTime({ running }: { running?: boolean }) {
|
|||
|
||||
useEffect(() => {
|
||||
if (!running) return;
|
||||
const interval = setInterval(() => setSecs((sec) => sec + 1), 1000);
|
||||
const interval = setInterval(() => setDuration((sec) => sec + 1), 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [running]);
|
||||
}, [running, setDuration]);
|
||||
|
||||
return (
|
||||
<div className="ml-1 flex w-10 items-center justify-center">
|
||||
<span className="text-sm">{calculateTimeDuration(secs)}</span>
|
||||
<span className="text-sm">{calculateTimeDuration(duration)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue