Version: 2.0.0 Last Updated: January 2026 Status: Final
- System Overview
- SDK Technical Specification
- Event Schema Specification
- Metrics Specification
- Dimensions Catalog
- Go Worker Specification
- Backend Integration
- CMCD Implementation
- Performance Requirements
- Security Specifications
┌─────────────────────────────────────────────────────────────┐
│ Browser Environment │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Video Player (HTML5/Video.js/HLS.js/dash.js/Shaka)│ │
│ └─────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌─────────────────▼──────────────────────────────────┐ │
│ │ openqoe-core SDK (TypeScript) │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Player Adapter Layer │ │ │
│ │ │ - HTML5Adapter │ │ │
│ │ │ - VideoJsAdapter │ │ │
│ │ │ - HlsJsAdapter │ │ │
│ │ │ - DashJsAdapter │ │ │
│ │ │ - ShakaAdapter │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Core SDK Components │ │ │
│ │ │ - Event Collector │ │ │
│ │ │ - CMCD Processor │ │ │
│ │ │ - Session Manager │ │ │
│ │ │ - Privacy Module (Hashing) │ │ │
│ │ │ - Batch Manager │ │ │
│ │ │ - Offline Queue │ │ │
│ │ │ - Retry Logic (Exponential Backoff) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────┬──────────────────────────────────┘ │
└────────────────────┼────────────────────────────────────────┘
│ HTTPS (TLS 1.2+)
│ Batched Events (JSON)
▼
┌─────────────────────────────────────────────────────────────┐
│ Go Worker (Ingest & OTLP) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Authentication & Validation Layer │ │
│ │ - Token verification │ │
│ │ - Schema validation (JSON Schema) │ │
│ │ - Version checks │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Privacy & PII Control Layer │ │
│ │ - Hash verification │ │
│ │ - PII allowlist enforcement │ │
│ │ - Redaction engine │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Cardinality Governance Engine │ │
│ │ - Dimension policy enforcement │ │
│ │ - Top-K bucketing │ │
│ │ - Label sanitization │ │
│ │ - Quota management │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Transform & Route Layer │ │
│ │ - Event → Loki transformer │ │
│ │ - Metric → Prometheus transformer │ │
│ │ - Circuit breaker │ │
│ │ - Batch accumulator │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────┬──────────────────┬───────────────────────────┘
│ │
│ HTTP POST │ remote_write
│ (JSON) │ (Protobuf)
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Loki │ │ Prometheus/Mimir│
│ (Logs) │ │ (Metrics) │
│ │ │ │
│ - Raw events │ │ - Aggregated │
│ - Per-view data │ │ metrics │
│ - Error traces │ │ - Time-series │
└────────┬────────┘ └────────┬────────┘
│ │
└──────────┬───────────┘
│
│ LogQL / PromQL
▼
┌─────────────────────────┐
│ Grafana │
│ │
│ - VOD Monitoring │
│ - Impact Explorer │
│ - Quality & Delivery │
│ - Content Performance │
│ - Ingest Health │
│ │
│ - Alerting Engine │
│ - Runbook Links │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ Notification Channels │
│ - Slack │
│ - PagerDuty │
│ - Email │
└─────────────────────────┘
| Component | Technology | Version |
|---|---|---|
| SDK Language | TypeScript | 5.x+ |
| Build Tool | Rollup/Webpack | Latest |
| Package Manager | npm/yarn | Latest |
| Ingestion Worker | Go (1.21+) | - |
| Telemetry Collector | Grafana Alloy | - |
| Tracing Backend | Grafana Tempo | - |
| Serialization | JSON (Ingest) / OTLP (Export) | - |
| Metrics Format | OTLP / Prometheus | - |
interface OpenQoEConfig {
// Required
orgId: string; // Organization identifier
playerId: string; // Player instance identifier
endpointUrl: string; // Ingest endpoint URL
// Optional
env?: "dev" | "staging" | "prod"; // Environment
appName?: string; // Application name
appVersion?: string; // Application version
samplingRate?: number; // 0.0 - 1.0 (default: 1.0)
// Privacy
enablePII?: boolean; // Enable PII collection (default: false)
hashSalt?: string; // Per-org salt for hashing
// Performance
batchSize?: number; // Events per batch (default: 10)
batchInterval?: number; // Batch flush interval ms (default: 5000)
maxQueueSize?: number; // Max offline queue (default: 100)
// Debugging
debug?: boolean; // Enable console logging
logLevel?: "error" | "warn" | "info" | "debug";
}
class OpenQoE {
constructor(config: OpenQoEConfig);
// Player integration
attachPlayer(
playerType: "html5" | "videojs" | "hlsjs" | "dashjs" | "shaka",
playerInstance: any,
metadata?: VideoMetadata,
): void;
// Manual event tracking
trackEvent(eventType: string, data: any): void;
// Session management
startSession(): void;
endSession(): void;
// Lifecycle
destroy(): void;
}Responsibilities:
- Capture player events
- Enrich with context (device, network, etc.)
- Apply privacy transformations
- Queue for batching
Performance Targets:
- Event capture: <1ms per event
- Memory footprint: <5MB
- CPU overhead: <1% on modern devices
Session Lifecycle:
interface Session {
sessionId: string; // UUID v4
viewId: string; // UUID v4 (unique per video view)
viewStart: number; // Unix timestamp ms
viewEnd?: number; // Unix timestamp ms
playbackPosition: number; // Current position ms
lastHeartbeat: number; // Last heartbeat timestamp
metadata: {
orgId: string;
playerId: string;
env: string;
appName?: string;
appVersion?: string;
};
}Session Rules:
- New
viewIdon eachviewstartevent sessionIdpersists across views (browser session)- Heartbeats every 10 seconds during playback
- Session expires after 30 minutes of inactivity
Hashing Implementation:
interface PrivacyConfig {
hashAlgorithm: "SHA-256";
salt: string; // Per-org salt
fieldsToHash: string[]; // viewer_id, session_id, etc.
}
// Example hashed field
const hashedViewerId = SHA256(viewerId + orgSalt);PII Handling:
- Always hashed: viewer_id, session_id, user_id
- Never collected: email, name, phone, IP address
- Configurable: device_id, custom fields
Batching Strategy:
interface BatchConfig {
maxBatchSize: number; // Max events per batch (default: 10)
maxBatchInterval: number; // Max time before flush ms (default: 5000)
maxRetries: number; // Max retry attempts (default: 3)
backoffMultiplier: number; // Exponential backoff (default: 2)
maxBackoff: number; // Max backoff ms (default: 30000)
}Flush Triggers:
- Batch size reached (default: 10 events)
- Time interval elapsed (default: 5 seconds)
- Critical event (error, ended)
- Page unload (beforeunload)
Queue Behavior:
- Store events in memory when offline
- Persist to localStorage (optional, max 1MB)
- Flush on reconnection
- FIFO eviction when full
interface QueueConfig {
maxSize: number; // Max events (default: 100)
persistToStorage: boolean; // Use localStorage (default: true)
storageKey: string; // Storage key (default: 'openqoe_queue')
}Each adapter implements the PlayerAdapter interface:
interface PlayerAdapter {
// Lifecycle
attach(player: any, metadata: VideoMetadata): void;
detach(): void;
// Event handlers
onPlayerReady(): void;
onViewStart(): void;
onPlaying(): void;
onPause(): void;
onSeek(): void;
onStallStart(): void;
onStallEnd(): void;
onQualityChange(): void;
onEnded(): void;
onError(error: PlayerError): void;
// State getters
getCurrentTime(): number;
getDuration(): number;
getBitrate(): number;
getVideoResolution(): { width: number; height: number };
getPlayerState(): PlayerState;
// CMCD extraction
getCMCDData(): CMCDSnapshot;
}Event Mapping:
| Native Event | openqoe Event |
|---|---|
loadedmetadata |
playerready |
loadstart |
viewstart |
play |
playing |
pause |
pause |
seeking |
seek |
waiting |
stall_start |
playing (after waiting) |
stall_end |
ended |
ended |
error |
error |
Event Mapping:
| Video.js Event | openqoe Event |
|---|---|
ready |
playerready |
loadstart |
viewstart |
play |
playing |
pause |
pause |
seeking |
seek |
waiting |
stall_start |
playing |
stall_end |
ended |
ended |
error |
error |
qualitychange |
quality_change |
Event Mapping:
| HLS.js Event | openqoe Event |
|---|---|
MANIFEST_LOADED |
playerready |
MEDIA_ATTACHED |
viewstart |
(video) play |
playing |
(video) pause |
pause |
(video) seeking |
seek |
BUFFER_STALLED |
stall_start |
(video) playing |
stall_end |
(video) ended |
ended |
ERROR |
error |
LEVEL_SWITCHED |
quality_change |
CMCD Extraction:
- Access
hls.config.cmcdobject - Extract from XHR/fetch request headers
Event Mapping:
| dash.js Event | openqoe Event |
|---|---|
streamInitialized |
playerready |
playbackStarted |
viewstart |
playbackPlaying |
playing |
playbackPaused |
pause |
playbackSeeking |
seek |
playbackStalled |
stall_start |
playbackPlaying |
stall_end |
playbackEnded |
ended |
error |
error |
qualityChangeRendered |
quality_change |
Event Mapping:
| Shaka Event | openqoe Event |
|---|---|
loaded |
playerready |
streaming |
viewstart |
(video) play |
playing |
(video) pause |
pause |
(video) seeking |
seek |
buffering (true) |
stall_start |
buffering (false) |
stall_end |
(video) ended |
ended |
error |
error |
adaptation |
quality_change |
All events share this base structure:
{
"event_type": "string", // Event name
"event_time": 1234567890123, // Event timestamp (ms)
"viewer_time": 1234567890123, // Client timestamp (ms)
"playback_time": 5432, // Position in video (ms)
// Session identifiers
"org_id": "org_abc123",
"player_id": "player_xyz789",
"view_id": "uuid-v4",
"session_id": "uuid-v4",
"viewer_id": "hashed-sha256",
// Environment
"env": "prod",
"app_name": "MyVideoApp",
"app_version": "2.1.0",
// Device context
"device": {
"name": "MacBook Pro",
"model": "MacBookPro18,1",
"category": "desktop",
"manufacturer": "Apple"
},
// OS context
"os": {
"family": "macOS",
"version": "14.1"
},
// Browser context
"browser": {
"family": "Chrome",
"version": "120.0.0"
},
// Player context
"player": {
"name": "hls.js",
"version": "1.5.0",
"autoplay": true,
"preload": "auto"
},
// Network context
"network": {
"asn": 7922,
"country_code": "US",
"region": "CA",
"city": "San Francisco" // Optional, based on policy
},
// CDN context (if detectable)
"cdn": {
"provider": "cloudflare",
"edge_pop": "SFO",
"origin": "origin.example.com"
},
// Video metadata
"video": {
"id": "video_123",
"title": "Sample Video",
"series": "Series Name",
"duration": 120000, // ms
"source_url": "https://cdn.example.com/video.m3u8" // Sanitized
},
// Event-specific data
"data": {} // Varies by event type
}{
"event_type": "playerready",
"data": {
"player_startup_time": 125, // ms
"page_load_time": 1234 // ms (if available)
}
}{
"event_type": "viewstart",
"data": {
"video_startup_time": 450, // ms to first frame
"preroll_requested": false
}
}{
"event_type": "playing",
"data": {
"bitrate": 2500000, // bps
"resolution": {
"width": 1920,
"height": 1080
},
"framerate": 30
}
}{
"event_type": "pause",
"data": {
"playing_time": 45000 // ms spent playing
}
}{
"event_type": "seek",
"data": {
"from": 10000, // ms
"to": 45000, // ms
"seek_latency": 234 // ms to resume playback
}
}{
"event_type": "stall_start",
"data": {
"buffer_length": 1200, // ms of buffered content
"bitrate": 2500000 // bps at stall
}
}{
"event_type": "stall_end",
"data": {
"stall_duration": 1234, // ms
"buffer_length": 5000 // ms after recovery
}
}{
"event_type": "quality_change",
"data": {
"previous_bitrate": 1500000, // bps
"new_bitrate": 2500000, // bps
"previous_resolution": {
"width": 1280,
"height": 720
},
"new_resolution": {
"width": 1920,
"height": 1080
},
"trigger": "abr" // "abr" | "manual"
}
}{
"event_type": "ended",
"data": {
"playing_time": 118000, // ms
"total_watch_time": 125000, // ms (includes pauses)
"completion_rate": 0.98, // 0.0 - 1.0
"rebuffer_count": 2,
"rebuffer_duration": 1500 // ms total
}
}{
"event_type": "error",
"data": {
"error_family": "network", // "network" | "decoder" | "source" | "drm"
"error_id": 1001,
"error_code": "MANIFEST_LOAD_ERROR",
"error_message": "Failed to load manifest",
"error_context": {
"url": "https://cdn.example.com/video.m3u8",
"http_status": 404,
"fatal": true
}
}
}{
"event_type": "quartile",
"data": {
"quartile": 25, // 25 | 50 | 75 | 100
"playing_time": 30000, // ms
"watch_time": 32000 // ms
}
}{
"event_type": "heartbeat",
"data": {
"playing_time": 45000, // ms since viewstart
"bitrate": 2500000, // bps
"buffer_length": 15000, // ms
"dropped_frames": 5
}
}| Metric ID | Display Name | Measurement | Unit | Description |
|---|---|---|---|---|
aggregate_startup_time |
Aggregate Startup Time | median, 95th, avg | ms | Total time from page load to first frame |
player_startup_time |
Player Startup Time | median, 95th | ms | Time to initialize player |
video_startup_time |
Video Startup Time | median, 95th | ms | Time from play request to first frame |
page_load_time |
Page Load Time | median, 95th | ms | Browser page load time |
video_startup_failure_percentage |
Startup Failure % | avg | % | Percentage of views that failed to start |
| Metric ID | Display Name | Measurement | Unit | Description |
|---|---|---|---|---|
rebuffer_percentage |
Rebuffer % | avg | % | % of playing time spent rebuffering |
rebuffer_duration |
Rebuffer Duration | median, 95th, sum | ms | Total rebuffer time per view |
rebuffer_frequency |
Rebuffer Frequency | avg | per min | Rebuffers per minute of playtime |
rebuffer_count |
Rebuffer Count | median, 95th, count | count | Number of rebuffer events |
rebuffer_score |
Rebuffer Score | avg | 0-1 | Composite smoothness score |
| Metric ID | Display Name | Measurement | Unit | Description |
|---|---|---|---|---|
weighted_average_bitrate |
Avg Bitrate | median, 95th | bps | Time-weighted average bitrate |
upscale_percentage |
Upscale % | median, 95th, avg | % | % time video upscaled vs player size |
downscale_percentage |
Downscale % | median, 95th, avg | % | % time video downscaled vs player size |
video_quality_score |
Video Quality Score | avg | 0-1 | Composite quality score |
| Metric ID | Display Name | Measurement | Unit | Description |
|---|---|---|---|---|
playing_time |
Playing Time | sum | ms | Total time in playing state |
watch_time |
Watch Time | sum | ms | Total session duration |
views |
Views | count | count | Total view count |
unique_viewers |
Unique Viewers | count | count | Distinct viewer count |
| Metric ID | Display Name | Measurement | Unit | Description |
|---|---|---|---|---|
exits_before_video_start |
Exit Before Start % | avg | % | % viewers who leave before playback |
playback_failure_percentage |
Playback Failure % | avg | % | % of views with playback errors |
| Metric ID | Display Name | Measurement | Unit | Description |
|---|---|---|---|---|
viewer_experience_score |
Viewer Experience Score | avg | 0-1 | Overall QoE score |
startup_time_score |
Startup Time Score | avg | 0-1 | Startup quality score |
| Dimension | Type | Cardinality | Example Values |
|---|---|---|---|
asn |
integer | ~50K | 7922, 15169 |
cdn |
string | ~20 | "cloudflare", "fastly", "akamai" |
country |
string | ~250 | "US", "GB", "JP" |
operating_system |
string | ~10 | "macOS", "Windows", "Linux", "iOS", "Android" |
player_name |
string | 5 | "html5", "videojs", "hlsjs", "dashjs", "shaka" |
region |
string | ~5K | "CA", "NY", "London" |
stream_type |
string | 2 | "VOD", "Live" |
video_title |
string | High | Bucketed to top-100 + "Other" |
| Dimension | Type | Cardinality | Policy |
|---|---|---|---|
viewer_device_name |
string | High | Bucket to families |
viewer_device_model |
string | Very High | Bucket to top-K |
viewer_device_category |
string | ~5 | "desktop", "mobile", "tablet", "tv" |
viewer_device_manufacturer |
string | ~100 | "Apple", "Samsung", "Google" |
| Dimension | Type | Cardinality | Policy |
|---|---|---|---|
browser |
string | ~10 | Allow |
browser_version |
string | High | Bucket to major version |
operating_system |
string | ~10 | Allow |
operating_system_version |
string | High | Bucket to major version |
| Dimension | Type | Cardinality | Policy |
|---|---|---|---|
player_software |
string | ~20 | Allow |
player_software_version |
string | High | Bucket to major version |
player_autoplay |
boolean | 2 | Allow |
player_preload |
string | 3 | "none", "metadata", "auto" |
| Dimension | Type | Cardinality | Policy |
|---|---|---|---|
video_id |
string | Very High | Hash or bucket |
video_title |
string | Very High | Top-100 + "Other", length cap 100 |
video_series |
string | High | Top-50 + "Other" |
stream_type |
string | 2 | "VOD", "Live" |
| Dimension | Type | Cardinality | Policy |
|---|---|---|---|
asn |
integer | ~50K | Allow |
country |
string | ~250 | Allow |
region |
string | ~5K | Allow |
cdn |
string | ~20 | Allow |
video_cdn_trace |
array | Very High | Drop (trace dimension) |
| Dimension | Type | Cardinality | Policy |
|---|---|---|---|
custom_1 - custom_10 |
string | Varies | Per-org approval required |
The Go Worker uses the Gin web framework for HTTP handling and a buffered channel with a worker pool for OTLP export.
// Simplified main handler logic
func handleEvents(c *gin.Context) {
var envelope EventEnvelope
if err := c.ShouldBindJSON(&envelope); err != nil {
c.JSON(400, gin.H{"error": "invalid schema"})
return
}
// Push to internal queue for async OTLP export
eventQueue <- envelope.Events
c.Status(202) // Accepted
}- Ingestion: Gin-based REST API for synchronous event reception.
- Enrichment: Adds server-side context (e.g., GeoIP, User-Agent parsing).
- Governance: Protects backends from cardinality explosion.
- Export: OTLP-native exporter for Mimir, Loki, and Tempo.
Events are mapped to OTel metrics:
playing→ Gaugeopenqoe_playing_statusstall_start/end→ Histogramopenqoe_rebuffer_duration_secondsviewstart→ Counteropenqoe_views_started_total
Uses JSON Schema validation:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["event_type", "event_time", "org_id", "player_id", "view_id"],
"properties": {
"event_type": {
"type": "string",
"enum": [
"manifestload playerready canplay canplaythrough playing pause seek waitstart stallstart stallend ended error quartile heartbeat qualitychangerequested qualitychange fpsdrop fragmentloaded bufferlevelchange bandwidthchange playbackratechange playbackvolumechange playbackdetached moveaway moveback"
]
},
"event_time": { "type": "number" },
"org_id": { "type": "string", "minLength": 1 },
"player_id": { "type": "string", "minLength": 1 },
"view_id": { "type": "string", "format": "uuid" }
}
}interface CardinalityPolicy {
field: string;
action: "allow" | "bucket" | "hash" | "drop";
topK?: number; // For bucketing
maxLength?: number; // String length cap
bucketPattern?: string; // Regex for bucketing
}
const DEFAULT_POLICIES: CardinalityPolicy[] = [
// Always safe
{ field: "country", action: "allow" },
{ field: "region", action: "allow" },
{ field: "asn", action: "allow" },
{ field: "cdn", action: "allow" },
// Controlled
{ field: "video_title", action: "bucket", topK: 100, maxLength: 100 },
{ field: "device_model", action: "bucket", topK: 50 },
{ field: "browser_version", action: "bucket", bucketPattern: "^(\\d+)" }, // Major version only
// Dangerous
{ field: "source_url", action: "hash" },
{ field: "referrer", action: "drop" },
{ field: "query_params", action: "drop" },
];async function sendToLoki(events: Event[]): Promise<void> {
const streams = events.map((event) => ({
stream: {
org_id: event.org_id,
player_id: event.player_id,
event_type: event.event_type,
env: event.env || "prod",
},
values: [[String(event.event_time * 1000000), JSON.stringify(event)]],
}));
await fetch(LOKI_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Scope-OrgID": event.org_id,
},
body: JSON.stringify({ streams }),
});
}async function sendToPrometheus(events: Event[]): Promise<void> {
const metrics = aggregateToMetrics(events);
const timeSeries = metrics.map((metric) => ({
labels: [
{ name: "__name__", value: metric.name },
{ name: "org_id", value: metric.orgId },
{ name: "player_id", value: metric.playerId },
...metric.labels,
],
samples: [
{
value: metric.value,
timestamp: metric.timestamp,
},
],
}));
const writeRequest = createRemoteWriteRequest(timeSeries);
await fetch(PROMETHEUS_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-protobuf",
"X-Prometheus-Remote-Write-Version": "0.1.0",
},
body: writeRequest,
});
}| Metric | Target | Measurement Method |
|---|---|---|
| Bundle Size (gzip) | < 10 KB | webpack-bundle-analyzer |
| Event Capture Overhead | < 1 ms | performance.measure() |
| Memory Footprint | < 5 MB | Chrome DevTools Memory Profiler |
| CPU Usage | < 1% | Chrome DevTools Performance |
| Network Requests | ≤ 1 per 5s | Chrome DevTools Network |
| Metric | Target | Measurement Method |
|---|---|---|
| P50 Latency | < 50 ms | Worker Internal Stats |
| P95 Latency | < 100 ms | Worker Internal Stats |
| P99 Latency | < 200 ms | Worker Internal Stats |
| Success Rate | > 99.9% | Worker Internal Stats |
| CPU Time | < 50 ms | Worker Metrics |
| Path | Target | Measurement |
|---|---|---|
| Event → Loki | < 2 seconds | Timestamp delta |
| Event → Prometheus | < 5 seconds | Timestamp delta |
| Alert Detection | < 1 minute | Alert timestamp - event timestamp |
- Protocol: HTTPS only
- TLS Version: TLS 1.2 minimum, TLS 1.3 preferred
- Certificate Validation: Strict (no self-signed certs in prod)
- HSTS: Enabled with max-age=31536000
// Worker authentication
interface TokenConfig {
algorithm: "HMAC-SHA256" | "JWT";
secret: string; // Stored in KV/Secrets
ttl?: number; // Token TTL (optional)
}
// SDK does NOT store secrets
// Tokens injected server-side or via secure config endpointHashing Standard:
SHA-256(value + per_org_salt)
- Salt length: 32 bytes (256 bits)
- Salt storage: Environment Variables / Secrets Manager
- Salt rotation: Supported (with migration period)
PII Allowlist (default: empty):
interface PIIPolicy {
allowedFields: string[]; // Fields allowed to contain PII
redactionPattern: RegExp; // Pattern for auto-redaction
auditLog: boolean; // Log PII access
}const CORS_HEADERS = {
"Access-Control-Allow-Origin": "https://yourdomain.com", // Specific origin
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, X-API-Token",
"Access-Control-Max-Age": "86400",
};Recommended CSP for pages using openqoe-core:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline';
connect-src 'self' https://ingest.openqoe.example.com;
media-src 'self' https://cdn.example.com;
| Field | Key | Type | Description |
|---|---|---|---|
| Buffer Length | bl |
integer | Buffer length (ms) |
| Buffer Starvation | bs |
boolean | Buffer starvation flag |
| Content ID | cid |
string | Unique content identifier |
| Object Duration | d |
integer | Object duration (ms) |
| Deadline | dl |
integer | Deadline (ms) |
| Measured Throughput | mtp |
integer | Throughput (kbps) |
| Next Object Request | nor |
string | Next request URL |
| Next Range Request | nrr |
string | Next byte range |
| Object Type | ot |
string | Object type (m=manifest, a=audio, v=video, etc.) |
| Playback Rate | pr |
float | Playback rate |
| Requested Max Throughput | rtp |
integer | Requested max throughput (kbps) |
| Streaming Format | sf |
string | Streaming format (d=DASH, h=HLS, etc.) |
| Session ID | sid |
string | Session identifier |
| Stream Type | st |
string | Stream type (v=VOD, l=Live) |
| Startup | su |
boolean | Startup flag |
| Top Bitrate | tb |
integer | Top bitrate (kbps) |
| Version | v |
integer | CMCD version |
// Access from request headers or player config
player.on(Hls.Events.FRAG_LOADING, (event, data) => {
const cmcd = extractCMCDFromHeaders(data.frag.url);
});// Access from RequestModifier
player.on(dashjs.MediaPlayer.events.FRAGMENT_LOADING_STARTED, (e) => {
const cmcd = e.request.cmcd;
});// Access from network request filter
player.getNetworkingEngine().registerRequestFilter((type, request) => {
const cmcd = request.headers["CMCD"];
});End of Technical Specification v2.0