Skip to content

feat: Refactor Quality of Experience (QoE) aggregate metrics along with it's configuration#119

Merged
skatti97 merged 12 commits intostable-betafrom
qoe-harvest-cycle
Mar 18, 2026
Merged

feat: Refactor Quality of Experience (QoE) aggregate metrics along with it's configuration#119
skatti97 merged 12 commits intostable-betafrom
qoe-harvest-cycle

Conversation

@skatti97
Copy link
Copy Markdown
Contributor

@skatti97 skatti97 commented Mar 11, 2026

Summary

Introduces a QOE_AGGREGATE action under VideoAction that periodically reports Quality of Experience KPIs during video playback. QoE events are generated at harvest time and injected directly into the harvest batch, operating independently from the regular event buffer.

KPIs Tracked

Attribute Type Description
startupTime Long (ms) Time from CONTENT_REQUEST to CONTENT_START, minus ad time and pause time, cached at CONTENT_START
peakBitrate Long (bps) Highest observed contentBitrate during the session
averageBitrate Long (bps) Time-weighted average of contentBitrate during active playback only; non-play time (pause, buffer, seek) is excluded
totalPlaytime Long (ms) Total content playtime excluding pause, buffer, and seek; computed in real-time at harvest
totalRebufferingTime Long (ms) Total duration of all CONTENT_BUFFER_END events except initial buffers (bufferType == "initial")
rebufferingRatio Double (%) (totalRebufferingTime / totalPlaytime) * 100, recomputed at harvest with real-time totalPlaytime
hadStartupFailure Boolean true if a CONTENT_ERROR occurred before CONTENT_START
hadPlaybackFailure Boolean true if a CONTENT_ERROR occurred after CONTENT_START

How It Works

Architecture Overview:

  • Early Registration - At CONTENT_REQUEST, the tracker registers a QoE provider with the harvest manager, ensuring QoE metrics (especially hadStartupFailure) are captured even if CONTENT_START never happens
  • Harvest-Time Generation - QoE events are generated at harvest time based on real-time metrics, decoupled from the regular VideoAction event buffer
  • Dirty Check - QoE events are only sent when KPI values have changed since the last send, using a snapshot-based comparison to reduce unnecessary data transmission
  • Independent Send - QoE sends independently on qualified harvest cycles based on the interval multiplier, regardless of whether other VideoAction events are present in the batch
  • Bitrate Timer Pause/Resume - Timer automatically pauses during non-play states (pause, buffer, seek) and resumes on playback, ensuring only active playback time is counted in the time-weighted average bitrate calculation
  • Final QoE - At CONTENT_END, the final QoE event is built eagerly while tracker state is still valid and enqueued for the next harvest, avoiding race conditions and ensuring no data loss at session end

Key Implementation Details:

  1. QoE Provider Pattern - Introduced QoeProvider interface that NRVideoTracker implements. Providers register with HarvestManager and are invoked during harvest cycles.

  2. Startup Time Calculation - Calculated once at CONTENT_START and cached for all subsequent QoE reports. Formula: startupTime = timeSinceRequested - adTime - pauseTime.

  3. Time-Weighted Bitrate - Maintains cumulative tracking: Σ(bitrate × duration) / Σ(duration). Segments are captured on rendition changes and timer pause events.

  4. Snapshot-Based Dirty Check - Stores a copy of the last sent QoE KPIs. Before generating QoE, compares current KPIs to the snapshot. Only sends if values have changed.

  5. Direct Injection - QoE events are injected directly into the harvest batch, bypassing the crash-safe event buffer, as they're computed on-demand.

  6. Context Attributes - QoE events include all standard VideoAction attributes (content metadata, player info, session data) by reusing the existing getAttributes() pipeline.

Configuration

Option Type Default Description
enableQoeAggregate Boolean false Master switch for QoE aggregate events (disabled by default)
qoeAggregateIntervalMultiplier Integer 1 QoE generated every N harvest cycles (1 = every cycle, 2 = every other, 3 = every third)

Harvest Cycle Formula: (harvestCycleNumber - 1) % intervalMultiplier == 0

Examples:

  • multiplier=1: cycles 1, 2, 3, 4... (every harvest)
  • multiplier=2: cycles 1, 3, 5, 7... (every other)
  • multiplier=3: cycles 1, 4, 7, 10... (every third)

Usage Example:

NRVideoConfiguration config = new NRVideoConfiguration.Builder("application-token")
        .autoDetectPlatform(getApplicationContext())
        .withHarvestCycle(5*60) // 5 minutes for on-demand content
        .enableQoeAggregate(true) // Enable QoE (disabled by default)
        .withQoeAggregateIntervalMultiplier(2) // Send every 2nd harvest cycle
        .build();
NRVideo.newBuilder(getApplicationContext()).withConfiguration(config).build();

Breaking Changes

None. QoE is disabled by default (enableQoeAggregate = false), so existing integrations are not affected.

@skatti97 skatti97 changed the base branch from master to stable-beta March 11, 2026 08:33
@skatti97 skatti97 changed the title fix: Qoe harvest cycle feat: Refactor Quality of Experience (QoE) aggregate metrics along with it's configuration Mar 14, 2026
@skatti97 skatti97 marked this pull request as ready for review March 16, 2026 04:56

private final CrashSafeHarvestFactory factory;
private final CopyOnWriteArrayList<QoeProvider> qoeProviders = new CopyOnWriteArrayList<>();
private int harvestCycleNumber = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should harvestCycleNumber be atomic int ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am manually updating the value so it's fine

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't understand. could you eloborate?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated

@skatti97 skatti97 changed the base branch from stable-beta to release/16MAR2026 March 17, 2026 11:41
@skatti97 skatti97 changed the base branch from release/16MAR2026 to stable-beta March 18, 2026 05:09
@ametku ametku self-requested a review March 18, 2026 05:09
Copy link
Copy Markdown
Contributor

@ametku ametku left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. nice

@skatti97 skatti97 merged commit de64faf into stable-beta Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants