playron 1.0.0 → 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 +417 -217
- 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
|
+
---
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- 🔒 **DRM**: Widevine, PlayReady, FairPlay desteği
|
|
9
|
-
- 📱 **Responsive**: Mobil ve desktop uyumlu
|
|
10
|
-
- 🎨 **Özelleştirilebilir**: Tüm UI bileşenleri özelleştirilebilir
|
|
11
|
-
- 📊 **Analytics**: Entegre analytics desteği
|
|
12
|
-
- 📺 **Ads**: VAST/VMAP reklam desteği
|
|
13
|
-
- ⚡ **Performanslı**: React Compiler ile optimize edilmiş
|
|
14
|
-
- 🎯 **TypeScript**: Full type safety
|
|
8
|
+
## Overview
|
|
15
9
|
|
|
16
|
-
|
|
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.
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
```tsx
|
|
13
|
+
import { Player } from 'playron'
|
|
14
|
+
import 'playron/dist/playron.css'
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
# Playron dizininde
|
|
24
|
-
npm install
|
|
25
|
-
npm run build
|
|
26
|
-
npm link
|
|
16
|
+
<Player src="https://example.com/stream.m3u8" />
|
|
27
17
|
```
|
|
28
18
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npm link playron
|
|
33
|
-
```
|
|
19
|
+
---
|
|
34
20
|
|
|
35
|
-
|
|
21
|
+
## Installation
|
|
36
22
|
|
|
37
23
|
```bash
|
|
38
24
|
npm install playron
|
|
39
|
-
#
|
|
25
|
+
# or
|
|
40
26
|
yarn add playron
|
|
41
|
-
# veya
|
|
42
27
|
pnpm add playron
|
|
43
28
|
```
|
|
44
29
|
|
|
45
|
-
|
|
30
|
+
### Local Development (yalc)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# In the playron repo
|
|
34
|
+
npm run build
|
|
35
|
+
npx yalc push
|
|
46
36
|
|
|
47
|
-
|
|
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 kontrolü
|
|
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
|
-
|
|
94
|
+
### Vanilla JavaScript
|
|
127
95
|
|
|
128
|
-
|
|
96
|
+
```html
|
|
97
|
+
<link rel="stylesheet" href="playron/dist/playron.css" />
|
|
129
98
|
|
|
130
|
-
|
|
131
|
-
interface PlayerProps {
|
|
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
|
-
```
|
|
99
|
+
<div id="player"></div>
|
|
158
100
|
|
|
159
|
-
|
|
101
|
+
<script type="module">
|
|
102
|
+
import { PlayerCore } from 'playron/dist/playron.es.js'
|
|
160
103
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
}
|
|
104
|
+
const player = new PlayerCore(document.querySelector('#player video'))
|
|
105
|
+
await player.setSource('https://example.com/stream.m3u8')
|
|
106
|
+
|
|
107
|
+
player.eventEmitter.on('play', () => console.log('playing'))
|
|
108
|
+
player.eventEmitter.on('ended', () => console.log('ended'))
|
|
109
|
+
</script>
|
|
187
110
|
```
|
|
188
111
|
|
|
189
|
-
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Features
|
|
115
|
+
|
|
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
|
|
190
193
|
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
```tsx
|
|
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
|
+
},
|
|
198
206
|
|
|
199
|
-
|
|
207
|
+
ui: {
|
|
208
|
+
autoHideDelay: 3000, // ms
|
|
209
|
+
showTitle: true,
|
|
210
|
+
showTimeTooltip: true,
|
|
211
|
+
showVolumeSlider: true,
|
|
212
|
+
controlBarPosition: 'bottom',
|
|
213
|
+
},
|
|
200
214
|
|
|
201
|
-
|
|
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
|
+
},
|
|
202
227
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
228
|
+
branding: {
|
|
229
|
+
primaryColor: '#e50914', // hex
|
|
230
|
+
accentColor: '#ffffff',
|
|
231
|
+
logo: 'https://example.com/logo.svg',
|
|
232
|
+
logoLink: 'https://example.com',
|
|
233
|
+
},
|
|
206
234
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<PlayButton />
|
|
213
|
-
<MuteButton />
|
|
214
|
-
{/* Kendi custom kontrollerini ekle */}
|
|
215
|
-
</ControlBar>
|
|
216
|
-
</div>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
```
|
|
235
|
+
metadata: {
|
|
236
|
+
title: 'My Video',
|
|
237
|
+
thumbnail: 'https://example.com/thumb.jpg',
|
|
238
|
+
rating: 'TV-MA',
|
|
239
|
+
},
|
|
220
240
|
|
|
221
|
-
|
|
241
|
+
thumbnails: {
|
|
242
|
+
vttUrl: 'https://example.com/thumbnails.vtt', // storyboard preview
|
|
243
|
+
},
|
|
222
244
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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())
|
|
331
|
+
|
|
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
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
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 }>
|
|
261
395
|
|
|
262
|
-
|
|
263
|
-
|
|
396
|
+
// Cleanup
|
|
397
|
+
destroy(): void
|
|
398
|
+
}
|
|
264
399
|
```
|
|
265
400
|
|
|
266
|
-
|
|
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
|
+
---
|
|
267
414
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
+
---
|
|
282
442
|
|
|
283
|
-
|
|
443
|
+
## Development
|
|
284
444
|
|
|
285
|
-
|
|
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
|
+
---
|
|
484
|
+
|
|
485
|
+
## License
|
|
286
486
|
|
|
287
|
-
Suat Erkilic
|
|
487
|
+
MIT © Suat Erkilic
|