@guardvideo/player-sdk 1.1.0 → 1.3.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 +292 -241
- package/dist/index.esm.js +760 -632
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +759 -631
- package/dist/index.js.map +1 -1
- package/dist/react/GuardVideoPlayer.d.ts +8 -5
- package/dist/react/GuardVideoPlayer.d.ts.map +1 -1
- package/dist/ui/PlayerUI.d.ts +94 -0
- package/dist/ui/PlayerUI.d.ts.map +1 -0
- package/dist/vanilla/guardvideo-player.js +869 -23
- package/dist/vanilla/guardvideo-player.js.map +1 -1
- package/dist/vanilla/guardvideo-player.min.js +1 -37321
- package/dist/vanilla/guardvideo-player.min.js.map +1 -1
- package/dist/vanilla/react/GuardVideoPlayer.d.ts +8 -5
- package/dist/vanilla/react/GuardVideoPlayer.d.ts.map +1 -1
- package/dist/vanilla/ui/PlayerUI.d.ts +94 -0
- package/dist/vanilla/ui/PlayerUI.d.ts.map +1 -0
- package/dist/vanilla/vanilla/index.d.ts +24 -7
- package/dist/vanilla/vanilla/index.d.ts.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
# GuardVideo Player SDK
|
|
2
2
|
|
|
3
|
-
🎬 **Secure HLS video player with embed token authentication
|
|
3
|
+
🎬 **Secure HLS video player with embed token authentication — React, Next.js, and vanilla JavaScript**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@guardvideo/player-sdk)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
✅ **
|
|
11
|
-
✅ **
|
|
12
|
-
✅ **
|
|
13
|
-
✅ **
|
|
14
|
-
✅ **
|
|
15
|
-
✅ **
|
|
16
|
-
✅ **
|
|
17
|
-
✅ **
|
|
10
|
+
✅ **Custom Professional Player UI** — No native browser controls; full custom design
|
|
11
|
+
✅ **Adaptive Bitrate Streaming** — Automatic quality switching based on network conditions
|
|
12
|
+
✅ **Secure Token Authentication** — API key never exposed to the browser
|
|
13
|
+
✅ **Multi-Quality Support** — 1080p, 720p, 480p, 360p
|
|
14
|
+
✅ **AES-128 Encryption** — Secure video segment delivery
|
|
15
|
+
✅ **Forensic Watermark** — Viewer-identifying overlay baked into playback (default: on)
|
|
16
|
+
✅ **React & Next.js Support** — First-class TypeScript support
|
|
17
|
+
✅ **Vanilla JS / CDN Support** — Full custom UI without any framework
|
|
18
|
+
✅ **TypeScript Types** — Full type safety included
|
|
19
|
+
✅ **Browser Support** — All modern browsers + Safari/iOS native HLS
|
|
18
20
|
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
## Installation
|
|
22
24
|
|
|
23
|
-
### NPM/Yarn (
|
|
25
|
+
### NPM / Yarn (React / Next.js)
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
28
|
npm install @guardvideo/player-sdk
|
|
@@ -28,43 +30,52 @@ npm install @guardvideo/player-sdk
|
|
|
28
30
|
yarn add @guardvideo/player-sdk
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
### CDN (
|
|
33
|
+
### CDN (Vanilla JavaScript)
|
|
32
34
|
|
|
33
35
|
```html
|
|
36
|
+
<!-- full bundle (recommended for development) -->
|
|
34
37
|
<script src="https://cdn.jsdelivr.net/npm/@guardvideo/player-sdk@latest/dist/vanilla/guardvideo-player.js"></script>
|
|
38
|
+
|
|
39
|
+
<!-- minified bundle (recommended for production) -->
|
|
40
|
+
<script src="https://cdn.jsdelivr.net/npm/@guardvideo/player-sdk@latest/dist/vanilla/guardvideo-player.min.js"></script>
|
|
35
41
|
```
|
|
36
42
|
|
|
37
43
|
---
|
|
38
44
|
|
|
39
45
|
## Quick Start
|
|
40
46
|
|
|
41
|
-
### React Component
|
|
47
|
+
### React / Next.js Component
|
|
42
48
|
|
|
43
49
|
```tsx
|
|
44
50
|
import { GuardVideoPlayer } from '@guardvideo/player-sdk';
|
|
45
51
|
|
|
46
|
-
function
|
|
52
|
+
function VideoPage() {
|
|
47
53
|
return (
|
|
48
54
|
<GuardVideoPlayer
|
|
49
55
|
videoId="your-video-id"
|
|
50
56
|
embedTokenEndpoint="/api/embed-token"
|
|
51
57
|
width="100%"
|
|
52
58
|
height="500px"
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
viewerEmail="user@example.com"
|
|
60
|
+
viewerName="Jane Doe"
|
|
61
|
+
forensicWatermark // default: true
|
|
55
62
|
onReady={() => console.log('Video ready!')}
|
|
56
63
|
onError={(error) => console.error(error)}
|
|
64
|
+
onQualityChange={(quality) => console.log('Quality:', quality)}
|
|
65
|
+
onStateChange={(state) => console.log('State:', state)}
|
|
57
66
|
/>
|
|
58
67
|
);
|
|
59
68
|
}
|
|
60
69
|
```
|
|
61
70
|
|
|
62
|
-
|
|
71
|
+
> **Note:** The component renders a fully custom player UI with play/pause, seek bar, volume control, quality selector, playback speed menu, and fullscreen. You do **not** need to add the native `controls` attribute or wrap it in any overlay.
|
|
72
|
+
|
|
73
|
+
### React Hook (build your own controls)
|
|
63
74
|
|
|
64
75
|
```tsx
|
|
65
76
|
import { useGuardVideoPlayer } from '@guardvideo/player-sdk';
|
|
66
77
|
|
|
67
|
-
function
|
|
78
|
+
function CustomPlayer({ videoId }: { videoId: string }) {
|
|
68
79
|
const {
|
|
69
80
|
videoRef,
|
|
70
81
|
isReady,
|
|
@@ -75,31 +86,38 @@ function VideoPlayer({ videoId }: { videoId: string }) {
|
|
|
75
86
|
play,
|
|
76
87
|
pause,
|
|
77
88
|
seek,
|
|
89
|
+
setVolume,
|
|
78
90
|
setQuality,
|
|
79
91
|
} = useGuardVideoPlayer({
|
|
80
92
|
videoId,
|
|
81
93
|
embedTokenEndpoint: '/api/embed-token',
|
|
82
|
-
|
|
94
|
+
viewerEmail: 'user@example.com',
|
|
83
95
|
});
|
|
84
96
|
|
|
97
|
+
const formatTime = (s: number) =>
|
|
98
|
+
`${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
|
|
99
|
+
|
|
85
100
|
return (
|
|
86
101
|
<div>
|
|
87
|
-
<video ref={videoRef} style={{ width: '100%'
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
<video ref={videoRef} style={{ width: '100%' }} />
|
|
103
|
+
{isReady && (
|
|
104
|
+
<div>
|
|
105
|
+
<button onClick={isPlaying ? pause : play}>
|
|
106
|
+
{isPlaying ? 'Pause' : 'Play'}
|
|
107
|
+
</button>
|
|
108
|
+
<input
|
|
109
|
+
type="range" min={0} max={duration} value={currentTime}
|
|
110
|
+
onChange={(e) => seek(Number(e.target.value))}
|
|
111
|
+
/>
|
|
112
|
+
<span>{formatTime(currentTime)} / {formatTime(duration)}</span>
|
|
113
|
+
<select onChange={(e) => setQuality(Number(e.target.value))}>
|
|
114
|
+
<option value="-1">Auto</option>
|
|
115
|
+
{qualityLevels.map((l) => (
|
|
116
|
+
<option key={l.index} value={l.index}>{l.name}</option>
|
|
117
|
+
))}
|
|
118
|
+
</select>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
103
121
|
</div>
|
|
104
122
|
);
|
|
105
123
|
}
|
|
@@ -108,149 +126,72 @@ function VideoPlayer({ videoId }: { videoId: string }) {
|
|
|
108
126
|
### Next.js App Router
|
|
109
127
|
|
|
110
128
|
```tsx
|
|
129
|
+
// app/watch/[videoId]/page.tsx
|
|
111
130
|
'use client';
|
|
112
131
|
|
|
113
132
|
import { GuardVideoPlayer } from '@guardvideo/player-sdk';
|
|
114
133
|
|
|
115
|
-
export default function
|
|
134
|
+
export default function WatchPage({ params }: { params: { videoId: string } }) {
|
|
116
135
|
return (
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
onReady={() => console.log('Ready!')}
|
|
127
|
-
onError={(error) => console.error('Error:', error)}
|
|
128
|
-
onQualityChange={(quality) => console.log('Quality:', quality)}
|
|
129
|
-
/>
|
|
130
|
-
</div>
|
|
136
|
+
<GuardVideoPlayer
|
|
137
|
+
videoId={params.videoId}
|
|
138
|
+
embedTokenEndpoint="/api/embed-token"
|
|
139
|
+
width="100%"
|
|
140
|
+
height="600px"
|
|
141
|
+
viewerEmail="user@example.com"
|
|
142
|
+
onReady={() => console.log('Ready!')}
|
|
143
|
+
onError={(e) => console.error(e)}
|
|
144
|
+
/>
|
|
131
145
|
);
|
|
132
146
|
}
|
|
133
147
|
```
|
|
134
148
|
|
|
135
|
-
### Vanilla JavaScript
|
|
149
|
+
### Vanilla JavaScript (CDN)
|
|
136
150
|
|
|
137
151
|
```html
|
|
138
152
|
<!DOCTYPE html>
|
|
139
|
-
<html>
|
|
153
|
+
<html lang="en">
|
|
140
154
|
<head>
|
|
155
|
+
<meta charset="UTF-8" />
|
|
141
156
|
<title>GuardVideo Player</title>
|
|
142
157
|
<style>
|
|
143
|
-
#
|
|
158
|
+
#player-container {
|
|
144
159
|
width: 100%;
|
|
145
|
-
max-width:
|
|
160
|
+
max-width: 900px;
|
|
146
161
|
aspect-ratio: 16/9;
|
|
147
|
-
margin:
|
|
162
|
+
margin: 40px auto;
|
|
148
163
|
}
|
|
149
164
|
</style>
|
|
150
165
|
</head>
|
|
151
166
|
<body>
|
|
152
|
-
<div id="
|
|
167
|
+
<div id="player-container"></div>
|
|
153
168
|
|
|
154
|
-
<script src="https://cdn.jsdelivr.net/npm/@guardvideo/player-sdk@latest/dist/vanilla/guardvideo-player.js"></script>
|
|
169
|
+
<script src="https://cdn.jsdelivr.net/npm/@guardvideo/player-sdk@latest/dist/vanilla/guardvideo-player.min.js"></script>
|
|
155
170
|
<script>
|
|
156
|
-
|
|
157
|
-
const player = GuardVideoPlayer.create('video-container', 'your-video-id', {
|
|
171
|
+
const player = GuardVideoPlayer.create('player-container', 'your-video-id', {
|
|
158
172
|
embedTokenEndpoint: '/api/embed-token',
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
onReady: function() {
|
|
163
|
-
console.log('
|
|
164
|
-
console.log('Available qualities:', player.getQualityLevels());
|
|
173
|
+
viewerEmail: 'user@example.com',
|
|
174
|
+
viewerName: 'Jane Doe',
|
|
175
|
+
|
|
176
|
+
onReady: function () {
|
|
177
|
+
console.log('Ready! Qualities:', player.getQualityLevels());
|
|
165
178
|
},
|
|
166
|
-
onError: function(
|
|
167
|
-
console.error('Player error:',
|
|
179
|
+
onError: function (err) {
|
|
180
|
+
console.error('Player error:', err);
|
|
168
181
|
},
|
|
169
|
-
onQualityChange: function(quality) {
|
|
182
|
+
onQualityChange: function (quality) {
|
|
170
183
|
console.log('Quality changed to:', quality);
|
|
171
184
|
},
|
|
172
|
-
onStateChange: function(state) {
|
|
185
|
+
onStateChange: function (state) {
|
|
173
186
|
console.log('State:', state);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// Control the player
|
|
178
|
-
document.getElementById('play-btn').addEventListener('click', function() {
|
|
179
|
-
player.play();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
document.getElementById('pause-btn').addEventListener('click', function() {
|
|
183
|
-
player.pause();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
document.getElementById('quality-select').addEventListener('change', function(e) {
|
|
187
|
-
player.setQuality(parseInt(e.target.value));
|
|
187
|
+
},
|
|
188
188
|
});
|
|
189
189
|
</script>
|
|
190
190
|
</body>
|
|
191
191
|
</html>
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
## API Reference
|
|
197
|
-
|
|
198
|
-
### PlayerConfig
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
interface PlayerConfig {
|
|
202
|
-
// Required
|
|
203
|
-
embedTokenEndpoint: string; // Your backend API endpoint
|
|
204
|
-
|
|
205
|
-
// Optional
|
|
206
|
-
apiBaseUrl?: string; // Video API base URL
|
|
207
|
-
debug?: boolean; // Enable debug logging (default: false)
|
|
208
|
-
autoplay?: boolean; // Auto-play on load (default: false)
|
|
209
|
-
controls?: boolean; // Show controls (default: true)
|
|
210
|
-
className?: string; // CSS class name
|
|
211
|
-
style?: CSSProperties; // Inline styles
|
|
212
|
-
hlsConfig?: HlsConfig; // HLS.js config overrides
|
|
213
|
-
|
|
214
|
-
// Callbacks
|
|
215
|
-
onReady?: () => void;
|
|
216
|
-
onError?: (error: PlayerError) => void;
|
|
217
|
-
onQualityChange?: (quality: string) => void;
|
|
218
|
-
onStateChange?: (state: PlayerState) => void;
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Player Methods
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
interface PlayerInstance {
|
|
226
|
-
play(): Promise<void>; // Play video
|
|
227
|
-
pause(): void; // Pause video
|
|
228
|
-
getCurrentTime(): number; // Get current time (seconds)
|
|
229
|
-
seek(time: number): void; // Seek to time (seconds)
|
|
230
|
-
getDuration(): number; // Get duration (seconds)
|
|
231
|
-
getVolume(): number; // Get volume (0-1)
|
|
232
|
-
setVolume(volume: number): void; // Set volume (0-1)
|
|
233
|
-
getQualityLevels(): QualityLevel[]; // Get available qualities
|
|
234
|
-
getCurrentQuality(): QualityLevel | null; // Get current quality
|
|
235
|
-
setQuality(levelIndex: number): void; // Set quality (-1 for auto)
|
|
236
|
-
getState(): PlayerState; // Get current state
|
|
237
|
-
destroy(): void; // Cleanup and destroy
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Player States
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
enum PlayerState {
|
|
245
|
-
IDLE = 'idle', // Not initialized
|
|
246
|
-
LOADING = 'loading', // Loading video
|
|
247
|
-
READY = 'ready', // Ready to play
|
|
248
|
-
PLAYING = 'playing', // Currently playing
|
|
249
|
-
PAUSED = 'paused', // Paused
|
|
250
|
-
BUFFERING = 'buffering', // Buffering
|
|
251
|
-
ERROR = 'error', // Error occurred
|
|
252
|
-
}
|
|
253
|
-
```
|
|
194
|
+
> The vanilla build bundles **everything** (hls.js included). No npm install, no bundler needed. The player renders the same full custom UI as the React component.
|
|
254
195
|
|
|
255
196
|
---
|
|
256
197
|
|
|
@@ -264,27 +205,28 @@ export async function POST(
|
|
|
264
205
|
request: Request,
|
|
265
206
|
{ params }: { params: { videoId: string } }
|
|
266
207
|
) {
|
|
267
|
-
const VIDEO_API_KEY
|
|
268
|
-
const VIDEO_API_BASE = process.env.NEXT_PUBLIC_VIDEO_API_BASE
|
|
208
|
+
const VIDEO_API_KEY = process.env.VIDEO_API_KEY!;
|
|
209
|
+
const VIDEO_API_BASE = process.env.NEXT_PUBLIC_VIDEO_API_BASE!;
|
|
269
210
|
|
|
270
211
|
const response = await fetch(
|
|
271
212
|
`${VIDEO_API_BASE}/videos/${params.videoId}/embed-token`,
|
|
272
213
|
{
|
|
273
214
|
method: 'POST',
|
|
274
215
|
headers: {
|
|
275
|
-
'X-API-Key': VIDEO_API_KEY,
|
|
276
|
-
'Content-Type': 'application/json'
|
|
216
|
+
'X-API-Key': VIDEO_API_KEY, // API key stays on the server!
|
|
217
|
+
'Content-Type': 'application/json',
|
|
277
218
|
},
|
|
278
219
|
body: JSON.stringify({
|
|
279
220
|
allowedDomain: request.headers.get('origin'),
|
|
280
221
|
expiresInMinutes: 120,
|
|
281
|
-
maxViews: null
|
|
282
|
-
|
|
222
|
+
maxViews: null,
|
|
223
|
+
viewerEmail: /* optionally forward from session */ undefined,
|
|
224
|
+
forensicWatermark: true,
|
|
225
|
+
}),
|
|
283
226
|
}
|
|
284
227
|
);
|
|
285
228
|
|
|
286
|
-
|
|
287
|
-
return Response.json(embedData);
|
|
229
|
+
return Response.json(await response.json());
|
|
288
230
|
}
|
|
289
231
|
```
|
|
290
232
|
|
|
@@ -297,86 +239,117 @@ NEXT_PUBLIC_VIDEO_API_BASE=http://localhost:3001
|
|
|
297
239
|
|
|
298
240
|
---
|
|
299
241
|
|
|
300
|
-
##
|
|
242
|
+
## API Reference
|
|
301
243
|
|
|
302
|
-
###
|
|
244
|
+
### React Component Props (`GuardVideoPlayerProps`)
|
|
245
|
+
|
|
246
|
+
| Prop | Type | Default | Description |
|
|
247
|
+
|------|------|---------|-------------|
|
|
248
|
+
| `videoId` | `string` | — | **Required.** Video ID to load |
|
|
249
|
+
| `embedTokenEndpoint` | `string` | — | **Required.** Your backend endpoint that mints embed tokens |
|
|
250
|
+
| `width` | `string \| number` | `"100%"` | Player container width |
|
|
251
|
+
| `height` | `string \| number` | `"auto"` | Player container height |
|
|
252
|
+
| `apiBaseUrl` | `string` | — | Override default API base URL |
|
|
253
|
+
| `autoplay` | `boolean` | `false` | Auto-play on load |
|
|
254
|
+
| `debug` | `boolean` | `false` | Enable verbose debug logging |
|
|
255
|
+
| `viewerName` | `string` | — | Viewer name (used for forensic watermark) |
|
|
256
|
+
| `viewerEmail` | `string` | — | Viewer email (used for forensic watermark) |
|
|
257
|
+
| `forensicWatermark` | `boolean` | `true` | Show viewer-identifying watermark overlay |
|
|
258
|
+
| `branding` | `BrandingConfig` | — | Custom accent colour and brand name |
|
|
259
|
+
| `security` | `SecurityConfig` | — | Security hardening options |
|
|
260
|
+
| `contextMenuItems` | `ContextMenuItem[]` | — | Extra context menu items |
|
|
261
|
+
| `hlsConfig` | `HlsConfig` | — | HLS.js configuration overrides |
|
|
262
|
+
| `onReady` | `() => void` | — | Called when video is ready to play |
|
|
263
|
+
| `onError` | `(err: PlayerError) => void` | — | Called on error |
|
|
264
|
+
| `onQualityChange` | `(quality: string) => void` | — | Called when quality changes |
|
|
265
|
+
| `onStateChange` | `(state: PlayerState) => void` | — | Called on state transitions |
|
|
266
|
+
| `onTimeUpdate` | `(time: number) => void` | — | Called on time updates |
|
|
267
|
+
| `onEnded` | `() => void` | — | Called when video ends |
|
|
268
|
+
|
|
269
|
+
### Vanilla JS Config (`PlayerConfig`)
|
|
270
|
+
|
|
271
|
+
All React props above except JSX-specific ones (`className`, `style`) are available.
|
|
272
|
+
Extra vanilla-only props:
|
|
273
|
+
|
|
274
|
+
| Prop | Type | Default | Description |
|
|
275
|
+
|------|------|---------|-------------|
|
|
276
|
+
| `width` | `string` | `"100%"` | Player container width CSS value |
|
|
277
|
+
| `height` | `string` | `"auto"` | Player container height CSS value |
|
|
303
278
|
|
|
304
|
-
|
|
305
|
-
function CustomVideoPlayer({ videoId }: { videoId: string }) {
|
|
306
|
-
const {
|
|
307
|
-
videoRef,
|
|
308
|
-
isReady,
|
|
309
|
-
isPlaying,
|
|
310
|
-
currentTime,
|
|
311
|
-
duration,
|
|
312
|
-
volume,
|
|
313
|
-
qualityLevels,
|
|
314
|
-
currentQuality,
|
|
315
|
-
play,
|
|
316
|
-
pause,
|
|
317
|
-
seek,
|
|
318
|
-
setVolume,
|
|
319
|
-
setQuality,
|
|
320
|
-
} = useGuardVideoPlayer({
|
|
321
|
-
videoId,
|
|
322
|
-
embedTokenEndpoint: '/api/embed-token',
|
|
323
|
-
debug: true,
|
|
324
|
-
});
|
|
279
|
+
### Player Methods
|
|
325
280
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
281
|
+
```typescript
|
|
282
|
+
player.play(): Promise<void> // Start playback
|
|
283
|
+
player.pause(): void // Pause
|
|
284
|
+
player.seek(time: number): void // Seek to time in seconds
|
|
285
|
+
player.getCurrentTime(): number // Current playback position (seconds)
|
|
286
|
+
player.getDuration(): number // Total duration (seconds)
|
|
287
|
+
player.getVolume(): number // Current volume (0–1)
|
|
288
|
+
player.setVolume(v: number): void // Set volume (0–1)
|
|
289
|
+
player.getQualityLevels(): QualityLevel[] // Available quality levels
|
|
290
|
+
player.getCurrentQuality(): QualityLevel | null // Active quality
|
|
291
|
+
player.setQuality(index: number): void // Set quality (-1 = auto)
|
|
292
|
+
player.getState(): PlayerState // Current player state
|
|
293
|
+
player.getVideoElement(): HTMLVideoElement | null // Underlying <video> element
|
|
294
|
+
player.destroy(): void // Clean up and remove player
|
|
295
|
+
```
|
|
331
296
|
|
|
332
|
-
|
|
333
|
-
<div className="video-player">
|
|
334
|
-
<video ref={videoRef} className="video-element" />
|
|
335
|
-
|
|
336
|
-
{isReady && (
|
|
337
|
-
<div className="controls">
|
|
338
|
-
<button onClick={isPlaying ? pause : play}>
|
|
339
|
-
{isPlaying ? 'Pause' : 'Play'}
|
|
340
|
-
</button>
|
|
297
|
+
### Player States
|
|
341
298
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
299
|
+
```typescript
|
|
300
|
+
enum PlayerState {
|
|
301
|
+
IDLE = 'idle', // Not yet initialized
|
|
302
|
+
LOADING = 'loading', // Loading video
|
|
303
|
+
READY = 'ready', // Ready to play
|
|
304
|
+
PLAYING = 'playing', // Currently playing
|
|
305
|
+
PAUSED = 'paused', // Paused
|
|
306
|
+
BUFFERING = 'buffering', // Re-buffering mid-play
|
|
307
|
+
ERROR = 'error', // Unrecoverable error
|
|
308
|
+
}
|
|
309
|
+
```
|
|
349
310
|
|
|
350
|
-
|
|
311
|
+
### BrandingConfig
|
|
351
312
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
313
|
+
```typescript
|
|
314
|
+
interface BrandingConfig {
|
|
315
|
+
accentColor?: string; // Hex color, default: '#44c09b'
|
|
316
|
+
name?: string; // Brand name shown in secure badge, default: 'GuardVideo'
|
|
317
|
+
logoUrl?: string; // Logo URL (used in context menu)
|
|
318
|
+
websiteUrl?: string; // Website URL (used in context menu)
|
|
319
|
+
}
|
|
320
|
+
```
|
|
360
321
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
))}
|
|
371
|
-
</select>
|
|
372
|
-
</div>
|
|
373
|
-
)}
|
|
374
|
-
</div>
|
|
375
|
-
);
|
|
322
|
+
### SecurityConfig
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
interface SecurityConfig {
|
|
326
|
+
disableContextMenu?: boolean; // Block right-click (default: true)
|
|
327
|
+
disableDevTools?: boolean; // Detect/block DevTools (default: false)
|
|
328
|
+
disablePiP?: boolean; // Block Picture-in-Picture (default: false)
|
|
329
|
+
disableDragDrop?: boolean; // Block drag-and-drop (default: true)
|
|
330
|
+
disableScreenshot?: boolean; // Attempt screenshot blocking (default: false)
|
|
376
331
|
}
|
|
377
332
|
```
|
|
378
333
|
|
|
379
|
-
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Keyboard Shortcuts
|
|
337
|
+
|
|
338
|
+
When the player has focus:
|
|
339
|
+
|
|
340
|
+
| Key | Action |
|
|
341
|
+
|-----|--------|
|
|
342
|
+
| `Space` / `K` | Play / Pause |
|
|
343
|
+
| `←` | Seek back 5 s |
|
|
344
|
+
| `→` | Seek forward 5 s |
|
|
345
|
+
| `↑` | Volume +10% |
|
|
346
|
+
| `↓` | Volume −10% |
|
|
347
|
+
| `M` | Toggle mute |
|
|
348
|
+
| `F` | Toggle fullscreen |
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Using Ref for Imperative Control (React)
|
|
380
353
|
|
|
381
354
|
```tsx
|
|
382
355
|
import { useRef } from 'react';
|
|
@@ -385,16 +358,80 @@ import { GuardVideoPlayer, GuardVideoPlayerRef } from '@guardvideo/player-sdk';
|
|
|
385
358
|
function VideoWithRef() {
|
|
386
359
|
const playerRef = useRef<GuardVideoPlayerRef>(null);
|
|
387
360
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
361
|
+
return (
|
|
362
|
+
<div>
|
|
363
|
+
<GuardVideoPlayer
|
|
364
|
+
ref={playerRef}
|
|
365
|
+
videoId="your-video-id"
|
|
366
|
+
embedTokenEndpoint="/api/embed-token"
|
|
367
|
+
/>
|
|
368
|
+
<button onClick={() => playerRef.current?.seek(playerRef.current.getCurrentTime() + 10)}>
|
|
369
|
+
+10 s
|
|
370
|
+
</button>
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## TypeScript Support
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import type {
|
|
382
|
+
PlayerConfig,
|
|
383
|
+
PlayerInstance,
|
|
384
|
+
PlayerState,
|
|
385
|
+
PlayerError,
|
|
386
|
+
QualityLevel,
|
|
387
|
+
BrandingConfig,
|
|
388
|
+
SecurityConfig,
|
|
389
|
+
} from '@guardvideo/player-sdk';
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Browser Support
|
|
395
|
+
|
|
396
|
+
| Browser | Minimum version |
|
|
397
|
+
|---------|----------------|
|
|
398
|
+
| Chrome | 90+ |
|
|
399
|
+
| Firefox | 88+ |
|
|
400
|
+
| Safari | 14+ (native HLS) |
|
|
401
|
+
| Edge | 90+ |
|
|
402
|
+
| iOS Safari | 14+ |
|
|
403
|
+
| Android Chrome | 90+ |
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## License
|
|
408
|
+
|
|
409
|
+
MIT © GuardVideo
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Support
|
|
414
|
+
|
|
415
|
+
- GitHub Issues: [github.com/guardvideo/player-sdk](https://github.com/guardvideo/player-sdk)
|
|
416
|
+
- Documentation: [docs.guardvideo.com](https://docs.guardvideo.com)
|
|
417
|
+
| `Space` / `K` | Play / Pause |
|
|
418
|
+
| `←` | Seek back 5 s |
|
|
419
|
+
| `→` | Seek forward 5 s |
|
|
420
|
+
| `↑` | Volume +10% |
|
|
421
|
+
| `↓` | Volume −10% |
|
|
422
|
+
| `M` | Toggle mute |
|
|
423
|
+
| `F` | Toggle fullscreen |
|
|
424
|
+
|
|
425
|
+
---
|
|
394
426
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
427
|
+
## Using Ref for Imperative Control (React)
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
import { useRef } from 'react';
|
|
431
|
+
import { GuardVideoPlayer, GuardVideoPlayerRef } from '@guardvideo/player-sdk';
|
|
432
|
+
|
|
433
|
+
function VideoWithRef() {
|
|
434
|
+
const playerRef = useRef<GuardVideoPlayerRef>(null);
|
|
398
435
|
|
|
399
436
|
return (
|
|
400
437
|
<div>
|
|
@@ -403,7 +440,9 @@ function VideoWithRef() {
|
|
|
403
440
|
videoId="your-video-id"
|
|
404
441
|
embedTokenEndpoint="/api/embed-token"
|
|
405
442
|
/>
|
|
406
|
-
<button onClick={
|
|
443
|
+
<button onClick={() => playerRef.current?.seek(playerRef.current.getCurrentTime() + 10)}>
|
|
444
|
+
+10 s
|
|
445
|
+
</button>
|
|
407
446
|
</div>
|
|
408
447
|
);
|
|
409
448
|
}
|
|
@@ -413,8 +452,6 @@ function VideoWithRef() {
|
|
|
413
452
|
|
|
414
453
|
## TypeScript Support
|
|
415
454
|
|
|
416
|
-
The SDK is written in TypeScript and includes full type definitions:
|
|
417
|
-
|
|
418
455
|
```typescript
|
|
419
456
|
import type {
|
|
420
457
|
PlayerConfig,
|
|
@@ -422,7 +459,8 @@ import type {
|
|
|
422
459
|
PlayerState,
|
|
423
460
|
PlayerError,
|
|
424
461
|
QualityLevel,
|
|
425
|
-
|
|
462
|
+
BrandingConfig,
|
|
463
|
+
SecurityConfig,
|
|
426
464
|
} from '@guardvideo/player-sdk';
|
|
427
465
|
```
|
|
428
466
|
|
|
@@ -430,12 +468,14 @@ import type {
|
|
|
430
468
|
|
|
431
469
|
## Browser Support
|
|
432
470
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
471
|
+
| Browser | Minimum version |
|
|
472
|
+
|---------|----------------|
|
|
473
|
+
| Chrome | 90+ |
|
|
474
|
+
| Firefox | 88+ |
|
|
475
|
+
| Safari | 14+ (native HLS) |
|
|
476
|
+
| Edge | 90+ |
|
|
477
|
+
| iOS Safari | 14+ |
|
|
478
|
+
| Android Chrome | 90+ |
|
|
439
479
|
|
|
440
480
|
---
|
|
441
481
|
|
|
@@ -447,12 +487,23 @@ MIT © GuardVideo
|
|
|
447
487
|
|
|
448
488
|
## Support
|
|
449
489
|
|
|
450
|
-
For issues and questions:
|
|
451
490
|
- GitHub Issues: [github.com/guardvideo/player-sdk](https://github.com/guardvideo/player-sdk)
|
|
452
491
|
- Documentation: [docs.guardvideo.com](https://docs.guardvideo.com)
|
|
453
492
|
|
|
493
|
+
| Safari | 14+ (native HLS) |
|
|
494
|
+
| Edge | 90+ |
|
|
495
|
+
| iOS Safari | 14+ |
|
|
496
|
+
| Android Chrome | 90+ |
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## License
|
|
501
|
+
|
|
502
|
+
MIT (C) GuardVideo
|
|
503
|
+
|
|
454
504
|
---
|
|
455
505
|
|
|
456
|
-
##
|
|
506
|
+
## Support
|
|
457
507
|
|
|
458
|
-
|
|
508
|
+
- GitHub Issues: https://github.com/guardvideo/player-sdk
|
|
509
|
+
- Documentation: https://docs.guardvideo.com
|