diff --git a/.changeset/sweet-candles-warn.md b/.changeset/sweet-candles-warn.md new file mode 100644 index 00000000..8eda3adb --- /dev/null +++ b/.changeset/sweet-candles-warn.md @@ -0,0 +1,5 @@ +--- +"@theoplayer/gemius-connector-web": minor +--- + +Initial release. diff --git a/README.md b/README.md index e30f04b7..2b8e841d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Using the available connectors allows you to augment the features delivered thro | CMCD | [![@theoplayer/cmcd-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fcmcd-connector-web?label=%40theoplayer%2Fcmcd-connector-web)](https://npmjs.com/package/@theoplayer/cmcd-connector-web) | [cmcd](https://github.com/THEOplayer/web-connectors/tree/main/cmcd) | | Comscore | [![@theoplayer/comscore-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fcomscore-connector-web?label=%40theoplayer%2Fcomscore-connector-web)](https://npmjs.com/package/@theoplayer/comscore-connector-web) | [comscore](https://github.com/THEOplayer/web-connectors/tree/main/comscore) | | Conviva | [![@theoplayer/conviva-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fconviva-connector-web?label=%40theoplayer%2Fconviva-connector-web)](https://npmjs.com/package/@theoplayer/conviva-connector-web) | [conviva](https://github.com/THEOplayer/web-connectors/tree/main/conviva) | +| Gemius | [![@theoplayer/gemius-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fgemius-connector-web?label=%40theoplayer%2Fgemius-connector-web)](https://npmjs.com/package/@theoplayer/gemius-connector-web) | [gemius](https://github.com/THEOplayer/web-connectors/tree/main/gemius) | | Nielsen | [![@theoplayer/nielsen-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fnielsen-connector-web?label=%40theoplayer%2Fnielsen-connector-web)](https://npmjs.com/package/@theoplayer/nielsen-connector-web) | [nielsen](https://github.com/THEOplayer/web-connectors/tree/main/nielsen) | | Yospace | [![@theoplayer/yospace-connector-web](https://img.shields.io/npm/v/%40theoplayer%2Fyospace-connector-web?label=%40theoplayer%2Fyospace-connector-web)](https://npmjs.com/package/@theoplayer/yospace-connector-web) | [yospace](https://github.com/THEOplayer/web-connectors/tree/main/yospace) | diff --git a/gemius/.gitignore b/gemius/.gitignore new file mode 100644 index 00000000..92b3d00f --- /dev/null +++ b/gemius/.gitignore @@ -0,0 +1,10 @@ +# Node artifact files +node_modules/ +lib/ +dist/ + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db diff --git a/gemius/README.md b/gemius/README.md new file mode 100644 index 00000000..d164f6ae --- /dev/null +++ b/gemius/README.md @@ -0,0 +1,133 @@ +# gemius-connector-web + +The Gemius connector provides a Gemius integration for THEOplayer. + +## Installation + +```sh +npm install @theoplayer/gemius-connector-web +``` + +Load the gplayer.js library from Gemius. There are two options to to this: either you do it synchronously: + +```html + + +``` + +... or asynchronously + +```html + + +``` + +* Add as an ES2015 module + +```html + +``` + +## Updating program parameters + +If the program parameters changed during playback, you can update it with: + +```javascript +const newProgramParameters = { ... }; + +gemiusConnector.update(newProgramParameters); +``` diff --git a/gemius/package.json b/gemius/package.json new file mode 100644 index 00000000..7f989521 --- /dev/null +++ b/gemius/package.json @@ -0,0 +1,47 @@ +{ + "name": "@theoplayer/gemius-connector-web", + "version": "0.0.1", + "description": "A connector implementing Gemius with THEOplayer", + "main": "dist/gemius-connector.umd.js", + "module": "dist/gemius-connector.esm.js", + "types": "dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/gemius-connector.esm.js", + "require": "./dist/gemius-connector.umd.js" + }, + "./dist/*": "./dist/*", + "./package": "./package.json", + "./package.json": "./package.json" + }, + "scripts": { + "clean": "rimraf lib dist", + "bundle": "rollup -c rollup.config.mjs", + "watch": "npm run bundle -- --watch", + "build": "npm run clean && npm run bundle", + "serve": "http-server ./.. -o /gemius/test/pages/main_esm.html", + "test": "echo \"No tests yet\"" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/THEOplayer/web-connectors.git", + "directory": "gemius" + }, + "author": "THEO Technologies NV", + "license": "MIT", + "bugs": { + "url": "https://github.com/THEOplayer/web-connectors/issues" + }, + "homepage": "https://github.com/THEOplayer/web-connectors/tree/main/gemius#readme", + "files": [ + "dist/", + "CHANGELOG.md", + "README.md", + "LICENSE.md", + "package.json" + ], + "peerDependencies": { + "theoplayer": "^7.0.0" + } +} diff --git a/gemius/rollup.config.mjs b/gemius/rollup.config.mjs new file mode 100644 index 00000000..be0c2f8d --- /dev/null +++ b/gemius/rollup.config.mjs @@ -0,0 +1,15 @@ +import fs from "node:fs"; +import {getSharedBuildConfiguration} from "../tools/build.mjs"; + + +const {version} = JSON.parse(fs.readFileSync("./package.json", "utf8")); + +const fileName = "gemius-connector"; +const globalName = "THEOplayerGemiusConnector"; + +const banner = ` +/** + * THEOplayer Gemius Web Connector v${version} + */`.trim(); + +export default getSharedBuildConfiguration({fileName, globalName, banner}); diff --git a/gemius/src/gemius/Gemius.d.ts b/gemius/src/gemius/Gemius.d.ts new file mode 100644 index 00000000..e07a457b --- /dev/null +++ b/gemius/src/gemius/Gemius.d.ts @@ -0,0 +1,108 @@ +import { + NewAdAdditionalParameters, + NewProgramAdditionalParameters, + PlayAdEventAdditionalParameters, + PlayProgramEventAdditionalParameters, + PlayerAdditionalParameters, + ListEventAdditionalParameters, + ChangeResolutionEventAddtionalParameters, + ChangeVolumeEventAddtionalParameters, + ChangeQualityEventAddtionalParameters +} from '../integration/GemiusParameters'; +import { + PlayEvent, + ListEvent, + ChangeResolutionEvent, + ChangeVolumeEvent, + ChangeQualityEvent, + BreakEvent, + BasicEvent +} from '../integration/GemiusEvents'; + +declare global { + interface Window { + GemiusPlayer: typeof GemiusPlayer; + } +} + +export class GemiusPlayer { + constructor(playerID: string, gemiusID: string, additionalParameters?: PlayerAdditionalParameters); + newProgram(programID: string, additionalParameters: NewProgramAdditionalParameters); + newAd(adId: string, additionalParameters?: NewAdAdditionalParameters); + + // List event + programEvent( + programID: string, + offset: number, + event: ListEvent, + additionalParameters?: ListEventAdditionalParameters + ); + + // Change resolution events + programEvent( + programID: string, + offset: number, + event: ChangeResolutionEvent, + additionalParameters?: ChangeResolutionEventAddtionalParameters + ); + adEvent( + programID: string, + offset: number, + event: ChangeResolutionEvent, + additionalParameters?: ChangeResolutionEventAddtionalParameters + ); + + // Change volume events + programEvent( + programID: string, + offset: number, + event: ChangeVolumeEvent, + additionalParameters?: ChangeVolumeEventAddtionalParameters + ); + adEvent( + programID: string, + adID: string, + offset: number, + event: ChangeVolumeEvent, + additionalParameters?: ChangeVolumeEventAddtionalParameters + ); + + // Change quality events + programEvent( + programID: string, + offset: number, + event: ChangeQualityEvent, + additionalParameters?: ChangeQualityEventAddtionalParameters + ); + adEvent( + programID: string, + adID: string, + offset: number, + event: ChangeQualityEvent, + additionalParameters?: ChangeQualityEventAddtionalParameters + ); + + // Play event + adEvent( + programID: string, + adID: string, + offset: number, + event: PlayEvent, + additionalParameters: PlayAdEventAdditionalParameters + ); + programEvent( + programID: string, + offset: number, + event: PlayEvent, + additionalParameters: PlayProgramEventAdditionalParameters + ); + + // Break event (program only) + programEvent(programID: string, offset: number, event: BreakEvent); + + // Basic events for both program and ads that don't require additional parameters + adEvent(programID: string, adID: string, offset: number, event: BasicEvent); + programEvent(programID: string, offset: number, event: BasicEvent); + + dispose(); +} diff --git a/gemius/src/index.ts b/gemius/src/index.ts new file mode 100644 index 00000000..09a8e697 --- /dev/null +++ b/gemius/src/index.ts @@ -0,0 +1,3 @@ +export { GemiusConnector } from './integration/GemiusConnector'; +export * from './integration/GemiusConfiguration'; +export * from './integration/GemiusParameters'; diff --git a/gemius/src/integration/GemiusConfiguration.ts b/gemius/src/integration/GemiusConfiguration.ts new file mode 100644 index 00000000..677a3e07 --- /dev/null +++ b/gemius/src/integration/GemiusConfiguration.ts @@ -0,0 +1,4 @@ +export interface GemiusConfiguration { + gemiusID: string; + debug?: boolean; +} diff --git a/gemius/src/integration/GemiusConnector.ts b/gemius/src/integration/GemiusConnector.ts new file mode 100644 index 00000000..b0ec4cb6 --- /dev/null +++ b/gemius/src/integration/GemiusConnector.ts @@ -0,0 +1,34 @@ +import { ChromelessPlayer } from 'theoplayer'; +import { GemiusTHEOIntegration } from './GemiusTHEOIntegration'; +import { GemiusConfiguration } from './GemiusConfiguration'; +import { GemiusProgramParameters } from './GemiusParameters'; + +export class GemiusConnector { + private gemiusIntegration: GemiusTHEOIntegration; + + /** + * Constructor for the THEOplayer Gemius connector + * @param player a THEOplayer instance reference + * @param configuration a configuration object for the Gemius connector + * @param programParameters the parameters associated with the first source that will be set to the player + * @returns + */ + constructor( + player: ChromelessPlayer, + configuration: GemiusConfiguration, + programParameters: GemiusProgramParameters + ) { + this.gemiusIntegration = new GemiusTHEOIntegration(player, configuration, programParameters); + } + + update(programParameters: GemiusProgramParameters) { + this.gemiusIntegration.update(programParameters); + } + + /** + * Destroy + */ + destroy(): void { + this.gemiusIntegration.destroy(); + } +} diff --git a/gemius/src/integration/GemiusEvents.ts b/gemius/src/integration/GemiusEvents.ts new file mode 100644 index 00000000..dba482c4 --- /dev/null +++ b/gemius/src/integration/GemiusEvents.ts @@ -0,0 +1,21 @@ +export type PlayEvent = 'play'; +export type BreakEvent = 'break'; +export type ChangeResolutionEvent = 'chngRes'; +export type ChangeVolumeEvent = 'chngVol'; +export type ChangeQualityEvent = 'chngQual'; + +export enum ListEvent { + NEXT = 'next', + PREVIOUS = 'prev' +} + +export enum BasicEvent { + STOP = 'stop', + PAUSE = 'pause', + BUFFER = 'buffer', + SEEK = 'seek', + COMPLETE = 'complete', + CLOSE = 'close', + SKIP = 'skip', + NEXT = 'next' +} diff --git a/gemius/src/integration/GemiusParameters.ts b/gemius/src/integration/GemiusParameters.ts new file mode 100644 index 00000000..ad51cdeb --- /dev/null +++ b/gemius/src/integration/GemiusParameters.ts @@ -0,0 +1,117 @@ +export enum ProgramType { + AUDIO = 'audio', + VIDEO = 'video' +} + +export enum TransmissionType { + ON_DEMAND = 1, + BROADCAST_LIVE_TIMESHIFT = 2 +} + +export enum ProgramGenre { + LIVE = 1, + FILM = 2, + SERIES_VLOG = 3, + PROGRAM = 4, + MUSIC = 5, + TRAILER_TEASER = 6 +} + +export enum AdType { + BREAK = 'break', + PROMO = 'promo', + SPOT = 'spot', + SPONSOR = 'sponsor' +} + +export enum AdFormat { + VIDEO = 1, + AUDIO = 2 +} + +export interface PlayerAdditionalParameters { + currentDomain?: string; + volume?: number; + resolution?: string; +} + +export interface NewProgramAdditionalParameters { + programName: string; + programDuration: number; + programType: ProgramType; + transmissionType?: TransmissionType; + transmissionChannel?: string; + transmissionStartTime?: EpochTimeStamp; + programGenre?: ProgramGenre; + programThematicCategory?: string; // TODO check separate document for details + series?: string; + programSeason?: string; + programPartialName?: string; + programProducer?: string; + typology?: string; + premiereDate?: string; + externalPremiereDate?: string; + quality?: string; + resolution?: string; + volume?: number; + customAttributes?: { + [key: string]: string; + }; +} + +export interface NewAdAdditionalParameters { + adName?: string; + adDuration?: number; + adType?: AdType; + campaignClassification?: string; + adFormat?: AdFormat; + quality?: string; + resolution?: string; + volume?: number; + customAttributes?: { + [key: string]: string; + }; +} + +export interface PlayAdEventAdditionalParameters { + autoPlay?: boolean; + adPosition?: number; + breakSize?: number; + resolution?: string; + volume?: number; + adDuration?: number; + customAttributes?: { + [key: string]: string; + }; +} + +export interface PlayProgramEventAdditionalParameters { + autoPlay?: boolean; + partID?: number; + resolution?: string; + volume?: number; + programDuration?: number; + customAttributes?: { + [key: string]: string; + }; +} + +export interface ListEventAdditionalParameters { + listID?: number; +} + +export interface ChangeResolutionEventAddtionalParameters { + resolution?: string; +} + +export interface ChangeVolumeEventAddtionalParameters { + volume?: number; +} + +export interface ChangeQualityEventAddtionalParameters { + quality?: string; +} + +export interface GemiusProgramParameters extends NewProgramAdditionalParameters { + programID: string; +} diff --git a/gemius/src/integration/GemiusTHEOIntegration.ts b/gemius/src/integration/GemiusTHEOIntegration.ts new file mode 100644 index 00000000..bc7bbb6b --- /dev/null +++ b/gemius/src/integration/GemiusTHEOIntegration.ts @@ -0,0 +1,325 @@ +import { + Ad, + AdBreak, + AdBreakEvent, + AdEvent, + AdSkipEvent, + AddTrackEvent, + ChromelessPlayer, + EndedEvent, + GoogleImaAd, + MediaTrack, + PauseEvent, + PlayEvent, + PlayingEvent, + QualityEvent, + RemoveTrackEvent, + SeekingEvent, + SourceChangeEvent, + VideoQuality, + VolumeChangeEvent, + WaitingEvent +} from 'theoplayer'; +import type { GemiusPlayer } from '../gemius/Gemius'; +import { GemiusConfiguration } from './GemiusConfiguration'; +import { AdType, GemiusProgramParameters } from './GemiusParameters'; +import { Logger } from '../utils/Logger'; +import { BasicEvent } from './GemiusEvents'; + +const THEOPLAYER_ID = 'THEOplayer'; +const DEFAULT_AD_ID = 'PLACEHOLDER_ID'; + +export class GemiusTHEOIntegration { + private player: ChromelessPlayer; + private logger: Logger; + private gemiusPlayer: GemiusPlayer; + private programParameters: GemiusProgramParameters; + + private partCount: number = 1; + private adCount: number = 1; + private currentAd: Ad | undefined; + + constructor( + player: ChromelessPlayer, + configuration: GemiusConfiguration, + programParameters: GemiusProgramParameters + ) { + this.player = player; + this.logger = new Logger(Boolean(configuration.debug)); + this.gemiusPlayer = new window.GemiusPlayer(THEOPLAYER_ID, configuration.gemiusID, {}); + this.programParameters = programParameters; + this.addListeners(); + } + + public update(programParameters: GemiusProgramParameters) { + this.programParameters = programParameters; + } + + public destroy() { + this.removeListeners(); + this.gemiusPlayer.dispose(); + } + + private addListeners(): void { + this.player.addEventListener('sourcechange', this.onSourceChange); + this.player.addEventListener('playing', this.onFirstPlaying); + this.player.addEventListener('play', this.onPlay); + this.player.addEventListener('pause', this.onPause); + this.player.addEventListener('waiting', this.onWaiting); + this.player.addEventListener('seeking', this.onSeeking); + this.player.addEventListener('ended', this.onEnded); + this.player.addEventListener('volumechange', this.onVolumeChange); + this.player.videoTracks.addEventListener('addtrack', this.onAddTrack); + this.player.videoTracks.addEventListener('removetrack', this.onRemoveTrack); + if (this.player.ads) { + this.player.ads.addEventListener('adbreakbegin', this.onAdBreakBegin); + this.player.ads.addEventListener('adbreakend', this.onAdBreakEnd); + this.player.ads.addEventListener('adbegin', this.onAdBegin); + this.player.ads.addEventListener('adend', this.onAdEnd); + this.player.ads.addEventListener('adskip', this.onAdSkip); + } + } + + private removeListeners(): void { + this.player.removeEventListener('sourcechange', this.onSourceChange); + this.player.removeEventListener('playing', this.onFirstPlaying); + this.player.removeEventListener('play', this.onPlay); + this.player.removeEventListener('pause', this.onPause); + this.player.removeEventListener('waiting', this.onWaiting); + this.player.removeEventListener('seeking', this.onSeeking); + this.player.removeEventListener('ended', this.onEnded); + this.player.removeEventListener('volumechange', this.onVolumeChange); + this.player.videoTracks.removeEventListener('addtrack', this.onAddTrack); + this.player.videoTracks.removeEventListener('removetrack', this.onRemoveTrack); + if (this.player.ads) { + this.player.ads.removeEventListener('adbreakbegin', this.onAdBreakBegin); + this.player.ads.removeEventListener('adbreakend', this.onAdBreakEnd); + this.player.ads.removeEventListener('adbegin', this.onAdBegin); + this.player.ads.removeEventListener('adend', this.onAdEnd); + this.player.ads.removeEventListener('adskip', this.onAdSkip); + } + } + + private onSourceChange = (event: SourceChangeEvent) => { + this.logger.log(event); + if (!this.programParameters) { + console.log(`[GEMIUS] No program parameters were provdided`); + return; + } + this.reportBasicEvent(BasicEvent.CLOSE); + if (!event.source) { + // TODO handle some clear source flow + return; + } + this.partCount = 1; + this.currentAd = undefined; + + const { programID, customAttributes, ...additionalParameters } = this.programParameters; + this.gemiusPlayer.newProgram(programID, { ...additionalParameters, ...customAttributes }); + }; + + private onFirstPlaying = (event: PlayingEvent) => { + this.logger.log(event); + const { programID } = this.programParameters; + const computedVolume = this.player.muted ? -1 : this.player.volume * 100; + if (this.currentAd) { + const { id, adBreak, duration } = this.currentAd; + const { timeOffset, ads } = adBreak; + const normalizedTimeOffset = this.normalizeTime(timeOffset); + this.gemiusPlayer.adEvent(programID, id ?? DEFAULT_AD_ID, normalizedTimeOffset, 'play', { + autoPlay: true, + adPosition: this.adCount, + breakSize: ads?.length, + // resolution: `AxB`, TODO + volume: computedVolume, + adDuration: duration + }); + } else { + if (this.player.ads?.scheduledAdBreaks.some((adBreak) => adBreak.timeOffset === 0)) return; + const offset = this.player.currentTime < 0.5 ? 0 : this.player.currentTime; + this.gemiusPlayer.programEvent(programID, offset, 'play', { + autoPlay: this.player.autoplay, + partID: this.partCount, + // resolution: `AxB`; TODO + volume: computedVolume, + programDuration: this.programParameters.programDuration + }); + } + this.player.removeEventListener('playing', this.onFirstPlaying); + }; + + private onPause = (event: PauseEvent) => { + this.logger.log(event); + this.reportBasicEvent(BasicEvent.PAUSE); + }; + + private onPlay = (event: PlayEvent) => { + this.logger.log(event); + const { programID } = this.programParameters; + const computedVolume = this.player.muted ? -1 : this.player.volume * 100; + if (this.currentAd) { + const { id, adBreak, duration } = this.currentAd; + const { timeOffset, ads } = adBreak; + const normalizedTimeOffset = this.normalizeTime(timeOffset); + this.gemiusPlayer.adEvent(programID, id ?? DEFAULT_AD_ID, normalizedTimeOffset, 'play', { + autoPlay: true, + adPosition: this.adCount, + breakSize: ads?.length, + // resolution: `AxB`, TODO + volume: computedVolume, + adDuration: duration + }); + } else { + if (this.player.ads?.scheduledAdBreaks.some((adBreak) => adBreak.timeOffset === 0)) return; + const offset = this.player.currentTime < 0.5 ? 0 : this.player.currentTime; + this.gemiusPlayer.programEvent(programID, offset, 'play', { + autoPlay: this.player.autoplay, + partID: this.partCount, + // resolution: `AxB`; TODO + volume: computedVolume, + programDuration: this.programParameters.programDuration + }); + } + }; + + private onWaiting = (event: WaitingEvent) => { + this.logger.log(event); + this.reportBasicEvent(BasicEvent.BUFFER); + }; + + private onSeeking = (event: SeekingEvent) => { + this.logger.log(event); + this.reportBasicEvent(BasicEvent.SEEK); + }; + + private onEnded = (event: EndedEvent) => { + this.logger.log(event); + this.reportBasicEvent(BasicEvent.COMPLETE); + }; + + private onVolumeChange = (event: VolumeChangeEvent) => { + this.logger.log(event); + const { volume } = event; + const { programID } = this.programParameters; + const computedVolume = this.player.muted ? -1 : volume * 100; + if (this.currentAd) { + const { id, adBreak } = this.currentAd; + const { timeOffset } = adBreak; + const normalizedTimeOffset = this.normalizeTime(timeOffset); + this.gemiusPlayer.adEvent(programID, id ?? DEFAULT_AD_ID, normalizedTimeOffset, 'chngVol', { + volume: computedVolume + }); // TODO make SSAI ready + } else { + const { currentTime } = this.player; + this.gemiusPlayer.programEvent(programID, currentTime, 'chngVol', { volume: computedVolume }); + } + }; + + private onAddTrack = (event: AddTrackEvent) => { + const videoTrack = event.track as MediaTrack; + videoTrack.addEventListener('activequalitychanged', this.onActiveQualityChanged); + }; + + private onRemoveTrack = (event: RemoveTrackEvent) => { + const videoTrack = event.track as MediaTrack; + videoTrack.removeEventListener('activequalitychanged', this.onActiveQualityChanged); + }; + + private onActiveQualityChanged = (event: QualityEvent<'activequalitychanged'>) => { + this.logger.log(event); + const { quality } = event; + const videoQuality = quality as VideoQuality; + const { width, height } = videoQuality; + const { programID } = this.programParameters; + const { currentTime } = this.player; + this.gemiusPlayer.programEvent(programID, currentTime, 'chngQual', { quality: `${width}x${height}` }); + }; + + private onAdBreakBegin = (event: AdBreakEvent<'adbreakbegin'>) => { + this.logger.log(event); + const { programID } = this.programParameters; + const { adBreak } = event; + const { timeOffset } = adBreak; + const normalizedTimeOffset = this.normalizeTime(timeOffset); + this.gemiusPlayer.programEvent(programID, normalizedTimeOffset, 'break'); + this.player.removeEventListener('playing', this.onFirstPlaying); + this.player.addEventListener('playing', this.onFirstPlaying); + }; + + private onAdBreakEnd = (event: AdBreakEvent<'adbreakend'>) => { + this.logger.log(event); + this.adCount = 1; + const { adBreak } = event; + const { timeOffset } = adBreak; + if (!this.isPreRoll(adBreak)) this.partCount++; + const { programID, customAttributes, ...additionalParameters } = this.programParameters; + this.gemiusPlayer.newProgram(programID, { ...additionalParameters, ...customAttributes }); + this.player.removeEventListener('playing', this.onFirstPlaying); + if (timeOffset === 0) this.player.addEventListener('playing', this.onFirstPlaying); + }; + + private onAdBegin = (event: AdEvent<'adbegin'>) => { + this.logger.log(event); + const { ad } = event; + this.currentAd = ad; + const { id, duration, width, height } = ad; + const { clientWidth, clientHeight } = this.player.element; + this.gemiusPlayer.newAd(id ?? DEFAULT_AD_ID, { + adName: ad.integration?.includes('google') ? (ad as GoogleImaAd).title ?? '' : '', + adDuration: duration, + adType: AdType.BREAK, + // TODO campaignClassification + adFormat: 1, + quality: `${width}x${height}`, + resolution: `${clientWidth}x${clientHeight}`, + volume: this.player.muted ? -1 : this.player.volume * 100 + }); + }; + + private onAdEnd = (event: AdEvent<'adend'>) => { + this.logger.log(event); + const { programID } = this.programParameters; + const { ad } = event; + const { adBreak } = ad; + const { timeOffset } = adBreak; + const normalizedTimeOffset = this.normalizeTime(timeOffset); + this.gemiusPlayer.adEvent(programID, ad.id ?? DEFAULT_AD_ID, normalizedTimeOffset, BasicEvent.COMPLETE); + this.gemiusPlayer.adEvent(programID, ad.id ?? DEFAULT_AD_ID, normalizedTimeOffset, BasicEvent.CLOSE); + this.adCount++; + this.currentAd = undefined; + this.player.removeEventListener('playing', this.onFirstPlaying); + this.player.addEventListener('playing', this.onFirstPlaying); + }; + + private onAdSkip = (event: AdSkipEvent) => { + this.logger.log(event); + const { programID } = this.programParameters; + const { ad } = event; + const { adBreak } = ad; + const { timeOffset } = adBreak; + const normalizedTimeOffset = this.normalizeTime(timeOffset); + this.gemiusPlayer.programEvent(programID, normalizedTimeOffset, BasicEvent.SKIP); + }; + + private reportBasicEvent = (event: BasicEvent) => { + const { programID } = this.programParameters; + const { currentTime } = this.player; + if (this.currentAd) { + const { id, adBreak } = this.currentAd; + const { timeOffset } = adBreak; + const normalizedTimeOffset = this.normalizeTime(timeOffset); + this.gemiusPlayer.adEvent(programID, id ?? DEFAULT_AD_ID, normalizedTimeOffset, event); // TODO make SSAI ready + } + if (!this.currentAd && event !== BasicEvent.PAUSE) { + this.gemiusPlayer.programEvent(programID, currentTime, event); + } + }; + + private normalizeTime = (time: number) => { + return this.player.ads?.dai?.contentTimeForStreamTime(time) ?? time; + }; + + private isPreRoll = (adBreak: AdBreak) => { + return adBreak.timeOffset === 0; + }; +} diff --git a/gemius/src/utils/Logger.ts b/gemius/src/utils/Logger.ts new file mode 100644 index 00000000..b5c01ef3 --- /dev/null +++ b/gemius/src/utils/Logger.ts @@ -0,0 +1,13 @@ +import { Event } from 'theoplayer'; + +export class Logger { + private readonly debug: boolean; + + constructor(debug: boolean = false) { + this.debug = debug; + } + + log = (event: Event) => { + if (this.debug) console.log(`[GEMIUS - THEOplayer EVENTS] ${event.type} event`); + }; +} diff --git a/gemius/test/pages/gplayer.js b/gemius/test/pages/gplayer.js new file mode 100644 index 00000000..21e55e10 --- /dev/null +++ b/gemius/test/pages/gplayer.js @@ -0,0 +1,680 @@ +// (c) by Gemius SA - gemius player tools +// ver. 2.12 + +// gemius_pending.js +function gemius_pending(i) { window[i] = window[i] || function() {var x = window[i+'_pdata'] = window[i+'_pdata'] || []; x[x.length]=Array.prototype.slice.call(arguments, 0);};}; +(function(cmds) { var c; while(c = cmds.pop()) gemius_pending(c)})(['gemius_cmd', 'gemius_hit', 'gemius_event', 'gemius_init', 'pp_gemius_hit', 'pp_gemius_event', 'pp_gemius_init']); +window.pp_gemius_cmd = window.pp_gemius_cmd || window.gemius_cmd; + +if (typeof GemiusPlayerVisibility == "undefined") { + var GemiusPlayerVisibility = { + isframe : null, + framevis : null, + childs : [], + timerID : null, + init : function() { + try { + GemiusPlayerVisibility.isframe = (top !== self); + if (window.addEventListener) { + window.addEventListener("message", GemiusPlayerVisibility._msgreceive, false); + } else if (window.attachEvent) { + window.attachEvent("onmessage", GemiusPlayerVisibility._msgreceive); + } + + if (GemiusPlayerVisibility.isframe) { + parent.postMessage("__xx_gplayer_vischeck_xx__","*"); + } + } catch (e) { + } + }, + check : function(object) { + try { + if (GemiusPlayerVisibility.isframe && GemiusPlayerVisibility.framevis===null) { + parent.postMessage("__xx_gplayer_vischeck_xx__","*"); + return null; + } else if (GemiusPlayerVisibility.isframe && GemiusPlayerVisibility.framevis===false) { + return false; + } else if (GemiusPlayerVisibility.isframe !== null) { + var vis = GemiusPlayerVisibility._inScreen(object); + return vis; + } else { + return null; + } + } catch (e) { + return null; + } + }, + _msgreceive : function(e) { + if (typeof e.data=="string" && e.data=="__xx_gplayer_vischeck_xx__") { + try { + if (GemiusPlayerVisibility.isframe && GemiusPlayerVisibility.framevis===null) { + parent.postMessage("__xx_gplayer_vischeck_xx__","*"); + } + var frames = document.getElementsByTagName('iframe'); + var frame = null; + try { + for (var i = 0; i0 && visy>0 && (visx*visy > rect.height*rect.width*0.5 || visx*visy > window.innerHeight*window.innerWidth*0.5); + return vis; + } + } + }, + _isChild : function(p, c) { + var node = c.parentNode; + while (node != null) { + if (node == p) { + return true; + } + node = node.parentNode; + } + return false; + } + } + + GemiusPlayerVisibility.init(); +} + +function GemiusPlayer(playerID, gemiusID, playerData) { + this.interval = 5 * 60; + this.updateInterval = 1; + this.instanceID = (((new Date()).getTime()) + Math.floor(Math.random()*1000)).toString(); + this.playerID = playerID; + this.gemiusID = gemiusID; + this.playerData = (playerData || {}); + this.initialized = false; + this.disposed = false; + this.programs = {}; + this.ads = {}; + this.hitsCounter = 0; + this.currentProgramID = null; + this.videoObject = null; + this.resolution = null; + this.visible = null; + this.hidetime = null; + this.continueIntervalID = null; + this.updateIntervalID = null; + this.unloadFun = null; + this.pagehideFun = null; + this.pageshowFun = null; + + this._playerDataKeys = {"currentDomain":"_SPD", "resolution":"_SPR", "volume":"_SPV"}; + this._programDataKeys = {'programType':'_SCTE','programDuration':'_SCD','programTransmission':'_SCTR','programName':'_SCT', + 'series':'_SCS','typology':'_SCTY','premiereDate':'_SCPD','externalPremiereDate':'_SCEPD','quality':'_SCQ', + 'resolution':'_SCR', 'volume':'_SCV', 'programGenre':'_SCG','programPartialName':'_SCPN','programProducer':'_SCPP', + 'programThematicCategory':'_SCTC','programSeason':'_SCSS','transmissionChannel':'_SCTB','transmissionStartTime':'_SCTS', + 'transmissionType':'_SCTT'}; + this._programExtraDataKeys = {'partID':'_SCP'}; + this._adDataKeys = {'adName':'_SAN','adDuration':'_SAD','adTransmission':'_SATR','adType':'_SAT','campaignClassification':'_SAC', + 'quality':'_SAQ', 'resolution':'_SAR', 'volume':'_SAV', 'adFormat':'_SAF'}; + this._adExtraDataKeys = {'adPosition':'_SAP', 'breakSize':'_SBS', 'breakType':'_SBT'}; + this._eventDataKeys = {'listID':'_SL'}; + this._reservedDataKeys = {'autoPlay':true}; + + //public methods + this.setVideoObject = function(video) { + this.videoObject = video; + } + + this.newProgram = function(programID, programData) { + this._init(); + this.programs[programID] = {"state": "new", "started": false, "program_started": false, "last_action_time": null, "data": this._clone(programData), "extradata": {}}; + this._validateParams(this.programs[programID]["data"]); + this._sendHit(programID, null, "data", "streamcontent", []); + } + + this.newAd = function(adID, adData) { + this._init(); + this.ads[adID] = {"state": "new", "started": false, "last_action_time": null, "currentProgramID": null, "currentAdPosition": null, "data": this._clone(adData), "extradata": {}}; + this._validateParams(this.ads[adID]["data"]); + this._sendHit(null, adID, "data", "streamspot", []); + } + + this.programEvent = function(programID, offset, eventType, eventData) { + eventData = eventData || {}; + if (this.programs[programID]) { + this._updateCustomProgramData(programID, eventData); + this.hitsCounter = (programID==this.currentProgramID)?(this.hitsCounter+1):1; + this.currentProgramID = programID; + if (this.hitsCounter<=2000) { + if (eventType == "play") { + this._playProgram(programID, offset, eventData); + } else if (eventType == "chngQual" || eventType == "chngRes" || eventType == "chngVol") { + this._changeParam(programID, null, offset, eventType, eventData); + } else { + this._event(programID, null, offset, eventType, eventData); + } + } else { + this._stopAll(); + } + } + } + + this.adEvent = function(programID, adID, offset, eventType, eventData) { + eventData = eventData || {}; + if (this.programs[programID] && this.ads[adID]) { + this._updateCustomAdData(adID, eventData); + this.hitsCounter = (programID==this.currentProgramID)?(this.hitsCounter+1):1; + this.currentProgramID = programID; + if (this.hitsCounter<=2000) { + if (eventType == "play") { + this._playAd(programID, adID, offset, eventData); + } else if (eventType == "chngQual" || eventType == "chngRes" || eventType == "chngVol") { + this._changeParam(programID, adID, offset, eventType, eventData); + } else { + this._event(programID, adID, offset, eventType, eventData); + } + } else { + this._stopAll(); + } + } + } + + this.dispose = function() { + this._dispose(); + } + + //private methods + this._playProgram = function(programID, offset, eventData) { + this._stopAll(); + var eventCategory = (this.programs[programID]['program_started']==false)?"programstart":(this.programs[programID]["started"]?"play":"start"); + var params = ["_SCO="+offset, "_SED="+this._updateActionTime(this.programs[programID])]; + if (typeof eventData['autoPlay'] != "undefined") params = params.concat(["_ECA=" + (eventData['autoPlay']?1:0)]); + if (typeof eventData['partID'] != "undefined") this.programs[programID]["extradata"]["partID"] = eventData['partID']; + else delete this.programs[programID]["extradata"]["partID"]; + if (typeof eventData['volume'] != "undefined") params = params.concat(["_SPVN=" + eventData['volume']]); + if (typeof eventData['resolution'] != "undefined") params = params.concat(["_SPRN=" + eventData['resolution']]); + this.programs[programID]["state"] = "play"; + this.programs[programID]["started"] = true; + this.programs[programID]["program_started"] = true; + this._sendHit(programID, null, "stream", eventCategory, params); + + if (typeof eventData['volume'] != "undefined") this.playerData['volume'] = eventData['volume']; + if (typeof eventData['resolution'] != "undefined") this.playerData['resolution'] = eventData['resolution']; + + for (var adID in this.ads) { + this.ads[adID]["started"] = false; + } + + for (var pID in this.programs) { + if (pID != programID) { + this.programs[pID]["started"] = false; + this.programs[pID]["program_started"] = false; + } + } + } + + this._playAd = function(programID, adID, offset, eventData) { + this._stopAll(); + var start = (!this.ads[adID]["started"] || this.ads[adID]["currentProgramID"]!=programID || this.ads[adID]["currentAdPosition"]!=eventData['adPosition']); + var breakType = this._getBreakType(programID, offset); + var eventCategory = (start && breakType!="post" && this.programs[programID]['program_started']==false)?"programstart":(start?"start":"play"); + var params = ["_SCO="+offset,"_SED="+this._updateActionTime(this.ads[adID])]; + if (typeof eventData['autoPlay'] != "undefined") params = params.concat(["_ECA=" + (eventData['autoPlay']?1:0)]); + if (typeof eventData['adPosition'] != "undefined") this.ads[adID]["extradata"]["adPosition"] = eventData['adPosition']; + else delete this.ads[adID]["extradata"]["adPosition"]; + if (typeof eventData['breakSize'] != "undefined") this.ads[adID]["extradata"]["breakSize"] = eventData['breakSize']; + else delete this.ads[adID]["extradata"]["breakSize"]; + if (breakType) this.ads[adID]["extradata"]["breakType"] = breakType; + else delete this.ads[adID]["extradata"]["breakType"]; + if (typeof eventData['volume'] != "undefined") params = params.concat(["_SPVN=" + eventData['volume']]); + if (typeof eventData['resolution'] != "undefined") params = params.concat(["_SPRN=" + eventData['resolution']]); + this.ads[adID]["state"] = "play"; + this.ads[adID]["started"] = true; + this.ads[adID]["currentProgramID"] = programID; + this.ads[adID]["currentAdPosition"] = eventData['adPosition']; + if (breakType!="post") this.programs[programID]["program_started"] = true; + this._sendHit(programID, adID, "stream", eventCategory, params); + + if (typeof eventData['volume'] != "undefined") this.playerData['volume'] = eventData['volume']; + if (typeof eventData['resolution'] != "undefined") this.playerData['resolution'] = eventData['resolution']; + } + + this._event = function(programID, adID, offset, eventType, eventData) { + var evtypes = {"pause":"pause", "stop":"stop", "close":"close", "buffer":"buffering", "break":"break", "seek":"seek", "complete":"complete", "skip":"skip", "next":"next", "prev":"prev"}; + var data = (adID?this.ads[adID]:this.programs[programID]); + if (evtypes[eventType]) { + var params = ["_SED="+this._updateActionTime(data)].concat(this._convertEventParams(eventData)); + if (typeof offset != "undefined") params = params.concat(["_SCO="+offset]); + if (eventType == "stop" || eventType == "complete" || eventType == "close") { + data["started"] = false; + if (!adID) this.programs[programID]["program_started"] = false; + } + data["state"] = evtypes[eventType]; + this._sendHit(programID, adID, "stream", evtypes[eventType], params); + } + } + + this._changeParam = function(programID, adID, offset, eventType, eventData) { + var data = (adID?this.ads[adID]:this.programs[programID]); + var params = ["_SED="+this._updateActionTime(data)]; + if (typeof offset != "undefined") params = params.concat(["_SCO="+offset]); + if (typeof eventData['volume'] != "undefined") params = params.concat(["_SPVN=" + eventData['volume']]); + if (typeof eventData['resolution'] != "undefined") params = params.concat(["_SPRN=" + eventData['resolution']]); + if (typeof eventData['quality'] != "undefined") params = params.concat([(adID?"_SAQN=":"_SCQN=") + eventData['quality']]); + this._sendHit(programID, adID, "stream", "continue", params); + + if (eventData['quality']) data['data']['quality'] = eventData['quality']; + if (eventData['resolution']) this.playerData['resolution'] = eventData['resolution']; + if (eventData['volume']) this.playerData['volume'] = eventData['volume']; + } + + this._getBreakType = function(programID, offset) { + try { + var duration = (this.programs[programID]['data']['programDuration'] || 0); + var transmissionType = (this.programs[programID]['data']['transmissionType'] || 0); + if (typeof duration != "number") duration = parseInt(duration,10); + if (typeof transmissionType != "number") transmissionType = parseInt(transmissionType,10); + if (typeof offset != "number") offset = parseInt(offset,10); + if (transmissionType==2) return "live"; + else if (duration<0) return "unknown"; + else if ((duration<=100 && offset<=0) || (duration>100 && offset<5)) return "pre"; + else if (duration>0 && ((duration<=100 && offset>=duration) || (duration>100 && offset>duration-5))) return "post"; + else return "mid"; + } catch (e) { + return ""; + } + } + + this._stopAll = function() { + for (var programID in this.programs) { + if (this.programs[programID]["state"] == "play") { + this._event(programID, null, undefined, "pause", {}); + } + } + + for (var adID in this.ads) { + if (this.ads[adID]["state"] == "play" && this.programs[this.ads[adID]["currentProgramID"]]) { + this._event(this.ads[adID]["currentProgramID"], adID, undefined, "pause", {}); + } + } + } + + this._update = function() { + var vis = GemiusPlayerVisibility.check(this.videoObject); + var res = this._getPlayerSize(); + if (vis !== this.visible || res !== this.resolution) { + this._continue(); + this.visible = vis; + this.resolution = res; + } + } + + this._getPlayerSize = function() { + if (!this.videoObject) { + return null; + } else { + try { + var rect = this.videoObject.getBoundingClientRect(); + return parseInt(rect.width,10) + "x" + parseInt(rect.height,10); + } catch (e) { + return null; + } + } + } + + this._continue = function() { + for (var programID in this.programs) { + if (this.programs[programID]["state"] == "play") { + var params = ["_SED="+this._updateActionTime(this.programs[programID])]; + this._sendHit(programID, null, "stream", "continue", params); + } + } + for (var adID in this.ads) { + if (this.ads[adID]["state"] == "play") { + var params = ["_SED="+this._updateActionTime(this.ads[adID])]; + this._sendHit(this.ads[adID]["currentProgramID"], adID, "stream", "continue", params); + } + } + } + + this._sendClosingHits = function(eventCategory) { + try { + if (typeof gemius_close == 'function') { + gemius_close(); + } + var delay = false; + for (var programID in this.programs) { + if (this.programs[programID]["state"] == "play") { + var params = ["_SED="+this._updateActionTime(this.programs[programID])]; + this._sendHit(programID, null, "stream", eventCategory, params); + delay = true; + } + } + for (var adID in this.ads) { + if (this.ads[adID]["state"] == "play") { + var params = ["_SED="+this._updateActionTime(this.ads[adID])]; + this._sendHit(this.ads[adID]["currentProgramID"], adID, "stream", eventCategory, params); + delay = true; + } + } + if (delay && typeof navigator.sendBeacon != "function") { + var start = (new Date()).getTime(); + while (start+250>(new Date()).getTime()); + } + } catch(e) {} + } + + this._unload = function() { + this._sendClosingHits("unload"); + } + + this._pagehide = function() { + this._unload(); + if (this.hidetime == null) { + this.hidetime = (new Date()).getTime(); + } + } + + this._pageshow = function() { + if (this.hidetime == null) { + return; + } + var showtime = ((new Date()).getTime()); + var leap = (showtime > this.hidetime) ? showtime - this.hidetime : 0; + try { + for (var programID in this.programs) { + this._updateHideTime(this.programs[programID], leap); + } + for (var adID in this.ads) { + this._updateHideTime(this.ads[adID], leap); + } + } catch(e) {} + this.hidetime = null; + } + + this._updateHideTime = function(streamData, leap) { + try { + if (streamData['state'] == 'play' && streamData['last_action_time']) { + streamData['last_action_time'] += leap; + } + } catch(e) {} + } + + this._updateActionTime = function(streamData) { + var duration = 0; + try { + if (streamData['state'] == 'play' && streamData['last_action_time']) { + duration = Math.round(((new Date()).getTime() - streamData['last_action_time']) / 1000); + if (duration < 0 || duration > 2*this.interval) { + duration = 0; + } + } + streamData['last_action_time'] = (new Date()).getTime(); + } catch (e) {} + return duration; + } + + this._sendHit = function(programID, adID, eventType, eventCategory, params) { + if (this.disposed) { + return; + } + var extra = ["_EC="+eventCategory].concat(this._getPlayerParams()).concat(this._getProgramParams(programID)).concat(this._getAdParams(adID)).concat(params); + gemius_event.apply(window, ['_' + eventType + '_', this.gemiusID].concat(extra)); + } + + this._getProgramParams = function(programID) { + if (!this.programs[programID]) return []; + var params = ["_SC="+programID]; + params = params.concat(this._convertParams(this.programs[programID]["data"], this._programDataKeys, true)); + params = params.concat(this._convertParams(this.programs[programID]["extradata"], this._programExtraDataKeys)); + return params; + } + + this._getAdParams = function(adID) { + if (!this.ads[adID]) return []; + var params = ["_SA="+adID]; + params = params.concat(this._convertParams(this.ads[adID]["data"], this._adDataKeys, true)); + params = params.concat(this._convertParams(this.ads[adID]["extradata"], this._adExtraDataKeys)); + return params; + } + + this._getPlayerParams = function() { + var params = ["_SPI="+this.instanceID,"_SP="+this.playerID]; + if (this.resolution !== null) params = params.concat(["_SPS="+this.resolution]); + if (this.visible !== null) params = params.concat(["_SPIS="+(this.visible?"1":"0")]); + return params.concat(this._convertParams(this.playerData,this._playerDataKeys)); + } + + this._validateParams = function(params) { + var intTypes = {'programDuration':1, 'adDuration':1}; + var maxLengths = {'programTransmission':20,'adTransmission':20,'programPartialName':64,'programProducer':64, + 'programSeason':64,'transmissionChannel':64,'transmissionStartTime':10}; + if (typeof params != 'undefined') { + for (var key in params) { + if (maxLengths[key]) { + if (typeof params[key] == 'string') { + params[key] = params[key].substr(0, maxLengths[key]); + } else { + delete params[key]; + } + } + if (intTypes[key]) { + params[key] = parseInt(params[key]); + } + } + } + } + + this._convertEventParams = function(eventData) { + return this._convertParams(eventData, this._eventDataKeys); + } + + this._convertParams = function(params, keyMap, allowCustomParams) { + var res = []; + if (typeof params != 'undefined') { + for (var key in params) { + if (keyMap[key] || allowCustomParams) { + res[res.length] = (keyMap[key] || key) + "=" + params[key]; + } + } + } + return res; + } + + this._updateCustomProgramData = function(programID, eventData) { + for (var key in eventData) { + if (!(key in this._reservedDataKeys)) { + this.programs[programID]['data'][key] = eventData[key]; + } + } + this._validateParams(this.programs[programID]['data']); + } + + this._updateCustomAdData = function(adID, eventData) { + for (var key in eventData) { + if (!(key in this._reservedDataKeys)) { + this.ads[adID]['data'][key] = eventData[key]; + } + } + this._validateParams(this.ads[adID]['data']); + } + + this._assignDataKeys = function(target, source) { + for (var key in source) { + target[key] = true; + } + } + + this._initReservedKeys = function() { + this._assignDataKeys(this._reservedDataKeys, this._playerDataKeys); + this._assignDataKeys(this._reservedDataKeys, this._programDataKeys); + this._assignDataKeys(this._reservedDataKeys, this._programExtraDataKeys); + this._assignDataKeys(this._reservedDataKeys, this._adDataKeys); + this._assignDataKeys(this._reservedDataKeys, this._adExtraDataKeys); + this._assignDataKeys(this._reservedDataKeys, this._eventDataKeys); + delete this._reservedDataKeys['programDuration']; + delete this._reservedDataKeys['adDuration']; + } + + this._addEvent = function(obj,type,fn) { + if (obj.addEventListener) { + obj.addEventListener(type, fn, false); + } else if (obj.attachEvent) { + obj.attachEvent('on'+type, fn); + } + } + + this._removeEvent = function(obj,type,fn) { + if (obj.removeEventListener) { + obj.removeEventListener(type, fn, false); + } else if (obj.detachEvent) { + obj.detachEvent('on'+type, fn); + } + } + + this._init = function() { + if (!this.initialized && !this.disposed) { + this.initialized = true; + this._initReservedKeys(); + this._update(); + this.continueIntervalID = setInterval(this._wrapFun(this,"_continue"), this.interval * 1000); + this.updateIntervalID = setInterval(this._wrapFun(this,"_update"), this.updateInterval * 1000); + this.unloadFun = this._wrapFun(this,"_unload"); + this.pagehideFun = this._wrapFun(this,"_pagehide"); + this.pageshowFun = this._wrapFun(this,"_pageshow"); + try { + if ('onpagehide' in window) { + this._addEvent(window.top, 'pagehide', this.pagehideFun); + this._addEvent(window.top, 'pageshow', this.pageshowFun); + } else if (typeof navigator.sendBeacon == "function") { + this._addEvent(window.top, 'unload', this.unloadFun); + } else { + this._addEvent(window.top, 'beforeunload', this.unloadFun); + } + } catch (e) { + if ('onpagehide' in window) { + this._addEvent(window, 'pagehide', this.pagehideFun); + this._addEvent(window, 'pageshow', this.pageshowFun); + } else if (typeof navigator.sendBeacon == "function") { + this._addEvent(window, 'unload', this.unloadFun); + } else { + this._addEvent(window, 'beforeunload', this.unloadFun); + } + } + } + } + + this._dispose = function() { + if (this.initialized && !this.disposed) { + this._sendClosingHits("dispose"); + clearInterval(this.continueIntervalID); + clearInterval(this.updateIntervalID); + try { + if ('onpagehide' in window) { + this._removeEvent(window.top, 'pagehide', this.pagehideFun); + this._removeEvent(window.top, 'pageshow', this.pageshowFun); + } else if (typeof navigator.sendBeacon == "function") { + this._removeEvent(window.top, 'unload', this.unloadFun); + } else { + this._removeEvent(window.top, 'beforeunload', this.unloadFun); + } + } catch (e) { + if ('onpagehide' in window) { + this._removeEvent(window, 'pagehide', this.pagehideFun); + this._removeEvent(window, 'pageshow', this.pageshowFun); + } else if (typeof navigator.sendBeacon == "function") { + this._removeEvent(window, 'unload', this.unloadFun); + } else { + this._removeEvent(window, 'beforeunload', this.unloadFun); + } + } + this.disposed = true; + } + } + + this._wrapFun = function(self, method) { + return function() { + self[method](); + } + } + + this._clone = function(obj) { + var res = {}; + if (!obj) return res; + for (var key in obj) { + if (obj.hasOwnProperty(key)) res[key] = obj[key]; + } + return res; + } +} + +if (typeof window['gemius_player_data'] != 'undefined') { + for (var i=0; i + + + + Connector test page + + + + + + + + + +
+
+
+ +
+
+ +
+
+
+
+ + + + + + + diff --git a/gemius/test/pages/main_umd.html b/gemius/test/pages/main_umd.html new file mode 100644 index 00000000..202dad1e --- /dev/null +++ b/gemius/test/pages/main_umd.html @@ -0,0 +1,100 @@ + + + + + Connector test page + + + + + + + + + + + +
+
+
+ +
+
+ +
+
+
+
+ + + + + + diff --git a/gemius/test/pages/preroll-empty.xml b/gemius/test/pages/preroll-empty.xml new file mode 100644 index 00000000..6850c951 --- /dev/null +++ b/gemius/test/pages/preroll-empty.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gemius/test/pages/test-assets.json b/gemius/test/pages/test-assets.json new file mode 100644 index 00000000..55772faf --- /dev/null +++ b/gemius/test/pages/test-assets.json @@ -0,0 +1,273 @@ +[ + { + "label": "VOD (DASH) + VAST (IMA)", + "source": { + "sources": [ + { + "src": "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd", + "useCredentials": false + } + ], + "ads": [ + { + "integration": "google-ima", + "timeOffset": "start", + "sources": "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=" + } + ] + }, + "metadata": { + "programID": "000001", + "programName": "Big Buck Bunny (DASH)", + "programDuration": 635, + "programType": "video", + "transmissionType": 1, + "programGenre": 4, + "series": "Test Content", + "programSeason": "season 1", + "programProducer": "Blender", + "customAttributes": { + "intCategory": "Comedy", + "intType": "vod", + "intStatus": "public" + } + } + }, + { + "label": "VOD (HLS)", + "source": { + "sources": [ + { + "src": "https://cdn.theoplayer.com/video/big_buck_bunny/big_buck_bunny.m3u8", + "type": "application/x-mpegurl" + } + ] + }, + "metadata": { + "programID": "000002", + "programName": "Big Buck Bunny (HLS)", + "programDuration": 596, + "programType": "video", + "transmissionType": 1, + "programGenre": 4, + "series": "Test Content", + "programSeason": "season 1", + "programProducer": "Blender", + "customAttributes": { + "intName": "BBB", + "intCategory": "Comedy", + "intType": "vod", + "intStatus": "public" + } + } + }, + { + "label": "VOD (HLS) - VMAP (IMA)", + "source": { + "sources": [ + { + "src": "https://cdn.theoplayer.com/video/big_buck_bunny/big_buck_bunny.m3u8", + "type": "application/x-mpegurl" + } + ], + "ads": [ + { + "integration": "google-ima", + "sources": "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpost&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=" + } + ] + }, + "metadata": { + "programID": "000002", + "programName": "Big Buck Bunny (HLS)", + "programDuration": 596, + "programType": "video", + "transmissionType": 1, + "programGenre": 4, + "series": "Test Content", + "programSeason": "season 1", + "programProducer": "Blender", + "customAttributes": { + "intName": "BBB", + "intCategory": "Comedy", + "intType": "vod", + "intStatus": "public" + } + } + }, + { + "label": "VOD (HLS) - VMAP (THEOAds)", + "source": { + "sources": [ + { + "src": "https://cdn.theoplayer.com/video/big_buck_bunny/big_buck_bunny.m3u8", + "type": "application/x-mpegurl" + } + ], + "ads": [ + { + "integration": "theo", + "sources": "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpost&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=" + } + ] + }, + "metadata": { + "programID": "000002", + "programName": "Big Buck Bunny (HLS)", + "programDuration": 596, + "programType": "video", + "transmissionType": 1, + "programGenre": 4, + "series": "Test Content", + "programSeason": "season 1", + "programProducer": "Blender", + "customAttributes": { + "intName": "BBB", + "intCategory": "Comedy", + "intType": "vod", + "intStatus": "public" + } + } + }, + { + "label": "LIVE (DASH) - VAST pre-roll (IMA)", + "source": { + "sources": [ + { + "src": "https://livesim2.dashif.org/livesim2/testpic_2s/Manifest.mpd", + "useCredentials": false + } + ], + "ads": [ + { + "integration": "google-ima", + "timeOffset": "start", + "sources": "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=" + } + ] + }, + "metadata": { + "programID": "000003", + "programName": "Livesim", + "programDuration": -1, + "programType": "video", + "transmissionType": 2, + "transmissionChannel": "DASHIF1", + "programGenre": 1, + "series": "Test Content", + "programSeason": "season 1", + "programProducer": "DASHIF", + "customAttributes": { + "intName": "LIVESIM", + "intCategory": "Generic", + "intType": "vod", + "intStatus": "public" + } + } + }, + { + "label": "LIVE (DASH) - empty VAST pre-roll (IMA)", + "source": { + "sources": [ + { + "src": "https://livesim2.dashif.org/livesim2/testpic_2s/Manifest.mpd", + "useCredentials": false + } + ], + "ads": [ + { + "integration": "google-ima", + "timeOffset": "start", + "sources": "http://localhost:8081/gemius/test/pages/preroll-empty.xml" + } + ] + }, + "metadata": { + "programID": "000003", + "programName": "Livesim", + "programDuration": -1, + "programType": "video", + "transmissionType": 2, + "programGenre": 1, + "series": "Test Content", + "programSeason": "season 1", + "programProducer": "DASHIF", + "customAttributes": { + "intName": "LIVESIM", + "intCategory": "Generic", + "intType": "vod", + "intStatus": "public" + } + } + }, + { + "label": "LIVE (DASH) - VAST pre-roll (THEOAds)", + "source": { + "sources": [ + { + "src": "https://livesim2.dashif.org/livesim2/testpic_2s/Manifest.mpd", + "useCredentials": false + } + ], + "ads": [ + { + "integration": "theo", + "timeOffset": "start", + "sources": "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&impl=s&correlator=" + } + ] + }, + "metadata": { + "programID": "000003", + "programName": "Livesim", + "programDuration": -1, + "programType": "video", + "transmissionType": 2, + "programGenre": 1, + "series": "Test Content", + "programSeason": "season 1", + "programProducer": "DASHIF", + "customAttributes": { + "intName": "LIVESIM", + "intCategory": "Generic", + "intType": "vod", + "intStatus": "public" + } + } + }, + { + "label": "VOD - Google DAI", + "source": { + "sources": [ + { + "type": "application/x-mpegurl", + "ssai": { + "integration": "google-dai", + "availabilityType": "vod", + "contentSourceID": "2548831", + "videoID": "tears-of-steel", + "assetKey": "", + "apiKey": "" + } + } + ] + }, + "metadata": { + "programID": "000003", + "programName": "Livesim", + "programDuration": 794, + "programType": "video", + "transmissionType": 1, + "programGenre": 4, + "series": "Test Content", + "programSeason": "season 1", + "programProducer": "Blender Foundation", + "customAttributes": { + "intName": "TOS", + "intCategory": "Generic", + "intType": "vod", + "intStatus": "public" + } + } + } +] \ No newline at end of file diff --git a/gemius/tsconfig.json b/gemius/tsconfig.json new file mode 100644 index 00000000..d416c1ae --- /dev/null +++ b/gemius/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declarationDir": "dist/types" + }, + "include": [ + "src/**/*" + ] +} diff --git a/gemius/typedoc.json b/gemius/typedoc.json new file mode 100644 index 00000000..7cafc8ff --- /dev/null +++ b/gemius/typedoc.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "extends": [ + "../typedoc.base.json" + ], + "entryPoints": [ + "src/index.ts" + ], + "tsconfig": "tsconfig.json", + "readme": "README.md", + "name": "Gemius Connector" +} diff --git a/package-lock.json b/package-lock.json index 4babe749..4df815c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "nielsen", "cmcd", "comscore", - "adscript" + "adscript", + "gemius" ], "devDependencies": { "@changesets/cli": "^2.27.1", @@ -45,7 +46,7 @@ }, "adscript": { "name": "@theoplayer/adscript-connector-web", - "version": "0.0.1", + "version": "0.1.0", "license": "MIT", "peerDependencies": { "theoplayer": "^7.0.0" @@ -69,7 +70,7 @@ }, "conviva": { "name": "@theoplayer/conviva-connector-web", - "version": "2.1.1", + "version": "2.1.3", "license": "MIT", "devDependencies": { "@convivainc/conviva-js-coresdk": "^4.7.4" @@ -85,6 +86,14 @@ } } }, + "gemius": { + "name": "@theoplayer/gemius-connector-web", + "version": "0.0.1", + "license": "MIT", + "peerDependencies": { + "theoplayer": "^7.0.0" + } + }, "nielsen": { "name": "@theoplayer/nielsen-connector-web", "version": "1.1.2", @@ -2421,6 +2430,10 @@ "resolved": "conviva", "link": true }, + "node_modules/@theoplayer/gemius-connector-web": { + "resolved": "gemius", + "link": true + }, "node_modules/@theoplayer/nielsen-connector-web": { "resolved": "nielsen", "link": true @@ -8750,7 +8763,7 @@ }, "yospace": { "name": "@theoplayer/yospace-connector-web", - "version": "2.2.0", + "version": "2.3.0", "license": "MIT", "peerDependencies": { "theoplayer": "^5.0.0 || ^6.0.0 || ^7.0.0" diff --git a/package.json b/package.json index b56f2b8f..2676258a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "nielsen", "cmcd", "comscore", - "adscript" + "adscript", + "gemius" ], "scripts": { "changeset:version": "changeset version && node .changeset/post-process.js",