Skip to main content
Back to Examples

Web Player Embed Guide

Embed WAVE streams on your website with custom branding, quality controls, and responsive design. Full JavaScript SDK integration.

Beginner Friendly45 minutesJavaScript SDK

What You'll Learn

  • Install and initialize the WAVE JavaScript SDK
  • Create responsive video player with custom controls
  • Implement adaptive bitrate quality selection
  • Handle events and integrate analytics tracking
  • Secure streams with tokens and DRM protection

Step 1: Install WAVE SDK

NPM Installation

For modern JavaScript frameworks (React, Vue, Angular)

# Install via NPM
npm install @wave/player-sdk

# Or using Yarn
yarn add @wave/player-sdk

# Or using PNPM
pnpm add @wave/player-sdk

CDN Installation

For vanilla HTML/JavaScript projects

<!-- Add to your HTML <head> section -->
<script src="https://cdn.wave.stream/player/v2/wave-player.min.js"></script>
<link rel="stylesheet" href="https://cdn.wave.stream/player/v2/wave-player.css">

Browser Support

WAVE Player SDK supports Chrome 90+, Firefox 88+, Safari 14+, Edge 90+, and mobile browsers (iOS 14+, Android 8+).

Step 2: Basic HTML Embed

Simple HTML Implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WAVE Stream Player</title>
  <script src="https://cdn.wave.stream/player/v2/wave-player.min.js"></script>
  <link rel="stylesheet" href="https://cdn.wave.stream/player/v2/wave-player.css">
</head>
<body>
  <!-- Player container -->
  <div id="wave-player" style="width: 100%; max-width: 1280px; aspect-ratio: 16/9;"></div>

  <script>
    // Initialize the player
    const player = new WavePlayer('wave-player', {
      streamId: 'your-stream-id-here',
      apiKey: 'your-api-key-here',

      // Player configuration
      autoplay: false,
      muted: false,
      controls: true,
      responsive: true,

      // Quality settings
      quality: 'auto', // 'auto', '1080p', '720p', '480p', '360p'

      // UI customization
      theme: 'dark', // 'dark' or 'light'
      primaryColor: '#0066CC',

      // Advanced features
      enableAnalytics: true,
      enableCaptions: true,
      enablePictureInPicture: true,
      enableFullscreen: true
    });

    // Player event listeners
    player.on('ready', () => {
      console.log('Player is ready');
    });

    player.on('play', () => {
      console.log('Playback started');
    });

    player.on('pause', () => {
      console.log('Playback paused');
    });

    player.on('ended', () => {
      console.log('Stream ended');
    });

    player.on('error', (error) => {
      console.error('Player error:', error);
    });
  </script>
</body>
</html>

Step 3: React Component Integration

React Component Example

import React, { useEffect, useRef, useState } from 'react';
import { WavePlayer } from '@wave/player-sdk';

interface WavePlayerComponentProps {
  streamId: string;
  apiKey: string;
  autoplay?: boolean;
  onReady?: () => void;
  onPlay?: () => void;
  onPause?: () => void;
  onError?: (error: Error) => void;
}

