@nowsoft-lab/player-sdk
v0.1.8
Published
UI 커스텀이 자유로운 React 웹 플레이어 SDK. hls.js / dash.js 는 번들에 포함되지 않으며 Tech 방식으로 외부에서 주입합니다.
Maintainers
Readme
특징
- Tech 주입 방식 —
hls.js/dashjs를 번들에 포함하지 않고 외부에서 주입합니다 (IVSregisterIVSTech와 유사). 동적 import 로 지연 로드도 가능합니다. - 자유로운 UI 구성 — 컨트롤 배치(
layout)와 테마·동작(ui)을 JSON 으로 지정합니다. PC / 모바일 / 전체 화면, VOD / LIVE 별로 분기할 수 있으며 VPEControlBarLayout과 호환됩니다. - 라이브 — ● LIVE 배지, DVR 탐색, 실시간 지점 복귀, LL-HLS 지원
- 화질 / 자막 / 재생 속도 설정 메뉴, WebVTT 자막(HTML 렌더링)
- YouTube 호환 단축키, 모바일 탭 · 더블탭 탐색 제스처
- 자동재생이 차단되면 음소거로 재생하고 "클릭하여 소리켜기" 안내
- Google IMA 광고 —
adsprop 하나로 프리롤 · 미드롤 · VMAP (IMA SDK 자동 로드) - TypeScript 타입 내장, SSR(Next.js) 안전, CSS 자동 삽입 (별도 import 불필요)
설치
npm install @nowsoft-lab/player-sdk
# 재생할 포맷에 필요한 엔진만 설치 (선택)
npm install hls.js # HLS / LL-HLS
npm install dashjs # MPEG-DASH| peerDependency | 버전 | 필수 |
| --- | --- | --- |
| react, react-dom | ^18.0.0 \|\| ^19.0.0 | ✅ |
| hls.js | ^1.5.0 | HLS 재생 시 |
| dashjs | ^4.7.0 \|\| ^5.0.0 | DASH 재생 시 |
MP4 / WebM 과 Safari 의 네이티브 HLS 는 엔진 없이 브라우저 기본 재생으로 동작합니다.
빠른 시작
import Hls from "hls.js";
import * as dashjs from "dashjs";
import { NowPlayer, registerTech, createHlsTech, createDashTech } from "@nowsoft-lab/player-sdk";
// 앱 시작 시 1회 등록 (전역)
registerTech(createHlsTech(Hls));
registerTech(createDashTech(dashjs));
export function App() {
return (
<NowPlayer
source={{
src: "https://CDN/video/index.m3u8",
title: "영상 제목",
poster: "https://CDN/poster.jpg",
textTracks: [{ src: "/ko.vtt", label: "한국어", srclang: "ko", default: true }],
}}
onEvent={(e) => console.log(e.type, e.data)}
/>
);
}source 에는 URL 문자열을 바로 넘길 수도 있습니다. 포맷은 확장자(.m3u8, .mpd)나 type 으로 판별합니다.
<NowPlayer source="https://CDN/video.mp4" autoplay muted />Tech (재생 엔진)
지연 로드 (코드 스플리팅)
라이브러리 대신 동적 import 함수를 넘기면 해당 포맷을 실제로 재생할 때만 로드됩니다.
registerTech(createHlsTech(() => import("hls.js")));
registerTech(createDashTech(() => import("dashjs")));엔진 설정
// Tech 단위 설정
registerTech(createHlsTech(Hls, { config: { maxBufferLength: 30 } }));
// 소스 단위 설정 — source.techOptions[tech 이름] 이 엔진 config 로 전달됩니다
<NowPlayer
source={{
src: "https://stream.example.com/live.m3u8",
techOptions: { hls: { lowLatencyMode: true } }, // LL-HLS
}}
/>;플레이어별 Tech 지정
techs 를 넘기면 전역 등록 대신 해당 Tech 만 사용합니다.
<NowPlayer techs={[createHlsTech(Hls)]} source="..." />커스텀 Tech
import type { Tech } from "@nowsoft-lab/player-sdk";
const myTech: Tech = {
name: "my-engine",
priority: 20, // 높을수록 먼저 시도. hls/dash 기본 10, native -100
canPlay: (source, type) => type === "hls" && source.src.includes("my-cdn"),
async setup(ctx) {
const engine = await createEngine(ctx.video, ctx.source.src);
engine.onLevels((levels) => ctx.setLevels(levels));
engine.onError((e) => ctx.error({ message: e.message, fatal: true }));
return {
setLevel: (i) => engine.setLevel(i), // -1 = 자동
destroy: () => engine.destroy(),
};
// null 을 반환하면 다음 Tech 로 양보
},
};
registerTech(myTech);Tech 선택 순서: canPlay 가 true 인 Tech 를 priority 순으로 setup → null 이면 다음 후보 → 마지막은 브라우저 native(<video src>).
Props
| Prop | 타입 | 기본값 | 설명 |
| --- | --- | --- | --- |
| source | string \| PlayerSource | - | 재생 소스 |
| techs | Tech[] | 전역 등록분 | 이 플레이어에서 사용할 Tech |
| autoplay | boolean | false | 자동재생. 소리 때문에 차단되면 음소거 후 재생하고 "클릭하여 소리켜기" 표시 |
| muted | boolean | false | |
| loop | boolean | false | |
| volume | number | 1 | 초기 볼륨 0~1 |
| dvr | boolean | true | 라이브 되감기(DVR) 허용. false 면 라이브에서 진행바·되감기/빨리감기 버튼을 숨기고 탐색 단축키·더블탭 탐색을 막으며, 일시정지 후 재생하면 라이브 엣지로 이동. VOD 에는 영향 없음 |
| controls | boolean | true | false 면 비디오 + children 만 렌더링 |
| keyboard | boolean | true | YouTube 호환 단축키 |
| aspectRatio | string | "16/9" | "fill" 이면 부모 크기를 채움 |
| objectFit | "contain" \| "cover" \| ... | "contain" | |
| accentColor | string | #7b37fc | 강조 색 (ui.theme.accent 보다 우선). 시크바 색은 ui.theme.progressColor (기본 #519cff) |
| layout | PlayerLayout | defaultLayout | 컨트롤 배치 (layout) |
| ui | PlayerUIConfig | defaultUIConfig | 테마 · 자막 스타일 · 설정 메뉴 · 제스처 (ui) |
| override | PlayerOverride | - | prevSource / nextSource / share 동작 |
| errorScreen | ComponentType<ErrorScreenProps> | - | 치명적 오류 시 기본 오류 화면 대신 렌더링할 컴포넌트 (커스텀 오류 화면) |
| offlineScreen | ComponentType<OfflineScreenProps> | - | 라이브 방송 전(source.live) 기본 오프라인 화면 대신 렌더링할 컴포넌트 (라이브 방송 전) |
| offline | PlayerOfflineConfig | 5초 / 100회 / 30초 | 방송 시작 확인 주기(interval / slowAfter / slowInterval)와 확인 함수(check) |
| ads | PlayerAdsConfig | - | Google IMA 광고 (광고) |
| locale | "ko" \| "en" \| "ja" \| "auto" | "auto" | UI 언어. auto = 브라우저 언어(지원 외 언어는 en). 실행 중 변경은 prop 또는 ref.setLocale() |
| labels | Partial<PlayerLabels> | - | 문구 오버라이드 |
| playbackRates | number[] | 0.25 ~ 2 | |
| settingsToggles | PlayerSettingsToggle[] | - | 설정(⚙) 메뉴 맨 아래 켜기/끄기 항목 { id, label, icon?, checked, onChange } — 라이브·VOD 모두 표시, 눌러도 메뉴는 열린 채. prop 전용(setOptions 대상 아님) |
| seekStep | number | 10 | j / l · 더블탭 · 10초 버튼 탐색 간격(초) |
| autoHideDelay | number | 3000 | 컨트롤 자동 숨김(ms). 0 이면 항상 표시 |
| crossOrigin | "anonymous" \| "use-credentials" | - | <video crossorigin> |
| injectStyles | boolean | true | false 면 playerStyles 를 직접 삽입 |
| styleNonce | string | - | CSP nonce |
| children | ReactNode | - | 플레이어 위 커스텀 오버레이 |
| onEvent | (e: PlayerEvent) => void | - | 모든 이벤트 콜백 |
| ref | NowPlayerHandle | - | 명령형 제어 |
PlayerSource
interface PlayerSource {
src: string; // mp4, m3u8, mpd ...
type?: "hls" | "dash" | "native" | string; // 생략 시 확장자/MIME 으로 판별
title?: string;
poster?: string;
textTracks?: { src: string; label: string; srclang?: string; default?: boolean }[]; // WebVTT
techOptions?: Record<string, unknown>; // { hls: {...}, dash: {...} }
live?: boolean; // 라이브 소스 명시. 방송 전이면 오프라인 화면 + 방송 시작 자동 확인
}명령형 제어 (ref)
import { useRef } from "react";
import { NowPlayer, type NowPlayerHandle } from "@nowsoft-lab/player-sdk";
const ref = useRef<NowPlayerHandle>(null);
<NowPlayer ref={ref} source="..." />;
ref.current?.play();
ref.current?.seek(30);
ref.current?.setLevel(-1); // 화질 자동| 메서드 / 속성 | 설명 |
| --- | --- |
| play() / pause() | 재생 / 일시정지 |
| seek(time) | 초 단위 이동 |
| load(source) | 소스 교체 |
| setVolume(0~1) / setMuted(bool) | 볼륨 / 음소거 |
| setPlaybackRate(rate) | 재생 속도 |
| setLevel(index) | 화질 선택 (-1 = 자동) |
| setTextTrack(index) | 자막 선택 (-1 = 끄기) |
| toggleFullscreen() | 전체 화면 전환 |
| setLocale(locale) | UI 언어 변경 ("ko" / "en" / "ja" / "auto"). 사이트 언어 전환에 연동할 때 사용. 이후 locale prop 이 바뀌면 prop 값이 우선 |
| getLocale() | 현재 표시 중인 UI 언어 (auto 는 실제 언어로 해석된 값) |
| setOptions(options) | 옵션을 실행 중에 변경. 넘긴 키만 덮어쓰며, 같은 이름의 prop 이 이후 바뀌면 prop 값이 우선. 값으로 undefined 를 넘기면 prop 값으로 복귀 |
| getOptions() | 현재 적용 중인 옵션 (prop + setOptions 반영 결과) |
| getState() | 현재 PlayerState |
| video / controller | <video> 요소 / PlayerController |
setOptions 로 바꿀 수 있는 옵션 (NowPlayerOptions, 목록은 RUNTIME_OPTION_KEYS):
dvr · locale · labels · layout · ui · accentColor · controls · keyboard · loop · autoHideDelay · seekStep · playbackRates · objectFit · aspectRatio
ref.current?.setOptions({ dvr: false }); // 라이브 되감기 끄기
ref.current?.setOptions({ locale: "ja", seekStep: 5 });
ref.current?.setOptions({ dvr: undefined }); // prop 값으로 되돌리기source · autoplay · ads 등 초기화가 필요한 값은 prop 으로 변경한다. 광고(IMA) UI 언어는 locale prop 만 따른다.
이벤트
onEvent 로 모든 이벤트를 { type, data } 형태로 받습니다.
<NowPlayer
source="..."
onEvent={(e) => {
if (e.type === "levelchange") console.log("화질", e.data.level, e.data.auto);
if (e.type === "error") console.error(e.data.code, e.data.message);
}}
/>| type | data |
| --- | --- |
| ready | { techName } |
| play / pause / playing / waiting / ended | - |
| timeupdate | { currentTime, duration } |
| seeking / seeked | { currentTime } |
| volumechange | { volume, muted } |
| ratechange | { playbackRate } |
| levelsupdate | { levels } |
| levelchange | { level, auto } |
| texttrackchange | { index } |
| fullscreenchange | { fullscreen } |
| pipchange | { pip } |
| sourcechange | { source } |
| error | { code, message, fatal, cause? } |
| offline | { source, error } — 라이브 소스가 방송 전으로 판단됨 (error 대신 발생) |
| offlinecheck | { checks, live } — 방송 시작 확인 1회 |
| online | { source, checks } — 방송이 시작되어 다시 불러옴 |
| adbreakstart / adbreakend | { type } — 광고 브레이크 시작(본편 일시정지) / 종료(본편 재개). type: preroll | midroll | postroll |
| adstart | { type, index, total, duration, adId, title } |
| adcomplete / adskip / adclick | { type } |
| aderror | { type, message, code? } — 광고만 건너뛰고 본편은 계속 재생 |
UI 커스터마이징
layout 과 ui 는 JSON 으로 직렬화 가능한 값이므로 서버 / CMS 에서 내려준 설정을 그대로 넘길 수 있습니다.
layout — 컨트롤 배치
VPE 의 ControlBarLayout 과 같은 구조입니다.
영역(area) → 그룹(group) → 아이템(item) 순으로 구성되며, 각 영역은 한 줄(row)로 렌더링됩니다.
<NowPlayer
source="..."
layout={{
pc: {
top: [{ items: ["MetaDesc"] }, { wrapper: "Blank" }, { items: ["ShareBtn"] }],
upper: [{ wrapper: "Blank", items: ["SeekBar"] }],
lower: [
{ items: ["PlayBtn", "VolumeBtn", "TimeBtn"] },
{ wrapper: "Blank" },
{ items: ["SubtitleBtn", "SettingBtn", "PipBtn", "FullscreenBtn"] },
],
},
mobile: {
center: [{ items: ["BigPlayBtn"] }],
upper: [{ wrapper: "Blank", items: ["SeekBar"] }],
lower: [{ items: ["MuteBtn", "TimeBtn"] }, { wrapper: "Blank" }, { items: ["FullscreenBtn"] }],
},
breakpoint: 768,
}}
/>| 구분 | 값 |
| --- | --- |
| 영역 | top / upper(시크바 위) / center(남은 높이) / lower(시크바 아래) / bottom |
| order | 영역 배치 순서(위→아래). 기본 ["top", "center", "upper", "lower", "bottom"]. "seekbar" 를 넣으면 시크바 단독 행. 목록에 없는 영역은 렌더링하지 않음 |
| 그룹 wrapper | "Blank" = 배경 없이 남은 공간 채움 (빈 그룹은 좌우를 벌리는 spacer), "Group" = 둥근 배경, null = 기본 |
| 그룹 align | "left" / "center" / "right". 고정 폭 그룹은 행 안 위치, Blank 그룹은 내용 정렬 |
| 그룹 기타 | key, noPadding, style(CSSProperties), cap(VPE 호환용, 현재 무시) |
| 분기 | { pc, mobile, fullscreen, breakpoint } — 플레이어 너비 기준 (기본 768px). 각 값은 레이아웃 또는 { vod, live } |
아이템:
| 아이템 | 설명 |
| --- | --- |
| PlayBtn / BigPlayBtn | 재생 · 일시정지 / 중앙 대형 버튼 |
| VolumeBtn / MuteBtn | 음소거 + 볼륨 슬라이더 / 음소거 버튼만 |
| TimeBtn / CurrentTimeBtn / DurationBtn | 현재 / 전체 시간 (라이브는 LIVE 표시) |
| LiveBtn | 라이브일 때만 ● LIVE 배지 (클릭 시 실시간으로 이동) |
| SeekBar | 진행바 |
| SubtitleBtn / SettingBtn / SettingModal | 자막 토글 / 설정 메뉴 (SettingModal 은 SettingBtn 과 동일) |
| PipBtn / FullscreenBtn | PIP / 전체 화면 |
| PrevBtn / NextBtn / NextPrevBtn | override.prevSource / nextSource 가 있을 때만 표시 |
| SkipBackBtn / SkipForwardBtn | seekStep 초 이동 |
| ShareBtn | override.share → 없으면 navigator.share → 미지원 시 URL 복사 |
| MetaDesc | 제목 (source.title) |
| Blank | 남은 공간을 채우는 빈 칸 |
TS / React 에서는 문자열 대신 React 요소를 아이템으로 넣을 수도 있습니다 (items: ["PlayBtn", <MyButton key="my" />]).
알 수 없는 아이템 이름은 무시되고 콘솔에 1회 경고합니다. 기본값은 defaultLayout 으로 export 됩니다.
ui — 테마 / 동작
<NowPlayer
source="..."
ui={{
settingsMenu: ["speed", "quality"], // 순서 지정, [] 이면 설정 버튼 숨김
tooltips: false,
doubleClickFullscreen: false,
theme: { accent: "#00c73c", progressColor: "#00c73c", radius: 0, iconSize: 24, controlBarHeight: 44 },
captions: { fontSize: 20, color: "#ffeb3b", background: "transparent" },
}}
/>| 옵션 | 기본값 | 설명 |
| --- | --- | --- |
| settingsMenu | ["quality", "captions", "speed"] | 설정 메뉴 항목 / 순서 |
| feedback / spinner / gradient / tooltips | true | 각 UI 요소 표시 |
| hdBadge | false | 설정 버튼 · 화질 메뉴의 HD 표시 |
| startTitle | true | 재생 전 포스터 위 제목 |
| unmuteHint | true | 자동재생이 차단돼 음소거로 시작했을 때 "클릭하여 소리켜기" |
| clickToPlay / doubleClickFullscreen / doubleTapSeek | true | 화면 제스처 |
| theme | - | accent, foreground, foregroundDim, background, radius, menuBackground, menuRadius, hoverBackground, groupBackground, progressColor, progressTrack, progressBuffered, fontFamily, fontSize, controlBarHeight, padding, iconSize |
| captions | - | fontSize, fontFamily, color, background |
| icons | - | 기본 아이콘 교체 { [PlayerIconKey]: string } — 아래 참고 |
theme / captions 의 숫자 값은 px 로 변환되어 --np-* CSS 변수로 적용됩니다. progressColor 는 시크바와 라이브 엣지의 ● LIVE 점 색상에 함께 쓰입니다.
ui.icons — 아이콘 교체
값은 이미지 data URI(data:image/svg+xml;base64,…) 또는 URL(https:// · /) 입니다. 이미지를 단색 마스크로 그려 기본 아이콘처럼 글자 색(theme.foreground)·hover·iconSize 를 따르므로 단색 SVG 를 권장합니다. 키가 없거나 값이 잘못되면(형식 불일치, "·\·줄바꿈 포함) 기본 아이콘을 씁니다. 값은 innerHTML 로 넣지 않고 CSS mask-image 로만 씁니다.
<NowPlayer source="..." ui={{ icons: { play: "data:image/svg+xml;base64,PHN2Zy...", pause: "/icons/pause.svg" } }} />키(PlayerIconKey): play · pause · replay · bigPlay · skipBack · skipForward · volumeUp · volumeDown · volumeOff · fullscreen · fullscreenExit · settings · captions · pip · speed · quality · subtitles · check · chevronLeft · chevronRight · forward · rewind · prev · next · share · error · offline · spinner. 상태가 둘인 버튼은 키가 나뉩니다(재생 play/pause/replay, 음량 volumeUp/volumeDown/volumeOff, 전체 화면 fullscreen/fullscreenExit). bigPlay 는 시작 전 중앙 버튼, forward/rewind 는 단축키 피드백, check/chevronLeft/chevronRight 는 설정 메뉴. 마스크 위에 그리므로 기본 아이콘의 그림자(drop-shadow)는 적용되지 않습니다.
커스텀 오버레이
NowPlayer 의 children 에서 usePlayer() / usePlayerState() 를 사용할 수 있습니다. usePlayerState 는 selector 결과가 바뀔 때만 리렌더됩니다.
import { NowPlayer, usePlayerState } from "@nowsoft-lab/player-sdk";
function QualityBadge() {
const label = usePlayerState((s) => s.levels.find((l) => l.index === s.currentLevel)?.label);
return label ? <div className="badge">{label}</div> : null;
}
<NowPlayer source="...">
<QualityBadge />
</NowPlayer>;커스텀 오류 화면
errorScreen 에 컴포넌트를 넘기면 치명적 오류(fatal: true) 시 기본 오류 화면 대신 그 컴포넌트를 플레이어 전체 영역에 그립니다 (controls={false} 여도 표시).
일부 오류만 직접 그리고 나머지는 DefaultErrorScreen 으로 넘길 수 있습니다.
import { DefaultErrorScreen, NowPlayer, type ErrorScreenProps } from "@nowsoft-lab/player-sdk";
// 렌더 함수 밖에 정의 (인라인 함수면 렌더마다 다시 마운트됨)
function MyErrorScreen(props: ErrorScreenProps) {
const { error, source, retry } = props;
if (error.code !== "NETWORK_ERR") return <DefaultErrorScreen {...props} />;
return (
<div className="my-error">
<strong>영상을 불러오지 못했어요</strong>
<span>{source?.title}</span>
<button onClick={() => retry()}>다시 시도</button>
</div>
);
}
<NowPlayer source="..." errorScreen={MyErrorScreen} />;| ErrorScreenProps | 설명 |
| --- | --- |
| error | PlayerError (code / message / fatal / cause) |
| source | 오류가 난 PlayerSource |
| labels | 현재 언어의 UI 문구 (labels.errorTitle 등) |
| retry() | 같은 소스를 다시 로드한 뒤 재생 (Promise) |
| controller | PlayerController |
라이브 방송 전 (오프라인)
소스에 live: true 를 지정하면, 방송 전이라 불러오지 못할 때 오류 대신 오프라인 화면을 보여주고 방송 시작을 주기적으로 확인합니다.
기본은 5초 주기, 100회 확인 후 30초 주기이며, 방송이 시작되면 소스를 다시 불러와 자동으로 재생합니다. 재생 중 방송이 끊겨도(NETWORK_ERR) 같은 방식으로 기다립니다.
import { NowPlayer, type OfflineScreenProps } from "@nowsoft-lab/player-sdk";
// 렌더 함수 밖에 정의
function MyOfflineScreen({ source, offline, checkNow }: OfflineScreenProps) {
return (
<div className="my-offline">
<strong>곧 방송이 시작됩니다</strong>
<span>{source.title}</span>
<span>{offline.checks}회 확인 · 다음 확인 {new Date(offline.nextCheckAt).toLocaleTimeString()}</span>
<button onClick={() => checkNow()}>지금 확인</button>
</div>
);
}
<NowPlayer
source={{ src: "https://CDN/live/stream.m3u8", title: "예정된 라이브", live: true }}
autoplay
offlineScreen={MyOfflineScreen} // 생략 시 기본 오프라인 화면 (labels.offlineTitle / offlineDetail)
offline={{ interval: 5000, slowAfter: 100, slowInterval: 30000, check: async () => (await fetch("/api/live/123")).ok }} // 선택
/>;- 기본 확인(
defaultLiveCheck)은 매니페스트를 요청해 세그먼트가 있는지 확인합니다 (HLS 마스터면 첫 번째 variant, DASH 는 Period). 인증 헤더가 필요하거나 방송 상태 API 가 있으면offline.check를 사용하세요. OfflineScreenProps:source/offline({ checks, interval, nextCheckAt, since }) /labels/checkNow()/controller. 오프라인 중에는state.offline이 채워지고state.error는null입니다.
전체 화면 영역
기본적으로 플레이어 루트가 전체 화면이 됩니다. 플레이어의 조상 요소에 np-fullscreen-target 클래스가 있으면 가장 가까운 그 요소를 전체 화면으로 엽니다. 채팅 등 플레이어 밖 UI 를 함께 전체 화면에 넣을 때 사용합니다.
<div className="np-fullscreen-target live-layout">
<NowPlayer source="..." aspectRatio="fill" />
<ChatPanel />
</div>- 전체 화면 요소가 플레이어를 포함하면 플레이어는 전체 화면 상태(
state.fullscreen,.np--fullscreen,fullscreen레이아웃)로 전환됩니다. 앱이 상위 영역을 직접requestFullscreen()해도 동일합니다. - 전체 화면 중 배치는 앱에서
.np-fullscreen-target:fullscreen { ... }로 지정합니다. 플레이어는 전체 화면 중 비율 고정을 해제하고 부모 크기를 채웁니다. - iOS Safari(iPhone)는 요소 전체 화면을 지원하지 않아 항상
<video>네이티브 전체 화면으로 동작합니다. - 클래스 이름은
FULLSCREEN_TARGET_CLASS로 export 되며,controller.getFullscreenTarget()으로 실제 대상을 확인할 수 있습니다.
광고 (Google IMA)
ads 에 VAST / VMAP 태그를 넘기면 Google IMA SDK 로 광고를 재생합니다. IMA SDK 는 ads 가 있을 때만 Google CDN 에서 자동 로드됩니다 (설치 불필요).
<NowPlayer
source="https://CDN/video/index.m3u8"
ads={{
// 프리롤 (첫 재생 전)
tagUrl: "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=",
// 미드롤 (초). 숫자만 쓰면 tagUrl 을 사용
midrolls: [30, { time: 120, tagUrl: "https://.../midroll-vast" }],
}}
/>| 필드 | 기본값 | 설명 |
| --- | --- | --- |
| tagUrl | - | VAST / VMAP 태그. 비어 있는 correlator= 는 요청마다 자동으로 채움 |
| preroll | true | tagUrl 을 프리롤로 재생. false 면 미드롤만 |
| midrolls | - | number \| { time, tagUrl? } 배열 (VOD 전용). 탐색으로 건너뛰면 마지막 하나만 재생, 같은 미드롤은 한 번만 |
| vastLoadTimeout | 8000 | VAST 로드 제한 시간(ms) |
| locale | 플레이어 locale | IMA 광고 UI 언어 |
| sdkUrl | ima3.js | IMA SDK 스크립트 URL |
- VMAP 태그면 IMA 가 광고 규칙대로 프리롤 · 미드롤 · 포스트롤을 재생합니다 (
midrolls와 함께 쓰지 않음). - 자동재생이 소리 때문에 막히면 광고도 음소거로 재생하고 "클릭하여 소리켜기"를 표시합니다.
- 광고 중에는 광고 바(재생/일시정지 · 음소거 · 남은 시간 · 진행 바 · 전체 화면)가 표시되며, 건너뛰기 버튼은 IMA 가 그립니다. 바 색은
--np-ad-color(기본#ffcc00). - 광고 상태는
state.ad(광고 중이 아니면null), 오류(광고 차단기 · no fill · 시간 초과)는aderror이벤트로 받으며 본편은 그대로 재생됩니다.
단축키
| 키 | 동작 |
| --- | --- |
| Space / k | 재생 / 일시정지 |
| ← / → | 5초 이동 |
| j / l | 10초 이동 |
| ↑ / ↓ | 볼륨 5% |
| m | 음소거 |
| f | 전체 화면 |
| c | 자막 켜기 / 끄기 |
| i | PIP |
| 0 ~ 9 | 0% ~ 90% 지점으로 이동 |
| Shift+< / Shift+> | 재생 속도 |
모바일: 탭 = 컨트롤 표시 / 숨김, 좌우 더블탭 = ±10초.
Next.js (App Router)
"use client" 컴포넌트에서 사용합니다. 서버 렌더링 시에도 에러 없이 마크업을 생성하며, Tech 는 클라이언트에서만 동작합니다.
dashjs 는 SSR 에서 window 를 참조하므로 지연 로드 방식을 권장합니다.
"use client";
import { NowPlayer, registerTech, createHlsTech, createDashTech } from "@nowsoft-lab/player-sdk";
registerTech(createHlsTech(() => import("hls.js")));
registerTech(createDashTech(() => import("dashjs")));
export default function Player({ src }: { src: string }) {
return <NowPlayer source={src} />;
}CSP / 스타일
스타일은 컴포넌트가 처음 렌더링될 때 <style id="nowsoft-lab-player-style"> 로 자동 삽입됩니다.
- CSP nonce 가 필요하면
styleNonce를 넘깁니다. - 직접 관리하려면
injectStyles={false}로 끄고, export 된playerStyles(CSS 문자열)를 원하는 방식으로 삽입합니다. ads사용 시 IMA SDK(imasdk.googleapis.com)와 광고 도메인(*.doubleclick.net,*.googlesyndication.com등)을script-src/frame-src/media-src에 허용해야 합니다.
문서
- 개발자 가이드 · 데모: https://nowsoft-player-sdk.web.app
- 코딩 에이전트(LLM)용 레퍼런스: https://nowsoft-player-sdk.web.app/llms.txt
License
UNLICENSED
