playron 1.0.1 → 1.0.3

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.
Files changed (2) hide show
  1. package/README.md +504 -209
  2. package/package.json +8 -2
package/README.md CHANGED
@@ -1,287 +1,582 @@
1
1
  # Playron
2
2
 
3
- Modern, customizable OTT video player. Can be used in React, Next.js, and vanilla JavaScript projects.
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
- ## Features
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
- ## Installation
8
+ ## Overview
17
9
 
18
- ### For Development (npm link)
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
- To use this project in other projects during development:
12
+ ```tsx
13
+ import { Player } from 'playron'
14
+ import 'playron/style.css'
21
15
 
22
- ```bash
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
- Then in the project you will use it:
30
-
31
- ```bash
32
- npm link playron
33
- ```
19
+ ---
34
20
 
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
- ## Usage
30
+ ### Local Development (yalc)
31
+
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
+ ---
46
42
 
47
- ### In React/Next.js Projects
43
+ ## Quick Start
44
+
45
+ ### React (Vite / CRA)
48
46
 
49
47
  ```tsx
50
- import { Player } from 'playron';
51
- import 'playron/style.css';
48
+ import { Player } from 'playron'
49
+ import 'playron/style.css'
52
50
 
53
- function MyApp() {
51
+ export default function VideoPage() {
54
52
  return (
55
- <div>
56
- <Player
57
- src="https://example.com/video.m3u8"
58
- poster="https://example.com/poster.jpg"
59
- autoPlay={false}
60
- controls={true}
61
- />
62
- </div>
63
- );
53
+ <Player
54
+ src="https://example.com/stream.m3u8"
55
+ poster="https://example.com/poster.jpg"
56
+ config={{
57
+ player: { defaultVolume: 0.8, skipSeconds: 10 },
58
+ features: {
59
+ pip: true,
60
+ fullscreen: true,
61
+ theaterMode: true,
62
+ subtitles: true,
63
+ qualitySelector: true,
64
+ playbackSpeed: true,
65
+ keyboardShortcuts: true,
66
+ },
67
+ branding: { primaryColor: '#e50914' },
68
+ }}
69
+ />
70
+ )
64
71
  }
65
72
  ```
66
73
 
67
- ### In Vanilla JavaScript (MVC) Projects
74
+ ### Next.js App Router
68
75
 
69
- ```html
70
- <!DOCTYPE html>
71
- <html>
72
- <head>
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>
102
- ```
76
+ > **Important:** Playron uses browser-only APIs (MediaSource, EME, hls.js, dash.js).
77
+ > You **must** use `dynamic()` with `ssr: false`. Using only `'use client'` is not enough —
78
+ > Next.js still pre-renders client components on the server for the initial HTML.
103
79
 
104
- ### UMD (with Script tag)
80
+ **Step 1** — Create a client wrapper with `dynamic`:
105
81
 
106
- ```html
107
- <!DOCTYPE html>
108
- <html>
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>
82
+ ```tsx
83
+ // components/VideoPlayer.tsx
84
+ 'use client'
85
+ import dynamic from 'next/dynamic'
86
+ import 'playron/style.css'
87
+
88
+ const Player = dynamic(
89
+ () => import('playron').then(m => m.Player),
90
+ {
91
+ ssr: false,
92
+ loading: () => (
93
+ <div style={{ width: '100%', aspectRatio: '16/9', background: '#000' }} />
94
+ ),
95
+ }
96
+ )
97
+
98
+ export default function VideoPlayer({ src }: { src: string }) {
99
+ return <Player src={src} />
100
+ }
124
101
  ```
125
102
 
126
- ## API
103
+ **Step 2** — Use it in your page (Server Component is fine here):
127
104
 
128
- ### Player Props (React)
105
+ ```tsx
106
+ // app/page.tsx
107
+ import VideoPlayer from '@/components/VideoPlayer'
129
108
 
130
- ```typescript
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;
109
+ export default function Page() {
110
+ return <VideoPlayer src="https://example.com/stream.m3u8" />
156
111
  }
157
112
  ```
158
113
 
159
- ### Playron Class (Vanilla JS)
114
+ ### Next.js Pages Router
160
115
 
