@doverunner/secureplay-web
v1.0.1
Published
SecurePlay Web SDK - Developer-focused media player with multi-DRM support
Maintainers
Readme
SecurePlay Web SDK
Developer-focused media player SDK with multi-DRM support, built on Clean Architecture principles.
Features
- Multi-DRM Support: Widevine, FairPlay, PlayReady, Wiseplay
- Clean Architecture: Maintainable and testable codebase
- License Cipher: Optional DRM challenge encryption
- Simple API: Easy integration in under a day
Browser Requirements
SecurePlay requires modern browsers with EME (Encrypted Media Extensions) support:
| Browser | Support | DRM | |---------|---------|-----| | Chrome | ✅ | Widevine, PlayReady (Windows 11) | | Firefox | ✅ | Widevine | | Safari | 14.1+ | FairPlay only | | Edge | ✅ | Widevine, PlayReady |
Not Supported:
- Legacy browsers without EME support
Notes:
- Safari only supports FairPlay DRM with HLS manifests
- Safari 14.1+ required for stable FairPlay Streaming API and HLS interoperability
- Chrome supports PlayReady SL3000 on Windows 11 (Chrome 142+)
Quick Start
For working examples, check out the Sample Project.
Installation
npm install @doverunner/secureplay-webBasic Usage
import { SecurePlay } from '@doverunner/secureplay-web';
// Create player instance
const player = new SecurePlay({
videoElement: document.getElementById('video'),
});
// Initialize
await player.initialize();
// Load DRM content
await player.load({
manifestUri: 'https://example.com/manifest.mpd',
drm: {
licenseUri: 'https://license.example.com/license',
headers: {
'pallycon-customdata-v2': 'YOUR_LICENSE_TOKEN'
},
drmType: 'widevine'
}
});
// Play
await player.play();API Reference
Constructor
const player = new SecurePlay({
videoElement: HTMLVideoElement, // Required: Video element
logger?: {
level?: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' // Optional: Log level
}
});Methods
initialize()
Initialize the player.
await player.initialize();load(options)
Load content for playback.
await player.load({
manifestUri: string, // Required: DASH or HLS manifest URI
drm?: {
licenseUri: string, // Required: License server URI
certificateUri?: string, // Optional: Certificate URI (Widevine/FairPlay)
headers?: object, // Optional: Custom headers (e.g., authentication tokens)
drmType?: 'widevine' | 'fairplay' | 'playready', // Optional: Specific DRM type
// Hardware DRM options
videoRobustness?: string | string[], // Optional: Video robustness level (e.g., ['HW_SECURE_ALL'])
audioRobustness?: string | string[], // Optional: Audio robustness level (e.g., ['HW_SECURE_CRYPTO'])
securityLevel?: number, // Optional: PlayReady security level (currently only 3000 is supported)
// License Cipher options
useLicenseCipher?: boolean, // Optional: Enable License Cipher (default: false)
siteId?: string, // Optional: DoveRunner site ID (required when useLicenseCipher is true)
cipherSdkUrl?: string, // Optional: URL to License Cipher SDK file (for dynamic loading)
// CSL (Concurrent Stream Limitation) options
manualRenewalIntervalSec?: number // Optional: Renewal interval in seconds (0 to disable, default: 0)
}
});play()
Start playback.
await player.play();pause()
Pause playback.
player.pause();seek(time)
Seek to specific time.
player.seek(30); // Seek to 30 secondsgetCurrentTime()
Get current playback time.
const time = player.getCurrentTime();getDuration()
Get video duration.
const duration = player.getDuration();isPaused() / isEnded()
Check playback state.
const paused = player.isPaused();
const ended = player.isEnded();setVolume() / getVolume()
Control volume (0.0 to 1.0).
player.setVolume(0.5);
const volume = player.getVolume();setMuted() / isMuted()
Control mute state.
player.setMuted(true);
const muted = player.isMuted();setPlaybackRate() / getPlaybackRate()
Control playback speed.
player.setPlaybackRate(1.5); // 1.5x speed
const rate = player.getPlaybackRate();getBuffered()
Get buffered time ranges.
const buffered = player.getBuffered();getPlayerInstance()
Get the underlying player instance (for advanced usage).
const shakaPlayer = player.getPlayerInstance();
// Now you can use Shaka Player API directlyaddEventListener(eventType, callback)
Add event listener.
player.addEventListener('error', (event) => {
console.error('Player error:', event);
});removeEventListener(eventType, callback)
Remove event listener.
player.removeEventListener('error', callback);destroy()
Destroy the player and clean up resources.
await player.destroy();DRM Support
Widevine (Chrome, Edge, Firefox)
await player.load({
manifestUri: 'https://example.com/stream.mpd',
drm: {
licenseUri: 'https://license.example.com/widevine',
certificateUri: 'https://license.example.com/cert',
headers: {
'pallycon-customdata-v2': 'YOUR_WIDEVINE_TOKEN' // or other custom headers
},
drmType: 'widevine'
}
});FairPlay (Safari)
await player.load({
manifestUri: 'https://example.com/master.m3u8',
drm: {
licenseUri: 'https://license.example.com/fairplay',
certificateUri: 'https://license.example.com/fps-cert',
headers: {
'pallycon-customdata-v2': 'YOUR_FAIRPLAY_TOKEN'
},
drmType: 'fairplay'
}
});PlayReady (IE, Edge Legacy)
await player.load({
manifestUri: 'https://example.com/stream.mpd',
drm: {
licenseUri: 'https://license.example.com/playready',
headers: {
'pallycon-customdata-v2': 'YOUR_PLAYREADY_TOKEN'
},
drmType: 'playready'
}
});Note: The headers field allows flexible authentication. Different DRM services may use different header names (e.g., Authorization, X-Custom-Data, etc.).
Hardware DRM (Widevine L1, PlayReady SL3000)
For enhanced security, you can use hardware-backed DRM which provides the highest level of content protection.
Widevine L1
await player.load({
manifestUri: 'https://example.com/stream_hardware.mpd',
drm: {
licenseUri: 'https://license.example.com/widevine',
certificateUri: 'https://license.example.com/cert',
headers: {
'pallycon-customdata-v2': 'YOUR_WIDEVINE_L1_TOKEN'
},
drmType: 'widevine',
// Hardware DRM configuration
videoRobustness: ['HW_SECURE_ALL'],
audioRobustness: ['HW_SECURE_CRYPTO']
}
});PlayReady SL3000
await player.load({
manifestUri: 'https://example.com/stream_hardware.mpd',
drm: {
licenseUri: 'https://license.example.com/playready',
headers: {
'pallycon-customdata-v2': 'YOUR_PLAYREADY_SL3000_TOKEN'
},
drmType: 'playready',
// Use hardware level key system (com.microsoft.playready.recommendation.3000)
securityLevel: '3000' // Currently only 3000 is supported (SL3000)
}
});Notes:
- Hardware DRM requires device hardware support (TEE, Secure Video Path)
- Widevine L1: Available on Android devices with hardware DRM support
- PlayReady SL3000: Available on Windows devices with hardware DRM support
Concurrent Stream Limitation (CSL)
For multi-device control, you can enable automatic license renewal which is required for CSL scenarios.
await player.load({
manifestUri: 'https://example.com/manifest.mpd',
drm: {
licenseUri: 'https://license.example.com/widevine',
certificateUri: 'https://license.example.com/cert',
headers: {
'pallycon-customdata-v2': 'YOUR_TOKEN_WITH_CSL'
},
drmType: 'fairplay',
// Enable automatic license renewal for CSL (0 to disable)
manualRenewalIntervalSec: 600 // Renew every 10 minutes
}
});How CSL Works:
- License server tracks active playback sessions
- SDK automatically renews licenses at specified intervals
- Server can revoke licenses when concurrent stream limit is exceeded
- Playback stops if license renewal fails
Notes:
- CSL options are typically used for FairPlay and PlayReady
- Widevine handles license renewal automatically via CDM
- Set
manualRenewalIntervalSecto 0 to disable automatic renewal - License renewal interval can be configured or returned from the license server response
License Cipher (DRM Challenge Encryption)
DoveRunner License Cipher provides an additional security layer by encrypting DRM license challenges at the network level before they are sent to the license server. This protects against content key extraction attacks on web browsers.
Prerequisites:
- Obtain License Cipher SDK files from DoveRunner
- Host all SDK files in the same directory on your server
- Each DoveRunner site ID requires its own unique SDK files
- License token must have
enable_license_cipher: truefor the license server to decrypt challenges
Option 1: Pre-load SDK via Script Tag
<!-- Load SDK before SecurePlay -->
<script src="/path/to/your/cipher/module.js"></script>
<script src="secureplay.umd.js"></script>
<script>
const player = new SecurePlay.SecurePlay({
videoElement: document.getElementById('video')
});
await player.load({
manifestUri: 'https://example.com/manifest.mpd',
drm: {
licenseUri: 'https://license.example.com/widevine',
certificateUri: 'https://license.example.com/cert',
headers: {
'pallycon-customdata-v2': 'YOUR_TOKEN'
},
drmType: 'widevine',
// Enable License Cipher
useLicenseCipher: true,
siteId: 'YOUR_SITE_ID' // Required when useLicenseCipher is enabled
}
});
</script>Option 2: Dynamic SDK Loading
await player.load({
manifestUri: 'https://example.com/manifest.mpd',
drm: {
licenseUri: 'https://license.example.com/widevine',
certificateUri: 'https://license.example.com/cert',
headers: {
'pallycon-customdata-v2': 'YOUR_TOKEN'
},
drmType: 'widevine',
// Enable License Cipher with dynamic SDK loading
useLicenseCipher: true,
siteId: 'YOUR_SITE_ID',
cipherSdkUrl: '/path/to/your/cipher/module.js' // SDK will be loaded dynamically
}
});Supported DRM Types:
- ✅ Widevine: License challenges are automatically encrypted
- ✅ PlayReady: License challenges are automatically encrypted
- ❌ FairPlay: Not supported
For License Cipher SDK files and detailed integration guide, please contact DoveRunner Support.
Error Handling
SecurePlay uses a structured error code system:
| Category | Code Range | Example | |----------|-----------|---------| | Player | SP-1xxx | SP-1001: PLAYER_INIT_FAILED, SP-1006: PLAYER_RENDERER_ERROR | | DRM | SP-2xxx | SP-2001: DRM_LICENSE_REQUEST_FAILED, SP-2008: DRM_PROVISIONING_FAILED | | Network | SP-3xxx | SP-3001: NETWORK_REQUEST_FAILED, SP-3005: NETWORK_NO_CONNECTION | | Config | SP-4xxx | SP-4001: CONFIG_INVALID, SP-4005: CONFIG_INVALID_MEDIA_URL | | Download | SP-5xxx | SP-5001: DOWNLOAD_FAILED, SP-5002: DOWNLOAD_INSUFFICIENT_STORAGE | | General | SP-9xxx | SP-9999: UNKNOWN_ERROR |
Example
try {
await player.load({ manifestUri, drm });
} catch (error) {
console.error('Error Code:', error.code); // SP-2001
console.error('Message:', error.message); // DRM license request failed
console.error('Recovery Hint:', error.recoveryHint);
console.error('Original:', error.originalError);
}Async Error Events
Errors that occur during playback (after initialization) are dispatched as events on the video element:
player.addEventListener('error', (event) => {
const error = event.detail;
console.error('Playback Error:', error.code, error.message);
// Handle specific error types
switch (error.code) {
case 'SP-2010': // DRM_LICENSE_EXPIRED
// Prompt user to refresh or re-authenticate
break;
case 'SP-3001': // NETWORK_REQUEST_FAILED
// Show network error message
break;
default:
// Generic error handling
}
});Logging Best Practices
SecurePlay includes a built-in structured logger that helps with debugging and monitoring. The SDK automatically sanitizes sensitive information (tokens, URLs with query parameters, etc.) from log output.
Security Recommendation
⚠️ Important: In production environments, set the log level to WARN or ERROR to minimize information disclosure and improve performance.
import { SecurePlay, LogLevel } from 'secureplay-web';
// Recommended: Use environment-based log level
const logLevel = process.env.NODE_ENV === 'production'
? LogLevel.WARN // Production: Only warnings and errors
: LogLevel.DEBUG; // Development: Full debug logging
const player = new SecurePlay({
videoElement: document.getElementById('video'),
logger: {
level: logLevel
}
});Available Log Levels
DEBUG: Detailed information for debugging (includes all logs)INFO: General informational messages (default in development)WARN: Warning messages (recommended for production)ERROR: Error messages only (most restrictive)
Automatic Data Sanitization
The logger automatically masks sensitive information:
// Input
logger.info('Loading content', {
url: 'https://cdn.example.com/video.mpd?token=secret123',
authorization: 'Bearer xyz-token-abc'
});
// Actual log output (sanitized)
{
url: 'https://cdn.example.com/video.mpd?***PARAMS_MASKED***',
authorization: 'Be...bc' // Only first 2 and last 2 characters shown
}Automatically masked keys: token, authorization, apikey, secret, password, session, cookie, deviceid
