@@ -93,7 +93,7 @@ public NRVideoTracker() {
9393 contentRequestTimestamp = null ;
9494 contentStartTimestamp = null ;
9595 contentErrorTimestamp = null ;
96- startupPeriodAdTime = 0L ;
96+ startupPeriodAdTime = null ;
9797 hasContentStarted = false ;
9898
9999 // Initialize time-weighted bitrate tracking
@@ -271,7 +271,12 @@ public Map<String, Object> getAttributes(String action, Map<String, Object> attr
271271 public void updatePlaytime () {
272272 if (playtimeSinceLastEventTimestamp > 0 ) {
273273 playtimeSinceLastEvent = System .currentTimeMillis () - playtimeSinceLastEventTimestamp ;
274- totalPlaytime += playtimeSinceLastEvent ;
274+ try {
275+ totalPlaytime = Math .addExact (totalPlaytime , playtimeSinceLastEvent );
276+ } catch (ArithmeticException e ) {
277+ NRLog .w ("Total playtime accumulation overflow - resetting to prevent data corruption" );
278+ totalPlaytime = playtimeSinceLastEvent ;
279+ }
275280 playtimeSinceLastEventTimestamp = System .currentTimeMillis ();
276281 } else {
277282 playtimeSinceLastEvent = 0L ;
@@ -487,7 +492,12 @@ public void sendBufferEnd() {
487492 Map <String , Object > attributes = getAttributes (CONTENT_BUFFER_END , null );
488493 Object timeSinceBufferBegin = attributes .get ("timeSinceBufferBegin" );
489494 if (timeSinceBufferBegin instanceof Long && !bufferType .equals ("initial" )) {
490- qoeTotalRebufferingTime += (Long ) timeSinceBufferBegin ;
495+ try {
496+ qoeTotalRebufferingTime = Math .addExact (qoeTotalRebufferingTime , (Long ) timeSinceBufferBegin );
497+ } catch (ArithmeticException e ) {
498+ NRLog .w ("QoE rebuffering time accumulation overflow - resetting to prevent data corruption" );
499+ qoeTotalRebufferingTime = (Long ) timeSinceBufferBegin ;
500+ }
491501 }
492502 sendVideoEvent (CONTENT_BUFFER_END );
493503 }
@@ -625,7 +635,7 @@ private Map<String, Object> calculateQOEKpiAttributes() {
625635 kpiAttributes .put ("averageBitrate" , timeWeightedAverage );
626636 } else if (qoeBitrateCount > 0 ) {
627637 // Fallback to simple average if time-weighted calculation is not available
628- long averageBitrate = qoeBitrateSum / qoeBitrateCount ;
638+ long averageBitrate = Math . round (( double ) qoeBitrateSum / qoeBitrateCount ) ;
629639 kpiAttributes .put ("averageBitrate" , averageBitrate );
630640 }
631641
@@ -644,10 +654,6 @@ private Map<String, Object> calculateQOEKpiAttributes() {
644654 private void updateTimeWeightedBitrate (Long newBitrate ) {
645655 long currentTime = System .currentTimeMillis ();
646656
647- // Safety check: ensure fields are initialized
648- if (qoeTotalBitrateWeightedTime == null ) qoeTotalBitrateWeightedTime = 0L ;
649- if (qoeTotalActiveTime == null ) qoeTotalActiveTime = 0L ;
650-
651657 // If we have a previous bitrate and timing, accumulate its weighted time
652658 if (qoeCurrentBitrate != null && qoeLastRenditionChangeTime != null && qoeCurrentBitrate > 0 ) {
653659 // Ensure valid timestamps
@@ -656,16 +662,23 @@ private void updateTimeWeightedBitrate(Long newBitrate) {
656662 if (segmentDuration > 0 ) {
657663 // Prevent overflow in multiplication
658664 if (qoeCurrentBitrate <= Long .MAX_VALUE / segmentDuration ) {
659- qoeTotalBitrateWeightedTime += qoeCurrentBitrate * segmentDuration ;
660- qoeTotalActiveTime += segmentDuration ;
665+ try {
666+ qoeTotalBitrateWeightedTime = Math .addExact (qoeTotalBitrateWeightedTime , qoeCurrentBitrate * segmentDuration );
667+ qoeTotalActiveTime = Math .addExact (qoeTotalActiveTime , segmentDuration );
668+ } catch (ArithmeticException e ) {
669+ // Overflow in time-weighted bitrate accumulation - reset to prevent future overflows
670+ NRLog .w ("QoE bitrate accumulation overflow - resetting accumulated values to start fresh" );
671+ qoeTotalBitrateWeightedTime = 0L ;
672+ qoeTotalActiveTime = 0L ;
673+ }
661674 }
662675 }
663676 }
664677 }
665678
666679 // Update current tracking values (accept null/zero values for reset scenarios)
667680 qoeCurrentBitrate = newBitrate ;
668- qoeLastRenditionChangeTime = ( currentTime > 0 ) ? currentTime : System . currentTimeMillis () ;
681+ qoeLastRenditionChangeTime = currentTime ;
669682 }
670683
671684 /**
@@ -675,10 +688,6 @@ private void updateTimeWeightedBitrate(Long newBitrate) {
675688 * @return Time-weighted average bitrate, or null if no data available
676689 */
677690 private Long calculateTimeWeightedAverageBitrate () {
678- // Safety check: ensure required fields are properly initialized
679- if (qoeTotalBitrateWeightedTime == null ) qoeTotalBitrateWeightedTime = 0L ;
680- if (qoeTotalActiveTime == null ) qoeTotalActiveTime = 0L ;
681-
682691 // Include current segment in calculation
683692 if (qoeCurrentBitrate != null && qoeLastRenditionChangeTime != null && qoeCurrentBitrate > 0 ) {
684693 long currentTime = System .currentTimeMillis ();
@@ -691,17 +700,24 @@ private Long calculateTimeWeightedAverageBitrate() {
691700 if (currentSegmentDuration > 0 ) {
692701 // Prevent overflow in multiplication
693702 if (qoeCurrentBitrate <= Long .MAX_VALUE / currentSegmentDuration ) {
694- long totalWeightedTime = qoeTotalBitrateWeightedTime + (qoeCurrentBitrate * currentSegmentDuration );
695- long totalTime = qoeTotalActiveTime + currentSegmentDuration ;
696-
697- if (totalTime > 0 ) {
698- return totalWeightedTime / totalTime ;
703+ try {
704+ long totalWeightedTime = Math .addExact (qoeTotalBitrateWeightedTime , qoeCurrentBitrate * currentSegmentDuration );
705+ long totalTime = Math .addExact (qoeTotalActiveTime , currentSegmentDuration );
706+
707+ if (totalTime > 0 ) {
708+ return Math .round ((double ) totalWeightedTime / totalTime );
709+ }
710+ } catch (ArithmeticException e ) {
711+ // Overflow in time-weighted bitrate calculation - reset to prevent future overflows
712+ NRLog .w ("QoE bitrate calculation overflow - resetting accumulated values to start fresh" );
713+ qoeTotalBitrateWeightedTime = 0L ;
714+ qoeTotalActiveTime = 0L ;
699715 }
700716 }
701717 }
702718 // If current segment has zero duration, check if we have accumulated data
703719 else if (qoeTotalActiveTime > 0 ) {
704- return qoeTotalBitrateWeightedTime / qoeTotalActiveTime ;
720+ return Math . round (( double ) qoeTotalBitrateWeightedTime / qoeTotalActiveTime ) ;
705721 }
706722 // If we have current bitrate but no accumulated time and zero segment duration,
707723 // return current bitrate as the average (single point average)
@@ -713,7 +729,7 @@ else if (qoeTotalActiveTime == 0 && currentSegmentDuration == 0) {
713729
714730 // Fallback to accumulated data only
715731 if (qoeTotalActiveTime != null && qoeTotalActiveTime > 0 && qoeTotalBitrateWeightedTime != null ) {
716- return qoeTotalBitrateWeightedTime / qoeTotalActiveTime ;
732+ return Math . round (( double ) qoeTotalBitrateWeightedTime / qoeTotalActiveTime ) ;
717733 }
718734
719735 return null ; // No time-weighted data available
@@ -746,6 +762,33 @@ private void resetQoeMetrics() {
746762 qoeTotalActiveTime = 0L ;
747763 }
748764
765+ /**
766+ * Reset QoE metrics but preserve error timestamps for startup time calculation.
767+ * Called after errors to clean metrics while keeping timing data for analysis.
768+ */
769+ private void resetQoeMetricsPreservingTimestamps () {
770+ qoePeakBitrate = null ;
771+ qoeHadPlaybackFailure = false ;
772+ qoeTotalRebufferingTime = 0L ;
773+ qoeBitrateSum = 0L ;
774+ qoeBitrateCount = 0L ;
775+ qoeLastTrackedBitrate = null ; // Reset cache
776+ qoeStartupTime = null ; // Reset cached startup time for new view session
777+
778+ // DO NOT reset startup time calculation fields - preserve for error analysis:
779+ // contentRequestTimestamp - PRESERVED
780+ // contentStartTimestamp - PRESERVED
781+ // contentErrorTimestamp - PRESERVED
782+ // startupPeriodAdTime - PRESERVED
783+ hasContentStarted = false ; // Reset state flag
784+
785+ // Reset time-weighted bitrate fields
786+ qoeCurrentBitrate = null ;
787+ qoeLastRenditionChangeTime = null ;
788+ qoeTotalBitrateWeightedTime = 0L ;
789+ qoeTotalActiveTime = 0L ;
790+ }
791+
749792 /**
750793 * Send request event.
751794 *
@@ -787,6 +830,10 @@ public void sendError(int errorCode, String errorMessage) {
787830 qoeHadPlaybackFailure = true ;
788831 }
789832 }
833+
834+ // Reset QoE metrics but preserve error timestamps for startup time calculation
835+ resetQoeMetricsPreservingTimestamps ();
836+
790837 sendVideoErrorEvent (actionName , errAttr );
791838 }
792839
@@ -1165,8 +1212,6 @@ public void generateTimeSinceTable() {
11651212 addTimeSinceEntry (CONTENT_RENDITION_CHANGE , "timeSinceLastRenditionChange" , "^CONTENT_RENDITION_CHANGE$" );
11661213 addTimeSinceEntry (AD_RENDITION_CHANGE , "timeSinceLastAdRenditionChange" , "^AD_RENDITION_CHANGE$" );
11671214
1168- addTimeSinceEntry (QOE_AGGREGATE , "timeSinceLastQoeAggregate" , "^QOE_AGGREGATE$" );
1169-
11701215 addTimeSinceEntry (AD_BREAK_START , "timeSinceAdBreakBegin" , "^AD_BREAK_END$" );
11711216
11721217 addTimeSinceEntry (AD_QUARTILE , "timeSinceLastAdQuartile" , "^AD_QUARTILE$" );
@@ -1250,11 +1295,6 @@ public void sendVideoEvent(String action, Map<String, Object> attributes) {
12501295 * @param processedAttributes Fully processed attributes including contentBitrate
12511296 */
12521297 private void trackBitrateFromProcessedAttributes (String action , Map <String , Object > processedAttributes ) {
1253- // Fast filter: only track for events that can have bitrate information
1254- if (!isContentBitrateEvent (action )) {
1255- return ;
1256- }
1257-
12581298 Long currentBitrate = extractBitrateValue (processedAttributes .get ("contentBitrate" ));
12591299 if (currentBitrate == null || currentBitrate <= 0 ) {
12601300 return ;
@@ -1272,13 +1312,6 @@ private void trackBitrateFromProcessedAttributes(String action, Map<String, Obje
12721312 updateQoeBitrateMetrics (currentBitrate , action );
12731313 }
12741314
1275- /**
1276- * Fast check if this action can contain bitrate information.
1277- */
1278- private static boolean isContentBitrateEvent (String action ) {
1279- return CONTENT_HEARTBEAT .equals (action ) || CONTENT_START .equals (action ) ||
1280- CONTENT_RENDITION_CHANGE .equals (action ) || CONTENT_RESUME .equals (action );
1281- }
12821315
12831316 /**
12841317 * Efficiently extract Long bitrate value from various numeric types.
0 commit comments