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.
- package/LICENSE +21 -21
- package/README.md +1369 -1369
- package/dist/stormcloud-vp.min.js +3 -1
- package/lib/Player.cjs +548 -0
- package/lib/Player.cjs.map +1 -0
- package/lib/Player.d.cts +146 -0
- package/lib/{index.js → StormcloudPlayer.cjs} +1108 -1877
- package/lib/StormcloudPlayer.cjs.map +1 -0
- package/lib/{index.d.ts → StormcloudPlayer.d.cts} +7 -400
- package/lib/index.cjs +979 -251
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +18 -4032
- package/lib/patterns.cjs +111 -0
- package/lib/patterns.cjs.map +1 -0
- package/lib/patterns.d.cts +14 -0
- package/lib/player/AdBreakOrchestrator.cjs +48 -14
- package/lib/player/AdBreakOrchestrator.cjs.map +1 -1
- package/lib/player/AdBreakOrchestrator.d.cts +3 -1
- package/lib/player/AdConfigManager.cjs +155 -17
- package/lib/player/AdConfigManager.cjs.map +1 -1
- package/lib/player/AdConfigManager.d.cts +1 -1
- package/lib/player/AdTimingService.cjs.map +1 -1
- package/lib/player/AdTimingService.d.cts +1 -1
- package/lib/player/HlsEngine.cjs.map +1 -1
- package/lib/player/HlsEngine.d.cts +1 -1
- package/lib/player/PlaceholderLayer.cjs.map +1 -1
- package/lib/player/PlayerControls.cjs.map +1 -1
- package/lib/player/PlayerControls.d.cts +1 -1
- package/lib/player/Scte35CueManager.cjs.map +1 -1
- package/lib/player/Scte35CueManager.d.cts +1 -1
- package/lib/player/Scte35Parser.cjs.map +1 -1
- package/lib/player/Scte35Parser.d.cts +1 -1
- package/lib/player/StormcloudVideoPlayer.cjs +979 -251
- package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/player/StormcloudVideoPlayer.d.cts +1 -1
- package/lib/player/playerTypes.cjs.map +1 -1
- package/lib/player/playerTypes.d.cts +1 -1
- package/lib/players/FilePlayer.cjs.map +1 -1
- package/lib/players/HlsPlayer.cjs +979 -251
- package/lib/players/HlsPlayer.cjs.map +1 -1
- package/lib/players/HlsPlayer.d.cts +1 -1
- package/lib/players/index.cjs +979 -251
- package/lib/players/index.cjs.map +1 -1
- package/lib/props.cjs +186 -0
- package/lib/props.cjs.map +1 -0
- package/lib/props.d.cts +62 -0
- package/lib/sdk/hlsAdPlayer.cjs +884 -234
- package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
- package/lib/sdk/hlsAdPlayer.d.cts +1 -1
- package/lib/sdk/pal.cjs.map +1 -1
- package/lib/types.cjs +49 -0
- package/lib/types.cjs.map +1 -0
- package/lib/{types-52ASml2N.d.cts → types.d.cts} +47 -1
- package/lib/ui/StormcloudVideoPlayer.cjs +979 -251
- package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
- package/lib/utils/browserCompat.cjs.map +1 -1
- package/lib/utils/ctvVastSignals.cjs +155 -17
- package/lib/utils/ctvVastSignals.cjs.map +1 -1
- package/lib/utils/devUrl.cjs.map +1 -1
- package/lib/utils/polyfills.cjs.map +1 -1
- package/lib/utils/tracking.cjs +42 -14
- package/lib/utils/tracking.cjs.map +1 -1
- package/lib/utils/tracking.d.cts +1 -1
- package/lib/utils/trackingIngest.cjs.map +1 -1
- package/lib/utils/vastEnvironmentSignals.cjs +167 -17
- package/lib/utils/vastEnvironmentSignals.cjs.map +1 -1
- package/lib/utils/vastEnvironmentSignals.d.cts +12 -1
- package/lib/utils/vastMacros.cjs +26 -0
- package/lib/utils/vastMacros.cjs.map +1 -1
- package/lib/utils/vastTrackingMacros.cjs +5 -0
- package/lib/utils/vastTrackingMacros.cjs.map +1 -1
- package/lib/utils/vastTrackingMacros.d.cts +2 -0
- package/lib/utils.cjs +292 -0
- package/lib/utils.cjs.map +1 -0
- package/lib/utils.d.cts +17 -0
- package/package.json +91 -91
- package/rollup.config.js +90 -90
- 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
|