161
- ```typescript
162
- class Playron {
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;
116
+ ```tsx
117
+ // pages/video.tsx
118
+ import dynamic from 'next/dynamic'
119
+ import 'playron/style.css'
120
+
121
+ const Player = dynamic(
122
+ () => import('playron').then(m => m.Player),
123
+ { ssr: false }
124
+ )
125
+
126
+ export default function VideoPage() {
127
+ return <Player src="https://example.com/stream.m3u8" />
186
128
  }
187
129
  ```
188
130
 
189
- ### Events
131
+ ### Vanilla JavaScript
190
132
 
191
- ```typescript
192
- // Available events
193
- 'play' | 'pause' | 'ended' | 'error' |
194
- 'timeupdate' | 'volumechange' | 'mute' | 'unmute' |
195
- 'seeked' | 'seeking' | 'loadstart' | 'loadeddata' |
196
- 'canplay' | 'waiting' | 'stalled'
197
- ```
133
+ ```html
134
+ <link rel="stylesheet" href="node_modules/playron/dist/playron.css" />
198
135
 
199
- ## Advanced Usage
136
+ <div id="player"></div>
200
137
 
201
- ### Custom Controls (React)
138
+ <script type="module">
139
+ import { PlayerCore } from 'playron/dist/playron.es.js'
202
140
 
203
- ```tsx
204
- import { PlayerCore, EventBus } from 'playron';
205
- import { ControlBar, PlayButton, MuteButton } from 'playron';
141
+ const player = new PlayerCore(document.querySelector('#player video'))
142
+ await player.setSource('https://example.com/stream.m3u8')
206
143
 
207
- function CustomPlayer() {
208
- return (
209
- <div className="custom-player">
210
- <video id="my-video" />
211
- <ControlBar>
212
- <PlayButton />
213
- <MuteButton />
214
- {/* Add your own custom controls */}
215
- </ControlBar>
216
- </div>
217
- );
218
- }
144
+ player.eventEmitter.on('play', () => console.log('playing'))
145
+ player.eventEmitter.on('ended', () => console.log('ended'))
146
+ </script>
219
147
  ```
220
148
 
221
- ### Using DRM
149
+ ---
150
+
151
+ ## Features
152
+
153
+ ### Streaming
154
+
155
+ | Feature | Status |
156
+ |---------|--------|
157
+ | HLS (HTTP Live Streaming) | ✅ via **hls.js** |
158
+ | DASH (Dynamic Adaptive Streaming) | ✅ via **dash.js** |
159
+ | Progressive MP4 | ✅ native |
160
+ | Low Latency HLS (LL-HLS) | ✅ |
161
+ | Low Latency DASH (LL-DASH) | ✅ |
162
+ | VOD | ✅ |
163
+ | Live Stream | ✅ |
164
+ | DVR Window | ✅ |
165
+ | Adaptive Bitrate (ABR) | ✅ engine-native |
166
+
167
+ ### DRM
168
+
169
+ | Key System | HLS | DASH | Platform |
170
+ |-----------|-----|------|----------|
171
+ | **Widevine** | ✅ | ✅ | Chrome, Firefox, Edge, Android |
172
+ | **PlayReady** | ❌ | ✅ | Edge, Windows |
173
+ | **FairPlay** | ❌ | ❌ | *Not yet implemented* |
174
+ | **AES-128** | ✅ native | — | All browsers |
175
+
176
+ > **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.
177
+
178
+ ### UI Controls
179
+
180
+ | Control | Description |
181
+ |---------|-------------|
182
+ | Play / Pause | Center overlay + control bar |
183
+ | Seek Bar | Scrubbing, buffered range visualization, thumbnail preview on hover |
184
+ | Volume | Slider + mute toggle, localStorage persistence |
185
+ | Playback Speed | 0.25× – 2× |
186
+ | Quality Selector | Manual ABR override |
187
+ | Audio Track | Multi-language audio |
188
+ | Subtitles | WebVTT + CEA-608/708 closed captions |
189
+ | Fullscreen | Native API |
190
+ | Picture-in-Picture | Native API |
191
+ | Theater Mode | Layout toggle |
192
+ | AirPlay | Safari WebKit API |
193
+ | Skip Forward / Backward | Configurable seconds |
194
+ | Skip Intro / Outro | Time-range based |
195
+ | Chapters | Navigation + auto-detection |
196
+ | Jump to Live | Appears when behind live edge |
197
+ | Live Latency Display | "Xs behind live" indicator |
198
+ | Timeline Markers | Sports events, chapters, ads |
199
+ | Settings Panel | YouTube-style unified panel (Quality · Speed · Audio · Subtitles) |
200
+ | Keyboard Shortcuts | Space, M, F, arrows, I, O, and more |
201
+ | Error Overlay | DRM-aware messages, network errors, retry |
202
+ | End Card | Next episode countdown |
203
+ | Context Menu | Loop, copy URL, copy speed |
204
+
205
+ ### Ad System
206
+
207
+ | Format | Support |
208
+ |--------|---------|
209
+ | VAST 2.0 / 3.0 / 4.x | ✅ |
210
+ | VMAP 1.0 | ✅ |
211
+ | Pre-roll | ✅ |
212
+ | Mid-roll | ✅ |
213
+ | Post-roll | ✅ |
214
+ | Skip button | ✅ configurable delay |
215
+ | VPAID 2.0 | ✅ iframe sandbox |
216
+ | Companion banner | ✅ |
217
+ | Ad pod | ✅ |
218
+ | Impression / tracking events | ✅ |
219
+
220
+ ### Accessibility
221
+
222
+ - WCAG 2.1 AA compliant
223
+ - ARIA roles on all interactive elements
224
+ - Full keyboard navigation
225
+ - Screen reader live region for status announcements
226
+
227
+ ---
228
+
229
+ ## Configuration
222
230
 
