Skip to content

Commit

Permalink
Introduce workaround for underreported VST after ad pre-roll
Browse files Browse the repository at this point in the history
  • Loading branch information
wasp898 committed Nov 26, 2024
1 parent 2c7077b commit 732ab9f
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 10 deletions.
74 changes: 74 additions & 0 deletions spec/tests/ConvivaAnalyticsTracker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,80 @@ describe(ConvivaAnalyticsTracker, () => {
expect(stallTrackingStopTimeoutSpy).toHaveBeenCalled();
})
})

/**
* Since the web player dispatches AdBreakFinished only after main content has successfully restored,
* a workaround is in place to signal the end of the ad break early so that VST can properly be tracked.
*/
describe('eager ad break ended signalling', () => {
let convivaAnalyticsTracker: ConvivaAnalyticsTracker;
let reportAdBreakEndedSpy: jest.SpyInstance;
let reportAdBreakStartedSpy: jest.SpyInstance;

beforeEach(() => {
convivaAnalyticsTracker = new ConvivaAnalyticsTracker('test-key');

const {playerMock} = MockHelper.createPlayerMock();
convivaAnalyticsTracker.attachPlayer(playerMock);
jest.spyOn(playerMock, 'getSource').mockImplementation(() => ({ title: 'test-title' }));
convivaAnalyticsTracker.initializeSession();

jest.useFakeTimers();
reportAdBreakEndedSpy = jest.spyOn(convivaAnalyticsTracker['convivaVideoAnalytics'], 'reportAdBreakEnded');
reportAdBreakStartedSpy = jest.spyOn(convivaAnalyticsTracker['convivaVideoAnalytics'], 'reportAdBreakStarted');
});

it('should report ad break ended on AdBreakFinished by default', () => {
convivaAnalyticsTracker.trackAdBreakStarted(Conviva.Constants.AdType.CLIENT_SIDE);
convivaAnalyticsTracker.trackAdStarted({}, Conviva.Constants.AdType.CLIENT_SIDE);
convivaAnalyticsTracker.trackAdFinished();

expect(reportAdBreakEndedSpy).toHaveBeenCalledTimes(0);

convivaAnalyticsTracker.trackAdBreakFinished();

expect(reportAdBreakEndedSpy).toHaveBeenCalledTimes(1);
})

it('should report ad break ended early if AdBreakFinished is too slow', () => {
convivaAnalyticsTracker.trackAdBreakStarted(Conviva.Constants.AdType.CLIENT_SIDE);
convivaAnalyticsTracker.trackAdStarted({}, Conviva.Constants.AdType.CLIENT_SIDE);
convivaAnalyticsTracker.trackAdFinished();

expect(reportAdBreakEndedSpy).toHaveBeenCalledTimes(0);

jest.runAllTimers();

expect(reportAdBreakEndedSpy).toHaveBeenCalledTimes(1);
})

it('should not report ad break ended if AdFinished is followed by a new AdStarted', () => {
convivaAnalyticsTracker.trackAdBreakStarted(Conviva.Constants.AdType.CLIENT_SIDE);
convivaAnalyticsTracker.trackAdStarted({}, Conviva.Constants.AdType.CLIENT_SIDE);
convivaAnalyticsTracker.trackAdFinished();

convivaAnalyticsTracker.trackAdStarted({}, Conviva.Constants.AdType.CLIENT_SIDE);

jest.runAllTimers();

expect(reportAdBreakEndedSpy).toHaveBeenCalledTimes(0);
expect(reportAdBreakStartedSpy).toHaveBeenCalledTimes(1);
})

it('should report ad break started again after wrongly reporting ad break ended', () => {
convivaAnalyticsTracker.trackAdBreakStarted(Conviva.Constants.AdType.CLIENT_SIDE);
convivaAnalyticsTracker.trackAdStarted({}, Conviva.Constants.AdType.CLIENT_SIDE);
convivaAnalyticsTracker.trackAdFinished();
jest.runAllTimers();

expect(reportAdBreakEndedSpy).toHaveBeenCalledTimes(1);
expect(reportAdBreakStartedSpy).toHaveBeenCalledTimes(1);

convivaAnalyticsTracker.trackAdStarted({}, Conviva.Constants.AdType.CLIENT_SIDE);

expect(reportAdBreakStartedSpy).toHaveBeenCalledTimes(2);
})
});
})

