Skip to content

Commit

Permalink
Merge pull request #71 from Ong-gi-Jong-gi/feature/TSK-21/facetime
Browse files Browse the repository at this point in the history
[TSK-21] 화상채팅 비율 및 이팩트 추가
  • Loading branch information
bada308 authored Jul 17, 2024
2 parents fcf4385 + c45d625 commit dfeb025
Show file tree
Hide file tree
Showing 14 changed files with 80 additions and 15 deletions.
3 changes: 2 additions & 1 deletion src/apis/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AxiosError } from 'axios';
import { useLocation, useNavigate } from 'react-router-dom';
import { api } from '..';
import API from '../../constants/API';
import FACETIME from '../../constants/FACETIME';
import routes from '../../constants/routes';
import { responseRoot } from '../../types/api';
import { LoginValues, UserLoginType } from '../../types/auth';
Expand Down Expand Up @@ -53,7 +54,7 @@ export const usePostLogin = () => {
expires: expireDate,
});

localStorage.setItem('mooluck-nickname', data.nickname);
localStorage.setItem(FACETIME.LOCAL_STORAGE_KEY, data.nickname);

if (data.familyId == 'null') navigate(routes.family.entry);
else if (location.state.code != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { useTrackRefContext } from '@livekit/components-react';
import FACETIME from '../../../constants/FACETIME';
import MyParticipantTile from './MyParticipantTile';
import OtherParticipantTile from './OtherParticipantTile';

const CustomParticipantTile = () => {
const trackRef = useTrackRefContext();
const isUser =
trackRef.participant?.identity === localStorage.getItem('mooluck-nickname');
trackRef.participant?.identity ===
localStorage.getItem(FACETIME.LOCAL_STORAGE_KEY);

return <>{isUser ? <MyParticipantTile /> : <OtherParticipantTile />}</>;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const CustomVideoConference = () => {

return (
<>
<div className="grid h-full min-h-0 w-full min-w-0 grid-cols-2 grid-rows-2 items-center justify-center">
<div className="grid h-full min-h-0 w-full min-w-0 grid-cols-2 grid-rows-2 items-center justify-center gap-2">
<TrackLoop tracks={camaraTracks}>
<CustomParticipantTile />
</TrackLoop>
Expand Down
7 changes: 5 additions & 2 deletions src/components/face-challenge/facetime/FacetimeContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import {
import '@livekit/components-styles';
import './translate_none.css';
import { useNavigate } from 'react-router-dom';
import FACETIME from '../../../constants/FACETIME';
import routes from '../../../constants/routes';
import FilterSelector from '../utils/FilterSelector';
import SnapshotEffect from '../utils/SnapshotEffect';
import MemoizedCustomVideoConference from './CustomVideoConference';
import StatusBar from './StatusBar';

Expand All @@ -23,8 +25,8 @@ const FacetimeContainer = ({ code }: FacetimeContainerProps) => {
code,
{
userInfo: {
identity: `${localStorage.getItem('mooluck-nickname')}`,
name: `${localStorage.getItem('mooluck-nickname')}`,
identity: `${localStorage.getItem(FACETIME.LOCAL_STORAGE_KEY)}`,
name: `${localStorage.getItem(FACETIME.LOCAL_STORAGE_KEY)}`,
},
},
);
Expand All @@ -43,6 +45,7 @@ const FacetimeContainer = ({ code }: FacetimeContainerProps) => {
navigate(routes.main);
}}
>
<SnapshotEffect />
<MemoizedCustomVideoConference />
<RoomAudioRenderer />
<FilterSelector />
Expand Down
6 changes: 5 additions & 1 deletion src/components/face-challenge/facetime/MyParticipantTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const MyParticipantTile = () => {
const { canvasRef, position, actualVideoSize } = useFaceFilterWithModel(
videoElement,
!isMuted || false,
trackRef.publication?.dimensions
? trackRef.publication?.dimensions.width /
trackRef.publication?.dimensions.height
: 1,
);

useEffect(() => {
Expand All @@ -44,7 +48,7 @@ const MyParticipantTile = () => {
}, [trackRef.publication?.track?.attachedElements]);

return (
<div className="relative -scale-x-100">
<div className="relative h-full -scale-x-100 rounded-lg">
<ScreenRecorder
customMediaStream={trackRef.publication?.track?.mediaStream || null}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const OtherParticipantTile = () => {
}, [message, trackRef.participant.identity]);

return (
<div className="relative -scale-x-100">
<div className="relative h-full -scale-x-100 rounded-lg">
<ParticipantTile />
<canvas className="absolute left-0 top-0" ref={canvasRef} />
</div>
Expand Down
8 changes: 8 additions & 0 deletions src/components/face-challenge/facetime/translate_none.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@
.lk-participant-metadata {
display: none;
}

.lk-participant-tile {
height: 100%;
}

.lk-participant-media-video {
height: 100%;
}
9 changes: 8 additions & 1 deletion src/components/face-challenge/prejoin/PrejoinCam.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ const PrejoinCam = () => {
const [videoElement, setVideoElement] = useState<HTMLVideoElement | null>(
null,
);
const { canvasRef } = useFaceFilterWithModel(videoElement, true);
const { canvasRef } = useFaceFilterWithModel(
videoElement,
true,
videoElement
? videoElement.getBoundingClientRect().width /
videoElement?.getBoundingClientRect().height
: 1,
);

useEffect(() => {
if (webcamRef.current && webcamRef.current.video) {
Expand Down
29 changes: 29 additions & 0 deletions src/components/face-challenge/utils/SnapshotEffect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import clsx from 'clsx';
import { useEffect, useState } from 'react';
import FACETIME from '../../../constants/FACETIME';
import { useFacetimeChallengeStore } from '../../../store/facetimeChallengeStore';

const SnapshotEffect = () => {
const [isSnapshot, setIsSnapshot] = useState(false);
const { status, remainingTime } = useFacetimeChallengeStore();
useEffect(() => {
if (
status === 'ongoing' &&
remainingTime !== FACETIME.TIMER_UNIT * FACETIME.PHOTO_COUNT &&
remainingTime % FACETIME.TIMER_UNIT === 0
) {
setIsSnapshot(true);
setTimeout(() => {
setIsSnapshot(false);
}, 1000);
}
}, [status, remainingTime]);
const snapshotClass = clsx(
'absolute left-0 top-0 z-50 h-full w-full bg-white',
{ 'pointer-events-none opacity-0': !isSnapshot },
{ 'animate-flash opacity-80': isSnapshot },
);
return <div className={snapshotClass}></div>;
};

export default SnapshotEffect;
4 changes: 3 additions & 1 deletion src/constants/FACETIME.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const TIMER_UNIT = 3;
const PHOTO_COUNT = 4;
const LOCAL_STORAGE_KEY = 'mooluck-nickname';

Object.freeze(TIMER_UNIT);
Object.freeze(PHOTO_COUNT);
Object.freeze(LOCAL_STORAGE_KEY);

export default { TIMER_UNIT, PHOTO_COUNT };
export default { TIMER_UNIT, PHOTO_COUNT, LOCAL_STORAGE_KEY };
4 changes: 2 additions & 2 deletions src/hooks/useFaceFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ const useFaceFilter = (
if (filterType === 'none' || !isFilterActive) return;
if (!video || !filterImage || !ctx || !position) return;

const actualWidth = video.getBoundingClientRect().width;
const actualHeight = video.getBoundingClientRect().height;
const actualWidth = actualHeight * position.ratio;
ctx.clearRect(0, 0, actualWidth, actualHeight);
canvasRef.current!.width = actualWidth;
canvasRef.current!.width = video.getBoundingClientRect().width;
canvasRef.current!.height = actualHeight;
const scaleX = actualWidth / otherVideoSize.width;
const scaleY = actualHeight / otherVideoSize.height;
Expand Down
11 changes: 7 additions & 4 deletions src/hooks/useFaceFilterWithModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const videoSize = {
const useFaceFilterWithModel = (
video: HTMLVideoElement | null,
isFilterActive: boolean,
ratio: number,
) => {
const { filterType } = useFilterTypeStore();
const { faceLandmarker, isLoaded, loadFaceLandmarker } = useFaceLandmarker();
Expand All @@ -26,9 +27,10 @@ const useFaceFilterWithModel = (
if (filterType === 'none' || !isFilterActive) return;
if (!video || !filterImage || !ctx || !faceLandmarker) return;

const actualWidth = video.getBoundingClientRect().width;
const actualHeight = video.getBoundingClientRect().height;
const actualWidth = actualHeight * ratio;
setActualVideoSize({ width: actualWidth, height: actualHeight });
const padding = (actualWidth - video.getBoundingClientRect().width) / 2;

if (actualWidth === 0 || actualHeight === 0) {
console.error('비디오 크기가 0입니다.');
Expand All @@ -45,7 +47,7 @@ const useFaceFilterWithModel = (
if (faceLandmarks && faceLandmarks.length > 0) {
// 캔버스 초기화
ctx.clearRect(0, 0, actualWidth, actualHeight);
canvasRef.current!.width = actualWidth;
canvasRef.current!.width = video.getBoundingClientRect().width;
canvasRef.current!.height = actualHeight;

// 필터 위치 계산
Expand All @@ -61,10 +63,10 @@ const useFaceFilterWithModel = (
}

const { x, y, width, height } = position;
setPosition({ x, y, width, height });
setPosition({ x: x - padding, y, width, height, ratio });

// 필터 그리기
ctx.drawImage(filterImage, x, y, width, height);
ctx.drawImage(filterImage, x - padding, y, width, height);
} else {
animationFrameId.current = requestAnimationFrame(estimateFacesLoop);
return;
Expand All @@ -86,6 +88,7 @@ const useFaceFilterWithModel = (
filterType,
video,
isFilterActive,
ratio,
]);

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions src/types/challenge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface FilterPosition {
y: number;
width: number;
height: number;
ratio: number;
}
export interface VideoSize {
width: number;
Expand Down
5 changes: 5 additions & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,15 @@ export default {
'0%': { transform: 'translateY(0) translateX(-50%)' },
'100%': { transform: 'translateY(100%) translateX(-50%)' },
},
flash: {
'0%, 100%': { opacity: 1 },
'50%': { opacity: 0.5 },
},
},
animation: {
'slide-up': 'slide-up 0.3s ease-out',
'slide-down': 'slide-down 0.3s ease-in',
flash: 'flash 0.5s ease-out',
},
boxShadow: {
'shadow-box':
Expand Down

0 comments on commit dfeb025

Please sign in to comment.