Video Playback
Video Playback API
HesedVid delivers video content through a global CDN using HLS (HTTP Live Streaming) with adaptive bitrate streaming. Our infrastructure automatically optimizes playback quality based on network conditions and device capabilities.
How It Works
HesedVid’s playback system provides:
- Global CDN: Videos cached at edge locations worldwide for low latency
- Adaptive Bitrate: Automatic quality switching based on bandwidth
- Multiple Formats: HLS and CMAF for broad compatibility
- Security: Signed URLs for private content access control
- Analytics: Real-time playback metrics and quality tracking
Public Video Playback
For videos with allowPublicAccess: true, stream directly from our CDN without authentication.
Public URL Format
https://worker.hesedvid.com/v1/public/{org_id}/{env_id}/{vid_id}/master.m3u8Inline public metadata has been removed. Use the path-based scheme above.
Example URLs
| Component | Example |
|---|---|
| Public Path | /v1/public/org_123/env_456/vid_789/master.m3u8 |
| 720p Playlist | /v1/public/org_123/env_456/vid_789/video-720p.m3u8 |
| Init/Segment | /v1/public/org_123/env_456/vid_789/video/init.m4s |
Private Video Playback
For videos with allowPublicAccess: false, request a signed URL with authentication token.
Get Signed Playback URL
/{orgID}/videos/{publicID}/signed-url
Generate a time-limited, authenticated URL for private video access.
Authentication
All requests require authentication via API key:
X-Api-Key: hv_live_123456789abcdef...Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
orgID | string | Yes | Your organization ID (e.g., org_123456789) |
publicID | string | Yes | Video public ID (e.g., vid_AbCdEfGhIjKlMnOp) |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
expiration | string | No | 1h | Token expiration time |
domains | string | No | - | Comma-separated allowed domains |
Expiration Formats
| Format | Duration | Use Case |
|---|---|---|
15m | 15 minutes | Short-term access |
30m | 30 minutes | Temporary viewing |
1h | 1 hour | Default, general use |
4h | 4 hours | Extended sessions |
24h | 24 hours | Long-term access |
7d | 7 days | Maximum duration |
Response
Success Response (200 OK)
{ "body": { "signed_url": "https://worker.hesedvid.com/v1/private/vid_AbCdEfGhIjKlMnOp/master.m3u8?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 3600 }}| Field | Type | Description |
|---|---|---|
signed_url | string | Complete URL with authentication token |
expires_in | integer | Seconds until token expiration |
Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | invalid_expiration | Expiration time exceeds maximum (7 days) |
| 401 | unauthorized | Invalid or missing API key |
| 403 | forbidden | Insufficient permissions for video |
| 404 | video_not_found | Video doesn’t exist or is deleted |
| 429 | rate_limit_exceeded | Too many signed URL requests |
Example: Request Signed URL
curl -X GET "https://api.hesedvid.com/v1/api/org_123/videos/vid_AbCdEfGhIjKlMnOp/signed-url?expiration=24h" \ -H "X-Api-Key: hv_live_123456789abcdef..."const getSignedURL = async (videoID, expiration = '1h') => { const response = await fetch( `https://api.hesedvid.com/v1/api/org_123/videos/${videoID}/signed-url?expiration=${expiration}`, { headers: { 'X-Api-Key': 'hv_live_123456789abcdef...' } } );
const { body } = await response.json(); return body.signed_url;};
const signedURL = await getSignedURL('vid_AbCdEfGhIjKlMnOp', '24h');console.log('Signed URL:', signedURL);import requests
def get_signed_url(video_id, expiration='1h'): response = requests.get( f'https://api.hesedvid.com/v1/api/org_123/videos/{video_id}/signed-url', params={'expiration': expiration}, headers={'X-Api-Key': 'hv_live_123456789abcdef...'} )
data = response.json() return data['body']['signed_url']
signed_url = get_signed_url('vid_AbCdEfGhIjKlMnOp', '24h')print(f'Signed URL: {signed_url}')<?phpfunction getSignedURL($videoID, $expiration = '1h') { $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.hesedvid.com/v1/api/org_123/videos/{$videoID}/signed-url?expiration={$expiration}"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Api-Key: hv_live_123456789abcdef...' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch); $data = json_decode($response, true);
curl_close($ch); return $data['body']['signed_url'];}
$signedURL = getSignedURL('vid_AbCdEfGhIjKlMnOp', '24h');echo "Signed URL: " . $signedURL;?>Caution
Signed URLs contain authentication tokens. Never expose them in client-side source code, share them publicly, or log them in plain text. Store them securely and refresh them before expiration.
Video Player Integration
HLS.js Integration
HLS.js is the most popular JavaScript library for HLS playback with broad browser support.
Basic Setup
<!DOCTYPE html><html><head> <title>HesedVid Player</title> <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script></head><body> <video id="video" controls width="100%" height="400"></video>
<script> const video = document.getElementById('video'); const videoSrc = 'https://worker.hesedvid.com/v1/private/vid_AbCdEfGhIjKlMnOp/master.m3u8?token=...';
if (Hls.isSupported()) { const hls = new Hls({ enableWorker: true, lowLatencyMode: false, backBufferLength: 90 });
hls.loadSource(videoSrc); hls.attachMedia(video);
// Error handling hls.on(Hls.Events.ERROR, (event, data) => { if (data.fatal) { console.error('Fatal error:', data.type, data.details); switch(data.type) { case Hls.ErrorTypes.NETWORK_ERROR: console.log('Network error, trying to recover...'); hls.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: console.log('Media error, trying to recover...'); hls.recoverMediaError(); break; default: console.log('Fatal error, cannot recover'); hls.destroy(); break; } } });
} else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Native HLS support (Safari) video.src = videoSrc; } else { console.error('HLS is not supported in this browser'); } </script></body></html>Advanced Configuration
const hls = new Hls({ // Performance settings enableWorker: true, lowLatencyMode: true, backBufferLength: 90, maxBufferLength: 30,
// Quality settings startLevel: -1, // Auto-detect starting quality capLevelToPlayerSize: true,
// Network settings maxLoadingDelay: 4, maxBufferHole: 0.5,
// Debugging debug: false, enableWorker: true});
// Quality change eventshls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => { const level = hls.levels[data.level]; console.log(`Switched to ${level.height}p (${level.bitrate} kbps)`);});
// Buffer eventshls.on(Hls.Events.BUFFER_STALLED, () => { console.log('Buffer stalled - network issue');});
hls.on(Hls.Events.BUFFER_RESUMED, () => { console.log('Buffer resumed');});Video.js Integration
Video.js provides a more feature-rich player with built-in HLS support.
<!DOCTYPE html><html><head> <title>HesedVid Video.js Player</title> <link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet"></head><body> <video-js id="my-video" class="vjs-default-skin vjs-big-play-centered" controls preload="auto" width="100%" height="400" data-setup='{}'> </video-js>
<script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script> <script> const player = videojs('my-video');
player.src({ src: 'https://worker.hesedvid.com/v1/private/vid_AbCdEfGhIjKlMnOp/master.m3u8?token=...', type: 'application/x-mpegURL' });
// Quality change events player.on('loadedmetadata', () => { console.log('Video metadata loaded'); });
player.on('error', (error) => { console.error('Player error:', error); }); </script></body></html>React Integration
import React, { useEffect, useRef } from 'react';import Hls from 'hls.js';
const HesedVidPlayer = ({ videoSrc, onError }) => { const videoRef = useRef(null); const hlsRef = useRef(null);
useEffect(() => { const video = videoRef.current; if (!video) return;
if (Hls.isSupported()) { const hls = new Hls({ enableWorker: true, lowLatencyMode: false });
hls.loadSource(videoSrc); hls.attachMedia(video);
hls.on(Hls.Events.ERROR, (event, data) => { if (data.fatal && onError) { onError(data); } });
hlsRef.current = hls; } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = videoSrc; }
return () => { if (hlsRef.current) { hlsRef.current.destroy(); } }; }, [videoSrc, onError]);
return ( <video ref={videoRef} controls width="100%" height="400" /> );};
export default HesedVidPlayer;Adaptive Bitrate Streaming
HesedVid automatically generates multiple quality levels based on your video quality settings:
Quality Presets
| Preset | Resolutions | Bitrates | Use Case |
|---|---|---|---|
standard | 360p, 480p, 720p | 500-2500 kbps | General content, mobile |
high | 480p, 720p, 1080p | 1000-5000 kbps | Premium content, desktop |
ultra | 720p, 1080p, 1440p, 2160p | 2000-15000 kbps | Professional content, 4K |
Quality Switching
The player automatically switches between qualities based on:
- Network bandwidth: Higher quality for faster connections
- Device capabilities: Appropriate resolution for screen size
- Buffer health: Lower quality if buffering issues occur
- CPU usage: Reduced quality on slower devices
Manual Quality Control
// Get available quality levelsconst levels = hls.levels;console.log('Available qualities:', levels.map(level => ({ height: level.height, bitrate: level.bitrate, width: level.width})));
// Set specific quality levelhls.currentLevel = 2; // Switch to level 2
// Enable manual quality selectionhls.on(Hls.Events.MANIFEST_PARSED, () => { // Add quality selector to UI const qualitySelect = document.getElementById('quality-select'); levels.forEach((level, index) => { const option = document.createElement('option'); option.value = index; option.textContent = `${level.height}p (${Math.round(level.bitrate/1000)}kbps)`; qualitySelect.appendChild(option); });
qualitySelect.addEventListener('change', (e) => { hls.currentLevel = parseInt(e.target.value); });});Low Latency Streaming
Enable low-latency mode for near real-time playback (2-6 second latency):
Configuration
const hls = new Hls({ enableWorker: true, lowLatencyMode: true, backBufferLength: 90, maxBufferLength: 30, maxBufferSize: 60 * 1000 * 1000, // 60MB maxBufferHole: 0.1});Low Latency Features
- LL-HLS: Low Latency HLS with CMAF chunks
- Bootstrap segments: Faster startup time
- Reduced buffering: Minimal pre-roll delay
- Real-time updates: Live-like experience for on-demand content
Tip
Low latency mode increases bandwidth usage and may cause more quality switches. Use only when real-time playback is essential.
CORS and Security
CORS Configuration
Our CDN includes permissive CORS headers for web applications:
Access-Control-Allow-Origin: *Access-Control-Allow-Methods: GET, HEAD, OPTIONSAccess-Control-Allow-Headers: Range, Content-TypeAccess-Control-Max-Age: 3600Domain Restrictions
For signed URLs, you can restrict playback to specific domains:
const response = await fetch( `https://api.hesedvid.com/v1/api/org_123/videos/${videoID}/signed-url?domains=example.com,app.example.com`, { headers: { 'X-Api-Key': 'hv_live_123456789abcdef...' } });Token Security
- JWT Tokens: Signed URLs use JWT tokens for authentication
- Expiration: Tokens automatically expire for security
- Domain Binding: Tokens can be bound to specific domains
- Single Use: Tokens are validated on each request
Direct Segment Access
Access individual segments or renditions directly for advanced use cases:
URL Patterns
| Component | Pattern | Example |
|---|---|---|
| Master Playlist | /{publicID}/master.m3u8 | /pubvid_AbCdEf/master.m3u8 |
| Rendition Playlist | /{publicID}/{quality}.m3u8 | /pubvid_AbCdEf/720p.m3u8 |
| Video Segments | /{publicID}/{quality}/{segment}.ts | /pubvid_AbCdEf/720p/segment-0001.ts |
| Init Segments | /{publicID}/{quality}/init.mp4 | /pubvid_AbCdEf/720p/init.mp4 |
Advanced Playlist Access
// Fetch master playlistconst masterResponse = await fetch('https://worker.hesedvid.com/pubvid_AbCdEf/master.m3u8');const masterPlaylist = await masterResponse.text();
// Parse available renditionsconst renditions = masterPlaylist.match(/#EXT-X-STREAM-INF:.*\n([^\n]+)/g);renditions.forEach(rendition => { const resolution = rendition.match(/RESOLUTION=(\d+x\d+)/); const bandwidth = rendition.match(/BANDWIDTH=(\d+)/); console.log(`Resolution: ${resolution[1]}, Bandwidth: ${bandwidth[1]} bps`);});Query Parameters
Additional parameters for playback optimization:
| Parameter | Type | Description |
|---|---|---|
token | string | Authentication token (for private videos) |
bootstrap | boolean | Include low-latency bootstrap segments |
_HLS_msn | integer | Media sequence number (for low latency) |
_HLS_part | integer | Part number (for low latency) |
_HLS_skip | boolean | Skip segments for faster startup |
Performance Monitoring
Player Events
Monitor playback quality and performance:
// Quality change trackinghls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => { const level = hls.levels[data.level]; console.log(`Quality changed to ${level.height}p (${level.bitrate} kbps)`);
// Send analytics analytics.track('quality_change', { resolution: level.height, bitrate: level.bitrate, reason: data.reason });});
// Buffer health monitoringhls.on(Hls.Events.BUFFER_STALLED, () => { console.log('Buffer stalled - network issue'); analytics.track('buffer_stall');});
hls.on(Hls.Events.BUFFER_RESUMED, () => { console.log('Buffer resumed'); analytics.track('buffer_resume');});
// Error trackinghls.on(Hls.Events.ERROR, (event, data) => { if (data.fatal) { console.error('Fatal error:', data.type, data.details); analytics.track('fatal_error', { type: data.type, details: data.details }); }});Analytics Integration
// Track playback metricsconst trackPlaybackMetrics = () => { const video = document.getElementById('video');
video.addEventListener('loadstart', () => { analytics.track('playback_started'); });
video.addEventListener('canplay', () => { analytics.track('playback_ready'); });
video.addEventListener('ended', () => { analytics.track('playback_completed'); });
video.addEventListener('timeupdate', () => { const progress = (video.currentTime / video.duration) * 100; if (progress % 25 === 0) { // Track 25%, 50%, 75%, 100% analytics.track('playback_progress', { progress }); } });};Best Practices
Performance Optimization
Use appropriate preload settings: Set preload="metadata" to improve page load without downloading video content.
Implement lazy loading: Only load video players when they’re about to be viewed.
Monitor bandwidth usage: Track quality switches and buffer events to optimize user experience.
Cache signed URLs: Store signed URLs temporarily to reduce API calls, but refresh before expiration.
Error Handling
const handlePlaybackError = (error) => { switch(error.type) { case Hls.ErrorTypes.NETWORK_ERROR: console.log('Network error - retrying...'); setTimeout(() => hls.startLoad(), 1000); break;
case Hls.ErrorTypes.MEDIA_ERROR: console.log('Media error - recovering...'); hls.recoverMediaError(); break;
case Hls.ErrorTypes.MUX_ERROR: console.log('Mux error - cannot recover'); showErrorMessage('Video playback failed. Please refresh the page.'); break;
default: console.log('Unknown error:', error); break; }};Security Considerations
- Never expose signed URLs in client-side source code
- Implement domain restrictions for sensitive content
- Use appropriate expiration times based on your use case
- Monitor token usage for security breaches
- Implement rate limiting on your signed URL requests
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Video won’t play | Invalid URL or expired token | Check URL format and token expiration |
| Poor quality | Network bandwidth issues | Implement quality selection UI |
| Buffering issues | Slow network or server issues | Check network connection and CDN status |
| CORS errors | Domain not allowed | Check CORS configuration and domain restrictions |
| Token errors | Invalid or expired token | Request new signed URL |
Debug Mode
Enable debug mode for detailed logging:
const hls = new Hls({ debug: true, enableWorker: true});
// Additional debugginghls.on(Hls.Events.MANIFEST_PARSED, () => { console.log('Manifest parsed:', hls.levels);});
hls.on(Hls.Events.FRAG_LOADED, (event, data) => { console.log('Fragment loaded:', data.frag.url);});Support
For additional help:
- Check our health endpoint
GET /healthfor service issues - Review error codes for detailed explanations
- Contact support with your organization ID and video ID for assistance
Public playback
Use the path-based public URL:
https://worker.hesedvid.com/v1/public/{org_id}/{env_id}/{vid_id}/master.m3u8Note: For private videos, the worker rewrites playlists on the fly so the token propagates to all variant and segment requests automatically. You only need to use the returned
signed_urlfor the initial load.