223
231
  ```tsx
224
232
  <Player
225
- src="https://example.com/encrypted-video.mpd"
226
- drm={{
227
- widevine: {
228
- licenseUrl: 'https://example.com/widevine-license'
233
+ src="..."
234
+ config={{
235
+ player: {
236
+ defaultVolume: 1, // 0–1
237
+ defaultPlaybackRate: 1,
238
+ skipSeconds: 10,
239
+ autoResume: true,
240
+ loop: false,
241
+ preload: 'auto', // 'none' | 'metadata' | 'auto'
242
+ },
243
+
244
+ ui: {
245
+ autoHideDelay: 3000, // ms
246
+ showTitle: true,
247
+ showTimeTooltip: true,
248
+ showVolumeSlider: true,
249
+ controlBarPosition: 'bottom',
250
+ },
251
+
252
+ features: {
253
+ pip: true,
254
+ fullscreen: true,
255
+ theaterMode: true,
256
+ chapters: true,
257
+ skipIntro: true,
258
+ subtitles: true,
259
+ qualitySelector: true,
260
+ audioTrackSelector: true,
261
+ playbackSpeed: true,
262
+ keyboardShortcuts: true,
263
+ },
264
+
265
+ branding: {
266
+ primaryColor: '#e50914', // hex
267
+ accentColor: '#ffffff',
268
+ logo: 'https://example.com/logo.svg',
269
+ logoLink: 'https://example.com',
270
+ },
271
+
272
+ metadata: {
273
+ title: 'My Video',
274
+ thumbnail: 'https://example.com/thumb.jpg',
275
+ rating: 'TV-MA',
276
+ },
277
+
278
+ thumbnails: {
279
+ vttUrl: 'https://example.com/thumbnails.vtt', // storyboard preview
280
+ },
281
+
282
+ drm: {
283
+ enabled: true,
284
+ widevine: {
285
+ licenseUrl: 'https://license.example.com/widevine',
286
+ headers: { 'Authorization': 'Bearer TOKEN' },
287
+ withCredentials: false,
288
+ },
289
+ playready: {
290
+ licenseUrl: 'https://license.example.com/playready',
291
+ },
292
+ // fairplay: not yet implemented
293
+ preferredSystem: 'widevine',
294
+ },
295
+
296
+ ads: {
297
+ enabled: true,
298
+ vmapUrl: 'https://ads.example.com/vmap.xml', // takes priority over vastUrl
299
+ vastUrl: 'https://ads.example.com/vast.xml',
300
+ skipAfter: 5, // seconds before skip button appears
301
+ timeout: 10000, // fetch timeout ms
302
+ maxWrapperDepth: 5, // VAST wrapper chain limit
303
+ },
304
+
305
+ live: {
306
+ atLiveEdgeTolerance: 3, // seconds
307
+ showJumpToLiveButton: true,
308
+ showLatency: true,
309
+ dvr: { enabled: true, maxDvrWindow: 1800 },
310
+ },
311
+
312
+ advanced: {
313
+ bufferAhead: 30,
314
+ bufferBehind: 10,
315
+ maxBufferLength: 60,
316
+ debug: false,
229
317
  },
230
- playready: {
231
- licenseUrl: 'https://example.com/playready-license'
232
- }
233
318
  }}
234
319
  />
235
320
  ```
