Skip to content

Conversation

@chamny20
Copy link
Collaborator

@chamny20 chamny20 commented Jan 6, 2026

🎯 이슈 번호


✅ 작업 내용

  • 미팅 로비(Lobby) UI 구현

    • 회의 참여 전 미디어 프리뷰 화면 구성
    • 카메라/마이크 상태에 따라 영상 프리뷰 또는 플레이스홀더 UI 렌더링
    • 회의 정보(방장, 참여자 수) 및 참여 버튼 배치
    • 미디어 제어 버튼 영역(카메라 / 마이크 토글) 포함
  • useMediaPreview 훅 구현

    • getUserMedia 기반 카메라/마이크 권한 요청 로직 구현
    • 비디오/오디오 on/off 상태 관리 (videoOn, audioOn)
    • 권한 상태 관리 (cameraPermission, micPermission)
    • MediaStream lifecycle 관리 (초기 요청, 언마운트 시 track 정리)
    • 현재 상태를 기반으로 영상 렌더링 가능 여부(canRenderVideo) 도출
  • useMediaDevices 훅 구현

    • navigator.mediaDevices.enumerateDevices()를 활용한 입력/출력 장치 목록 조회
    • 카메라, 마이크, 스피커 디바이스 분리 관리
    • 사용자 선택 장치 ID 상태 관리

전체 흐름

src/
└── app/
        └── [meetingId]/
            ├── page.tsx          # 메인 엔트리 

...

components/      # 관련 컴포넌트
├── MeetingLobby.tsx     # 입장 전 장치 설정 화면
└── MeetingRoom.tsx    # 실제 회의 화면

구글 미트의 회의 흐름을 참고하여 하나의 미팅페이지에서 컴포넌트가 교체되도록 작업하였습니다.
시나리오는 아래와 같습니다.

  1. 사용자가 /meeting/abc-defg-hij로 접속
  2. 프론트엔드 앱은 이 코드가 유효한 회의인지 확인
  3. 사용자가 아직 '참여' 버튼을 누르기 전이라면, 같은 URL 상에서 [입장 준비 UI - MeetingLobby]를 먼저 보여줌.
  4. 참여 버튼을 누르면 URL 이동 없이 [실시간 회의 UI - MeetingRoom]로 컴포넌트만 교체

개념 정리

개념 의미
permission 브라우저가 장치 사용을 허용했는지
track.enabled 해당 장치가 현재 켜져 있는지
audioOn / videoOn UI에서 표현하는 ON / OFF 상태
상태 micPermission cameraPermission
마이크만 허용 granted denied
카메라만 허용 denied granted
둘 다 허용 granted granted
둘 다 거부 denied denied

🤔 리뷰 요구사항

  • DeviceDropdown 컴포넌트를 어디에 위치시킬지 고민입니다.
  • [마이크 및 카메라 접근 허용] 버튼 클릭 시 권한요청 팝업을 띄우고 싶었는데, 현재 http에서는 권한상태가 denied일 때 띄울 수 없는 걸로 알고있어요. 이부분과 관련해서 어떻게 처리해줄지 고민중입니다. 일단 우선적으로 조건부 렌더링을 시켜놓았습니다.
  • 카메라/마이크 토글 버튼 클릭시 다른 컴포넌트도 렌더되는 이슈가 있는데 어떻게 개선할 수 있을지 피드백 받고 싶어요.
  • 관심사 분리가 잘 되어있는지 확인해주시고 피드백 주시면 감사하겠습니다!

메모

  • useOutsideClick 적용 필요

📸 스크린샷 (선택)

스크린샷 2026-01-07 01 33 20 스크린샷 2026-01-07 01 34 35

-- 01.07 updated --
스크린샷 2026-01-07 20 45 42
스크린샷 2026-01-07 22 10 51
스크린샷 2026-01-07 22 11 03

