feat: 화상회의 수신 화질을 스테이지 역할 기반으로 중앙 제어

스테이지(featured) 유저만 HIGH, 나머지 원격 참여자는 LOW 로 구독한다.
스테이지가 바뀔 때마다 강등된 참여자가 LOW 로 내려가도록 보장한다.

- adaptiveStream 비활성화: 화질이 "엘리먼트 크기"가 아니라 "스테이지 역할"로만
  결정되도록(스테이지=HIGH 보장, 강등 반영 결정적). dynacast 는 유지.
- 화질 제어를 useLiveKitRoom 로 중앙화: featuredIdentity 변경·참여자/트랙 변동
  시마다 전원 재적용. 기존 타일 리마운트 의존(레이스로 강등자가 HIGH 유지되던 버그) 제거.
- ParticipantTile 의 개별 setVideoQuality 제거(중복·레이스 원인), 영상 연결만 담당.

검증: type-check, lint 통과.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 15:15:40 +09:00
parent 04995ca91a
commit 2015b794fc
2 changed files with 60 additions and 13 deletions
@@ -6,8 +6,6 @@ import {
Track,
ParticipantEvent,
ConnectionQuality,
RemoteTrackPublication,
VideoQuality,
type Participant,
} from 'livekit-client'
import QualityBars from './QualityBars.vue'
@@ -63,15 +61,12 @@ function sync() {
isHost.value = parseHost(p.metadata)
// 오디오는 룸 레벨(useLiveKitRoom)에서 전원 재생하므로 여기서는 영상만 다룬다.
// 영상 트랙 연결만 담당한다. 수신 화질(스테이지=HIGH/나머지=LOW)은 타일이 아니라
// useLiveKitRoom 가 스테이지 변경에 맞춰 중앙에서 제어한다(강등 시 누락·레이스 방지).
const camPub = p.getTrackPublication(Track.Source.Camera)
if (camPub?.track && videoEl.value) {
camPub.track.attach(videoEl.value)
hasVideo.value = !camPub.isMuted
// 원격 참여자: 스테이지(featured)는 고화질, 썸네일은 저화질로 구독한다.
// (adaptiveStream 의 타일 크기 기반 선택과도 일치 — 대역폭 절약)
if (!props.isLocal && camPub instanceof RemoteTrackPublication) {
camPub.setVideoQuality(props.featured ? VideoQuality.HIGH : VideoQuality.LOW)
}
} else {
hasVideo.value = false
}