Skip to content

Commit 02468fd

Browse files
authored
feat: add toggle feature for qoe
add toggle feature for qoe
2 parents 5cb62a6 + c343c4a commit 02468fd

File tree

10 files changed

+273
-69
lines changed

10 files changed

+273
-69
lines changed

NRExoPlayerTracker/src/main/java/com/newrelic/videoagent/exoplayer/tracker/NRTrackerExoPlayer.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import androidx.media3.exoplayer.source.LoadEventInfo;
2020
import androidx.media3.exoplayer.source.MediaLoadData;
2121

22+
import com.newrelic.videoagent.core.NRVideoConfiguration;
2223
import com.newrelic.videoagent.core.tracker.NRVideoTracker;
2324
import com.newrelic.videoagent.core.utils.NRLog;
2425
import com.newrelic.videoagent.exoplayer.BuildConfig;
@@ -73,15 +74,39 @@ public class NRTrackerExoPlayer extends NRVideoTracker implements Player.Listene
7374
/**
7475
* Init a new ExoPlayer tracker.
7576
*/
77+
public NRTrackerExoPlayer(NRVideoConfiguration configuration) {
78+
super(configuration);
79+
}
80+
81+
/**
82+
* Create a new NRTrackerExoPlayer (deprecated - use constructor with configuration).
83+
* @deprecated Use NRTrackerExoPlayer(NRVideoConfiguration) constructor instead
84+
*/
85+
@Deprecated
7686
public NRTrackerExoPlayer() {
87+
super();
7788
}
7889

7990
/**
8091
* Init a new ExoPlayer tracker.
8192
*
93+
* @param configuration Video configuration
94+
* @param player ExoPlayer instance.
95+
*/
96+
public NRTrackerExoPlayer(NRVideoConfiguration configuration, ExoPlayer player) {
97+
super(configuration);
98+
setPlayer(player);
99+
}
100+
101+
/**
102+
* Init a new ExoPlayer tracker (deprecated).
103+
*
82104
* @param player ExoPlayer instance.
105+
* @deprecated Use NRTrackerExoPlayer(NRVideoConfiguration, ExoPlayer) constructor instead
83106
*/
107+
@Deprecated
84108
public NRTrackerExoPlayer(ExoPlayer player) {
109+
super();
85110
setPlayer(player);
86111
}
87112

NRIMATracker/src/main/java/com/newrelic/videoagent/ima/tracker/NRTrackerIMA.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.ads.interactivemedia.v3.api.Ad;
44
import com.google.ads.interactivemedia.v3.api.AdErrorEvent;
55
import com.google.ads.interactivemedia.v3.api.AdEvent;
6+
import com.newrelic.videoagent.core.NRVideoConfiguration;
67
import com.newrelic.videoagent.core.tracker.NRVideoTracker;
78
import com.newrelic.videoagent.core.utils.NRLog;
89
import com.newrelic.videoagent.ima.BuildConfig;
@@ -13,6 +14,22 @@
1314