- 미참여시 로비 컴포넌트 렌더
- 참여확정 시 미팅룸 컴포넌트 렌더
- 미디어스트림 생성 및 연결
- 카메라 및 오디오 연결 로직 추가
- 미디어 프리뷰 UI 구현 및 훅 활용
- 미팅 로비 UI 레이아웃 세팅
@chamny20 chamny20 self-assigned this Jan 6, 2026
@chamny20 chamny20 linked an issue Jan 7, 2026 that may be closed by this pull request
- 빈 라벨 필터링 로직 추가
- 스피커, 마이크, 카메라 기기 리스트 페칭
- 기기와 아이콘 등 props 설정
- 비활성화 스타일 추가
- Lobby에서 컴포넌트 레이아웃 호출 등
- 각 스트림별로 권한 여부를 관리하기 위해 로직 추가
- 스트림별 상태에 따른 아이콘 조건부 스타일링 로직 추가
@chamny20 chamny20 marked this pull request as ready for review January 8, 2026 00:10
Copy link
Collaborator

@tjsdn052 tjsdn052 left a comment

Choose a reason for hiding this comment

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

일단 역할 분리가 굉장히 잘 되어있다는 느낌을 받았습니다. 고생하셨습니다.!!

질문은 아래 코멘트로 남겼습니다!!

Comment on lines 99 to 111
const toggleVideo = useCallback(() => {
streamRef.current?.getVideoTracks().forEach((track) => {
track.enabled = !track.enabled;
setMedia((prev) => ({ ...prev, videoOn: track.enabled }));
});
}, []);

const toggleAudio = useCallback(() => {
streamRef.current?.getAudioTracks().forEach((track) => {
track.enabled = !track.enabled;
setMedia((prev) => ({ ...prev, audioOn: track.enabled }));
});
}, []);
Copy link
Collaborator

@tjsdn052 tjsdn052 Jan 8, 2026

Choose a reason for hiding this comment

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

p4: 카메라를 비활성화했음에도 브라우저에서는 계속 사용 중으로 떠 찾아보니 track.enabled = false로는 실제 디바이스 사용 중단까지는 가지 않는다고 해요 완전히 사용을 종료하려면 track.stop()을 사용해야하는데

줌이나 구글 미트에서는 카메라 차단시 완전히 꺼졌던거같아서

