Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 634 바코드리더 초기값 변경 #638

Merged
merged 4 commits into from
Nov 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 58 additions & 54 deletions src/component/utils/BarcodeReader.tsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,94 @@
import { useState, useRef, useEffect, useCallback } from "react";
import { BrowserMultiFormatReader, Result } from "@zxing/library";
import "../../asset/css/BarcodeReader.css";
import { useState, useRef, useEffect, ChangeEventHandler } from "react";
import { BrowserMultiFormatReader, Result, BrowserQRCodeReader } from "@zxing/library";
import { useNewDialog } from "~/hook/useNewDialog";

type Props = {
wrapperClassName?: string;
toDoAfterRead: (text: string) => void;
};

const updateNewData = <T,>(prevData: T, newData: T) =>
prevData === newData ? prevData : newData;

const codeReader = new BrowserMultiFormatReader();

const BarcodeReader = ({ toDoAfterRead, wrapperClassName = "" }: Props) => {
const [videoDeviceList, setVideoDeviceList] = useState<MediaDeviceInfo[]>([]);
const [selectedDeviceId, setSelectedDeviceId] = useState("");
const videoRef = useRef<HTMLVideoElement>(null);

const { addDialogWithTitleAndMessage } = useNewDialog();
const alertError = (message: string) => {
addDialogWithTitleAndMessage("barcodeReader", "바코드 리더 에러", message);
};

const loadVideoInputDeviceList = useCallback(async () => {
try {
const videoDeviceList = await codeReader.listVideoInputDevices();
setVideoDeviceList(videoDeviceList);
} catch (error) {
alertError("기기 목록을 불러오는데 실패했습니다.");
setVideoDeviceList([]);
}
}, []);

useEffect(() => {
/** 바코드리더 초기 로드시, 사용가능한 카메라 목록을 가져옵니다. */
const loadVideoInputDeviceList = async () => {
let videoDeviceList: MediaDeviceInfo[] = [];
try {
videoDeviceList = await codeReader.listVideoInputDevices();
} catch (error) {
console.error(error);
}
setVideoDeviceList((prev) => updateNewData(prev, videoDeviceList));
};
loadVideoInputDeviceList();
return () => codeReader.reset();
}, []);

useEffect(() => {
/** 바코드리더 초기 로드시 & 목록 변화시, 사용가능한 카메라 목록중 하나를 디폴트로 선택합니다. */
if (videoDeviceList.length) {
const firstVideoDeviceId = videoDeviceList[0].deviceId;
setSelectedDeviceId((prev) => updateNewData(prev, firstVideoDeviceId));
}
if (!videoDeviceList.length) return;
/** 사용가능한 기기중 마지막, 주로 후면카메라를 기본으로 설정 */
const initialDevice = videoDeviceList[videoDeviceList.length - 1];
setSelectedDeviceId(initialDevice.deviceId);
}, [videoDeviceList]);

useEffect(() => {
/** 디바이스ID로 스트림을 받아옵니다. */
if (selectedDeviceId !== "" && videoRef.current) {
const setMedia = async (htmlVideoElement: HTMLVideoElement) => {
codeReader.reset(); /* 기존 연결 해제 */
const constraints = { video: { deviceId: { exact: selectedDeviceId } } };
let newVideoStream: MediaStream | null = null;
try {
/** 디바이스 목록엔 떴지만 사용불가시 throw */
newVideoStream = await navigator.mediaDevices.getUserMedia(constraints);
} catch (error) {
console.error(error);
} finally {
htmlVideoElement.srcObject = newVideoStream;
htmlVideoElement.autoplay = true;
}
if (newVideoStream) {
/** 연결된 디바이스에 콜백함수를 전달해줍니다. */
codeReader.decodeFromVideoElementContinuously(
htmlVideoElement,
(result: Result) => result && toDoAfterRead(result.getText())
);
} else {
/** 사용 불가능한 디바이스는 선택지에서 제거합니다. */
alert(`해당 디바이스는 사용할 수 없습니다. (${selectedDeviceId})`)
setVideoDeviceList((prev) => prev.filter(prev => prev.deviceId !== selectedDeviceId));
if (selectedDeviceId === "" || !videoRef.current) return;

const setMedia = async (htmlVideoElement: HTMLVideoElement) => {
codeReader.reset();

try {
/** 해당 기기 확인 및 카메라 권한 요청 */
const newVideoStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: selectedDeviceId } },
});
/** 권한 변경 대비 목록 업데이트 */
loadVideoInputDeviceList();
htmlVideoElement.srcObject = newVideoStream;
htmlVideoElement.autoplay = true;
/** 연결된 디바이스에 콜백함수를 전달해줍니다. */
codeReader.decodeFromVideoElementContinuously(
htmlVideoElement,
(result: Result) => result && toDoAfterRead(result.getText()),
);
} catch (error) {
htmlVideoElement.srcObject = null;
htmlVideoElement.autoplay = false;
if (error instanceof DOMException && error.name === "NotAllowedError") {
alertError("카메라 접근 권한을 확인해주세요.");
return;
}
};
setMedia(videoRef.current);
}
alertError(`해당 디바이스는 사용할 수 없습니다. (${selectedDeviceId})`);
setVideoDeviceList(
videoDeviceList.filter(v => v.deviceId !== selectedDeviceId),
);
}
};
setMedia(videoRef.current);
}, [selectedDeviceId]);

const handleSelectedIdChange: ChangeEventHandler<HTMLSelectElement> = (e) =>
setSelectedDeviceId((prev) => updateNewData(prev, e.target.value));

return (
<div className={`barcode-reader__wrapper ${wrapperClassName || ""}`}>
<video id="video" ref={videoRef} muted />
<select
id="sourceSelect"
onChange={handleSelectedIdChange}
onChange={e => setSelectedDeviceId(e.target.value)}
value={selectedDeviceId}
>
{(videoDeviceList ?? []).map((device) => (
<option key={device.deviceId} value={device.deviceId}>
{videoDeviceList.map(device => (
<option key={device.deviceId ?? ""} value={device.deviceId}>
{device.label}
</option>
))}
Expand Down
Loading