npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nowsoft-lab/player-sdk

v0.1.8

Published

UI 커스텀이 자유로운 React 웹 플레이어 SDK. hls.js / dash.js 는 번들에 포함되지 않으며 Tech 방식으로 외부에서 주입합니다.

Readme


특징

  • Tech 주입 방식 — hls.js / dashjs 를 번들에 포함하지 않고 외부에서 주입합니다 (IVS registerIVSTech 와 유사). 동적 import 로 지연 로드도 가능합니다.
  • 자유로운 UI 구성 — 컨트롤 배치(layout)와 테마·동작(ui)을 JSON 으로 지정합니다. PC / 모바일 / 전체 화면, VOD / LIVE 별로 분기할 수 있으며 VPE ControlBarLayout 과 호환됩니다.
  • 라이브 — ● LIVE 배지, DVR 탐색, 실시간 지점 복귀, LL-HLS 지원
  • 화질 / 자막 / 재생 속도 설정 메뉴, WebVTT 자막(HTML 렌더링)
  • YouTube 호환 단축키, 모바일 탭 · 더블탭 탐색 제스처
  • 자동재생이 차단되면 음소거로 재생하고 "클릭하여 소리켜기" 안내
  • Google IMA 광고 — ads prop 하나로 프리롤 · 미드롤 · 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