@obipascal/player 1.0.1 โ 1.0.2
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 +261 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ A modern, feature-rich HLS video player SDK for educational platforms with Cloud
|
|
|
5
5
|
## โจ Features
|
|
6
6
|
|
|
7
7
|
### Core Playback
|
|
8
|
+
|
|
8
9
|
- ๐ฌ **HLS Streaming**: Full HLS.js support with adaptive bitrate streaming
|
|
9
10
|
- ๐ **CloudFront Integration**: Native support for CloudFront signed cookies and S3-hosted videos
|
|
10
11
|
- ๐ฏ **Skip Controls**: 10-second forward/backward skip with circular arrow buttons
|
|
@@ -13,11 +14,13 @@ A modern, feature-rich HLS video player SDK for educational platforms with Cloud
|
|
|
13
14
|
- ๐๏ธ **Playback Rate**: Adjustable speed (0.5x - 2x)
|
|
14
15
|
|
|
15
16
|
### Subtitle & Accessibility
|
|
17
|
+
|
|
16
18
|
- ๐ **Subtitle Support**: Full subtitle/caption support with programmatic API
|
|
17
19
|
- ๐ **Multi-language**: Support for multiple subtitle tracks with language selection
|
|
18
20
|
- โฟ **Accessibility**: WCAG compliant with keyboard navigation
|
|
19
21
|
|
|
20
22
|
### UI & Controls
|
|
23
|
+
|
|
21
24
|
- ๐จ **Modern UI Design**: Beautiful controls with blur effects, gradients, and smooth animations
|
|
22
25
|
- ๐ฑ๏ธ **Smart Controls**: Auto-hide on inactivity, fade on hover
|
|
23
26
|
- ๐ **Sticky Controls**: Optional persistent controls (toggle in settings)
|
|
@@ -27,6 +30,7 @@ A modern, feature-rich HLS video player SDK for educational platforms with Cloud
|
|
|
27
30
|
- ๐จ **Custom Theming**: Full CSS variable theming with 8 customizable properties
|
|
28
31
|
|
|
29
32
|
### Developer Experience
|
|
33
|
+
|
|
30
34
|
- โ๏ธ **React Support**: Component, Hook, and Context Provider patterns
|
|
31
35
|
- ๐ง **TypeScript**: Full TypeScript support with comprehensive type definitions
|
|
32
36
|
- ๐ **Analytics & QoE**: Built-in analytics tracking and Quality of Experience metrics
|
|
@@ -98,7 +102,7 @@ yarn add @obipascal/player hls.js
|
|
|
98
102
|
player.on("timeupdate", (event) => {
|
|
99
103
|
console.log("Current time:", event.data.currentTime)
|
|
100
104
|
})
|
|
101
|
-
|
|
105
|
+
|
|
102
106
|
// Programmatic subtitle control
|
|
103
107
|
player.enableSubtitles(0) // Enable first subtitle track
|
|
104
108
|
player.toggleSubtitles() // Toggle subtitles on/off
|
|
@@ -213,43 +217,165 @@ function ControlPanel() {
|
|
|
213
217
|
|
|
214
218
|
## ๐ CloudFront & S3 Integration
|
|
215
219
|
|
|
216
|
-
|
|
220
|
+
This player supports **three video hosting scenarios**. Choose the one that fits your needs:
|
|
221
|
+
|
|
222
|
+
### Scenario 1: Public Videos (Easiest - No Authentication Required)
|
|
223
|
+
|
|
224
|
+
**When to use:** Your videos are publicly accessible and don't require user authentication.
|
|
217
225
|
|
|
218
|
-
|
|
226
|
+
**Setup:** Just provide the video URL!
|
|
219
227
|
|
|
220
228
|
```typescript
|
|
221
229
|
import { WontumPlayer } from "@obipascal/player"
|
|
222
230
|
|
|
223
|
-
// Your backend sets signed cookies for CloudFront
|
|
224
|
-
// Cookie names: CloudFront-Policy, CloudFront-Signature, CloudFront-Key-Pair-Id
|
|
225
|
-
|
|
226
231
|
const player = new WontumPlayer({
|
|
227
|
-
src: "https://
|
|
232
|
+
src: "https://d1234567890.cloudfront.net/video/playlist.m3u8",
|
|
228
233
|
container: "#player",
|
|
229
|
-
s3Config: {
|
|
230
|
-
cloudfront: {
|
|
231
|
-
domain: "media.yourdomain.com",
|
|
232
|
-
// Cookies are automatically sent by browser
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
234
|
})
|
|
236
235
|
```
|
|
237
236
|
|
|
238
|
-
|
|
237
|
+
โ
**That's it!** No backend needed. Works for public S3 buckets or CloudFront distributions.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### Scenario 2: Private Videos with CloudFront Signed Cookies (Recommended)
|
|
242
|
+
|
|
243
|
+
**When to use:** You want to restrict video access to authorized users (e.g., paid courses, premium content).
|
|
244
|
+
|
|
245
|
+
**How it works:**
|
|
246
|
+
|
|
247
|
+
1. User logs into your app
|
|
248
|
+
2. Your backend verifies the user and sets CloudFront signed cookies
|
|
249
|
+
3. Player automatically sends these cookies with every video request
|
|
250
|
+
4. CloudFront checks the cookies and allows/denies access
|
|
251
|
+
|
|
252
|
+
**Frontend Setup:**
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { WontumPlayer } from "@obipascal/player"
|
|
256
|
+
|
|
257
|
+
// STEP 1: Call your backend to set signed cookies BEFORE creating the player
|
|
258
|
+
async function initializePlayer() {
|
|
259
|
+
// This endpoint sets CloudFront cookies in the browser
|
|
260
|
+
await fetch("/api/auth/video-access", {
|
|
261
|
+
credentials: "include", // Important: include cookies
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// STEP 2: Create player - it will automatically use the cookies
|
|
265
|
+
const player = new WontumPlayer({
|
|
266
|
+
src: "https://media.yourdomain.com/videos/lesson-1/playlist.m3u8",
|
|
267
|
+
container: "#player",
|
|
268
|
+
s3Config: {
|
|
269
|
+
cloudFrontDomains: ["media.yourdomain.com"], // Your CloudFront domain
|
|
270
|
+
signUrl: async (url) => {
|
|
271
|
+
// This function is called when player needs to access a video
|
|
272
|
+
// Call your backend to refresh/set cookies if needed
|
|
273
|
+
const response = await fetch("/api/auth/sign-url", {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: { "Content-Type": "application/json" },
|
|
276
|
+
credentials: "include",
|
|
277
|
+
body: JSON.stringify({ url }),
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
throw new Error("Failed to authenticate video access")
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Backend sets cookies, return the URL
|
|
285
|
+
return url
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
initializePlayer()
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Backend Setup (Node.js/Express):**
|
|
239
295
|
|
|
240
296
|
```typescript
|
|
241
|
-
|
|
242
|
-
import { CloudFrontClient } from "@aws-sdk/client-cloudfront"
|
|
297
|
+
import express from "express"
|
|
243
298
|
import { getSignedCookies } from "@aws-sdk/cloudfront-signer"
|
|
299
|
+
import fs from "fs"
|
|
300
|
+
|
|
301
|
+
const app = express()
|
|
302
|
+
|
|
303
|
+
// STEP 1: Create endpoint that sets CloudFront signed cookies
|
|
304
|
+
app.get("/api/auth/video-access", (req, res) => {
|
|
305
|
+
// Check if user is logged in (your authentication logic)
|
|
306
|
+
if (!req.user) {
|
|
307
|
+
return res.status(401).json({ error: "Not authenticated" })
|
|
308
|
+
}
|
|
244
309
|
|
|
245
|
-
|
|
310
|
+
// Define what resources user can access
|
|
311
|
+
const policy = {
|
|
312
|
+
Statement: [
|
|
313
|
+
{
|
|
314
|
+
Resource: "https://media.yourdomain.com/*", // All videos on this domain
|
|
315
|
+
Condition: {
|
|
316
|
+
DateLessThan: {
|
|
317
|
+
"AWS:EpochTime": Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Generate CloudFront signed cookies
|
|
325
|
+
const cookies = getSignedCookies({
|
|
326
|
+
keyPairId: process.env.CLOUDFRONT_KEY_PAIR_ID!, // Your CloudFront key pair ID
|
|
327
|
+
privateKey: fs.readFileSync("./cloudfront-private-key.pem", "utf8"), // Your private key
|
|
328
|
+
policy: JSON.stringify(policy),
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// Set the three required cookies
|
|
332
|
+
res.cookie("CloudFront-Policy", cookies["CloudFront-Policy"], {
|
|
333
|
+
domain: ".yourdomain.com", // Use your domain
|
|
334
|
+
path: "/",
|
|
335
|
+
secure: true, // HTTPS only
|
|
336
|
+
httpOnly: true, // Prevent JavaScript access
|
|
337
|
+
sameSite: "none",
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
res.cookie("CloudFront-Signature", cookies["CloudFront-Signature"], {
|
|
341
|
+
domain: ".yourdomain.com",
|
|
342
|
+
path: "/",
|
|
343
|
+
secure: true,
|
|
344
|
+
httpOnly: true,
|
|
345
|
+
sameSite: "none",
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
res.cookie("CloudFront-Key-Pair-Id", cookies["CloudFront-Key-Pair-Id"], {
|
|
349
|
+
domain: ".yourdomain.com",
|
|
350
|
+
path: "/",
|
|
351
|
+
secure: true,
|
|
352
|
+
httpOnly: true,
|
|
353
|
+
sameSite: "none",
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
res.json({ success: true })
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
// STEP 2: Optional endpoint for on-demand signing (called by signUrl function)
|
|
360
|
+
app.post("/api/auth/sign-url", (req, res) => {
|
|
361
|
+
const { url } = req.body
|
|
362
|
+
|
|
363
|
+
// Verify user is authorized
|
|
364
|
+
if (!req.user) {
|
|
365
|
+
return res.status(401).json({ error: "Not authenticated" })
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// You can add additional authorization logic here
|
|
369
|
+
// For example, check if user has access to this specific video
|
|
370
|
+
|
|
371
|
+
// Refresh cookies (same code as above)
|
|
246
372
|
const policy = {
|
|
247
373
|
Statement: [
|
|
248
374
|
{
|
|
249
375
|
Resource: "https://media.yourdomain.com/*",
|
|
250
376
|
Condition: {
|
|
251
377
|
DateLessThan: {
|
|
252
|
-
"AWS:EpochTime": Math.floor(Date.now() / 1000) + 3600,
|
|
378
|
+
"AWS:EpochTime": Math.floor(Date.now() / 1000) + 3600,
|
|
253
379
|
},
|
|
254
380
|
},
|
|
255
381
|
},
|
|
@@ -257,43 +383,142 @@ app.get("/api/video-auth", async (req, res) => {
|
|
|
257
383
|
}
|
|
258
384
|
|
|
259
385
|
const cookies = getSignedCookies({
|
|
260
|
-
keyPairId: process.env.CLOUDFRONT_KEY_PAIR_ID
|
|
261
|
-
privateKey:
|
|
386
|
+
keyPairId: process.env.CLOUDFRONT_KEY_PAIR_ID!,
|
|
387
|
+
privateKey: fs.readFileSync("./cloudfront-private-key.pem", "utf8"),
|
|
262
388
|
policy: JSON.stringify(policy),
|
|
263
389
|
})
|
|
264
390
|
|
|
265
|
-
// Set cookies
|
|
266
391
|
res.cookie("CloudFront-Policy", cookies["CloudFront-Policy"], {
|
|
267
392
|
domain: ".yourdomain.com",
|
|
393
|
+
path: "/",
|
|
268
394
|
secure: true,
|
|
269
395
|
httpOnly: true,
|
|
396
|
+
sameSite: "none",
|
|
270
397
|
})
|
|
398
|
+
|
|
271
399
|
res.cookie("CloudFront-Signature", cookies["CloudFront-Signature"], {
|
|
272
400
|
domain: ".yourdomain.com",
|
|
401
|
+
path: "/",
|
|
273
402
|
secure: true,
|
|
274
403
|
httpOnly: true,
|
|
404
|
+
sameSite: "none",
|
|
275
405
|
})
|
|
406
|
+
|
|
276
407
|
res.cookie("CloudFront-Key-Pair-Id", cookies["CloudFront-Key-Pair-Id"], {
|
|
277
408
|
domain: ".yourdomain.com",
|
|
409
|
+
path: "/",
|
|
278
410
|
secure: true,
|
|
279
411
|
httpOnly: true,
|
|
412
|
+
sameSite: "none",
|
|
280
413
|
})
|
|
281
414
|
|
|
282
415
|
res.json({ success: true })
|
|
283
416
|
})
|
|
284
417
|
```
|
|
285
418
|
|
|
286
|
-
|
|
419
|
+
**AWS CloudFront Setup:**
|
|
420
|
+
|
|
421
|
+
1. Create a CloudFront key pair in AWS Console โ CloudFront โ Key pairs
|
|
422
|
+
2. Download the private key file
|
|
423
|
+
3. Set up environment variables:
|
|
424
|
+
```bash
|
|
425
|
+
CLOUDFRONT_KEY_PAIR_ID=APKA...
|
|
426
|
+
```
|
|
427
|
+
4. Configure your CloudFront distribution to require signed cookies
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
### Scenario 3: Private S3 Videos with Presigned URLs
|
|
432
|
+
|
|
433
|
+
**When to use:** Videos are in private S3 buckets without CloudFront.
|
|
434
|
+
|
|
435
|
+
**How it works:**
|
|
436
|
+
|
|
437
|
+
1. Your backend generates temporary presigned URLs for S3 objects
|
|
438
|
+
2. Player uses these URLs to access videos
|
|
439
|
+
3. URLs expire after a set time (e.g., 1 hour)
|
|
287
440
|
|
|
288
|
-
|
|
441
|
+
**Frontend Setup:**
|
|
289
442
|
|
|
290
443
|
```typescript
|
|
444
|
+
import { WontumPlayer } from "@obipascal/player"
|
|
445
|
+
|
|
291
446
|
const player = new WontumPlayer({
|
|
292
|
-
src: "
|
|
447
|
+
src: "s3://my-bucket/videos/lesson-1/playlist.m3u8", // S3 URI
|
|
293
448
|
container: "#player",
|
|
449
|
+
s3Config: {
|
|
450
|
+
getPresignedUrl: async (s3Key) => {
|
|
451
|
+
// Call your backend to generate presigned URL
|
|
452
|
+
const response = await fetch("/api/s3/presigned-url", {
|
|
453
|
+
method: "POST",
|
|
454
|
+
headers: { "Content-Type": "application/json" },
|
|
455
|
+
body: JSON.stringify({ key: s3Key }),
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
throw new Error("Failed to get presigned URL")
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const data = await response.json()
|
|
463
|
+
return data.url // Return the presigned URL
|
|
464
|
+
},
|
|
465
|
+
},
|
|
294
466
|
})
|
|
295
467
|
```
|
|
296
468
|
|
|
469
|
+
**Backend Setup (Node.js):**
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"
|
|
473
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
|
474
|
+
|
|
475
|
+
const s3Client = new S3Client({
|
|
476
|
+
region: "us-east-1",
|
|
477
|
+
credentials: {
|
|
478
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
479
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
480
|
+
},
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
app.post("/api/s3/presigned-url", async (req, res) => {
|
|
484
|
+
const { key } = req.body
|
|
485
|
+
|
|
486
|
+
// Verify user is authorized to access this video
|
|
487
|
+
if (!req.user) {
|
|
488
|
+
return res.status(401).json({ error: "Not authenticated" })
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
// Generate presigned URL
|
|
493
|
+
const command = new GetObjectCommand({
|
|
494
|
+
Bucket: "my-bucket",
|
|
495
|
+
Key: key, // e.g., "videos/lesson-1/playlist.m3u8"
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
const url = await getSignedUrl(s3Client, command, {
|
|
499
|
+
expiresIn: 3600, // URL valid for 1 hour
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
res.json({ url })
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error("Error generating presigned URL:", error)
|
|
505
|
+
res.status(500).json({ error: "Failed to generate presigned URL" })
|
|
506
|
+
}
|
|
507
|
+
})
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
### Which Method Should I Use?
|
|
513
|
+
|
|
514
|
+
| Method | Best For | Complexity | Performance |
|
|
515
|
+
| ---------------------- | ---------------------------------------------- | ----------- | ------------ |
|
|
516
|
+
| **Public Videos** | Free content, marketing videos | โญ Easy | โก Fast |
|
|
517
|
+
| **CloudFront Cookies** | โญ **Recommended** for paid courses, premium | โญโญ Medium | โกโก Fastest |
|
|
518
|
+
| **S3 Presigned URLs** | Direct S3 access, simple private video hosting | โญโญ Medium | โก Good |
|
|
519
|
+
|
|
520
|
+
๐ก **Tip:** Use CloudFront with signed cookies for production. It's more secure and performant for HLS videos (which have many file segments).
|
|
521
|
+
|
|
297
522
|
## ๐ Subtitle Support
|
|
298
523
|
|
|
299
524
|
### Adding Subtitles
|
|
@@ -568,15 +793,7 @@ function CustomPlayer() {
|
|
|
568
793
|
Wontum Player comes with 7 beautiful pre-made themes:
|
|
569
794
|
|
|
570
795
|
```typescript
|
|
571
|
-
import {
|
|
572
|
-
netflixTheme,
|
|
573
|
-
youtubeTheme,
|
|
574
|
-
modernTheme,
|
|
575
|
-
greenTheme,
|
|
576
|
-
cyberpunkTheme,
|
|
577
|
-
pastelTheme,
|
|
578
|
-
educationTheme,
|
|
579
|
-
} from "@obipascal/player"
|
|
796
|
+
import { netflixTheme, youtubeTheme, modernTheme, greenTheme, cyberpunkTheme, pastelTheme, educationTheme } from "@obipascal/player"
|
|
580
797
|
|
|
581
798
|
const player = new WontumPlayer({
|
|
582
799
|
src: "https://media.example.com/video/playlist.m3u8",
|
|
@@ -586,6 +803,7 @@ const player = new WontumPlayer({
|
|
|
586
803
|
```
|
|
587
804
|
|
|
588
805
|
**Available Themes:**
|
|
806
|
+
|
|
589
807
|
- `netflixTheme()` - Netflix-inspired red and black
|
|
590
808
|
- `youtubeTheme()` - YouTube-inspired red and white
|
|
591
809
|
- `modernTheme()` - Modern blue gradient
|
|
@@ -634,6 +852,7 @@ const player = new WontumPlayer({
|
|
|
634
852
|
```
|
|
635
853
|
|
|
636
854
|
**Available Brand Colors:**
|
|
855
|
+
|
|
637
856
|
- `blue`, `lightBlue`, `darkBlue`
|
|
638
857
|
- `red`, `lightRed`, `darkRed`
|
|
639
858
|
- `green`, `lightGreen`, `darkGreen`
|
|
@@ -788,6 +1007,7 @@ For detailed API documentation including all methods, events, types, and configu
|
|
|
788
1007
|
### Quick Reference
|
|
789
1008
|
|
|
790
1009
|
**Player Methods:**
|
|
1010
|
+
|
|
791
1011
|
- **Playback:** `play()`, `pause()`, `seek(time)`, `skipForward(seconds)`, `skipBackward(seconds)`
|
|
792
1012
|
- **Volume:** `setVolume(level)`, `mute()`, `unmute()`
|
|
793
1013
|
- **Subtitles:** `enableSubtitles(index)`, `disableSubtitles()`, `toggleSubtitles()`, `getSubtitleTracks()`, `areSubtitlesEnabled()`
|
|
@@ -798,6 +1018,7 @@ For detailed API documentation including all methods, events, types, and configu
|
|
|
798
1018
|
- **Lifecycle:** `destroy()`
|
|
799
1019
|
|
|
800
1020
|
**Events (25 total):**
|
|
1021
|
+
|
|
801
1022
|
- **Playback:** `play`, `pause`, `ended`, `timeupdate`, `durationchange`
|
|
802
1023
|
- **Loading:** `loadstart`, `loadedmetadata`, `loadeddata`, `canplay`, `canplaythrough`
|
|
803
1024
|
- **Buffering:** `waiting`, `playing`, `stalled`, `suspend`, `abort`
|
|
@@ -812,13 +1033,13 @@ For detailed API documentation including all methods, events, types, and configu
|
|
|
812
1033
|
|
|
813
1034
|
## ๐ Browser Support
|
|
814
1035
|
|
|
815
|
-
| Browser
|
|
816
|
-
|
|
817
|
-
| Chrome
|
|
818
|
-
| Edge
|
|
819
|
-
| Firefox
|
|
820
|
-
| Safari
|
|
821
|
-
| iOS Safari
|
|
1036
|
+
| Browser | Minimum Version |
|
|
1037
|
+
| -------------- | ----------------- |
|
|
1038
|
+
| Chrome | Latest 2 versions |
|
|
1039
|
+
| Edge | Latest 2 versions |
|
|
1040
|
+
| Firefox | Latest 2 versions |
|
|
1041
|
+
| Safari | Latest 2 versions |
|
|
1042
|
+
| iOS Safari | iOS 12+ |
|
|
822
1043
|
| Android Chrome | Latest 2 versions |
|
|
823
1044
|
|
|
824
1045
|
**Note:** HLS playback requires HLS.js support. Native HLS playback is supported on Safari.
|
package/package.json
CHANGED