From 32583596e86a0dadcf1913b9b75ebced2ce9ec81 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:49:47 +0100 Subject: [PATCH 01/75] feat: add ad holiday feature --- src/index.html | 2 +- src/ts/BitmovinYospacePlayerAPI.ts | 1 + src/ts/BitmovinYospacePlayerPolicy.ts | 7 ++- src/ts/InternalBitmovinYospacePlayer.ts | 77 +++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/index.html b/src/index.html index 1b7741a4..feea305c 100644 --- a/src/index.html +++ b/src/index.html @@ -575,7 +575,7 @@ log('[BYP] Metadata Parsed: ', cloneDeepSimple(event)); }); - yospacePlayer.setPolicy(sampleYospacePlayerPolicy); + // yospacePlayer.setPolicy(sampleYospacePlayerPolicy); yospacePlayer.on('segmentplayback', function (event) { if (event.mimeType === 'video/mp4') { diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index 0d6f1c5a..5afe8c96 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -84,6 +84,7 @@ export interface TruexConfiguration { export interface YospaceAdBreak extends AdBreak { duration: number; position?: YospaceAdBreakPosition; + active: boolean; } export interface YospaceAdBreakEvent extends PlayerEventBase { diff --git a/src/ts/BitmovinYospacePlayerPolicy.ts b/src/ts/BitmovinYospacePlayerPolicy.ts index 18805fa6..a4502537 100644 --- a/src/ts/BitmovinYospacePlayerPolicy.ts +++ b/src/ts/BitmovinYospacePlayerPolicy.ts @@ -1,5 +1,5 @@ import { LinearAd } from 'bitmovin-player'; -import { BitmovinYospacePlayerAPI, BitmovinYospacePlayerPolicy } from './BitmovinYospacePlayerAPI'; +import { BitmovinYospacePlayerAPI, BitmovinYospacePlayerPolicy, YospaceAdBreak } from './BitmovinYospacePlayerAPI'; export class DefaultBitmovinYospacePlayerPolicy implements BitmovinYospacePlayerPolicy { private player: BitmovinYospacePlayerAPI; @@ -15,10 +15,11 @@ export class DefaultBitmovinYospacePlayerPolicy implements BitmovinYospacePlayer canSeekTo(seekTarget: number): number { const currentTime = this.player.getCurrentTime(); - const adBreaks = this.player.ads.list(); + // @ts-expect-error the BitmovinYospacePlayerAPI interface is not up to date + const adBreaks: YospaceAdBreak[] = this.player.ads.list(); const skippedAdBreaks = adBreaks.filter((adBreak) => { - return adBreak.scheduleTime > currentTime && adBreak.scheduleTime < seekTarget; + return adBreak.active && adBreak.scheduleTime > currentTime && adBreak.scheduleTime < seekTarget; }); if (skippedAdBreaks.length > 0) { diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 05fbe203..9297bf0b 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -147,6 +147,11 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { private startSent: boolean; private lastTimeChangedTime = 0; + private adImmunityConfig = { + duration: 20000, // 0 duration = disabled + }; + private adImmune = false; + private adImmunityCountDown: number | null = null; constructor(containerElement: HTMLElement, player: PlayerAPI, yospaceConfig: YospaceConfiguration = {}) { this.yospaceConfig = yospaceConfig; @@ -393,7 +398,25 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { return false; } + // set all breaks seeked past during immunity to inactive + // this prevents the default player policy from redirecting + // the seek target + if (this.adImmune) { + const adBreaks: AdBreak[] = this.session.getAdBreaksByType(BreakType.LINEAR); + const currentTime = this.player.getCurrentTime(); + + adBreaks.forEach((adBreak) => { + const breakStart = this.toMagicTime(toSeconds(adBreak.getStart())); + + // Check if break is being seeked past and deactivate it + if (breakStart > currentTime && breakStart < time) { + adBreak.setInactive(); + } + }); + } + const allowedSeekTarget = this.playerPolicy.canSeekTo(time); + if (allowedSeekTarget !== time) { // cache original seek target this.cachedSeekTarget = time; @@ -548,7 +571,26 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.player.setPlaybackSpeed(this.playbackSpeed); } + setAdImmunityConfig(config: any) { + this.adImmunityConfig = config; + } + // Helper + private startAdHoliday() { + // only start a timer if a duration has been configured, and ad immunity is not + // already active. Only enable for VOD content. + if (this.adImmune || this.yospaceSourceConfig.assetType !== YospaceAssetType.VOD) { + return; + } else if (this.adImmunityConfig.duration) { + this.adImmune = true; + + this.adImmunityCountDown = window.setTimeout(() => { + this.adImmune = false; + this.adImmunityCountDown = null; + }, this.adImmunityConfig.duration); + } + } + private isAdActive(): boolean { return Boolean(this.getCurrentAd()); } @@ -723,6 +765,8 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.fireEvent(playerEvent); + this.startAdHoliday(); + if (this.cachedSeekTarget) { this.seek(this.cachedSeekTarget, 'yospace-ad-skipping'); this.cachedSeekTarget = null; @@ -756,6 +800,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { ads: ysAdBreak.getAdverts().map(AdTranslator.mapYsAdvert), duration: toSeconds(ysAdBreak.getDuration()), position: ysAdBreak.getPosition() as YospaceAdBreakPosition, + active: ysAdBreak.isActive(), }; } @@ -850,10 +895,18 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.session = null; } + if (this.adImmunityCountDown) { + window.clearTimeout(this.adImmunityCountDown); + this.adImmunityCountDown = null; + } + if (this.dateRangeEmitter) { this.dateRangeEmitter.reset(); } + this.adImmune = false; + // should adImmunityConfig be reset here? If yes, it + // would require configuration for each new video start this.adParts = []; this.adStartedTimestamp = null; this.cachedSeekTarget = null; @@ -1127,6 +1180,30 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { }; private onTimeChanged = (event: TimeChangedEvent) => { + // the offset of 500ms is an attempt to prevent the + // first few frames of an ad playing before the seek + // past the break has time to propagate + const adBreakCheckOffset = 500; + const upcomingAdBreak: AdBreak | null = this.session.getAdBreakForPlayhead(event.time * 1000 + adBreakCheckOffset); + + // Seek past previously deactivated ad breaks + if (upcomingAdBreak && !upcomingAdBreak.isActive()) { + this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); + + // do not propagate time to the rest of the app, we want to seek past it + return; + } + + // seek past and deactivate ad breaks entered during ad immunity + if (upcomingAdBreak && this.adImmune) { + upcomingAdBreak.setInactive(); + + this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); + + // do not propagate time to the rest of the app, we want to seek past it + return; + } + // There is an outstanding bug on Safari mobile where upon exiting an ad break, // our TimeChanged event "rewinds" ~12 ms. This is a temporary fix. // If we report this "rewind" to Yospace, it results in duplicate ad events. From b011b782908e152fb9215547648af5fa692fdedd Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Wed, 13 Mar 2024 09:48:56 +0100 Subject: [PATCH 02/75] chore: update syntax, add logging --- src/ts/InternalBitmovinYospacePlayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 9297bf0b..974c7cab 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -402,14 +402,14 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { // this prevents the default player policy from redirecting // the seek target if (this.adImmune) { - const adBreaks: AdBreak[] = this.session.getAdBreaksByType(BreakType.LINEAR); const currentTime = this.player.getCurrentTime(); - adBreaks.forEach((adBreak) => { + this.session.getAdBreaksByType(BreakType.LINEAR).forEach((adBreak) => { const breakStart = this.toMagicTime(toSeconds(adBreak.getStart())); // Check if break is being seeked past and deactivate it if (breakStart > currentTime && breakStart < time) { + Logger.log('[BitmovinYospacePlayer] Ad Immunity deactivated ad break during seek', adBreak); adBreak.setInactive(); } }); From eef9cff2d3e949631c4818a8c4385d242d59612a Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:02:33 +0100 Subject: [PATCH 03/75] chore: add ad immunity event structure --- src/ts/BitmovinYospacePlayerAPI.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index 5afe8c96..7f4df8a9 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -165,6 +165,9 @@ export enum YospacePlayerEvent { PolicyError = 'policyerror', TruexAdFree = 'truexadfree', TruexAdBreakFinished = 'truexadbreakfinished', + AdImmunityConfigured = 'adimmunityconfigured', + AdImmunityStarted = 'adimmunitystarted', + AdImmunityEnded = 'adimmunityended', } export enum YospaceErrorCode { @@ -213,3 +216,17 @@ export interface YospaceEventBase { export interface YospacePlayerEventCallback { (event: PlayerEventBase | YospaceEventBase): void; } + +export interface AdImmunityConfiguredEvent extends YospaceEventBase { + type: YospacePlayerEvent.AdImmunityConfigured; + config: { duration: number }; +} + +export interface AdImmunityStartedEvent extends YospaceEventBase { + type: YospacePlayerEvent.AdImmunityStarted; + duration: number; +} + +export interface AdImmunityEndedEvent extends YospaceEventBase { + type: YospacePlayerEvent.AdImmunityEnded; +} From 96493185562cdb6d924f1faa42a5615912b642a2 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:34:45 +0100 Subject: [PATCH 04/75] feat: make api public, add documentation, add events --- src/ts/BitmovinYospacePlayer.ts | 17 ++++++++ src/ts/BitmovinYospacePlayerAPI.ts | 30 ++++++++++++++ src/ts/InternalBitmovinYospacePlayer.ts | 52 +++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/ts/BitmovinYospacePlayer.ts b/src/ts/BitmovinYospacePlayer.ts index b40a848b..c3e2f67f 100644 --- a/src/ts/BitmovinYospacePlayer.ts +++ b/src/ts/BitmovinYospacePlayer.ts @@ -50,6 +50,7 @@ import { YospacePlayerType, YospacePolicyErrorCode, YospaceSourceConfig, + AdImmunityConfig, } from './BitmovinYospacePlayerAPI'; import { Logger } from './Logger'; import { BitmovinYospaceHelper } from './BitmovinYospaceHelper'; @@ -576,5 +577,21 @@ export class BitmovinYospacePlayer implements BitmovinYospacePlayerAPI { return this.player.getAspectRatio(); } + getAdImmunityConfig() { + return this.player.getAdImmunityConfig(); + } + + isAdImmunityActive() { + return this.player.isAdImmunityActive(); + } + + endAdImmunity() { + this.player.endAdImmunity(); + } + + setAdImmunityConfig(options: AdImmunityConfig) { + this.player.setAdImmunityConfig(options); + } + readonly drm: DrmAPI; } diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index 7f4df8a9..9e68a52f 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -69,6 +69,32 @@ export interface BitmovinYospacePlayerAPI extends PlayerAPI { getDuration(mode?: TimeMode): number; forceSeek(time: number, issuer?: string): boolean; + + /** + * Provide a duration greater than 0 to enable the ad immunity feature. + * The user will become immune to ad breaks for the duration upon + * fully watching an ad break. + * + * duration 0 disables the feature + */ + setAdImmunityConfig(options: AdImmunityConfig): void; + + /** + * Returns the current ad immunity configuration + * + * duration 0 means the feature is disabled + */ + getAdImmunityConfig(): AdImmunityConfig; + + /** + * Returns a boolean value indicating if the user is currently immune to ad breaks + */ + isAdImmunityActive(): boolean; + + /** + * Immediately ends an ongoing ad immunity period, before it would naturally expire + */ + endAdImmunity(): void; } export interface YospaceSourceConfig extends SourceConfig { @@ -230,3 +256,7 @@ export interface AdImmunityStartedEvent extends YospaceEventBase { export interface AdImmunityEndedEvent extends YospaceEventBase { type: YospacePlayerEvent.AdImmunityEnded; } + +export interface AdImmunityConfig { + duration: number; +} diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 974c7cab..46efb2b5 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -52,6 +52,8 @@ import { import { DefaultBitmovinYospacePlayerPolicy } from './BitmovinYospacePlayerPolicy'; import { ArrayUtils } from 'bitmovin-player-ui/dist/js/framework/arrayutils'; import { + AdImmunityConfiguredEvent, + AdImmunityEndedEvent, BitmovinYospacePlayerAPI, BitmovinYospacePlayerPolicy, CompanionAdType, @@ -69,6 +71,7 @@ import { YospacePolicyErrorCode, YospacePolicyErrorEvent, YospaceSourceConfig, + AdImmunityStartedEvent, } from './BitmovinYospacePlayerAPI'; import { YospacePlayerError } from './YospaceError'; import { @@ -573,10 +576,43 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { setAdImmunityConfig(config: any) { this.adImmunityConfig = config; + Logger.log('[BitmovinYospacePlayer] Ad Immunity Configured:', this.adImmunityConfig); + this.handleYospaceEvent({ + timestamp: Date.now(), + type: YospacePlayerEvent.AdImmunityConfigured, + config, + }); + } + + getAdImmunityConfig() { + return this.adImmunityConfig; + } + + isAdImmunityActive() { + return this.adImmune; + } + + endAdImmunity() { + this.endAdImmunityPeriod(); } // Helper - private startAdHoliday() { + + private endAdImmunityPeriod() { + if (typeof this.adImmunityCountDown === 'number') { + window.clearTimeout(this.adImmunityCountDown); + } + + this.adImmune = false; + this.adImmunityCountDown = null; + Logger.log('[BitmovinYospacePlayer] Ad Immunity Ended'); + this.handleYospaceEvent({ + timestamp: Date.now(), + type: YospacePlayerEvent.AdImmunityEnded, + }); + } + + private startAdImmunityPeriod() { // only start a timer if a duration has been configured, and ad immunity is not // already active. Only enable for VOD content. if (this.adImmune || this.yospaceSourceConfig.assetType !== YospaceAssetType.VOD) { @@ -585,9 +621,15 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.adImmune = true; this.adImmunityCountDown = window.setTimeout(() => { - this.adImmune = false; - this.adImmunityCountDown = null; + this.endAdImmunityPeriod(); }, this.adImmunityConfig.duration); + + Logger.log('[BitmovinYospacePlayer] Ad Immunity Started, duration', this.adImmunityConfig.duration); + this.handleYospaceEvent({ + timestamp: Date.now(), + type: YospacePlayerEvent.AdImmunityStarted, + duration: this.adImmunityConfig.duration, + }); } } @@ -765,7 +807,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.fireEvent(playerEvent); - this.startAdHoliday(); + this.startAdImmunityPeriod(); if (this.cachedSeekTarget) { this.seek(this.cachedSeekTarget, 'yospace-ad-skipping'); @@ -1190,6 +1232,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { if (upcomingAdBreak && !upcomingAdBreak.isActive()) { this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); + Logger.log('[BitmovinYospacePlayer] - seeking past deactivated ad break'); // do not propagate time to the rest of the app, we want to seek past it return; } @@ -1198,6 +1241,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { if (upcomingAdBreak && this.adImmune) { upcomingAdBreak.setInactive(); + Logger.log('[BitmovinYospacePlayer] - Ad Immunity - seeking past and deactivating ad break'); this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); // do not propagate time to the rest of the app, we want to seek past it From 20a41e9073eb82814ec433c7ad2f18f233050583 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:35:12 +0100 Subject: [PATCH 05/75] chore: update ad immunity documentation --- src/ts/BitmovinYospacePlayerAPI.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index 9e68a52f..aae03dc4 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -75,6 +75,9 @@ export interface BitmovinYospacePlayerAPI extends PlayerAPI { * The user will become immune to ad breaks for the duration upon * fully watching an ad break. * + * Ad breaks played over or seeked past during immunity will be marked + * as deactivated, making the user permanently immune to those breaks. + * * duration 0 disables the feature */ setAdImmunityConfig(options: AdImmunityConfig): void; From a91fa813ba71ecb205a343f980c09d70ec22d7af Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Wed, 13 Mar 2024 10:35:50 +0100 Subject: [PATCH 06/75] chore: set default ad immunity duraiton to 0 (disabled) --- src/ts/InternalBitmovinYospacePlayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 46efb2b5..b978db2e 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -151,7 +151,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { private lastTimeChangedTime = 0; private adImmunityConfig = { - duration: 20000, // 0 duration = disabled + duration: 0, // 0 duration = disabled }; private adImmune = false; private adImmunityCountDown: number | null = null; From cbca676b3b442e0db8cb436ccffaba2400f39cb5 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Wed, 13 Mar 2024 11:50:20 +0100 Subject: [PATCH 07/75] feat: exclude postrolls from ad immunity, update documentation --- src/index.html | 2 ++ src/ts/BitmovinYospacePlayerAPI.ts | 5 +++- src/ts/InternalBitmovinYospacePlayer.ts | 33 ++++++++++++++----------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/index.html b/src/index.html index feea305c..279a16ad 100644 --- a/src/index.html +++ b/src/index.html @@ -533,6 +533,8 @@ yospaceConfig ); + // yospacePlayer.setAdImmunityConfig({ duration: 20 }); + var uiManager = new bitmovin.playerui.UIFactory.buildDefaultUI(yospacePlayer); yospacePlayer.on('sourceloaded', function (event) { diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index aae03dc4..51848f56 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -71,7 +71,7 @@ export interface BitmovinYospacePlayerAPI extends PlayerAPI { forceSeek(time: number, issuer?: string): boolean; /** - * Provide a duration greater than 0 to enable the ad immunity feature. + * Provide a duration in seconds greater than 0 to enable the ad immunity feature. * The user will become immune to ad breaks for the duration upon * fully watching an ad break. * @@ -79,6 +79,9 @@ export interface BitmovinYospacePlayerAPI extends PlayerAPI { * as deactivated, making the user permanently immune to those breaks. * * duration 0 disables the feature + * + * postrolls are excluded from ad immunity + * */ setAdImmunityConfig(options: AdImmunityConfig): void; diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index b978db2e..a3e73bf4 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -622,7 +622,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.adImmunityCountDown = window.setTimeout(() => { this.endAdImmunityPeriod(); - }, this.adImmunityConfig.duration); + }, this.adImmunityConfig.duration * 1000); Logger.log('[BitmovinYospacePlayer] Ad Immunity Started, duration', this.adImmunityConfig.duration); this.handleYospaceEvent({ @@ -1228,24 +1228,27 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { const adBreakCheckOffset = 500; const upcomingAdBreak: AdBreak | null = this.session.getAdBreakForPlayhead(event.time * 1000 + adBreakCheckOffset); - // Seek past previously deactivated ad breaks - if (upcomingAdBreak && !upcomingAdBreak.isActive()) { - this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); + // exclude postrolls and unknown break positions from ad immunity to prevent seek loops at end of video + if (upcomingAdBreak?.getPosition() !== 'postroll' && upcomingAdBreak?.getPosition() !== 'unknown') { + // Seek past previously deactivated ad breaks + if (upcomingAdBreak && !upcomingAdBreak.isActive()) { + Logger.log('[BitmovinYospacePlayer] - Ad Immunity seeking past deactivated ad break'); + this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); - Logger.log('[BitmovinYospacePlayer] - seeking past deactivated ad break'); - // do not propagate time to the rest of the app, we want to seek past it - return; - } + // do not propagate time to the rest of the app, we want to seek past it + return; + } - // seek past and deactivate ad breaks entered during ad immunity - if (upcomingAdBreak && this.adImmune) { - upcomingAdBreak.setInactive(); + // seek past and deactivate ad breaks entered during ad immunity + if (upcomingAdBreak && this.adImmune) { + upcomingAdBreak.setInactive(); - Logger.log('[BitmovinYospacePlayer] - Ad Immunity - seeking past and deactivating ad break'); - this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); + Logger.log('[BitmovinYospacePlayer] - Ad Immunity - seeking past and deactivating ad break'); + this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); - // do not propagate time to the rest of the app, we want to seek past it - return; + // do not propagate time to the rest of the app, we want to seek past it + return; + } } // There is an outstanding bug on Safari mobile where upon exiting an ad break, From 2522295bcd7f4d040ee113692ff0d57ae6667de7 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:46:16 +0100 Subject: [PATCH 08/75] chore: add changelog (cherry picked from commit 5fc19b3dbe915084d6f462c744f1191a5ac51361) --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4aa737d..548c64cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +<<<<<<< HEAD + ### Fixed - Event loop on pre-roll ad end @@ -16,6 +18,46 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `mode` argument to `getCurrentTime` to enable fetching absolute time including ad durations - `mode` argument to `getDuration` to enable fetching absolute duration including ad durations +# ||||||| parent of 5fc19b3 (chore: add changelog) + +## 2.4.0 + +### Added + +- ad immunity feature: + + The user will become immune to ad breaks for a duration upon + fully watching an ad break. + + Ad breaks played over or seeked past during immunity will be marked + as deactivated, making the user permanently immune to them. + + setting duration to 0 disables the feature. + + postrolls and ads with unknown positioning are excluded from ad immunity. + + `setAdImmunityConfig(options: AdImmunityConfig): void;` + + Returns the current ad immunity configuration. + duration 0 means the feature is disabled. + `getAdImmunityConfig(): AdImmunityConfig;` + + Returns a boolean value indicating if the user is currently immune to ad breaks + `isAdImmunityActive(): boolean;` + + Immediately ends an ongoing ad immunity period, before it would naturally expire + `endAdImmunity(): void;` + +- ad immunity events to `YospacePlayerEvent` enum +- `getCurrentAdBreakDuration` method. It returns the full duration of the currently playing ad break. +- methods to fetch current time and duration of a vod, including stitched ad duration: `getCurrentTimeWithAds` and `getDurationWithAds`. + +### Fixed + +- prevent multiple ad break finished events + +> > > > > > > 5fc19b3 (chore: add changelog) + ## 2.3.1 - 2024-02-14 ### Removed From 91fbdb10e6ff0fb9945a5f81ac57f41535f15eba Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:57:29 +0100 Subject: [PATCH 09/75] chore: update types --- src/ts/BitmovinYospacePlayerAPI.ts | 2 +- src/ts/InternalBitmovinYospacePlayer.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index 51848f56..53c5578e 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -251,7 +251,7 @@ export interface YospacePlayerEventCallback { export interface AdImmunityConfiguredEvent extends YospaceEventBase { type: YospacePlayerEvent.AdImmunityConfigured; - config: { duration: number }; + config: AdImmunityConfig; } export interface AdImmunityStartedEvent extends YospaceEventBase { diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index a3e73bf4..19951c65 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -72,6 +72,7 @@ import { YospacePolicyErrorEvent, YospaceSourceConfig, AdImmunityStartedEvent, + AdImmunityConfig, } from './BitmovinYospacePlayerAPI'; import { YospacePlayerError } from './YospaceError'; import { @@ -574,7 +575,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.player.setPlaybackSpeed(this.playbackSpeed); } - setAdImmunityConfig(config: any) { + setAdImmunityConfig(config: AdImmunityConfig) { this.adImmunityConfig = config; Logger.log('[BitmovinYospacePlayer] Ad Immunity Configured:', this.adImmunityConfig); this.handleYospaceEvent({ From f99b6db550ec7d9e712a2584c978f8193a9e1682 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:07:59 +0100 Subject: [PATCH 10/75] feat: filter deactivated ad breaks in listing API --- src/ts/InternalBitmovinYospacePlayer.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 19951c65..770949fb 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -1118,10 +1118,14 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { // need to stay in the SDK as there might be AdBreak Impressions and other beacons Yospace might need to trigger. // These ad breaks wouldn't be visible to the user and have a duration of `0`. Yospace's recommendation for us // is to filter out ads with duration = 0. - return this.session - .getAdBreaksByType(BreakType.LINEAR) - .map((adBreak: AdBreak) => this.mapAdBreak(adBreak)) - .filter((adBreak: YospaceAdBreak) => adBreak.duration > 0); + return ( + this.session + .getAdBreaksByType(BreakType.LINEAR) + .map((adBreak: AdBreak) => this.mapAdBreak(adBreak)) + // filter out ad breaks deactivated by ad immunity + .filter((adBreak: YospaceAdBreak) => adBreak.active) + .filter((adBreak: YospaceAdBreak) => adBreak.duration > 0) + ); }, schedule: (adConfig: AdConfig) => { From 2881f00e98e1a854f1f639325bfffc1253fc00b3 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:09:45 +0100 Subject: [PATCH 11/75] fix: use a single way of cleaning up ad immunity --- src/ts/InternalBitmovinYospacePlayer.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 770949fb..a034e44c 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -938,16 +938,12 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.session = null; } - if (this.adImmunityCountDown) { - window.clearTimeout(this.adImmunityCountDown); - this.adImmunityCountDown = null; - } + this.endAdImmunityPeriod(); if (this.dateRangeEmitter) { this.dateRangeEmitter.reset(); } - this.adImmune = false; // should adImmunityConfig be reset here? If yes, it // would require configuration for each new video start this.adParts = []; From 0fcda166b6eb89a053244ea0b3e05dfe56865791 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:42:00 +0100 Subject: [PATCH 12/75] fix: use helper methods for timer calcs --- src/ts/InternalBitmovinYospacePlayer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index a034e44c..3db7b82c 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -623,7 +623,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.adImmunityCountDown = window.setTimeout(() => { this.endAdImmunityPeriod(); - }, this.adImmunityConfig.duration * 1000); + }, toMilliseconds(this.adImmunityConfig.duration)); Logger.log('[BitmovinYospacePlayer] Ad Immunity Started, duration', this.adImmunityConfig.duration); this.handleYospaceEvent({ @@ -1227,7 +1227,9 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { // first few frames of an ad playing before the seek // past the break has time to propagate const adBreakCheckOffset = 500; - const upcomingAdBreak: AdBreak | null = this.session.getAdBreakForPlayhead(event.time * 1000 + adBreakCheckOffset); + const upcomingAdBreak: AdBreak | null = this.session.getAdBreakForPlayhead( + toMilliseconds(event.time) + adBreakCheckOffset + ); // exclude postrolls and unknown break positions from ad immunity to prevent seek loops at end of video if (upcomingAdBreak?.getPosition() !== 'postroll' && upcomingAdBreak?.getPosition() !== 'unknown') { From 16912de3215063f3a54af62e8a57f4f878e67efd Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:22:00 +0100 Subject: [PATCH 13/75] fix: prevent superfluous ad immunity ended events --- src/ts/InternalBitmovinYospacePlayer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 3db7b82c..06301a3d 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -602,10 +602,12 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { private endAdImmunityPeriod() { if (typeof this.adImmunityCountDown === 'number') { window.clearTimeout(this.adImmunityCountDown); + this.adImmunityCountDown = null; } + if (!this.adImmune) return; + this.adImmune = false; - this.adImmunityCountDown = null; Logger.log('[BitmovinYospacePlayer] Ad Immunity Ended'); this.handleYospaceEvent({ timestamp: Date.now(), From 7dc11539b30000b05eb6198f4b4bcd22ef2efa7a Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:22:19 +0100 Subject: [PATCH 14/75] chore: restore demo index.html --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index 279a16ad..f78d46d1 100644 --- a/src/index.html +++ b/src/index.html @@ -577,7 +577,7 @@ log('[BYP] Metadata Parsed: ', cloneDeepSimple(event)); }); - // yospacePlayer.setPolicy(sampleYospacePlayerPolicy); + yospacePlayer.setPolicy(sampleYospacePlayerPolicy); yospacePlayer.on('segmentplayback', function (event) { if (event.mimeType === 'video/mp4') { From 4bebc1daffd6419fb07c02adac1801e15a19d0d9 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:51:28 +0100 Subject: [PATCH 15/75] chore: add documentation to AdImmunityConfig --- src/ts/BitmovinYospacePlayerAPI.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index 53c5578e..1ebccb61 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -263,6 +263,10 @@ export interface AdImmunityEndedEvent extends YospaceEventBase { type: YospacePlayerEvent.AdImmunityEnded; } +/** + * @description Ad Immunity Configuration Object + * @property duration - a number indicating the duration of the ad immunity period. 0 disables the feature. + */ export interface AdImmunityConfig { duration: number; } From 9deeb8a28884a735ac1c39162322c805574d8452 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:58:40 +0100 Subject: [PATCH 16/75] chore: update documentation --- src/ts/BitmovinYospacePlayerAPI.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index 1ebccb61..032c0b91 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -78,17 +78,15 @@ export interface BitmovinYospacePlayerAPI extends PlayerAPI { * Ad breaks played over or seeked past during immunity will be marked * as deactivated, making the user permanently immune to those breaks. * - * duration 0 disables the feature - * - * postrolls are excluded from ad immunity + * Post-rolls are excluded from ad immunity * + * Pre-roll ads are excluded from ad immunity as at least one ad break needs to be + * watched completely */ setAdImmunityConfig(options: AdImmunityConfig): void; /** * Returns the current ad immunity configuration - * - * duration 0 means the feature is disabled */ getAdImmunityConfig(): AdImmunityConfig; From fbbb6cad249863463f68f107fdad793db25961f3 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:03:49 +0100 Subject: [PATCH 17/75] fix: improve precision for deactivating ad breaks --- src/ts/InternalBitmovinYospacePlayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 06301a3d..36b9cfd7 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -412,7 +412,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { const breakStart = this.toMagicTime(toSeconds(adBreak.getStart())); // Check if break is being seeked past and deactivate it - if (breakStart > currentTime && breakStart < time) { + if (breakStart > currentTime && breakStart <= time) { Logger.log('[BitmovinYospacePlayer] Ad Immunity deactivated ad break during seek', adBreak); adBreak.setInactive(); } From 9d75e710bccaac281cb0c908db0057b74e36f9ae Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:05:27 +0100 Subject: [PATCH 18/75] fix: update ad immunity countdown check --- src/ts/InternalBitmovinYospacePlayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 36b9cfd7..daf867f6 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -600,7 +600,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { // Helper private endAdImmunityPeriod() { - if (typeof this.adImmunityCountDown === 'number') { + if (this.adImmunityCountDown !== null) { window.clearTimeout(this.adImmunityCountDown); this.adImmunityCountDown = null; } From 0fc1dc753a4f6a74873d28478dc9fb02f86c6532 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:11:24 +0100 Subject: [PATCH 19/75] feat: make ad break check offset configurable --- src/ts/BitmovinYospacePlayerAPI.ts | 3 +++ src/ts/InternalBitmovinYospacePlayer.ts | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index 032c0b91..ba715fd4 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -264,7 +264,10 @@ export interface AdImmunityEndedEvent extends YospaceEventBase { /** * @description Ad Immunity Configuration Object * @property duration - a number indicating the duration of the ad immunity period. 0 disables the feature. + * @property adBreakCheckOffset - a number indicating how far ahead ad immunity should look for ad breaks + * to skip past, in order to mitigate ad frames being displayed before they have time to be seeked past. */ export interface AdImmunityConfig { duration: number; + adBreakCheckOffset?: number; } diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index daf867f6..5b34bf35 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -151,8 +151,9 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { private startSent: boolean; private lastTimeChangedTime = 0; - private adImmunityConfig = { + private adImmunityConfig: AdImmunityConfig = { duration: 0, // 0 duration = disabled + adBreakCheckOffset: 200, }; private adImmune = false; private adImmunityCountDown: number | null = null; @@ -1228,7 +1229,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { // the offset of 500ms is an attempt to prevent the // first few frames of an ad playing before the seek // past the break has time to propagate - const adBreakCheckOffset = 500; + const adBreakCheckOffset = this.adImmunityConfig.adBreakCheckOffset; const upcomingAdBreak: AdBreak | null = this.session.getAdBreakForPlayhead( toMilliseconds(event.time) + adBreakCheckOffset ); From bfe38c48ec76c0866a4a72bf67bc9e395ca53d73 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 15:29:04 +0100 Subject: [PATCH 20/75] chore: update changelog --- CHANGELOG.md | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 548c64cd..cdf456f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -<<<<<<< HEAD - ### Fixed - Event loop on pre-roll ad end @@ -17,13 +15,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `mode` argument to `getCurrentTime` to enable fetching absolute time including ad durations - `mode` argument to `getDuration` to enable fetching absolute duration including ad durations - -# ||||||| parent of 5fc19b3 (chore: add changelog) - -## 2.4.0 - -### Added - - ad immunity feature: The user will become immune to ad breaks for a duration upon @@ -32,14 +23,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Ad breaks played over or seeked past during immunity will be marked as deactivated, making the user permanently immune to them. - setting duration to 0 disables the feature. + Post-roll ads and ads with unknown positioning are excluded from ad immunity. - postrolls and ads with unknown positioning are excluded from ad immunity. + By default, pre-rolls are also excluded, since the user needs to finish + an ad break to enter an ad immunity period. `setAdImmunityConfig(options: AdImmunityConfig): void;` Returns the current ad immunity configuration. - duration 0 means the feature is disabled. + `getAdImmunityConfig(): AdImmunityConfig;` Returns a boolean value indicating if the user is currently immune to ad breaks @@ -49,14 +41,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). `endAdImmunity(): void;` - ad immunity events to `YospacePlayerEvent` enum -- `getCurrentAdBreakDuration` method. It returns the full duration of the currently playing ad break. -- methods to fetch current time and duration of a vod, including stitched ad duration: `getCurrentTimeWithAds` and `getDurationWithAds`. - -### Fixed - -- prevent multiple ad break finished events - -> > > > > > > 5fc19b3 (chore: add changelog) ## 2.3.1 - 2024-02-14 From cdfc6c577b0b4dc1cafb25bbe772bac70eadd65f Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:02:28 +0100 Subject: [PATCH 21/75] fix: pause before seeking past ad break to prevent ad frames showing --- src/ts/InternalBitmovinYospacePlayer.ts | 43 ++++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 5b34bf35..2014b5a9 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -153,10 +153,10 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { private lastTimeChangedTime = 0; private adImmunityConfig: AdImmunityConfig = { duration: 0, // 0 duration = disabled - adBreakCheckOffset: 200, }; private adImmune = false; private adImmunityCountDown: number | null = null; + private unpauseAfterSeek = false; constructor(containerElement: HTMLElement, player: PlayerAPI, yospaceConfig: YospaceConfiguration = {}) { this.yospaceConfig = yospaceConfig; @@ -577,7 +577,10 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { } setAdImmunityConfig(config: AdImmunityConfig) { - this.adImmunityConfig = config; + this.adImmunityConfig = { + ...this.adImmunityConfig, + ...config, + }; Logger.log('[BitmovinYospacePlayer] Ad Immunity Configured:', this.adImmunityConfig); this.handleYospaceEvent({ timestamp: Date.now(), @@ -1226,10 +1229,10 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { }; private onTimeChanged = (event: TimeChangedEvent) => { - // the offset of 500ms is an attempt to prevent the - // first few frames of an ad playing before the seek - // past the break has time to propagate - const adBreakCheckOffset = this.adImmunityConfig.adBreakCheckOffset; + // the offset is an attempt to prevent the first few frames of an ad + // playing before the seek past the break has time to propagate + const adBreakCheckOffset = + typeof this.adImmunityConfig.adBreakCheckOffset === 'number' ? this.adImmunityConfig.adBreakCheckOffset : 200; const upcomingAdBreak: AdBreak | null = this.session.getAdBreakForPlayhead( toMilliseconds(event.time) + adBreakCheckOffset ); @@ -1238,7 +1241,16 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { if (upcomingAdBreak?.getPosition() !== 'postroll' && upcomingAdBreak?.getPosition() !== 'unknown') { // Seek past previously deactivated ad breaks if (upcomingAdBreak && !upcomingAdBreak.isActive()) { - Logger.log('[BitmovinYospacePlayer] - Ad Immunity seeking past deactivated ad break'); + this.suppressedEventsController.add( + this.player.exports.PlayerEvent.Paused, + this.player.exports.PlayerEvent.Seek, + this.player.exports.PlayerEvent.Seeked + ); + + this.player.pause(); + this.unpauseAfterSeek = true; + + Logger.log('[BitmovinYospacePlayer] - Ad Immunity pausing and seeking past deactivated ad break'); this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); // do not propagate time to the rest of the app, we want to seek past it @@ -1249,7 +1261,16 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { if (upcomingAdBreak && this.adImmune) { upcomingAdBreak.setInactive(); - Logger.log('[BitmovinYospacePlayer] - Ad Immunity - seeking past and deactivating ad break'); + this.suppressedEventsController.add( + this.player.exports.PlayerEvent.Paused, + this.player.exports.PlayerEvent.Seek, + this.player.exports.PlayerEvent.Seeked + ); + + this.player.pause(); + this.unpauseAfterSeek = true; + + Logger.log('[BitmovinYospacePlayer] - Ad Immunity pausing, seeking past and deactivating ad break'); this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); // do not propagate time to the rest of the app, we want to seek past it @@ -1300,6 +1321,12 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { private onSeeked = (event: SeekEvent) => { Logger.log('[BitmovinYospacePlayer] - sending YospaceAdManagement.PlayerEvent.SEEK (from Seeked player event)'); + + if (this.unpauseAfterSeek) { + this.unpauseAfterSeek = false; + this.play(); + } + this.session.onPlayerEvent(YsPlayerEvent.SEEK, toMilliseconds(this.player.getCurrentTime())); if (!this.suppressedEventsController.isSuppressed(this.player.exports.PlayerEvent.Seeked)) { From ddbb29a9b8b76b4bb5c0bec3e3a9abbbf3fc03c1 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:42:51 +0100 Subject: [PATCH 22/75] chore: update adBreakCheckOffset default value --- src/ts/InternalBitmovinYospacePlayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 2014b5a9..c4120ca3 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -1232,7 +1232,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { // the offset is an attempt to prevent the first few frames of an ad // playing before the seek past the break has time to propagate const adBreakCheckOffset = - typeof this.adImmunityConfig.adBreakCheckOffset === 'number' ? this.adImmunityConfig.adBreakCheckOffset : 200; + typeof this.adImmunityConfig.adBreakCheckOffset === 'number' ? this.adImmunityConfig.adBreakCheckOffset : 300; const upcomingAdBreak: AdBreak | null = this.session.getAdBreakForPlayhead( toMilliseconds(event.time) + adBreakCheckOffset ); From 2de540a209288737cb8b2ab1fb162ede6ef9e9ed Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:43:23 +0100 Subject: [PATCH 23/75] chore: remove code duplication --- src/ts/InternalBitmovinYospacePlayer.ts | 37 ++++++++++++------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index c4120ca3..9d06a34e 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -1228,6 +1228,19 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { } }; + private performBreakSkip(seekTarget: number) { + this.suppressedEventsController.add( + this.player.exports.PlayerEvent.Paused, + this.player.exports.PlayerEvent.Seek, + this.player.exports.PlayerEvent.Seeked + ); + + this.player.pause(); + this.unpauseAfterSeek = true; + + this.player.seek(seekTarget); + } + private onTimeChanged = (event: TimeChangedEvent) => { // the offset is an attempt to prevent the first few frames of an ad // playing before the seek past the break has time to propagate @@ -1241,17 +1254,9 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { if (upcomingAdBreak?.getPosition() !== 'postroll' && upcomingAdBreak?.getPosition() !== 'unknown') { // Seek past previously deactivated ad breaks if (upcomingAdBreak && !upcomingAdBreak.isActive()) { - this.suppressedEventsController.add( - this.player.exports.PlayerEvent.Paused, - this.player.exports.PlayerEvent.Seek, - this.player.exports.PlayerEvent.Seeked - ); - - this.player.pause(); - this.unpauseAfterSeek = true; - Logger.log('[BitmovinYospacePlayer] - Ad Immunity pausing and seeking past deactivated ad break'); - this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); + + this.performBreakSkip(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); // do not propagate time to the rest of the app, we want to seek past it return; @@ -1261,17 +1266,9 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { if (upcomingAdBreak && this.adImmune) { upcomingAdBreak.setInactive(); - this.suppressedEventsController.add( - this.player.exports.PlayerEvent.Paused, - this.player.exports.PlayerEvent.Seek, - this.player.exports.PlayerEvent.Seeked - ); - - this.player.pause(); - this.unpauseAfterSeek = true; - Logger.log('[BitmovinYospacePlayer] - Ad Immunity pausing, seeking past and deactivating ad break'); - this.player.seek(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); + + this.performBreakSkip(toSeconds(upcomingAdBreak.getStart() + upcomingAdBreak.getDuration())); // do not propagate time to the rest of the app, we want to seek past it return; From 2c88fb1315dd50e70c823973a167aee1373277e4 Mon Sep 17 00:00:00 2001 From: dweinber Date: Wed, 10 Apr 2024 09:39:49 +0200 Subject: [PATCH 24/75] 2.4.0-rc.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 97fafc0b..99f2e295 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitmovin/player-integration-yospace", - "version": "2.3.1", + "version": "2.4.0-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bitmovin/player-integration-yospace", - "version": "2.3.1", + "version": "2.4.0-rc.0", "license": "MIT", "dependencies": { "@yospace/admanagement-sdk": "3.6.0", diff --git a/package.json b/package.json index 39e096bb..ff473acd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitmovin/player-integration-yospace", - "version": "2.3.1", + "version": "2.4.0-rc.0", "description": "Yospace integration for the Bitmovin Player", "main": "./dist/js/bitmovin-player-yospace.js", "types": "./dist/js/main.d.ts", From 602c88560539d14a95163c323a270f3ab1f56aae Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:54:47 +0200 Subject: [PATCH 25/75] fix: remove undesired event suppression This fixes an issue with ads not being properly deactivated when seeked past --- src/ts/InternalBitmovinYospacePlayer.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index 9d06a34e..a9d60cc5 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -1229,12 +1229,6 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { }; private performBreakSkip(seekTarget: number) { - this.suppressedEventsController.add( - this.player.exports.PlayerEvent.Paused, - this.player.exports.PlayerEvent.Seek, - this.player.exports.PlayerEvent.Seeked - ); - this.player.pause(); this.unpauseAfterSeek = true; From b53de5f3795866244d4167ccfad5143e71653010 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:03:13 +0200 Subject: [PATCH 26/75] feat: add startAdImmunity public method --- CHANGELOG.md | 3 +++ src/ts/BitmovinYospacePlayer.ts | 4 ++++ src/ts/BitmovinYospacePlayerAPI.ts | 6 ++++++ src/ts/InternalBitmovinYospacePlayer.ts | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf456f6..e3afc5da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Returns a boolean value indicating if the user is currently immune to ad breaks `isAdImmunityActive(): boolean;` + Immediately starts an ad immunity period, if ad immunity config exists. This method does nothing if ad immunity is already active. + `startAdImmunity(): void;` + Immediately ends an ongoing ad immunity period, before it would naturally expire `endAdImmunity(): void;` diff --git a/src/ts/BitmovinYospacePlayer.ts b/src/ts/BitmovinYospacePlayer.ts index c3e2f67f..a6268133 100644 --- a/src/ts/BitmovinYospacePlayer.ts +++ b/src/ts/BitmovinYospacePlayer.ts @@ -585,6 +585,10 @@ export class BitmovinYospacePlayer implements BitmovinYospacePlayerAPI { return this.player.isAdImmunityActive(); } + startAdImmunity() { + this.player.startAdImmunity(); + } + endAdImmunity() { this.player.endAdImmunity(); } diff --git a/src/ts/BitmovinYospacePlayerAPI.ts b/src/ts/BitmovinYospacePlayerAPI.ts index ba715fd4..9c439264 100644 --- a/src/ts/BitmovinYospacePlayerAPI.ts +++ b/src/ts/BitmovinYospacePlayerAPI.ts @@ -95,6 +95,12 @@ export interface BitmovinYospacePlayerAPI extends PlayerAPI { */ isAdImmunityActive(): boolean; + /** + * Immediately starts an ad immunity period, if ad immunity config exists. This method does nothing if ad immunity is already active. + * To refresh an ad immunity period, first call endAdImmunity followed by startAdImmunity. + */ + startAdImmunity(): void; + /** * Immediately ends an ongoing ad immunity period, before it would naturally expire */ diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index a9d60cc5..a51de534 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -601,6 +601,10 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { this.endAdImmunityPeriod(); } + startAdImmunity() { + this.startAdImmunityPeriod(); + } + // Helper private endAdImmunityPeriod() { From 3a8888d28e252f4914232c7a47e9b3142762a670 Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:07:29 +0200 Subject: [PATCH 27/75] fix: update adBreakCheckOffset to seconds --- src/ts/InternalBitmovinYospacePlayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index a51de534..f7c8f473 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -1243,9 +1243,9 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { // the offset is an attempt to prevent the first few frames of an ad // playing before the seek past the break has time to propagate const adBreakCheckOffset = - typeof this.adImmunityConfig.adBreakCheckOffset === 'number' ? this.adImmunityConfig.adBreakCheckOffset : 300; + typeof this.adImmunityConfig.adBreakCheckOffset === 'number' ? this.adImmunityConfig.adBreakCheckOffset : 0.3; const upcomingAdBreak: AdBreak | null = this.session.getAdBreakForPlayhead( - toMilliseconds(event.time) + adBreakCheckOffset + toMilliseconds(event.time) + toMilliseconds(adBreakCheckOffset) ); // exclude postrolls and unknown break positions from ad immunity to prevent seek loops at end of video From 3e570e6a80c3c5d986960440762ba372e9210a6d Mon Sep 17 00:00:00 2001 From: Martin <901824+martinstark@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:09:17 +0200 Subject: [PATCH 28/75] fix: update filter syntax --- src/ts/InternalBitmovinYospacePlayer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ts/InternalBitmovinYospacePlayer.ts b/src/ts/InternalBitmovinYospacePlayer.ts index f7c8f473..e03aae1b 100644 --- a/src/ts/InternalBitmovinYospacePlayer.ts +++ b/src/ts/InternalBitmovinYospacePlayer.ts @@ -1129,8 +1129,7 @@ export class InternalBitmovinYospacePlayer implements BitmovinYospacePlayerAPI { .getAdBreaksByType(BreakType.LINEAR) .map((adBreak: AdBreak) => this.mapAdBreak(adBreak)) // filter out ad breaks deactivated by ad immunity - .filter((adBreak: YospaceAdBreak) => adBreak.active) - .filter((adBreak: YospaceAdBreak) => adBreak.duration > 0) + .filter((adBreak: YospaceAdBreak) => adBreak.active && adBreak.duration > 0) ); }, From 95029771c62e504e4083e5c547d1bf7072ed2fb2 Mon Sep 17 00:00:00 2001 From: dweinber Date: Tue, 23 Apr 2024 17:14:34 +0200 Subject: [PATCH 29/75] 2.4.0-rc.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 99f2e295..b9832afc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitmovin/player-integration-yospace", - "version": "2.4.0-rc.0", + "version": "2.4.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@bitmovin/player-integration-yospace", - "version": "2.4.0-rc.0", + "version": "2.4.0-rc.1", "license": "MIT", "dependencies": { "@yospace/admanagement-sdk": "3.6.0", diff --git a/package.json b/package.json index ff473acd..68808499 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitmovin/player-integration-yospace", - "version": "2.4.0-rc.0", + "version": "2.4.0-rc.1", "description": "Yospace integration for the Bitmovin Player", "main": "./dist/js/bitmovin-player-yospace.js", "types": "./dist/js/main.d.ts", From f74a9804e532d895c6edd87b381c5611c014b398 Mon Sep 17 00:00:00 2001 From: Daniel Weinberger Date: Mon, 10 Jun 2024 17:06:22 +0200 Subject: [PATCH 30/75] Update prettier config --- .prettierrc.json | 2 +- src/index.html | 72 +++++++++++------------------------------------- 2 files changed, 17 insertions(+), 57 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 2cee17d2..5c0b477b 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,5 +1,5 @@ { "singleQuote": true, "semi": true, - "printWidth": 120 + "printWidth": 140 } diff --git a/src/index.html b/src/index.html index f78d46d1..dcf09630 100644 --- a/src/index.html +++ b/src/index.html @@ -27,47 +27,20 @@ - + - - - - - + + + + + - - - + + + @@ -141,9 +114,7 @@