@frameset/plex-player 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 FRAMESET Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,601 @@
1
+ <p align="center">
2
+ <img src="https://frameset.dev/favicon.svg" alt="FRAMESET" width="180" />
3
+ </p>
4
+
5
+ <h1 align="center">@frameset/plex-player</h1>
6
+
7
+ <p align="center">
8
+ <strong>🎬 Professional HTML5 Video Player for Modern Web Applications</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@frameset/plex-player">
13
+ <img src="https://img.shields.io/npm/v/@frameset/plex-player.svg?style=flat-square" alt="npm version" />
14
+ </a>
15
+ <a href="https://www.npmjs.com/package/@frameset/plex-player">
16
+ <img src="https://img.shields.io/npm/dm/@frameset/plex-player.svg?style=flat-square" alt="npm downloads" />
17
+ </a>
18
+ <a href="https://github.com/deadseti/plex-player/blob/main/LICENSE">
19
+ <img src="https://img.shields.io/npm/l/@frameset/plex-player.svg?style=flat-square" alt="license" />
20
+ </a>
21
+ <a href="https://bundlephobia.com/package/@frameset/plex-player">
22
+ <img src="https://img.shields.io/bundlephobia/minzip/@frameset/plex-player?style=flat-square" alt="bundle size" />
23
+ </a>
24
+ </p>
25
+
26
+ <p align="center">
27
+ <a href="#features">Features</a> •
28
+ <a href="#installation">Installation</a> •
29
+ <a href="#quick-start">Quick Start</a> •
30
+ <a href="#api">API</a> •
31
+ <a href="#frameworks">Frameworks</a> •
32
+ <a href="#ads">Ads</a> •
33
+ <a href="#chromecast">Chromecast</a> •
34
+ <a href="#license">License</a>
35
+ </p>
36
+
37
+ ---
38
+
39
+ ## ✨ Features
40
+
41
+ - 🎥 **Full-Featured Player** — Play, pause, seek, volume, playback rate, and more
42
+ - 📺 **Chromecast Support** — Cast videos to any Chromecast-enabled device
43
+ - 📋 **Playlist Management** — Queue management with thumbnail preview
44
+ - 🎨 **Customizable UI** — Fully themeable with CSS variables
45
+ - 📱 **Responsive Design** — Works perfectly on desktop, tablet, and mobile
46
+ - ⌨️ **Keyboard Shortcuts** — Full keyboard navigation support
47
+ - 👆 **Touch Gestures** — Swipe to seek, double-tap to skip
48
+ - 🖼️ **Picture-in-Picture** — Native PiP support
49
+ - 📺 **Fullscreen Mode** — True fullscreen with controls
50
+ - 📝 **Subtitles/Captions** — Multi-track subtitle support
51
+ - 💰 **VAST Ads** — VAST 2.0/3.0/4.0 video advertising
52
+ - 🔌 **Framework Support** — React, Vue 3, and Vanilla JS
53
+ - 📦 **TypeScript** — Full type definitions included
54
+ - 🌍 **i18n Ready** — Easy localization support
55
+
56
+ ---
57
+
58
+ ## 📦 Installation
59
+
60
+ ### NPM / Yarn / PNPM
61
+
62
+ ```bash
63
+ # npm
64
+ npm install @frameset/plex-player
65
+
66
+ # yarn
67
+ yarn add @frameset/plex-player
68
+
69
+ # pnpm
70
+ pnpm add @frameset/plex-player
71
+ ```
72
+
73
+ ### CDN
74
+
75
+ ```html
76
+ <!-- CSS -->
77
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@frameset/plex-player/dist/plex-player.min.css">
78
+
79
+ <!-- JavaScript -->
80
+ <script src="https://cdn.jsdelivr.net/npm/@frameset/plex-player/dist/plex-player.min.js"></script>
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 🚀 Quick Start
86
+
87
+ ### Vanilla JavaScript
88
+
89
+ ```html
90
+ <!DOCTYPE html>
91
+ <html>
92
+ <head>
93
+ <link rel="stylesheet" href="@frameset/plex-player/dist/plex-player.css">
94
+ </head>
95
+ <body>
96
+ <div id="player"></div>
97
+
98
+ <script src="@frameset/plex-player/dist/plex-player.min.js"></script>
99
+ <script>
100
+ const player = new PlexPlayer({
101
+ container: '#player',
102
+ autoplay: false,
103
+ muted: false,
104
+ });
105
+
106
+ player.load('https://example.com/video.mp4');
107
+ </script>
108
+ </body>
109
+ </html>
110
+ ```
111
+
112
+ ### ES Modules
113
+
114
+ ```javascript
115
+ import PlexPlayer from '@frameset/plex-player';
116
+ import '@frameset/plex-player/css';
117
+
118
+ const player = new PlexPlayer({
119
+ container: '#player',
120
+ autoplay: true,
121
+ });
122
+
123
+ player.load('https://example.com/video.mp4');
124
+ ```
125
+
126
+ ---
127
+
128
+ ## ⚛️ React
129
+
130
+ ```bash
131
+ npm install @frameset/plex-player react
132
+ ```
133
+
134
+ ```jsx
135
+ import { PlexPlayerReact, usePlexPlayer } from '@frameset/plex-player/react';
136
+ import '@frameset/plex-player/css';
137
+
138
+ function App() {
139
+ const playerRef = useRef(null);
140
+
141
+ const handlePlay = () => {
142
+ console.log('Video started playing');
143
+ };
144
+
145
+ const handleTimeUpdate = (currentTime) => {
146
+ console.log('Current time:', currentTime);
147
+ };
148
+
149
+ return (
150
+ <PlexPlayerReact
151
+ ref={playerRef}
152
+ src="https://example.com/video.mp4"
153
+ autoplay={false}
154
+ onPlay={handlePlay}
155
+ onTimeUpdate={handleTimeUpdate}
156
+ />
157
+ );
158
+ }
159
+ ```
160
+
161
+ ### Hooks
162
+
163
+ ```jsx
164
+ import {
165
+ PlexPlayerReact,
166
+ PlexPlayerProvider,
167
+ usePlexPlayer,
168
+ usePlexPlayerState,
169
+ usePlexPlayerTime
170
+ } from '@frameset/plex-player/react';
171
+
172
+ function Controls() {
173
+ const player = usePlexPlayer();
174
+ const state = usePlexPlayerState();
175
+ const { current, duration } = usePlexPlayerTime();
176
+
177
+ return (
178
+ <div>
179
+ <button onClick={() => player?.togglePlay()}>
180
+ {state?.isPlaying ? 'Pause' : 'Play'}
181
+ </button>
182
+ <span>{current}s / {duration}s</span>
183
+ </div>
184
+ );
185
+ }
186
+
187
+ function App() {
188
+ return (
189
+ <PlexPlayerProvider>
190
+ <PlexPlayerReact src="video.mp4" />
191
+ <Controls />
192
+ </PlexPlayerProvider>
193
+ );
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ## 💚 Vue 3
200
+
201
+ ```bash
202
+ npm install @frameset/plex-player vue
203
+ ```
204
+
205
+ ```vue
206
+ <template>
207
+ <PlexPlayerVue
208
+ ref="player"
209
+ src="https://example.com/video.mp4"
210
+ :autoplay="false"
211
+ @play="onPlay"
212
+ @timeupdate="onTimeUpdate"
213
+ />
214
+ </template>
215
+
216
+ <script setup>
217
+ import { ref } from 'vue';
218
+ import { PlexPlayerVue } from '@frameset/plex-player/vue';
219
+ import '@frameset/plex-player/css';
220
+
221
+ const player = ref(null);
222
+
223
+ const onPlay = () => {
224
+ console.log('Video started playing');
225
+ };
226
+
227
+ const onTimeUpdate = (currentTime) => {
228
+ console.log('Current time:', currentTime);
229
+ };
230
+
231
+ // Control player
232
+ const togglePlay = () => {
233
+ player.value?.togglePlay();
234
+ };
235
+ </script>
236
+ ```
237
+
238
+ ### Composables
239
+
240
+ ```vue
241
+ <script setup>
242
+ import { PlexPlayerVue, usePlexPlayer, usePlexPlayerTime } from '@frameset/plex-player/vue';
243
+
244
+ const player = usePlexPlayer();
245
+ const time = usePlexPlayerTime();
246
+ </script>
247
+
248
+ <template>
249
+ <PlexPlayerVue src="video.mp4" />
250
+ <div>{{ time.current }} / {{ time.duration }}</div>
251
+ </template>
252
+ ```
253
+
254
+ ---
255
+
256
+ ## 📖 API Reference
257
+
258
+ ### Constructor Options
259
+
260
+ ```typescript
261
+ interface PlexPlayerOptions {
262
+ container: string | HTMLElement; // Required: container selector or element
263
+ autoplay?: boolean; // Auto-play video (default: false)
264
+ muted?: boolean; // Start muted (default: false)
265
+ loop?: boolean; // Loop video (default: false)
266
+ volume?: number; // Initial volume 0-1 (default: 1)
267
+ poster?: string; // Poster image URL
268
+ preload?: 'none' | 'metadata' | 'auto'; // Preload behavior (default: 'metadata')
269
+ keyboard?: boolean; // Enable keyboard shortcuts (default: true)
270
+ touch?: boolean; // Enable touch gestures (default: true)
271
+ pip?: boolean; // Enable PiP button (default: true)
272
+ cast?: boolean; // Enable Chromecast (default: true)
273
+ fullscreen?: boolean; // Enable fullscreen (default: true)
274
+ controlsHideDelay?: number; // Hide controls after ms (default: 3000)
275
+ theme?: ThemeOptions; // Custom theme colors
276
+ subtitles?: SubtitleOptions; // Subtitle configuration
277
+ ads?: AdsOptions; // VAST ads configuration
278
+ i18n?: I18nOptions; // Localization strings
279
+ }
280
+ ```
281
+
282
+ ### Methods
283
+
284
+ | Method | Description | Returns |
285
+ |--------|-------------|---------|
286
+ | `load(src)` | Load a video URL | `void` |
287
+ | `loadPlaylist(items)` | Load a playlist | `void` |
288
+ | `play()` | Start playback | `Promise<void>` |
289
+ | `pause()` | Pause playback | `void` |
290
+ | `togglePlay()` | Toggle play/pause | `void` |
291
+ | `seek(time)` | Seek to time in seconds | `void` |
292
+ | `seekPercent(percent)` | Seek to percentage (0-100) | `void` |
293
+ | `setVolume(level)` | Set volume (0-1) | `void` |
294
+ | `getVolume()` | Get current volume | `number` |
295
+ | `mute()` | Mute audio | `void` |
296
+ | `unmute()` | Unmute audio | `void` |
297
+ | `toggleMute()` | Toggle mute | `void` |
298
+ | `setPlaybackRate(rate)` | Set playback speed | `void` |
299
+ | `enterFullscreen()` | Enter fullscreen | `Promise<void>` |
300
+ | `exitFullscreen()` | Exit fullscreen | `Promise<void>` |
301
+ | `toggleFullscreen()` | Toggle fullscreen | `void` |
302
+ | `enterPiP()` | Enter Picture-in-Picture | `Promise<void>` |
303
+ | `exitPiP()` | Exit Picture-in-Picture | `Promise<void>` |
304
+ | `togglePiP()` | Toggle PiP | `void` |
305
+ | `cast()` | Start Chromecast | `void` |
306
+ | `next()` | Play next in playlist | `void` |
307
+ | `previous()` | Play previous in playlist | `void` |
308
+ | `playAt(index)` | Play specific playlist item | `void` |
309
+ | `getState()` | Get current player state | `PlayerState` |
310
+ | `destroy()` | Destroy player instance | `void` |
311
+
312
+ ### Events
313
+
314
+ ```javascript
315
+ player.on('play', () => { /* Video started */ });
316
+ player.on('pause', () => { /* Video paused */ });
317
+ player.on('ended', () => { /* Video ended */ });
318
+ player.on('timeupdate', ({ currentTime, duration }) => { /* Time changed */ });
319
+ player.on('progress', ({ buffered }) => { /* Buffer progress */ });
320
+ player.on('volumechange', ({ volume, muted }) => { /* Volume changed */ });
321
+ player.on('fullscreenchange', ({ isFullscreen }) => { /* Fullscreen toggled */ });
322
+ player.on('error', (error) => { /* Error occurred */ });
323
+ player.on('ready', () => { /* Player ready */ });
324
+ player.on('trackchange', ({ index, item }) => { /* Playlist track changed */ });
325
+ ```
326
+
327
+ ---
328
+
329
+ ## 💰 VAST Ads
330
+
331
+ Plex Player supports VAST 2.0, 3.0, and 4.0 video advertising standards.
332
+
333
+ ```javascript
334
+ const player = new PlexPlayer({
335
+ container: '#player',
336
+ ads: {
337
+ enabled: true,
338
+ tagUrl: 'https://your-ad-server.com/vast.xml',
339
+ skipOffset: 5, // Allow skip after 5 seconds
340
+ timeout: 10000, // Ad request timeout in ms
341
+ },
342
+ });
343
+
344
+ // Listen to ad events
345
+ player.on('adstart', (ad) => {
346
+ console.log('Ad started:', ad);
347
+ });
348
+
349
+ player.on('adend', () => {
350
+ console.log('Ad finished');
351
+ });
352
+
353
+ player.on('adskip', () => {
354
+ console.log('Ad skipped');
355
+ });
356
+
357
+ player.on('aderror', (error) => {
358
+ console.log('Ad error:', error);
359
+ });
360
+ ```
361
+
362
+ ### Ad Options
363
+
364
+ | Option | Type | Default | Description |
365
+ |--------|------|---------|-------------|
366
+ | `enabled` | `boolean` | `false` | Enable ads |
367
+ | `tagUrl` | `string` | — | VAST tag URL |
368
+ | `skipOffset` | `number` | `5` | Seconds before skip allowed |
369
+ | `timeout` | `number` | `10000` | Request timeout in ms |
370
+ | `maxRedirects` | `number` | `5` | Maximum VAST redirects |
371
+
372
+ ---
373
+
374
+ ## 📺 Chromecast
375
+
376
+ Chromecast is enabled by default. The cast button appears automatically when a Chromecast device is available on the network.
377
+
378
+ ```javascript
379
+ const player = new PlexPlayer({
380
+ container: '#player',
381
+ cast: true, // Enable Chromecast (default)
382
+ });
383
+
384
+ // Manual cast
385
+ player.cast();
386
+
387
+ // Listen to cast events
388
+ player.on('castconnected', ({ deviceName }) => {
389
+ console.log('Connected to:', deviceName);
390
+ });
391
+
392
+ player.on('castdisconnected', () => {
393
+ console.log('Disconnected from Chromecast');
394
+ });
395
+
396
+ player.on('caststatechange', ({ state }) => {
397
+ console.log('Cast state:', state);
398
+ });
399
+ ```
400
+
401
+ ### Requirements
402
+
403
+ - Chromecast requires HTTPS in production
404
+ - Works in Chrome and Edge browsers
405
+ - Cast SDK is loaded automatically
406
+
407
+ ---
408
+
409
+ ## 🎨 Theming
410
+
411
+ Customize the player appearance using CSS variables:
412
+
413
+ ```css
414
+ :root {
415
+ --plex-primary: #e5a00d; /* Primary accent color */
416
+ --plex-bg: rgba(0, 0, 0, 0.8); /* Background color */
417
+ --plex-text: #ffffff; /* Text color */
418
+ --plex-progress-bg: #3a3a3a; /* Progress bar background */
419
+ --plex-progress: #e5a00d; /* Progress bar fill */
420
+ --plex-buffered: #6a6a6a; /* Buffered area color */
421
+ --plex-control-size: 40px; /* Control button size */
422
+ --plex-border-radius: 4px; /* Border radius */
423
+ }
424
+ ```
425
+
426
+ Or via JavaScript:
427
+
428
+ ```javascript
429
+ const player = new PlexPlayer({
430
+ container: '#player',
431
+ theme: {
432
+ primary: '#e5a00d',
433
+ background: 'rgba(0, 0, 0, 0.8)',
434
+ text: '#ffffff',
435
+ borderRadius: '8px',
436
+ },
437
+ });
438
+ ```
439
+
440
+ ---
441
+
442
+ ## ⌨️ Keyboard Shortcuts
443
+
444
+ | Key | Action |
445
+ |-----|--------|
446
+ | `Space` / `K` | Toggle play/pause |
447
+ | `←` | Rewind 10 seconds |
448
+ | `→` | Forward 10 seconds |
449
+ | `↑` | Volume up |
450
+ | `↓` | Volume down |
451
+ | `M` | Toggle mute |
452
+ | `F` | Toggle fullscreen |
453
+ | `P` | Toggle Picture-in-Picture |
454
+ | `C` | Toggle captions |
455
+ | `N` | Next track |
456
+ | `Shift + N` | Previous track |
457
+ | `0-9` | Seek to 0%-90% |
458
+ | `Home` | Go to beginning |
459
+ | `End` | Go to end |
460
+
461
+ ---
462
+
463
+ ## 🌍 Localization (i18n)
464
+
465
+ ```javascript
466
+ const player = new PlexPlayer({
467
+ container: '#player',
468
+ i18n: {
469
+ play: 'Play',
470
+ pause: 'Pause',
471
+ mute: 'Mute',
472
+ unmute: 'Unmute',
473
+ fullscreen: 'Fullscreen',
474
+ exitFullscreen: 'Exit Fullscreen',
475
+ pip: 'Picture-in-Picture',
476
+ cast: 'Cast',
477
+ settings: 'Settings',
478
+ speed: 'Speed',
479
+ quality: 'Quality',
480
+ subtitles: 'Subtitles',
481
+ off: 'Off',
482
+ },
483
+ });
484
+ ```
485
+
486
+ ---
487
+
488
+ ## 📋 Playlist
489
+
490
+ ```javascript
491
+ const player = new PlexPlayer({
492
+ container: '#player',
493
+ });
494
+
495
+ // Load playlist
496
+ player.loadPlaylist([
497
+ {
498
+ src: 'video1.mp4',
499
+ title: 'Episode 1',
500
+ poster: 'thumb1.jpg',
501
+ duration: 3600,
502
+ },
503
+ {
504
+ src: 'video2.mp4',
505
+ title: 'Episode 2',
506
+ poster: 'thumb2.jpg',
507
+ duration: 3540,
508
+ },
509
+ ]);
510
+
511
+ // Navigation
512
+ player.next();
513
+ player.previous();
514
+ player.playAt(2);
515
+ ```
516
+
517
+ ---
518
+
519
+ ## 📝 Subtitles
520
+
521
+ ```javascript
522
+ const player = new PlexPlayer({
523
+ container: '#player',
524
+ subtitles: {
525
+ default: 'en',
526
+ tracks: [
527
+ { label: 'English', srclang: 'en', src: 'subs/en.vtt' },
528
+ { label: 'Spanish', srclang: 'es', src: 'subs/es.vtt' },
529
+ { label: 'French', srclang: 'fr', src: 'subs/fr.vtt' },
530
+ ],
531
+ style: {
532
+ fontSize: '20px',
533
+ color: '#ffffff',
534
+ background: 'rgba(0, 0, 0, 0.75)',
535
+ },
536
+ },
537
+ });
538
+ ```
539
+
540
+ ---
541
+
542
+ ## 📊 Browser Support
543
+
544
+ | Browser | Version |
545
+ |---------|---------|
546
+ | Chrome | 60+ |
547
+ | Firefox | 55+ |
548
+ | Safari | 11+ |
549
+ | Edge | 79+ |
550
+ | Opera | 47+ |
551
+ | iOS Safari | 11+ |
552
+ | Chrome Android | 60+ |
553
+
554
+ ---
555
+
556
+ ## 🔧 Development
557
+
558
+ ```bash
559
+ # Clone repository
560
+ git clone https://github.com/frameset-studio/plex-player.git
561
+ cd plex-player
562
+
563
+ # Install dependencies
564
+ npm install
565
+
566
+ # Start development server
567
+ npm run dev
568
+
569
+ # Build for production
570
+ npm run build
571
+
572
+ # Run tests
573
+ npm test
574
+
575
+ # Lint code
576
+ npm run lint
577
+ ```
578
+
579
+ ---
580
+
581
+ ## 📄 License
582
+
583
+ MIT © [FRAMESET Studio](https://frameset.dev)
584
+
585
+ ---
586
+
587
+ <p align="center">
588
+ <a href="https://frameset.dev">
589
+ <img src="https://frameset.dev/logo-dark.svg" alt="FRAMESET" width="120" />
590
+ </a>
591
+ </p>
592
+
593
+ <p align="center">
594
+ Made with ❤️ by <a href="https://frameset.dev">FRAMESET Studio</a>
595
+ </p>
596
+
597
+ <p align="center">
598
+ <a href="https://www.linkedin.com/company/framesetdev/">LinkedIn</a> •
599
+ <a href="https://github.com/deadseti">GitHub</a> •
600
+ <a href="https://frameset.dev">Website</a>
601
+ </p>