Skip to content

Feature(#81,#86) 프로그래스바 음성 출력 구현 #259

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

Merged
merged 5 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,16 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
axios
.get(`${import.meta.env.VITE_API_SERVER_URL}/lecture?code=${roomid}`)
.then((result) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소하긴하지만 result를 {data}로 구조분해 할당해서 가져오는 것도 좋을 것 같아요.

//현재 강의 생성이 잘 되지 않아서 보류
console.log(result.data);
//setTitle(result.data.title);
const lecureTitle = result.data.title;
console.log(lecureTitle);
// 이후 작업 내일 예정
})
.catch(() => {
showToast({ message: "존재하지 않는 강의실입니다.", type: "alert" });
//navigate("/");
// showToast({ message: "존재하지 않는 강의실입니다.", type: "alert" });
// navigate("/");
});
setLectureCode(roomid);
window.addEventListener("popstate", handlePopstate);
console.log("Strart");
}, []);
useEffect(() => {
inputMicVolumeRef.current = inputMicVolume;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
/>
<audio id="localAudio" playsInline autoPlay muted ref={localAudioRef}></audio>
<audio playsInline autoPlay muted ref={localAudioRef}></audio>
</>
);
};
Expand Down
23 changes: 6 additions & 17 deletions frontend/src/components/LogContainer/LogContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import SendMessage from "@/assets/svgs/sendMessage.svg?react";
import participantSocketRefState from "@/stores/stateParticipantSocketRef";

import { CSSProperties, useEffect, useLayoutEffect, useRef, useState } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { useRecoilValue } from "recoil";
import { useLocation } from "react-router-dom";
import { convertMsToTimeString } from "@/utils/convertMsToTimeString";
import axios from "axios";
import progressMsTimeState from "@/stores/stateProgressMsTime";
import convertTimeStringToMS from "@/utils/converTimeStringToMS";

Expand All @@ -21,6 +20,7 @@ interface LogItemInterface {
interface LogContainerInterface {
type: "question" | "prompt";
className: string;
scriptList?: Array<{ start: string; text: string }>;
updateProgressMsTime?: (time: number) => void;
}

Expand All @@ -37,30 +37,20 @@ const LogItem = ({ title, contents, className, onClick, style }: LogItemInterfac
);
};

