저사양 기기 분류 기준

파일: apps/web/lib/utils.ts 작성일: 2026-05-22

Android 분류 기준

기준 API 저사양 조건 결합 방식
CPU 코어 수 navigator.hardwareConcurrency ≤ 4코어 셋 중 하나라도
해당하면 저사양
메모리 navigator.deviceMemory ≤ 4 GB
GPU WEBGL_debug_renderer_info Mali-T830 / T720 / 400

iOS / iPadOS 분류 기준

기준 API / 계산 저사양 조건
물리 코어 수 navigator.hardwareConcurrency ≤ 2코어 (즉시 저사양)
논리 코어 hardwareConcurrency ≤ 3 무조건 저사양
iPad 5세대 판정 코어 = 4 + iOS ≤ 16 + 해상도 2048×1536 저사양
복합 판정 (코어 = 4) JS 벤치마크 < 60 FPS 또는 (iOS ≤ 16 + 구형 해상도) 저사양

데스크탑 / 노트북 NEW 분류 기준

1

initLowPerformanceDevice() 를 리소스 캐싱 시작 전에 호출

결과는 모듈 레벨 캐시에 저장 — 이후 동기 호출(isLowPerformanceDevice())은 캐시 반환

2

MediaCapabilities.decodingInfo() 로 MP4 H.264 720p 디코딩 실측

type: "file", contentType: "video/mp4; codecs=avc1.42E01E", 1280×720 @ 30fps, 1 Mbps

3

result.smooth === false 이면 저사양으로 분류

API 미지원 브라우저이거나 예외 발생 시 → 고사양으로 폴백

수정 이력 (2026-05-22): 초기 구현은 type: "webrtc" + video/VP8 프로브를 사용했으나, 실제 서빙되는 리소스(MP4/H.264)와 다른 코덱 경로를 측정해 오탐 가능성이 있었습니다. type: "file" + video/mp4; codecs=avc1.42E01E으로 변경하여 실제 재생 경로와 일치시켰습니다.

판정 결과와 리소스 선택

기기 유형 저사양 판정 비디오 리소스 조건
Android / iOS (저사양) true 480p s3KeyProcessed480p 존재 시
Android / iOS (고사양) false 720p s3KeyProcessed 또는 원본
데스크탑 / 노트북 (저사양) true 480p smooth=false + s3KeyProcessed480p 존재
데스크탑 / 노트북 (고사양) false 720p smooth=true

핵심 코드

// apps/web/lib/utils.ts

async function _checkDesktopDecodingCapability(): Promise<boolean> {
  if (!("mediaCapabilities" in navigator)) return false;
  try {
    const result = await navigator.mediaCapabilities.decodingInfo({
      type: "file",
      video: {
        contentType: "video/mp4; codecs=avc1.42E01E",
        width: 1280, height: 720,
        bitrate: 1_000_000, framerate: 30,
      },
    });
    return !result.smooth;  // smooth=false → 저사양
  } catch {
    return false;  // 예외 → 고사양 폴백
  }
}

// 동기 호출용 (JSX 인라인, 캐시 히트 후)
export function isLowPerformanceDevice(): boolean {
  if (_lowPerfCache !== null) return _lowPerfCache;
  return _computeSyncLowPerf();
}

// 리소스 캐싱 전 1회 호출 — 결과를 모듈 캐시에 저장
export async function initLowPerformanceDevice(): Promise<boolean> {
  if (_lowPerfCache !== null) return _lowPerfCache;
  if (!_lowPerfPromise) {
    _lowPerfPromise = (async () => {
      const syncResult = _computeSyncLowPerf();
      const isDesktop =
        !/Android|iPad|iPhone|iPod/i.test(navigator.userAgent) &&
        !(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
      const result =
        syncResult || (isDesktop && await _checkDesktopDecodingCapability());
      _lowPerfCache = result;
      return result;
    })();
  }
  return _lowPerfPromise;
}