playron 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 +416 -216
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,287 +1,487 @@
|
|
|
1
1
|
# Playron
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Professional OTT video player library for React, Next.js, and Vanilla JS.
|
|
4
|
+
> HLS · DASH · DRM · VAST/VMAP · Live Stream · Subtitles · TypeScript-first
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- 🎬 **Adaptive Streaming**: HLS and DASH support
|
|
8
|
-
- 🔒 **DRM**: Widevine, PlayReady, FairPlay support
|
|
9
|
-
- 📱 **Responsive**: Mobile and desktop compatible
|
|
10
|
-
- 🎨 **Customizable**: All UI components can be customized
|
|
11
|
-
- 📊 **Analytics**: Integrated analytics support
|
|
12
|
-
- 📺 **Ads**: VAST/VMAP ad support
|
|
13
|
-
- ⚡ **Performant**: Optimized with React Compiler
|
|
14
|
-
- 🎯 **TypeScript**: Full type safety
|
|
6
|
+
---
|
|
15
7
|
|
|
16
|
-
##
|
|
8
|
+
## Overview
|
|
17
9
|
|
|
18
|
-
|
|
10
|
+
Playron is a modular, tree-shakeable video player library designed for OTT platforms. It wraps **hls.js** and **dash.js** as streaming engines and adds a full React UI layer with adaptive controls, DRM passthrough, ad integration, live stream support, and more.
|
|
19
11
|
|
|
20
|
-
|
|
12
|
+
```tsx
|
|
13
|
+
import { Player } from 'playron'
|
|
14
|
+
import 'playron/dist/playron.css'
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
# In the Playron directory
|
|
24
|
-
npm install
|
|
25
|
-
npm run build
|
|
26
|
-
npm link
|
|
16
|
+
<Player src="https://example.com/stream.m3u8" />
|
|
27
17
|
```
|
|
28
18
|
|
|
29
|
-
|
|
19
|
+
---
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
npm link playron
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### From NPM
|
|
21
|
+
## Installation
|
|
36
22
|
|
|
37
23
|
```bash
|
|
38
24
|
npm install playron
|
|
39
25
|
# or
|
|
40
26
|
yarn add playron
|
|
41
|
-
# or
|
|
42
27
|
pnpm add playron
|
|
43
28
|
```
|
|
44
29
|
|
|
45
|
-
|
|
30
|
+
### Local Development (yalc)
|
|
46
31
|
|
|
47
|
-
|
|
32
|
+
```bash
|
|
33
|
+
# In the playron repo
|
|
34
|
+
npm run build
|
|
35
|
+
npx yalc push
|
|
36
|
+
|
|
37
|
+
# In your project
|
|
38
|
+
npx yalc add playron
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### React / Next.js
|
|
48
46
|
|
|
49
47
|
```tsx
|
|
50
|
-
import { Player } from 'playron'
|
|
51
|
-
import 'playron/
|
|
48
|
+
import { Player } from 'playron'
|
|
49
|
+
import 'playron/dist/playron.css'
|
|
52
50
|
|
|
53
|
-
function
|
|
51
|
+
export default function VideoPage() {
|
|
54
52
|
return (
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
<Player
|
|
54
|
+
src="https://example.com/stream.m3u8"
|
|
55
|
+
poster="https://example.com/poster.jpg"
|
|
56
|
+
config={{
|
|
57
|
+
player: {
|
|
58
|
+
defaultVolume: 0.8,
|
|
59
|
+
skipSeconds: 10,
|
|
60
|
+
},
|
|
61
|
+
features: {
|
|
62
|
+
pip: true,
|
|
63
|
+
fullscreen: true,
|
|
64
|
+
theaterMode: true,
|
|
65
|
+
subtitles: true,
|
|
66
|
+
qualitySelector: true,
|
|
67
|
+
playbackSpeed: true,
|
|
68
|
+
keyboardShortcuts: true,
|
|
69
|
+
},
|
|
70
|
+
branding: {
|
|
71
|
+
primaryColor: '#e50914',
|
|
72
|
+
},
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
64
76
|
}
|
|
65
77
|
```
|
|
66
78
|
|
|
67
|
-
###
|
|
79
|
+
### Next.js App Router
|
|
68
80
|
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<link rel="stylesheet" href="node_modules/playron/dist/playron.css">
|
|
74
|
-
</head>
|
|
75
|
-
<body>
|
|
76
|
-
<div id="player-container"></div>
|
|
77
|
-
|
|
78
|
-
<script type="module">
|
|
79
|
-
import { Playron } from './node_modules/playron/dist/playron.es.js';
|
|
80
|
-
|
|
81
|
-
const player = new Playron({
|
|
82
|
-
containerId: 'player-container',
|
|
83
|
-
src: 'https://example.com/video.m3u8',
|
|
84
|
-
poster: 'https://example.com/poster.jpg',
|
|
85
|
-
autoPlay: false,
|
|
86
|
-
width: '100%',
|
|
87
|
-
height: 'auto'
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Event listeners
|
|
91
|
-
player.on('play', () => console.log('Video playing'));
|
|
92
|
-
player.on('pause', () => console.log('Video paused'));
|
|
93
|
-
|
|
94
|
-
// Player controls
|
|
95
|
-
player.play();
|
|
96
|
-
player.pause();
|
|
97
|
-
player.setVolume(0.5);
|
|
98
|
-
player.seekTo(30);
|
|
99
|
-
</script>
|
|
100
|
-
</body>
|
|
101
|
-
</html>
|
|
81
|
+
```tsx
|
|
82
|
+
'use client'
|
|
83
|
+
import { Player } from 'playron'
|
|
84
|
+
import 'playron/dist/playron.css'
|
|
102
85
|
```
|
|
103
86
|
|
|
104
|
-
###
|
|
87
|
+
### Next.js Pages Router
|
|
105
88
|
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<head>
|
|
110
|
-
<link rel="stylesheet" href="node_modules/playron/dist/playron.css">
|
|
111
|
-
<script src="node_modules/playron/dist/playron.umd.js"></script>
|
|
112
|
-
</head>
|
|
113
|
-
<body>
|
|
114
|
-
<div id="player-container"></div>
|
|
115
|
-
|
|
116
|
-
<script>
|
|
117
|
-
const player = new Playron.Playron({
|
|
118
|
-
containerId: 'player-container',
|
|
119
|
-
src: 'https://example.com/video.m3u8'
|
|
120
|
-
});
|
|
121
|
-
</script>
|
|
122
|
-
</body>
|
|
123
|
-
</html>
|
|
89
|
+
```tsx
|
|
90
|
+
import dynamic from 'next/dynamic'
|
|
91
|
+
const Player = dynamic(() => import('playron').then(m => m.Player), { ssr: false })
|
|
124
92
|
```
|
|
125
93
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
### Player Props (React)
|
|
94
|
+
### Vanilla JavaScript
|
|
129
95
|
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
src: string; // Video source URL
|
|
133
|
-
poster?: string; // Poster image URL
|
|
134
|
-
autoPlay?: boolean; // Auto-play video
|
|
135
|
-
muted?: boolean; // Start muted
|
|
136
|
-
controls?: boolean; // Show controls
|
|
137
|
-
loop?: boolean; // Loop video
|
|
138
|
-
width?: string | number; // Player width
|
|
139
|
-
height?: string | number; // Player height
|
|
140
|
-
className?: string; // Custom CSS class
|
|
141
|
-
style?: React.CSSProperties; // Inline styles
|
|
142
|
-
|
|
143
|
-
// DRM Config
|
|
144
|
-
drm?: {
|
|
145
|
-
widevine?: { licenseUrl: string };
|
|
146
|
-
playready?: { licenseUrl: string };
|
|
147
|
-
fairplay?: { licenseUrl: string; certificateUrl: string };
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// Events
|
|
151
|
-
onPlay?: () => void;
|
|
152
|
-
onPause?: () => void;
|
|
153
|
-
onEnded?: () => void;
|
|
154
|
-
onError?: (error: Error) => void;
|
|
155
|
-
onTimeUpdate?: (time: number) => void;
|
|
156
|
-
}
|
|
157
|
-
```
|
|
96
|
+
```html
|
|
97
|
+
<link rel="stylesheet" href="playron/dist/playron.css" />
|
|
158
98
|
|
|
159
|
-
|
|
99
|
+
<div id="player"></div>
|
|
160
100
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
constructor(config: PlayerConfig);
|
|
164
|
-
|
|
165
|
-
// Playback
|
|
166
|
-
play(): Promise<void>;
|
|
167
|
-
pause(): void;
|
|
168
|
-
toggleMute(): void;
|
|
169
|
-
setVolume(volume: number): void;
|
|
170
|
-
seekTo(time: number): void;
|
|
171
|
-
setPlaybackRate(rate: number): void;
|
|
172
|
-
|
|
173
|
-
// Source
|
|
174
|
-
setSource(src: string): Promise<void>;
|
|
175
|
-
setPoster(poster: string): void;
|
|
176
|
-
|
|
177
|
-
// Events
|
|
178
|
-
on<T>(eventType: string, callback: (event: T) => void): void;
|
|
179
|
-
off<T>(eventType: string, callback: (event: T) => void): void;
|
|
180
|
-
|
|
181
|
-
// State
|
|
182
|
-
getState(): PlayerState;
|
|
183
|
-
|
|
184
|
-
// Cleanup
|
|
185
|
-
destroy(): void;
|
|
186
|
-
}
|
|
187
|
-
```
|
|
101
|
+
<script type="module">
|
|
102
|
+
import { PlayerCore } from 'playron/dist/playron.es.js'
|
|
188
103
|
|
|
189
|
-
|
|
104
|
+
const player = new PlayerCore(document.querySelector('#player video'))
|
|
105
|
+
await player.setSource('https://example.com/stream.m3u8')
|
|
190
106
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
'timeupdate' | 'volumechange' | 'mute' | 'unmute' |
|
|
195
|
-
'seeked' | 'seeking' | 'loadstart' | 'loadeddata' |
|
|
196
|
-
'canplay' | 'waiting' | 'stalled'
|
|
107
|
+
player.eventEmitter.on('play', () => console.log('playing'))
|
|
108
|
+
player.eventEmitter.on('ended', () => console.log('ended'))
|
|
109
|
+
</script>
|
|
197
110
|
```
|
|
198
111
|
|
|
199
|
-
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Features
|
|
200
115
|
|
|
201
|
-
###
|
|
116
|
+
### Streaming
|
|
117
|
+
|
|
118
|
+
| Feature | Status |
|
|
119
|
+
|---------|--------|
|
|
120
|
+
| HLS (HTTP Live Streaming) | ✅ via **hls.js** |
|
|
121
|
+
| DASH (Dynamic Adaptive Streaming) | ✅ via **dash.js** |
|
|
122
|
+
| Progressive MP4 | ✅ native |
|
|
123
|
+
| Low Latency HLS (LL-HLS) | ✅ |
|
|
124
|
+
| Low Latency DASH (LL-DASH) | ✅ |
|
|
125
|
+
| VOD | ✅ |
|
|
126
|
+
| Live Stream | ✅ |
|
|
127
|
+
| DVR Window | ✅ |
|
|
128
|
+
| Adaptive Bitrate (ABR) | ✅ engine-native |
|
|
129
|
+
|
|
130
|
+
### DRM
|
|
131
|
+
|
|
132
|
+
| Key System | HLS | DASH | Platform |
|
|
133
|
+
|-----------|-----|------|----------|
|
|
134
|
+
| **Widevine** | ✅ | ✅ | Chrome, Firefox, Edge, Android |
|
|
135
|
+
| **PlayReady** | ❌ | ✅ | Edge, Windows |
|
|
136
|
+
| **FairPlay** | ❌ | ❌ | *Not yet implemented* |
|
|
137
|
+
| **AES-128** | ✅ native | — | All browsers |
|
|
138
|
+
|
|
139
|
+
> **Note:** FairPlay (Safari/iOS) is planned for a future release. When Widevine content is loaded in Safari, Playron automatically shows a DRM error overlay with browser recommendations.
|
|
140
|
+
|
|
141
|
+
### UI Controls
|
|
142
|
+
|
|
143
|
+
| Control | Description |
|
|
144
|
+
|---------|-------------|
|
|
145
|
+
| Play / Pause | Center overlay + control bar |
|
|
146
|
+
| Seek Bar | Scrubbing, buffered range visualization, thumbnail preview on hover |
|
|
147
|
+
| Volume | Slider + mute toggle, localStorage persistence |
|
|
148
|
+
| Playback Speed | 0.25× – 2× |
|
|
149
|
+
| Quality Selector | Manual ABR override |
|
|
150
|
+
| Audio Track | Multi-language audio |
|
|
151
|
+
| Subtitles | WebVTT + CEA-608/708 closed captions |
|
|
152
|
+
| Fullscreen | Native API |
|
|
153
|
+
| Picture-in-Picture | Native API |
|
|
154
|
+
| Theater Mode | Layout toggle |
|
|
155
|
+
| AirPlay | Safari WebKit API |
|
|
156
|
+
| Skip Forward / Backward | Configurable seconds |
|
|
157
|
+
| Skip Intro / Outro | Time-range based |
|
|
158
|
+
| Chapters | Navigation + auto-detection |
|
|
159
|
+
| Jump to Live | Appears when behind live edge |
|
|
160
|
+
| Live Latency Display | "Xs behind live" indicator |
|
|
161
|
+
| Timeline Markers | Sports events, chapters, ads |
|
|
162
|
+
| Settings Panel | YouTube-style unified panel (Quality · Speed · Audio · Subtitles) |
|
|
163
|
+
| Keyboard Shortcuts | Space, M, F, arrows, I, O, and more |
|
|
164
|
+
| Error Overlay | DRM-aware messages, network errors, retry |
|
|
165
|
+
| End Card | Next episode countdown |
|
|
166
|
+
| Context Menu | Loop, copy URL, copy speed |
|
|
167
|
+
|
|
168
|
+
### Ad System
|
|
169
|
+
|
|
170
|
+
| Format | Support |
|
|
171
|
+
|--------|---------|
|
|
172
|
+
| VAST 2.0 / 3.0 / 4.x | ✅ |
|
|
173
|
+
| VMAP 1.0 | ✅ |
|
|
174
|
+
| Pre-roll | ✅ |
|
|
175
|
+
| Mid-roll | ✅ |
|
|
176
|
+
| Post-roll | ✅ |
|
|
177
|
+
| Skip button | ✅ configurable delay |
|
|
178
|
+
| VPAID 2.0 | ✅ iframe sandbox |
|
|
179
|
+
| Companion banner | ✅ |
|
|
180
|
+
| Ad pod | ✅ |
|
|
181
|
+
| Impression / tracking events | ✅ |
|
|
182
|
+
|
|
183
|
+
### Accessibility
|
|
184
|
+
|
|
185
|
+
- WCAG 2.1 AA compliant
|
|
186
|
+
- ARIA roles on all interactive elements
|
|
187
|
+
- Full keyboard navigation
|
|
188
|
+
- Screen reader live region for status announcements
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Configuration
|
|
202
193
|
|
|
203
194
|
```tsx
|
|
204
|
-
|
|
205
|
-
|
|
195
|
+
<Player
|
|
196
|
+
src="..."
|
|
197
|
+
config={{
|
|
198
|
+
player: {
|
|
199
|
+
defaultVolume: 1, // 0–1
|
|
200
|
+
defaultPlaybackRate: 1,
|
|
201
|
+
skipSeconds: 10,
|
|
202
|
+
autoResume: true,
|
|
203
|
+
loop: false,
|
|
204
|
+
preload: 'auto', // 'none' | 'metadata' | 'auto'
|
|
205
|
+
},
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
{/* Add your own custom controls */}
|
|
215
|
-
</ControlBar>
|
|
216
|
-
</div>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
```
|
|
207
|
+
ui: {
|
|
208
|
+
autoHideDelay: 3000, // ms
|
|
209
|
+
showTitle: true,
|
|
210
|
+
showTimeTooltip: true,
|
|
211
|
+
showVolumeSlider: true,
|
|
212
|
+
controlBarPosition: 'bottom',
|
|
213
|
+
},
|
|
220
214
|
|
|
221
|
-
|
|
215
|
+
features: {
|
|
216
|
+
pip: true,
|
|
217
|
+
fullscreen: true,
|
|
218
|
+
theaterMode: true,
|
|
219
|
+
chapters: true,
|
|
220
|
+
skipIntro: true,
|
|
221
|
+
subtitles: true,
|
|
222
|
+
qualitySelector: true,
|
|
223
|
+
audioTrackSelector: true,
|
|
224
|
+
playbackSpeed: true,
|
|
225
|
+
keyboardShortcuts: true,
|
|
226
|
+
},
|
|
222
227
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
branding: {
|
|
229
|
+
primaryColor: '#e50914', // hex
|
|
230
|
+
accentColor: '#ffffff',
|
|
231
|
+
logo: 'https://example.com/logo.svg',
|
|
232
|
+
logoLink: 'https://example.com',
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
metadata: {
|
|
236
|
+
title: 'My Video',
|
|
237
|
+
thumbnail: 'https://example.com/thumb.jpg',
|
|
238
|
+
rating: 'TV-MA',
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
thumbnails: {
|
|
242
|
+
vttUrl: 'https://example.com/thumbnails.vtt', // storyboard preview
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
drm: {
|
|
246
|
+
enabled: true,
|
|
247
|
+
widevine: {
|
|
248
|
+
licenseUrl: 'https://license.example.com/widevine',
|
|
249
|
+
headers: { 'Authorization': 'Bearer TOKEN' },
|
|
250
|
+
withCredentials: false,
|
|
251
|
+
},
|
|
252
|
+
playready: {
|
|
253
|
+
licenseUrl: 'https://license.example.com/playready',
|
|
254
|
+
},
|
|
255
|
+
// fairplay: not yet implemented
|
|
256
|
+
preferredSystem: 'widevine',
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
ads: {
|
|
260
|
+
enabled: true,
|
|
261
|
+
vmapUrl: 'https://ads.example.com/vmap.xml', // takes priority over vastUrl
|
|
262
|
+
vastUrl: 'https://ads.example.com/vast.xml',
|
|
263
|
+
skipAfter: 5, // seconds before skip button appears
|
|
264
|
+
timeout: 10000, // fetch timeout ms
|
|
265
|
+
maxWrapperDepth: 5, // VAST wrapper chain limit
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
live: {
|
|
269
|
+
atLiveEdgeTolerance: 3, // seconds
|
|
270
|
+
showJumpToLiveButton: true,
|
|
271
|
+
showLatency: true,
|
|
272
|
+
dvr: { enabled: true, maxDvrWindow: 1800 },
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
advanced: {
|
|
276
|
+
bufferAhead: 30,
|
|
277
|
+
bufferBehind: 10,
|
|
278
|
+
maxBufferLength: 60,
|
|
279
|
+
debug: false,
|
|
229
280
|
},
|
|
230
|
-
playready: {
|
|
231
|
-
licenseUrl: 'https://example.com/playready-license'
|
|
232
|
-
}
|
|
233
281
|
}}
|
|
234
282
|
/>
|
|
235
283
|
```
|
|
236
284
|
|
|
237
|
-
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## DRM Usage
|
|
288
|
+
|
|
289
|
+
### Widevine (Chrome / Firefox / Edge)
|
|
238
290
|
|
|
239
291
|
```tsx
|
|
240
292
|
<Player
|
|
241
|
-
src="https://example.com/
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
293
|
+
src="https://example.com/encrypted.mpd"
|
|
294
|
+
config={{
|
|
295
|
+
drm: {
|
|
296
|
+
enabled: true,
|
|
297
|
+
widevine: {
|
|
298
|
+
licenseUrl: 'https://license.example.com/widevine',
|
|
299
|
+
headers: { 'Authorization': 'Bearer YOUR_TOKEN' },
|
|
300
|
+
},
|
|
301
|
+
},
|
|
246
302
|
}}
|
|
247
303
|
/>
|
|
248
304
|
```
|
|
249
305
|
|
|
250
|
-
|
|
306
|
+
### Safari / iOS
|
|
251
307
|
|
|
252
|
-
|
|
253
|
-
# Install dependencies
|
|
254
|
-
npm install
|
|
308
|
+
FairPlay is not yet implemented. When a user on Safari attempts to play Widevine-protected content, Playron shows a DRM error overlay recommending Chrome or Firefox. This behaviour is automatic — no extra configuration needed.
|
|
255
309
|
|
|
256
|
-
|
|
257
|
-
npm run dev
|
|
310
|
+
---
|
|
258
311
|
|
|
259
|
-
|
|
260
|
-
|
|
312
|
+
## Event System
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { PlayerCore } from 'playron'
|
|
316
|
+
|
|
317
|
+
const player = new PlayerCore(videoElement)
|
|
318
|
+
|
|
319
|
+
player.eventEmitter.on('play', ({ timestamp }) => { })
|
|
320
|
+
player.eventEmitter.on('pause', ({ timestamp }) => { })
|
|
321
|
+
player.eventEmitter.on('ended', ({ timestamp }) => { })
|
|
322
|
+
player.eventEmitter.on('timeupdate', ({ currentTime, duration }) => { })
|
|
323
|
+
player.eventEmitter.on('volumechange',({ volume, isMuted }) => { })
|
|
324
|
+
player.eventEmitter.on('qualitychange',({ from, to }) => { })
|
|
325
|
+
player.eventEmitter.on('error', ({ code, message, details }) => { })
|
|
326
|
+
player.eventEmitter.on('ready', ({ player }) => { })
|
|
327
|
+
player.eventEmitter.on('destroy', ({ timestamp }) => { })
|
|
328
|
+
|
|
329
|
+
// One-time listener
|
|
330
|
+
player.eventEmitter.once('ready', ({ player }) => player.play())
|
|
261
331
|
|
|
262
|
-
|
|
263
|
-
|
|
332
|
+
// Remove listener
|
|
333
|
+
player.eventEmitter.off('play', handler)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### React Hooks
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
import { usePlayerState, usePlayerMethods, usePlayer } from 'playron'
|
|
340
|
+
|
|
341
|
+
function MyControls() {
|
|
342
|
+
const { isPlaying, volume, currentTime, duration, isLive, isBuffering } = usePlayerState()
|
|
343
|
+
const { play, pause, seekTo, setVolume, seekToLiveEdge } = usePlayerMethods()
|
|
344
|
+
const player = usePlayer() // raw PlayerCore instance
|
|
345
|
+
}
|
|
264
346
|
```
|
|
265
347
|
|
|
266
|
-
|
|
348
|
+
---
|
|
267
349
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
350
|
+
## PlayerCore API
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
class PlayerCore {
|
|
354
|
+
// Playback
|
|
355
|
+
play(): Promise<void>
|
|
356
|
+
pause(): void
|
|
357
|
+
seekTo(time: number): void
|
|
358
|
+
setPlaybackRate(rate: number): void
|
|
359
|
+
|
|
360
|
+
// Volume
|
|
361
|
+
setVolume(volume: number): void // 0–1
|
|
362
|
+
toggleMute(): void
|
|
363
|
+
|
|
364
|
+
// Display
|
|
365
|
+
toggleFullscreen(): Promise<void>
|
|
366
|
+
togglePip(): Promise<void>
|
|
367
|
+
toggleTheaterMode(): void
|
|
368
|
+
|
|
369
|
+
// Stream
|
|
370
|
+
setSource(src: string): Promise<void>
|
|
371
|
+
getStreamType(): 'hls' | 'dash' | 'mp4' | 'unknown'
|
|
372
|
+
isLive(): boolean
|
|
373
|
+
|
|
374
|
+
// Live
|
|
375
|
+
seekToLiveEdge(): void
|
|
376
|
+
isAtLiveEdge(): boolean
|
|
377
|
+
getCurrentLatency(): number
|
|
378
|
+
getDVRRange(): { start: number; end: number } | null
|
|
379
|
+
|
|
380
|
+
// Quality / Audio / Subtitles
|
|
381
|
+
setQuality(id: string): void
|
|
382
|
+
getAvailableQualities(): QualityLevel[]
|
|
383
|
+
setAudioTrack(id: string): void
|
|
384
|
+
getAvailableAudioTracks(): AudioTrack[]
|
|
385
|
+
|
|
386
|
+
// Skip
|
|
387
|
+
skipForward(seconds?: number): void
|
|
388
|
+
skipBackward(seconds?: number): void
|
|
389
|
+
|
|
390
|
+
// DRM
|
|
391
|
+
setupDrm(config: DrmConfig): void
|
|
392
|
+
|
|
393
|
+
// Buffer
|
|
394
|
+
getBufferedRanges(): Array<{ start: number; end: number }>
|
|
395
|
+
|
|
396
|
+
// Cleanup
|
|
397
|
+
destroy(): void
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Bundle Size
|
|
404
|
+
|
|
405
|
+
| File | Raw | Gzip |
|
|
406
|
+
|------|-----|------|
|
|
407
|
+
| `playron.es.js` | 2.1 MB | **559 KB** |
|
|
408
|
+
| `playron.cjs.js` | 1.6 MB | **490 KB** |
|
|
409
|
+
| `playron.css` | 5.9 KB | **2 KB** |
|
|
410
|
+
|
|
411
|
+
> Bundle includes hls.js + dash.js streaming engines, DRM passthrough layer, VAST/VMAP ad engine, WebVTT + CEA-608 subtitle decoders, and full React UI. In production, ESM tree-shaking eliminates unused modules.
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## Framework Support
|
|
416
|
+
|
|
417
|
+
| Framework | Support | Notes |
|
|
418
|
+
|-----------|---------|-------|
|
|
419
|
+
| React 18 / 19 | ✅ Full | Hooks + Context API |
|
|
420
|
+
| Next.js App Router | ✅ Full | Add `'use client'` |
|
|
421
|
+
| Next.js Pages Router | ✅ Full | Use `dynamic(..., { ssr: false })` |
|
|
422
|
+
| Vite + React | ✅ Full | Reference dev environment |
|
|
423
|
+
| Remix | ✅ Full | Use `ClientOnly` wrapper |
|
|
424
|
+
| Astro | 🟡 Partial | `client:only="react"` |
|
|
425
|
+
| Vanilla JS | 🟡 Partial | Use `PlayerCore` directly |
|
|
426
|
+
| Vue / Nuxt | 🟡 Partial | Mount `PlayerCore` in `onMounted` |
|
|
427
|
+
|
|
428
|
+
---
|
|
273
429
|
|
|
274
430
|
## Browser Support
|
|
275
431
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
432
|
+
| Browser | HLS | DASH | Widevine | PlayReady | FairPlay |
|
|
433
|
+
|---------|-----|------|----------|-----------|---------|
|
|
434
|
+
| Chrome 90+ | ✅ | ✅ | ✅ | — | — |
|
|
435
|
+
| Firefox 88+ | ✅ | ✅ | ✅ | — | — |
|
|
436
|
+
| Edge 90+ | ✅ | ✅ | ✅ | ✅ | — |
|
|
437
|
+
| Safari 14+ | ✅ | ✅ | ❌ | — | ⏳ planned |
|
|
438
|
+
| iOS Safari 14+ | ✅ | ✅ | ❌ | — | ⏳ planned |
|
|
439
|
+
| Chrome Android | ✅ | ✅ | ✅ | — | — |
|
|
280
440
|
|
|
281
|
-
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Development
|
|
282
444
|
|
|
283
|
-
|
|
445
|
+
```bash
|
|
446
|
+
npm install
|
|
447
|
+
|
|
448
|
+
npm run dev # HTTPS dev server (port 5173)
|
|
449
|
+
npm run build # TypeScript check + library build
|
|
450
|
+
npm run build:watch # Watch mode
|
|
451
|
+
npm run push # Build + yalc push to linked projects
|
|
452
|
+
npm run lint # ESLint
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
> **HTTPS is required.** See `SSL-SETUP.md` for local certificate setup.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## Roadmap
|
|
460
|
+
|
|
461
|
+
- [x] HLS engine (hls.js wrapper)
|
|
462
|
+
- [x] DASH engine (dash.js wrapper)
|
|
463
|
+
- [x] Widevine DRM — HLS + DASH
|
|
464
|
+
- [x] PlayReady DRM — DASH
|
|
465
|
+
- [x] AES-128 HLS decryption
|
|
466
|
+
- [x] VAST / VMAP ad system
|
|
467
|
+
- [x] WebVTT subtitles
|
|
468
|
+
- [x] CEA-608 / 708 closed captions
|
|
469
|
+
- [x] Live stream + DVR
|
|
470
|
+
- [x] LL-HLS / LL-DASH
|
|
471
|
+
- [x] Thumbnail storyboard preview
|
|
472
|
+
- [x] AirPlay
|
|
473
|
+
- [x] Analytics plugin
|
|
474
|
+
- [x] Stall detection + auto-recovery
|
|
475
|
+
- [x] DRM-aware error overlay
|
|
476
|
+
- [ ] **FairPlay** — Safari / iOS *(in progress)*
|
|
477
|
+
- [ ] Chromecast
|
|
478
|
+
- [ ] SSAI (Google DAI / AWS Elemental)
|
|
479
|
+
- [ ] TTML / IMSC subtitles
|
|
480
|
+
- [ ] Offline playback (Service Worker)
|
|
481
|
+
- [ ] 360° video (WebGL)
|
|
482
|
+
|
|
483
|
+
---
|
|
284
484
|
|
|
285
|
-
##
|
|
485
|
+
## License
|
|
286
486
|
|
|
287
|
-
Suat Erkilic
|
|
487
|
+
MIT © Suat Erkilic
|