236
321
 
237
- ### VAST/VMAP Ads
322
+ ---
323
+
324
+ ## DRM Usage
325
+
326
+ ### Widevine (Chrome / Firefox / Edge)
238
327
 
239
328
  ```tsx
240
329
  <Player
241
- src="https://example.com/video.m3u8"
242
- ads={{
243
- vastUrl: 'https://example.com/vast.xml',
244
- // or
245
- vmapUrl: 'https://example.com/vmap.xml'
330
+ src="https://example.com/encrypted.mpd"
331
+ config={{
332
+ drm: {
333
+ enabled: true,
334
+ widevine: {
335
+ licenseUrl: 'https://license.example.com/widevine',
336
+ headers: { 'Authorization': 'Bearer YOUR_TOKEN' },
337
+ },
338
+ },
246
339
  }}
247
340
  />
248
341
  ```
249
342
 
343
+ ### Safari / iOS
344
+
345
+ 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.
346
+
347
+ ---
348
+
349
+ ## Event System
350
+
351
+ ```tsx
352
+ import { PlayerCore } from 'playron'
353
+
354
+ const player = new PlayerCore(videoElement)
355
+
356
+ player.eventEmitter.on('play', ({ timestamp }) => { })
357
+ player.eventEmitter.on('pause', ({ timestamp }) => { })
358
+ player.eventEmitter.on('ended', ({ timestamp }) => { })
359
+ player.eventEmitter.on('timeupdate', ({ currentTime, duration }) => { })
360
+ player.eventEmitter.on('volumechange',({ volume, isMuted }) => { })
361
+ player.eventEmitter.on('qualitychange',({ from, to }) => { })
362
+ player.eventEmitter.on('error', ({ code, message, details }) => { })
363
+ player.eventEmitter.on('ready', ({ player }) => { })
364
+ player.eventEmitter.on('destroy', ({ timestamp }) => { })
365
+
366
+ // One-time listener
367
+ player.eventEmitter.once('ready', ({ player }) => player.play())
368
+
369
+ // Remove listener
370
+ player.eventEmitter.off('play', handler)
371
+ ```
372
+
373
+ ### React Hooks
374
+
375
+ ```tsx
376
+ import { usePlayerState, usePlayerMethods, usePlayer } from 'playron'
377
+
378
+ function MyControls() {
379
+ const { isPlaying, volume, currentTime, duration, isLive, isBuffering } = usePlayerState()
380
+ const { play, pause, seekTo, setVolume, seekToLiveEdge } = usePlayerMethods()
381
+ const player = usePlayer() // raw PlayerCore instance
382
+ }
383
+ ```
384
+
385
+ ---
386
+
387
+ ## PlayerCore API
388
+
389
+ ```typescript
390
+ class PlayerCore {
391
+ // Playback
392
+ play(): Promise<void>
393
+ pause(): void
394
+ seekTo(time: number): void
395
+ setPlaybackRate(rate: number): void
396
+
397
+ // Volume
398
+ setVolume(volume: number): void // 0–1
399
+ toggleMute(): void
400
+
401
+ // Display
402
+ toggleFullscreen(): Promise<void>
403
+ togglePip(): Promise<void>
404
+ toggleTheaterMode(): void
405
+
406
+ // Stream
407
+ setSource(src: string): Promise<void>
408
+ getStreamType(): 'hls' | 'dash' | 'mp4' | 'unknown'
409
+ isLive(): boolean
410
+
411
+ // Live
412
+ seekToLiveEdge(): void
413
+ isAtLiveEdge(): boolean
414
+ getCurrentLatency(): number
415
+ getDVRRange(): { start: number; end: number } | null
416
+
417
+ // Quality / Audio / Subtitles
418
+ setQuality(id: string): void
419
+ getAvailableQualities(): QualityLevel[]
420
+ setAudioTrack(id: string): void
421
+ getAvailableAudioTracks(): AudioTrack[]
422
+
423
+ // Skip
424
+ skipForward(seconds?: number): void
425
+ skipBackward(seconds?: number): void
426
+
427
+ // DRM
428
+ setupDrm(config: DrmConfig): void
429
+
430
+ // Buffer
431
+ getBufferedRanges(): Array<{ start: number; end: number }>
432
+
433
+ // Cleanup
434
+ destroy(): void
435
+ }
436
+ ```
437
+
438
+ ---
439
+
440
+ ## Bundle Size
441
+
442
+ | File | Raw | Gzip |
443
+ |------|-----|------|
444
+ | `playron.es.js` | 2.1 MB | **559 KB** |
445
+ | `playron.cjs.js` | 1.6 MB | **490 KB** |
446
+ | `playron.css` | 5.9 KB | **2 KB** |
447
+
448
+ > 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.
449
+
450
+ ---
451
+
452
+ ## Framework Support
453
+
454
+ | Framework | Support | Notes |
455
+ |-----------|---------|-------|
456
+ | React 18 / 19 | ✅ Full | Hooks + Context API |
457
+ | Next.js App Router | ✅ Full | Add `'use client'` |
458
+ | Next.js Pages Router | ✅ Full | Use `dynamic(..., { ssr: false })` |
459
+ | Vite + React | ✅ Full | Reference dev environment |
460
+ | Remix | ✅ Full | Use `ClientOnly` wrapper |
461
+ | Astro | 🟡 Partial | `client:only="react"` |
462
+ | Vanilla JS | 🟡 Partial | Use `PlayerCore` directly |
463
+ | Vue / Nuxt | 🟡 Partial | Mount `PlayerCore` in `onMounted` |
464
+
465
+ ---
466
+
467
+ ## Browser Support
468
+
469
+ | Browser | HLS | DASH | Widevine | PlayReady | FairPlay |
470
+ |---------|-----|------|----------|-----------|---------|
471
+ | Chrome 90+ | ✅ | ✅ | ✅ | — | — |
472
+ | Firefox 88+ | ✅ | ✅ | ✅ | — | — |
473
+ | Edge 90+ | ✅ | ✅ | ✅ | ✅ | — |
474
+ | Safari 14+ | ✅ | ✅ | ❌ | — | ⏳ planned |
475
+ | iOS Safari 14+ | ✅ | ✅ | ❌ | — | ⏳ planned |
476
+ | Chrome Android | ✅ | ✅ | ✅ | — | — |
477
+
478
+ ---
479
+
250
480
  ## Development
