Skip to content

Commit

Permalink
Split delayed stalling reporting from playback state reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
wasp898 committed Dec 23, 2024
1 parent 3345e20 commit 1383a0e
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 79 deletions.
54 changes: 36 additions & 18 deletions src/ts/ConvivaAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
112 changes: 51 additions & 61 deletions src/ts/ConvivaAnalyticsTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Conviva.ConvivaConstants['PlayerState']>) {
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;
Expand All @@ -681,30 +665,35 @@ 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;

this.convivaAdAnalytics.release();
this.convivaAdAnalytics = null;
};

public trackPlaybackFinished = () => {
this.debugLog('[ ConvivaAnalyticsTracker ] report playback ended');
this.convivaVideoAnalytics.reportPlaybackEnded();
}

public trackVideoQualityChanged = (event: VideoQualityChangedEvent) => {
if (!this.isSessionActive()) {
return;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/ts/helper/PlayerStateHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 1383a0e

Please sign in to comment.