1+ package com .newrelic .videoagent .exoplayer .tracker ;
2+
3+ import java .util .Map ;
4+ import java .util .concurrent .ConcurrentHashMap ;
5+ public class MockNRTrackerExoPlayer extends NRTrackerExoPlayer {
6+
7+ private boolean eventSent = false ;
8+ private String lastEventType = null ;
9+ private Map <String , Object > lastEventAttributes = null ;
10+
11+ private long currentTimeMs = System .currentTimeMillis ();
12+ private final ConcurrentHashMap <String , Object > mockLastTrackData = new ConcurrentHashMap <>();
13+ private final Map <String , Object > mockAggregationStatus = new ConcurrentHashMap <>();
14+ private boolean mockAggregationEnabled = true ;
15+
16+ public MockNRTrackerExoPlayer () {
17+ super ();
18+ mockLastTrackData .put ("lastFrameDropCount" , 0 );
19+ mockLastTrackData .put ("lastFrameDropDuration" , 0 );
20+ mockLastTrackData .put ("lastFrameDropTime" , 0L );
21+ mockLastTrackData .put ("lastUpdateTime" , System .currentTimeMillis ());
22+ mockLastTrackData .put ("currentTotalFrames" , 0 );
23+ mockLastTrackData .put ("currentTotalDuration" , 0 );
24+ mockLastTrackData .put ("currentEventCount" , 0 );
25+
26+ mockAggregationStatus .put ("hasActiveAggregation" , false );
27+ mockAggregationStatus .put ("totalLostFrames" , 0 );
28+ mockAggregationStatus .put ("totalLostFramesDuration" , 0 );
29+ mockAggregationStatus .put ("eventCount" , 0 );
30+ mockAggregationStatus .put ("firstDropTimestamp" , 0L );
31+ mockAggregationStatus .put ("lastDropTimestamp" , 0L );
32+ }
33+
34+ public void simulateTimeExpiry () {
35+ this .currentTimeMs += 6000 ; // Beyond DEFAULT_AGGREGATION_WINDOW_MS (5000)
36+ simulateFlush ();
37+ }
38+
39+ @ Override
40+ public void sendVideoEvent (String eventType , Map <String , Object > attributes ) {
41+ this .eventSent = true ;
42+ this .lastEventType = eventType ;
43+ this .lastEventAttributes = attributes != null ? new ConcurrentHashMap <>(attributes ) : null ;
44+ }
45+
46+ @ Override
47+ public void sendVideoAdEvent (String eventType , Map <String , Object > attributes ) {
48+ this .eventSent = true ;
49+ this .lastEventType = eventType ;
50+ this .lastEventAttributes = attributes != null ? new ConcurrentHashMap <>(attributes ) : null ;
51+ }
52+
53+ public boolean wasEventSent () {
54+ return eventSent ;
55+ }
56+
57+ public String getLastEventType () {
58+ return lastEventType ;
59+ }
60+
61+ public Map <String , Object > getLastEventAttributes () {
62+ return lastEventAttributes ;
63+ }
64+
65+ public void clearEvents () {
66+ eventSent = false ;
67+ lastEventType = null ;
68+ lastEventAttributes = null ;
69+ }
70+
71+ @ Override
72+ protected void scheduleDelayedFlush () {
73+ // Bypass Android Handler for unit tests - use explicit flush calls instead
74+ }
75+
76+ @ Override
77+ public void sendDroppedFrame (int count , int elapsed ) {
78+ long currentTime = System .currentTimeMillis ();
79+
80+ mockLastTrackData .put ("lastFrameDropCount" , count );
81+ mockLastTrackData .put ("lastFrameDropDuration" , elapsed );
82+ mockLastTrackData .put ("lastFrameDropTime" , currentTime );
83+ mockLastTrackData .put ("lastUpdateTime" , currentTime );
84+
85+ if (mockAggregationEnabled ) {
86+ boolean hasActive = (boolean ) mockAggregationStatus .get ("hasActiveAggregation" );
87+ int currentTotal = (int ) mockAggregationStatus .get ("totalLostFrames" );
88+ int currentDuration = (int ) mockAggregationStatus .get ("totalLostFramesDuration" );
89+ int eventCount = (int ) mockAggregationStatus .get ("eventCount" );
90+
91+ if (!hasActive ) {
92+ // Start new aggregation
93+ mockAggregationStatus .put ("hasActiveAggregation" , true );
94+ mockAggregationStatus .put ("totalLostFrames" , count );
95+ mockAggregationStatus .put ("totalLostFramesDuration" , elapsed );
96+ mockAggregationStatus .put ("eventCount" , 1 );
97+ mockAggregationStatus .put ("firstDropTimestamp" , currentTime );
98+ mockAggregationStatus .put ("lastDropTimestamp" , currentTime );
99+ } else {
100+ // Add to existing aggregation
101+ int newEventCount = eventCount + 1 ;
102+ mockAggregationStatus .put ("totalLostFrames" , currentTotal + count );
103+ mockAggregationStatus .put ("totalLostFramesDuration" , currentDuration + elapsed );
104+ mockAggregationStatus .put ("eventCount" , newEventCount );
105+ mockAggregationStatus .put ("lastDropTimestamp" , currentTime );
106+
107+ if (newEventCount >= 50 ) {
108+ simulateFlush ();
109+ }
110+ }
111+ mockLastTrackData .put ("currentTotalFrames" , mockAggregationStatus .get ("totalLostFrames" ));
112+ mockLastTrackData .put ("currentTotalDuration" , mockAggregationStatus .get ("totalLostFramesDuration" ));
113+ mockLastTrackData .put ("currentEventCount" , mockAggregationStatus .get ("eventCount" ));
114+ } else {
115+ Map <String , Object > attributes = new ConcurrentHashMap <>();
116+ attributes .put ("lostFrames" , count );
117+ attributes .put ("lostFramesDuration" , elapsed );
118+ sendVideoEvent ("CONTENT_DROPPED_FRAMES" , attributes );
119+ }
120+ }
121+
122+ public ConcurrentHashMap <String , Object > getLastTrackData () {
123+ return mockLastTrackData ;
124+ }
125+ public Map <String , Object > getCurrentAggregationStatus () {
126+ return mockAggregationStatus ;
127+ }
128+ @ Override
129+ public void setDroppedFrameAggregationEnabled (boolean enabled ) {
130+ if (!enabled && mockAggregationEnabled ) {
131+ // Flush current aggregation when disabling
132+ simulateFlush ();
133+ }
134+ this .mockAggregationEnabled = enabled ;
135+ }
136+ @ Override
137+ public boolean isDroppedFrameAggregationEnabled () {
138+ return mockAggregationEnabled ;
139+ }
140+ public void resetAggregationState () {
141+ // Reset aggregation state but preserve last track data
142+ mockAggregationStatus .put ("hasActiveAggregation" , false );
143+ mockAggregationStatus .put ("totalLostFrames" , 0 );
144+ mockAggregationStatus .put ("totalLostFramesDuration" , 0 );
145+ mockAggregationStatus .put ("eventCount" , 0 );
146+ mockAggregationStatus .put ("firstDropTimestamp" , 0L );
147+ mockAggregationStatus .put ("lastDropTimestamp" , 0L );
148+
149+ // Reset event capture state - only clear if no event was just sent
150+ // This is needed for external resets, not after flushing
151+ eventSent = false ;
152+ lastEventType = null ;
153+ lastEventAttributes = null ;
154+ }
155+ public void simulateFlush () {
156+ boolean hasActive = (boolean ) mockAggregationStatus .get ("hasActiveAggregation" );
157+ if (hasActive ) {
158+ Map <String , Object > attributes = new ConcurrentHashMap <>();
159+ attributes .put ("lostFrames" , mockAggregationStatus .get ("totalLostFrames" ));
160+ attributes .put ("lostFramesDuration" , mockAggregationStatus .get ("totalLostFramesDuration" ));
161+ attributes .put ("eventCount" , mockAggregationStatus .get ("eventCount" ));
162+ attributes .put ("aggregationWindowMs" , 5000L ); // DEFAULT_AGGREGATION_WINDOW_MS
163+ attributes .put ("firstDropTimestamp" , mockAggregationStatus .get ("firstDropTimestamp" ));
164+ attributes .put ("lastDropTimestamp" , mockAggregationStatus .get ("lastDropTimestamp" ));
165+
166+ long firstTime = (long ) mockAggregationStatus .get ("firstDropTimestamp" );
167+ long lastTime = (long ) mockAggregationStatus .get ("lastDropTimestamp" );
168+ attributes .put ("actualAggregationDurationMs" , lastTime - firstTime );
169+
170+ sendVideoEvent ("CONTENT_DROPPED_FRAMES" , attributes );
171+ resetAggregationAfterFlush ();
172+ }
173+ }
174+ private void resetAggregationAfterFlush () {
175+ mockAggregationStatus .put ("hasActiveAggregation" , false );
176+ mockAggregationStatus .put ("totalLostFrames" , 0 );
177+ mockAggregationStatus .put ("totalLostFramesDuration" , 0 );
178+ mockAggregationStatus .put ("eventCount" , 0 );
179+ mockAggregationStatus .put ("firstDropTimestamp" , 0L );
180+ mockAggregationStatus .put ("lastDropTimestamp" , 0L );
181+ // DON'T reset eventSent flag here - we want to preserve it after flushing
182+ }
183+ }
0 commit comments