카메라 또는 마이크가 완전히 종료되지 않는 동작을 의도하신건지 궁금합니다. (빠른 토글 같은 장점?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아하 카메라 불빛까지는 신경을 못 썼던 것 같아요! 놓친 부분이었는데 감사합니다 ~!

Copy link
Collaborator

@seorang42 seorang42 left a comment

Choose a reason for hiding this comment

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

잘 작동하는 것 확인했습니다!
고생 많으셨어요!!

base: 'inline-flex items-center justify-center box-border select-none m-0 p-0 w-fit h-fit cursor-pointer disabled:cursor-default',
size: {
sm: 'h-full max-h-[50px] px-3 py-[6px] text-sm font-bold',
sm: 'h-full h-auto px-3 py-[6px] text-sm font-bold',
Copy link
Collaborator

Choose a reason for hiding this comment

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

px-[6px]로 작성하면 혹시 VSCODE에서

The class `px-[6px]` can be written as `px-1.5`(suggestCanonicalClasses)

이런 Warning 밑줄 안생기나요??

저도 저렇게 했었다가 최근 Tailwind에선 저렇게 경고가 생겨서 .5나 .25 등의 기능을 사용하는 편이라 질문 드려요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

className에서 쓸 땐 warning이 뜨는데 style 정의로 작성했다보니 안 뜬 것 같아요! 놓친 부분이었는데 감사합니다~

Comment on lines +25 to +29
{!isJoined ? (
<MeetingLobby meetingId={meetingId} onJoin={handleJoin} />
) : (
<MeetingRoom meetingId={meetingId} />
)}
Copy link
Collaborator

Choose a reason for hiding this comment

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

제 PR에 리뷰를 남겨주셨던 부분이 이 부분이었나보네요!

참여 준비 화면과 실제 화상 회의 페이지를 하나로 만드는게 좋을까요?
아니면 별도의 페이지로 분리하는게 좋을까요?

일단 구글 meet에서는 구현해주신대로
새로고침 시 검증 및 준비를 다시 요청하는 방식으로 진행되고 있기는 하네요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

저도 하나의 페이지에서 핸들하는 게 좋을지, 별도의 페이지로 분리하는 게 좋을지 고민이 되었는데 구글미트를 참고해 우선 진행했어요. 그리고 AI에게 피드백을 받아봤는데 아래와 같은 관점으로 봐도 좋을 것 같아요!

Next.js 관점

  1. 사용자 경험 (Seamless UX): 사용자가 '참여하기'를 눌렀을 때 페이지 전체가 새로고침되지 않고, 필요한 컴포넌트(회의창)만 즉시 로드되게 한다.
  2. 데이터 무결성: meetingId라는 동일한 컨텍스트 안에서 상태가 유지되므로, 로비에서 설정한 오디오/비디오 스트림 객체를 회의실 컴포넌트로 그대로 넘겨주기 쉽다.
  3. URL 간결화: 구글 미트처럼 단일 URL(.../meeting/abc-def)을 유지하면서도 내부 로직만으로 흐름을 완벽히 제어할 수 있다.

추가 포인트

만약 로그인하지 않은 사용자를 /landing으로 보내야 한다면, page.tsx 상단이나 Next.js Middleware에서 체크하는 것이 좋다.

// middleware.ts 예시
if (!session && request.nextUrl.pathname.startsWith('/meeting')) {
  return NextResponse.redirect(new URL('/landing', request.url));
}

⇒ 일단 우리 서비스에선 비로그인 사용자도 참여 가능하다. → 비회원인 경우 넘어갈 페이지로 미들웨어 설정해주기?

Comment on lines 37 to 41
<CamOffIcon className="h-12 w-12 rounded-full bg-white p-4 text-neutral-700 shadow-lg" />
</button>

<button onClick={toggleAudio}>
<MicOffIcon className="h-12 w-12 rounded-full bg-white p-4 text-neutral-700 shadow-lg" />
Copy link
Collaborator

Choose a reason for hiding this comment

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

P1: 영상 내부의 아이콘에 p-4가 적용되어 있어서 아이콘의 크기가 의도한 것보다 더 작아진 것 같아요
p-3으로 수정 부탁드려요!

Comment on lines 24 to 30
button {
cursor: pointer;
}

button:disabled {
cursor: not-allowed;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

P4: 두 개를 Tailwind로 합쳐서
button { @apply cursor-pointer disabled:cursor-not-allowed } 로 정의할 수도 있을 것 같네요

</button>

{!isDisabled && isOpen && (
<ul className="absolute z-10 mt-1 w-full rounded-sm border border-neutral-400 bg-white shadow">
Copy link
Collaborator

Choose a reason for hiding this comment

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

P3: shadow가 있어서 border가 조금 연해도 보기 불편하지 않을 것 같아요!
적용해봤을 때 neutral-200이 가장 적당했던 것 같습니다!

P5: 직접 사용해보니까 장치가 많아졌을 때 드롭다운의 높이가 높아져서 전체 페이지가 스크롤되게 되더라고요
드롭다운의 높이를 고정값으로 설정하고 스크롤 방식으로 목록을 표시하는 것도 괜찮을 것 같다고 생각했습니다!

<Icon className="h-4 w-4 shrink-0" />
<span className="truncate">{selected?.label || `접근 권한 필요`}</span>

<span className="flex-1 text-right"></span>
Copy link
Collaborator

Choose a reason for hiding this comment

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

P2: assets/icons/common/arrowDownIcon.svg에 여기서 사용될 아이콘도 저장되어 있어요!
추가적으로 이 부분이 텍스트가 길어졌을 때 flex로 인해 찌그러지더라고요
위의 아이콘처럼 shrink-0 설정해주시면 좋을 것 같아요!

@chamny20 chamny20 merged commit a03a60a into dev Jan 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FE] 입장 준비 화면 UI 구현

4 participants