1415
public class NRTrackerIMA extends NRVideoTracker implements AdErrorEvent.AdErrorListener, AdEvent.AdEventListener {
1516

17+
/**
18+
* Create a new IMA tracker with configuration.
19+
*/
20+
public NRTrackerIMA(NRVideoConfiguration configuration) {
21+
super(configuration);
22+
}
23+
24+
/**
25+
* Create a new IMA tracker (deprecated - use constructor with configuration).
26+
* @deprecated Use NRTrackerIMA(NRVideoConfiguration) constructor instead
27+
*/
28+
@Deprecated
29+
public NRTrackerIMA() {
30+
super();
31+
}
32+
1633
private String adPosition = null;
1734
private String creativeId = null;
1835
private Long quartile = null;

NewRelicVideoCore/src/main/java/com/newrelic/videoagent/core/NRVideo.java

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public final class NRVideo {
2222
private static final Object lock = new Object();
2323

2424
private volatile HarvestManager harvestManager;
25+
private volatile NRVideoConfiguration configuration;
2526
private final Map<String, Integer> trackerIds = new HashMap<>();
2627

2728
// Private constructor for singleton
@@ -60,10 +61,10 @@ public static Integer addPlayer(NRVideoPlayerConfiguration config) {
6061
}
6162

6263
// Create content tracker with ExoPlayer instance
63-
NRTracker contentTracker = createContentTracker();
64+
NRTracker contentTracker = createContentTracker(instance.configuration);
6465
NRTracker adsTracker = null;
6566
if (config.isAdEnabled()) {
66-
adsTracker = createAdTracker();
67+
adsTracker = createAdTracker(instance.configuration);
6768
NRLog.d("add tracker is added");
6869
}
6970

@@ -228,6 +229,9 @@ private NRVideo initialize(Context context, NRVideoConfiguration config) {
228229
try {
229230
Context applicationContext = context.getApplicationContext();
230231

232+
// Store configuration for tracker creation
233+
this.configuration = config;
234+
231235
// Always use crash-safe storage - it's now the default behavior
232236
harvestManager = new HarvestManager(config, applicationContext);
233237

@@ -256,25 +260,35 @@ private NRVideo initialize(Context context, NRVideoConfiguration config) {
256260
}
257261
}
258262

259-
private static NRTracker createContentTracker() {
263+
private static NRTracker createContentTracker(NRVideoConfiguration config) {
260264
try {
261-
// Create ExoPlayer tracker with player instance
265+
// Create ExoPlayer tracker with configuration
262266
Class<?> exoTrackerClass = Class.forName("com.newrelic.videoagent.exoplayer.tracker.NRTrackerExoPlayer");
263-
return (NRTracker) exoTrackerClass.newInstance();
267+
return (NRTracker) exoTrackerClass.getConstructor(NRVideoConfiguration.class).newInstance(config);
264268
} catch (Exception e) {
265-
// Fallback to basic video tracker
266-
throw new RuntimeException("Failed to create NRTrackerExoPlayer", e);
269+
// Fallback to deprecated constructor for backward compatibility
270+
try {
271+
Class<?> exoTrackerClass = Class.forName("com.newrelic.videoagent.exoplayer.tracker.NRTrackerExoPlayer");
272+
return (NRTracker) exoTrackerClass.newInstance();
273+
} catch (Exception fallbackException) {
274+
throw new RuntimeException("Failed to create NRTrackerExoPlayer", fallbackException);
275+
}
267276
}
268277
}
269278

270-
private static NRTracker createAdTracker() {
271-
279+
private static NRTracker createAdTracker(NRVideoConfiguration config) {
272280
try {
273-
// Always use IMA tracker for ads
281+
// Always use IMA tracker for ads with configuration
274282
Class<?> imaTrackerClass = Class.forName("com.newrelic.videoagent.ima.tracker.NRTrackerIMA");
275-
return (NRTracker) imaTrackerClass.newInstance();
283+
return (NRTracker) imaTrackerClass.getConstructor(NRVideoConfiguration.class).newInstance(config);
276284
} catch (Exception e) {
277-
return null;
285+
// Fallback to deprecated constructor for backward compatibility
286+
try {
287+
Class<?> imaTrackerClass = Class.forName("com.newrelic.videoagent.ima.tracker.NRTrackerIMA");
288+
return (NRTracker) imaTrackerClass.newInstance();
289+
} catch (Exception fallbackException) {
290+
return null;
291+
}
278292
}
279293
}
280294

NewRelicVideoCore/src/main/java/com/newrelic/videoagent/core/NRVideoConfiguration.java

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.content.Context;
55
import android.content.pm.PackageManager;
66
import com.newrelic.videoagent.core.utils.NRLog;
7+
import java.util.concurrent.atomic.AtomicBoolean;
78

89
import java.util.HashMap;
910
import java.util.Map;
@@ -44,6 +45,11 @@ public final class NRVideoConfiguration {
4445
private final boolean isTV;
4546
private final String collectorAddress;
4647

48+
// Runtime configuration fields (mutable, thread-safe) - Using AtomicBoolean for better performance
49+
private final AtomicBoolean qoeAggregateEnabled = new AtomicBoolean(true);
50+
private final AtomicBoolean runtimeConfigInitialized = new AtomicBoolean(false);
51+
52+
4753
// Performance optimization constants
4854
private static final int DEFAULT_HARVEST_CYCLE_SECONDS = 5 * 60; // 5 minutes
4955
private static final int DEFAULT_LIVE_HARVEST_CYCLE_SECONDS = 30; // 30 seconds
@@ -80,6 +86,10 @@ private NRVideoConfiguration(Builder builder) {
8086
this.debugLoggingEnabled = builder.debugLoggingEnabled;
8187
this.isTV = builder.isTV;
8288
this.collectorAddress = builder.collectorAddress;
89+
90+
// Initialize runtime configuration
91+
this.qoeAggregateEnabled.set(builder.qoeAggregateEnabled);
92+
this.runtimeConfigInitialized.set(true);
8393
}
8494

8595
// Immutable getters
@@ -95,6 +105,38 @@ private NRVideoConfiguration(Builder builder) {
95105
public boolean isTV() { return isTV; }
96106
public String getCollectorAddress() { return collectorAddress; }
97107

108+
// Runtime configuration getters and setters
109+
/**
110+
* Check if QOE_AGGREGATE events should be sent during harvest cycles
111+
* @return true if QOE_AGGREGATE should be sent, false otherwise
112+
*/
113+
public boolean isQoeAggregateEnabled() {
114+
if (!runtimeConfigInitialized.get()) {
115+
throw new IllegalStateException("NRVideoConfiguration not initialized! Call build() first.");
116+
}
117+
return qoeAggregateEnabled.get();
118+
}
119+
120+
/**
121+
* Set whether QOE_AGGREGATE events should be sent during harvest cycles
122+
* Lock-free, thread-safe runtime configuration using AtomicBoolean
123+
* @param enabled true to enable QOE_AGGREGATE, false to disable
124+
*/
125+
public void setQoeAggregateEnabled(boolean enabled) {
126+
this.qoeAggregateEnabled.set(enabled);
127+
}
128+
129+
/**
130+
* Initialize configuration with client settings
131+
* @param clientQoeAggregateEnabled QOE aggregate setting from client (null if not provided)
132+
*/
133+
public void initializeFromClient(Boolean clientQoeAggregateEnabled) {
134+
// If client provides a value, use it; otherwise keep current default
135+
if (clientQoeAggregateEnabled != null) {
136+
this.qoeAggregateEnabled.set(clientQoeAggregateEnabled);
137+
}
138+
}
139+
98140
/**
99141
* Get dead letter retry interval in milliseconds
100142
* Optimized for different device types and network conditions
@@ -110,56 +152,41 @@ public long getDeadLetterRetryInterval() {
110152
}
111153

112154
/**
113-
* Parse region code from application token prefix
114-
* Matches NewRelic iOS Agent pattern: extracts region prefix before 'x'
115-
* Examples: "EUxABCD..." -> "EU", "APxABCD..." -> "AP", "AA..." -> ""
116-
*/
117-
private static String parseRegionFromToken(String applicationToken) {
118-
if (applicationToken == null || applicationToken.length() < 3) {
119-
return "";
120-
}
121-
122-
// Find the first 'x' in the token
123-
int xIndex = applicationToken.indexOf('x');
124-
if (xIndex == -1) {
125-
return ""; // No region prefix found
126-
}
127-
128-
// Extract everything before the first 'x'
129-
String regionCode = applicationToken.substring(0, xIndex);
130-
131-
// Remove any trailing 'x' characters
132-
while (regionCode.length() > 0 && regionCode.charAt(regionCode.length() - 1) == 'x') {
133-
regionCode = regionCode.substring(0, regionCode.length() - 1);
134-
}
135-
136-
return regionCode;
137-
}
138-
139-
/**
140-
* Identify region with proper token parsing and fallback logic
141-
* Behavior similar to NewRelic iOS Agent's NRMAAgentConfiguration
155+
* Enterprise-grade region identification with multiple fallback strategies
156+
* Thread-safe and optimized for performance
142157
*/
143158
private static String identifyRegion(String applicationToken) {
144159
if (applicationToken == null || applicationToken.length() < 10) {
145160
return "US"; // Safe default
146161
}
147162

148-
// First, try to parse region from token prefix (e.g., "EUx", "APx")
149-
String regionCode = parseRegionFromToken(applicationToken);
163+
String cleanToken = applicationToken.trim().toLowerCase();
150164

151-
if (regionCode != null && regionCode.length() > 0) {
152-
// Convert region code to uppercase and validate
153-
String upperRegion = regionCode.toUpperCase();
165+
// Strategy 1: Direct prefix matching (most reliable)
166+
for (Map.Entry<String, String> entry : REGION_MAPPINGS.entrySet()) {
167+
String regionKey = entry.getKey().toLowerCase();
168+
if (cleanToken.startsWith(regionKey) || cleanToken.contains("-" + regionKey + "-")) {
169+
return entry.getValue();
170+
}
171+
}
154172

155-
// Map region codes to standard regions
156-
String mappedRegion = REGION_MAPPINGS.get(upperRegion);
157-
if (mappedRegion != null) {
158-
return mappedRegion;
173+
// Strategy 2: Token structure analysis
174+
if (cleanToken.length() >= 40) { // Standard NR token length
175+
// EU tokens often have specific patterns
176+
if (cleanToken.contains("eu") || cleanToken.contains("europe")) {
177+
return "EU";
178+
}
179+
// AP tokens often have specific patterns
180+
if (cleanToken.contains("ap") || cleanToken.contains("asia") || cleanToken.contains("pacific")) {
181+
return "AP";
182+
}
183+
// Gov tokens have specific patterns
184+
if (cleanToken.contains("gov") || cleanToken.contains("fed")) {
185+
return "GOV";
159186
}
160187
}
161188

162-
// Default to US for standard tokens without region prefix
189+
// Strategy 3: Default to US for production stability
163190
return "US";
164191
}
165192

@@ -177,6 +204,7 @@ public static final class Builder {
177204
private boolean debugLoggingEnabled = false;
178205
private boolean isTV = false;
179206
private String collectorAddress = null;
207+
private boolean qoeAggregateEnabled = true; // Default enabled
180208

181209
public Builder(String applicationToken) {
182210
this.applicationToken = applicationToken;
@@ -264,6 +292,25 @@ public Builder withCollectorAddress(String collectorAddress) {
264292
return this;
265293
}
266294

295+
/**
296+
* Enable QOE aggregate events (default: enabled)
297+
* @return Builder instance for method chaining
298+
*/
299+
public Builder enableQoeAggregate() {
300+
this.qoeAggregateEnabled = true;
301+
return this;
302+
}
303+
304+
/**
305+
* Configure QOE aggregate events
306+
* @param enabled true to enable QOE_AGGREGATE events, false to disable
307+
* @return Builder instance for method chaining
308+
*/
309+
public Builder enableQoeAggregate(boolean enabled) {
310+
this.qoeAggregateEnabled = enabled;
311+
return this;
312+
}
313+
267314
private void applyTVOptimizations() {
268315
this.harvestCycleSeconds = TV_HARVEST_CYCLE_SECONDS;
269316
this.liveHarvestCycleSeconds = TV_LIVE_HARVEST_CYCLE_SECONDS;
@@ -280,7 +327,10 @@ private void applyMemoryOptimizations() {
280327
}
281328

282329
public NRVideoConfiguration build() {
283-
return new NRVideoConfiguration(this);
330+
NRVideoConfiguration config = new NRVideoConfiguration(this);
331+
// Mark runtime configuration as initialized
332+
config.runtimeConfigInitialized.set(true);
333+
return config;
284334
}
285335
}
286336

NewRelicVideoCore/src/main/java/com/newrelic/videoagent/core/tracker/NRTracker.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.newrelic.videoagent.core.tracker;
22

33
import com.newrelic.videoagent.core.NRVideo;
4+
import com.newrelic.videoagent.core.NRVideoConfiguration;
45
import com.newrelic.videoagent.core.NewRelicVideoAgent;
56
import com.newrelic.videoagent.core.model.NREventAttributes;
67
import com.newrelic.videoagent.core.model.NRTimeSince;
@@ -21,17 +22,37 @@ public class NRTracker {
2122
*/
2223
public NRTracker linkedTracker;
2324

25+
protected final NRVideoConfiguration configuration;
2426
private final NREventAttributes eventAttributes;
2527
private NRTimeSinceTable timeSinceTable;
2628

2729
/**
28-
* Create a new NRTracker.
30+
* Create a new NRTracker with configuration.
2931
*/
32+
public NRTracker(NRVideoConfiguration configuration) {
33+
this.configuration = configuration;
34+
eventAttributes = new NREventAttributes();
35+
generateTimeSinceTable();
36+
}
37+
38+
/**
39+
* Create a new NRTracker (deprecated - use constructor with configuration).
40+
* @deprecated Use NRTracker(NRVideoConfiguration) constructor instead
41+
*/
42+
@Deprecated
3043
public NRTracker() {
44+
this.configuration = null;
3145
eventAttributes = new NREventAttributes();
3246
generateTimeSinceTable();
3347
}
3448

49+
/**
50+
* Get the tracker configuration.
51+
*/
52+
public NRVideoConfiguration getConfiguration() {
53+
return configuration;
54+
}
55+
3556
/**
3657
* Tracker is ready.
3758
*/

0 commit comments

Comments
 (0)