251
481
 
252
482
  ```bash
253
- # Install dependencies
254
483
  npm install
255
484
 
256
- # Development mode
257
- npm run dev
485
+ npm run dev # HTTPS dev server (port 5173)
486
+ npm run build # TypeScript check + library build
487
+ npm run build:watch # Watch mode
488
+ npm run push # Build + yalc push to linked projects
489
+ npm run lint # ESLint
490
+ ```
491
+
492
+ > **HTTPS is required.** See `SSL-SETUP.md` for local certificate setup.
258
493
 
259
- # Build library
260
- npm run build
494
+ ---
261
495
 
262
- # Lint
263
- npm run lint
496
+ ## Roadmap
497
+
498
+ - [x] HLS engine (hls.js wrapper)
499
+ - [x] DASH engine (dash.js wrapper)
500
+ - [x] Widevine DRM — HLS + DASH
501
+ - [x] PlayReady DRM — DASH
502
+ - [x] AES-128 HLS decryption
503
+ - [x] VAST / VMAP ad system
504
+ - [x] WebVTT subtitles
505
+ - [x] CEA-608 / 708 closed captions
506
+ - [x] Live stream + DVR
507
+ - [x] LL-HLS / LL-DASH
508
+ - [x] Thumbnail storyboard preview
509
+ - [x] AirPlay
510
+ - [x] Analytics plugin
511
+ - [x] Stall detection + auto-recovery
512
+ - [x] DRM-aware error overlay
513
+ - [ ] **FairPlay** — Safari / iOS *(in progress)*
514
+ - [ ] Chromecast
515
+ - [ ] SSAI (Google DAI / AWS Elemental)
516
+ - [ ] TTML / IMSC subtitles
517
+ - [ ] Offline playback (Service Worker)
518
+ - [ ] 360° video (WebGL)
519
+
520
+ ---
521
+
522
+ ## Troubleshooting
523
+
524
+ ### `window is not defined` (Next.js)
525
+
526
+ **Cause:** Playron bundles hls.js and dash.js which access browser APIs at module evaluation time. Next.js pre-renders even `'use client'` components on the server for the initial HTML — `'use client'` alone is not sufficient.
527
+
528
+ **Fix:** Always wrap Playron in `dynamic()` with `ssr: false`:
529
+
530
+ ```tsx
531
+ // ❌ Wrong — causes "window is not defined" on the server
532
+ 'use client'
533
+ import { Player } from 'playron'
534
+
535
+ // ✅ Correct
536
+ 'use client'
537
+ import dynamic from 'next/dynamic'
538
+
539
+ const Player = dynamic(
540
+ () => import('playron').then(m => m.Player),
541
+ { ssr: false }
542
+ )
264
543
  ```
