kplayer-ts 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,6 +41,99 @@ const dp = new KPlayer({
41
41
 
42
42
  ---
43
43
 
44
+ ## React / Next.js
45
+
46
+ KPlayer is **SSR-safe** — importing the package never touches `window` / `document`
47
+ at module load, so it works in Next.js (App Router or Pages Router) and during the
48
+ server build without crashing.
49
+
50
+ The player ships with a built-in `16 / 9` aspect ratio, so it stays visible even when
51
+ the container has no explicit height. Give the container a `height` to override it.
52
+
53
+ ### Use the bundled component (recommended)
54
+
55
+ A ready-made wrapper is published at `kplayer-ts/react`. It creates the player in
56
+ `useEffect` and destroys it on unmount for you. `react` is an optional peer dependency.
57
+
58
+ ```tsx
59
+ import { KPlayer } from "kplayer-ts/react";
60
+
61
+ export function VideoPlayer({ url }: { url: string }) {
62
+ return (
63
+ <KPlayer
64
+ options={{ video: { url, type: "hls" } }}
65
+ style={{ width: "100%" }}
66
+ />
67
+ );
68
+ }
69
+ ```
70
+
71
+ In **Next.js (App Router)**, add `"use client"` at the top of the file. To access the
72
+ underlying instance, pass a `ref` or use `onReady`:
73
+
74
+ ```tsx
75
+ "use client";
76
+ import { useRef } from "react";
77
+ import { KPlayer, type KPlayerInstance } from "kplayer-ts/react";
78
+
79
+ export default function VideoPlayer({ url }: { url: string }) {
80
+ const ref = useRef<KPlayerInstance>(null);
81
+ return (
82
+ <KPlayer
83
+ ref={ref}
84
+ options={{ video: { url, type: "hls" } }}
85
+ onReady={(player) => console.log("ready", player.version)}
86
+ style={{ width: "100%" }}
87
+ />
88
+ );
89
+ }
90
+ ```
91
+
92
+ | Prop | Type | Description |
93
+ |---|---|---|
94
+ | `options` | `Omit<KPlayerOptions, "container">` | Player options (no `container` — the component owns the `<div>`). Read once on mount; change `key` to reload. |
95
+ | `onReady` | `(player) => void` | Called once with the instance right after creation |
96
+ | `ref` | `Ref<KPlayerInstance>` | Receives the player instance (`null` after unmount) |
97
+ | …rest | `HTMLAttributes<HTMLDivElement>` | `style`, `className`, etc. are forwarded to the container `<div>` |
98
+
99
+ ### Or wire it up manually
100
+
101
+ If you prefer not to use the wrapper, create the instance yourself inside `useEffect`
102
+ (browser only) and call `destroy()` on unmount:
103
+
104
+ ```tsx
105
+ "use client";
106
+ import { useEffect, useRef } from "react";
107
+ import KPlayer from "kplayer-ts";
108
+
109
+ export default function VideoPlayer({ url }: { url: string }) {
110
+ const ref = useRef<HTMLDivElement>(null);
111
+ useEffect(() => {
112
+ if (!ref.current) return;
113
+ const dp = new KPlayer({ container: ref.current, video: { url, type: "hls" } });
114
+ return () => dp.destroy();
115
+ }, [url]);
116
+ return <div ref={ref} style={{ width: "100%" }} />;
117
+ }
118
+ ```
119
+
120
+ ### Loading `hls.js`
121
+
122
+ `hls.js` is an external peer of the bundle — the player reads it from `window.Hls`.
123
+ Import it and expose it before creating the player:
124
+
125
+ ```tsx
126
+ import Hls from "hls.js";
127
+ if (typeof window !== "undefined") {
128
+ (window as unknown as { Hls: typeof Hls }).Hls = Hls;
129
+ }
130
+ ```
131
+
132
+ Alternatively, pass your own instance via [`video.customType`](#mse-support) — that
133
+ path does not need `window.Hls`.
134
+
135
+ ---
136
+
44
137
  ## Options
45
138
 
46
139
  | Name | Default | Description |
@@ -96,6 +189,31 @@ const dp = new KPlayer({
96
189
  | `subtitle.encrypt` | `false` | Enable AES-CBC encrypted subtitles |
97
190
  | `subtitle.key` | — | AES key (hex string, 32 chars) |
98
191
  | `subtitle.iv` | — | AES IV (hex string, 32 chars) |
192
+ | `subtitle.keyProvider` | — | `() => { key, iv } \| Promise<{ key, iv }>` — fetch the key/iv at runtime instead of hard-coding them |
193
+
194
+ > ⚠️ **Security note on encrypted subtitles.** Decryption happens **in the browser**,
195
+ > so the `key` and `iv` can never be fully hidden from a determined user — they are
196
+ > always recoverable from DevTools / the Network tab / the JS bundle. Subtitle
197
+ > "encryption" here only deters *casual* scraping; it is **not** real content
198
+ > protection. Two practical steps:
199
+ >
200
+ > 1. **Don't hard-code the key in your page/bundle.** Use `keyProvider` to fetch it
201
+ > at runtime from an authenticated backend endpoint, so the key isn't published
202
+ > in static source and the server can apply auth / rate-limiting / per-session keys.
203
+ > 2. For genuine protection of the *video* itself, use DRM (EME — Widevine / PlayReady
204
+ > / FairPlay), not client-side AES.
205
+ >
206
+ > ```ts
207
+ > subtitle: {
208
+ > url: [{ name: "Монгол", url: "mn.vtt", lang: "mn" }],
209
+ > encrypt: true,
210
+ > // key/iv-г bundle-д бичихгүйгээр backend-ээс татаж авна
211
+ > keyProvider: async () => {
212
+ > const res = await fetch("/api/subtitle-key", { credentials: "include" });
213
+ > return res.json(); // → { key: "…hex…", iv: "…hex…" }
214
+ > },
215
+ > }
216
+ > ```
99
217
 
100
218
  ### `title` options
101
219
 
@@ -458,6 +576,34 @@ const dp = new KPlayer({
458
576
  | `↓` | Volume down |
459
577
  | `Esc` | Exit web fullscreen |
460
578
 
579
+ ### Touch gestures (mobile)
580
+
581
+ | Gesture | Action |
582
+ |---|---|
583
+ | Single tap | Show / hide controls |
584
+ | Double-tap left half | Seek back 10s (repeat to stack: 20s, 30s…) |
585
+ | Double-tap right half | Seek forward 10s (repeat to stack) |
586
+
587
+ A YouTube-style ripple shows the seek direction and accumulated amount.
588
+
589
+ ---
590
+
591
+ ## Accessibility
592
+
593
+ KPlayer ships with built-in a11y support:
594
+
595
+ - All controls are real `<button>`s with localized `aria-label`s; decorative icons are
596
+ `aria-hidden`. The player root is a labelled `role="region"` (uses the video title).
597
+ - The progress and volume bars are `role="slider"` with live `aria-valuenow` /
598
+ `aria-valuetext`, and are keyboard-operable when focused:
599
+ - **Seek bar**: `←` / `→` (±5s), `PageUp` / `PageDown` (±30s), `Home` / `End`.
600
+ - **Volume bar**: `↑` / `↓` (±5%), `Home` / `End`.
601
+ - Settings-menu rows are `role="menuitem"`, reachable by `Tab` and activated with
602
+ `Enter` / `Space`.
603
+ - A visible focus ring is shown for keyboard users (`:focus-visible`).
604
+
605
+ Labels are localized in every supported `lang`.
606
+
461
607
  ---
462
608
 
463
609
  ## License