Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import com.newrelic.videoagent.core.utils.NRLog;
import java.util.concurrent.atomic.AtomicBoolean;

import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -43,6 +44,14 @@ public final class NRVideoConfiguration {
private final boolean debugLoggingEnabled;
private final boolean isTV;

// Runtime configuration fields (mutable, thread-safe) - Using AtomicBoolean for better performance
private final AtomicBoolean qoeAggregateEnabled = new AtomicBoolean(true);
private final AtomicBoolean runtimeConfigInitialized = new AtomicBoolean(false);

// Static holder for current configuration instance (for tracker access)
// WeakReference to prevent memory leaks if static cleanup is missed
private static volatile java.lang.ref.WeakReference<NRVideoConfiguration> currentInstanceRef;

// Performance optimization constants
private static final int DEFAULT_HARVEST_CYCLE_SECONDS = 5 * 60; // 5 minutes
private static final int DEFAULT_LIVE_HARVEST_CYCLE_SECONDS = 30; // 30 seconds
Expand Down Expand Up @@ -78,6 +87,10 @@ private NRVideoConfiguration(Builder builder) {
this.memoryOptimized = builder.memoryOptimized;
this.debugLoggingEnabled = builder.debugLoggingEnabled;
this.isTV = builder.isTV;

// Initialize runtime configuration
this.qoeAggregateEnabled.set(builder.qoeAggregateEnabled);
this.runtimeConfigInitialized.set(true);
}

// Immutable getters
Expand All @@ -92,6 +105,55 @@ private NRVideoConfiguration(Builder builder) {
public boolean isDebugLoggingEnabled() { return debugLoggingEnabled; }
public boolean isTV() { return isTV; }

// Runtime configuration getters and setters
/**
* Check if QOE_AGGREGATE events should be sent during harvest cycles
* @return true if QOE_AGGREGATE should be sent, false otherwise
*/
public boolean isQoeAggregateEnabled() {
if (!runtimeConfigInitialized.get()) {
throw new IllegalStateException("NRVideoConfiguration not initialized! Call build() first.");
}
return qoeAggregateEnabled.get();
}

/**
* Set whether QOE_AGGREGATE events should be sent during harvest cycles
* Lock-free, thread-safe runtime configuration using AtomicBoolean
* @param enabled true to enable QOE_AGGREGATE, false to disable
*/
public void setQoeAggregateEnabled(boolean enabled) {
this.qoeAggregateEnabled.set(enabled);
}

/**
* Initialize configuration with client settings
* @param clientQoeAggregateEnabled QOE aggregate setting from client (null if not provided)
*/
public void initializeFromClient(Boolean clientQoeAggregateEnabled) {
// If client provides a value, use it; otherwise keep current default
if (clientQoeAggregateEnabled != null) {
this.qoeAggregateEnabled.set(clientQoeAggregateEnabled);
}
}

/**
* Get the current configuration instance (for tracker access)
* @return Current NRVideoConfiguration instance
* @throws IllegalStateException if no configuration has been built yet
*/
public static NRVideoConfiguration getCurrentInstance() {
if (currentInstanceRef == null) {
throw new IllegalStateException("No NRVideoConfiguration has been built yet!");
}

NRVideoConfiguration instance = currentInstanceRef.get();
if (instance == null) {
throw new IllegalStateException("NRVideoConfiguration has been garbage collected! Create a new instance.");
}

return instance;
}
/**
* Get dead letter retry interval in milliseconds
* Optimized for different device types and network conditions
Expand Down Expand Up @@ -158,6 +220,7 @@ public static final class Builder {
private boolean memoryOptimized = true;
private boolean debugLoggingEnabled = false;
private boolean isTV = false;
private boolean qoeAggregateEnabled = true; // Default enabled

public Builder(String applicationToken) {
this.applicationToken = applicationToken;
Expand Down Expand Up @@ -235,6 +298,25 @@ public Builder enableLogging() {
return this;
}

/**
* Enable QOE aggregate events (default: enabled)
* @return Builder instance for method chaining
*/
public Builder enableQoeAggregate() {
this.qoeAggregateEnabled = true;
return this;
}

/**
* Configure QOE aggregate events
* @param enabled true to enable QOE_AGGREGATE events, false to disable
* @return Builder instance for method chaining
*/
public Builder enableQoeAggregate(boolean enabled) {
this.qoeAggregateEnabled = enabled;
return this;
}

private void applyTVOptimizations() {
this.harvestCycleSeconds = TV_HARVEST_CYCLE_SECONDS;
this.liveHarvestCycleSeconds = TV_LIVE_HARVEST_CYCLE_SECONDS;
Expand All @@ -251,7 +333,10 @@ private void applyMemoryOptimizations() {
}

public NRVideoConfiguration build() {
return new NRVideoConfiguration(this);
NRVideoConfiguration config = new NRVideoConfiguration(this);
// Set current instance for tracker access using WeakReference
NRVideoConfiguration.currentInstanceRef = new java.lang.ref.WeakReference<>(config);
return config;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.os.Handler;

import com.newrelic.videoagent.core.NRVideo;
import com.newrelic.videoagent.core.NRVideoConfiguration;
import com.newrelic.videoagent.core.model.NRTimeSince;
import com.newrelic.videoagent.core.model.NRTrackerState;
import com.newrelic.videoagent.core.utils.NRLog;
Expand Down Expand Up @@ -587,7 +588,8 @@ private void resetHarvestCycleFlags() {
* Send QOE_AGGREGATE event (internal method - called once per cycle)
*/
private void sendQoeAggregate() {
if (!state.isAd) { // Only send for content, not ads
if (!state.isAd && NRVideoConfiguration.getCurrentInstance().isQoeAggregateEnabled()) {
// Only send for content, not ads, and only if QOE aggregate is enabled
Map<String, Object> kpiAttributes = calculateQOEKpiAttributes();
sendVideoEvent(QOE_AGGREGATE, kpiAttributes);
qoeAggregateAlreadySent = true;
Expand Down
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ if (localPropertiesFile.exists()) {
}
}
def applicationToken = localProperties.getProperty('nr.applicationToken', "")
def qoeAggregateEnabled = localProperties.getProperty('nr.qoeAggregate', "true").toBoolean()

android {
compileSdkVersion 34
buildToolsVersion "34.0.0"
defaultConfig {
buildConfigField "String", "NR_APPLICATION_TOKEN", "\"${applicationToken}\""
buildConfigField "boolean", "QOE_AGGREGATE_DEFAULT", "${qoeAggregateEnabled}"
applicationId "com.newrelic.nrvideoproject"
minSdkVersion 16
targetSdkVersion 34
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/com/newrelic/nrvideoproject/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

Switch adsSwitch;
Switch qoeSwitch;
int counter = 0;
NRVideoConfiguration config;

@Override
protected void onCreate(Bundle savedInstanceState) {
Expand All @@ -24,11 +26,27 @@ protected void onCreate(Bundle savedInstanceState) {
.autoDetectPlatform(getApplicationContext())
.withHarvestCycle(60)
.enableLogging()
.enableQoeAggregate(BuildConfig.QOE_AGGREGATE_DEFAULT)
.build();
NRVideo.newBuilder(getApplicationContext()).withConfiguration(config).build();
setContentView(R.layout.activity_main);

adsSwitch = findViewById(R.id.ads_switch);
qoeSwitch = findViewById(R.id.qoe_switch);

// Initialize QOE switch with current configuration state
qoeSwitch.setChecked(config.isQoeAggregateEnabled());

// Set up QOE switch listener with optimized UI operations
qoeSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
// Perform config update on background thread to avoid UI blocking
// Toggle QOE aggregate functionality at runtime
config.setQoeAggregateEnabled(isChecked);

// Show user feedback on UI thread
String message = "QOE Aggregate " + (isChecked ? "Enabled" : "Disabled");
android.widget.Toast.makeText(MainActivity.this, message, android.widget.Toast.LENGTH_SHORT).show();
});

findViewById(R.id.video0).setOnClickListener(this);
findViewById(R.id.video1).setOnClickListener(this);
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/video2" />

<Switch
android:id="@+id/qoe_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Enable QOE Aggregate"
app:layout_constraintBottom_toTopOf="@+id/ads_switch"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toLeftOf="parent"/>

<Switch
android:id="@+id/ads_switch"
android:layout_width="wrap_content"
Expand Down
9 changes: 4 additions & 5 deletions local.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=/Users/asantaren/Library/Android/sdk
#Thu Feb 05 11:43:29 IST 2026
nr.qoeAggregate=true
sdk.dir=/Users/swatijha/Library/Android/sdk