export const WavePlayerComponent: React.FC<WavePlayerComponentProps> = ({
  streamId,
  apiKey,
  autoplay = false,
  onReady,
  onPlay,
  onPause,
  onError
}) => {
  const playerRef = useRef<HTMLDivElement>(null);
  const wavePlayerRef = useRef<WavePlayer | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [currentQuality, setCurrentQuality] = useState<string>('auto');

  useEffect(() => {
    if (!playerRef.current) return;

    // Initialize player
    const player = new WavePlayer(playerRef.current, {
      streamId,
      apiKey,
      autoplay,
      muted: autoplay, // Auto-mute if autoplay is enabled
      controls: true,
      responsive: true,
      quality: currentQuality,
      theme: 'dark',
      enableAnalytics: true,
      enableCaptions: true,
      enablePictureInPicture: true
    });

    // Store player reference
    wavePlayerRef.current = player;

    // Event handlers
    player.on('ready', () => {
      setIsLoading(false);
      onReady?.();
    });

    player.on('play', () => {
      onPlay?.();
    });

    player.on('pause', () => {
      onPause?.();
    });

    player.on('error', (error) => {
      setIsLoading(false);
      onError?.(error);
    });

    player.on('qualitychange', (quality) => {
      setCurrentQuality(quality);
    });

    // Cleanup on unmount
    return () => {
      player.destroy();
    };
  }, [streamId, apiKey, autoplay]);

  const handleQualityChange = (quality: string) => {
    wavePlayerRef.current?.setQuality(quality);
  };

  return (
    <div className="wave-player-wrapper">
      {isLoading && (
        <div className="absolute inset-0 flex items-center justify-center bg-black/50">
          <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-500"></div>
        </div>
      )}

      <div
        ref={playerRef}
        className="w-full aspect-video bg-black rounded-lg overflow-hidden"
      />

      {/* Custom quality selector */}
      <div className="mt-4 flex gap-2">
        {['auto', '1080p', '720p', '480p', '360p'].map((quality) => (
          <button
            key={quality}
            onClick={() => handleQualityChange(quality)}
            className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${
              currentQuality === quality
                ? 'bg-primary-500 text-white'
                : 'bg-background-secondary text-text-secondary hover:bg-background-tertiary'
            }`}
          >
            {quality.toUpperCase()}
          </button>
        ))}
      </div>
    </div>
  );
};

// Usage example
export default function StreamPage() {
  return (
    <div className={getSection('minimal', 'page')}>
      <h1 className={combineTokens(DesignTokens.typography.h2, 'mb-6')}>Live Stream</h1>

      <WavePlayerComponent
        streamId="stream_abc123xyz"
        apiKey="your-api-key-here"
        autoplay={false}
        onReady={() => console.log('Player ready')}
        onPlay={() => console.log('Playing')}
        onPause={() => console.log('Paused')}
        onError={(error) => console.error('Error:', error)}
      />
    </div>
  );
}

Step 4: Advanced Features

Adaptive Bitrate Quality Selection

// Automatic quality selection based on network conditions
player.setQuality('auto');

// Get available quality levels
const qualities = player.getAvailableQualities();
// Returns: ['1080p', '720p', '480p', '360p']

// Set specific quality
player.setQuality('1080p');

// Listen for quality changes
player.on('qualitychange', (quality) => {
  console.log(`Quality changed to: ${quality}`);

  // Track quality changes in analytics
  analytics.track('video_quality_change', {
    quality: quality,
    timestamp: Date.now()
  });
});

// Get current quality
const currentQuality = player.getCurrentQuality();

// Enable/disable adaptive bitrate
player.setAdaptiveBitrate(true); // Enabled by default

Adaptive bitrate automatically adjusts stream quality based on viewer's network conditions for optimal playback.

Analytics Integration

// Enable built-in analytics
const player = new WavePlayer('player-container', {
  streamId: 'stream_abc123',
  apiKey: 'your-api-key',
  enableAnalytics: true,

  // Custom analytics configuration
  analytics: {
    trackingId: 'UA-XXXXXXXXX-X', // Google Analytics
    customData: {
      userId: 'user_12345',
      contentCategory: 'live-event',
      campaignId: 'summer-2024'
    }
  }
});

// Track custom events
player.on('play', () => {
  // Send to your analytics service
  analytics.track('stream_play', {
    streamId: player.getStreamId(),
    quality: player.getCurrentQuality(),
    timestamp: new Date().toISOString(),
    userAgent: navigator.userAgent
  });
});

player.on('buffering', (event) => {
  analytics.track('stream_buffering', {
    bufferDuration: event.duration,
    currentTime: player.getCurrentTime()
  });
});

// Get playback statistics
const stats = player.getStats();
console.log({
  totalWatchTime: stats.totalWatchTime,      // milliseconds
  bufferingEvents: stats.bufferingCount,
  averageBitrate: stats.averageBitrate,      // kbps
  droppedFrames: stats.droppedFrames,
  currentBandwidth: stats.bandwidth          // kbps
});

Security & DRM Protection

// Generate signed token on your backend
// Backend (Node.js example)
import jwt from 'jsonwebtoken';

import { DesignTokens, getContainer, getSection } from '@/lib/design-tokens';
function generateStreamToken(streamId: string, userId: string) {
  const token = jwt.sign(
    {
      streamId: streamId,
      userId: userId,
      permissions: ['watch'],
      exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour expiry
    },
    process.env.WAVE_SECRET_KEY
  );

  return token;
}

// Frontend - Use token to authenticate
const player = new WavePlayer('player-container', {
  streamId: 'stream_abc123',
  token: signedToken, // JWT token from backend

  // DRM configuration (for protected content)
  drm: {
    enabled: true,
    widevine: {
      licenseUrl: 'https://api.wave.stream/drm/widevine/license'
    },
    fairplay: {
      certificateUrl: 'https://api.wave.stream/drm/fairplay/cert',
      licenseUrl: 'https://api.wave.stream/drm/fairplay/license'
    },
    playready: {
      licenseUrl: 'https://api.wave.stream/drm/playready/license'
    }
  },

  // Domain restriction
  allowedDomains: ['yourdomain.com', 'www.yourdomain.com'],

  // Geographic restriction
  geoRestriction: {
    allowedCountries: ['US', 'CA', 'GB'],
    blockedCountries: []
  }
});

// Handle authentication errors
player.on('autherror', (error) => {
  console.error('Authentication failed:', error);
  // Redirect to login or show error message
  window.location.href = '/login?redirect=' + encodeURIComponent(window.location.href);
});

Security Best Practice

Never expose your WAVE secret key in frontend code. Always generate tokens on your backend server.

Step 5: Responsive Design Patterns

Mobile-First Responsive Player

// CSS for responsive player container
.wave-player-container {
  position: relative;
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
}

.wave-player-wrapper {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 aspect ratio */
  height: 0;
  overflow: hidden;
}

.wave-player-wrapper iframe,
.wave-player-wrapper video {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* Mobile optimizations */
@media (max-width: 768px) {
  .wave-player-wrapper {
    border-radius: 0; /* Full-width on mobile */
  }

  /* Hide certain controls on small screens */
  .wave-player-controls .advanced-controls {
    display: none;
  }
}

/* Tablet breakpoint */
@media (min-width: 769px) and (max-width: 1024px) {
  .wave-player-container {
    max-width: 720px;
  }
}

/* Desktop breakpoint */
@media (min-width: 1025px) {
  .wave-player-container {
    max-width: 1280px;
  }
}

Adaptive Player Behavior

// Detect device and adjust player settings
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth > 768;

const playerConfig = {
  streamId: 'stream_abc123',
  apiKey: 'your-api-key',

  // Mobile-specific settings
  autoplay: !isMobile, // Don't autoplay on mobile to save data
  muted: isMobile,     // Mute on mobile if autoplaying

  // Adjust initial quality based on device
  quality: isMobile ? '480p' : 'auto',

  // Mobile UI adjustments
  controls: {
    showFullscreen: true,
    showQualitySelector: !isMobile, // Hide on mobile to save space
    showPlaybackRate: !isMobile,
    showPictureInPicture: !isMobile,
    touchControls: isMobile,
    doubleTapToSeek: isMobile
  },

  // Network-aware quality selection
  adaptiveBitrate: {
    enabled: true,
    maxQuality: isMobile ? '720p' : '1080p',
    minQuality: '360p',
    fastSwitch: isMobile // Switch quality faster on mobile
  }
};

const player = new WavePlayer('player-container', playerConfig);

// Adjust to orientation changes
window.addEventListener('orientationchange', () => {
  if (window.orientation === 90 || window.orientation === -90) {
    // Landscape - enter fullscreen on mobile
    if (isMobile) {
      player.requestFullscreen();
    }
  }
});

Troubleshooting Common Issues

Player not loading or showing black screen

  • Verify your API key and stream ID are correct
  • Check browser console for CORS or network errors
  • Ensure the stream is currently live (check dashboard)
  • Verify your domain is whitelisted in WAVE dashboard settings

Autoplay blocked by browser

  • Set muted: true when using autoplay
  • Handle autoplay failures gracefully with play button overlay
  • Listen for 'autoplayblocked' event to show user prompt

Buffering or playback stuttering

  • Enable adaptive bitrate: quality: 'auto'
  • Check viewer's network speed (recommend 5 Mbps+ for 1080p)
  • Monitor player stats: player.getStats()
  • Use lower initial quality on mobile: quality: '480p'

Player not responsive on mobile

  • Add viewport meta tag: width=device-width, initial-scale=1.0
  • Enable responsive mode: responsive: true
  • Use aspect ratio CSS instead of fixed dimensions
  • Test across different devices and orientations
Examples - Code Samples & Implementation Guides | WAVE | WAVE