<# .SYNOPSIS 화상회의 "화면 띄우기" 시각 테스트 러너 (Windows 네이티브 lk) .DESCRIPTION livekit-cli(lk)로 가상 video publisher 를 대량 접속시켜, 운영(또는 지정) LiveKit 서버의 특정 방(code)에 합성 영상을 발행한다. 사용자가 같은 방에 브라우저로 입장하면 그 가상 참가자들이 실제 영상 타일로 화면에 표시된다(=50명 화면 띄우기 검증). ⚠️ 왜 Docker(load-test.sh) 가 아니라 네이티브 lk 인가: Docker Desktop(Windows)은 컨테이너 UDP/ICE NAT 한계로 WebRTC 미디어 연결이 타임아웃된다. 브라우저와 동일한 네트워크 경로를 쓰려면 호스트 네이티브 lk 가 필요하다. 설치: winget install --id LiveKit.LiveKitCLI 자격증명/URL 은 루트 .env.{APP_ENV} 에서 읽는다(기본 .env.production). - LIVEKIT_API_KEY / LIVEKIT_API_SECRET : lk 가 방 토큰 서명에 사용(브라우저 백엔드와 동일 키) - LIVEKIT_WS_URL : 브라우저가 접속하는 공개 wss URL (백엔드용 LIVEKIT_URL=내부주소 가 아님!) (.env 의 "==" 오타나 CRLF 는 자동 보정한다) .PARAMETER Room 대상 방의 입장 code. 앱(relay)에서 방을 먼저 생성한 뒤 /meeting/ 의 code 를 넣는다. 생략 시 임의 이름으로 들어가지만, 그 경우 브라우저는 해당 code 의 DB 방이 없어 입장 불가하다. .EXAMPLE # 운영 서버, 특정 방에 50명 저해상도 10분 .\livekit\visual-test.ps1 -Room a1b2c3d4e5 -Publishers 50 -Duration 10m .EXAMPLE # 화질을 올리되 업로드 대역폭 절약 위해 simulcast 끄기 .\livekit\visual-test.ps1 -Room a1b2c3d4e5 -Publishers 30 -Resolution medium -NoSimulcast #> [CmdletBinding()] param( [string] $Room, [int] $Publishers = 50, [int] $Subscribers = 0, [string] $Duration = '10m', [ValidateSet('low', 'medium', 'high')] [string] $Resolution = 'low', [int] $NumPerSecond = 5, [switch] $NoSimulcast, [switch] $WithAudio, # 각 가상 유저가 마이크(오디오)도 발행 (영상 인원만큼) [int] $AudioPublishers = 0, # 오디오 발행 인원 직접 지정(고급). -WithAudio 보다 우선 [string] $IdentityPrefix = 'load', [string] $EnvFile ) $ErrorActionPreference = 'Stop' # 인원/방 코드를 인자로 안 넘기면 대화형으로 입력받는다(매번 플래그 외울 필요 없음). if (-not $PSBoundParameters.ContainsKey('Room') -or [string]::IsNullOrWhiteSpace($Room)) { $Room = (Read-Host '입장할 방 코드(code) 입력 (앱 /meeting/ 의 code)').Trim() } if ([string]::IsNullOrWhiteSpace($Room)) { throw '방 코드(code)가 필요합니다.' } $promptedCount = $false if (-not $PSBoundParameters.ContainsKey('Publishers')) { $promptedCount = $true $ans = (Read-Host "입장시킬 가상 인원 수 [기본 $Publishers]").Trim() if ($ans) { if ($ans -as [int]) { $Publishers = [int]$ans } else { throw "인원 수는 숫자여야 합니다: $ans" } } } if ($Publishers -lt 1) { throw "인원 수는 1명 이상이어야 합니다: $Publishers" } # 마이크(오디오) 처리. # - -AudioPublishers N : 그 값 그대로 사용(고급). # - -WithAudio : 영상 인원만큼 오디오도 발행(각 가상 유저가 캠+마이크). # - 둘 다 없고 대화형이면 켤지 물어본다(스크립트 인자모드에서는 안 물어봐 멈춤 없음). if (-not $PSBoundParameters.ContainsKey('AudioPublishers')) { if ($WithAudio) { $AudioPublishers = $Publishers } elseif ($promptedCount) { $a = (Read-Host '마이크(오디오)도 켤까요? 각 가상 유저가 마이크 발행 [y/N]').Trim() if ($a -match '^(y|yes)$') { $AudioPublishers = $Publishers } } } if ($AudioPublishers -lt 0) { $AudioPublishers = 0 } # 루트 경로 / env 파일 결정 $root = Split-Path -Parent $PSScriptRoot if (-not $EnvFile) { $appEnv = if ($env:APP_ENV) { $env:APP_ENV } else { 'production' } $EnvFile = Join-Path $root ".env.$appEnv" } if (-not (Test-Path $EnvFile)) { throw "env 파일을 찾을 수 없습니다: $EnvFile" } # .env 에서 필요한 값만 읽기(주석/CRLF 무시, 마지막 정의 우선) function Read-EnvValue([string]$key) { $line = Get-Content $EnvFile | Where-Object { $_ -match "^\s*$([regex]::Escape($key))=" } | Select-Object -Last 1 if (-not $line) { return $null } $val = ($line -split '=', 2)[1] return ($val -replace "`r", '').Trim() } $apiKey = Read-EnvValue 'LIVEKIT_API_KEY' $apiSecret = Read-EnvValue 'LIVEKIT_API_SECRET' # 브라우저 접속용 공개 WS URL. .env 의 "==" 오타(앞 등호) 자동 보정. $wsUrl = Read-EnvValue 'LIVEKIT_WS_URL' if ($wsUrl) { $wsUrl = $wsUrl.TrimStart('=').Trim() } if (-not $apiKey -or -not $apiSecret -or -not $wsUrl) { throw "LIVEKIT_API_KEY / LIVEKIT_API_SECRET / LIVEKIT_WS_URL 를 $EnvFile 에서 찾을 수 없습니다." } # lk 실행 파일 경로 해석 (PATH 우선, 없으면 winget Links 폴백) $lk = (Get-Command lk -ErrorAction SilentlyContinue).Source if (-not $lk) { $lk = Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Links\lk.exe' } if (-not (Test-Path $lk)) { throw "lk(livekit-cli) 를 찾을 수 없습니다. 설치: winget install --id LiveKit.LiveKitCLI" } # 자격증명은 환경변수로만 전달(명령줄 노출 금지) $env:LIVEKIT_URL = $wsUrl $env:LIVEKIT_API_KEY = $apiKey $env:LIVEKIT_API_SECRET = $apiSecret Write-Host "▶ LiveKit 시각 테스트 (네이티브 lk)" -ForegroundColor Cyan Write-Host " URL=$wsUrl ROOM=$Room" Write-Host " publishers=$Publishers audio=$AudioPublishers subscribers=$Subscribers duration=$Duration resolution=$Resolution simulcast=$([bool](-not $NoSimulcast))" Write-Host " 브라우저에서 같은 방(code=$Room)에 입장하면 가상 참가자 영상이 타일로 보입니다. 중단: Ctrl+C" -ForegroundColor DarkGray Write-Host "" $lkArgs = @( 'load-test', '--room', $Room, '--duration', $Duration, '--video-publishers', $Publishers, '--audio-publishers', $AudioPublishers, '--subscribers', $Subscribers, '--video-resolution', $Resolution, '--num-per-second', $NumPerSecond, '--identity-prefix', $IdentityPrefix ) if ($NoSimulcast) { $lkArgs += '--no-simulcast' } & $lk @lkArgs