@videncrypt/react 0.1.0

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 ADDED
@@ -0,0 +1,288 @@
1
+ # @videncrypt/react
2
+
3
+ React component and hook for embedding secure VidEncrypt videos.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @videncrypt/react
9
+ ```
10
+
11
+ > `@videncrypt/js` is installed automatically as a dependency.
12
+
13
+ ---
14
+
15
+ ## Quick start
16
+
17
+ ```tsx
18
+ import { VidEncryptPlayer } from '@videncrypt/react';
19
+
20
+ export default function Page() {
21
+ return (
22
+ <VidEncryptPlayer
23
+ videoId="YOUR_VIDEO_ID"
24
+ onPlay={() => console.log('playing')}
25
+ onEnded={() => console.log('ended')}
26
+ />
27
+ );
28
+ }
29
+ ```
30
+
31
+ ---
32
+
33
+ ## VidEncryptPlayer
34
+
35
+ The main component. Renders the player inside a responsive container with built-in loading and error states.
36
+
37
+ ### Props
38
+
39
+ | Prop | Type | Default | Description |
40
+ |---|---|---|---|
41
+ | `videoId` | `string` | **required** | Video ID from your VidEncrypt dashboard |
42
+ | `width` | `string \| number` | `'100%'` | Player width |
43
+ | `aspectRatio` | `string` | `'16/9'` | Aspect ratio e.g. `'4/3'`, `'1/1'` |
44
+ | `title` | `string` | `''` | iframe title (accessibility) |
45
+ | `autoPlay` | `boolean` | `false` | Auto-play on load (requires `muted: true` in most browsers) |
46
+ | `muted` | `boolean` | `false` | Start muted |
47
+ | `loop` | `boolean` | `false` | Loop video |
48
+ | `startTime` | `number` | `0` | Start at N seconds |
49
+ | `showControls` | `boolean` | `true` | Show player controls |
50
+ | `showBranding` | `boolean` | `true` | Show VidEncrypt badge |
51
+ | `primaryColor` | `string` | `'#2563EB'` | Accent color (hex) |
52
+ | `embedBaseUrl` | `string` | `'https://app.videncrypt.com'` | Override for staging |
53
+ | `className` | `string` | — | CSS class on the wrapper div |
54
+ | `style` | `CSSProperties` | — | Inline style on the wrapper div |
55
+ | `loadingSlot` | `ReactNode` | built-in spinner | Custom loading state |
56
+ | `errorSlot` | `(error) => ReactNode` | built-in error UI | Custom error state |
57
+ | `onReady` | `() => void` | — | Player initialized |
58
+ | `onPlay` | `() => void` | — | Playback started |
59
+ | `onPause` | `() => void` | — | Playback paused |
60
+ | `onEnded` | `() => void` | — | Video ended |
61
+ | `onProgress` | `(currentTime, duration) => void` | — | Position update (~5s interval) |
62
+ | `onError` | `(error: PlayerError) => void` | — | Playback error |
63
+ | `onFullscreenChange` | `(isFullscreen: boolean) => void` | — | Fullscreen toggled |
64
+
65
+ ### Examples
66
+
67
+ **Custom aspect ratio:**
68
+ ```tsx
69
+ <VidEncryptPlayer
70
+ videoId="YOUR_VIDEO_ID"
71
+ aspectRatio="4/3"
72
+ width={640}
73
+ />
74
+ ```
75
+
76
+ **Autoplay muted:**
77
+ ```tsx
78
+ <VidEncryptPlayer
79
+ videoId="YOUR_VIDEO_ID"
80
+ autoPlay
81
+ muted
82
+ />
83
+ ```
84
+
85
+ **Custom loading and error states:**
86
+ ```tsx
87
+ <VidEncryptPlayer
88
+ videoId="YOUR_VIDEO_ID"
89
+ loadingSlot={<MySpinner />}
90
+ errorSlot={(error) => (
91
+ <div className="error">
92
+ {error.message} ({error.code})
93
+ </div>
94
+ )}
95
+ />
96
+ ```
97
+
98
+ **Custom accent color:**
99
+ ```tsx
100
+ <VidEncryptPlayer
101
+ videoId="YOUR_VIDEO_ID"
102
+ primaryColor="#7C3AED"
103
+ />
104
+ ```
105
+
106
+ ---
107
+
108
+ ## usePlayer hook
109
+
110
+ For full control over the player with your own UI.
111
+
112
+ ```tsx
113
+ import { usePlayer } from '@videncrypt/react';
114
+
115
+ function CustomPlayer({ videoId }: { videoId: string }) {
116
+ const {
117
+ containerRef,
118
+ playing,
119
+ state,
120
+ play,
121
+ pause,
122
+ seek,
123
+ setVolume,
124
+ } = usePlayer({ videoId });
125
+
126
+ return (
127
+ <div>
128
+ {/* Player mounts here */}
129
+ <div ref={containerRef} />
130
+
131
+ {/* Custom controls */}
132
+ <button onClick={playing ? pause : play}>
133
+ {playing ? 'Pause' : 'Play'}
134
+ </button>
135
+
136
+ <button onClick={() => seek(30)}>
137
+ Skip to 30s
138
+ </button>
139
+
140
+ <input
141
+ type="range"
142
+ min={0}
143
+ max={1}
144
+ step={0.1}
145
+ onChange={(e) => setVolume(Number(e.target.value))}
146
+ />
147
+
148
+ <p>
149
+ {state.currentTime.toFixed(0)}s / {state.duration.toFixed(0)}s
150
+ </p>
151
+ </div>
152
+ );
153
+ }
154
+ ```
155
+
156
+ ### Hook return values
157
+
158
+ | Value | Type | Description |
159
+ |---|---|---|
160
+ | `containerRef` | `RefObject<HTMLDivElement>` | Attach to your container div |
161
+ | `state` | `PlayerState` | Full player state snapshot |
162
+ | `ready` | `boolean` | True after player initializes |
163
+ | `playing` | `boolean` | True while playing |
164
+ | `error` | `PlayerError \| null` | Current error or null |
165
+ | `play()` | `() => void` | Start playback |
166
+ | `pause()` | `() => void` | Pause playback |
167
+ | `seek(time)` | `(number) => void` | Seek to seconds |
168
+ | `setVolume(v)` | `(number) => void` | Set volume 0–1 |
169
+ | `mute()` | `() => void` | Mute audio |
170
+ | `unmute()` | `() => void` | Unmute audio |
171
+ | `enterFullscreen()` | `() => void` | Enter fullscreen |
172
+ | `exitFullscreen()` | `() => void` | Exit fullscreen |
173
+
174
+ ### PlayerState
175
+
176
+ ```typescript
177
+ interface PlayerState {
178
+ playing: boolean;
179
+ muted: boolean;
180
+ currentTime: number;
181
+ duration: number;
182
+ fullscreen: boolean;
183
+ ready: boolean;
184
+ }
185
+ ```
186
+
187
+ ### PlayerError
188
+
189
+ ```typescript
190
+ interface PlayerError {
191
+ code: 'token-expired' | 'not-found' | 'domain-not-allowed'
192
+ | 'network-error' | 'unknown';
193
+ message: string;
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Framework examples
200
+
201
+ ### Next.js App Router
202
+
203
+ ```tsx
204
+ // app/watch/[id]/page.tsx
205
+ import { VidEncryptPlayer } from '@videncrypt/react';
206
+
207
+ export default function WatchPage({ params }: { params: { id: string } }) {
208
+ return (
209
+ <main className="max-w-4xl mx-auto p-6">
210
+ <VidEncryptPlayer videoId={params.id} />
211
+ </main>
212
+ );
213
+ }
214
+ ```
215
+
216
+ ### Next.js with progress tracking
217
+
218
+ ```tsx
219
+ 'use client';
220
+
221
+ import { VidEncryptPlayer } from '@videncrypt/react';
222
+ import { useState } from 'react';
223
+
224
+ export default function VideoPage({ videoId }: { videoId: string }) {
225
+ const [progress, setProgress] = useState(0);
226
+
227
+ return (
228
+ <div>
229
+ <VidEncryptPlayer
230
+ videoId={videoId}
231
+ onProgress={(currentTime, duration) => {
232
+ setProgress(Math.round((currentTime / duration) * 100));
233
+ }}
234
+ />
235
+ <p>{progress}% watched</p>
236
+ </div>
237
+ );
238
+ }
239
+ ```
240
+
241
+ ### Pause other videos when one plays
242
+
243
+ ```tsx
244
+ 'use client';
245
+
246
+ import { usePlayer } from '@videncrypt/react';
247
+
248
+ const VIDEO_IDS = ['video-aaa', 'video-bbb', 'video-ccc'];
249
+
250
+ export default function VideoList() {
251
+ const players = VIDEO_IDS.map((id) =>
252
+ usePlayer({
253
+ videoId: id,
254
+ onPlay: () => {
255
+ // pause all others when this one plays
256
+ players.forEach((p, i) => {
257
+ if (VIDEO_IDS[i] !== id) p.pause();
258
+ });
259
+ },
260
+ })
261
+ );
262
+
263
+ return (
264
+ <div className="grid grid-cols-3 gap-4">
265
+ {players.map((player, i) => (
266
+ <div key={VIDEO_IDS[i]} ref={player.containerRef} />
267
+ ))}
268
+ </div>
269
+ );
270
+ }
271
+ ```
272
+
273
+ ---
274
+
275
+ ## TypeScript
276
+
277
+ All types are exported from `@videncrypt/react` — no need to import from `@videncrypt/js` separately.
278
+
279
+ ```typescript
280
+ import type {
281
+ PlayerOptions,
282
+ PlayerState,
283
+ PlayerError,
284
+ VidEncryptPlayerProps,
285
+ UsePlayerOptions,
286
+ UsePlayerReturn,
287
+ } from '@videncrypt/react';
288
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ VidEncryptPlayer: () => VidEncryptPlayer,
24
+ usePlayer: () => usePlayer
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/use-player.ts
29
+ var import_react = require("react");
30
+ var import_js = require("@videncrypt/js");
31
+ function usePlayer(options) {
32
+ var _a, _b;
33
+ const containerRef = (0, import_react.useRef)(null);
34
+ const playerRef = (0, import_react.useRef)(null);
35
+ const [state, setState] = (0, import_react.useState)({
36
+ playing: false,
37
+ muted: (_a = options.muted) != null ? _a : false,
38
+ currentTime: (_b = options.startTime) != null ? _b : 0,
39
+ duration: 0,
40
+ fullscreen: false,
41
+ ready: false
42
+ });
43
+ const [error, setError] = (0, import_react.useState)(null);
44
+ const optsRef = (0, import_react.useRef)(options);
45
+ optsRef.current = options;
46
+ (0, import_react.useEffect)(() => {
47
+ var _a2;
48
+ const container = containerRef.current;
49
+ if (!container) return;
50
+ (_a2 = playerRef.current) == null ? void 0 : _a2.destroy();
51
+ setError(null);
52
+ const player = new import_js.Player({
53
+ ...optsRef.current,
54
+ container,
55
+ onReady: () => {
56
+ var _a3, _b2;
57
+ setState((s) => ({ ...s, ready: true }));
58
+ (_b2 = (_a3 = optsRef.current).onReady) == null ? void 0 : _b2.call(_a3);
59
+ },
60
+ onPlay: () => {
61
+ var _a3, _b2;
62
+ setState((s) => ({ ...s, playing: true }));
63
+ (_b2 = (_a3 = optsRef.current).onPlay) == null ? void 0 : _b2.call(_a3);
64
+ },
65
+ onPause: () => {
66
+ var _a3, _b2;
67
+ setState((s) => ({ ...s, playing: false }));
68
+ (_b2 = (_a3 = optsRef.current).onPause) == null ? void 0 : _b2.call(_a3);
69
+ },
70
+ onEnded: () => {
71
+ var _a3, _b2;
72
+ setState((s) => ({ ...s, playing: false }));
73
+ (_b2 = (_a3 = optsRef.current).onEnded) == null ? void 0 : _b2.call(_a3);
74
+ },
75
+ onProgress: (currentTime, duration) => {
76
+ var _a3, _b2;
77
+ setState((s) => ({ ...s, currentTime, duration }));
78
+ (_b2 = (_a3 = optsRef.current).onProgress) == null ? void 0 : _b2.call(_a3, currentTime, duration);
79
+ },
80
+ onError: (e) => {
81
+ var _a3, _b2;
82
+ setError(e);
83
+ (_b2 = (_a3 = optsRef.current).onError) == null ? void 0 : _b2.call(_a3, e);
84
+ },
85
+ onFullscreenChange: (isFullscreen) => {
86
+ var _a3, _b2;
87
+ setState((s) => ({ ...s, fullscreen: isFullscreen }));
88
+ (_b2 = (_a3 = optsRef.current).onFullscreenChange) == null ? void 0 : _b2.call(_a3, isFullscreen);
89
+ }
90
+ });
91
+ playerRef.current = player;
92
+ return () => {
93
+ player.destroy();
94
+ playerRef.current = null;
95
+ };
96
+ }, [options.videoId]);
97
+ const play = (0, import_react.useCallback)(() => {
98
+ var _a2;
99
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.play();
100
+ }, []);
101
+ const pause = (0, import_react.useCallback)(() => {
102
+ var _a2;
103
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.pause();
104
+ }, []);
105
+ const seek = (0, import_react.useCallback)((t) => {
106
+ var _a2;
107
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.seek(t);
108
+ }, []);
109
+ const setVolume = (0, import_react.useCallback)((v) => {
110
+ var _a2;
111
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.setVolume(v);
112
+ }, []);
113
+ const mute = (0, import_react.useCallback)(() => {
114
+ var _a2;
115
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.mute();
116
+ }, []);
117
+ const unmute = (0, import_react.useCallback)(() => {
118
+ var _a2;
119
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.unmute();
120
+ }, []);
121
+ const enterFullscreen = (0, import_react.useCallback)(() => {
122
+ var _a2;
123
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.enterFullscreen();
124
+ }, []);
125
+ const exitFullscreen = (0, import_react.useCallback)(() => {
126
+ var _a2;
127
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.exitFullscreen();
128
+ }, []);
129
+ return {
130
+ containerRef,
131
+ state,
132
+ ready: state.ready,
133
+ playing: state.playing,
134
+ error,
135
+ play,
136
+ pause,
137
+ seek,
138
+ setVolume,
139
+ mute,
140
+ unmute,
141
+ enterFullscreen,
142
+ exitFullscreen
143
+ };
144
+ }
145
+
146
+ // src/player.tsx
147
+ var import_jsx_runtime = require("react/jsx-runtime");
148
+ function VidEncryptPlayer({
149
+ className,
150
+ style,
151
+ loadingSlot,
152
+ errorSlot,
153
+ ...options
154
+ }) {
155
+ const { containerRef, ready, error } = usePlayer(options);
156
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
157
+ "div",
158
+ {
159
+ style: {
160
+ position: "relative",
161
+ width: "100%",
162
+ background: "#000",
163
+ ...style
164
+ },
165
+ className,
166
+ children: [
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, style: { width: "100%" } }),
168
+ !ready && !error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: overlayStyle, children: loadingSlot != null ? loadingSlot : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultLoading, {}) }),
169
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: overlayStyle, children: errorSlot ? errorSlot(error) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultError, { error }) })
170
+ ]
171
+ }
172
+ );
173
+ }
174
+ function DefaultLoading() {
175
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
176
+ position: "absolute",
177
+ inset: 0,
178
+ display: "flex",
179
+ alignItems: "center",
180
+ justifyContent: "center",
181
+ background: "#000",
182
+ pointerEvents: "none"
183
+ }, children: [
184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
185
+ width: 32,
186
+ height: 32,
187
+ border: "3px solid rgba(255,255,255,0.15)",
188
+ borderTopColor: "#2563EB",
189
+ borderRadius: "50%",
190
+ animation: "ve-spin 0.75s linear infinite"
191
+ } }),
192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
193
+ @keyframes ve-spin { to { transform: rotate(360deg); } }
194
+ ` })
195
+ ] });
196
+ }
197
+ function DefaultError({ error }) {
198
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
199
+ position: "absolute",
200
+ inset: 0,
201
+ display: "flex",
202
+ flexDirection: "column",
203
+ alignItems: "center",
204
+ justifyContent: "center",
205
+ background: "#000",
206
+ gap: 8
207
+ }, children: [
208
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: "rgba(255,255,255,0.5)", fontSize: 13, margin: 0 }, children: error.message }),
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { color: "rgba(255,255,255,0.25)", fontSize: 11, margin: 0 }, children: error.code })
210
+ ] });
211
+ }
212
+ var overlayStyle = {
213
+ position: "absolute",
214
+ inset: 0
215
+ };
216
+ // Annotate the CommonJS export names for ESM import in node:
217
+ 0 && (module.exports = {
218
+ VidEncryptPlayer,
219
+ usePlayer
220
+ });
221
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/use-player.ts","../src/player.tsx"],"sourcesContent":["// Main component\nexport { VidEncryptPlayer } from './player';\nexport type { VidEncryptPlayerProps } from './player';\n\n// Hook for custom implementations\nexport { usePlayer } from './use-player';\nexport type { UsePlayerOptions, UsePlayerReturn } from './use-player';\n\n// Re-export types from @videncrypt/js so consumers\n// only need to import from @videncrypt/react\nexport type {\n PlayerOptions,\n PlayerState,\n PlayerError,\n PlayerEventMap,\n} from '@videncrypt/js';","import { useEffect, useRef, useState, useCallback } from 'react';\nimport { Player } from '@videncrypt/js';\nimport type { PlayerOptions, PlayerState, PlayerError } from '@videncrypt/js';\n\n// ── usePlayer hook ─────────────────────────────────────────\n// Creates and manages a VidEncryptPlayer instance.\n// Handles lifecycle — creates on mount, destroys on unmount.\n// Re-creates when videoId changes.\n\nexport interface UsePlayerOptions\n extends Omit<PlayerOptions, 'container'> {\n // container is handled by the component via ref\n}\n\nexport interface UsePlayerReturn {\n // Attach this ref to the container div\n containerRef: React.RefObject<HTMLDivElement | null>;\n\n // Current player state\n state: PlayerState;\n ready: boolean;\n playing: boolean;\n error: PlayerError | null;\n\n // Playback controls\n play: () => void;\n pause: () => void;\n seek: (time: number) => void;\n setVolume: (volume: number) => void;\n mute: () => void;\n unmute: () => void;\n enterFullscreen: () => void;\n exitFullscreen: () => void;\n}\n\nexport function usePlayer(options: UsePlayerOptions): UsePlayerReturn {\n const containerRef = useRef<HTMLDivElement>(null);\n const playerRef = useRef<InstanceType<typeof Player> | null>(null);\n\n const [state, setState] = useState<PlayerState>({\n playing: false,\n muted: options.muted ?? false,\n currentTime: options.startTime ?? 0,\n duration: 0,\n fullscreen: false,\n ready: false,\n });\n\n const [error, setError] = useState<PlayerError | null>(null);\n\n // Stable options ref — avoids recreating player on every render\n // when callbacks are inline arrow functions\n const optsRef = useRef(options);\n optsRef.current = options;\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Destroy previous instance if videoId changed\n playerRef.current?.destroy();\n setError(null);\n\n const player = new Player({\n ...optsRef.current,\n container,\n\n onReady: () => {\n setState((s) => ({ ...s, ready: true }));\n optsRef.current.onReady?.();\n },\n onPlay: () => {\n setState((s) => ({ ...s, playing: true }));\n optsRef.current.onPlay?.();\n },\n onPause: () => {\n setState((s) => ({ ...s, playing: false }));\n optsRef.current.onPause?.();\n },\n onEnded: () => {\n setState((s) => ({ ...s, playing: false }));\n optsRef.current.onEnded?.();\n },\n onProgress: (currentTime, duration) => {\n setState((s) => ({ ...s, currentTime, duration }));\n optsRef.current.onProgress?.(currentTime, duration);\n },\n onError: (e) => {\n setError(e);\n optsRef.current.onError?.(e);\n },\n onFullscreenChange: (isFullscreen) => {\n setState((s) => ({ ...s, fullscreen: isFullscreen }));\n optsRef.current.onFullscreenChange?.(isFullscreen);\n },\n });\n\n playerRef.current = player;\n\n return () => {\n player.destroy();\n playerRef.current = null;\n };\n }, [options.videoId]); // only re-create when videoId changes\n\n // ── Stable control functions ───────────────────────────\n // useCallback with empty deps — these never change,\n // they just call the current player ref\n\n const play = useCallback(() => playerRef.current?.play(), []);\n const pause = useCallback(() => playerRef.current?.pause(), []);\n const seek = useCallback((t: number) => playerRef.current?.seek(t), []);\n const setVolume = useCallback((v: number) => playerRef.current?.setVolume(v), []);\n const mute = useCallback(() => playerRef.current?.mute(), []);\n const unmute = useCallback(() => playerRef.current?.unmute(), []);\n const enterFullscreen = useCallback(() => playerRef.current?.enterFullscreen(), []);\n const exitFullscreen = useCallback(() => playerRef.current?.exitFullscreen(), []);\n\n return {\n containerRef,\n state,\n ready: state.ready,\n playing: state.playing,\n error,\n play,\n pause,\n seek,\n setVolume,\n mute,\n unmute,\n enterFullscreen,\n exitFullscreen,\n };\n}","'use client';\n\nimport { usePlayer, type UsePlayerOptions } from './use-player';\nimport type { PlayerError } from '@videncrypt/js';\n\n// ── Props ──────────────────────────────────────────────────\n\nexport interface VidEncryptPlayerProps extends UsePlayerOptions {\n // Container sizing\n className?: string;\n style?: React.CSSProperties;\n\n // Loading slot — shown while player initializes\n // Default: simple grey box\n loadingSlot?: React.ReactNode;\n\n // Error slot — shown on playback error\n // Default: error message + code\n errorSlot?: (error: PlayerError) => React.ReactNode;\n}\n\n// ── Component ──────────────────────────────────────────────\n\nexport function VidEncryptPlayer({\n className,\n style,\n loadingSlot,\n errorSlot,\n ...options\n}: VidEncryptPlayerProps) {\n const { containerRef, ready, error } = usePlayer(options);\n\n return (\n <div\n style={{\n position: 'relative',\n width: '100%',\n background: '#000',\n ...style,\n }}\n className={className}\n >\n {/* Player mounts here — always rendered so the ref is stable */}\n <div ref={containerRef} style={{ width: '100%' }} />\n\n {/* Loading overlay — shown until player is ready */}\n {!ready && !error && (\n <div style={overlayStyle}>\n {loadingSlot ?? <DefaultLoading />}\n </div>\n )}\n\n {/* Error overlay */}\n {error && (\n <div style={overlayStyle}>\n {errorSlot ? errorSlot(error) : <DefaultError error={error} />}\n </div>\n )}\n </div>\n );\n}\n\n// ── Default loading state ──────────────────────────────────\n\nfunction DefaultLoading() {\n return (\n <div style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: '#000',\n pointerEvents: 'none',\n }}>\n <div style={{\n width: 32,\n height: 32,\n border: '3px solid rgba(255,255,255,0.15)',\n borderTopColor: '#2563EB',\n borderRadius: '50%',\n animation: 've-spin 0.75s linear infinite',\n }} />\n <style>{`\n @keyframes ve-spin { to { transform: rotate(360deg); } }\n `}</style>\n </div>\n );\n}\n\n// ── Default error state ────────────────────────────────────\n\nfunction DefaultError({ error }: { error: PlayerError }) {\n return (\n <div style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n background: '#000',\n gap: 8,\n }}>\n <p style={{ color: 'rgba(255,255,255,0.5)', fontSize: 13, margin: 0 }}>\n {error.message}\n </p>\n <p style={{ color: 'rgba(255,255,255,0.25)', fontSize: 11, margin: 0 }}>\n {error.code}\n </p>\n </div>\n );\n}\n\n// ── Shared styles ──────────────────────────────────────────\n\nconst overlayStyle: React.CSSProperties = {\n position: 'absolute',\n inset: 0,\n};"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAyD;AACzD,gBAAuB;AAkChB,SAAS,UAAU,SAA4C;AAnCtE;AAoCE,QAAM,mBAAe,qBAAuB,IAAI;AAChD,QAAM,gBAAe,qBAA2C,IAAI;AAEpE,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAsB;AAAA,IAC9C,SAAa;AAAA,IACb,QAAa,aAAQ,UAAR,YAAoB;AAAA,IACjC,cAAa,aAAQ,cAAR,YAAqB;AAAA,IAClC,UAAa;AAAA,IACb,YAAa;AAAA,IACb,OAAa;AAAA,EACf,CAAC;AAED,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAA6B,IAAI;AAI3D,QAAM,cAAU,qBAAO,OAAO;AAC9B,UAAQ,UAAU;AAElB,8BAAU,MAAM;AAvDlB,QAAAA;AAwDI,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAGhB,KAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB;AACnB,aAAS,IAAI;AAEb,UAAM,SAAS,IAAI,iBAAO;AAAA,MACxB,GAAG,QAAQ;AAAA,MACX;AAAA,MAEA,SAAS,MAAM;AAnErB,YAAAA,KAAAC;AAoEQ,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,KAAK,EAAE;AACvC,SAAAA,OAAAD,MAAA,QAAQ,SAAQ,YAAhB,gBAAAC,IAAA,KAAAD;AAAA,MACF;AAAA,MACA,QAAQ,MAAM;AAvEpB,YAAAA,KAAAC;AAwEQ,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,SAAS,KAAK,EAAE;AACzC,SAAAA,OAAAD,MAAA,QAAQ,SAAQ,WAAhB,gBAAAC,IAAA,KAAAD;AAAA,MACF;AAAA,MACA,SAAS,MAAM;AA3ErB,YAAAA,KAAAC;AA4EQ,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,SAAS,MAAM,EAAE;AAC1C,SAAAA,OAAAD,MAAA,QAAQ,SAAQ,YAAhB,gBAAAC,IAAA,KAAAD;AAAA,MACF;AAAA,MACA,SAAS,MAAM;AA/ErB,YAAAA,KAAAC;AAgFQ,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,SAAS,MAAM,EAAE;AAC1C,SAAAA,OAAAD,MAAA,QAAQ,SAAQ,YAAhB,gBAAAC,IAAA,KAAAD;AAAA,MACF;AAAA,MACA,YAAY,CAAC,aAAa,aAAa;AAnF7C,YAAAA,KAAAC;AAoFQ,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,aAAa,SAAS,EAAE;AACjD,SAAAA,OAAAD,MAAA,QAAQ,SAAQ,eAAhB,gBAAAC,IAAA,KAAAD,KAA6B,aAAa;AAAA,MAC5C;AAAA,MACA,SAAS,CAAC,MAAM;AAvFtB,YAAAA,KAAAC;AAwFQ,iBAAS,CAAC;AACV,SAAAA,OAAAD,MAAA,QAAQ,SAAQ,YAAhB,gBAAAC,IAAA,KAAAD,KAA0B;AAAA,MAC5B;AAAA,MACA,oBAAoB,CAAC,iBAAiB;AA3F5C,YAAAA,KAAAC;AA4FQ,iBAAS,CAAC,OAAO,EAAE,GAAG,GAAG,YAAY,aAAa,EAAE;AACpD,SAAAA,OAAAD,MAAA,QAAQ,SAAQ,uBAAhB,gBAAAC,IAAA,KAAAD,KAAqC;AAAA,MACvC;AAAA,IACF,CAAC;AAED,cAAU,UAAU;AAEpB,WAAO,MAAM;AACX,aAAO,QAAQ;AACf,gBAAU,UAAU;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,CAAC;AAMpB,QAAM,WAAkB,0BAAY,MAAG;AA7GzC,QAAAA;AA6G4C,YAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB;AAAA,KAA2B,CAAC,CAAC;AAC1F,QAAM,YAAkB,0BAAY,MAAG;AA9GzC,QAAAA;AA8G4C,YAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB;AAAA,KAA2B,CAAC,CAAC;AAC1F,QAAM,WAAkB,0BAAY,CAAC,MAAW;AA/GlD,QAAAA;AA+GqD,YAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB,KAAK;AAAA,KAAa,CAAC,CAAC;AAC1F,QAAM,gBAAkB,0BAAY,CAAC,MAAW;AAhHlD,QAAAA;AAgHqD,YAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB,UAAU;AAAA,KAAQ,CAAC,CAAC;AAC1F,QAAM,WAAkB,0BAAY,MAAG;AAjHzC,QAAAA;AAiH4C,YAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB;AAAA,KAA2B,CAAC,CAAC;AAC1F,QAAM,aAAkB,0BAAY,MAAG;AAlHzC,QAAAA;AAkH4C,YAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB;AAAA,KAA2B,CAAC,CAAC;AAC1F,QAAM,sBAAkB,0BAAY,MAAG;AAnHzC,QAAAA;AAmH4C,YAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB;AAAA,KAA2B,CAAC,CAAC;AAC1F,QAAM,qBAAkB,0BAAY,MAAG;AApHzC,QAAAA;AAoH4C,YAAAA,MAAA,UAAU,YAAV,gBAAAA,IAAmB;AAAA,KAA2B,CAAC,CAAC;AAE1F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAS,MAAM;AAAA,IACf,SAAS,MAAM;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACpGI;AAVG,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAA0B;AACxB,QAAM,EAAE,cAAc,OAAO,MAAM,IAAI,UAAU,OAAO;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,QACL,UAAY;AAAA,QACZ,OAAY;AAAA,QACZ,YAAY;AAAA,QACZ,GAAG;AAAA,MACL;AAAA,MACA;AAAA,MAGA;AAAA,oDAAC,SAAI,KAAK,cAAc,OAAO,EAAE,OAAO,OAAO,GAAG;AAAA,QAGjD,CAAC,SAAS,CAAC,SACV,4CAAC,SAAI,OAAO,cACT,8CAAe,4CAAC,kBAAe,GAClC;AAAA,QAID,SACC,4CAAC,SAAI,OAAO,cACT,sBAAY,UAAU,KAAK,IAAI,4CAAC,gBAAa,OAAc,GAC9D;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAIA,SAAS,iBAAiB;AACxB,SACE,6CAAC,SAAI,OAAO;AAAA,IACV,UAAiB;AAAA,IACjB,OAAiB;AAAA,IACjB,SAAiB;AAAA,IACjB,YAAiB;AAAA,IACjB,gBAAiB;AAAA,IACjB,YAAiB;AAAA,IACjB,eAAiB;AAAA,EACnB,GACE;AAAA,gDAAC,SAAI,OAAO;AAAA,MACV,OAAiB;AAAA,MACjB,QAAiB;AAAA,MACjB,QAAiB;AAAA,MACjB,gBAAiB;AAAA,MACjB,cAAiB;AAAA,MACjB,WAAiB;AAAA,IACnB,GAAG;AAAA,IACH,4CAAC,WAAO;AAAA;AAAA,SAEN;AAAA,KACJ;AAEJ;AAIA,SAAS,aAAa,EAAE,MAAM,GAA2B;AACvD,SACE,6CAAC,SAAI,OAAO;AAAA,IACV,UAAgB;AAAA,IAChB,OAAgB;AAAA,IAChB,SAAgB;AAAA,IAChB,eAAgB;AAAA,IAChB,YAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,YAAgB;AAAA,IAChB,KAAgB;AAAA,EAClB,GACE;AAAA,gDAAC,OAAE,OAAO,EAAE,OAAO,yBAAyB,UAAU,IAAI,QAAQ,EAAE,GACjE,gBAAM,SACT;AAAA,IACA,4CAAC,OAAE,OAAO,EAAE,OAAO,0BAA0B,UAAU,IAAI,QAAQ,EAAE,GAClE,gBAAM,MACT;AAAA,KACF;AAEJ;AAIA,IAAM,eAAoC;AAAA,EACxC,UAAU;AAAA,EACV,OAAU;AACZ;","names":["_a","_b"]}
@@ -0,0 +1,32 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { PlayerOptions, PlayerState, PlayerError } from '@videncrypt/js';
3
+ export { PlayerError, PlayerEventMap, PlayerOptions, PlayerState } from '@videncrypt/js';
4
+
5
+ interface UsePlayerOptions extends Omit<PlayerOptions, 'container'> {
6
+ }
7
+ interface UsePlayerReturn {
8
+ containerRef: React.RefObject<HTMLDivElement | null>;
9
+ state: PlayerState;
10
+ ready: boolean;
11
+ playing: boolean;
12
+ error: PlayerError | null;
13
+ play: () => void;
14
+ pause: () => void;
15
+ seek: (time: number) => void;
16
+ setVolume: (volume: number) => void;
17
+ mute: () => void;
18
+ unmute: () => void;
19
+ enterFullscreen: () => void;
20
+ exitFullscreen: () => void;
21
+ }
22
+ declare function usePlayer(options: UsePlayerOptions): UsePlayerReturn;
23
+
24
+ interface VidEncryptPlayerProps extends UsePlayerOptions {
25
+ className?: string;
26
+ style?: React.CSSProperties;
27
+ loadingSlot?: React.ReactNode;
28
+ errorSlot?: (error: PlayerError) => React.ReactNode;
29
+ }
30
+ declare function VidEncryptPlayer({ className, style, loadingSlot, errorSlot, ...options }: VidEncryptPlayerProps): react_jsx_runtime.JSX.Element;
31
+
32
+ export { type UsePlayerOptions, type UsePlayerReturn, VidEncryptPlayer, type VidEncryptPlayerProps, usePlayer };
@@ -0,0 +1,32 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { PlayerOptions, PlayerState, PlayerError } from '@videncrypt/js';
3
+ export { PlayerError, PlayerEventMap, PlayerOptions, PlayerState } from '@videncrypt/js';
4
+
5
+ interface UsePlayerOptions extends Omit<PlayerOptions, 'container'> {
6
+ }
7
+ interface UsePlayerReturn {
8
+ containerRef: React.RefObject<HTMLDivElement | null>;
9
+ state: PlayerState;
10
+ ready: boolean;
11
+ playing: boolean;
12
+ error: PlayerError | null;
13
+ play: () => void;
14
+ pause: () => void;
15
+ seek: (time: number) => void;
16
+ setVolume: (volume: number) => void;
17
+ mute: () => void;
18
+ unmute: () => void;
19
+ enterFullscreen: () => void;
20
+ exitFullscreen: () => void;
21
+ }
22
+ declare function usePlayer(options: UsePlayerOptions): UsePlayerReturn;
23
+
24
+ interface VidEncryptPlayerProps extends UsePlayerOptions {
25
+ className?: string;
26
+ style?: React.CSSProperties;
27
+ loadingSlot?: React.ReactNode;
28
+ errorSlot?: (error: PlayerError) => React.ReactNode;
29
+ }
30
+ declare function VidEncryptPlayer({ className, style, loadingSlot, errorSlot, ...options }: VidEncryptPlayerProps): react_jsx_runtime.JSX.Element;
31
+
32
+ export { type UsePlayerOptions, type UsePlayerReturn, VidEncryptPlayer, type VidEncryptPlayerProps, usePlayer };
package/dist/index.mjs ADDED
@@ -0,0 +1,193 @@
1
+ // src/use-player.ts
2
+ import { useEffect, useRef, useState, useCallback } from "react";
3
+ import { Player } from "@videncrypt/js";
4
+ function usePlayer(options) {
5
+ var _a, _b;
6
+ const containerRef = useRef(null);
7
+ const playerRef = useRef(null);
8
+ const [state, setState] = useState({
9
+ playing: false,
10
+ muted: (_a = options.muted) != null ? _a : false,
11
+ currentTime: (_b = options.startTime) != null ? _b : 0,
12
+ duration: 0,
13
+ fullscreen: false,
14
+ ready: false
15
+ });
16
+ const [error, setError] = useState(null);
17
+ const optsRef = useRef(options);
18
+ optsRef.current = options;
19
+ useEffect(() => {
20
+ var _a2;
21
+ const container = containerRef.current;
22
+ if (!container) return;
23
+ (_a2 = playerRef.current) == null ? void 0 : _a2.destroy();
24
+ setError(null);
25
+ const player = new Player({
26
+ ...optsRef.current,
27
+ container,
28
+ onReady: () => {
29
+ var _a3, _b2;
30
+ setState((s) => ({ ...s, ready: true }));
31
+ (_b2 = (_a3 = optsRef.current).onReady) == null ? void 0 : _b2.call(_a3);
32
+ },
33
+ onPlay: () => {
34
+ var _a3, _b2;
35
+ setState((s) => ({ ...s, playing: true }));
36
+ (_b2 = (_a3 = optsRef.current).onPlay) == null ? void 0 : _b2.call(_a3);
37
+ },
38
+ onPause: () => {
39
+ var _a3, _b2;
40
+ setState((s) => ({ ...s, playing: false }));
41
+ (_b2 = (_a3 = optsRef.current).onPause) == null ? void 0 : _b2.call(_a3);
42
+ },
43
+ onEnded: () => {
44
+ var _a3, _b2;
45
+ setState((s) => ({ ...s, playing: false }));
46
+ (_b2 = (_a3 = optsRef.current).onEnded) == null ? void 0 : _b2.call(_a3);
47
+ },
48
+ onProgress: (currentTime, duration) => {
49
+ var _a3, _b2;
50
+ setState((s) => ({ ...s, currentTime, duration }));
51
+ (_b2 = (_a3 = optsRef.current).onProgress) == null ? void 0 : _b2.call(_a3, currentTime, duration);
52
+ },
53
+ onError: (e) => {
54
+ var _a3, _b2;
55
+ setError(e);
56
+ (_b2 = (_a3 = optsRef.current).onError) == null ? void 0 : _b2.call(_a3, e);
57
+ },
58
+ onFullscreenChange: (isFullscreen) => {
59
+ var _a3, _b2;
60
+ setState((s) => ({ ...s, fullscreen: isFullscreen }));
61
+ (_b2 = (_a3 = optsRef.current).onFullscreenChange) == null ? void 0 : _b2.call(_a3, isFullscreen);
62
+ }
63
+ });
64
+ playerRef.current = player;
65
+ return () => {
66
+ player.destroy();
67
+ playerRef.current = null;
68
+ };
69
+ }, [options.videoId]);
70
+ const play = useCallback(() => {
71
+ var _a2;
72
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.play();
73
+ }, []);
74
+ const pause = useCallback(() => {
75
+ var _a2;
76
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.pause();
77
+ }, []);
78
+ const seek = useCallback((t) => {
79
+ var _a2;
80
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.seek(t);
81
+ }, []);
82
+ const setVolume = useCallback((v) => {
83
+ var _a2;
84
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.setVolume(v);
85
+ }, []);
86
+ const mute = useCallback(() => {
87
+ var _a2;
88
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.mute();
89
+ }, []);
90
+ const unmute = useCallback(() => {
91
+ var _a2;
92
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.unmute();
93
+ }, []);
94
+ const enterFullscreen = useCallback(() => {
95
+ var _a2;
96
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.enterFullscreen();
97
+ }, []);
98
+ const exitFullscreen = useCallback(() => {
99
+ var _a2;
100
+ return (_a2 = playerRef.current) == null ? void 0 : _a2.exitFullscreen();
101
+ }, []);
102
+ return {
103
+ containerRef,
104
+ state,
105
+ ready: state.ready,
106
+ playing: state.playing,
107
+ error,
108
+ play,
109
+ pause,
110
+ seek,
111
+ setVolume,
112
+ mute,
113
+ unmute,
114
+ enterFullscreen,
115
+ exitFullscreen
116
+ };
117
+ }
118
+
119
+ // src/player.tsx
120
+ import { jsx, jsxs } from "react/jsx-runtime";
121
+ function VidEncryptPlayer({
122
+ className,
123
+ style,
124
+ loadingSlot,
125
+ errorSlot,
126
+ ...options
127
+ }) {
128
+ const { containerRef, ready, error } = usePlayer(options);
129
+ return /* @__PURE__ */ jsxs(
130
+ "div",
131
+ {
132
+ style: {
133
+ position: "relative",
134
+ width: "100%",
135
+ background: "#000",
136
+ ...style
137
+ },
138
+ className,
139
+ children: [
140
+ /* @__PURE__ */ jsx("div", { ref: containerRef, style: { width: "100%" } }),
141
+ !ready && !error && /* @__PURE__ */ jsx("div", { style: overlayStyle, children: loadingSlot != null ? loadingSlot : /* @__PURE__ */ jsx(DefaultLoading, {}) }),
142
+ error && /* @__PURE__ */ jsx("div", { style: overlayStyle, children: errorSlot ? errorSlot(error) : /* @__PURE__ */ jsx(DefaultError, { error }) })
143
+ ]
144
+ }
145
+ );
146
+ }
147
+ function DefaultLoading() {
148
+ return /* @__PURE__ */ jsxs("div", { style: {
149
+ position: "absolute",
150
+ inset: 0,
151
+ display: "flex",
152
+ alignItems: "center",
153
+ justifyContent: "center",
154
+ background: "#000",
155
+ pointerEvents: "none"
156
+ }, children: [
157
+ /* @__PURE__ */ jsx("div", { style: {
158
+ width: 32,
159
+ height: 32,
160
+ border: "3px solid rgba(255,255,255,0.15)",
161
+ borderTopColor: "#2563EB",
162
+ borderRadius: "50%",
163
+ animation: "ve-spin 0.75s linear infinite"
164
+ } }),
165
+ /* @__PURE__ */ jsx("style", { children: `
166
+ @keyframes ve-spin { to { transform: rotate(360deg); } }
167
+ ` })
168
+ ] });
169
+ }
170
+ function DefaultError({ error }) {
171
+ return /* @__PURE__ */ jsxs("div", { style: {
172
+ position: "absolute",
173
+ inset: 0,
174
+ display: "flex",
175
+ flexDirection: "column",
176
+ alignItems: "center",
177
+ justifyContent: "center",
178
+ background: "#000",
179
+ gap: 8
180
+ }, children: [
181
+ /* @__PURE__ */ jsx("p", { style: { color: "rgba(255,255,255,0.5)", fontSize: 13, margin: 0 }, children: error.message }),
182
+ /* @__PURE__ */ jsx("p", { style: { color: "rgba(255,255,255,0.25)", fontSize: 11, margin: 0 }, children: error.code })
183
+ ] });
184
+ }
185
+ var overlayStyle = {
186
+ position: "absolute",
187
+ inset: 0
188
+ };
189
+ export {
190
+ VidEncryptPlayer,
191
+ usePlayer
192
+ };
193
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/use-player.ts","../src/player.tsx"],"sourcesContent":["import { useEffect, useRef, useState, useCallback } from 'react';\nimport { Player } from '@videncrypt/js';\nimport type { PlayerOptions, PlayerState, PlayerError } from '@videncrypt/js';\n\n// ── usePlayer hook ─────────────────────────────────────────\n// Creates and manages a VidEncryptPlayer instance.\n// Handles lifecycle — creates on mount, destroys on unmount.\n// Re-creates when videoId changes.\n\nexport interface UsePlayerOptions\n extends Omit<PlayerOptions, 'container'> {\n // container is handled by the component via ref\n}\n\nexport interface UsePlayerReturn {\n // Attach this ref to the container div\n containerRef: React.RefObject<HTMLDivElement | null>;\n\n // Current player state\n state: PlayerState;\n ready: boolean;\n playing: boolean;\n error: PlayerError | null;\n\n // Playback controls\n play: () => void;\n pause: () => void;\n seek: (time: number) => void;\n setVolume: (volume: number) => void;\n mute: () => void;\n unmute: () => void;\n enterFullscreen: () => void;\n exitFullscreen: () => void;\n}\n\nexport function usePlayer(options: UsePlayerOptions): UsePlayerReturn {\n const containerRef = useRef<HTMLDivElement>(null);\n const playerRef = useRef<InstanceType<typeof Player> | null>(null);\n\n const [state, setState] = useState<PlayerState>({\n playing: false,\n muted: options.muted ?? false,\n currentTime: options.startTime ?? 0,\n duration: 0,\n fullscreen: false,\n ready: false,\n });\n\n const [error, setError] = useState<PlayerError | null>(null);\n\n // Stable options ref — avoids recreating player on every render\n // when callbacks are inline arrow functions\n const optsRef = useRef(options);\n optsRef.current = options;\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Destroy previous instance if videoId changed\n playerRef.current?.destroy();\n setError(null);\n\n const player = new Player({\n ...optsRef.current,\n container,\n\n onReady: () => {\n setState((s) => ({ ...s, ready: true }));\n optsRef.current.onReady?.();\n },\n onPlay: () => {\n setState((s) => ({ ...s, playing: true }));\n optsRef.current.onPlay?.();\n },\n onPause: () => {\n setState((s) => ({ ...s, playing: false }));\n optsRef.current.onPause?.();\n },\n onEnded: () => {\n setState((s) => ({ ...s, playing: false }));\n optsRef.current.onEnded?.();\n },\n onProgress: (currentTime, duration) => {\n setState((s) => ({ ...s, currentTime, duration }));\n optsRef.current.onProgress?.(currentTime, duration);\n },\n onError: (e) => {\n setError(e);\n optsRef.current.onError?.(e);\n },\n onFullscreenChange: (isFullscreen) => {\n setState((s) => ({ ...s, fullscreen: isFullscreen }));\n optsRef.current.onFullscreenChange?.(isFullscreen);\n },\n });\n\n playerRef.current = player;\n\n return () => {\n player.destroy();\n playerRef.current = null;\n };\n }, [options.videoId]); // only re-create when videoId changes\n\n // ── Stable control functions ───────────────────────────\n // useCallback with empty deps — these never change,\n // they just call the current player ref\n\n const play = useCallback(() => playerRef.current?.play(), []);\n const pause = useCallback(() => playerRef.current?.pause(), []);\n const seek = useCallback((t: number) => playerRef.current?.seek(t), []);\n const setVolume = useCallback((v: number) => playerRef.current?.setVolume(v), []);\n const mute = useCallback(() => playerRef.current?.mute(), []);\n const unmute = useCallback(() => playerRef.current?.unmute(), []);\n const enterFullscreen = useCallback(() => playerRef.current?.enterFullscreen(), []);\n const exitFullscreen = useCallback(() => playerRef.current?.exitFullscreen(), []);\n\n return {\n containerRef,\n state,\n ready: state.ready,\n playing: state.playing,\n error,\n play,\n pause,\n seek,\n setVolume,\n mute,\n unmute,\n enterFullscreen,\n exitFullscreen,\n };\n}","'use client';\n\nimport { usePlayer, type UsePlayerOptions } from './use-player';\nimport type { PlayerError } from '@videncrypt/js';\n\n// ── Props ──────────────────────────────────────────────────\n\nexport interface VidEncryptPlayerProps extends UsePlayerOptions {\n // Container sizing\n className?: string;\n style?: React.CSSProperties;\n\n // Loading slot — shown while player initializes\n // Default: simple grey box\n loadingSlot?: React.ReactNode;\n\n // Error slot — shown on playback error\n // Default: error message + code\n errorSlot?: (error: PlayerError) => React.ReactNode;\n}\n\n// ── Component ──────────────────────────────────────────────\n\nexport function VidEncryptPlayer({\n className,\n style,\n loadingSlot,\n errorSlot,\n ...options\n}: VidEncryptPlayerProps) {\n const { containerRef, ready, error } = usePlayer(options);\n\n return (\n <div\n style={{\n position: 'relative',\n width: '100%',\n background: '#000',\n ...style,\n }}\n className={className}\n >\n {/* Player mounts here — always rendered so the ref is stable */}\n <div ref={containerRef} style={{ width: '100%' }} />\n\n {/* Loading overlay — shown until player is ready */}\n {!ready && !error && (\n <div style={overlayStyle}>\n {loadingSlot ?? <DefaultLoading />}\n </div>\n )}\n\n {/* Error overlay */}\n {error && (\n <div style={overlayStyle}>\n {errorSlot ? errorSlot(error) : <DefaultError error={error} />}\n </div>\n )}\n </div>\n );\n}\n\n// ── Default loading state ──────────────────────────────────\n\nfunction DefaultLoading() {\n return (\n <div style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n background: '#000',\n pointerEvents: 'none',\n }}>\n <div style={{\n width: 32,\n height: 32,\n border: '3px solid rgba(255,255,255,0.15)',\n borderTopColor: '#2563EB',\n borderRadius: '50%',\n animation: 've-spin 0.75s linear infinite',\n }} />\n <style>{`\n @keyframes ve-spin { to { transform: rotate(360deg); } }\n `}</style>\n </div>\n );\n}\n\n// ── Default error state ────────────────────────────────────\n\nfunction DefaultError({ error }: { error: PlayerError }) {\n return (\n <div style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n background: '#000',\n gap: 8,\n }}>\n <p style={{ color: 'rgba(255,255,255,0.5)', fontSize: 13, margin: 0 }}>\n {error.message}\n </p>\n <p style={{ color: 'rgba(255,255,255,0.25)', fontSize: 11, margin: 0 }}>\n {error.code}\n </p>\n </div>\n );\n}\n\n// ── Shared styles ──────────────────────────────────────────\n\nconst overlayStyle: React.CSSProperties = {\n position: 'absolute',\n inset: 0,\n};"],"mappings":"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","names":["_a","_b"]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@videncrypt/react",
3
+ "version": "0.1.0",
4
+ "description": "React component for VidEncrypt secure video player",
5
+ "license": "MIT",
6
+ "author": "VidEncrypt",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "peerDependencies": {
23
+ "react": ">=18.0.0",
24
+ "react-dom": ">=18.0.0"
25
+ },
26
+ "dependencies": {
27
+ "@videncrypt/js": "0.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.0.0",
31
+ "tsup": "^8.0.0",
32
+ "@types/react": "^19.0.0",
33
+ "@types/react-dom": "^19.0.0",
34
+ "react": "^19.0.0",
35
+ "react-dom": "^19.0.0"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "build:watch": "tsup --watch",
40
+ "typecheck": "tsc --noEmit",
41
+ "clean": "rm -rf dist"
42
+ }
43
+ }