stormcloud-video-player
v0.3.53
Published
Ad-first HLS video player with SCTE-35 support and Google IMA integration for precise ad break alignment
Maintainers
Readme
Stormcloud Video Player
A professional video player with advanced ad integration for web applications. Built with precision ad break alignment, SCTE-35 signal parsing, custom VAST ad serving, and optional Google IMA SDK integration for seamless ad playback. Now featuring a modern, extensible architecture inspired by react-player.
🎯 Key Features
- Multi-Format Support: Automatic detection and playback of HLS streams and regular video files
- Precision Ad Alignment: Tight synchronization with SCTE-35 CUE-OUT signals
- Smart Mid-Roll Handling: Automatic detection and playback of remaining ad portions when joining late
- Flexible Ad Scheduling: Support for both SCTE-35 markers and external ad schedules
- Enhanced UI Controls: Beautiful, adaptive video controls that work on any background color
- Live Mode Support: Specialized controls for live streaming with volume adjustment
- Cross-Platform: Works on desktop, mobile, tablets, and smart TVs
- React Ready: Multiple React components for different use cases
- TypeScript Support: Full type definitions included
- Professional Architecture: Modular player system with lazy loading
🚀 Quick Start
Installation
npm install stormcloud-video-player hls.jsReact Integration
Option 1: Legacy Component (Recommended for existing projects)
import React from "react";
import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";
function MyVideoApp() {
return (
<StormcloudVideoPlayerComponent
src="https://your-stream.com/playlist.m3u8"
autoplay={true}
muted={true}
controls={true}
showCustomControls={true} // Enable enhanced UI controls
allowNativeHls={true} // Allow native HLS for better performance
licenseKey="your_license_key_here"
vastMode="adstorm" // Use AdStorm mode with HLS ad player
style={{ width: "100%", aspectRatio: "16/9" }}
wrapperStyle={{ borderRadius: "12px", overflow: "hidden" }}
onReady={(player) => {
console.log("Player is ready!", player);
}}
onVolumeToggle={() => {
console.log("Volume toggled");
}}
onFullscreenToggle={() => {
console.log("Fullscreen toggled");
}}
/>
);
}Option 2: New Professional Component (For new projects)
import React from "react";
import StormcloudPlayer from "stormcloud-video-player";
function MyVideoApp() {
return (
<StormcloudPlayer
src="https://your-stream.com/playlist.m3u8"
playing={true}
muted={true}
controls={true}
width="100%"
height="auto"
style={{ aspectRatio: "16/9" }}
// Stormcloud-specific props
allowNativeHls={true}
showCustomControls={true}
licenseKey="your_license_key_here"
vastMode="adstorm" // Use AdStorm mode (or omit for default mode)
onReady={(player) => {
console.log("Player is ready!", player);
}}
onPlay={() => console.log("Playing")}
onPause={() => console.log("Paused")}
onProgress={(state) => console.log("Progress:", state)}
/>
);
}Option 3: Specific Player Types (Optimized bundles)
// For HLS streams only (smaller bundle)
import StormcloudPlayer from "stormcloud-video-player/hls";
// For regular video files only (smallest bundle)
import StormcloudPlayer from "stormcloud-video-player/file";Vanilla JavaScript
import { StormcloudVideoPlayer } from "stormcloud-video-player";
const video = document.getElementById("my-video");
const player = new StormcloudVideoPlayer({
videoElement: video,
src: "https://your-stream.com/playlist.m3u8",
autoplay: true,
muted: true,
allowNativeHls: true, // Enable native HLS when supported
showCustomControls: true, // Enable enhanced UI controls
lowLatencyMode: false, // Set to true for live streams
driftToleranceMs: 3000, // Drift tolerance for live streams
licenseKey: "your_license_key_here",
onVolumeToggle: () => console.log("Volume toggled"),
onFullscreenToggle: () => console.log("Fullscreen toggled"),
});
await player.load();CDN Usage
<script src="https://cdn.jsdelivr.net/npm/stormcloud-video-player/dist/stormcloud-vp.min.js"></script>
<script>
const { StormcloudVideoPlayer } = window.StormcloudVP;
const video = document.getElementById("video");
const player = new StormcloudVideoPlayer({
videoElement: video,
src: "https://your-stream.com/playlist.m3u8",
autoplay: true,
muted: true,
});
player.load();
</script>🏗️ Professional Architecture
The Stormcloud Video Player now follows a professional, modular architecture similar to react-player:
Player System Overview
StormcloudPlayer (Main Component)
├── Player (Internal Wrapper)
├── HlsPlayer (HLS Stream Handler)
├── FilePlayer (Regular Video Handler)
└── Legacy StormcloudVideoPlayerComponentWhat Each Player Does
🎬 HlsPlayer - HLS Stream Specialist
- Purpose: Handles HLS (.m3u8) streams with advanced features
- Features:
- SCTE-35 ad marker detection and processing
- Google IMA SDK integration for VAST/VPAID ads
- Live stream support with low-latency mode
- Drift correction for live timing
- Manifest-based and ID3 ad markers
- Ad failsafe and recovery mechanisms
// Automatically used for HLS streams
<StormcloudPlayer src="https://example.com/stream.m3u8" />📹 FilePlayer - Regular Video Handler
- Purpose: Handles regular video files (MP4, WebM, etc.)
- Features:
- Direct video element control
- Picture-in-Picture support
- Standard HTML5 video features
- Lightweight and fast
- No HLS.js dependency
// Automatically used for regular video files
<StormcloudPlayer src="https://example.com/video.mp4" />Automatic Player Selection
The system automatically chooses the right player based on your video source:
// HLS streams → HlsPlayer
"https://example.com/playlist.m3u8"; // Uses HlsPlayer
"https://example.com/stream/index.m3u8"; // Uses HlsPlayer
// Regular videos → FilePlayer
"https://example.com/video.mp4"; // Uses FilePlayer
"https://example.com/video.webm"; // Uses FilePlayer
"https://example.com/video.mov"; // Uses FilePlayerBundle Optimization
Import only what you need for smaller bundles:
// Full player (auto-detection) - ~140KB
import StormcloudPlayer from "stormcloud-video-player";
// HLS only - ~120KB
import StormcloudPlayer from "stormcloud-video-player/hls";
// File only - ~80KB
import StormcloudPlayer from "stormcloud-video-player/file";
// Legacy component - Full features
import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";📖 API Reference
StormcloudPlayer Component (New)
Props
interface StormcloudPlayerProps {
// Media source
src?: string;
// Playback control
playing?: boolean;
loop?: boolean;
controls?: boolean;
volume?: number;
muted?: boolean;
playbackRate?: number;
// Styling
width?: string | number;
height?: string | number;
style?: CSSProperties;
className?: string;
wrapperClassName?: string;
wrapperStyle?: CSSProperties;
// Video attributes
playsInline?: boolean;
autoplay?: boolean;
preload?: string;
poster?: string;
// Stormcloud-specific
allowNativeHls?: boolean;
lowLatencyMode?: boolean;
driftToleranceMs?: number;
immediateManifestAds?: boolean;
debugAdTiming?: boolean;
showCustomControls?: boolean;
licenseKey?: string;
adFailsafeTimeoutMs?: number;
minSegmentsBeforePlay?: number; // Number of segments to buffer before starting playback (default: 2)
// Ad player configuration
vastMode?: 'adstorm' | 'default'; // VAST mode: 'adstorm' (HLS player + AdStorm VAST endpoint) or 'default' (IMA SDK + /ads/web endpoint) (default: 'default')
vastTagUrl?: string; // Custom VAST URL (used in default mode if provided; when not provided, uses /ads/web endpoint)
adPlayerType?: 'ima' | 'hls'; // Manual override for ad player type (auto-determined by vastMode if not specified)
// Event handlers
onReady?: (player: StormcloudVideoPlayer) => void;
onStart?: () => void;
onPlay?: () => void;
onPause?: () => void;
onBuffer?: () => void;
onBufferEnd?: () => void;
onEnded?: () => void;
onError?: (
error: any,
data?: any,
hlsInstance?: any,
hlsGlobal?: any
) => void;
onDuration?: (duration: number) => void;
onSeek?: (seconds: number) => void;
onProgress?: (state: {
played: number;
playedSeconds: number;
loaded: number;
loadedSeconds: number;
}) => void;
onVolumeToggle?: () => void;
onFullscreenToggle?: () => void;
onControlClick?: () => void;
}Methods
// Player instance methods (available via ref or onReady)
player.seekTo(amount: number, type?: 'seconds' | 'fraction')
player.getCurrentTime(): number | null
player.getSecondsLoaded(): number | null
player.getDuration(): number | null
player.getInternalPlayer(key?: string): anyStormcloudVideoPlayer Class (Core)
Constructor
new StormcloudVideoPlayer(config: StormcloudVideoPlayerConfig)Methods
| Method | Description | Returns |
| ---------------------------- | --------------------------------------------- | ------------------ |
| load() | Initialize and start video playback | Promise<void> |
| destroy() | Clean up player resources and event listeners | void |
| toggleMute() | Toggle video mute state | void |
| toggleFullscreen() | Enter/exit fullscreen mode | Promise<void> |
| isMuted() | Check if video is currently muted | boolean |
| isFullscreen() | Check if player is in fullscreen mode | boolean |
| isAdPlaying() | Check if an ad is currently playing | boolean |
| isShowingAds() | Check if ads are being shown | boolean |
| getCurrentAdIndex() | Get current ad index in pod | number |
| getTotalAdsInBreak() | Get total ads in current break | number |
| shouldShowNativeControls() | Check if native controls should be shown | boolean |
| getStreamType() | Get detected stream type | 'hls' \| 'other' |
Configuration Options
interface StormcloudVideoPlayerConfig {
videoElement: HTMLVideoElement; // Target video element
src: string; // Stream URL (HLS or regular video)
autoplay?: boolean; // Auto-start playback (default: false)
muted?: boolean; // Start muted (default: false)
allowNativeHls?: boolean; // Use native HLS when available (default: false)
showCustomControls?: boolean; // Enable enhanced UI controls (default: false)
lowLatencyMode?: boolean; // Enable low-latency mode for live streams (default: false)
driftToleranceMs?: number; // Drift tolerance for live streams (default: 1000)
immediateManifestAds?: boolean; // Load ads immediately from manifest (default: true)
licenseKey?: string; // API authentication key
debugAdTiming?: boolean; // Enable debug logging (default: false)
adFailsafeTimeoutMs?: number; // Ad timeout in milliseconds (default: 10000)
minSegmentsBeforePlay?: number; // Number of segments to buffer before starting playback (default: 2)
// Ad configuration
vastMode?: 'adstorm' | 'default'; // VAST mode: 'adstorm' (uses HLS player + AdStorm VAST endpoint) or 'default' (uses Google IMA SDK + /ads/web endpoint) (default: 'default')
vastTagUrl?: string; // Custom VAST tag URL (used in default mode if provided; when not provided, defaults to /ads/web endpoint)
adPlayerType?: 'ima' | 'hls'; // Manual override for ad player type (auto-determined by vastMode if not specified)
onVolumeToggle?: () => void; // Callback for volume toggle
onFullscreenToggle?: () => void; // Callback for fullscreen toggle
onControlClick?: () => void; // Callback for control area clicks
}🎨 Enhanced UI Controls
The player includes beautiful, adaptive video controls that ensure visibility on any video background:
Features
- Adaptive Visibility: High-contrast design that works on both light and dark video backgrounds
- Live Mode Support: Specialized controls for live streaming with hover-activated volume slider
- Modern Design: Glassmorphism effects with smooth animations and transitions
- Touch-Friendly: Large, accessible buttons optimized for mobile and desktop
- Customizable: Full control over appearance and behavior through props
Control Types
Full Controls (HLS Streams)
When showCustomControls={true} for HLS streams:
- Progress timeline with seek functionality
- Play/pause button with smooth animations
- Volume control with hover-activated vertical slider
- Playback speed menu (0.25x to 2x)
- Fullscreen toggle
- Time display (current/duration)
Live Mode Controls (Non-HLS or when native HLS is used)
When showCustomControls={true} for regular video files:
- Volume control with hover-activated slider
- Fullscreen toggle
- Compact, overlay-style positioning
Styling
All controls use a consistent high-contrast design:
- Dark backgrounds with gradient overlays
- White borders for clear definition
- Enhanced shadows for depth and separation
- Smooth transitions for professional feel
<StormcloudPlayer
showCustomControls={true}
wrapperStyle={{ borderRadius: "12px", overflow: "hidden" }}
onVolumeToggle={() => console.log("Volume toggled")}
onFullscreenToggle={() => console.log("Fullscreen toggled")}
/>🎬 Ad Integration (HLS Streams Only)
VAST Mode Configuration
The player supports two VAST modes that automatically configure the appropriate ad player:
1. AdStorm Mode (Recommended)
Uses AdStorm backend with HLS ad player for optimal performance:
const player = new StormcloudVideoPlayer({
videoElement: video,
src: "https://your-stream.com/playlist.m3u8",
licenseKey: "your-license-key",
// AdStorm mode - uses HLS ad player automatically
vastMode: 'adstorm',
debugAdTiming: true,
});What happens:
- 🎯 Automatically uses HLS ad player (
adPlayerType: 'hls') - 🔗 VAST endpoint:
GET https://adstorm.co/api-adstorm-dev/adstorm/vast/{licenseKey}- License key is passed in the URL path (no authorization header needed)
- Returns VAST XML directly with HLS media files
- 📊 Direct tracking and analytics through AdStorm backend
- ⚠️ Gracefully handles "no ads available" scenarios (logs warnings, not errors)
API Flow:
- Player calls
/vast/{licenseKey}endpoint - Backend returns VAST XML with HLS media files
- Player parses VAST XML and extracts MediaFile URLs
- HLS ad player loads and plays the ad segments
Benefits:
- ✅ Zero external dependencies (no Google IMA SDK)
- ✅ Full control over ad serving
- ✅ Native HLS playback (same format as content)
- ✅ Better performance and reliability
- ✅ Custom targeting and selection
- ✅ Proper error handling (distinguishes parsing errors from "no ads available")
2. Default Mode
Uses Google IMA SDK for traditional ad serving:
const player = new StormcloudVideoPlayer({
videoElement: video,
src: "https://your-stream.com/playlist.m3u8",
licenseKey: "your-license-key",
// Default mode - uses Google IMA SDK automatically
vastMode: 'default', // or omit this property entirely
vastTagUrl: 'https://your-vast-server.com/vast.xml', // optional
debugAdTiming: true,
});What happens:
- 🎯 Automatically uses Google IMA SDK (
adPlayerType: 'ima') - 🔗 VAST endpoint resolution:
- If
vastTagUrlis provided, uses that URL directly - Otherwise, calls
GET https://adstorm.co/api-adstorm-dev/adstorm/ads/web- Requires
Authorization: Bearer {licenseKey}header - Returns JSON response with IMA payload:
{ response: { ima: { "publisherdesk.ima": { payload: "VAST_URL" } } } } - Extracts VAST tag URL from the
payloadfield
- Requires
- If
- 📊 Standard VAST/VPAID ad serving through Google IMA SDK
API Flow:
- Player calls
/ads/webendpoint with Bearer token (if novastTagUrlprovided) - Backend returns JSON with IMA configuration
- Player extracts VAST tag URL from
response.ima["publisherdesk.ima"].payload - Google IMA SDK loads and plays the VAST ad
Benefits:
- ✅ Industry-standard ad serving
- ✅ Wide format support (VAST, VPAID)
- ✅ Established ecosystem
- ✅ Backward compatible with existing VAST tags
- ✅ Supports both custom VAST URLs and AdStorm backend
Ad Pod Generation (Multiple Consecutive Ads)
The player automatically generates ad pods (multiple ads played consecutively) from a single VAST URL. This works differently for VOD and live streams:
VOD Mode: Fixed Ad Count
For Video on Demand, the player uses the number_ads field from the API response to determine how many ads to play:
API Response Configuration:
{
"response": {
"ima": {
"publisherdesk.ima": {
"payload": "https://pubads.g.doubleclick.net/gampad/ads?...",
"priority": 1
}
},
"options": {
"vast": {
"cue_tones": {
"number_ads": 3
}
}
}
}
}How it works:
- Player receives a single VAST URL from the API
- The
number_adsfield specifies how many ads should play (e.g., 3) - Player generates 3 unique VAST URLs by adding different
correlatorvalues - Each unique correlator causes GAM to return a different ad
- All 3 ads play consecutively in the ad break
Example:
// Base URL from API
"https://pubads.g.doubleclick.net/...&correlator="
// Generated URLs (3 ads)
[
"https://pubads.g.doubleclick.net/...&correlator=1730995200000123456780",
"https://pubads.g.doubleclick.net/...&correlator=1730995200000789012341",
"https://pubads.g.doubleclick.net/...&correlator=1730995200000345678902"
]Live Mode: Adaptive Duration-Based Ad Filling
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:
Configuration:
const player = new StormcloudVideoPlayer({
videoElement: video,
src: "https://your-live-stream.com/playlist.m3u8",
licenseKey: "your-license-key",
vastMode: 'default',
debugAdTiming: true, // Enable to see adaptive calculations
});How Adaptive Mode Works:
- Initial Request: Player starts with 2 initial VAST URLs
- Fetch Real Duration: As each VAST is fetched, the player extracts the actual
<Duration>from the XML - Adaptive Calculation: After fetching each ad, the player:
- Calculates total duration of fetched ads
- Compares against target SCTE-35 duration
- Dynamically generates more VAST URLs if needed
- Smart Filling: Uses average of actual fetched durations (not estimates) to calculate remaining ads
- Continuous Adaptation: Recalculates after each ad fetch until target duration is met
Example Scenario:
SCTE-35 Duration: 120 seconds
Step 1: Start with 2 initial ads
→ Fetch Ad 1: Actual duration = 45s
→ Recalculate: Need (120-45)/45 = 2 more ads
→ Generate 2 additional VAST URLs
Step 2: Fetch Ad 2: Actual duration = 40s
→ Total so far: 85s (45s + 40s)
→ Recalculate: Need (120-85)/42.5 = 1 more ad
→ Generate 1 additional VAST URL
Step 3: Fetch Ad 3: Actual duration = 35s
→ Total: 120s ✅ Target reached!
Final: 3 ads played (perfectly fills 120 seconds)Debug Output:
[ADAPTIVE-POD] 📺 LIVE MODE (ADAPTIVE): Target duration=120000ms | Starting with 2 ads, will fetch actual durations and add more dynamically
[DEBUG-POD] 🔄 Generated 2 initial VAST URLs with unique correlators
[ADAPTIVE-POD] ✓ Fetched ad duration: 45s (1 ads fetched so far)
[ADAPTIVE-POD] 📊 Need 2 more ads | Fetched: 45000ms / Target: 120000ms | Remaining: 75000ms | Avg duration: 45000ms
[ADAPTIVE-POD] 🔄 Adding 2 additional VAST URLs to queue
[ADAPTIVE-POD] ✓ Fetched ad duration: 40s (2 ads fetched so far)
[ADAPTIVE-POD] 📊 Need 1 more ads | Fetched: 85000ms / Target: 120000ms | Remaining: 35000ms | Avg duration: 42500ms
[ADAPTIVE-POD] ✓ Fetched ad duration: 35s (3 ads fetched so far)
[ADAPTIVE-POD] ✅ Target duration reached: 120000ms / 120000msBenefits of Adaptive Approach:
- ✅ Accurate Filling: Uses actual ad durations from VAST responses
- ✅ Less Waste: Doesn't over-request ads unnecessarily
- ✅ Flexible: Adapts to GAM returning 15s, 30s, 60s, or mixed-length ads
- ✅ Efficient: Fetches durations while preloading, no extra latency
- ✅ Smart: Improves calculation as more data is gathered (uses average of fetched durations)
- ✅ Self-Correcting: Automatically adjusts if actual ad lengths differ from expectations
Manual Ad Player Override
You can still manually override the ad player type if needed:
const player = new StormcloudVideoPlayer({
videoElement: video,
src: "https://your-stream.com/playlist.m3u8",
vastMode: 'default',
adPlayerType: 'hls', // Manual override to use HLS player with default mode
vastTagUrl: 'https://your-backend.com/vast', // Will use this URL directly
debugAdTiming: true,
});Note: When adPlayerType is manually set, the vastMode property still determines which backend endpoint is called:
vastMode: 'adstorm'→ Always calls/vast/{licenseKey}endpointvastMode: 'default'→ Calls/ads/webendpoint (unlessvastTagUrlis provided)
SCTE-35 Support
The HlsPlayer automatically detects and responds to SCTE-35 signals embedded in HLS streams:
- CUE-OUT: Triggers ad break start
- CUE-OUT-CONT: Handles mid-roll continuation
- CUE-IN: Resumes content playback
- DATERANGE: Processes time-based ad markers
Supported HLS Tags
#EXT-X-CUE-OUT#EXT-X-CUE-OUT-CONT#EXT-X-CUE-IN#EXT-X-DATERANGE- ID3 timed metadata
Early Ad Prefetching & Preload Pool
The player includes an advanced ad prefetching system that dramatically reduces ad start latency:
Early SCTE-35 Detection
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:
- Prefetching Begins: The player immediately starts generating VAST URLs and prefetching ads
- Preload Pool: Up to 3 ads are preloaded and ready to play instantly
- Zero-Delay Starts: When the ad break actually begins, ads start immediately (no request delay)
Benefits:
- ⚡ 80% reduction in ad start latency
- 🎯 Instant ad playback when breaks begin
- 📊 Better fill rates through proactive ad preparation
- 🔄 Seamless transitions between content and ads
How It Works
const player = new StormcloudVideoPlayer({
videoElement: video,
src: "https://your-stream.com/playlist.m3u8",
licenseKey: "your-license-key",
debugAdTiming: true, // Enable to see prefetching logs
});
// The player automatically:
// 1. Scans manifest fragments for SCTE-35 markers
// 2. Detects upcoming ad breaks before playback reaches them
// 3. Prefetches and preloads ads into a pool
// 4. Starts ads instantly when breaks beginDebug Output Example:
[PREFETCH] 🔄 Starting ad prefetch for upcoming ad break
[PREFETCH] 📋 Pre-generated 5 VAST URLs
[PRELOAD-POOL] 🏊 Starting preload pool EARLY (target size: 3)
[PRELOAD-POOL] 📥 Preloading ad into pool: https://...
[PRELOAD-POOL] ✅ Ad preloaded (pool size: 1/3)
[CONTINUOUS-FETCH] 🚀 Using preloaded ad from pool (preloaded in advance, ready immediately!)Continuous Ad Fetching
During ad breaks, the player continuously fetches additional ads to fill the entire SCTE-35 duration:
- Dynamic Queue: Maintains a queue of ready-to-play VAST URLs
- Smart Rate Limiting: 2.5s minimum interval between requests with exponential backoff
- Automatic Filling: Fetches ads until the SCTE-35 duration is fully filled
- Error Recovery: Distinguishes temporary failures (no-fill) from permanent failures
Rate Limiting & Backoff:
- Base interval: 2.5 seconds between requests
- Exponential backoff: Increases with consecutive failures
- Max backoff: 15 seconds
- Cooldown period: 30 seconds for temporary failures (no-fill)
Error Handling & Recovery
The player intelligently handles different types of ad failures:
Temporary Failures (Retryable):
- No-fill responses (no ads available)
- Network timeouts
- Rate limiting errors
- Action: URL enters 30s cooldown, then can be retried
Permanent Failures (Blacklisted):
- VAST parsing errors
- Malformed responses
- Action: URL is permanently blacklisted for the current ad break
Fallback System:
- If a primary ad request fails, the player automatically tries a preloaded ad from the pool
- Ensures continuous ad playback even when individual requests fail
Late Join Behavior
When viewers join during an ad break:
- play_remaining: Plays the remaining portion of the current ad
- skip_to_content: Skips to main content (configurable via API)
Ad Status Monitoring
<StormcloudPlayer
onReady={(player) => {
// Monitor ad playback
console.log("Is ad playing:", player.isAdPlaying());
console.log("Current ad index:", player.getCurrentAdIndex());
console.log("Total ads in break:", player.getTotalAdsInBreak());
}}
/>Error Handling
The player distinguishes between different types of ad-related issues:
"No Ads Available" (Warning):
- When the VAST response indicates no ads are available (e.g., empty
<MediaFiles>orAdTitle: "No Ad Available") - Logs warnings, not errors
- Content playback continues normally
- No error events are emitted
VAST XML Parsing Errors (Error):
- When the VAST XML is malformed or cannot be parsed
- Logs errors
- Emits
ad_errorevent - Content playback continues
Network/Fetch Errors (Error):
- When the VAST endpoint is unreachable or returns an error status
- Logs errors
- Emits
ad_errorevent - Content playback continues
Example with error handling:
const player = new StormcloudVideoPlayer({
// ... config
vastMode: 'adstorm',
debugAdTiming: true, // Enable detailed logging
});
// Monitor ad events
player.on('ad_error', (error) => {
console.error('Ad error occurred:', error);
// Handle error (e.g., show fallback, retry, etc.)
});🔐 Authentication
The player supports license key authentication for enhanced features:
const player = new StormcloudVideoPlayer({
// ... other config
licenseKey: "ADSTORM-YOUR-LICENSE-KEY-HERE",
});Authenticated requests are sent to:
AdStorm Mode (
vastMode: 'adstorm'):- VAST endpoint:
GET https://adstorm.co/api-adstorm-dev/adstorm/vast/{licenseKey}(license key in URL path)
- VAST endpoint:
Default Mode (
vastMode: 'default'):- Ad configuration:
GET https://adstorm.co/api-adstorm-dev/adstorm/ads/web(requiresAuthorization: Bearer {licenseKey}header) - Returns JSON with IMA payload containing VAST tag URL
- Ad configuration:
Player Tracking (both modes):
- Player tracking:
POST https://adstorm.co/api-adstorm-dev/adstorm/player-tracking/track(requiresAuthorization: Bearer {licenseKey}header) - Heartbeat monitoring:
POST https://adstorm.co/api-adstorm-dev/adstorm/player-tracking/heartbeat(requiresAuthorization: Bearer {licenseKey}header)
- Player tracking:
🔧 Advanced Configuration
Custom Player Creation
Create your own player with specific capabilities:
import { createStormcloudPlayer, players } from "stormcloud-video-player";
// Create a player with only HLS support
const HLSOnlyPlayer = createStormcloudPlayer([
players.find((p) => p.key === "hls"),
]);
// Create a player with custom fallback
const CustomPlayer = createStormcloudPlayer(
players,
players.find((p) => p.key === "file") // fallback to file player
);Adding Custom Players
import StormcloudPlayer from "stormcloud-video-player";
// Add a custom player for a specific format
StormcloudPlayer.addCustomPlayer({
key: "custom",
name: "CustomPlayer",
canPlay: (src) => src.includes("custom://"),
lazyPlayer: React.lazy(() => import("./CustomPlayer")),
});Buffering Configuration
Min Segments Before Play
Control how many segments must be buffered before playback starts. This helps ensure smooth playback, especially on slower connections:
const player = new StormcloudVideoPlayer({
videoElement: video,
src: "https://your-stream.com/playlist.m3u8",
autoplay: true,
minSegmentsBeforePlay: 3, // Wait for 3 segments before starting (default: 2)
});
// Or with React component
<StormcloudPlayer
src="https://your-stream.com/playlist.m3u8"
playing={true}
minSegmentsBeforePlay={3}
/>Configuration Options:
minSegmentsBeforePlay: 0- Start immediately (may cause stuttering on slow connections)minSegmentsBeforePlay: 2- Default, good balance for most use casesminSegmentsBeforePlay: 3-5- Recommended for slower connections or higher quality streamsminSegmentsBeforePlay: undefined- Uses default value (2)
When to Adjust:
- Increase for high-bitrate streams or unreliable networks
- Decrease for low-latency requirements or fast connections
- Set to 0 only if you need immediate playback and can tolerate potential stuttering
Player Detection
import StormcloudPlayer from "stormcloud-video-player";
// Check if a URL can be played
const canPlay = StormcloudPlayer.canPlay("https://example.com/video.m3u8");
console.log("Can play:", canPlay); // true
// Check Picture-in-Picture support
const canPIP = StormcloudPlayer.canEnablePIP("https://example.com/video.mp4");
console.log("Supports PIP:", canPIP); // true for file playerEvent Handling
<StormcloudPlayer
onReady={(player) => {
console.log("Player ready");
}}
onPlay={() => {
console.log("Playback started");
}}
onPause={() => {
console.log("Playback paused");
}}
onProgress={(state) => {
console.log("Progress:", state.playedSeconds, "of", state.loadedSeconds);
}}
onError={(error, data, hlsInstance, hlsGlobal) => {
console.error("Player error:", error);
if (hlsInstance) {
console.log("HLS instance available for recovery");
}
}}
/>🌐 Browser Support
- Desktop: Chrome 60+, Firefox 55+, Safari 12+, Edge 79+
- Mobile: iOS Safari 12+, Chrome Mobile 60+
- Smart TV: WebOS, Tizen, Android TV, Roku, Apple TV
Format Support by Player
HlsPlayer
- HLS Streams:
.m3u8files - Native HLS: Safari, iOS Safari (when
allowNativeHls=true) - HLS.js: Chrome, Firefox, Edge (automatic fallback)
FilePlayer
- Video Formats: MP4, WebM, MOV, AVI, OGV
- Audio Formats: MP3, WAV, OGG, AAC
- Streaming: Progressive download
- Picture-in-Picture: Supported browsers
🏗️ Development
Build Commands
# Build everything
npm run build:all
# Individual builds
npm run build:lib # Library build (ESM/CJS)
npm run build:minified # Minified UMD bundle
npm run build:dist # Production UMD bundle
# Development
npm run dev # Development watch mode
npm run clean # Clean build artifacts
npm run test # Run test suite
npm run lint # Lint codebaseProject Structure
src/
├── index.ts # Main exports
├── StormcloudPlayer.tsx # New main component
├── Player.tsx # Internal wrapper component
├── players/
│ ├── index.ts # Player registry
│ ├── HlsPlayer.tsx # HLS stream handler
│ └── FilePlayer.tsx # Regular video handler
├── player/
│ └── StormcloudVideoPlayer.ts # Core player class
├── ui/
│ └── StormcloudVideoPlayer.tsx # Legacy React component
├── sdk/
│ └── ima.ts # Google IMA integration
├── utils/
│ ├── tracking.ts # Analytics and tracking
│ └── index.ts # Utility functions
├── props.ts # Centralized props system
├── patterns.ts # URL pattern matching
└── types.ts # TypeScript definitionsExport Structure
stormcloud-video-player/
├── index.js # Main entry (auto-detection)
├── hls.js # HLS-only entry
├── file.js # File-only entry
├── base.d.ts # Base type definitions
├── hls.d.ts # HLS-specific types
├── file.d.ts # File-specific types
├── lib/ # Compiled library
├── dist/ # UMD bundles
└── scripts/ # Build utilities🎯 Use Cases
By Player Type
HlsPlayer Use Cases
- Live Streaming: Sports, news, and event broadcasts
- Live Shopping: E-commerce live streams with ads
- FAST Channels: Free ad-supported streaming TV
- Linear TV: Traditional broadcast over IP
- Event Streaming: Conferences, concerts, webinars
FilePlayer Use Cases
- VOD Platforms: Movie and series streaming services
- Educational Content: E-learning and training platforms
- Corporate Communications: Internal videos and presentations
- Digital Signage: Retail and public display systems
- Social Media: User-generated content playback
Migration Guide
From Legacy Component
// Old way (still works)
import { StormcloudVideoPlayerComponent } from "stormcloud-video-player";
<StormcloudVideoPlayerComponent
src="https://example.com/stream.m3u8"
autoplay={true}
// ... other props
/>;
// New way (recommended for new projects)
import StormcloudPlayer from "stormcloud-video-player";
<StormcloudPlayer
src="https://example.com/stream.m3u8"
playing={true} // Note: 'playing' instead of 'autoplay'
// ... other props
/>;Key Differences
| Legacy Component | New Component | Notes |
| ------------------------ | -------------------------- | --------------------------- |
| autoplay={true} | playing={true} | More React-like prop naming |
| Always loads full bundle | Supports optimized imports | Better performance |
| Single component | Modular architecture | More maintainable |
| Fixed UI structure | Flexible wrapper system | More customizable |
⚠️ Known Limitations
- Requires DOM environment (browser-only)
- SCTE-35 binary splice parsing not yet implemented
- Multi-ad pod competitive separation handled by ad server
- Low-latency HLS (LL-HLS) optimizations in development
- Picture-in-Picture only available in FilePlayer
🗺️ Roadmap
Upcoming Features
- Enhanced SCTE-35: Full binary splice_info_section parsing
- Advanced Late Join: Improved partial pod handling
- LL-HLS Support: Low-latency streaming optimizations
- Rich Analytics: Comprehensive event tracking and reporting
- UI Themes: Multiple control themes and color schemes
- Accessibility: Enhanced ARIA support and keyboard navigation
- Error Recovery: Advanced retry logic and failover handling
- More Players: DASH, YouTube, Vimeo player implementations
Performance Improvements
- Drift correction between PTS and wall-clock timing
- Memory optimization for long-running sessions
- Network bandwidth adaptation
- Smart preloading strategies
- Code splitting and lazy loading optimizations
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
📄 License
MIT License - see LICENSE file for details.
Built with ❤️ by the Stormcloud team
What's New in v0.4
- 🚀 Early Ad Prefetching: Detects SCTE-35 markers in manifest fragments before playback reaches them, prefetching ads in advance for zero-delay ad starts
- 🏊 Ad Preload Pool: Maintains a pool of preloaded, ready-to-play ads (up to 3 by default) for instant ad playback when breaks start
- 🔄 Continuous Ad Fetching: Dynamically fetches additional ads during ad breaks to perfectly fill SCTE-35 durations, ensuring no wasted ad time
- ⏱️ Smart Rate Limiting: Intelligent rate limiting (2.5s minimum interval) with exponential backoff to prevent ad server overload
- 🛡️ Advanced Error Handling: Distinguishes between temporary failures (no-fill, timeouts) and permanent failures, with 30s cooldown periods for retryable errors
- ⏳ Min Segments Before Play: New
minSegmentsBeforePlayoption (default: 2) to ensure smooth playback start by buffering multiple segments - 🎯 Ad Request Watchdog: Timeout system prevents hanging ad requests and automatically recovers from stuck states
- ⬛ Placeholder System: Seamless placeholder layer shown during ad transitions, preventing content flash between ads
- 📊 Improved Queue Management: Better handling of ad request queues with automatic cleanup and failure tracking
- 🔍 Early SCTE-35 Detection: Scans up to 5 manifest fragments ahead to detect upcoming ad breaks, enabling proactive ad preparation
- ⚡ Zero-Delay Ad Starts: Preloaded ads start instantly when ad breaks begin, eliminating the traditional ad request delay
- 🎬 Fallback Ad System: Automatic fallback to preloaded ads when primary ad requests fail, ensuring continuous ad playback
Performance Improvements:
- Reduced ad start latency by up to 80% through prefetching and preloading
- Better resource utilization with intelligent ad pool management
- Improved reliability with comprehensive error recovery mechanisms
- Enhanced user experience with seamless ad transitions
What's New in v0.3
- 🎬 Custom HLS Ad Player: Native HLS ad playback with custom VAST service
- 🎯 Flexible Ad Integration: Choose between custom HLS or Google IMA SDK
- 📊 Direct Analytics: Full control over ad tracking and metrics
- ⚡ Better Performance: Native HLS playback for ads (same format as content)
- 🔧 Custom VAST URLs: Point to your own ad serving backend
- 🔄 Backward Compatible: Existing Google IMA integration still works
- 📦 Zero Dependencies: No external ad SDKs required (when using HLS ad player)
- 🎨 Seamless Playback: Same player for content and ads
- 🔀 VAST Mode System:
vastModeproperty automatically configures endpoints and ad playersvastMode: 'adstorm'→ Uses/vast/{licenseKey}endpoint with HLS ad playervastMode: 'default'→ Uses/ads/webendpoint with Google IMA SDK
- ⚠️ Improved Error Handling: Distinguishes between parsing errors and "no ads available" scenarios
- Logs warnings for "no ads available" (graceful handling)
- Logs errors for actual parsing/fetch failures
What's New in v0.2
- 🎯 Professional Architecture: Modular player system inspired by react-player
- 🚀 Automatic Format Detection: Smart player selection based on video source
- 📦 Optimized Bundles: Import only what you need (HLS-only, File-only, or Full)
- 🔄 Backward Compatibility: Legacy component still works without changes
- 🎨 Enhanced Controls: Improved UI with better accessibility and design
- 📚 Better TypeScript: Comprehensive type definitions and IntelliSense
- 🏗️ Extensible: Easy to add custom players for new formats
- ⚡ Performance: Lazy loading and code splitting for faster load times
