stormcloud-video-player 0.8.59 → 0.8.60

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 (79) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1369 -1369
  3. package/dist/stormcloud-vp.min.js +3 -1
  4. package/lib/Player.cjs +548 -0
  5. package/lib/Player.cjs.map +1 -0
  6. package/lib/Player.d.cts +146 -0
  7. package/lib/{index.js → StormcloudPlayer.cjs} +1108 -1877
  8. package/lib/StormcloudPlayer.cjs.map +1 -0
  9. package/lib/{index.d.ts → StormcloudPlayer.d.cts} +7 -400
  10. package/lib/index.cjs +979 -251
  11. package/lib/index.cjs.map +1 -1
  12. package/lib/index.d.cts +18 -4032
  13. package/lib/patterns.cjs +111 -0
  14. package/lib/patterns.cjs.map +1 -0
  15. package/lib/patterns.d.cts +14 -0
  16. package/lib/player/AdBreakOrchestrator.cjs +48 -14
  17. package/lib/player/AdBreakOrchestrator.cjs.map +1 -1
  18. package/lib/player/AdBreakOrchestrator.d.cts +3 -1
  19. package/lib/player/AdConfigManager.cjs +155 -17
  20. package/lib/player/AdConfigManager.cjs.map +1 -1
  21. package/lib/player/AdConfigManager.d.cts +1 -1
  22. package/lib/player/AdTimingService.cjs.map +1 -1
  23. package/lib/player/AdTimingService.d.cts +1 -1
  24. package/lib/player/HlsEngine.cjs.map +1 -1
  25. package/lib/player/HlsEngine.d.cts +1 -1
  26. package/lib/player/PlaceholderLayer.cjs.map +1 -1
  27. package/lib/player/PlayerControls.cjs.map +1 -1
  28. package/lib/player/PlayerControls.d.cts +1 -1
  29. package/lib/player/Scte35CueManager.cjs.map +1 -1
  30. package/lib/player/Scte35CueManager.d.cts +1 -1
  31. package/lib/player/Scte35Parser.cjs.map +1 -1
  32. package/lib/player/Scte35Parser.d.cts +1 -1
  33. package/lib/player/StormcloudVideoPlayer.cjs +979 -251
  34. package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
  35. package/lib/player/StormcloudVideoPlayer.d.cts +1 -1
  36. package/lib/player/playerTypes.cjs.map +1 -1
  37. package/lib/player/playerTypes.d.cts +1 -1
  38. package/lib/players/FilePlayer.cjs.map +1 -1
  39. package/lib/players/HlsPlayer.cjs +979 -251
  40. package/lib/players/HlsPlayer.cjs.map +1 -1
  41. package/lib/players/HlsPlayer.d.cts +1 -1
  42. package/lib/players/index.cjs +979 -251
  43. package/lib/players/index.cjs.map +1 -1
  44. package/lib/props.cjs +186 -0
  45. package/lib/props.cjs.map +1 -0
  46. package/lib/props.d.cts +62 -0
  47. package/lib/sdk/hlsAdPlayer.cjs +884 -234
  48. package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
  49. package/lib/sdk/hlsAdPlayer.d.cts +1 -1
  50. package/lib/sdk/pal.cjs.map +1 -1
  51. package/lib/types.cjs +49 -0
  52. package/lib/types.cjs.map +1 -0
  53. package/lib/{types-52ASml2N.d.cts → types.d.cts} +47 -1
  54. package/lib/ui/StormcloudVideoPlayer.cjs +979 -251
  55. package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
  56. package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
  57. package/lib/utils/browserCompat.cjs.map +1 -1
  58. package/lib/utils/ctvVastSignals.cjs +155 -17
  59. package/lib/utils/ctvVastSignals.cjs.map +1 -1
  60. package/lib/utils/devUrl.cjs.map +1 -1
  61. package/lib/utils/polyfills.cjs.map +1 -1
  62. package/lib/utils/tracking.cjs +42 -14
  63. package/lib/utils/tracking.cjs.map +1 -1
  64. package/lib/utils/tracking.d.cts +1 -1
  65. package/lib/utils/trackingIngest.cjs.map +1 -1
  66. package/lib/utils/vastEnvironmentSignals.cjs +167 -17
  67. package/lib/utils/vastEnvironmentSignals.cjs.map +1 -1
  68. package/lib/utils/vastEnvironmentSignals.d.cts +12 -1
  69. package/lib/utils/vastMacros.cjs +26 -0
  70. package/lib/utils/vastMacros.cjs.map +1 -1
  71. package/lib/utils/vastTrackingMacros.cjs +5 -0
  72. package/lib/utils/vastTrackingMacros.cjs.map +1 -1
  73. package/lib/utils/vastTrackingMacros.d.cts +2 -0
  74. package/lib/utils.cjs +292 -0
  75. package/lib/utils.cjs.map +1 -0
  76. package/lib/utils.d.cts +17 -0
  77. package/package.json +91 -91
  78. package/rollup.config.js +90 -90
  79. package/lib/index.js.map +0 -1