const LogContainer = ({ type, className, updateProgressMsTime }: LogContainerInterface) => {
const LogContainer = ({ type, className, scriptList, updateProgressMsTime }: LogContainerInterface) => {
const [isInputEmpty, setIsInputEmpty] = useState<boolean>(true);
const [questionList, setQuestionList] = useState<Array<{ title: string; contents: string }>>([]);
const [scriptList, setScriptList] = useState<Array<{ start: string; text: string }>>([]);
const messageInputRef = useRef<HTMLInputElement | null>(null);
const logContainerRef = useRef<HTMLUListElement | null>(null);
const [progressMsTime, setProgressMsTime] = useRecoilState(progressMsTimeState);
const progressMsTime = useRecoilValue(progressMsTimeState);
const socket = useRecoilValue(participantSocketRefState);
const [hilightedItemIndex, setHilightedItemIndex] = useState(0);

const roomid = new URLSearchParams(useLocation().search).get("roomid") || "999999";

if (type === "prompt") {
useEffect(() => {
axios("./reviewLecture.json")
.then(({ data }) => {
setScriptList(data);
})
.catch((error) => {
console.log("프롬프트에 표시할 스크립트 로딩 실패", error);
});
}, []);

useLayoutEffect(() => {
if (!scriptList) return;
let currentIndexOfPrompt =
scriptList.findIndex((value) => {
const startTime = Math.floor(+value.start / 1000) * 1000;
Expand Down Expand Up @@ -132,7 +122,7 @@ const LogContainer = ({ type, className, updateProgressMsTime }: LogContainerInt
)}
{type === "prompt" && (
<ul className="px-4 flex-grow overflow-y-auto " ref={logContainerRef}>
{scriptList.map(({ start, text }, index) => {
{scriptList?.map(({ start, text }, index) => {
return (
<LogItem
key={`k-${index}`}
Expand All @@ -144,7 +134,6 @@ const LogContainer = ({ type, className, updateProgressMsTime }: LogContainerInt
const currentTarget = event.currentTarget as HTMLLIElement;
if (!currentTarget.children[0].textContent || !updateProgressMsTime) return;
convertTimeStringToMS(currentTarget.children[0].textContent);
setProgressMsTime(convertTimeStringToMS(currentTarget.children[0].textContent));
updateProgressMsTime(convertTimeStringToMS(currentTarget.children[0].textContent));
}}
/>
Expand Down
65 changes: 49 additions & 16 deletions frontend/src/pages/Review/Review.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
import { useRecoilValue, useRecoilState } from "recoil";
import { useState, useEffect, useRef } from "react";
import { fabric } from "fabric";
import { ICanvasData, loadCanvasData, updateCanvasSize } from "@/utils/fabricCanvasUtil";
import progressMsTimeState from "@/stores/stateProgressMsTime";

import axios from "axios";

import { ICanvasData, loadCanvasData, updateCanvasSize } from "@/utils/fabricCanvasUtil";

import CloseIcon from "@/assets/svgs/close.svg?react";
import ScriptIcon from "@/assets/svgs/whiteboard/script.svg?react";

import progressMsTimeState from "@/stores/stateProgressMsTime";
import isQuestionLogOpenState from "@/stores/stateIsQuestionLogOpen";

import LogToggleButton from "@/components/Button/LogToggleButton";
import LogContainer from "@/components/LogContainer/LogContainer";
import Header from "@/components/Header/Header";
import ProgressBar from "./components/ProgressBar";
import { useToast } from "@/components/Toast/useToast";

const Review = () => {
const [prograssBarState, setPrograssBarState] = useState<"disabled" | "playing" | "paused">("disabled");
const isQuestionLogOpen = useRecoilValue(isQuestionLogOpenState);
const [progressMsTime, setProgressMsTime] = useRecoilState(progressMsTimeState);
const loadedDataRef = useRef<ICanvasData[]>();
const showToast = useToast();

const loadedDataRef = useRef<ICanvasData[]>();
const scriptListRef = useRef<Array<{ start: string; text: string }>>();
const onFrameIdRef = useRef<number | null>(null); // 마이크 볼륨 측정 타이머 id
const canvasContainerRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const localAudioRef = useRef<HTMLAudioElement>(null);
let fabricCanvasRef = useRef<fabric.Canvas>();
let canvasCntRef = useRef<number>(0);
let totalTimeRef = useRef<number>(0);

const [prograssBarState, setPrograssBarState] = useState<"disabled" | "playing" | "paused">("disabled");
let startTime = Date.now();
let canvasData: ICanvasData = {
canvasJSON: "",
Expand All @@ -36,8 +41,6 @@ const Review = () => {
width: 0,
height: 0
};
// 추후 해당 다시보기의 전체 플레이 타임을 받아올 수 있어야 할 것 같습니다.
let TOTAL_MS_TIME_OF_REVIEW = 200000;

useEffect(() => {
handleInitCanvas();
Expand All @@ -48,7 +51,6 @@ const Review = () => {
window.addEventListener("resize", handleResize);
};
}, []);

// 윈도우 리사이즈 이벤트 감지
const handleInitCanvas = () => {
if (!canvasContainerRef.current || !canvasRef.current) return;
Expand All @@ -74,15 +76,34 @@ const Review = () => {
fabricCanvasRef.current = newCanvas;
};
const handleLoadData = () => {
/*
현재 /lecture/record/:id 에서 불러오는 임시 데이터의 canvasJSON 데이터가
실제 canvasJSON 데이터와 다르기 때문에 임시로 더미 데이터를 불러오도록 설정했습니다.
*/
axios("./dummyCanvasData.json")
.then(({ data }) => {
// 추후 해당 다시보기의 전체 플레이 타임을 받아올 수 있어야 할 것 같습니다.
TOTAL_MS_TIME_OF_REVIEW = 200000;
loadedDataRef.current = data;
setPrograssBarState("paused");
})
.catch((error) => {
console.log("화이트보드 데이터 로딩 실패", error);
.catch(() => {
showToast({ message: "강의 데이터를 불러오는 데 실패했습니다.", type: "alert" });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toast 칭찬해주셔서 감사합니다. :)

여담) 요즘에는 다른 팀 중에서 훨씬 쉽게 사용할 수 있는 Toast를 구성하신 분이 계셔서 그쪽 구현 방식이 탐나기 시작하네요.. ㅎㅎㅎ 나중에 개선할 수 있으면 개선해보고 싶습니다.

});

// 실제 데이터를 불러오는 코드
axios
.get(`https://boarlog.shop/lecture/record/6576c9dfccd3e23a8e0fe473`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 env로 따로 빼주는 편이 좋을 것 같습니다. VITE_API_SERVER_URL로 빼두었습니다 :)

.then((result) => {
// console.log(result.data);
// loadedDataRef.current = result.data.;
scriptListRef.current = result.data.subtitles;
localAudioRef.current!.src = result.data.audio_file;
localAudioRef.current!.addEventListener("loadedmetadata", () => {
totalTimeRef.current = localAudioRef.current!.duration * 1000;
});
//setPrograssBarState("paused");
})
.catch(() => {
showToast({ message: "강의 데이터를 불러오는 데 실패했습니다.", type: "alert" });
});
};
const handleResize = () => {
Expand All @@ -106,8 +127,12 @@ const Review = () => {
});
canvasCntRef.current += 1;
}
if (elapsedTime < TOTAL_MS_TIME_OF_REVIEW) onFrameIdRef.current = window.requestAnimationFrame(onFrame);
else console.log("다시보기 끝");
if (elapsedTime < totalTimeRef.current) onFrameIdRef.current = window.requestAnimationFrame(onFrame);
else {
setPrograssBarState("paused");
setProgressMsTime(0);
canvasCntRef.current = 0;
}
};

const play = () => {
Expand All @@ -123,10 +148,13 @@ const Review = () => {
}
onFrameIdRef.current = window.requestAnimationFrame(onFrame);

localAudioRef.current!.play();
setPrograssBarState("playing");
};
const pause = () => {
if (onFrameIdRef.current) window.cancelAnimationFrame(onFrameIdRef.current);

localAudioRef.current!.pause();
setPrograssBarState("paused");
};

Expand All @@ -144,11 +172,12 @@ const Review = () => {
}
}

return closestSmallerIndex;
return closestSmallerIndex >= 0 ? closestSmallerIndex : 0;
};

// logContainer에서 프롬프트를 클릭하거나 프로그래스 바를 클릭했을 때 진행시간을 조정하는 함수입니다.
const updateProgressMsTime = (newProgressMsTime: number) => {
setProgressMsTime(newProgressMsTime);
const currentProgressBarState = prograssBarState;
pause();
const newCanvasCntRef = findClosest(loadedDataRef.current!, newProgressMsTime);
Expand All @@ -161,10 +190,12 @@ const Review = () => {
canvasCntRef.current = newCanvasCntRef + 1;

startTime = Date.now() - newProgressMsTime;
localAudioRef.current!.currentTime = newProgressMsTime / 1000;

if (currentProgressBarState === "playing") {
onFrameIdRef.current = window.requestAnimationFrame(onFrame);
setPrograssBarState("playing");
localAudioRef.current!.play();
}
};

Expand All @@ -176,20 +207,22 @@ const Review = () => {
<LogContainer
type="prompt"
className={`absolute top-2.5 right-2.5 ${isQuestionLogOpen ? "block" : "hidden"}`}
scriptList={scriptListRef.current}
updateProgressMsTime={updateProgressMsTime}
/>
<LogToggleButton className="absolute top-2.5 right-2.5">
{isQuestionLogOpen ? <CloseIcon /> : <ScriptIcon fill="black" />}
</LogToggleButton>
<ProgressBar
className="absolute bottom-2.5 left-1/2 -translate-x-1/2"
totalTime={TOTAL_MS_TIME_OF_REVIEW}
totalTime={totalTimeRef.current}
prograssBarState={prograssBarState}
play={play}
pause={pause}
updateProgressMsTime={updateProgressMsTime}
/>
</section>
<audio playsInline ref={localAudioRef}></audio>
</>
);
};
Expand Down
31 changes: 4 additions & 27 deletions frontend/src/pages/Review/components/ProgressBar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import PlayIcon from "@/assets/svgs/progressPlay.svg?react";
import PauseIcon from "@/assets/svgs/progressPause.svg?react";

import { useRecoilState } from "recoil";
import { useEffect, useRef, useState } from "react";
import { useRecoilValue } from "recoil";
import { useEffect, useRef } from "react";
import { convertMsToTimeString } from "@/utils/convertMsToTimeString";

import progressMsTimeState from "@/stores/stateProgressMsTime";
Expand Down Expand Up @@ -39,41 +39,18 @@ const ProgressBar = ({
pause,
updateProgressMsTime
}: ProgressBarProps) => {
const [isPlaying, setIsPlaying] = useState(false);
const [progressMsTime, setProgressMsTime] = useRecoilState(progressMsTimeState);
const progressMsTime = useRecoilValue(progressMsTimeState);
const timerRef = useRef<any>();
const lastUpdatedTime = useRef<any>();
const UPDATE_INTERVAL_MS = 150;

const handleProgressBarMouseDown = (event: React.MouseEvent) => {
const { left, width } = event.currentTarget.getBoundingClientRect();
const mouseClickedX = event.clientX;
const percent = (mouseClickedX - left) / width;
setProgressMsTime(Math.round(totalTime * percent));
updateProgressMsTime(Math.round(totalTime * percent));
};

useEffect(() => {
if (isPlaying) {
lastUpdatedTime.current = new Date().getTime();

timerRef.current = setInterval(() => {
const dateNow = new Date().getTime();
const diffTime = dateNow - lastUpdatedTime.current;
setProgressMsTime((progressMsTime) => progressMsTime + diffTime);

lastUpdatedTime.current = dateNow;
}, UPDATE_INTERVAL_MS);
} else {
clearInterval(timerRef.current);
}
}, [isPlaying]);

useEffect(() => {
if (progressMsTime >= totalTime) {
prograssBarState = "disabled";
setProgressMsTime(totalTime);
setIsPlaying(false);
clearInterval(timerRef.current);
}
}, [progressMsTime]);
Expand All @@ -95,7 +72,7 @@ const ProgressBar = ({
) : prograssBarState === "playing" ? (
<PauseIcon />
) : (
<PlayIcon fill={`${progressMsTime >= totalTime && "gray"}`} />
<PlayIcon />
)}
</button>
<div
Expand Down