diff --git a/src/ts/ConvivaAnalytics.ts b/src/ts/ConvivaAnalytics.ts index ac886f5..2ed263b 100644 --- a/src/ts/ConvivaAnalytics.ts +++ b/src/ts/ConvivaAnalytics.ts @@ -240,17 +240,7 @@ export class ConvivaAnalytics { private onPlaybackStateChanged = (event: PlayerEventBase) => { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] playback state change related event', event); - this.convivaAnalyticsTracker.trackPlaybackStateChanged(event); - }; - - private onPlay = (event: PlaybackEvent) => { - this.debugLog('[ ConvivaAnalytics ] [ Player Event ] play', event); - - if (!this.convivaAnalyticsTracker.canTrackPlayEvent) { - return; - } - - this.onPlaybackStateChanged(event); + this.convivaAnalyticsTracker.trackPlaybackStateFromEvent(event); }; private onPlaying = (event: PlaybackEvent) => { @@ -260,7 +250,7 @@ export class ConvivaAnalytics { private onPlaybackFinished = (event: PlayerEventBase) => { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] playback finished', event); - this.onPlaybackStateChanged(event); + this.convivaAnalyticsTracker.trackPlaybackFinished(); }; private onVideoQualityChanged = (event: VideoQualityChangedEvent) => { @@ -278,7 +268,7 @@ export class ConvivaAnalytics { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] adbreak started', event); this.lastAdBreakEvent = event; this.convivaAnalyticsTracker.trackAdBreakStarted(Conviva.Constants.AdType.CLIENT_SIDE); - this.convivaAnalyticsTracker.trackPlaybackStateChanged(event); + this.convivaAnalyticsTracker.trackPlaybackStateFromEvent(event); }; private onAdStarted = (event: AdEvent) => { @@ -288,13 +278,13 @@ export class ConvivaAnalytics { const bitrateKbps = event.ad.data?.bitrate; this.convivaAnalyticsTracker.trackAdStarted(adInfo, Conviva.Constants.AdType.CLIENT_SIDE, bitrateKbps); - this.convivaAnalyticsTracker.trackPlaybackStateChanged(event); + // No need to call reportPlaybackStateFromEvent as this is covered by `trackAdStarted` } private onAdFinished = (event: AdEvent) => { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] ad finished', event); this.convivaAnalyticsTracker.trackAdFinished(); - this.convivaAnalyticsTracker.trackPlaybackStateChanged(event); + this.convivaAnalyticsTracker.trackPlaybackStateFromEvent(event); } private onAdSkipped = (event: AdEvent) => { @@ -306,11 +296,13 @@ export class ConvivaAnalytics { private onRestoringContent = (event: PlayerEventBase) => { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] restoring content', event); this.convivaAnalyticsTracker.trackRestoringContent(); + this.convivaAnalyticsTracker.trackPlaybackStateFromEvent(event); }; private onAdBreakFinished = (event: AdBreakEvent) => { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] adbreak finished', event); this.convivaAnalyticsTracker.trackAdBreakFinished(); + // No need to call reportPlaybackStateFromEvent as this is covered by `trackAdBreakFinished` } private onAdError = (event: ErrorEvent) => { @@ -322,7 +314,6 @@ export class ConvivaAnalytics { private onSeek = (event: SeekEvent) => { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] seek', event); this.convivaAnalyticsTracker.trackSeekStart(event.seekTarget); - this.onPlaybackStateChanged(event); }; private onSeeked = (event: SeekEvent) => { @@ -335,7 +326,6 @@ export class ConvivaAnalytics { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] time shift', event); // According to conviva it is valid to pass -1 for seeking in live streams this.convivaAnalyticsTracker.trackSeekStart(-1); - this.onPlaybackStateChanged(event); }; private onTimeShifted = (event: TimeShiftEvent) => { @@ -374,9 +364,25 @@ export class ConvivaAnalytics { this.mainContentDuration = this.player.getDuration(); }; + private static readonly stallTrackingStartEvents = [ + PlayerEvent.Play, + PlayerEvent.Seek, + PlayerEvent.TimeShift, + ]; + + private static readonly stallTrackingClearEvents = [ + PlayerEvent.StallStarted, // StallStarted is reported as BUFFERING immediately. Does not need the delayed timeout approach. + PlayerEvent.Playing, + PlayerEvent.Paused, + PlayerEvent.Seeked, + PlayerEvent.TimeShifted, + PlayerEvent.StallEnded, + PlayerEvent.PlaybackFinished, + PlayerEvent.AdStarted, + ]; + private registerPlayerEvents(): void { this.handlers.add(PlayerEvent.SourceLoaded, this.onSourceLoaded); - this.handlers.add(PlayerEvent.Play, this.onPlay); this.handlers.add(PlayerEvent.Playing, this.onPlaying); this.handlers.add(PlayerEvent.Paused, this.onPlaybackStateChanged); this.handlers.add(PlayerEvent.StallStarted, this.onPlaybackStateChanged); @@ -406,6 +412,18 @@ export class ConvivaAnalytics { this.handlers.add(PlayerEvent.CastStarted, this.onCustomEvent); this.handlers.add(PlayerEvent.CastStopped, this.onCustomEvent); + + ConvivaAnalytics.stallTrackingStartEvents.forEach((eventName) => { + this.handlers.add(eventName, (event) => { + this.convivaAnalyticsTracker.startStallTrackingTimeout(event); + }); + }); + + ConvivaAnalytics.stallTrackingClearEvents.forEach((eventName) => { + this.handlers.add(eventName, (event) => { + this.convivaAnalyticsTracker.clearStallTrackingTimeout(event); + }); + }); } private unregisterPlayerEvents(): void { diff --git a/src/ts/ConvivaAnalyticsTracker.ts b/src/ts/ConvivaAnalyticsTracker.ts index 1e7b230..b61bfdd 100644 --- a/src/ts/ConvivaAnalyticsTracker.ts +++ b/src/ts/ConvivaAnalyticsTracker.ts @@ -205,21 +205,29 @@ export class ConvivaAnalyticsTracker { // Since there are no stall events during play / playing; seek / seeked; timeShift / timeShifted we need // to track stalling state between those events. To prevent tracking eg. when seeking in buffer we delay it. private stallTrackingTimeout: Timeout = new Timeout(ConvivaAnalyticsTracker.STALL_TRACKING_DELAY_MS, () => { - if (this._isAdBreakActive) { - this.debugLog('[ ConvivaAnalyticsTracker ] report buffering ad playback state'); - this.convivaAdAnalytics.reportAdMetric( - Conviva.Constants.Playback.PLAYER_STATE, - Conviva.Constants.PlayerState.BUFFERING, - ); - } else { - this.debugLog('[ ConvivaAnalyticsTracker ] report buffering playback state'); - this.convivaVideoAnalytics.reportPlaybackMetric( - Conviva.Constants.Playback.PLAYER_STATE, - Conviva.Constants.PlayerState.BUFFERING, - ); - } + this.trackPlaybackState(Conviva.Constants.PlayerState.BUFFERING); }); + public startStallTrackingTimeout(event: PlayerEventBase) { + if (!this.isSessionActive()) { + return; + } + + this.debugLog(`[ ConvivaAnalyticsTracker ] start stall tracking after ${event.type} event`); + + this.stallTrackingTimeout.start(); + } + + public clearStallTrackingTimeout(event: PlayerEventBase) { + if (!this.stallTrackingTimeout.isActive()) { + return; + } + + this.debugLog(`[ ConvivaAnalyticsTracker ] stop stall tracking after ${event.type} event`); + + this.stallTrackingTimeout.clear(); + } + /** * Boolean to track whether a session was ended by an upstream caller instead of within internal session management. * If this is true, we should avoid initializing a new session internally if a session is not active @@ -601,66 +609,42 @@ export class ConvivaAnalyticsTracker { } private onSourceLoaded = (event: PlayerEventBase) => { - this.debugLog('[ ConvivaAnalyticsTracker ] [ Player Event ] source loaded', event); - if (!this.isSessionActive()) { return; } + this.debugLog('[ ConvivaAnalyticsTracker ] building content metadata after source loaded event', event); + this.buildContentMetadata(); this.updateSession(); }; - public trackPlaybackStateChanged(event: PlayerEventBase) { - if (!this.isSessionActive()) { - return; - } - + public trackPlaybackStateFromEvent(event: PlayerEventBase) { const playerState = PlayerStateHelper.getPlayerStateFromEvent(event, this.player); - const stallTrackingStartEvents = [ - PlayerEvent.Play, - PlayerEvent.Seek, - PlayerEvent.TimeShift, - PlayerEvent.AdBreakStarted, - PlayerEvent.AdFinished, - PlayerEvent.RestoringContent, - ]; - const stallTrackingClearEvents = [ - PlayerEvent.StallStarted, // StallStarted is reported as BUFFERING immediately. Does not need the delayed timeout approach. - PlayerEvent.Playing, - PlayerEvent.Paused, - PlayerEvent.Seeked, - PlayerEvent.TimeShifted, - PlayerEvent.StallEnded, - PlayerEvent.PlaybackFinished, - PlayerEvent.AdStarted, - ]; - - if (stallTrackingStartEvents.indexOf(event.type) !== -1) { - this.stallTrackingTimeout.start(); - } else if (stallTrackingClearEvents.indexOf(event.type) !== -1) { - this.stallTrackingTimeout.clear(); - } + this.debugLog(`[ ConvivaAnalyticsTracker ] inferred player state ${playerState} from ${event.type} event`, {playerState, event}); if (playerState) { - if (this._isAdBreakActive) { - this.debugLog('[ ConvivaAnalyticsTracker ] report ad playback state', playerState); - this.convivaAdAnalytics.reportAdMetric(Conviva.Constants.Playback.PLAYER_STATE, playerState); - } else { - this.debugLog('[ ConvivaAnalyticsTracker ] report playback state', playerState); - this.convivaVideoAnalytics.reportPlaybackMetric(Conviva.Constants.Playback.PLAYER_STATE, playerState); - } + this.trackPlaybackState(PlayerStateHelper.getPlayerStateFromEvent(event, this.player)) } + } - if (event.type === PlayerEvent.PlaybackFinished) { - this.debugLog('[ ConvivaAnalyticsTracker ] report playback ended'); - this.convivaVideoAnalytics.reportPlaybackEnded(); + private trackPlaybackState(playerState: Conviva.valueof) { + if (!this.isSessionActive()) { + return; + } + + if (this._isAdBreakActive) { + this.debugLog('[ ConvivaAnalyticsTracker ] report ad playback state', playerState); + this.convivaAdAnalytics.reportAdMetric(Conviva.Constants.Playback.PLAYER_STATE, playerState); + } else { + this.debugLog('[ ConvivaAnalyticsTracker ] report playback state', playerState); + this.convivaVideoAnalytics.reportPlaybackMetric(Conviva.Constants.Playback.PLAYER_STATE, playerState); } } private onPlay = (event: PlaybackEvent) => { - this.debugLog('[ ConvivaAnalyticsTracker ] [ Player Event ] play'); + this.debugLog('[ ConvivaAnalyticsTracker ] checking if session needs to be initialized after play event'); if (!this.canTrackPlayEvent) { return; @@ -681,23 +665,23 @@ export class ConvivaAnalyticsTracker { }; private onPlaying = (event: PlaybackEvent) => { - this.debugLog('[ ConvivaAnalyticsTracker ] [ Player Event ] playing', event); - if (!this.isSessionActive()) { return; } + this.debugLog('[ ConvivaAnalyticsTracker ] updating session metadata after playing event', event); + this.contentMetadataBuilder.setPlaybackStarted(true); this.updateSession(); }; private onPlaybackFinished = (event: PlayerEventBase) => { - this.debugLog('[ ConvivaAnalyticsTracker ] [ Player Event ] playback finished', event); - if (!this.isSessionActive()) { return; } + this.debugLog('[ ConvivaAnalyticsTracker ] releasing everything after playback finished event', event); + this.convivaVideoAnalytics.release(); this.convivaVideoAnalytics = null; @@ -705,6 +689,11 @@ export class ConvivaAnalyticsTracker { this.convivaAdAnalytics = null; }; + public trackPlaybackFinished = () => { + this.debugLog('[ ConvivaAnalyticsTracker ] report playback ended'); + this.convivaVideoAnalytics.reportPlaybackEnded(); + } + public trackVideoQualityChanged = (event: VideoQualityChangedEvent) => { if (!this.isSessionActive()) { return; @@ -747,7 +736,7 @@ export class ConvivaAnalyticsTracker { }); this.convivaAdAnalytics.reportAdStarted(adInfo); - this.debugLog(`[ ConvivaAnalyticsTracker ] report ${PlayerStateHelper.getPlayerState(this.player)} ad playback state`); + this.debugLog(`[ ConvivaAnalyticsTracker ] report ${PlayerStateHelper.getPlayerState(this.player)} ad playback state within tracking ad started`); this.convivaAdAnalytics.reportAdMetric(Conviva.Constants.Playback.PLAYER_STATE, PlayerStateHelper.getPlayerState(this.player)); if (type === Conviva.Constants.AdType.SERVER_SIDE) { @@ -929,7 +918,7 @@ export class ConvivaAnalyticsTracker { }; private onSourceUnloaded = (event: PlayerEventBase) => { - this.debugLog('[ ConvivaAnalyticsTracker ] [ Player Event ] source unloaded', event); + this.debugLog('[ ConvivaAnalyticsTracker ] checking if seession needs to be ended after source unloaded', event); if (this._isAdBreakActive) { // Ignore sourceUnloaded events during ads @@ -939,6 +928,7 @@ export class ConvivaAnalyticsTracker { } }; + // These are attached earlier than the ones inside `ConvivaAnalytics` private registerPlayerEvents(): void { this.handlers.add(PlayerEvent.SourceLoaded, this.onSourceLoaded); this.handlers.add(PlayerEvent.Play, this.onPlay); diff --git a/src/ts/helper/PlayerStateHelper.ts b/src/ts/helper/PlayerStateHelper.ts index 2b51aae..9b7a7c2 100644 --- a/src/ts/helper/PlayerStateHelper.ts +++ b/src/ts/helper/PlayerStateHelper.ts @@ -6,6 +6,9 @@ export class PlayerStateHelper { let playerState; switch (event.type) { + case PlayerEvent.AdBreakStarted: + case PlayerEvent.AdFinished: + case PlayerEvent.RestoringContent: case PlayerEvent.StallStarted: playerState = Conviva.Constants.PlayerState.BUFFERING; break;