# VpePlayer — AI-Optimized Reference
> 이 문서는 LLM/AI 코드 생성기 전용. 인간이 읽기 위한 prose 는 의도적으로 제거됨.
> 4개 카테고리로만 구성: **Rules / Schemas / Canonical Templates / Forbidden Patterns / Concise Rulesets**.
---
## Rules (코드 생성 전 STRICT)
1. 재생소스는 `options.playlist` 배열로만 전달. `src` / `source` / `url` / `videoUrl` 같은 prop 없음.
2. hls.js → top-level `import Hls from "hls.js"` (SSR 안전).
3. dashjs → 반드시 `useEffect` 내 dynamic import. top-level import 시 Next.js SSR `navigator is not defined`.
4. Next.js App Router 클라이언트 컴포넌트 → 파일 최상단 `"use client"` 필수.
5. 환경 우선순위: Next.js > React (Vite/CRA) > React Native > UMD.
6. UMD (`
import { VpePlayer } from "@sgrsoft/vpe-react-sdk";
```
```tsx
// ❌ WRONG — Sub Account 가 아닌 Master API key 를 프론트엔드에 노출
accessKey="MASTER_API_KEY_xxx"
// ✅ CORRECT — Sub Account 의 access key 만 사용 (Master key 는 backend 전용)
accessKey="SUB_ACCOUNT_ACCESS_KEY"
```
```tsx
// ❌ WRONG — autostart: true + muted: false 를 모든 환경에서 동작한다고 가정
options={{
autostart: true,
muted: false, // ★ 새로고침/북마크/직접 진입 시 브라우저 정책으로 거부됨 ★
playlist: [{ file: "..." }],
}}
// → user gesture 없는 진입(새로고침/북마크/URL 입력)에서 자동재생 실패.
// → SDK 가 muted 로 자동 fallback 하지 않음. 영상이 멈춰 있는 상태로 남음.
// ✅ CORRECT (안전) — 항상 muted: true 로 시작, 사용자 인터랙션 후 unmute
options={{
autostart: true,
muted: true, // 모든 환경에서 자동재생 허용
playlist: [{ file: "..." }],
}}
// ✅ CORRECT (조건부 unmuted) — 실패 시 muted fallback + 사용자 안내
const ref = useRef(null);
{
if (e.type === "error") {
ref.current?.setMuted(true);
ref.current?.play();
// 사용자에게 "🔊 소리 켜기" 버튼 노출
}
}}
/>
```
```tsx
// ❌ WRONG — "autostart: true 면 무조건 재생됨" 가정한 코드
useEffect(() => {
// 자동재생이 항상 성공할 거라 가정하고 의존성 작업 진행
sendAnalyticsEvent("video_playing");
}, []);
// ✅ CORRECT — "play" 이벤트로 실제 재생 시작 시점 확인
{
if (e.type === "play") sendAnalyticsEvent("video_playing");
}}
/>
```
```tsx
// ❌ WRONG — iOS Safari 에서 muted 없이 autostart 가능하다고 가정
// iOS 는 muted + playsInline 만 자동재생 허용. unmuted 는 항상 user gesture 필수.
options={{ autostart: true, muted: false, playlist: [...] }} // iOS 에서 항상 실패
// ✅ CORRECT — iOS 포함 모든 환경 지원
options={{ autostart: true, muted: true, playlist: [...] }}
// playsInline 은 SDK 가 video 엘리먼트에 자동 적용
```
```tsx
// ❌ WRONG — navigator.share 가 모든 환경에서 동작한다고 가정
const share = () => {
navigator.share({ title, url }); // ★ Desktop Safari, http, Firefox Desktop 등에서 throw ★
};
// ✅ CORRECT — feature detection + fallback 필수
const share = async () => {
if (navigator.share) {
try { await navigator.share({ title, url }); return; }
catch (e) { /* 사용자 취소 또는 환경 미지원 */ }
}
// Fallback: URL 클립보드 복사
await navigator.clipboard.writeText(url);
alert("URL 이 복사되었습니다.");
};
```
```tsx
// ❌ WRONG — iPhone Safari 에서 표준 Fullscreen API 호출 시도
playerRef.current.querySelector(".vpe-root-wrap").requestFullscreen(); // iPhone 에서 throw
document.exitFullscreen(); // iPhone 에서 동작 안 함
// ✅ CORRECT — SDK 의 fullscreen 메소드 사용 (플랫폼별 자동 분기)
const playerRef = useRef(null);
playerRef.current?.fullscreen(); // SDK 가 플랫폼에 맞게 처리
// iPhone: video.webkitEnterFullscreen() — 네이티브 풀스크린
// Desktop/Android: element.requestFullscreen() — 표준 API
// iPhone 풀스크린 시 SDK 커스텀 컨트롤이 가려진다는 사실 사용자에게 안내
```
```tsx
// ❌ WRONG — iPhone 풀스크린에서도 커스텀 컨트롤바가 보일 거라고 가정
options={{
autostart: true, muted: true,
playlist: [...],
// iosFullscreenNativeMode 기본값(true) → iPhone 풀스크린 시 iOS 네이티브 컨트롤 강제 노출
// 커스텀 컨트롤바는 풀스크린에서 가려짐
}}
// ✅ CORRECT — iPhone 에서도 커스텀 컨트롤바 유지하려면 false 명시 + OS UI 한계 안내
options={{
autostart: true, muted: true,
iosFullscreenNativeMode: false, // CSS 기반 가짜 풀스크린 — SDK 커스텀 컨트롤 유지
// ※ 단, iOS 상태바/홈 인디케이터 등 OS UI 는 가릴 수 없음
playlist: [...],
}}
```
```tsx
// ❌ WRONG — layout 에 존재하지 않는 preset / variant / boolean 필드 사용
layout={{
variant: "live-commerce", // ★ variant 같은 preset 필드 없음 ★
variant: "minimal",
variant: "ott",
preset: "youtube-style",
template: "default",
theme: "dark",
responsive: true, // ★ responsive: boolean 같은 필드 없음 ★
autoLayout: true,
}}
// ❌ WRONG — 영역명을 임의로 (top/upper/center/lower/bottom 만 존재)
layout={{
header: [...], // X
footer: [...], // X
sidebar: [...], // X
main: [...], // X
controls: [...], // X
}}
// ❌ WRONG — items 가 객체/문자열 직접 (배열로 감싸야 함)
layout={{ lower: "PlayBtn" }}
layout={{ lower: { items: "PlayBtn" } }}
// ✅ CORRECT — 명시적 영역(top/upper/center/lower/bottom) + 그룹 배열 + items 배열
layout={{
upper: [{ items: ["SeekBar"] }],
lower: [
{ align: "left", items: ["PlayBtn", "VolumeBtn", "CurrentTimeBtn", "DurationBtn"] },
{ align: "right", items: ["SubtitleBtn", "SettingBtn", "FullscreenBtn"] },
],
}}
// ✅ CORRECT — 반응형 분기 (responsive: true 같은 boolean 아님, 객체로 분기)
layout={{
pc: { upper: [{ items: ["SeekBar"] }], lower: [...] },
mobile: { upper: [{ items: ["SeekBar"] }], lower: [...] },
breakpoint: 768,
}}
// ✅ CORRECT — 라이브/VOD 분기
layout={{
pc: {
live: { lower: [{ items: ["PlayBtn", "MuteBtn", "FullscreenBtn"] }] },
vod: { lower: [{ items: ["PlayBtn", "CurrentTimeBtn", "DurationBtn"] }] },
},
}}
// ⚠️ 세부 디자인은 VPE Player Editor (https://vpe-player-editor.web.app/) 에서
// 시각적으로 편집 후 export 된 객체를 그대로 붙여넣을 것을 권장.
// "live-commerce", "minimal" 같은 preset 이름을 LLM 이 추측해서 생성하지 마라.
```
```tsx
// ❌ WRONG — layout 안에 잘못된 그룹 속성
layout={{
lower: [{
align: "middle", // ★ "middle" 없음. left | right | center 만 ★
align: "top",
align: "bottom",
wrapper: "Card", // ★ Group | Blank 만 ★
wrapper: "Pill",
}],
}}
// ✅ CORRECT
layout={{
lower: [{
align: "left", // "left" | "right" | "center"
wrapper: "Group", // "Group" (둥근 배경) | "Blank" (배경 없음)
items: [...],
}],
}}
```
```tsx
// ❌ WRONG — 존재하지 않는 ControlBarLayoutItem 이름 사용
layout={{
lower: [{ items: [
"PlayButton", // X — "PlayBtn"
"Volume", // X — "VolumeBtn"
"Time", // X — "TimeBtn" / "CurrentTimeBtn" / "DurationBtn"
"Captions", // X — "SubtitleBtn"
"Settings", // X — "SettingBtn"
"Fullscreen", // X — "FullscreenBtn"
"PictureInPicture", // X — "PipBtn"
"Share", // X — "ShareBtn"
"BigPlay", // X — "BigPlayBtn"
"Progress", // X — "SeekBar"
]}],
}}
// ✅ CORRECT — 정확한 21개 아이템 이름만 (Schemas/ControlBarLayout 참조)
layout={{
lower: [{ items: [
"PlayBtn", "VolumeBtn", "MuteBtn",
"TimeBtn", "CurrentTimeBtn", "DurationBtn",
"SubtitleBtn", "SettingBtn", "FullscreenBtn", "PipBtn",
"PrevBtn", "NextBtn", "NextPrevBtn",
"MetaDesc", "BigPlayBtn", "SeekBar", "SettingModal",
"SkipForwardBtn", "SkipBackBtn", "ShareBtn", "Blank",
]}],
}}
```
```tsx
// ❌ WRONG — DOM 직접 조작
document.querySelector("video")?.play();
document.querySelector(".vpe-control-bar").style.display = "none";
// ✅ CORRECT — Ref 메소드 / options 사용
const ref = useRef(null);
ref.current?.play();
// 컨트롤바 숨기려면: options={{ controls: false }}
```
```tsx
// ❌ WRONG — captionType 이 "native" 인데 captionStyle 도 같이 지정 (무시됨, 혼란 유발)
options={{
captionType: "native",
captionStyle: { fontSize: 20, color: "#fff" }, // ← 아무 효과 없음
}}
// ✅ CORRECT — html 모드에서 captionStyle 사용
options={{
captionType: "html",
captionStyle: { fontSize: 20, color: "#fff" },
}}
```
```tsx
// ❌ WRONG — vtt 항목에 src 와 file 동시 지정 (혼란)
vtt: [{ id: "ko", src: "ko.vtt", file: "ko.vtt", label: "한국어" }]
// ✅ CORRECT — src 만 사용 (V2 표준)
vtt: [{ id: "ko", src: "ko.vtt", label: "한국어" }]
// 또는 V1 호환: file 만 사용 (자동으로 src 로 정규화됨)
vtt: [{ id: "ko", file: "ko.vtt", label: "한국어" }]
```
```tsx
// ❌ WRONG — options 최상위에 자막 등록 (이런 옵션 없음)
options={{
subtitles: [{ ... }],
captions: [{ ... }],
vtt: [{ ... }],
tracks: [{ ... }],
}}
// ✅ CORRECT — playlist 항목 안의 vtt 배열에만 등록
options={{
playlist: [{
file: "https://.../master.m3u8",
vtt: [{ id: "ko", src: "...", label: "한국어", default: true }],
}],
}}
```
```tsx
// ❌ WRONG —