265
544
 
266
- ## Build Output
545
+ ---
267
546
 
268
- After building, the `dist/` folder will contain:
269
- - `playron.es.js` - ES Module (for modern projects)
270
- - `playron.umd.js` - UMD format (direct use in browser)
271
- - `playron.css` - Styles
272
- - `index.d.ts` - TypeScript definitions
547
+ ### `Module not found: Can't resolve 'playron/dist/playron.css'`
273
548
 
274
- ## Browser Support
549
+ **Cause:** The correct CSS export path is `playron/style.css`, not `playron/dist/playron.css`.
275
550
 
276
- - Chrome/Edge 90+
277
- - Firefox 88+
278
- - Safari 14+
279
- - Mobile browsers (iOS Safari 14+, Chrome Android 90+)
551
+ ```tsx
552
+ // Wrong
553
+ import 'playron/dist/playron.css'
280
554
 
281
- ## License
555
+ // ✅ Correct
556
+ import 'playron/style.css'
557
+ ```
282
558
 
283
- MIT
559
+ Both paths now work as of v1.0.3, but `playron/style.css` is the canonical import.
284
560
 
285
- ## Author
561
+ ---
562
+
563
+ ### Player renders but has no styles
564
+
565
+ Make sure you import the CSS **once** at the top level of your app (e.g. `layout.tsx`, `_app.tsx`, or `globals.css`):
566
+
567
+ ```tsx
568
+ // app/layout.tsx
569
+ import 'playron/style.css'
570
+ ```
571
+
572
+ ---
573
+
574
+ ### Video plays but DRM content fails on Safari
575
+
576
+ FairPlay (Safari/iOS) is not yet implemented. Playron will automatically show a DRM error overlay with browser recommendations when Widevine content is loaded in Safari. See the [DRM section](#drm-usage) for the current support matrix.
577
+
578
+ ---
579
+
580
+ ## License
286
581
 
287
- Suat Erkilic
582
+ MIT © Suat Erkilic
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playron",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Modern OTT video player for React, Next.js, and vanilla JS",
5
5
  "type": "module",
6
6
  "main": "./dist/playron.cjs.js",
@@ -12,8 +12,14 @@
12
12
  "import": "./dist/playron.es.js",
13
13
  "require": "./dist/playron.cjs.js"
14
14
  },
15
- "./style.css": "./dist/playron.css"
15
+ "./style.css": "./dist/playron.css",
16
+ "./dist/playron.css": "./dist/playron.css",
17
+ "./dist/*": "./dist/*"
16
18
  },
19
+ "sideEffects": [
20
+ "*.css",
21
+ "./dist/playron.css"
22
+ ],
17
23
  "files": [
18
24
  "dist"
19
25
  ],