diff --git a/CHANGELOG.md b/CHANGELOG.md index 51b9871..5257b10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Fixed +- Mid-roll ads erroneously reported as post-rolls when mid-roll's schedule time is greater than the duration of the ad ## [6.1.0] - 2024-12-11 ### Changed diff --git a/package-lock.json b/package-lock.json index f7f03d1..29cb23a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1106,9 +1106,9 @@ } }, "node_modules/bitmovin-player": { - "version": "8.193.0", - "resolved": "https://registry.npmjs.org/bitmovin-player/-/bitmovin-player-8.193.0.tgz", - "integrity": "sha512-zxI3tzZKcP2opac/PhfwyhNp1pMqbzMl/FPWuI0E63DNmvppUVfOhVWAGLNTBIc9sEpbYUPyMBq5XWd7Ml10jw==", + "version": "8.194.0", + "resolved": "https://registry.npmjs.org/bitmovin-player/-/bitmovin-player-8.194.0.tgz", + "integrity": "sha512-+amENi2mG8UQxE7d89ZUmfX14eSqlLq9iGLZuXUWuYLQc2IUJ2kpmtZjGEJdAHRscwl9sdindMs1RyHQJXRS0w==", "dev": true }, "node_modules/bitmovin-player-ui": { @@ -12391,9 +12391,9 @@ } }, "bitmovin-player": { - "version": "8.193.0", - "resolved": "https://registry.npmjs.org/bitmovin-player/-/bitmovin-player-8.193.0.tgz", - "integrity": "sha512-zxI3tzZKcP2opac/PhfwyhNp1pMqbzMl/FPWuI0E63DNmvppUVfOhVWAGLNTBIc9sEpbYUPyMBq5XWd7Ml10jw==", + "version": "8.194.0", + "resolved": "https://registry.npmjs.org/bitmovin-player/-/bitmovin-player-8.194.0.tgz", + "integrity": "sha512-+amENi2mG8UQxE7d89ZUmfX14eSqlLq9iGLZuXUWuYLQc2IUJ2kpmtZjGEJdAHRscwl9sdindMs1RyHQJXRS0w==", "dev": true }, "bitmovin-player-ui": { diff --git a/spec/tests/AdHelper.spec.ts b/spec/tests/AdHelper.spec.ts index 6933ccf..21464c2 100644 --- a/spec/tests/AdHelper.spec.ts +++ b/spec/tests/AdHelper.spec.ts @@ -5,36 +5,30 @@ import * as Conviva from '@convivainc/conviva-js-coresdk'; describe(AdHelper, () => { describe('mapAdPosition', () => { it('should map ad position to preroll', () => { - const player = { - getDuration: () => 100, - } as PlayerAPI; + const mainContentDuration = 100; const adBreak = { scheduleTime: 0 } as AdBreak; - expect(AdHelper.mapCsaiAdPosition(adBreak, player)).toEqual(Conviva.Constants.AdPosition.PREROLL) + expect(AdHelper.mapCsaiAdPosition(adBreak, mainContentDuration)).toEqual(Conviva.Constants.AdPosition.PREROLL) }) it('should map ad position to postroll', () => { - const player = { - getDuration: () => 100, - } as PlayerAPI; + const mainContentDuration = 100; const adBreak = { scheduleTime: 100 } as AdBreak; - expect(AdHelper.mapCsaiAdPosition(adBreak, player)).toEqual(Conviva.Constants.AdPosition.POSTROLL) + expect(AdHelper.mapCsaiAdPosition(adBreak, mainContentDuration)).toEqual(Conviva.Constants.AdPosition.POSTROLL) }) it('should map ad position to midroll', () => { - const player = { - getDuration: () => 100, - } as PlayerAPI; + const mainContentDuration = 100; const adBreak = { scheduleTime: 50 } as AdBreak; - expect(AdHelper.mapCsaiAdPosition(adBreak, player)).toEqual(Conviva.Constants.AdPosition.MIDROLL) + expect(AdHelper.mapCsaiAdPosition(adBreak, mainContentDuration)).toEqual(Conviva.Constants.AdPosition.MIDROLL) }) }) @@ -66,7 +60,7 @@ describe(AdHelper, () => { describe('extractConvivaAdInfo', () => { it('should extract minimal Conviva ad info', () => { - const player = {} as PlayerAPI; + const mainContentDuration = 100; const adBreakEvent = { adBreak: { scheduleTime: 0 @@ -79,7 +73,7 @@ describe(AdHelper, () => { }, } as AdEvent; - expect(AdHelper.extractCsaiConvivaAdInfo(player, adBreakEvent, adEvent)).toEqual({ + expect(AdHelper.extractCsaiConvivaAdInfo(adBreakEvent, mainContentDuration, adEvent)).toEqual({ "c3.ad.creativeId": "NA", "c3.ad.firstAdId": "123", "c3.ad.firstAdSystem": "NA", @@ -95,7 +89,7 @@ describe(AdHelper, () => { }) it('should extract full Conviva ad info', () => { - const player = {} as PlayerAPI; + const mainContentDuration = 100; const adBreakEvent = { adBreak: { scheduleTime: 0 @@ -119,7 +113,7 @@ describe(AdHelper, () => { } as Ad | LinearAd, } as AdEvent; - expect(AdHelper.extractCsaiConvivaAdInfo(player, adBreakEvent, adEvent)).toEqual({ + expect(AdHelper.extractCsaiConvivaAdInfo(adBreakEvent, mainContentDuration, adEvent)).toEqual({ [Conviva.Constants.ASSET_NAME]: "Test title", [Conviva.Constants.STREAM_URL]: 'https://test.com', [Conviva.Constants.DURATION]: 100, diff --git a/src/ts/ConvivaAnalytics.ts b/src/ts/ConvivaAnalytics.ts index 8ac4a00..ac886f5 100644 --- a/src/ts/ConvivaAnalytics.ts +++ b/src/ts/ConvivaAnalytics.ts @@ -45,6 +45,11 @@ export class ConvivaAnalytics { private readonly logger: Conviva.LoggingInterface = new Html5Logging(); + /** + * Tracks the duration of main content. Needed as the player may return the ad duration instead. + */ + private mainContentDuration = 0; + public readonly ssai: Omit; constructor(player: PlayerAPI | undefined, customerKey: string, config: ConvivaAnalyticsConfiguration = {}) { @@ -279,7 +284,7 @@ export class ConvivaAnalytics { private onAdStarted = (event: AdEvent) => { this.debugLog('[ ConvivaAnalytics ] [ Player Event ] ad started', event); - const adInfo = AdHelper.extractCsaiConvivaAdInfo(this.player, this.lastAdBreakEvent, event); + const adInfo = AdHelper.extractCsaiConvivaAdInfo(this.lastAdBreakEvent, this.mainContentDuration, event); const bitrateKbps = event.ad.data?.bitrate; this.convivaAnalyticsTracker.trackAdStarted(adInfo, Conviva.Constants.AdType.CLIENT_SIDE, bitrateKbps); @@ -364,7 +369,13 @@ export class ConvivaAnalytics { this.releaseInternal(event); }; + private onSourceLoaded = (event: PlayerEventBase) => { + this.debugLog('[ ConvivaAnalytics ] [ Player Event ] onSourceLoaded', event); + this.mainContentDuration = this.player.getDuration(); + }; + 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); diff --git a/src/ts/helper/AdHelper.ts b/src/ts/helper/AdHelper.ts index 37f2f03..26614f3 100644 --- a/src/ts/helper/AdHelper.ts +++ b/src/ts/helper/AdHelper.ts @@ -43,13 +43,14 @@ export interface SsaiAdInfo { export class AdHelper { public static mapCsaiAdPosition( adBreak: AdBreak, - player: PlayerAPI, + mainContentDuration: number, ): Conviva.valueof { + if (adBreak.scheduleTime <= 0) { return Conviva.Constants.AdPosition.PREROLL; } - if (adBreak.scheduleTime >= player.getDuration()) { + if (adBreak.scheduleTime >= mainContentDuration) { return Conviva.Constants.AdPosition.POSTROLL; } @@ -76,7 +77,11 @@ export class AdHelper { return formattedErrorParts.join(' '); } - public static extractCsaiConvivaAdInfo(player: PlayerAPI, adBreakEvent: AdBreakEvent, adEvent: AdEvent): Conviva.ConvivaMetadata { + public static extractCsaiConvivaAdInfo( + adBreakEvent: AdBreakEvent, + mainContentDuration: number, + adEvent: AdEvent, + ): Conviva.ConvivaMetadata { const ad = adEvent.ad as Ad | LinearAd; const adData = ad.data as undefined | AdData | VastAdData; @@ -107,7 +112,7 @@ export class AdHelper { const adInfo: Conviva.ConvivaMetadata = { 'c3.ad.id': ad.id, 'c3.ad.technology': Conviva.Constants.AdType.CLIENT_SIDE, - 'c3.ad.position': AdHelper.mapCsaiAdPosition(adBreakEvent.adBreak, player), + 'c3.ad.position': AdHelper.mapCsaiAdPosition(adBreakEvent.adBreak, mainContentDuration), 'c3.ad.system': adSystemName, 'c3.ad.creativeId': creativeId, 'c3.ad.firstAdId': firstAdId,