npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

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.js

React 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 StormcloudVideoPlayerComponent

What 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 FilePlayer

Bundle 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): any

StormcloudVideoPlayer 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:

  1. Player calls /vast/{licenseKey} endpoint
  2. Backend returns VAST XML with HLS media files
  3. Player parses VAST XML and extracts MediaFile URLs
  4. 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:
    1. If vastTagUrl is provided, uses that URL directly
    2. 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 payload field
  • 📊 Standard VAST/VPAID ad serving through Google IMA SDK

API Flow:

  1. Player calls /ads/web endpoint with Bearer token (if no vastTagUrl provided)
  2. Backend returns JSON with IMA configuration
  3. Player extracts VAST tag URL from response.ima["publisherdesk.ima"].payload
  4. 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:

  1. Player receives a single VAST URL from the API
  2. The number_ads field specifies how many ads should play (e.g., 3)
  3. Player generates 3 unique VAST URLs by adding different correlator values
  4. Each unique correlator causes GAM to return a different ad
  5. 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:

  1. Initial Request: Player starts with 2 initial VAST URLs
  2. Fetch Real Duration: As each VAST is fetched, the player extracts the actual <Duration> from the XML
  3. 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
  4. Smart Filling: Uses average of actual fetched durations (not estimates) to calculate remaining ads
  5. 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 / 120000ms

Benefits 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} endpoint
  • vastMode: 'default' → Calls /ads/web endpoint (unless vastTagUrl is 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:

  1. Prefetching Begins: The player immediately starts generating VAST URLs and prefetching ads
  2. Preload Pool: Up to 3 ads are preloaded and ready to play instantly
  3. 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 begin

Debug 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> or AdTitle: "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_error event
  • Content playback continues

Network/Fetch Errors (Error):

  • When the VAST endpoint is unreachable or returns an error status
  • Logs errors
  • Emits ad_error event
  • 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)
  • Default Mode (vastMode: 'default'):

    • Ad configuration: GET https://adstorm.co/api-adstorm-dev/adstorm/ads/web (requires Authorization: Bearer {licenseKey} header)
    • Returns JSON with IMA payload containing VAST tag URL
  • Player Tracking (both modes):

    • Player tracking: POST https://adstorm.co/api-adstorm-dev/adstorm/player-tracking/track (requires Authorization: Bearer {licenseKey} header)
    • Heartbeat monitoring: POST https://adstorm.co/api-adstorm-dev/adstorm/player-tracking/heartbeat (requires Authorization: Bearer {licenseKey} header)

🔧 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 cases
  • minSegmentsBeforePlay: 3-5 - Recommended for slower connections or higher quality streams
  • minSegmentsBeforePlay: 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 player

Event 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: .m3u8 files
  • 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 codebase

Project 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 definitions

Export 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 minSegmentsBeforePlay option (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: vastMode property automatically configures endpoints and ad players
    • vastMode: 'adstorm' → Uses /vast/{licenseKey} endpoint with HLS ad player
    • vastMode: 'default' → Uses /ads/web endpoint 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