Embed WAVE streams on your website with custom branding, quality controls, and responsive design. Full JavaScript SDK integration.
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
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+).
<!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>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>
);
}// 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 defaultAdaptive bitrate automatically adjusts stream quality based on viewer's network conditions for optimal playback.
// 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
});// 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.
// 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;
}
}// 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();
}
}
});muted: true when using autoplayquality: 'auto'player.getStats()quality: '480p'width=device-width, initial-scale=1.0responsive: true