22
33import android .net .Uri ;
44
5+ import androidx .annotation .NonNull ;
6+ import androidx .media3 .common .C ;
57import androidx .media3 .common .MediaLibraryInfo ;
68import androidx .media3 .common .MediaMetadata ;
79import androidx .media3 .common .PlaybackException ;
1618import com .newrelic .videoagent .core .tracker .NRVideoTracker ;
1719import com .newrelic .videoagent .core .utils .NRLog ;
1820import com .newrelic .videoagent .exoplayer .BuildConfig ;
19- import static com .newrelic .videoagent .core .NRDef .*;
2021
2122import java .io .IOException ;
2223import java .util .HashMap ;
2324import java .util .List ;
2425import java .util .Map ;
26+ import java .util .regex .Matcher ;
27+ import java .util .regex .Pattern ;
2528
2629import static com .newrelic .videoagent .core .NRDef .*;
27- import androidx .media3 .common .C ;
2830
2931/**
3032 * New Relic Video tracker for ExoPlayer.
@@ -73,7 +75,7 @@ public void setPlayer(Object player) {
7375 *
7476 * @param action Action being generated.
7577 * @param attributes Specific attributes sent along the action.
76- * @return
78+ * @return Map of attributes with action-specific data.
7779 */
7880 @ Override
7981 public Map <String , Object > getAttributes (String action , Map <String , Object > attributes ) {
@@ -192,7 +194,7 @@ public Long getRenditionHeight() {
192194 public Long getDuration () {
193195 if (player == null ) return null ;
194196
195- return player .getDuration ();
197+ return Math . max ( player .getDuration () , 0 );
196198 }
197199
198200 /**
@@ -212,19 +214,21 @@ public Long getPlayhead() {
212214 * @return Attribute.
213215 */
214216 public String getSrc () {
215- if (player == null ) return null ;
216-
217+ // Prefer direct MediaItem URI if available
218+ if (player == null || player .getCurrentMediaItem () == null ) return null ;
219+ if (player .getCurrentMediaItem ().localConfiguration != null ) {
220+ return player .getCurrentMediaItem ().localConfiguration .uri .toString ();
221+ }
222+ // Fallback to playlist if available
217223 if (getPlaylist () != null ) {
218- NRLog .d ("Current window index = " + player .getCurrentMediaItemIndex ());
219224 try {
220225 Uri src = getPlaylist ().get (player .getCurrentMediaItemIndex ());
221226 return src .toString ();
222- }catch (Exception e ) {
227+ } catch (Exception e ) {
223228 return null ;
224229 }
225- } else {
226- return null ;
227230 }
231+ return null ;
228232 }
229233
230234 /**
@@ -273,15 +277,31 @@ public Boolean getIsMuted() {
273277 * @return String of the current title
274278 */
275279 public String getTitle () {
276- String contentTitle = "Unknown" ;
277- if (player != null && player .getCurrentMediaItem () != null && player . getCurrentMediaItem (). mediaMetadata . title != null ) {
280+ // Try to get title from MediaItem metadata
281+ if (player != null && player .getCurrentMediaItem () != null ) {
278282 MediaMetadata mm = player .getCurrentMediaItem ().mediaMetadata ;
279- contentTitle = mm .title .toString ();
280- if (mm .subtitle != null ) {
281- contentTitle += ": " + mm .subtitle ; // Usually the episode title is available in subtitle
283+ if (mm != null && mm .title != null ) {
284+ String contentTitle = mm .title .toString ();
285+ if (mm .subtitle != null ) {
286+ contentTitle += ": " + mm .subtitle ;
287+ }
288+ return contentTitle ;
289+ }
290+ // Fallback: use URI if title is not set
291+ if (player .getCurrentMediaItem ().localConfiguration != null ) {
292+ return player .getCurrentMediaItem ().localConfiguration .uri .getLastPathSegment ();
282293 }
283294 }
284- return contentTitle ;
295+ // Fallback: use playlist URI if available
296+ if (getPlaylist () != null && player != null ) {
297+ try {
298+ Uri src = getPlaylist ().get (player .getCurrentMediaItemIndex ());
299+ return src .getLastPathSegment ();
300+ } catch (Exception e ) {
301+ // ignore
302+ }
303+ }
304+ return "Unknown" ;
285305 }
286306
287307 /**
@@ -458,15 +478,15 @@ private void logOnPlayerStateChanged(boolean playWhenReady, int playbackState) {
458478 }
459479
460480 @ Override
461- public void onPlayerError (PlaybackException error ) {
481+ public void onPlayerError (@ NonNull PlaybackException error ) {
462482 NRLog .d ("onPlayerError" );
463483 sendError (error );
464484 }
465485
466486 // ExoPlayer AnalyticsListener
467487
468488 @ Override
469- public void onPositionDiscontinuity (Player .PositionInfo oldPosition , Player .PositionInfo newPosition , int reason ) {
489+ public void onPositionDiscontinuity (@ NonNull Player .PositionInfo oldPosition , @ NonNull Player .PositionInfo newPosition , int reason ) {
470490 if (reason == Player .DISCONTINUITY_REASON_SEEK ) {
471491 NRLog .d ("onSeekStarted analytics" );
472492
@@ -477,7 +497,7 @@ public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.Posi
477497 }
478498
479499 @ Override
480- public void onTracksChanged (EventTime eventTime , Tracks tracksInfo ) {
500+ public void onTracksChanged (@ NonNull EventTime eventTime , @ NonNull Tracks tracksInfo ) {
481501 NRLog .d ("onTracksChanged analytics" );
482502
483503 // Next track in the playlist
@@ -489,13 +509,13 @@ public void onTracksChanged(EventTime eventTime, Tracks tracksInfo) {
489509 }
490510
491511 @ Override
492- public void onLoadError (EventTime eventTime , LoadEventInfo loadEventInfo , MediaLoadData mediaLoadData , IOException error , boolean wasCanceled ) {
512+ public void onLoadError (@ NonNull EventTime eventTime , @ NonNull LoadEventInfo loadEventInfo , @ NonNull MediaLoadData mediaLoadData , @ NonNull IOException error , boolean wasCanceled ) {
493513 NRLog .d ("onLoadError analytics" );
494514 sendError (error );
495515 }
496516
497517 @ Override
498- public void onLoadCompleted (EventTime eventTime , LoadEventInfo loadEventInfo , MediaLoadData mediaLoadData ) {
518+ public void onLoadCompleted (@ NonNull EventTime eventTime , @ NonNull LoadEventInfo loadEventInfo , @ NonNull MediaLoadData mediaLoadData ) {
499519 if (mediaLoadData .dataType == C .DATA_TYPE_MEDIA
500520 && mediaLoadData .trackType == C .TRACK_TYPE_VIDEO
501521 && loadEventInfo .loadDurationMs > 0 ) {
@@ -505,14 +525,14 @@ public void onLoadCompleted(EventTime eventTime, LoadEventInfo loadEventInfo, Me
505525 }
506526
507527 @ Override
508- public void onBandwidthEstimate (AnalyticsListener .EventTime eventTime , int totalLoadTimeMs , long totalBytesLoaded , long bitrateEstimate ) {
528+ public void onBandwidthEstimate (@ NonNull AnalyticsListener .EventTime eventTime , int totalLoadTimeMs , long totalBytesLoaded , long bitrateEstimate ) {
509529 NRLog .d ("onBandwidthEstimate analytics" );
510530
511531 this .bitrateEstimate = bitrateEstimate ;
512532 }
513533
514534 @ Override
515- public void onDroppedVideoFrames (AnalyticsListener .EventTime eventTime , int droppedFrames , long elapsedMs ) {
535+ public void onDroppedVideoFrames (@ NonNull AnalyticsListener .EventTime eventTime , int droppedFrames , long elapsedMs ) {
516536 NRLog .d ("onDroppedVideoFrames analytics" );
517537 if (!player .isPlayingAd ()) {
518538 sendDroppedFrame (droppedFrames , (int ) elapsedMs );
@@ -527,8 +547,8 @@ public void onVideoSizeChanged(VideoSize videoSize) {
527547
528548 if (player .isPlayingAd ()) return ;
529549
530- long currMul = width * height ;
531- long lastMul = lastWidth * lastHeight ;
550+ long currMul = ( long ) width * height ;
551+ long lastMul = ( long ) lastWidth * lastHeight ;
532552
533553 if (lastMul != 0 ) {
534554 if (lastMul < currMul ) {
@@ -543,4 +563,54 @@ public void onVideoSizeChanged(VideoSize videoSize) {
543563 lastHeight = height ;
544564 lastWidth = width ;
545565 }
546- }
566+
567+ @ Override
568+ public String getLanguage () {
569+ if (player != null && player .getCurrentMediaItem () != null ) {
570+ // 1. Try to get language from URI query param
571+ if (player .getCurrentMediaItem ().localConfiguration != null ) {
572+ Uri uri = player .getCurrentMediaItem ().localConfiguration .uri ;
573+ String lang = uri .getQueryParameter ("lang" );
574+ if (lang != null ) return lang ;
575+ // 2. Try to get language from any path segment
576+ for (String segment : uri .getPathSegments ()) {
577+ if (segment .matches ("[a-zA-Z]{2,5}" )) return segment ;
578+ }
579+ // 3. Try to get language from last path segment
580+ String lastSegment = uri .getLastPathSegment ();
581+ if (lastSegment != null && lastSegment .matches ("[a-zA-Z]{2,5}" )) return lastSegment ;
582+ }
583+ // 4. Try to get language from MediaMetadata title or description
584+ MediaMetadata mm = player .getCurrentMediaItem ().mediaMetadata ;
585+ if (mm != null ) {
586+ String [] fields = { mm .title != null ? mm .title .toString () : null , mm .description != null ? mm .description .toString () : null };
587+ for (String field : fields ) {
588+ if (field == null ) continue ;
589+ // Look for patterns like (en), [en], en:
590+ Matcher m = Pattern .compile ("(?:\\ (|\\ [)?([a-zA-Z]{2,5})(?:\\ )|\\ ])?|([a-zA-Z]{2,5}):" ).matcher (field );
591+ if (m .find ()) {
592+ if (m .group (1 ) != null ) return m .group (1 );
593+ if (m .group (2 ) != null ) return m .group (2 );
594+ }
595+ }
596+ }
597+ }
598+ // 5. Fallback: try playlist URI
599+ if (getPlaylist () != null && player != null ) {
600+ try {
601+ Uri src = getPlaylist ().get (player .getCurrentMediaItemIndex ());
602+ String lang = src .getQueryParameter ("lang" );
603+ if (lang != null ) return lang ;
604+ for (String segment : src .getPathSegments ()) {
605+ if (segment .matches ("[a-zA-Z]{2,5}" )) return segment ;
606+ }
607+ String lastSegment = src .getLastPathSegment ();
608+ if (lastSegment != null && lastSegment .matches ("[a-zA-Z]{2,5}" )) return lastSegment ;
609+ } catch (Exception e ) {
610+ // ignore
611+ }
612+ }
613+ return null ;
614+ }
615+
616+ }
0 commit comments