Skip to content
This repository has been archived by the owner on Jan 15, 2025. It is now read-only.

Commit

Permalink
Merge pull request #1462 from ecency/feature/video-upload-camera-swit…
Browse files Browse the repository at this point in the history
…ching

Video recorder: Added camera switcher
  • Loading branch information
feruzm authored Sep 10, 2023
2 parents 9967acf + f168283 commit 9883a6d
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 42 deletions.
4 changes: 4 additions & 0 deletions src/common/api/threespeak/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export function useThreeSpeakVideo(
const apiQuery = useQuery(
[QueryIdentifiers.THREE_SPEAK_VIDEO_LIST, activeUser?.username ?? ""],
async () => {
if (!activeUser) {
return [];
}

try {
return await getAllVideoStatuses(activeUser!.username);
} catch (e) {
Expand Down
19 changes: 18 additions & 1 deletion src/common/components/video-upload-threespeak/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,26 @@ label {
left: 0;
right: 0;
z-index: 9;
display: flex;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
justify-content: center;

div {
display: flex;
align-items: center;
justify-content: center;
}

.switch-camera {
opacity: 0.75;
cursor: pointer;
color: $white;

&:hover {
opacity: 1;
}
}

.record-btn {
color: $danger;
border: 0.25rem solid $white;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./use-get-camera-list";
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useState } from "react";
import useMount from "react-use/lib/useMount";

/**
* Retrieves a list of available cameras.
*
* @return {MediaDeviceInfo[]} The list of available cameras.
*/
export function useGetCameraList() {
const [list, setList] = useState<MediaDeviceInfo[]>([]);

useMount(async () => {
const devices = await navigator.mediaDevices.enumerateDevices();
setList(devices.filter(({ kind }) => kind === "videoinput"));
});

return list;
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,66 @@
import { circleSvg, rectSvg } from "../../img/svg";
import { circleSvg, rectSvg, switchCameraSvg } from "../../img/svg";
import React, { useState } from "react";
import { useGetCameraList } from "./utils";

interface Props {
noPermission: boolean;
mediaRecorder?: MediaRecorder;
onCameraSelect: (camera: MediaDeviceInfo) => void;
}

export function VideoUploadRecorderActions({ noPermission, mediaRecorder }: Props) {
export function VideoUploadRecorderActions({ noPermission, mediaRecorder, onCameraSelect }: Props) {
const cameraList = useGetCameraList();

const [currentCameraIndex, setCurrentCameraIndex] = useState(0);
const [recordStarted, setRecordStarted] = useState(false);

const getNextCameraIndex = (index: number) => (index + 1) % cameraList.length;

return (
<div className="actions">
{recordStarted ? (
<div
aria-disabled={noPermission}
className="record-btn"
onClick={() => {
mediaRecorder?.stop();
setRecordStarted(false);
}}
>
{rectSvg}
</div>
) : (
<div
aria-disabled={noPermission}
className="record-btn"
onClick={() => {
mediaRecorder?.start();
setRecordStarted(true);
}}
>
{circleSvg}
</div>
)}
<div>
{!recordStarted && cameraList.length > 1 ? (
<div
className="switch-camera"
onClick={() => {
const nextCameraIndex = getNextCameraIndex(currentCameraIndex);
onCameraSelect(cameraList[nextCameraIndex]);
setCurrentCameraIndex(nextCameraIndex);
}}
>
{switchCameraSvg}
</div>
) : (
<></>
)}
</div>

<div>
{recordStarted ? (
<div
aria-disabled={noPermission}
className="record-btn"
onClick={() => {
mediaRecorder?.stop();
setRecordStarted(false);
}}
>
{rectSvg}
</div>
) : (
<div
aria-disabled={noPermission}
className="record-btn"
onClick={() => {
mediaRecorder?.start();
setRecordStarted(true);
}}
>
{circleSvg}
</div>
)}
</div>
<div />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function VideoUploadRecorder({
const [recordedVideoSrc, setRecordedVideoSrc] = useState<string>();
const [recordedBlob, setRecordedBlob] = useState<Blob>();
const [noPermission, setNoPermission] = useState(false);
const [currentCamera, setCurrentCamera] = useState<MediaDeviceInfo>();

const ref = useRef<HTMLVideoElement | null>(null);

Expand All @@ -39,10 +40,27 @@ export function VideoUploadRecorder({
isSuccess
} = useThreeSpeakVideoUpload("video");

useMount(async () => {
useMount(() => initStream());

useUnmount(() => {
stream?.getTracks().forEach((track) => track.stop());
});

useEffect(() => {
initStream();
}, [currentCamera]);

useEffect(() => {
if (stream && ref.current) {
// @ts-ignore
ref.current?.srcObject = stream;
}
}, [stream, ref]);

const initStream = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
video: currentCamera ? { deviceId: currentCamera.deviceId } : true,
audio: true
});
const mediaRecorder = new MediaRecorder(stream, {
Expand All @@ -60,20 +78,10 @@ export function VideoUploadRecorder({
}
});
} catch (e) {
console.error("Video recording:", e);
setNoPermission(true);
}
});

useUnmount(() => {
stream?.getTracks().forEach((track) => track.stop());
});

useEffect(() => {
if (stream && ref.current) {
// @ts-ignore
ref.current?.srcObject = stream;
}
}, [stream, ref]);
};

return (
<div className="video-upload-recorder">
Expand All @@ -86,7 +94,18 @@ export function VideoUploadRecorder({
)}

{!noPermission && !recordedVideoSrc ? (
<VideoUploadRecorderActions noPermission={noPermission} mediaRecorder={mediaRecorder} />
<VideoUploadRecorderActions
noPermission={noPermission}
mediaRecorder={mediaRecorder}
onCameraSelect={(camera) => {
stream
?.getTracks()
.filter(({ kind }) => kind === "video")
.forEach((track) => track.stop());

setCurrentCamera(camera);
}}
/>
) : (
<></>
)}
Expand Down
21 changes: 20 additions & 1 deletion src/common/img/svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2052,7 +2052,7 @@ export const recordVideoSvg = (
/>
<path
d="M10.5 12C10.5 13.3807 9.38071 14.5 8 14.5C6.61929 14.5 5.5 13.3807 5.5 12C5.5 10.6193 6.61929 9.5 8 9.5C9.38071 9.5 10.5 10.6193 10.5 12Z"
stroke="#1C274C"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M8 14.5H16" stroke="#1C274C" strokeWidth="1.5" strokeLinecap="round" />
Expand All @@ -2076,3 +2076,22 @@ export const rectSvg = (
<rect x={6} y={6} fill="currentColor" width={12} height={12} />
</svg>
);

export const switchCameraSvg = (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M11 19H4a2 2 0 01-2-2V7a2 2 0 012-2h5" />
<path d="M13 5h7a2 2 0 012 2v10a2 2 0 01-2 2h-5" />
<circle cx="12" cy="12" r="3" />
<path d="M18 22l-3-3 3-3" />
<path d="M6 2l3 3-3 3" />
</svg>
);

0 comments on commit 9883a6d

Please sign in to comment.