# 화상회의 화면 명세 (단계 1: 인프라 토대) > 이 문서는 **현재 구현된 화상회의 화면의 기능·구조·데이터 계약**을 정리한 것이다. > 새 UI를 직접 설계/구현할 때, 아래 "데이터 계약"과 "상태 흐름"만 그대로 연결하면 > 시각 디자인은 자유롭게 바꿔도 동작이 보장된다. > (미디어 전송은 LiveKit SDK가 담당하고, 우리는 토큰 발급 + 화면 구성만 한다.) --- ## 1. 범위 - **포함(단계 1~3 구현됨)** - 단계 1: 방 입장 → LiveKit 연결 → 카메라/마이크 발행 → 나가기, 마이크/카메라 on·off - 단계 2: **권한 분기** — 방 생성자=진행자(host), 이후 입장자=일반. 역할별 토큰 grant 차등 - 단계 3: 진행자/승격된 참여자 **큰 화면(stage)** + 나머지 **썸네일**, 진행자의 **화면 승격**(데이터 채널 브로드캐스트), 썸네일 **페이지네이션** - **제외(후속 단계)**: 단계 4(방 목록/예약/참여 제한 UI), 5(부하 테스트), 6(셀프호스팅 배포) - ⚠️ **현재 화면 레이아웃은 임시(대충)이며 UI 는 새로 개편 예정.** 아래 "데이터 계약"만 그대로 연결하면 동작이 유지된다. --- ## 2. 진입 / 라우트 | 항목 | 값 | |---|---| | 라우트 | `/meeting/:room?` (room 파라미터는 선택) | | 라우트 name | `meeting` | | 인증 | `requiresAuth: true` (로그인 필요) | | 페이지 컴포넌트 | `frontend/src/pages/relay/MeetingPage.vue` | | 사이드바 네비 | `AppShell.vue` 의 "화상회의" 항목 (`/meeting`) | - `/meeting` 으로 들어오면 방 이름 미입력 상태의 로비. - `/meeting/<방이름>` 으로 들어오면 그 방 이름이 입력란에 채워진 로비(공유 링크 용도). **입장 버튼을 눌러야** 실제 연결된다(카메라 권한 타이밍 확보). - 입장 성공 시 주소창을 `/meeting/<방이름>` 으로 `replace` 한다(공유 가능 링크). --- ## 3. 화면 상태 (4가지) | 상태 | 조건 | 화면 | |---|---|---| | **로비(미연결)** | `connected === false` | 방 이름 입력 폼 | | **연결 중** | `connecting === true` | 입장 버튼이 "입장 중…" + 비활성 | | **회의실(연결됨)** | `connected === true` | 컨트롤 바 + 영상 그리드 | | **에러** | `joinError` / `errorMsg` 존재 | 해당 영역에 빨간/주황 문구 | - `joinError`: 입장 시도 단계의 오류(방 이름 형식 오류, 토큰 발급 실패 등) — 로비에 표시. - `errorMsg`: 연결 이후 미디어 단계의 오류(카메라/마이크 권한 거부 등) — 회의실에 표시. 권한 거부여도 연결 자체는 유지된다. --- ## 4. 로비 화면 (현재 구성) - 페이지 제목 `화상회의` + 안내 문구(lede) - 카드 폼: - 라벨 `* 방 이름` (필수 표기) - 힌트: "영문·숫자·하이픈(-)·밑줄(_)만 사용할 수 있습니다." - 입력란: `maxlength=64`, placeholder `예: team-standup` - 오류 문구(`joinError`) - 푸터: `입장하기` 버튼(제출 시 `onJoin`) - **방 이름 규칙**: `^[A-Za-z0-9_-]+$`, 1~64자. (백엔드 DTO와 동일하게 검증 — 한글/공백 불가) --- ## 5. 회의실 화면 (현재 구성) - **상단 바(room-bar)** - 방 이름(`roomName`) - 참여자 수(`참여자 N명` = `participants.length`) - 컨트롤 버튼 3개: - 마이크 토글 → `toggleMic()` (상태 `micEnabled`) - 카메라 토글 → `toggleCam()` (상태 `camEnabled`) - 나가기 → `onLeave()` (연결 해제 후 `/meeting` 로 이동) - **큰 화면(stage)**: `featured`(승격된 참여자 > 진행자 > 첫 참여자) 1명을 크게 렌더. - **썸네일(thumbs)**: 나머지 참여자를 작은 타일로 렌더. 진행자에게만 각 썸네일에 **"크게"(승격) 버튼** 표시 → `promote(identity)` 호출(데이터 채널로 전원 동기화). - **페이지네이션(pager)**: 썸네일이 페이지당 12명을 넘으면 이전/다음. **현재 페이지 타일만 렌더**되므로 adaptiveStream 이 안 보이는 참여자의 미디어를 자동으로 구독 해제(대역폭 절약). - 본인 식별: `participants[0]` 이 항상 본인. 타일 `is-local` 은 `p.identity === participants[0].identity` 로 판정. > 현재 컨트롤은 이모지(🎙/📹) 버튼·상단 바 등 임시 배치다 → 새 UI에서 하단 중앙 컨트롤 바, 큰화면/썸네일 비율, 승격 버튼 위치 등 자유롭게 재구성 가능. **연결되는 함수/상태만 동일하게 쓰면 된다.** --- ## 6. 참여자 타일 (`ParticipantTile.vue`) - **Props** - `participant: Participant` (livekit-client) — 필수 - `isLocal?: boolean` — 본인 타일 여부 - **동작** - 카메라 트랙을 `