const getInvokedTimes = (mock: unknown) => {
Expand Down
46 changes: 36 additions & 10 deletions src/ts/ConvivaAnalyticsTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ export interface EventAttributes {
export class ConvivaAnalyticsTracker {
private static readonly VERSION: string = '{{VERSION}}';

public static readonly AD_BREAK_FINISHED_DELAY_MS = 250;
public static readonly STALL_TRACKING_DELAY_MS = 100;

private _player: PlayerAPI;

private get player(): PlayerAPI {
Expand Down Expand Up @@ -736,10 +738,19 @@ export class ConvivaAnalyticsTracker {
};

public trackAdStarted = (adInfo: Conviva.ConvivaMetadata, type: Conviva.valueof<Conviva.ConvivaConstants['AdType']>, bitrateKbps?: number) => {
// Clear the timeout that may have been scheduled by a previous ad finished event, as the ad break is not actually over yet.
this.adBreakFinishedTimeout.clear();

if (!this.isSessionActive()) {
return;
}

if (!this.isAdBreakActive) {
// If no ad break is active, it must mean that the `adBreakFinishedTimeout` ran before AdStarted was emitted.
// Then we need to report this as the start of a new ad break.
this.trackAdBreakStarted(type);
}

this.debugLog('[ ConvivaAnalyticsTracker ] report ad started', {
adInfo,
type,
Expand Down Expand Up @@ -769,15 +780,6 @@ export class ConvivaAnalyticsTracker {
}
}

public trackAdFinished = () => {
if (!this.isSessionActive()) {
return;
}

this.debugLog('[ ConvivaAnalyticsTracker ] report ad ended');
this.convivaAdAnalytics.reportAdEnded();
}

public trackAdSkipped = () => {
if (!this.isSessionActive()) {
return;
Expand All @@ -787,15 +789,39 @@ export class ConvivaAnalyticsTracker {
this.convivaAdAnalytics.reportAdSkipped();
};

public trackAdBreakFinished = () => {
public trackAdFinished = () => {
if (!this.isSessionActive()) {
return;
}

this.debugLog('[ ConvivaAnalyticsTracker ] report ad ended');
this.convivaAdAnalytics.reportAdEnded();

// Start timer to report ad break finished, as waiting for the event will cause VST to be too low.
this.adBreakFinishedTimeout.start();
}

private reportAdBreakEnded = () => {
this._isAdBreakActive = false;

this.debugLog('[ ConvivaAnalyticsTracker ] report ad break ended');
this.convivaVideoAnalytics.reportAdBreakEnded();
}

private adBreakFinishedTimeout = new Timeout(ConvivaAnalyticsTracker.AD_BREAK_FINISHED_DELAY_MS, this.reportAdBreakEnded);

public trackAdBreakFinished = () => {
// Clear the timeout to prevent the ad break finished event from being reported twice
this.adBreakFinishedTimeout.clear();

if (!this.isSessionActive()) {
return;
}

if (this.isAdBreakActive) {
// If ad break is still active, it must mean that the event was faster than the `adBreakFinishedTimeout`
this.reportAdBreakEnded();
}

this.debugLog(`[ ConvivaAnalyticsTracker ] report ${PlayerStateHelper.getPlayerState(this.player)} playback state`);
this.convivaVideoAnalytics.reportPlaybackMetric(
Expand Down

0 comments on commit 732ab9f

Please sign in to comment.