package/README.md CHANGED
@@ -1,1369 +1,1369 @@
1
- # Stormcloud Video Player
2
-
3
- A professional video player with advanced ad integration for web applications. Built with precision ad break alignment, SCTE-35 signal parsing, VMAP ad-break scheduling, and a native HLS ad player for VAST-based ad playback. Now featuring a modern, extensible architecture inspired by react-player.
4
-
5
- ## 🎯 Key Features
6
-
7
- - **Multi-Format Support**: Automatic detection and playback of HLS streams and regular video files
8
- - **Precision Ad Alignment**: Tight synchronization with SCTE-35 CUE-OUT signals
9
- - **VMAP 1.0 Support**: Schedule pre-roll, mid-roll, and post-roll ad breaks via a VMAP manifest
10
- - **Smart Mid-Roll Handling**: Automatic detection and playback of remaining ad portions when joining late
11
- - **Flexible Ad Scheduling**: Support for SCTE-35 markers, VMAP manifests, and external ad schedules
12
- - **Enhanced UI Controls**: Beautiful, adaptive video controls that work on any background color
13
- - **Live Mode Support**: Specialized controls for live streaming with volume adjustment
14
- - **Cross-Platform & Smart TV Ready**: Desktop, mobile, tablets, LG WebOS, Samsung Tizen, Sony BRAVIA, Android TV, Roku, Apple TV
15
- - **Automatic Browser Compatibility**: Built-in browser detection, polyfills, and Smart TV playback overrides (e.g. native HLS)
16
- - **React Ready**: Multiple React components for different use cases
17
- - **TypeScript Support**: Full type definitions included
18
- - **Professional Architecture**: Modular player system with lazy loading
19
-
20
- ## 🚀 Quick Start
21
-
22
- ### Installation
23
-
24
- ```bash
25
- npm install stormcloud-video-player hls.js
26
- ```
27
-
28
- ### React Integration
29
-
30
- #### Option 1: Legacy Component (Recommended for existing projects)
31
-
32
- ```jsx
33
- import React from "react";
34
- import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";
35
-
36
- function MyVideoApp() {
37
- return (
38
- <StormcloudVideoPlayerComponent
39
- src="https://your-stream.com/playlist.m3u8"
40
- autoplay={true}
41
- muted={true}
42
- controls={true}
43
- showCustomControls={true} // Enable enhanced UI controls
44
- hideLoadingIndicator={false} // Hide the built-in loading spinner
45
- allowNativeHls={true} // Allow native HLS for better performance
46
- licenseKey="your_license_key_here"
47
- vastMode="adstorm" // AdStorm VAST endpoint resolution
48
- isVmap={true}
49
- vmapUrl="https://your-cdn.com/ads.vmap" // Optional VMAP manifest for scheduled breaks
50
- style={{ width: "100%", aspectRatio: "16/9" }}
51
- wrapperStyle={{ borderRadius: "12px", overflow: "hidden" }}
52
- onReady={(player) => {
53
- console.log("Player is ready!", player);
54
- }}
55
- onVolumeToggle={() => {
56
- console.log("Volume toggled");
57
- }}
58
- onFullscreenToggle={() => {
59
- console.log("Fullscreen toggled");
60
- }}
61
- />
62
- );
63
- }
64
- ```
65
-
66
- #### Option 2: New Professional Component (For new projects)
67
-
68
- ```jsx
69
- import React from "react";
70
- import StormcloudPlayer from "stormcloud-video-player";
71
-
72
- function MyVideoApp() {
73
- return (
74
- <StormcloudPlayer
75
- src="https://your-stream.com/playlist.m3u8"
76
- playing={true}
77
- muted={true}
78
- controls={true}
79
- width="100%"
80
- height="auto"
81
- style={{ aspectRatio: "16/9" }}
82
- // Stormcloud-specific props
83
- allowNativeHls={true}
84
- showCustomControls={true}
85
- hideLoadingIndicator={false}
86
- licenseKey="your_license_key_here"
87
- vastMode="adstorm" // Use AdStorm mode (or omit for default mode)
88
- vmapUrl="https://your-cdn.com/ads.vmap" // Optional: VMAP manifest URL
89
- onReady={(player) => {
90
- console.log("Player is ready!", player);
91
- }}
92
- onPlay={() => console.log("Playing")}
93
- onPause={() => console.log("Paused")}
94
- onProgress={(state) => console.log("Progress:", state)}
95
- />
96
- );
97
- }
98
- ```
99
-
100
- #### Option 3: Specific Player Types (Optimized bundles)
101
-
102
- ```jsx
103
- // For HLS streams only (smaller bundle)
104
- import StormcloudPlayer from "stormcloud-video-player/hls";
105
-
106
- // For regular video files only (smallest bundle)
107
- import StormcloudPlayer from "stormcloud-video-player/file";
108
- ```
109
-
110
- ### Vanilla JavaScript
111
-
112
- ```javascript
113
- import { StormcloudVideoPlayer } from "stormcloud-video-player";
114
-
115
- const video = document.getElementById("my-video");
116
-
117
- const player = new StormcloudVideoPlayer({
118
- videoElement: video,
119
- src: "https://your-stream.com/playlist.m3u8",
120
- autoplay: true,
121
- muted: true,
122
- allowNativeHls: true, // Enable native HLS when supported
123
- showCustomControls: true, // Enable enhanced UI controls
124
- hideLoadingIndicator: false, // Hide built-in loading spinner
125
- lowLatencyMode: false, // Set to true for live streams
126
- driftToleranceMs: 3000, // Drift tolerance for live streams
127
- licenseKey: "your_license_key_here",
128
- vastMode: "default", // "adstorm" | "default"
129
- vmapUrl: "https://your-cdn.com/ads.vmap", // Optional VMAP manifest for scheduled ad breaks
130
- onVolumeToggle: () => console.log("Volume toggled"),
131
- onFullscreenToggle: () => console.log("Fullscreen toggled"),
132
- });
133
-
134
- await player.load();
135
- ```
136
-
137
- ### CDN Usage
138
-
139
- ```html
140
- <script src="https://cdn.jsdelivr.net/npm/stormcloud-video-player/dist/stormcloud-vp.min.js"></script>
141
-
142
- <script>
143
- const { StormcloudVideoPlayer } = window.StormcloudVP;
144
-
145
- const video = document.getElementById("video");
146
- const player = new StormcloudVideoPlayer({
147
- videoElement: video,
148
- src: "https://your-stream.com/playlist.m3u8",
149
- autoplay: true,
150
- muted: true,
151
- });
152
-
153
- player.load();
154
- </script>
155
- ```
156
-
157
- ## 🏗️ Professional Architecture
158
-
159
- The Stormcloud Video Player now follows a professional, modular architecture similar to react-player:
160
-
161
- ### Player System Overview
162
-
163
- ```
164
- StormcloudPlayer (Main Component)
165
- ├── Player (Internal Wrapper)
166
- ├── HlsPlayer (HLS Stream Handler)
167
- ├── FilePlayer (Regular Video Handler)
168
- └── Legacy StormcloudVideoPlayerComponent
169
- ```
170
-
171
- ### What Each Player Does
172
-
173
- #### 🎬 **HlsPlayer** - HLS Stream Specialist
174
-
175
- - **Purpose**: Handles HLS (.m3u8) streams with advanced features
176
- - **Features**:
177
- - SCTE-35 ad marker detection and processing
178
- - HLS ad player with VAST XML parsing (HLS and progressive media files)
179
- - Live stream support with low-latency mode
180
- - Drift correction for live timing
181
- - Manifest-based and ID3 ad markers
182
- - Ad failsafe and recovery mechanisms
183
-
184
- ```jsx
185
- // Automatically used for HLS streams
186
- <StormcloudPlayer src="https://example.com/stream.m3u8" />
187
- ```
188
-
189
- #### 📹 **FilePlayer** - Regular Video Handler
190
-
191
- - **Purpose**: Handles regular video files (MP4, WebM, etc.)
192
- - **Features**:
193
- - Direct video element control
194
- - Picture-in-Picture support
195
- - Standard HTML5 video features
196
- - Lightweight and fast
197
- - No HLS.js dependency
198
-
199
- ```jsx
200
- // Automatically used for regular video files
201
- <StormcloudPlayer src="https://example.com/video.mp4" />
202
- ```
203
-
204
- ### Automatic Player Selection
205
-
206
- The system automatically chooses the right player based on your video source:
207
-
208
- ```javascript
209
- // HLS streams → HlsPlayer
210
- "https://example.com/playlist.m3u8"; // Uses HlsPlayer
211
- "https://example.com/stream/index.m3u8"; // Uses HlsPlayer
212
-
213
- // Regular videos → FilePlayer
214
- "https://example.com/video.mp4"; // Uses FilePlayer
215
- "https://example.com/video.webm"; // Uses FilePlayer
216
- "https://example.com/video.mov"; // Uses FilePlayer
217
- ```
218
-
219
- ### Bundle Optimization
220
-
221
- Import only what you need for smaller bundles:
222
-
223
- ```javascript
224
- // Full player (auto-detection) - ~140KB
225
- import StormcloudPlayer from "stormcloud-video-player";
226
-
227
- // HLS only - ~120KB
228
- import StormcloudPlayer from "stormcloud-video-player/hls";
229
-
230
- // File only - ~80KB
231
- import StormcloudPlayer from "stormcloud-video-player/file";
232
-
233
- // Legacy component - Full features
234
- import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";
235
- ```
236
-
237
- ## 📖 API Reference
238
-
239
- ### StormcloudPlayer Component (New)
240
-
241
- #### Props
242
-
243
- ```typescript
244
- interface StormcloudPlayerProps {
245
- // Media source
246
- src?: string;
247
-
248
- // Playback control
249
- playing?: boolean;
250
- loop?: boolean;
251
- controls?: boolean;
252
- volume?: number;
253
- muted?: boolean;
254
- playbackRate?: number;
255
-
256
- // Styling
257
- width?: string | number;
258
- height?: string | number;
259
- style?: CSSProperties;
260
- className?: string;
261
- wrapperClassName?: string;
262
- wrapperStyle?: CSSProperties;
263
-
264
- // Video attributes
265
- playsInline?: boolean;
266
- autoplay?: boolean;
267
- preload?: string;
268
- poster?: string;
269
-
270
- // Stormcloud-specific
271
- allowNativeHls?: boolean;
272
- lowLatencyMode?: boolean;
273
- driftToleranceMs?: number;
274
- immediateManifestAds?: boolean;
275
- debugAdTiming?: boolean;
276
- showCustomControls?: boolean;
277
- hideLoadingIndicator?: boolean; // Hide the built-in loading spinner (default: false)
278
- licenseKey?: string;
279
- adFailsafeTimeoutMs?: number;
280
- minSegmentsBeforePlay?: number; // Number of segments to buffer before starting playback (default: 2)
281
-
282
- // Ad player configuration
283
- vastMode?: 'adstorm' | 'default'; // VAST endpoint resolution: 'adstorm' (/vast/{licenseKey}) or 'default' (/ads/web or vastTagUrl) (default: 'default')
284
- vastTagUrl?: string; // Custom VAST tag URL (default mode; when omitted, /ads/web supplies the tag URL)
285
- isVmap?: boolean; // Enable VMAP manifest scheduling (requires vmapUrl)
286
- vmapUrl?: string; // VMAP 1.0 manifest URL for pre/mid/post-roll ad breaks (requires isVmap: true)
287
-
288
- // Event handlers
289
- onReady?: (player: StormcloudVideoPlayer) => void;
290
- onStart?: () => void;
291
- onPlay?: () => void;
292
- onPause?: () => void;
293
- onBuffer?: () => void;
294
- onBufferEnd?: () => void;
295
- onEnded?: () => void;
296
- onError?: (
297
- error: any,
298
- data?: any,
299
- hlsInstance?: any,
300
- hlsGlobal?: any
301
- ) => void;
302
- onDuration?: (duration: number) => void;
303
- onSeek?: (seconds: number) => void;
304
- onProgress?: (state: {
305
- played: number;
306
- playedSeconds: number;
307
- loaded: number;
308
- loadedSeconds: number;
309
- }) => void;
310
- onVolumeToggle?: () => void;
311
- onFullscreenToggle?: () => void;
312
- onControlClick?: () => void;
313
- }
314
- ```
315
-
316
- #### Methods
317
-
318
- ```typescript
319
- // Player instance methods (available via ref or onReady)
320
- player.seekTo(amount: number, type?: 'seconds' | 'fraction')
321
- player.getCurrentTime(): number | null
322
- player.getSecondsLoaded(): number | null
323
- player.getDuration(): number | null
324
- player.getInternalPlayer(key?: string): any
325
- ```
326
-
327
- ### StormcloudVideoPlayer Class (Core)
328
-
329
- #### Constructor
330
-
331
- ```typescript
332
- new StormcloudVideoPlayer(config: StormcloudVideoPlayerConfig)
333
- ```
334
-
335
- #### Methods
336
-
337
- | Method | Description | Returns |
338
- | ---------------------------- | --------------------------------------------- | ------------------ |
339
- | `load()` | Initialize and start video playback | `Promise<void>` |
340
- | `destroy()` | Clean up player resources and event listeners | `void` |
341
- | `toggleMute()` | Toggle video mute state | `void` |
342
- | `toggleFullscreen()` | Enter/exit fullscreen mode | `Promise<void>` |
343
- | `isMuted()` | Check if video is currently muted | `boolean` |
344
- | `isFullscreen()` | Check if player is in fullscreen mode | `boolean` |
345
- | `isAdPlaying()` | Check if an ad is currently playing | `boolean` |
346
- | `isShowingAds()` | Check if ads are being shown | `boolean` |
347
- | `getCurrentAdIndex()` | Get current ad index in pod | `number` |
348
- | `getTotalAdsInBreak()` | Get total ads in current break | `number` |
349
- | `shouldShowNativeControls()` | Check if native controls should be shown | `boolean` |
350
- | `getStreamType()` | Get detected stream type | `'hls' \| 'other'` |
351
-
352
- #### Configuration Options
353
-
354
- ```typescript
355
- interface StormcloudVideoPlayerConfig {
356
- videoElement: HTMLVideoElement; // Target video element
357
- src: string; // Stream URL (HLS or regular video)
358
- autoplay?: boolean; // Auto-start playback (default: false)
359
- muted?: boolean; // Start muted (default: false)
360
- allowNativeHls?: boolean; // Use native HLS when available (default: false)
361
- showCustomControls?: boolean; // Enable enhanced UI controls (default: false)
362
- hideLoadingIndicator?: boolean; // Hide the built-in loading spinner (default: false)
363
- lowLatencyMode?: boolean; // Enable low-latency mode for live streams (default: false)
364
- driftToleranceMs?: number; // Drift tolerance for live streams (default: 1000)
365
- immediateManifestAds?: boolean; // Load ads immediately from manifest (default: true)
366
- licenseKey?: string; // API authentication key
367
- debugAdTiming?: boolean; // Enable debug logging (default: false)
368
- adFailsafeTimeoutMs?: number; // Ad timeout in milliseconds (default: 10000)
369
- minSegmentsBeforePlay?: number; // Number of segments to buffer before starting playback (default: 2)
370
-
371
- // Ad break timing
372
- adBreakCheckIntervalMs?: number; // Interval used to re-check an active ad break (default: 1000, min: 250)
373
- maxAdBreakExtensionMs?: number; // Max time an ad break may be extended past its SCTE-35 duration when ads are still playing/queued (default: 60000)
374
-
375
- // Ad configuration
376
- vastMode?: 'adstorm' | 'default'; // VAST endpoint resolution: 'adstorm' (/vast/{licenseKey}) or 'default' (/ads/web or vastTagUrl) (default: 'default')
377
- vastTagUrl?: string; // Custom VAST tag URL (default mode; when omitted, /ads/web supplies the tag URL)
378
- isVmap?: boolean; // Enable VMAP manifest scheduling (requires vmapUrl)
379
- vmapUrl?: string; // VMAP 1.0 manifest URL (requires isVmap: true)
380
- ctvAdRequest?: boolean; // Include CTV device signals in VAST macro substitution
381
- adTest?: boolean; // Enable test ad mode in VAST requests
382
-
383
- onVolumeToggle?: () => void; // Callback for volume toggle
384
- onFullscreenToggle?: () => void; // Callback for fullscreen toggle
385
- onControlClick?: () => void; // Callback for control area clicks
386
- }
387
- ```
388
-
389
- ## 🎨 Enhanced UI Controls
390
-
391
- The player includes beautiful, adaptive video controls that ensure visibility on any video background:
392
-
393
- ### Features
394
-
395
- - **Adaptive Visibility**: High-contrast design that works on both light and dark video backgrounds
396
- - **Live Mode Support**: Specialized controls for live streaming with hover-activated volume slider
397
- - **Modern Design**: Glassmorphism effects with smooth animations and transitions
398
- - **Touch-Friendly**: Large, accessible buttons optimized for mobile and desktop
399
- - **Customizable**: Full control over appearance and behavior through props
400
-
401
- ### Control Types
402
-
403
- #### Full Controls (HLS Streams)
404
-
405
- When `showCustomControls={true}` for HLS streams:
406
-
407
- - Progress timeline with seek functionality
408
- - Play/pause button with smooth animations
409
- - Volume control with hover-activated vertical slider
410
- - Playback speed menu (0.25x to 2x)
411
- - Fullscreen toggle
412
- - Time display (current/duration)
413
-
414
- #### Live Mode Controls (Non-HLS or when native HLS is used)
415
-
416
- When `showCustomControls={true}` for regular video files:
417
-
418
- - Volume control with hover-activated slider
419
- - Fullscreen toggle
420
- - Compact, overlay-style positioning
421
-
422
- ### Styling
423
-
424
- All controls use a consistent high-contrast design:
425
-
426
- - **Dark backgrounds** with gradient overlays
427
- - **White borders** for clear definition
428
- - **Enhanced shadows** for depth and separation
429
- - **Smooth transitions** for professional feel
430
-
431
- ```jsx
432
- <StormcloudPlayer
433
- showCustomControls={true}
434
- wrapperStyle={{ borderRadius: "12px", overflow: "hidden" }}
435
- onVolumeToggle={() => console.log("Volume toggled")}
436
- onFullscreenToggle={() => console.log("Fullscreen toggled")}
437
- />
438
- ```
439
-
440
- ## 🎬 Ad Integration (HLS Streams Only)
441
-
442
- ### VAST Mode Configuration
443
-
444
- The player always uses the **HLS ad player** to fetch VAST XML and play ad media (HLS or progressive). The `vastMode` option only controls **which endpoint supplies the VAST tag URL**:
445
-
446
- #### 1. **AdStorm Mode** (Recommended)
447
-
448
- Uses the AdStorm `/vast/{licenseKey}` endpoint, which returns VAST XML directly:
449
-
450
- ```javascript
451
- const player = new StormcloudVideoPlayer({
452
- videoElement: video,
453
- src: "https://your-stream.com/playlist.m3u8",
454
- licenseKey: "your-license-key",
455
-
456
- vastMode: 'adstorm',
457
-
458
- debugAdTiming: true,
459
- });
460
- ```
461
-
462
- **What happens:**
463
- - 🔗 VAST endpoint: `GET https://adstorm.co/api-adstorm-dev/adstorm/vast/{licenseKey}`
464
- - License key is passed in the URL path (no authorization header needed)
465
- - Returns VAST XML directly with HLS media files
466
- - 📊 Direct tracking and analytics through AdStorm backend
467
- - ⚠️ Gracefully handles "no ads available" scenarios (logs warnings, not errors)
468
-
469
- **API Flow:**
470
- 1. Player calls `/vast/{licenseKey}` endpoint
471
- 2. Backend returns VAST XML with HLS media files
472
- 3. HLS ad player parses VAST XML and extracts MediaFile URLs
473
- 4. Ad segments are loaded and played
474
-
475
- **Benefits:**
476
- - ✅ Zero external ad SDK dependencies
477
- - ✅ Full control over ad serving
478
- - ✅ Native HLS playback (same format as content)
479
- - ✅ Better performance and reliability on Smart TVs
480
- - ✅ Custom targeting and selection
481
- - ✅ Proper error handling (distinguishes parsing errors from "no ads available")
482
-
483
- #### 2. **Default Mode**
484
-
485
- Uses the AdStorm `/ads/web` API (or a custom `vastTagUrl`) to obtain a VAST tag URL, then plays ads through the same HLS ad player:
486
-
487
- ```javascript
488
- const player = new StormcloudVideoPlayer({
489
- videoElement: video,
490
- src: "https://your-stream.com/playlist.m3u8",
491
- licenseKey: "your-license-key",
492
-
493
- vastMode: 'default', // or omit this property entirely
494
- vastTagUrl: 'https://your-vast-server.com/vast.xml', // optional
495
-
496
- debugAdTiming: true,
497
- });
498
- ```
499
-
500
- **What happens:**
501
- - 🔗 VAST tag URL resolution:
502
- 1. If `vastTagUrl` is provided, uses that URL directly
503
- 2. Otherwise, calls `GET https://adstorm.co/api-adstorm-dev/adstorm/ads/web`
504
- - Requires `Authorization: Bearer {licenseKey}` header
505
- - Returns JSON with a VAST tag URL in `response.ima["publisherdesk.ima"].payload`
506
- - 📊 HLS ad player fetches VAST XML from the resolved tag URL and plays returned media files
507
-
508
- **API Flow:**
509
- 1. Player calls `/ads/web` with Bearer token (if no `vastTagUrl` provided)
510
- 2. Backend returns JSON with a VAST tag URL in `response.ima["publisherdesk.ima"].payload`
511
- 3. HLS ad player requests VAST XML from that URL
512
- 4. Ad media is parsed from VAST and played
513
-
514
- **Benefits:**
515
- - ✅ Industry-standard VAST tag URLs (e.g. Google Ad Manager)
516
- - ✅ Backward compatible with existing AdStorm `/ads/web` integrations
517
- - ✅ Supports both custom VAST URLs and AdStorm backend configuration
518
- - ✅ Same HLS ad player stack as AdStorm mode
519
-
520
- ### Ad Pod Generation (Multiple Consecutive Ads)
521
-
522
- The player automatically generates **ad pods** (multiple ads played consecutively) from a single VAST URL. This works differently for VOD and live streams:
523
-
524
- #### VOD Mode: Fixed Ad Count
525
-
526
- For **Video on Demand**, the player uses the `number_ads` field from the API response to determine how many ads to play:
527
-
528
- **API Response Configuration:**
529
- ```json
530
- {
531
- "response": {
532
- "ima": {
533
- "publisherdesk.ima": {
534
- "payload": "https://pubads.g.doubleclick.net/gampad/ads?...",
535
- "priority": 1
536
- }
537
- },
538
- "options": {
539
- "vast": {
540
- "cue_tones": {
541
- "number_ads": 3
542
- }
543
- }
544
- }
545
- }
546
- }
547
- ```
548
-
549
- **How it works:**
550
- 1. Player receives a single VAST URL from the API
551
- 2. The `number_ads` field specifies how many ads should play (e.g., 3)
552
- 3. Player generates 3 unique VAST URLs by adding different `correlator` values
553
- 4. Each unique correlator causes GAM to return a different ad
554
- 5. All 3 ads play consecutively in the ad break
555
-
556
- **Example:**
557
- ```javascript
558
- // Base URL from API
559
- "https://pubads.g.doubleclick.net/...&correlator="
560
-
561
- // Generated URLs (3 ads)
562
- [
563
- "https://pubads.g.doubleclick.net/...&correlator=1730995200000123456780",
564
- "https://pubads.g.doubleclick.net/...&correlator=1730995200000789012341",
565
- "https://pubads.g.doubleclick.net/...&correlator=1730995200000345678902"
566
- ]
567
- ```
568
-
569
- #### Live Mode: Adaptive Duration-Based Ad Filling
570
-
571
- For **live streams**, the player uses an **adaptive strategy** that fetches VAST responses, extracts actual ad durations, and dynamically requests additional ads as needed to fill the SCTE-35 marker duration:
572
-
573
- **Configuration:**
574
- ```javascript
575
- const player = new StormcloudVideoPlayer({
576
- videoElement: video,
577
- src: "https://your-live-stream.com/playlist.m3u8",
578
- licenseKey: "your-license-key",
579
-
580
- vastMode: 'default',
581
- debugAdTiming: true, // Enable to see adaptive calculations
582
- });
583
- ```
584
-
585
- **How Adaptive Mode Works:**
586
-
587
- 1. **Initial Request**: Player starts with 2 initial VAST URLs
588
- 2. **Fetch Real Duration**: As each VAST is fetched, the player extracts the actual `<Duration>` from the XML
589
- 3. **Adaptive Calculation**: After fetching each ad, the player:
590
- - Calculates total duration of fetched ads
591
- - Compares against target SCTE-35 duration
592
- - Dynamically generates more VAST URLs if needed
593
- 4. **Smart Filling**: Uses **average** of actual fetched durations (not estimates) to calculate remaining ads
594
- 5. **Continuous Adaptation**: Recalculates after each ad fetch until target duration is met
595
-
596
- **Example Scenario:**
597
- ```
598
- SCTE-35 Duration: 120 seconds
599
-
600
- Step 1: Start with 2 initial ads
601
- → Fetch Ad 1: Actual duration = 45s
602
- → Recalculate: Need (120-45)/45 = 2 more ads
603
- → Generate 2 additional VAST URLs
604
-
605
- Step 2: Fetch Ad 2: Actual duration = 40s
606
- → Total so far: 85s (45s + 40s)
607
- → Recalculate: Need (120-85)/42.5 = 1 more ad
608
- → Generate 1 additional VAST URL
609
-
610
- Step 3: Fetch Ad 3: Actual duration = 35s
611
- → Total: 120s ✅ Target reached!
612
-
613
- Final: 3 ads played (perfectly fills 120 seconds)
614
- ```
615
-
616
- **Debug Output:**
617
- ```
618
- [ADAPTIVE-POD] 📺 LIVE MODE (ADAPTIVE): Target duration=120000ms | Starting with 2 ads, will fetch actual durations and add more dynamically
619
- [DEBUG-POD] 🔄 Generated 2 initial VAST URLs with unique correlators
620
- [ADAPTIVE-POD] ✓ Fetched ad duration: 45s (1 ads fetched so far)
621
- [ADAPTIVE-POD] 📊 Need 2 more ads | Fetched: 45000ms / Target: 120000ms | Remaining: 75000ms | Avg duration: 45000ms
622
- [ADAPTIVE-POD] 🔄 Adding 2 additional VAST URLs to queue
623
- [ADAPTIVE-POD] ✓ Fetched ad duration: 40s (2 ads fetched so far)
624
- [ADAPTIVE-POD] 📊 Need 1 more ads | Fetched: 85000ms / Target: 120000ms | Remaining: 35000ms | Avg duration: 42500ms
625
- [ADAPTIVE-POD] ✓ Fetched ad duration: 35s (3 ads fetched so far)
626
- [ADAPTIVE-POD] ✅ Target duration reached: 120000ms / 120000ms
627
- ```
628
-
629
- **Benefits of Adaptive Approach:**
630
-
631
- - ✅ **Accurate Filling**: Uses actual ad durations from VAST responses
632
- - ✅ **Less Waste**: Doesn't over-request ads unnecessarily
633
- - ✅ **Flexible**: Adapts to GAM returning 15s, 30s, 60s, or mixed-length ads
634
- - ✅ **Efficient**: Fetches durations while preloading, no extra latency
635
- - ✅ **Smart**: Improves calculation as more data is gathered (uses average of fetched durations)
636
- - ✅ **Self-Correcting**: Automatically adjusts if actual ad lengths differ from expectations
637
-
638
- ### VMAP Ad Break Scheduling
639
-
640
- In addition to SCTE-35 markers and continuous-ad-fetch, the player supports **VMAP 1.0** manifests for scheduling ad breaks at specific positions in the timeline. This is ideal for VOD content where you want to declaratively define pre-roll, mid-roll, and post-roll breaks with explicit time offsets.
641
-
642
- #### Usage
643
-
644
- Provide `isVmap: true` and a `vmapUrl` in addition to (or instead of) a VAST/AdStorm configuration:
645
-
646
- ```javascript
647
- const player = new StormcloudVideoPlayer({
648
- videoElement: video,
649
- src: "https://your-stream.com/video.m3u8",
650
- licenseKey: "your-license-key",
651
-
652
- vastMode: 'default',
653
- isVmap: true,
654
- vmapUrl: 'https://your-cdn.com/schedule.vmap', // VMAP 1.0 manifest
655
-
656
- debugAdTiming: true,
657
- });
658
- ```
659
-
660
- Or with React:
661
-
662
- ```jsx
663
- <StormcloudPlayer
664
- src="https://your-stream.com/video.m3u8"
665
- licenseKey="your-license-key"
666
- isVmap={true}
667
- vmapUrl="https://your-cdn.com/schedule.vmap"
668
- playing={true}
669
- />
670
- ```
671
-
672
- #### Supported `timeOffset` Formats
673
-
674
- The VMAP parser recognizes all standard `timeOffset` values:
675
-
676
- | Value | Meaning |
677
- | --------------------- | ---------------------------------------------------- |
678
- | `start` | Pre-roll (playback position 0) |
679
- | `end` | Post-roll (resolved at runtime using media duration) |
680
- | `HH:MM:SS` / `HH:MM:SS.mmm` | Absolute timestamp mid-roll |
681
- | `NN%` | Percentage of media duration (resolved at runtime) |
682
-
683
- #### Example VMAP Manifest
684
-
685
- ```xml
686
- <?xml version="1.0" encoding="UTF-8"?>
687
- <vmap:VMAP xmlns:vmap="http://www.iab.net/videosuite/vmap" version="1.0">
688
- <vmap:AdBreak timeOffset="start" breakType="linear" breakId="preroll">
689
- <vmap:AdSource>
690
- <vmap:AdTagURI templateType="vast3">
691
- <![CDATA[https://your-ad-server.com/vast?position=preroll]]>
692
- </vmap:AdTagURI>
693
- </vmap:AdSource>
694
- </vmap:AdBreak>
695
-
696
- <vmap:AdBreak timeOffset="00:05:00" breakType="linear" breakId="midroll-1">
697
- <vmap:AdSource>
698
- <vmap:AdTagURI templateType="vast3">
699
- <![CDATA[https://your-ad-server.com/vast?position=midroll-1]]>
700
- </vmap:AdTagURI>
701
- </vmap:AdSource>
702
- </vmap:AdBreak>
703
-
704
- <vmap:AdBreak timeOffset="end" breakType="linear" breakId="postroll">
705
- <vmap:AdSource>
706
- <vmap:AdTagURI templateType="vast3">
707
- <![CDATA[https://your-ad-server.com/vast?position=postroll]]>
708
- </vmap:AdTagURI>
709
- </vmap:AdSource>
710
- </vmap:AdBreak>
711
- </vmap:VMAP>
712
- ```
713
-
714
- #### How It Works
715
-
716
- 1. The player fetches the VMAP manifest on load (before playback starts).
717
- 2. Each `<AdBreak>` is parsed into an `AdBreak` object with a resolved `startTimeMs` and the associated `vastTagUrl` from `<AdTagURI>`.
718
- 3. During playback, the player matches the current time against each scheduled break and triggers the ad request at the correct offset.
719
- 4. **Mid-roll join behavior**: If the viewer joins mid-stream and has already passed a scheduled break, the player will honor the configured late-join policy (e.g., play the remaining portion or skip to content).
720
- 5. Each break is only consumed once per session to avoid replaying breaks after seeking.
721
-
722
- **Benefits:**
723
- - ✅ Declarative ad scheduling — no custom code needed per break
724
- - ✅ Works alongside `vastMode: 'adstorm'` and `vastMode: 'default'`
725
- - ✅ Supports pre-roll, mid-roll, percentage-based, and post-roll breaks
726
- - ✅ Gracefully handles malformed XML and fetch failures (logged when `debugAdTiming` is enabled)
727
-
728
- ### Ad Player Architecture
729
-
730
- All ads are played through the built-in **HLS ad player** (`createHlsAdPlayer`).
731
-
732
- The player:
733
- 1. Resolves a VAST tag URL (`vastMode`, `vastTagUrl`, VMAP, or `/ads/web`)
734
- 2. Fetches and parses VAST XML (including wrapper chains)
735
- 3. Plays HLS or progressive `MediaFile` URLs from the VAST response
736
-
737
- `vastMode` only changes how the initial VAST tag URL is obtained:
738
- - `vastMode: 'adstorm'` → `/vast/{licenseKey}` returns VAST XML directly (used as the tag URL for pod generation)
739
- - `vastMode: 'default'` → `/ads/web` or `vastTagUrl` supplies the tag URL; the HLS ad player fetches VAST from that URL
740
-
741
- ### SCTE-35 Support
742
-
743
- The HlsPlayer automatically detects and responds to SCTE-35 signals embedded in HLS streams:
744
-
745
- - **CUE-OUT**: Triggers ad break start
746
- - **CUE-OUT-CONT**: Handles mid-roll continuation
747
- - **CUE-IN**: Resumes content playback
748
- - **DATERANGE**: Processes time-based ad markers
749
-
750
- ### Supported HLS Tags
751
-
752
- - `#EXT-X-CUE-OUT`
753
- - `#EXT-X-CUE-OUT-CONT`
754
- - `#EXT-X-CUE-IN`
755
- - `#EXT-X-DATERANGE`
756
- - ID3 timed metadata
757
-
758
- ### Early Ad Prefetching & Preload Pool
759
-
760
- The player includes an advanced ad prefetching system that dramatically reduces ad start latency:
761
-
762
- #### Early SCTE-35 Detection
763
-
764
- The player scans up to 5 manifest fragments ahead of current playback to detect upcoming SCTE-35 ad markers. When an ad break is detected early:
765
-
766
- 1. **Prefetching Begins**: The player immediately starts generating VAST URLs and prefetching ads
767
- 2. **Preload Pool**: Up to 3 ads are preloaded and ready to play instantly
768
- 3. **Zero-Delay Starts**: When the ad break actually begins, ads start immediately (no request delay)
769
-
770
- **Benefits:**
771
- - ⚡ **80% reduction** in ad start latency
772
- - 🎯 **Instant ad playback** when breaks begin
773
- - 📊 **Better fill rates** through proactive ad preparation
774
- - 🔄 **Seamless transitions** between content and ads
775
-
776
- #### How It Works
777
-
778
- ```javascript
779
- const player = new StormcloudVideoPlayer({
780
- videoElement: video,
781
- src: "https://your-stream.com/playlist.m3u8",
782
- licenseKey: "your-license-key",
783
- debugAdTiming: true, // Enable to see prefetching logs
784
- });
785
-
786
- // The player automatically:
787
- // 1. Scans manifest fragments for SCTE-35 markers
788
- // 2. Detects upcoming ad breaks before playback reaches them
789
- // 3. Prefetches and preloads ads into a pool
790
- // 4. Starts ads instantly when breaks begin
791
- ```
792
-
793
- **Debug Output Example:**
794
- ```
795
- [PREFETCH] 🔄 Starting ad prefetch for upcoming ad break
796
- [PREFETCH] 📋 Pre-generated 5 VAST URLs
797
- [PRELOAD-POOL] 🏊 Starting preload pool EARLY (target size: 3)
798
- [PRELOAD-POOL] 📥 Preloading ad into pool: https://...
799
- [PRELOAD-POOL] ✅ Ad preloaded (pool size: 1/3)
800
- [CONTINUOUS-FETCH] 🚀 Using preloaded ad from pool (preloaded in advance, ready immediately!)
801
- ```
802
-
803
- #### Continuous Ad Fetching
804
-
805
- During ad breaks, the player continuously fetches additional ads to fill the entire SCTE-35 duration:
806
-
807
- - **Dynamic Queue**: Maintains a queue of ready-to-play VAST URLs
808
- - **Smart Rate Limiting**: 2.5s minimum interval between requests with exponential backoff
809
- - **Automatic Filling**: Fetches ads until the SCTE-35 duration is fully filled
810
- - **Error Recovery**: Distinguishes temporary failures (no-fill) from permanent failures
811
-
812
- **Rate Limiting & Backoff:**
813
- - Base interval: 2.5 seconds between requests
814
- - Exponential backoff: Increases with consecutive failures
815
- - Max backoff: 15 seconds
816
- - Cooldown period: 30 seconds for temporary failures (no-fill)
817
-
818
- #### Error Handling & Recovery
819
-
820
- The player intelligently handles different types of ad failures:
821
-
822
- **Temporary Failures (Retryable):**
823
- - No-fill responses (no ads available)
824
- - Network timeouts
825
- - Rate limiting errors
826
- - **Action**: URL enters 30s cooldown, then can be retried
827
-
828
- **Permanent Failures (Blacklisted):**
829
- - VAST parsing errors
830
- - Malformed responses
831
- - **Action**: URL is permanently blacklisted for the current ad break
832
-
833
- **Fallback System:**
834
- - If a primary ad request fails, the player automatically tries a preloaded ad from the pool
835
- - Ensures continuous ad playback even when individual requests fail
836
-
837
- ### Late Join Behavior
838
-
839
- When viewers join during an ad break:
840
-
841
- - **play_remaining**: Plays the remaining portion of the current ad
842
- - **skip_to_content**: Skips to main content (configurable via API)
843
-
844
- ### Ad Status Monitoring
845
-
846
- ```jsx
847
- <StormcloudPlayer
848
- onReady={(player) => {
849
- // Monitor ad playback
850
- console.log("Is ad playing:", player.isAdPlaying());
851
- console.log("Current ad index:", player.getCurrentAdIndex());
852
- console.log("Total ads in break:", player.getTotalAdsInBreak());
853
- }}
854
- />
855
- ```
856
-
857
- ### Error Handling
858
-
859
- The player distinguishes between different types of ad-related issues:
860
-
861
- **"No Ads Available" (Warning):**
862
- - When the VAST response indicates no ads are available (e.g., empty `<MediaFiles>` or `AdTitle: "No Ad Available"`)
863
- - Logs warnings, not errors
864
- - Content playback continues normally
865
- - No error events are emitted
866
-
867
- **VAST XML Parsing Errors (Error):**
868
- - When the VAST XML is malformed or cannot be parsed
869
- - Logs errors
870
- - Emits `ad_error` event
871
- - Content playback continues
872
-
873
- **Network/Fetch Errors (Error):**
874
- - When the VAST endpoint is unreachable or returns an error status
875
- - Logs errors
876
- - Emits `ad_error` event
877
- - Content playback continues
878
-
879
- Example with error handling:
880
-
881
- ```javascript
882
- const player = new StormcloudVideoPlayer({
883
- // ... config
884
- vastMode: 'adstorm',
885
- debugAdTiming: true, // Enable detailed logging
886
- });
887
-
888
- // Monitor ad events
889
- player.on('ad_error', (error) => {
890
- console.error('Ad error occurred:', error);
891
- // Handle error (e.g., show fallback, retry, etc.)
892
- });
893
- ```
894
-
895
- ## 🔐 Authentication
896
-
897
- The player supports license key authentication for enhanced features:
898
-
899
- ```javascript
900
- const player = new StormcloudVideoPlayer({
901
- // ... other config
902
- licenseKey: "ADSTORM-YOUR-LICENSE-KEY-HERE",
903
- });
904
- ```
905
-
906
- Authenticated requests are sent to:
907
-
908
- - **AdStorm Mode** (`vastMode: 'adstorm'`):
909
- - VAST endpoint: `GET https://adstorm.co/api-adstorm-dev/adstorm/vast/{licenseKey}` (license key in URL path)
910
-
911
- - **Default Mode** (`vastMode: 'default'`):
912
- - Ad configuration: `GET https://adstorm.co/api-adstorm-dev/adstorm/ads/web` (requires `Authorization: Bearer {licenseKey}` header)
913
- - Returns JSON with a VAST tag URL in `response.ima["publisherdesk.ima"].payload`
914
-
915
- - **Player Tracking** (both modes):
916
- - Metrics / heartbeat: `POST https://player-analytics-ingest-api-760490581852.us-central1.run.app/api/adstorm/player-tracking/metrics/ingest` (requires `Authorization: License-Key {licenseKey}` header)
917
- - Ad impressions: `POST https://player-analytics-ingest-api-760490581852.us-central1.run.app/api/adstorm/player-tracking/impressions/ingest` (requires `Authorization: License-Key {licenseKey}` header)
918
- - Ad-detect, ad-loaded, and ad-impression events are automatically emitted during ad breaks.
919
-
920
- ### Tracking Utilities
921
-
922
- The player exposes low-level tracking helpers if you want to fire events manually or build custom analytics:
923
-
924
- ```javascript
925
- import {
926
- getClientInfo,
927
- getBrowserID,
928
- sendInitialTracking,
929
- sendHeartbeat,
930
- sendAdDetectTracking,
931
- sendAdLoadedTracking,
932
- sendAdImpressionTracking,
933
- } from "stormcloud-video-player";
934
-
935
- const clientInfo = getClientInfo();
936
- const browserId = getBrowserID();
937
-
938
- await sendInitialTracking(licenseKey, clientInfo);
939
- await sendHeartbeat(licenseKey, { browserId, timestamp: new Date().toISOString() });
940
-
941
- // Ad lifecycle tracking
942
- await sendAdDetectTracking(licenseKey, {
943
- source: "scte35",
944
- durationSeconds: 30,
945
- ptsSeconds: 120.5,
946
- detectedAtFragmentSn: 1234,
947
- timestamp: new Date().toISOString(),
948
- });
949
-
950
- await sendAdLoadedTracking(licenseKey, {
951
- source: "hls", // 'vast' | 'hls' (ad playback path)
952
- vastUrl: "https://...",
953
- durationSeconds: 15,
954
- timestamp: new Date().toISOString(),
955
- });
956
-
957
- await sendAdImpressionTracking(licenseKey, {
958
- source: "hls",
959
- adIndex: 0,
960
- durationSeconds: 15,
961
- timestamp: new Date().toISOString(),
962
- });
963
- ```
964
-
965
- ## 🔧 Advanced Configuration
966
-
967
- ### Custom Player Creation
968
-
969
- Create your own player with specific capabilities:
970
-
971
- ```javascript
972
- import { createStormcloudPlayer, players } from "stormcloud-video-player";
973
-
974
- // Create a player with only HLS support
975
- const HLSOnlyPlayer = createStormcloudPlayer([
976
- players.find((p) => p.key === "hls"),
977
- ]);
978
-
979
- // Create a player with custom fallback
980
- const CustomPlayer = createStormcloudPlayer(
981
- players,
982
- players.find((p) => p.key === "file") // fallback to file player
983
- );
984
- ```
985
-
986
- ### Adding Custom Players
987
-
988
- ```javascript
989
- import StormcloudPlayer from "stormcloud-video-player";
990
-
991
- // Add a custom player for a specific format
992
- StormcloudPlayer.addCustomPlayer({
993
- key: "custom",
994
- name: "CustomPlayer",
995
- canPlay: (src) => src.includes("custom://"),
996
- lazyPlayer: React.lazy(() => import("./CustomPlayer")),
997
- });
998
- ```
999
-
1000
- ### Buffering Configuration
1001
-
1002
- #### Min Segments Before Play
1003
-
1004
- Control how many segments must be buffered before playback starts. This helps ensure smooth playback, especially on slower connections:
1005
-
1006
- ```javascript
1007
- const player = new StormcloudVideoPlayer({
1008
- videoElement: video,
1009
- src: "https://your-stream.com/playlist.m3u8",
1010
- autoplay: true,
1011
- minSegmentsBeforePlay: 3, // Wait for 3 segments before starting (default: 2)
1012
- });
1013
-
1014
- // Or with React component
1015
- <StormcloudPlayer
1016
- src="https://your-stream.com/playlist.m3u8"
1017
- playing={true}
1018
- minSegmentsBeforePlay={3}
1019
- />
1020
- ```
1021
-
1022
- **Configuration Options:**
1023
- - `minSegmentsBeforePlay: 0` - Start immediately (may cause stuttering on slow connections)
1024
- - `minSegmentsBeforePlay: 2` - Default, good balance for most use cases
1025
- - `minSegmentsBeforePlay: 3-5` - Recommended for slower connections or higher quality streams
1026
- - `minSegmentsBeforePlay: undefined` - Uses default value (2)
1027
-
1028
- **When to Adjust:**
1029
- - **Increase** for high-bitrate streams or unreliable networks
1030
- - **Decrease** for low-latency requirements or fast connections
1031
- - **Set to 0** only if you need immediate playback and can tolerate potential stuttering
1032
-
1033
- ### Player Detection
1034
-
1035
- ```javascript
1036
- import StormcloudPlayer from "stormcloud-video-player";
1037
-
1038
- // Check if a URL can be played
1039
- const canPlay = StormcloudPlayer.canPlay("https://example.com/video.m3u8");
1040
- console.log("Can play:", canPlay); // true
1041
-
1042
- // Check Picture-in-Picture support
1043
- const canPIP = StormcloudPlayer.canEnablePIP("https://example.com/video.mp4");
1044
- console.log("Supports PIP:", canPIP); // true for file player
1045
- ```
1046
-
1047
- ### Event Handling
1048
-
1049
- ```jsx
1050
- <StormcloudPlayer
1051
- onReady={(player) => {
1052
- console.log("Player ready");
1053
- }}
1054
- onPlay={() => {
1055
- console.log("Playback started");
1056
- }}
1057
- onPause={() => {
1058
- console.log("Playback paused");
1059
- }}
1060
- onProgress={(state) => {
1061
- console.log("Progress:", state.playedSeconds, "of", state.loadedSeconds);
1062
- }}
1063
- onError={(error, data, hlsInstance, hlsGlobal) => {
1064
- console.error("Player error:", error);
1065
- if (hlsInstance) {
1066
- console.log("HLS instance available for recovery");
1067
- }
1068
- }}
1069
- />
1070
- ```
1071
-
1072
- ## 🌐 Browser & Smart TV Support
1073
-
1074
- - **Desktop**: Chrome 60+, Firefox 55+, Safari 12+, Edge 79+
1075
- - **Mobile**: iOS Safari 12+, Chrome Mobile 60+
1076
- - **Smart TV**: LG WebOS (v2+), Samsung Tizen (v2+), Sony BRAVIA, Android TV, Roku, Apple TV, generic Smart TV user-agents
1077
-
1078
- ### Automatic Browser Detection & Smart TV Overrides
1079
-
1080
- The player detects the runtime environment and applies Smart TV-friendly defaults via `getBrowserConfigOverrides()`.
1081
-
1082
- ```javascript
1083
- import {
1084
- detectBrowser,
1085
- supportsModernJS,
1086
- logBrowserInfo,
1087
- getBrowserConfigOverrides,
1088
- supportsFeature,
1089
- } from "stormcloud-video-player";
1090
-
1091
- const info = detectBrowser();
1092
- // {
1093
- // name: 'LG WebOS',
1094
- // version: '5.0',
1095
- // majorVersion: 5,
1096
- // isSmartTV: true,
1097
- // isLegacyTV: false,
1098
- // supportsModernJS: true,
1099
- // webOSVersion: 5,
1100
- // chromeVersion: 79,
1101
- // ...
1102
- // }
1103
-
1104
- console.log(supportsModernJS()); // true | false
1105
- console.log(supportsFeature('fetch')); // true | false
1106
- console.log(getBrowserConfigOverrides()); // e.g. { allowNativeHls: true } on Smart TVs
1107
- ```
1108
-
1109
- **What happens automatically:**
1110
-
1111
- - **Smart TVs** (LG WebOS, Samsung Tizen, etc.): `allowNativeHls` is enabled by default (TVs generally play HLS better natively).
1112
- - **Browser overrides** are merged with your config — user-provided options always take precedence.
1113
- - **Ads**: HLS ad player on all supported devices.
1114
-
1115
- Call `logBrowserInfo(true)` (or enable `debugAdTiming`) to print a detailed compatibility report to the console.
1116
-
1117
- ### Automatic Polyfills for Legacy Devices
1118
-
1119
- On older Smart TVs and browsers, `initializePolyfills()` is invoked automatically at construction time. It adds missing primitives needed for the player to run:
1120
-
1121
- - `URLSearchParams`
1122
- - `TextEncoder`
1123
- - `Promise.prototype.finally`
1124
- - `Object.assign`
1125
- - `Array.from`
1126
- - `String.prototype.startsWith` / `endsWith` / `includes`
1127
-
1128
- You can also run it manually before bootstrapping anything else:
1129
-
1130
- ```javascript
1131
- import { initializePolyfills } from "stormcloud-video-player";
1132
-
1133
- initializePolyfills();
1134
- ```
1135
-
1136
- ### Format Support by Player
1137
-
1138
- #### HlsPlayer
1139
-
1140
- - **HLS Streams**: `.m3u8` files
1141
- - **Native HLS**: Safari, iOS Safari (when `allowNativeHls=true`)
1142
- - **HLS.js**: Chrome, Firefox, Edge (automatic fallback)
1143
-
1144
- #### FilePlayer
1145
-
1146
- - **Video Formats**: MP4, WebM, MOV, AVI, OGV
1147
- - **Audio Formats**: MP3, WAV, OGG, AAC
1148
- - **Streaming**: Progressive download
1149
- - **Picture-in-Picture**: Supported browsers
1150
-
1151
- ## 🏗️ Development
1152
-
1153
- ### Build Commands
1154
-
1155
- ```bash
1156
- # Build everything
1157
- npm run build:all
1158
-
1159
- # Individual builds
1160
- npm run build:lib # Library build (ESM/CJS)
1161
- npm run build:minified # Minified UMD bundle
1162
- npm run build:dist # Production UMD bundle
1163
-
1164
- # Development
1165
- npm run dev # Development watch mode
1166
- npm run clean # Clean build artifacts
1167
- npm run test # Run test suite
1168
- npm run lint # Lint codebase
1169
- ```
1170
-
1171
- ### Project Structure
1172
-
1173
- ```
1174
- src/
1175
- ├── index.ts # Main exports
1176
- ├── StormcloudPlayer.tsx # New main component
1177
- ├── Player.tsx # Internal wrapper component
1178
- ├── players/
1179
- │ ├── index.ts # Player registry
1180
- │ ├── HlsPlayer.tsx # HLS stream handler
1181
- │ └── FilePlayer.tsx # Regular video handler
1182
- ├── player/
1183
- │ └── StormcloudVideoPlayer.ts # Core player class (SCTE-35, VMAP, ad pods)
1184
- ├── ui/
1185
- │ └── StormcloudVideoPlayer.tsx # Legacy React component
1186
- ├── sdk/
1187
- │ ├── pal.ts # Google PAL nonce manager
1188
- │ └── hlsAdPlayer.ts # HLS / progressive VAST ad player
1189
- ├── utils/
1190
- │ ├── tracking.ts # Analytics and ad tracking
1191
- │ ├── browserCompat.ts # Browser / Smart TV detection & auto-overrides
1192
- │ └── polyfills.ts # Legacy browser polyfills
1193
- ├── props.ts # Centralized props system
1194
- ├── patterns.ts # URL pattern matching
1195
- ├── utils.ts # Shared utilities
1196
- └── types.ts # TypeScript definitions
1197
- ```
1198
-
1199
- ### Export Structure
1200
-
1201
- ```
1202
- stormcloud-video-player/
1203
- ├── index.js # Main entry (auto-detection)
1204
- ├── hls.js # HLS-only entry
1205
- ├── file.js # File-only entry
1206
- ├── base.d.ts # Base type definitions
1207
- ├── hls.d.ts # HLS-specific types
1208
- ├── file.d.ts # File-specific types
1209
- ├── lib/ # Compiled library
1210
- ├── dist/ # UMD bundles
1211
- └── scripts/ # Build utilities
1212
- ```
1213
-
1214
- ## 🎯 Use Cases
1215
-
1216
- ### By Player Type
1217
-
1218
- #### HlsPlayer Use Cases
1219
-
1220
- - **Live Streaming**: Sports, news, and event broadcasts
1221
- - **Live Shopping**: E-commerce live streams with ads
1222
- - **FAST Channels**: Free ad-supported streaming TV
1223
- - **Linear TV**: Traditional broadcast over IP
1224
- - **Event Streaming**: Conferences, concerts, webinars
1225
-
1226
- #### FilePlayer Use Cases
1227
-
1228
- - **VOD Platforms**: Movie and series streaming services
1229
- - **Educational Content**: E-learning and training platforms
1230
- - **Corporate Communications**: Internal videos and presentations
1231
- - **Digital Signage**: Retail and public display systems
1232
- - **Social Media**: User-generated content playback
1233
-
1234
- ### Migration Guide
1235
-
1236
- #### From Legacy Component
1237
-
1238
- ```jsx
1239
- // Old way (still works)
1240
- import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";
1241
-
1242
- <StormcloudVideoPlayerComponent
1243
- src="https://example.com/stream.m3u8"
1244
- autoplay={true}
1245
- // ... other props
1246
- />;
1247
-
1248
- // New way (recommended for new projects)
1249
- import StormcloudPlayer from "stormcloud-video-player";
1250
-
1251
- <StormcloudPlayer
1252
- src="https://example.com/stream.m3u8"
1253
- playing={true} // Note: 'playing' instead of 'autoplay'
1254
- // ... other props
1255
- />;
1256
- ```
1257
-
1258
- #### Key Differences
1259
-
1260
- | Legacy Component | New Component | Notes |
1261
- | ------------------------ | -------------------------- | --------------------------- |
1262
- | `autoplay={true}` | `playing={true}` | More React-like prop naming |
1263
- | Always loads full bundle | Supports optimized imports | Better performance |
1264
- | Single component | Modular architecture | More maintainable |
1265
- | Fixed UI structure | Flexible wrapper system | More customizable |
1266
-
1267
- ## ⚠️ Known Limitations
1268
-
1269
- - Requires DOM environment (browser-only)
1270
- - SCTE-35 binary splice parsing not yet implemented
1271
- - Multi-ad pod competitive separation handled by ad server
1272
- - Low-latency HLS (LL-HLS) optimizations in development
1273
- - Picture-in-Picture only available in FilePlayer
1274
-
1275
- ## 🗺️ Roadmap
1276
-
1277
- ### Upcoming Features
1278
-
1279
- - **Enhanced SCTE-35**: Full binary splice_info_section parsing
1280
- - **Advanced Late Join**: Improved partial pod handling
1281
- - **LL-HLS Support**: Low-latency streaming optimizations
1282
- - **Rich Analytics**: Comprehensive event tracking and reporting
1283
- - **UI Themes**: Multiple control themes and color schemes
1284
- - **Accessibility**: Enhanced ARIA support and keyboard navigation
1285
- - **Error Recovery**: Advanced retry logic and failover handling
1286
- - **More Players**: DASH, YouTube, Vimeo player implementations
1287
-
1288
- ### Performance Improvements
1289
-
1290
- - Drift correction between PTS and wall-clock timing
1291
- - Memory optimization for long-running sessions
1292
- - Network bandwidth adaptation
1293
- - Smart preloading strategies
1294
- - Code splitting and lazy loading optimizations
1295
-
1296
- ## 🤝 Contributing
1297
-
1298
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1299
-
1300
- ## 📄 License
1301
-
1302
- MIT License - see [LICENSE](LICENSE) file for details.
1303
-
1304
- ---
1305
-
1306
- Built with ❤️ by the Stormcloud team
1307
-
1308
- ### What's New in v0.5
1309
-
1310
- - 🗓️ **VMAP 1.0 Support**: `isVmap` + `vmapUrl` load a VMAP manifest and schedule pre-roll, mid-roll, percentage-based, and post-roll breaks automatically
1311
- - Supports `start`, `end`, `HH:MM:SS[.mmm]`, and `NN%` `timeOffset` values
1312
- - Namespaced (`vmap:AdBreak`) and non-namespaced manifests both supported
1313
- - Breaks are consumed-once-per-session and integrate with the existing late-join policy
1314
- - 📺 **Smart TV First-Class Support**: New browser-compat layer auto-detects LG WebOS, Samsung Tizen, Sony BRAVIA, LG NetCast and generic Smart TV UAs
1315
- - Exports `detectBrowser`, `supportsModernJS`, `logBrowserInfo`, `getBrowserConfigOverrides`, `supportsFeature`
1316
- - Automatically enables `allowNativeHls` on Smart TVs for more reliable playback
1317
- - 🧩 **Automatic Polyfills**: `initializePolyfills()` runs at construction time and backfills `URLSearchParams`, `TextEncoder`, `Promise.prototype.finally`, `Object.assign`, `Array.from`, and `String.prototype.startsWith/endsWith/includes` for legacy environments
1318
- - 📊 **Expanded Ad Tracking**: New `sendAdDetectTracking`, `sendAdLoadedTracking`, and `sendAdImpressionTracking` helpers (plus `AdDetectInfo`, `AdLoadedInfo`, `AdImpressionInfo` types) for SCTE-35 and HLS ad lifecycle events
1319
- - ⚙️ **Ad-Break Timing Controls**: New `adBreakCheckIntervalMs` (default 1000ms, min 250ms) and `maxAdBreakExtensionMs` (default 60000ms) options give you precise control over how long the player is allowed to extend an ad break past its SCTE-35 duration when ads are still playing or queued
1320
- - 🙈 **Hide Loading Indicator**: New `hideLoadingIndicator` prop/config hides the built-in buffering spinner when you want to render your own overlay
1321
- - 🔌 **Expanded Public API**: `createHlsAdPlayer`, `createPalNonceManager`, and `initializePolyfills` are exported alongside browser-compat utilities for custom integrations
1322
-
1323
- ### What's New in v0.4
1324
-
1325
- - 🚀 **Early Ad Prefetching**: Detects SCTE-35 markers in manifest fragments before playback reaches them, prefetching ads in advance for zero-delay ad starts
1326
- - 🏊 **Ad Preload Pool**: Maintains a pool of preloaded, ready-to-play ads (up to 3 by default) for instant ad playback when breaks start
1327
- - 🔄 **Continuous Ad Fetching**: Dynamically fetches additional ads during ad breaks to perfectly fill SCTE-35 durations, ensuring no wasted ad time
1328
- - ⏱️ **Smart Rate Limiting**: Intelligent rate limiting (2.5s minimum interval) with exponential backoff to prevent ad server overload
1329
- - 🛡️ **Advanced Error Handling**: Distinguishes between temporary failures (no-fill, timeouts) and permanent failures, with 30s cooldown periods for retryable errors
1330
- - ⏳ **Min Segments Before Play**: New `minSegmentsBeforePlay` option (default: 2) to ensure smooth playback start by buffering multiple segments
1331
- - 🎯 **Ad Request Watchdog**: Timeout system prevents hanging ad requests and automatically recovers from stuck states
1332
- - ⬛ **Placeholder System**: Seamless placeholder layer shown during ad transitions, preventing content flash between ads
1333
- - 📊 **Improved Queue Management**: Better handling of ad request queues with automatic cleanup and failure tracking
1334
- - 🔍 **Early SCTE-35 Detection**: Scans up to 5 manifest fragments ahead to detect upcoming ad breaks, enabling proactive ad preparation
1335
- - ⚡ **Zero-Delay Ad Starts**: Preloaded ads start instantly when ad breaks begin, eliminating the traditional ad request delay
1336
- - 🎬 **Fallback Ad System**: Automatic fallback to preloaded ads when primary ad requests fail, ensuring continuous ad playback
1337
-
1338
- **Performance Improvements:**
1339
- - Reduced ad start latency by up to 80% through prefetching and preloading
1340
- - Better resource utilization with intelligent ad pool management
1341
- - Improved reliability with comprehensive error recovery mechanisms
1342
- - Enhanced user experience with seamless ad transitions
1343
-
1344
- ### What's New in v0.3
1345
-
1346
- - 🎬 **Custom HLS Ad Player**: Native HLS ad playback with VAST XML parsing
1347
- - 🎯 **VAST Tag Resolution**: `vastMode` selects AdStorm `/vast/{licenseKey}` vs `/ads/web` or custom `vastTagUrl`
1348
- - 📊 **Direct Analytics**: Full control over ad tracking and metrics
1349
- - ⚡ **Better Performance**: Native HLS playback for ads (same format as content)
1350
- - 🔧 **Custom VAST URLs**: Point to your own ad serving backend
1351
- - 📦 **Zero external ad SDK dependencies**
1352
- - 🎨 **Seamless Playback**: Same player stack for content and ads
1353
- - 🔀 **VAST Mode System**: `vastMode` configures which endpoint supplies the VAST tag URL
1354
- - `vastMode: 'adstorm'` → `/vast/{licenseKey}` endpoint
1355
- - `vastMode: 'default'` → `/ads/web` endpoint or `vastTagUrl` (both resolve to HLS ad player playback)
1356
- - ⚠️ **Improved Error Handling**: Distinguishes between parsing errors and "no ads available" scenarios
1357
- - Logs warnings for "no ads available" (graceful handling)
1358
- - Logs errors for actual parsing/fetch failures
1359
-
1360
- ### What's New in v0.2
1361
-
1362
- - 🎯 **Professional Architecture**: Modular player system inspired by react-player
1363
- - 🚀 **Automatic Format Detection**: Smart player selection based on video source
1364
- - 📦 **Optimized Bundles**: Import only what you need (HLS-only, File-only, or Full)
1365
- - 🔄 **Backward Compatibility**: Legacy component still works without changes
1366
- - 🎨 **Enhanced Controls**: Improved UI with better accessibility and design
1367
- - 📚 **Better TypeScript**: Comprehensive type definitions and IntelliSense
1368
- - 🏗️ **Extensible**: Easy to add custom players for new formats
1369
- - ⚡ **Performance**: Lazy loading and code splitting for faster load times
1
+ # Stormcloud Video Player
2
+
3
+ A professional video player with advanced ad integration for web applications. Built with precision ad break alignment, SCTE-35 signal parsing, VMAP ad-break scheduling, and a native HLS ad player for VAST-based ad playback. Now featuring a modern, extensible architecture inspired by react-player.
4
+
5
+ ## 🎯 Key Features
6
+
7
+ - **Multi-Format Support**: Automatic detection and playback of HLS streams and regular video files
8
+ - **Precision Ad Alignment**: Tight synchronization with SCTE-35 CUE-OUT signals
9
+ - **VMAP 1.0 Support**: Schedule pre-roll, mid-roll, and post-roll ad breaks via a VMAP manifest
10
+ - **Smart Mid-Roll Handling**: Automatic detection and playback of remaining ad portions when joining late
11
+ - **Flexible Ad Scheduling**: Support for SCTE-35 markers, VMAP manifests, and external ad schedules
12
+ - **Enhanced UI Controls**: Beautiful, adaptive video controls that work on any background color
13
+ - **Live Mode Support**: Specialized controls for live streaming with volume adjustment
14
+ - **Cross-Platform & Smart TV Ready**: Desktop, mobile, tablets, LG WebOS, Samsung Tizen, Sony BRAVIA, Android TV, Roku, Apple TV
15
+ - **Automatic Browser Compatibility**: Built-in browser detection, polyfills, and Smart TV playback overrides (e.g. native HLS)
16
+ - **React Ready**: Multiple React components for different use cases
17
+ - **TypeScript Support**: Full type definitions included
18
+ - **Professional Architecture**: Modular player system with lazy loading
19
+
20
+ ## 🚀 Quick Start
21
+
22
+ ### Installation
23
+
24
+ ```bash
25
+ npm install stormcloud-video-player hls.js
26
+ ```
27
+
28
+ ### React Integration
29
+
30
+ #### Option 1: Legacy Component (Recommended for existing projects)
31
+
32
+ ```jsx
33
+ import React from "react";
34
+ import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";
35
+
36
+ function MyVideoApp() {
37
+ return (
38
+ <StormcloudVideoPlayerComponent
39
+ src="https://your-stream.com/playlist.m3u8"
40
+ autoplay={true}
41
+ muted={true}
42
+ controls={true}
43
+ showCustomControls={true} // Enable enhanced UI controls
44
+ hideLoadingIndicator={false} // Hide the built-in loading spinner
45
+ allowNativeHls={true} // Allow native HLS for better performance
46
+ licenseKey="your_license_key_here"
47
+ vastMode="adstorm" // AdStorm VAST endpoint resolution
48
+ isVmap={true}
49
+ vmapUrl="https://your-cdn.com/ads.vmap" // Optional VMAP manifest for scheduled breaks
50
+ style={{ width: "100%", aspectRatio: "16/9" }}
51
+ wrapperStyle={{ borderRadius: "12px", overflow: "hidden" }}
52
+ onReady={(player) => {
53
+ console.log("Player is ready!", player);
54
+ }}
55
+ onVolumeToggle={() => {
56
+ console.log("Volume toggled");
57
+ }}
58
+ onFullscreenToggle={() => {
59
+ console.log("Fullscreen toggled");
60
+ }}
61
+ />
62
+ );
63
+ }
64
+ ```
65
+
66
+ #### Option 2: New Professional Component (For new projects)
67
+
68
+ ```jsx
69
+ import React from "react";
70
+ import StormcloudPlayer from "stormcloud-video-player";
71
+
72
+ function MyVideoApp() {
73
+ return (
74
+ <StormcloudPlayer
75
+ src="https://your-stream.com/playlist.m3u8"
76
+ playing={true}
77
+ muted={true}
78
+ controls={true}
79
+ width="100%"
80
+ height="auto"
81
+ style={{ aspectRatio: "16/9" }}
82
+ // Stormcloud-specific props
83
+ allowNativeHls={true}
84
+ showCustomControls={true}
85
+ hideLoadingIndicator={false}
86
+ licenseKey="your_license_key_here"
87
+ vastMode="adstorm" // Use AdStorm mode (or omit for default mode)
88
+ vmapUrl="https://your-cdn.com/ads.vmap" // Optional: VMAP manifest URL
89
+ onReady={(player) => {
90
+ console.log("Player is ready!", player);
91
+ }}
92
+ onPlay={() => console.log("Playing")}
93
+ onPause={() => console.log("Paused")}
94
+ onProgress={(state) => console.log("Progress:", state)}
95
+ />
96
+ );
97
+ }
98
+ ```
99
+
100
+ #### Option 3: Specific Player Types (Optimized bundles)
101
+
102
+ ```jsx
103
+ // For HLS streams only (smaller bundle)
104
+ import StormcloudPlayer from "stormcloud-video-player/hls";
105
+
106
+ // For regular video files only (smallest bundle)
107
+ import StormcloudPlayer from "stormcloud-video-player/file";
108
+ ```
109
+
110
+ ### Vanilla JavaScript
111
+
112
+ ```javascript
113
+ import { StormcloudVideoPlayer } from "stormcloud-video-player";
114
+
115
+ const video = document.getElementById("my-video");
116
+
117
+ const player = new StormcloudVideoPlayer({
118
+ videoElement: video,
119
+ src: "https://your-stream.com/playlist.m3u8",
120
+ autoplay: true,
121
+ muted: true,
122
+ allowNativeHls: true, // Enable native HLS when supported
123
+ showCustomControls: true, // Enable enhanced UI controls
124
+ hideLoadingIndicator: false, // Hide built-in loading spinner
125
+ lowLatencyMode: false, // Set to true for live streams
126
+ driftToleranceMs: 3000, // Drift tolerance for live streams
127
+ licenseKey: "your_license_key_here",
128
+ vastMode: "default", // "adstorm" | "default"
129
+ vmapUrl: "https://your-cdn.com/ads.vmap", // Optional VMAP manifest for scheduled ad breaks
130
+ onVolumeToggle: () => console.log("Volume toggled"),
131
+ onFullscreenToggle: () => console.log("Fullscreen toggled"),
132
+ });
133
+
134
+ await player.load();
135
+ ```
136
+
137
+ ### CDN Usage
138
+
139
+ ```html
140
+ <script src="https://cdn.jsdelivr.net/npm/stormcloud-video-player/dist/stormcloud-vp.min.js"></script>
141
+
142
+ <script>
143
+ const { StormcloudVideoPlayer } = window.StormcloudVP;
144
+
145
+ const video = document.getElementById("video");
146
+ const player = new StormcloudVideoPlayer({
147
+ videoElement: video,
148
+ src: "https://your-stream.com/playlist.m3u8",
149
+ autoplay: true,
150
+ muted: true,
151
+ });
152
+
153
+ player.load();
154
+ </script>
155
+ ```
156
+
157
+ ## 🏗️ Professional Architecture
158
+
159
+ The Stormcloud Video Player now follows a professional, modular architecture similar to react-player:
160
+
161
+ ### Player System Overview
162
+
163
+ ```
164
+ StormcloudPlayer (Main Component)
165
+ ├── Player (Internal Wrapper)
166
+ ├── HlsPlayer (HLS Stream Handler)
167
+ ├── FilePlayer (Regular Video Handler)
168
+ └── Legacy StormcloudVideoPlayerComponent
169
+ ```
170
+
171
+ ### What Each Player Does
172
+
173
+ #### 🎬 **HlsPlayer** - HLS Stream Specialist
174
+
175
+ - **Purpose**: Handles HLS (.m3u8) streams with advanced features
176
+ - **Features**:
177
+ - SCTE-35 ad marker detection and processing
178
+ - HLS ad player with VAST XML parsing (HLS and progressive media files)
179
+ - Live stream support with low-latency mode
180
+ - Drift correction for live timing
181
+ - Manifest-based and ID3 ad markers
182
+ - Ad failsafe and recovery mechanisms
183
+
184
+ ```jsx
185
+ // Automatically used for HLS streams
186
+ <StormcloudPlayer src="https://example.com/stream.m3u8" />
187
+ ```
188
+
189
+ #### 📹 **FilePlayer** - Regular Video Handler
190
+
191
+ - **Purpose**: Handles regular video files (MP4, WebM, etc.)
192
+ - **Features**:
193
+ - Direct video element control
194
+ - Picture-in-Picture support
195
+ - Standard HTML5 video features
196
+ - Lightweight and fast
197
+ - No HLS.js dependency
198
+
199
+ ```jsx
200
+ // Automatically used for regular video files
201
+ <StormcloudPlayer src="https://example.com/video.mp4" />
202
+ ```
203
+
204
+ ### Automatic Player Selection
205
+
206
+ The system automatically chooses the right player based on your video source:
207
+
208
+ ```javascript
209
+ // HLS streams → HlsPlayer
210
+ "https://example.com/playlist.m3u8"; // Uses HlsPlayer
211
+ "https://example.com/stream/index.m3u8"; // Uses HlsPlayer
212
+
213
+ // Regular videos → FilePlayer
214
+ "https://example.com/video.mp4"; // Uses FilePlayer
215
+ "https://example.com/video.webm"; // Uses FilePlayer
216
+ "https://example.com/video.mov"; // Uses FilePlayer
217
+ ```
218
+
219
+ ### Bundle Optimization
220
+
221
+ Import only what you need for smaller bundles:
222
+
223
+ ```javascript
224
+ // Full player (auto-detection) - ~140KB
225
+ import StormcloudPlayer from "stormcloud-video-player";
226
+
227
+ // HLS only - ~120KB
228
+ import StormcloudPlayer from "stormcloud-video-player/hls";
229
+
230
+ // File only - ~80KB
231
+ import StormcloudPlayer from "stormcloud-video-player/file";
232
+
233
+ // Legacy component - Full features
234
+ import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";
235
+ ```
236
+
237
+ ## 📖 API Reference
238
+
239
+ ### StormcloudPlayer Component (New)
240
+
241
+ #### Props
242
+
243
+ ```typescript
244
+ interface StormcloudPlayerProps {
245
+ // Media source
246
+ src?: string;
247
+
248
+ // Playback control
249
+ playing?: boolean;
250
+ loop?: boolean;
251
+ controls?: boolean;
252
+ volume?: number;
253
+ muted?: boolean;
254
+ playbackRate?: number;
255
+
256
+ // Styling
257
+ width?: string | number;
258
+ height?: string | number;
259
+ style?: CSSProperties;
260
+ className?: string;
261
+ wrapperClassName?: string;
262
+ wrapperStyle?: CSSProperties;
263
+
264
+ // Video attributes
265
+ playsInline?: boolean;
266
+ autoplay?: boolean;
267
+ preload?: string;
268
+ poster?: string;
269
+
270
+ // Stormcloud-specific
271
+ allowNativeHls?: boolean;
272
+ lowLatencyMode?: boolean;
273
+ driftToleranceMs?: number;
274
+ immediateManifestAds?: boolean;
275
+ debugAdTiming?: boolean;
276
+ showCustomControls?: boolean;
277
+ hideLoadingIndicator?: boolean; // Hide the built-in loading spinner (default: false)
278
+ licenseKey?: string;
279
+ adFailsafeTimeoutMs?: number;
280
+ minSegmentsBeforePlay?: number; // Number of segments to buffer before starting playback (default: 2)
281
+
282
+ // Ad player configuration
283
+ vastMode?: 'adstorm' | 'default'; // VAST endpoint resolution: 'adstorm' (/vast/{licenseKey}) or 'default' (/ads/web or vastTagUrl) (default: 'default')
284
+ vastTagUrl?: string; // Custom VAST tag URL (default mode; when omitted, /ads/web supplies the tag URL)
285
+ isVmap?: boolean; // Enable VMAP manifest scheduling (requires vmapUrl)
286
+ vmapUrl?: string; // VMAP 1.0 manifest URL for pre/mid/post-roll ad breaks (requires isVmap: true)
287
+
288
+ // Event handlers
289
+ onReady?: (player: StormcloudVideoPlayer) => void;
290
+ onStart?: () => void;
291
+ onPlay?: () => void;
292
+ onPause?: () => void;
293
+ onBuffer?: () => void;
294
+ onBufferEnd?: () => void;
295
+ onEnded?: () => void;
296
+ onError?: (
297
+ error: any,
298
+ data?: any,
299
+ hlsInstance?: any,
300
+ hlsGlobal?: any
301
+ ) => void;
302
+ onDuration?: (duration: number) => void;
303
+ onSeek?: (seconds: number) => void;
304
+ onProgress?: (state: {
305
+ played: number;
306
+ playedSeconds: number;
307
+ loaded: number;
308
+ loadedSeconds: number;
309
+ }) => void;
310
+ onVolumeToggle?: () => void;
311
+ onFullscreenToggle?: () => void;
312
+ onControlClick?: () => void;
313
+ }
314
+ ```
315
+
316
+ #### Methods
317
+
318
+ ```typescript
319
+ // Player instance methods (available via ref or onReady)
320
+ player.seekTo(amount: number, type?: 'seconds' | 'fraction')
321
+ player.getCurrentTime(): number | null
322
+ player.getSecondsLoaded(): number | null
323
+ player.getDuration(): number | null
324
+ player.getInternalPlayer(key?: string): any
325
+ ```
326
+
327
+ ### StormcloudVideoPlayer Class (Core)
328
+
329
+ #### Constructor
330
+
331
+ ```typescript
332
+ new StormcloudVideoPlayer(config: StormcloudVideoPlayerConfig)
333
+ ```
334
+
335
+ #### Methods
336
+
337
+ | Method | Description | Returns |
338
+ | ---------------------------- | --------------------------------------------- | ------------------ |
339
+ | `load()` | Initialize and start video playback | `Promise<void>` |
340
+ | `destroy()` | Clean up player resources and event listeners | `void` |
341
+ | `toggleMute()` | Toggle video mute state | `void` |
342
+ | `toggleFullscreen()` | Enter/exit fullscreen mode | `Promise<void>` |
343
+ | `isMuted()` | Check if video is currently muted | `boolean` |
344
+ | `isFullscreen()` | Check if player is in fullscreen mode | `boolean` |
345
+ | `isAdPlaying()` | Check if an ad is currently playing | `boolean` |
346
+ | `isShowingAds()` | Check if ads are being shown | `boolean` |
347
+ | `getCurrentAdIndex()` | Get current ad index in pod | `number` |
348
+ | `getTotalAdsInBreak()` | Get total ads in current break | `number` |
349
+ | `shouldShowNativeControls()` | Check if native controls should be shown | `boolean` |
350
+ | `getStreamType()` | Get detected stream type | `'hls' \| 'other'` |
351
+
352
+ #### Configuration Options
353
+
354
+ ```typescript
355
+ interface StormcloudVideoPlayerConfig {
356
+ videoElement: HTMLVideoElement; // Target video element
357
+ src: string; // Stream URL (HLS or regular video)
358
+ autoplay?: boolean; // Auto-start playback (default: false)
359
+ muted?: boolean; // Start muted (default: false)
360
+ allowNativeHls?: boolean; // Use native HLS when available (default: false)
361
+ showCustomControls?: boolean; // Enable enhanced UI controls (default: false)
362
+ hideLoadingIndicator?: boolean; // Hide the built-in loading spinner (default: false)
363
+ lowLatencyMode?: boolean; // Enable low-latency mode for live streams (default: false)
364
+ driftToleranceMs?: number; // Drift tolerance for live streams (default: 1000)
365
+ immediateManifestAds?: boolean; // Load ads immediately from manifest (default: true)
366
+ licenseKey?: string; // API authentication key
367
+ debugAdTiming?: boolean; // Enable debug logging (default: false)
368
+ adFailsafeTimeoutMs?: number; // Ad timeout in milliseconds (default: 10000)
369
+ minSegmentsBeforePlay?: number; // Number of segments to buffer before starting playback (default: 2)
370
+
371
+ // Ad break timing
372
+ adBreakCheckIntervalMs?: number; // Interval used to re-check an active ad break (default: 1000, min: 250)
373
+ maxAdBreakExtensionMs?: number; // Max time an ad break may be extended past its SCTE-35 duration when ads are still playing/queued (default: 60000)
374
+
375
+ // Ad configuration
376
+ vastMode?: 'adstorm' | 'default'; // VAST endpoint resolution: 'adstorm' (/vast/{licenseKey}) or 'default' (/ads/web or vastTagUrl) (default: 'default')
377
+ vastTagUrl?: string; // Custom VAST tag URL (default mode; when omitted, /ads/web supplies the tag URL)
378
+ isVmap?: boolean; // Enable VMAP manifest scheduling (requires vmapUrl)
379
+ vmapUrl?: string; // VMAP 1.0 manifest URL (requires isVmap: true)
380
+ ctvAdRequest?: boolean; // Include CTV device signals in VAST macro substitution
381
+ adTest?: boolean; // Enable test ad mode in VAST requests
382
+
383
+ onVolumeToggle?: () => void; // Callback for volume toggle
384
+ onFullscreenToggle?: () => void; // Callback for fullscreen toggle
385
+ onControlClick?: () => void; // Callback for control area clicks
386
+ }
387
+ ```
388
+
389
+ ## 🎨 Enhanced UI Controls
390
+
391
+ The player includes beautiful, adaptive video controls that ensure visibility on any video background:
392
+
393
+ ### Features
394
+
395
+ - **Adaptive Visibility**: High-contrast design that works on both light and dark video backgrounds
396
+ - **Live Mode Support**: Specialized controls for live streaming with hover-activated volume slider
397
+ - **Modern Design**: Glassmorphism effects with smooth animations and transitions
398
+ - **Touch-Friendly**: Large, accessible buttons optimized for mobile and desktop
399
+ - **Customizable**: Full control over appearance and behavior through props
400
+
401
+ ### Control Types
402
+
403
+ #### Full Controls (HLS Streams)
404
+
405
+ When `showCustomControls={true}` for HLS streams:
406
+
407
+ - Progress timeline with seek functionality
408
+ - Play/pause button with smooth animations
409
+ - Volume control with hover-activated vertical slider
410
+ - Playback speed menu (0.25x to 2x)
411
+ - Fullscreen toggle
412
+ - Time display (current/duration)
413
+
414
+ #### Live Mode Controls (Non-HLS or when native HLS is used)
415
+
416
+ When `showCustomControls={true}` for regular video files:
417
+
418
+ - Volume control with hover-activated slider
419
+ - Fullscreen toggle
420
+ - Compact, overlay-style positioning
421
+
422
+ ### Styling
423
+
424
+ All controls use a consistent high-contrast design:
425
+
426
+ - **Dark backgrounds** with gradient overlays
427
+ - **White borders** for clear definition
428
+ - **Enhanced shadows** for depth and separation
429
+ - **Smooth transitions** for professional feel
430
+
431
+ ```jsx
432
+ <StormcloudPlayer
433
+ showCustomControls={true}
434
+ wrapperStyle={{ borderRadius: "12px", overflow: "hidden" }}
435
+ onVolumeToggle={() => console.log("Volume toggled")}
436
+ onFullscreenToggle={() => console.log("Fullscreen toggled")}
437
+ />
438
+ ```
439
+
440
+ ## 🎬 Ad Integration (HLS Streams Only)
441
+
442
+ ### VAST Mode Configuration
443
+
444
+ The player always uses the **HLS ad player** to fetch VAST XML and play ad media (HLS or progressive). The `vastMode` option only controls **which endpoint supplies the VAST tag URL**:
445
+
446
+ #### 1. **AdStorm Mode** (Recommended)
447
+
448
+ Uses the AdStorm `/vast/{licenseKey}` endpoint, which returns VAST XML directly:
449
+
450
+ ```javascript
451
+ const player = new StormcloudVideoPlayer({
452
+ videoElement: video,
453
+ src: "https://your-stream.com/playlist.m3u8",
454
+ licenseKey: "your-license-key",
455
+
456
+ vastMode: 'adstorm',
457
+
458
+ debugAdTiming: true,
459
+ });
460
+ ```
461
+
462
+ **What happens:**
463
+ - 🔗 VAST endpoint: `GET https://adstorm.co/api-adstorm-dev/adstorm/vast/{licenseKey}`
464
+ - License key is passed in the URL path (no authorization header needed)
465
+ - Returns VAST XML directly with HLS media files
466
+ - 📊 Direct tracking and analytics through AdStorm backend
467
+ - ⚠️ Gracefully handles "no ads available" scenarios (logs warnings, not errors)
468
+
469
+ **API Flow:**
470
+ 1. Player calls `/vast/{licenseKey}` endpoint
471
+ 2. Backend returns VAST XML with HLS media files
472
+ 3. HLS ad player parses VAST XML and extracts MediaFile URLs
473
+ 4. Ad segments are loaded and played
474
+
475
+ **Benefits:**
476
+ - ✅ Zero external ad SDK dependencies
477
+ - ✅ Full control over ad serving
478
+ - ✅ Native HLS playback (same format as content)
479
+ - ✅ Better performance and reliability on Smart TVs
480
+ - ✅ Custom targeting and selection
481
+ - ✅ Proper error handling (distinguishes parsing errors from "no ads available")
482
+
483
+ #### 2. **Default Mode**
484
+
485
+ Uses the AdStorm `/ads/web` API (or a custom `vastTagUrl`) to obtain a VAST tag URL, then plays ads through the same HLS ad player:
486
+
487
+ ```javascript
488
+ const player = new StormcloudVideoPlayer({
489
+ videoElement: video,
490
+ src: "https://your-stream.com/playlist.m3u8",
491
+ licenseKey: "your-license-key",
492
+
493
+ vastMode: 'default', // or omit this property entirely
494
+ vastTagUrl: 'https://your-vast-server.com/vast.xml', // optional
495
+
496
+ debugAdTiming: true,
497
+ });
498
+ ```
499
+
500
+ **What happens:**
501
+ - 🔗 VAST tag URL resolution:
502
+ 1. If `vastTagUrl` is provided, uses that URL directly
503
+ 2. Otherwise, calls `GET https://adstorm.co/api-adstorm-dev/adstorm/ads/web`
504
+ - Requires `Authorization: Bearer {licenseKey}` header
505
+ - Returns JSON with a VAST tag URL in `response.ima["publisherdesk.ima"].payload`
506
+ - 📊 HLS ad player fetches VAST XML from the resolved tag URL and plays returned media files
507
+
508
+ **API Flow:**
509
+ 1. Player calls `/ads/web` with Bearer token (if no `vastTagUrl` provided)
510
+ 2. Backend returns JSON with a VAST tag URL in `response.ima["publisherdesk.ima"].payload`
511
+ 3. HLS ad player requests VAST XML from that URL
512
+ 4. Ad media is parsed from VAST and played
513
+
514
+ **Benefits:**
515
+ - ✅ Industry-standard VAST tag URLs (e.g. Google Ad Manager)
516
+ - ✅ Backward compatible with existing AdStorm `/ads/web` integrations
517
+ - ✅ Supports both custom VAST URLs and AdStorm backend configuration
518
+ - ✅ Same HLS ad player stack as AdStorm mode
519
+
520
+ ### Ad Pod Generation (Multiple Consecutive Ads)
521
+
522
+ The player automatically generates **ad pods** (multiple ads played consecutively) from a single VAST URL. This works differently for VOD and live streams:
523
+
524
+ #### VOD Mode: Fixed Ad Count
525
+
526
+ For **Video on Demand**, the player uses the `number_ads` field from the API response to determine how many ads to play:
527
+
528
+ **API Response Configuration:**
529
+ ```json
530
+ {
531
+ "response": {
532
+ "ima": {
533
+ "publisherdesk.ima": {
534
+ "payload": "https://pubads.g.doubleclick.net/gampad/ads?...",
535
+ "priority": 1
536
+ }
537
+ },
538
+ "options": {
539
+ "vast": {
540
+ "cue_tones": {
541
+ "number_ads": 3
542
+ }
543
+ }
544
+ }
545
+ }
546
+ }
547
+ ```
548
+
549
+ **How it works:**
550
+ 1. Player receives a single VAST URL from the API
551
+ 2. The `number_ads` field specifies how many ads should play (e.g., 3)
552
+ 3. Player generates 3 unique VAST URLs by adding different `correlator` values
553
+ 4. Each unique correlator causes GAM to return a different ad
554
+ 5. All 3 ads play consecutively in the ad break
555
+
556
+ **Example:**
557
+ ```javascript
558
+ // Base URL from API
559
+ "https://pubads.g.doubleclick.net/...&correlator="
560
+
561
+ // Generated URLs (3 ads)
562
+ [
563
+ "https://pubads.g.doubleclick.net/...&correlator=1730995200000123456780",
564
+ "https://pubads.g.doubleclick.net/...&correlator=1730995200000789012341",
565
+ "https://pubads.g.doubleclick.net/...&correlator=1730995200000345678902"
566
+ ]
567
+ ```
568
+
569
+ #### Live Mode: Adaptive Duration-Based Ad Filling
570
+
571
+ For **live streams**, the player uses an **adaptive strategy** that fetches VAST responses, extracts actual ad durations, and dynamically requests additional ads as needed to fill the SCTE-35 marker duration:
572
+
573
+ **Configuration:**
574
+ ```javascript
575
+ const player = new StormcloudVideoPlayer({
576
+ videoElement: video,
577
+ src: "https://your-live-stream.com/playlist.m3u8",
578
+ licenseKey: "your-license-key",
579
+
580
+ vastMode: 'default',
581
+ debugAdTiming: true, // Enable to see adaptive calculations
582
+ });
583
+ ```
584
+
585
+ **How Adaptive Mode Works:**
586
+
587
+ 1. **Initial Request**: Player starts with 2 initial VAST URLs
588
+ 2. **Fetch Real Duration**: As each VAST is fetched, the player extracts the actual `<Duration>` from the XML
589
+ 3. **Adaptive Calculation**: After fetching each ad, the player:
590
+ - Calculates total duration of fetched ads
591
+ - Compares against target SCTE-35 duration
592
+ - Dynamically generates more VAST URLs if needed
593
+ 4. **Smart Filling**: Uses **average** of actual fetched durations (not estimates) to calculate remaining ads
594
+ 5. **Continuous Adaptation**: Recalculates after each ad fetch until target duration is met
595
+
596
+ **Example Scenario:**
597
+ ```
598
+ SCTE-35 Duration: 120 seconds
599
+
600
+ Step 1: Start with 2 initial ads
601
+ → Fetch Ad 1: Actual duration = 45s
602
+ → Recalculate: Need (120-45)/45 = 2 more ads
603
+ → Generate 2 additional VAST URLs
604
+
605
+ Step 2: Fetch Ad 2: Actual duration = 40s
606
+ → Total so far: 85s (45s + 40s)
607
+ → Recalculate: Need (120-85)/42.5 = 1 more ad
608
+ → Generate 1 additional VAST URL
609
+
610
+ Step 3: Fetch Ad 3: Actual duration = 35s
611
+ → Total: 120s ✅ Target reached!
612
+
613
+ Final: 3 ads played (perfectly fills 120 seconds)
614
+ ```
615
+
616
+ **Debug Output:**
617
+ ```
618
+ [ADAPTIVE-POD] 📺 LIVE MODE (ADAPTIVE): Target duration=120000ms | Starting with 2 ads, will fetch actual durations and add more dynamically
619
+ [DEBUG-POD] 🔄 Generated 2 initial VAST URLs with unique correlators
620
+ [ADAPTIVE-POD] ✓ Fetched ad duration: 45s (1 ads fetched so far)
621
+ [ADAPTIVE-POD] 📊 Need 2 more ads | Fetched: 45000ms / Target: 120000ms | Remaining: 75000ms | Avg duration: 45000ms
622
+ [ADAPTIVE-POD] 🔄 Adding 2 additional VAST URLs to queue
623
+ [ADAPTIVE-POD] ✓ Fetched ad duration: 40s (2 ads fetched so far)
624
+ [ADAPTIVE-POD] 📊 Need 1 more ads | Fetched: 85000ms / Target: 120000ms | Remaining: 35000ms | Avg duration: 42500ms
625
+ [ADAPTIVE-POD] ✓ Fetched ad duration: 35s (3 ads fetched so far)
626
+ [ADAPTIVE-POD] ✅ Target duration reached: 120000ms / 120000ms
627
+ ```
628
+
629
+ **Benefits of Adaptive Approach:**
630
+
631
+ - ✅ **Accurate Filling**: Uses actual ad durations from VAST responses
632
+ - ✅ **Less Waste**: Doesn't over-request ads unnecessarily
633
+ - ✅ **Flexible**: Adapts to GAM returning 15s, 30s, 60s, or mixed-length ads
634
+ - ✅ **Efficient**: Fetches durations while preloading, no extra latency
635
+ - ✅ **Smart**: Improves calculation as more data is gathered (uses average of fetched durations)
636
+ - ✅ **Self-Correcting**: Automatically adjusts if actual ad lengths differ from expectations
637
+
638
+ ### VMAP Ad Break Scheduling
639
+
640
+ In addition to SCTE-35 markers and continuous-ad-fetch, the player supports **VMAP 1.0** manifests for scheduling ad breaks at specific positions in the timeline. This is ideal for VOD content where you want to declaratively define pre-roll, mid-roll, and post-roll breaks with explicit time offsets.
641
+
642
+ #### Usage
643
+
644
+ Provide `isVmap: true` and a `vmapUrl` in addition to (or instead of) a VAST/AdStorm configuration:
645
+
646
+ ```javascript
647
+ const player = new StormcloudVideoPlayer({
648
+ videoElement: video,
649
+ src: "https://your-stream.com/video.m3u8",
650
+ licenseKey: "your-license-key",
651
+
652
+ vastMode: 'default',
653
+ isVmap: true,
654
+ vmapUrl: 'https://your-cdn.com/schedule.vmap', // VMAP 1.0 manifest
655
+
656
+ debugAdTiming: true,
657
+ });
658
+ ```
659
+
660
+ Or with React:
661
+
662
+ ```jsx
663
+ <StormcloudPlayer
664
+ src="https://your-stream.com/video.m3u8"
665
+ licenseKey="your-license-key"
666
+ isVmap={true}
667
+ vmapUrl="https://your-cdn.com/schedule.vmap"
668
+ playing={true}
669
+ />
670
+ ```
671
+
672
+ #### Supported `timeOffset` Formats
673
+
674
+ The VMAP parser recognizes all standard `timeOffset` values:
675
+
676
+ | Value | Meaning |
677
+ | --------------------- | ---------------------------------------------------- |
678
+ | `start` | Pre-roll (playback position 0) |
679
+ | `end` | Post-roll (resolved at runtime using media duration) |
680
+ | `HH:MM:SS` / `HH:MM:SS.mmm` | Absolute timestamp mid-roll |
681
+ | `NN%` | Percentage of media duration (resolved at runtime) |
682
+
683
+ #### Example VMAP Manifest
684
+
685
+ ```xml
686
+ <?xml version="1.0" encoding="UTF-8"?>
687
+ <vmap:VMAP xmlns:vmap="http://www.iab.net/videosuite/vmap" version="1.0">
688
+ <vmap:AdBreak timeOffset="start" breakType="linear" breakId="preroll">
689
+ <vmap:AdSource>
690
+ <vmap:AdTagURI templateType="vast3">
691
+ <![CDATA[https://your-ad-server.com/vast?position=preroll]]>
692
+ </vmap:AdTagURI>
693
+ </vmap:AdSource>
694
+ </vmap:AdBreak>
695
+
696
+ <vmap:AdBreak timeOffset="00:05:00" breakType="linear" breakId="midroll-1">
697
+ <vmap:AdSource>
698
+ <vmap:AdTagURI templateType="vast3">
699
+ <![CDATA[https://your-ad-server.com/vast?position=midroll-1]]>
700
+ </vmap:AdTagURI>
701
+ </vmap:AdSource>
702
+ </vmap:AdBreak>
703
+
704
+ <vmap:AdBreak timeOffset="end" breakType="linear" breakId="postroll">
705
+ <vmap:AdSource>
706
+ <vmap:AdTagURI templateType="vast3">
707
+ <![CDATA[https://your-ad-server.com/vast?position=postroll]]>
708
+ </vmap:AdTagURI>
709
+ </vmap:AdSource>
710
+ </vmap:AdBreak>
711
+ </vmap:VMAP>
712
+ ```
713
+
714
+ #### How It Works
715
+
716
+ 1. The player fetches the VMAP manifest on load (before playback starts).
717
+ 2. Each `<AdBreak>` is parsed into an `AdBreak` object with a resolved `startTimeMs` and the associated `vastTagUrl` from `<AdTagURI>`.
718
+ 3. During playback, the player matches the current time against each scheduled break and triggers the ad request at the correct offset.
719
+ 4. **Mid-roll join behavior**: If the viewer joins mid-stream and has already passed a scheduled break, the player will honor the configured late-join policy (e.g., play the remaining portion or skip to content).
720
+ 5. Each break is only consumed once per session to avoid replaying breaks after seeking.
721
+
722
+ **Benefits:**
723
+ - ✅ Declarative ad scheduling — no custom code needed per break
724
+ - ✅ Works alongside `vastMode: 'adstorm'` and `vastMode: 'default'`
725
+ - ✅ Supports pre-roll, mid-roll, percentage-based, and post-roll breaks
726
+ - ✅ Gracefully handles malformed XML and fetch failures (logged when `debugAdTiming` is enabled)
727
+
728
+ ### Ad Player Architecture
729
+
730
+ All ads are played through the built-in **HLS ad player** (`createHlsAdPlayer`).
731
+
732
+ The player:
733
+ 1. Resolves a VAST tag URL (`vastMode`, `vastTagUrl`, VMAP, or `/ads/web`)
734
+ 2. Fetches and parses VAST XML (including wrapper chains)
735
+ 3. Plays HLS or progressive `MediaFile` URLs from the VAST response
736
+
737
+ `vastMode` only changes how the initial VAST tag URL is obtained:
738
+ - `vastMode: 'adstorm'` → `/vast/{licenseKey}` returns VAST XML directly (used as the tag URL for pod generation)
739
+ - `vastMode: 'default'` → `/ads/web` or `vastTagUrl` supplies the tag URL; the HLS ad player fetches VAST from that URL
740
+
741
+ ### SCTE-35 Support
742
+
743
+ The HlsPlayer automatically detects and responds to SCTE-35 signals embedded in HLS streams:
744
+
745
+ - **CUE-OUT**: Triggers ad break start
746
+ - **CUE-OUT-CONT**: Handles mid-roll continuation
747
+ - **CUE-IN**: Resumes content playback
748
+ - **DATERANGE**: Processes time-based ad markers
749
+
750
+ ### Supported HLS Tags
751
+
752
+ - `#EXT-X-CUE-OUT`
753
+ - `#EXT-X-CUE-OUT-CONT`
754
+ - `#EXT-X-CUE-IN`
755
+ - `#EXT-X-DATERANGE`
756
+ - ID3 timed metadata
757
+
758
+ ### Early Ad Prefetching & Preload Pool
759
+
760
+ The player includes an advanced ad prefetching system that dramatically reduces ad start latency:
761
+
762
+ #### Early SCTE-35 Detection
763
+
764
+ The player scans up to 5 manifest fragments ahead of current playback to detect upcoming SCTE-35 ad markers. When an ad break is detected early:
765
+
766
+ 1. **Prefetching Begins**: The player immediately starts generating VAST URLs and prefetching ads
767
+ 2. **Preload Pool**: Up to 3 ads are preloaded and ready to play instantly
768
+ 3. **Zero-Delay Starts**: When the ad break actually begins, ads start immediately (no request delay)
769
+
770
+ **Benefits:**
771
+ - ⚡ **80% reduction** in ad start latency
772
+ - 🎯 **Instant ad playback** when breaks begin
773
+ - 📊 **Better fill rates** through proactive ad preparation
774
+ - 🔄 **Seamless transitions** between content and ads
775
+
776
+ #### How It Works
777
+
778
+ ```javascript
779
+ const player = new StormcloudVideoPlayer({
780
+ videoElement: video,
781
+ src: "https://your-stream.com/playlist.m3u8",
782
+ licenseKey: "your-license-key",
783
+ debugAdTiming: true, // Enable to see prefetching logs
784
+ });
785
+
786
+ // The player automatically:
787
+ // 1. Scans manifest fragments for SCTE-35 markers
788
+ // 2. Detects upcoming ad breaks before playback reaches them
789
+ // 3. Prefetches and preloads ads into a pool
790
+ // 4. Starts ads instantly when breaks begin
791
+ ```
792
+
793
+ **Debug Output Example:**
794
+ ```
795
+ [PREFETCH] 🔄 Starting ad prefetch for upcoming ad break
796
+ [PREFETCH] 📋 Pre-generated 5 VAST URLs
797
+ [PRELOAD-POOL] 🏊 Starting preload pool EARLY (target size: 3)
798
+ [PRELOAD-POOL] 📥 Preloading ad into pool: https://...
799
+ [PRELOAD-POOL] ✅ Ad preloaded (pool size: 1/3)
800
+ [CONTINUOUS-FETCH] 🚀 Using preloaded ad from pool (preloaded in advance, ready immediately!)
801
+ ```
802
+
803
+ #### Continuous Ad Fetching
804
+
805
+ During ad breaks, the player continuously fetches additional ads to fill the entire SCTE-35 duration:
806
+
807
+ - **Dynamic Queue**: Maintains a queue of ready-to-play VAST URLs
808
+ - **Smart Rate Limiting**: 2.5s minimum interval between requests with exponential backoff
809
+ - **Automatic Filling**: Fetches ads until the SCTE-35 duration is fully filled
810
+ - **Error Recovery**: Distinguishes temporary failures (no-fill) from permanent failures
811
+
812
+ **Rate Limiting & Backoff:**
813
+ - Base interval: 2.5 seconds between requests
814
+ - Exponential backoff: Increases with consecutive failures
815
+ - Max backoff: 15 seconds
816
+ - Cooldown period: 30 seconds for temporary failures (no-fill)
817
+
818
+ #### Error Handling & Recovery
819
+
820
+ The player intelligently handles different types of ad failures:
821
+
822
+ **Temporary Failures (Retryable):**
823
+ - No-fill responses (no ads available)
824
+ - Network timeouts
825
+ - Rate limiting errors
826
+ - **Action**: URL enters 30s cooldown, then can be retried
827
+
828
+ **Permanent Failures (Blacklisted):**
829
+ - VAST parsing errors
830
+ - Malformed responses
831
+ - **Action**: URL is permanently blacklisted for the current ad break
832
+
833
+ **Fallback System:**
834
+ - If a primary ad request fails, the player automatically tries a preloaded ad from the pool
835
+ - Ensures continuous ad playback even when individual requests fail
836
+
837
+ ### Late Join Behavior
838
+
839
+ When viewers join during an ad break:
840
+
841
+ - **play_remaining**: Plays the remaining portion of the current ad
842
+ - **skip_to_content**: Skips to main content (configurable via API)
843
+
844
+ ### Ad Status Monitoring
845
+
846
+ ```jsx
847
+ <StormcloudPlayer
848
+ onReady={(player) => {
849
+ // Monitor ad playback
850
+ console.log("Is ad playing:", player.isAdPlaying());
851
+ console.log("Current ad index:", player.getCurrentAdIndex());
852
+ console.log("Total ads in break:", player.getTotalAdsInBreak());
853
+ }}
854
+ />
855
+ ```
856
+
857
+ ### Error Handling
858
+
859
+ The player distinguishes between different types of ad-related issues:
860
+
861
+ **"No Ads Available" (Warning):**
862
+ - When the VAST response indicates no ads are available (e.g., empty `<MediaFiles>` or `AdTitle: "No Ad Available"`)
863
+ - Logs warnings, not errors
864
+ - Content playback continues normally
865
+ - No error events are emitted
866
+
867
+ **VAST XML Parsing Errors (Error):**
868
+ - When the VAST XML is malformed or cannot be parsed
869
+ - Logs errors
870
+ - Emits `ad_error` event
871
+ - Content playback continues
872
+
873
+ **Network/Fetch Errors (Error):**
874
+ - When the VAST endpoint is unreachable or returns an error status
875
+ - Logs errors
876
+ - Emits `ad_error` event
877
+ - Content playback continues
878
+
879
+ Example with error handling:
880
+
881
+ ```javascript
882
+ const player = new StormcloudVideoPlayer({
883
+ // ... config
884
+ vastMode: 'adstorm',
885
+ debugAdTiming: true, // Enable detailed logging
886
+ });
887
+
888
+ // Monitor ad events
889
+ player.on('ad_error', (error) => {
890
+ console.error('Ad error occurred:', error);
891
+ // Handle error (e.g., show fallback, retry, etc.)
892
+ });
893
+ ```
894
+
895
+ ## 🔐 Authentication
896
+
897
+ The player supports license key authentication for enhanced features:
898
+
899
+ ```javascript
900
+ const player = new StormcloudVideoPlayer({
901
+ // ... other config
902
+ licenseKey: "ADSTORM-YOUR-LICENSE-KEY-HERE",
903
+ });
904
+ ```
905
+
906
+ Authenticated requests are sent to:
907
+
908
+ - **AdStorm Mode** (`vastMode: 'adstorm'`):
909
+ - VAST endpoint: `GET https://adstorm.co/api-adstorm-dev/adstorm/vast/{licenseKey}` (license key in URL path)
910
+
911
+ - **Default Mode** (`vastMode: 'default'`):
912
+ - Ad configuration: `GET https://adstorm.co/api-adstorm-dev/adstorm/ads/web` (requires `Authorization: Bearer {licenseKey}` header)
913
+ - Returns JSON with a VAST tag URL in `response.ima["publisherdesk.ima"].payload`
914
+
915
+ - **Player Tracking** (both modes):
916
+ - Metrics / heartbeat: `POST https://player-analytics-ingest-api-760490581852.us-central1.run.app/api/adstorm/player-tracking/metrics/ingest` (requires `Authorization: License-Key {licenseKey}` header)
917
+ - Ad impressions: `POST https://player-analytics-ingest-api-760490581852.us-central1.run.app/api/adstorm/player-tracking/impressions/ingest` (requires `Authorization: License-Key {licenseKey}` header)
918
+ - Ad-detect, ad-loaded, and ad-impression events are automatically emitted during ad breaks.
919
+
920
+ ### Tracking Utilities
921
+
922
+ The player exposes low-level tracking helpers if you want to fire events manually or build custom analytics:
923
+
924
+ ```javascript
925
+ import {
926
+ getClientInfo,
927
+ getBrowserID,
928
+ sendInitialTracking,
929
+ sendHeartbeat,
930
+ sendAdDetectTracking,
931
+ sendAdLoadedTracking,
932
+ sendAdImpressionTracking,
933
+ } from "stormcloud-video-player";
934
+
935
+ const clientInfo = getClientInfo();
936
+ const browserId = getBrowserID();
937
+
938
+ await sendInitialTracking(licenseKey, clientInfo);
939
+ await sendHeartbeat(licenseKey, { browserId, timestamp: new Date().toISOString() });
940
+
941
+ // Ad lifecycle tracking
942
+ await sendAdDetectTracking(licenseKey, {
943
+ source: "scte35",
944
+ durationSeconds: 30,
945
+ ptsSeconds: 120.5,
946
+ detectedAtFragmentSn: 1234,
947
+ timestamp: new Date().toISOString(),
948
+ });
949
+
950
+ await sendAdLoadedTracking(licenseKey, {
951
+ source: "hls", // 'vast' | 'hls' (ad playback path)
952
+ vastUrl: "https://...",
953
+ durationSeconds: 15,
954
+ timestamp: new Date().toISOString(),
955
+ });
956
+
957
+ await sendAdImpressionTracking(licenseKey, {
958
+ source: "hls",
959
+ adIndex: 0,
960
+ durationSeconds: 15,
961
+ timestamp: new Date().toISOString(),
962
+ });
963
+ ```
964
+
965
+ ## 🔧 Advanced Configuration
966
+
967
+ ### Custom Player Creation
968
+
969
+ Create your own player with specific capabilities:
970
+
971
+ ```javascript
972
+ import { createStormcloudPlayer, players } from "stormcloud-video-player";
973
+
974
+ // Create a player with only HLS support
975
+ const HLSOnlyPlayer = createStormcloudPlayer([
976
+ players.find((p) => p.key === "hls"),
977
+ ]);
978
+
979
+ // Create a player with custom fallback
980
+ const CustomPlayer = createStormcloudPlayer(
981
+ players,
982
+ players.find((p) => p.key === "file") // fallback to file player
983
+ );
984
+ ```
985
+
986
+ ### Adding Custom Players
987
+
988
+ ```javascript
989
+ import StormcloudPlayer from "stormcloud-video-player";
990
+
991
+ // Add a custom player for a specific format
992
+ StormcloudPlayer.addCustomPlayer({
993
+ key: "custom",
994
+ name: "CustomPlayer",
995
+ canPlay: (src) => src.includes("custom://"),
996
+ lazyPlayer: React.lazy(() => import("./CustomPlayer")),
997
+ });
998
+ ```
999
+
1000
+ ### Buffering Configuration
1001
+
1002
+ #### Min Segments Before Play
1003
+
1004
+ Control how many segments must be buffered before playback starts. This helps ensure smooth playback, especially on slower connections:
1005
+
1006
+ ```javascript
1007
+ const player = new StormcloudVideoPlayer({
1008
+ videoElement: video,
1009
+ src: "https://your-stream.com/playlist.m3u8",
1010
+ autoplay: true,
1011
+ minSegmentsBeforePlay: 3, // Wait for 3 segments before starting (default: 2)
1012
+ });
1013
+
1014
+ // Or with React component
1015
+ <StormcloudPlayer
1016
+ src="https://your-stream.com/playlist.m3u8"
1017
+ playing={true}
1018
+ minSegmentsBeforePlay={3}
1019
+ />
1020
+ ```
1021
+
1022
+ **Configuration Options:**
1023
+ - `minSegmentsBeforePlay: 0` - Start immediately (may cause stuttering on slow connections)
1024
+ - `minSegmentsBeforePlay: 2` - Default, good balance for most use cases
1025
+ - `minSegmentsBeforePlay: 3-5` - Recommended for slower connections or higher quality streams
1026
+ - `minSegmentsBeforePlay: undefined` - Uses default value (2)
1027
+
1028
+ **When to Adjust:**
1029
+ - **Increase** for high-bitrate streams or unreliable networks
1030
+ - **Decrease** for low-latency requirements or fast connections
1031
+ - **Set to 0** only if you need immediate playback and can tolerate potential stuttering
1032
+
1033
+ ### Player Detection
1034
+
1035
+ ```javascript
1036
+ import StormcloudPlayer from "stormcloud-video-player";
1037
+
1038
+ // Check if a URL can be played
1039
+ const canPlay = StormcloudPlayer.canPlay("https://example.com/video.m3u8");
1040
+ console.log("Can play:", canPlay); // true
1041
+
1042
+ // Check Picture-in-Picture support
1043
+ const canPIP = StormcloudPlayer.canEnablePIP("https://example.com/video.mp4");
1044
+ console.log("Supports PIP:", canPIP); // true for file player
1045
+ ```
1046
+
1047
+ ### Event Handling
1048
+
1049
+ ```jsx
1050
+ <StormcloudPlayer
1051
+ onReady={(player) => {
1052
+ console.log("Player ready");
1053
+ }}
1054
+ onPlay={() => {
1055
+ console.log("Playback started");
1056
+ }}
1057
+ onPause={() => {
1058
+ console.log("Playback paused");
1059
+ }}
1060
+ onProgress={(state) => {
1061
+ console.log("Progress:", state.playedSeconds, "of", state.loadedSeconds);
1062
+ }}
1063
+ onError={(error, data, hlsInstance, hlsGlobal) => {
1064
+ console.error("Player error:", error);
1065
+ if (hlsInstance) {
1066
+ console.log("HLS instance available for recovery");
1067
+ }
1068
+ }}
1069
+ />
1070
+ ```
1071
+
1072
+ ## 🌐 Browser & Smart TV Support
1073
+
1074
+ - **Desktop**: Chrome 60+, Firefox 55+, Safari 12+, Edge 79+
1075
+ - **Mobile**: iOS Safari 12+, Chrome Mobile 60+
1076
+ - **Smart TV**: LG WebOS (v2+), Samsung Tizen (v2+), Sony BRAVIA, Android TV, Roku, Apple TV, generic Smart TV user-agents
1077
+
1078
+ ### Automatic Browser Detection & Smart TV Overrides
1079
+
1080
+ The player detects the runtime environment and applies Smart TV-friendly defaults via `getBrowserConfigOverrides()`.
1081
+
1082
+ ```javascript
1083
+ import {
1084
+ detectBrowser,
1085
+ supportsModernJS,
1086
+ logBrowserInfo,
1087
+ getBrowserConfigOverrides,
1088
+ supportsFeature,
1089
+ } from "stormcloud-video-player";
1090
+
1091
+ const info = detectBrowser();
1092
+ // {
1093
+ // name: 'LG WebOS',
1094
+ // version: '5.0',
1095
+ // majorVersion: 5,
1096
+ // isSmartTV: true,
1097
+ // isLegacyTV: false,
1098
+ // supportsModernJS: true,
1099
+ // webOSVersion: 5,
1100
+ // chromeVersion: 79,
1101
+ // ...
1102
+ // }
1103
+
1104
+ console.log(supportsModernJS()); // true | false
1105
+ console.log(supportsFeature('fetch')); // true | false
1106
+ console.log(getBrowserConfigOverrides()); // e.g. { allowNativeHls: true } on Smart TVs
1107
+ ```
1108
+
1109
+ **What happens automatically:**
1110
+
1111
+ - **Smart TVs** (LG WebOS, Samsung Tizen, etc.): `allowNativeHls` is enabled by default (TVs generally play HLS better natively).
1112
+ - **Browser overrides** are merged with your config — user-provided options always take precedence.
1113
+ - **Ads**: HLS ad player on all supported devices.
1114
+
1115
+ Call `logBrowserInfo(true)` (or enable `debugAdTiming`) to print a detailed compatibility report to the console.
1116
+
1117
+ ### Automatic Polyfills for Legacy Devices
1118
+
1119
+ On older Smart TVs and browsers, `initializePolyfills()` is invoked automatically at construction time. It adds missing primitives needed for the player to run:
1120
+
1121
+ - `URLSearchParams`
1122
+ - `TextEncoder`
1123
+ - `Promise.prototype.finally`
1124
+ - `Object.assign`
1125
+ - `Array.from`
1126
+ - `String.prototype.startsWith` / `endsWith` / `includes`
1127
+
1128
+ You can also run it manually before bootstrapping anything else:
1129
+
1130
+ ```javascript
1131
+ import { initializePolyfills } from "stormcloud-video-player";
1132
+
1133
+ initializePolyfills();
1134
+ ```
1135
+
1136
+ ### Format Support by Player
1137
+
1138
+ #### HlsPlayer
1139
+
1140
+ - **HLS Streams**: `.m3u8` files
1141
+ - **Native HLS**: Safari, iOS Safari (when `allowNativeHls=true`)
1142
+ - **HLS.js**: Chrome, Firefox, Edge (automatic fallback)
1143
+
1144
+ #### FilePlayer
1145
+
1146
+ - **Video Formats**: MP4, WebM, MOV, AVI, OGV
1147
+ - **Audio Formats**: MP3, WAV, OGG, AAC
1148
+ - **Streaming**: Progressive download
1149
+ - **Picture-in-Picture**: Supported browsers
1150
+
1151
+ ## 🏗️ Development
1152
+
1153
+ ### Build Commands
1154
+
1155
+ ```bash
1156
+ # Build everything
1157
+ npm run build:all
1158
+
1159
+ # Individual builds
1160
+ npm run build:lib # Library build (ESM/CJS)
1161
+ npm run build:minified # Minified UMD bundle
1162
+ npm run build:dist # Production UMD bundle
1163
+
1164
+ # Development
1165
+ npm run dev # Development watch mode
1166
+ npm run clean # Clean build artifacts
1167
+ npm run test # Run test suite
1168
+ npm run lint # Lint codebase
1169
+ ```
1170
+
1171
+ ### Project Structure
1172
+
1173
+ ```
1174
+ src/
1175
+ ├── index.ts # Main exports
1176
+ ├── StormcloudPlayer.tsx # New main component
1177
+ ├── Player.tsx # Internal wrapper component
1178
+ ├── players/
1179
+ │ ├── index.ts # Player registry
1180
+ │ ├── HlsPlayer.tsx # HLS stream handler
1181
+ │ └── FilePlayer.tsx # Regular video handler
1182
+ ├── player/
1183
+ │ └── StormcloudVideoPlayer.ts # Core player class (SCTE-35, VMAP, ad pods)
1184
+ ├── ui/
1185
+ │ └── StormcloudVideoPlayer.tsx # Legacy React component
1186
+ ├── sdk/
1187
+ │ ├── pal.ts # Google PAL nonce manager
1188
+ │ └── hlsAdPlayer.ts # HLS / progressive VAST ad player
1189
+ ├── utils/
1190
+ │ ├── tracking.ts # Analytics and ad tracking
1191
+ │ ├── browserCompat.ts # Browser / Smart TV detection & auto-overrides
1192
+ │ └── polyfills.ts # Legacy browser polyfills
1193
+ ├── props.ts # Centralized props system
1194
+ ├── patterns.ts # URL pattern matching
1195
+ ├── utils.ts # Shared utilities
1196
+ └── types.ts # TypeScript definitions
1197
+ ```
1198
+
1199
+ ### Export Structure
1200
+
1201
+ ```
1202
+ stormcloud-video-player/
1203
+ ├── index.js # Main entry (auto-detection)
1204
+ ├── hls.js # HLS-only entry
1205
+ ├── file.js # File-only entry
1206
+ ├── base.d.ts # Base type definitions
1207
+ ├── hls.d.ts # HLS-specific types
1208
+ ├── file.d.ts # File-specific types
1209
+ ├── lib/ # Compiled library
1210
+ ├── dist/ # UMD bundles
1211
+ └── scripts/ # Build utilities
1212
+ ```
1213
+
1214
+ ## 🎯 Use Cases
1215
+
1216
+ ### By Player Type
1217
+
1218
+ #### HlsPlayer Use Cases
1219
+
1220
+ - **Live Streaming**: Sports, news, and event broadcasts
1221
+ - **Live Shopping**: E-commerce live streams with ads
1222
+ - **FAST Channels**: Free ad-supported streaming TV
1223
+ - **Linear TV**: Traditional broadcast over IP
1224
+ - **Event Streaming**: Conferences, concerts, webinars
1225
+
1226
+ #### FilePlayer Use Cases
1227
+
1228
+ - **VOD Platforms**: Movie and series streaming services
1229
+ - **Educational Content**: E-learning and training platforms
1230
+ - **Corporate Communications**: Internal videos and presentations
1231
+ - **Digital Signage**: Retail and public display systems
1232
+ - **Social Media**: User-generated content playback
1233
+
1234
+ ### Migration Guide
1235
+
1236
+ #### From Legacy Component
1237
+
1238
+ ```jsx
1239
+ // Old way (still works)
1240
+ import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";
1241
+
1242
+ <StormcloudVideoPlayerComponent
1243
+ src="https://example.com/stream.m3u8"
1244
+ autoplay={true}
1245
+ // ... other props
1246
+ />;
1247
+
1248
+ // New way (recommended for new projects)
1249
+ import StormcloudPlayer from "stormcloud-video-player";
1250
+
1251
+ <StormcloudPlayer
1252
+ src="https://example.com/stream.m3u8"
1253
+ playing={true} // Note: 'playing' instead of 'autoplay'
1254
+ // ... other props
1255
+ />;
1256
+ ```
1257
+
1258
+ #### Key Differences
1259
+
1260
+ | Legacy Component | New Component | Notes |
1261
+ | ------------------------ | -------------------------- | --------------------------- |
1262
+ | `autoplay={true}` | `playing={true}` | More React-like prop naming |
1263
+ | Always loads full bundle | Supports optimized imports | Better performance |
1264
+ | Single component | Modular architecture | More maintainable |
1265
+ | Fixed UI structure | Flexible wrapper system | More customizable |
1266
+
1267
+ ## ⚠️ Known Limitations
1268
+
1269
+ - Requires DOM environment (browser-only)
1270
+ - SCTE-35 binary splice parsing not yet implemented
1271
+ - Multi-ad pod competitive separation handled by ad server
1272
+ - Low-latency HLS (LL-HLS) optimizations in development
1273
+ - Picture-in-Picture only available in FilePlayer
1274
+
1275
+ ## 🗺️ Roadmap
1276
+
1277
+ ### Upcoming Features
1278
+
1279
+ - **Enhanced SCTE-35**: Full binary splice_info_section parsing
1280
+ - **Advanced Late Join**: Improved partial pod handling
1281
+ - **LL-HLS Support**: Low-latency streaming optimizations
1282
+ - **Rich Analytics**: Comprehensive event tracking and reporting
1283
+ - **UI Themes**: Multiple control themes and color schemes
1284
+ - **Accessibility**: Enhanced ARIA support and keyboard navigation
1285
+ - **Error Recovery**: Advanced retry logic and failover handling
1286
+ - **More Players**: DASH, YouTube, Vimeo player implementations
1287
+
1288
+ ### Performance Improvements
1289
+
1290
+ - Drift correction between PTS and wall-clock timing
1291
+ - Memory optimization for long-running sessions
1292
+ - Network bandwidth adaptation
1293
+ - Smart preloading strategies
1294
+ - Code splitting and lazy loading optimizations
1295
+
1296
+ ## 🤝 Contributing
1297
+
1298
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1299
+
1300
+ ## 📄 License
1301
+
1302
+ MIT License - see [LICENSE](LICENSE) file for details.
1303
+
1304
+ ---
1305
+
1306
+ Built with ❤️ by the Stormcloud team
1307
+
1308
+ ### What's New in v0.5
1309
+
1310
+ - 🗓️ **VMAP 1.0 Support**: `isVmap` + `vmapUrl` load a VMAP manifest and schedule pre-roll, mid-roll, percentage-based, and post-roll breaks automatically
1311
+ - Supports `start`, `end`, `HH:MM:SS[.mmm]`, and `NN%` `timeOffset` values
1312
+ - Namespaced (`vmap:AdBreak`) and non-namespaced manifests both supported
1313
+ - Breaks are consumed-once-per-session and integrate with the existing late-join policy
1314
+ - 📺 **Smart TV First-Class Support**: New browser-compat layer auto-detects LG WebOS, Samsung Tizen, Sony BRAVIA, LG NetCast and generic Smart TV UAs
1315
+ - Exports `detectBrowser`, `supportsModernJS`, `logBrowserInfo`, `getBrowserConfigOverrides`, `supportsFeature`
1316
+ - Automatically enables `allowNativeHls` on Smart TVs for more reliable playback
1317
+ - 🧩 **Automatic Polyfills**: `initializePolyfills()` runs at construction time and backfills `URLSearchParams`, `TextEncoder`, `Promise.prototype.finally`, `Object.assign`, `Array.from`, and `String.prototype.startsWith/endsWith/includes` for legacy environments
1318
+ - 📊 **Expanded Ad Tracking**: New `sendAdDetectTracking`, `sendAdLoadedTracking`, and `sendAdImpressionTracking` helpers (plus `AdDetectInfo`, `AdLoadedInfo`, `AdImpressionInfo` types) for SCTE-35 and HLS ad lifecycle events
1319
+ - ⚙️ **Ad-Break Timing Controls**: New `adBreakCheckIntervalMs` (default 1000ms, min 250ms) and `maxAdBreakExtensionMs` (default 60000ms) options give you precise control over how long the player is allowed to extend an ad break past its SCTE-35 duration when ads are still playing or queued
1320
+ - 🙈 **Hide Loading Indicator**: New `hideLoadingIndicator` prop/config hides the built-in buffering spinner when you want to render your own overlay
1321
+ - 🔌 **Expanded Public API**: `createHlsAdPlayer`, `createPalNonceManager`, and `initializePolyfills` are exported alongside browser-compat utilities for custom integrations
1322
+
1323
+ ### What's New in v0.4
1324
+
1325
+ - 🚀 **Early Ad Prefetching**: Detects SCTE-35 markers in manifest fragments before playback reaches them, prefetching ads in advance for zero-delay ad starts
1326
+ - 🏊 **Ad Preload Pool**: Maintains a pool of preloaded, ready-to-play ads (up to 3 by default) for instant ad playback when breaks start
1327
+ - 🔄 **Continuous Ad Fetching**: Dynamically fetches additional ads during ad breaks to perfectly fill SCTE-35 durations, ensuring no wasted ad time
1328
+ - ⏱️ **Smart Rate Limiting**: Intelligent rate limiting (2.5s minimum interval) with exponential backoff to prevent ad server overload
1329
+ - 🛡️ **Advanced Error Handling**: Distinguishes between temporary failures (no-fill, timeouts) and permanent failures, with 30s cooldown periods for retryable errors
1330
+ - ⏳ **Min Segments Before Play**: New `minSegmentsBeforePlay` option (default: 2) to ensure smooth playback start by buffering multiple segments
1331
+ - 🎯 **Ad Request Watchdog**: Timeout system prevents hanging ad requests and automatically recovers from stuck states
1332
+ - ⬛ **Placeholder System**: Seamless placeholder layer shown during ad transitions, preventing content flash between ads
1333
+ - 📊 **Improved Queue Management**: Better handling of ad request queues with automatic cleanup and failure tracking
1334
+ - 🔍 **Early SCTE-35 Detection**: Scans up to 5 manifest fragments ahead to detect upcoming ad breaks, enabling proactive ad preparation
1335
+ - ⚡ **Zero-Delay Ad Starts**: Preloaded ads start instantly when ad breaks begin, eliminating the traditional ad request delay
1336
+ - 🎬 **Fallback Ad System**: Automatic fallback to preloaded ads when primary ad requests fail, ensuring continuous ad playback
1337
+
1338
+ **Performance Improvements:**
1339
+ - Reduced ad start latency by up to 80% through prefetching and preloading
1340
+ - Better resource utilization with intelligent ad pool management
1341
+ - Improved reliability with comprehensive error recovery mechanisms
1342
+ - Enhanced user experience with seamless ad transitions
1343
+
1344
+ ### What's New in v0.3
1345
+
1346
+ - 🎬 **Custom HLS Ad Player**: Native HLS ad playback with VAST XML parsing
1347
+ - 🎯 **VAST Tag Resolution**: `vastMode` selects AdStorm `/vast/{licenseKey}` vs `/ads/web` or custom `vastTagUrl`
1348
+ - 📊 **Direct Analytics**: Full control over ad tracking and metrics
1349
+ - ⚡ **Better Performance**: Native HLS playback for ads (same format as content)
1350
+ - 🔧 **Custom VAST URLs**: Point to your own ad serving backend
1351
+ - 📦 **Zero external ad SDK dependencies**
1352
+ - 🎨 **Seamless Playback**: Same player stack for content and ads
1353
+ - 🔀 **VAST Mode System**: `vastMode` configures which endpoint supplies the VAST tag URL
1354
+ - `vastMode: 'adstorm'` → `/vast/{licenseKey}` endpoint
1355
+ - `vastMode: 'default'` → `/ads/web` endpoint or `vastTagUrl` (both resolve to HLS ad player playback)
1356
+ - ⚠️ **Improved Error Handling**: Distinguishes between parsing errors and "no ads available" scenarios
1357
+ - Logs warnings for "no ads available" (graceful handling)
1358
+ - Logs errors for actual parsing/fetch failures
1359
+
1360
+ ### What's New in v0.2
1361
+
1362
+ - 🎯 **Professional Architecture**: Modular player system inspired by react-player
1363
+ - 🚀 **Automatic Format Detection**: Smart player selection based on video source
1364
+ - 📦 **Optimized Bundles**: Import only what you need (HLS-only, File-only, or Full)
1365
+ - 🔄 **Backward Compatibility**: Legacy component still works without changes
1366
+ - 🎨 **Enhanced Controls**: Improved UI with better accessibility and design
1367
+ - 📚 **Better TypeScript**: Comprehensive type definitions and IntelliSense
1368
+ - 🏗️ **Extensible**: Easy to add custom players for new formats
1369
+ - ⚡ **Performance**: Lazy loading and code splitting for faster load times