@theoplayer/react-native-analytics-comscore
Version:
Comscore analytics connector for @theoplayer/react-native
640 lines (582 loc) • 15.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TheoBase = void 0;
var _Utils = require("./Utils");
const MINIMUM_DVR_DURATION = 60;
const LIVE_MARGIN = 10;
/**
* This abstract class is responsible for filtering appropriate events from THEO
*/
class TheoBase {
/**
* sotres THEOplayer
* @protected
* @type {*}
* @memberof TheoBase
*/
/**
* array of subscribed listeners
* @private
* @type {any[]}
* @memberof TheoBase
*/
listeners = [];
/**
* stores the ad playhead position
* @private
* @type {number}
* @memberof TheoBase
*/
/**
* stores the number of prerolls started
* @private
* @type {number}
* @memberof TheoBase
*/
prerollCount = 0;
/**
* stores the number of midRolls started
* @private
* @type {number}
* @memberof TheoBase
*/
midrollCount = 0;
/**
* stores the number of postrolls started
* @private
* @type {number}
* @memberof TheoBase
*/
postrollCount = 0;
/**
* stores the stream playhead position
* @private
* @type {number}
* @memberof TheoBase
*/
/**
* stores the actual duration (no ad duration)
* @protected
* @type {number}
* @memberof TheoBase
*/
/**
* the flag for buffering
* @private
* @type {boolean}
* @memberof TheoBase
*/
isBuffering = false;
/**
* flag to switch the plugin to report low level buffer state changes,
* or only report the waiting event.
* @private
* @type {boolean}
* @memberof TheoBase
*/
simpleBufferNotifications = true;
/**
* the flag for waiting state
* @private
* @type {boolean}
* @memberof TheoBase
*/
isWaiting = false;
/**
* the flag used to inform to ignore ended after postroll
* @private
* @type {boolean}
* @memberof TheoBase
*/
ignoreEndedAfterPostroll = false;
/**
* the flag used to inform to ignore ended after postroll
* @private
* @type {boolean}
* @memberof TheoBase
*/
_adsPlaying = false;
/**
* the flag used to verify whether the player has fired its first play event
* @private
* @type {boolean}
* @memberof TheoBase
*/
_isFirstPlay = true;
/**
* the flag used to inform not to call notifyPlay with ContentMetadata
* After a postroll has ended or after main stream has ended
* @private
* @type {boolean}
* @memberof TheoBase
*/
sendContentPlayOnEnded = false;
/**
* Instanciate object and bind internal listeners
* @param player THEOPlayer instance
*/
constructor(player) {
this.player = player;
this.bindListenersInternal();
//CVA: this is certainly not something to be done in the integration
//if needed this needs to be handled at the apliction level
//this.handleVisibilityChange();
}
/**
* handles changing tab visibility on mobile
* when the tab is clicked than media by default is paused
* tough it is not handled by theo properly at the moment
* this solves the issue
*/
handleVisibilityChange() {
//window.addEventListener('blur', this.player.pause, false);
}
/**
* Reinitialises some of the boolean values that
* determine the behavior of this pluging
*/
reset() {
this._isFirstPlay = true;
this._adsPlaying = false;
this.sendContentPlayOnEnded = true;
this.ignoreEndedAfterPostroll = false;
this.playheadPosition = 0;
this.adPlayheadPosition = 0;
this.prerollCount = 0;
this.midrollCount = 0;
this.postrollCount = 0;
this.isBuffering = false;
}
/**
* binds listeners used only internally
*/
bindListenersInternal() {
this.onLoadedData();
this.onLoadedMetaData();
this.onTimeUpdate();
}
/**
* Binds all listeners
* override this method to specify which listeners should be used
*/
/**
* This method should be used to handle source change
*/
/**
* This method should be used to notify once the ad starts
* @param {String} adId ID of ad if exists, otherwise null
* @param {AD_TYPES} adType type of add: preroll, midroll, postroll
*/
/**
* Tis method should be used to notify once the stream playout ends
*/
/**
* Tis method should be used to notify once ad ends
*/
/**
* notifies that buffer starts
* @param {*} currentTime in sec
*/
/**
* notifies that buffer stops
* @param {*} currentTime in sec
*/
/**
* notifies play starts
* @param {*} currentTime in sec
*/
/**
* notifies pause
* @param {*} currentTime in sec
*/
/**
* notifies seeking starts
* @param {*} currentTime in sec
*/
/**
* notifies data loaded
*/
/**
* notifies about rate change
*/
adsPlaying() {
// this sometimes falsely returns true in stead of false upon initial playback
// return this.player.ads.playing
// return this.player && this.player.ads.currentAdBreak;
return this._adsPlaying;
}
/**
* Determines if the content is Linear TV
*/
/**
* used to set actual stream duration
*/
onLoadedData() {
this.addListener(this.player, _Utils.THEO_EVENTS.LOADED_DATA, event => {
const {
currentTime,
readyState
} = event;
if (!this.adsPlaying() && this.player.duration) {
this.playerDuration = this.player.duration;
}
const notfiredOncePlayoutEnds = currentTime !== this.player.duration;
// not works for LIVE+DVR
//const haveEnoughData = readyState === 4;
//if (haveEnoughData && notfiredOncePlayoutEnds) {
if (notfiredOncePlayoutEnds) {
this.notifyDataLoaded();
}
});
}
/**
* used to set DVR window
*/
onLoadedMetaData() {
this.addListener(this.player, _Utils.THEO_EVENTS.LOADED_METADATA, event => {
// Do nothing
// console.log('on loaded metadata')
});
}
/**
* keeps playhead position separate for ads and stream
*/
onTimeUpdate() {
this.addListener(this.player, _Utils.THEO_EVENTS.TIME_UPDATE, event => {
const {
currentTime
} = event;
if (!this.adsPlaying()) {
this.playheadPosition = currentTime;
if (currentTime == 0) {
//console.log('SETTIMG PLAYHEAD to 0');
}
} else if (this.adsPlaying() && this.player.currentTime > 0) {
this.adPlayheadPosition = currentTime;
}
});
}
/**
* fired once source change
*/
onSourceChange() {
this.addListener(this.player, _Utils.THEO_EVENTS.SOURCE_CHANGE, () => {
this.reset();
this.notifySourceChange();
});
}
/**
* set ad metadata once ad begin
*/
onAdBegin() {
this.addListener(this.player._player.ads, _Utils.THEO_EVENTS.AD_BEGIN, event => {
// console.log('[THEO AD EVENT] AD BEGIN')
this.adPlayheadPosition = 0;
this._adsPlaying = true;
const adId = event && event.ad && event.ad.id || null;
const adType = this.getAdType();
const ad = event.ad;
if (adType === _Utils.AD_TYPES.PREROLL) {
this.prerollCount++;
} else if (adType === _Utils.AD_TYPES.MIDROLL) {
this.midrollCount++;
} else {
this.postrollCount++;
}
this.isBuffering = false;
this.notifyAdBegin(adId, adType, ad);
});
}
/**
* notify end of ad
*/
onAdEnds() {
this.addListener(this.player._player.ads, _Utils.THEO_EVENTS.AD_END, () => {
// console.log('[THEO AD EVENT] AD END')
this._adsPlaying = false;
const adType = this.getAdType();
if (adType === _Utils.AD_TYPES.POSTROLL) {
this.ignoreEndedAfterPostroll = true;
}
this.notifyAdEnd(adType);
this.adPlayheadPosition = 0;
});
}
/**
* informs about buffering start and stop
*/
onBuffering() {
this.addListener(this.player, _Utils.THEO_EVENTS.READY_STATE_CHANGE, event => {
//omit notifying in case playout ends
// if (this.player.ended) { // why does this property not exist on the THEOplayer interface?
// return;
// }
//buffering starts
const {
readyState,
currentTime
} = event;
const {
HAVE_NOTHING,
HAVE_METADATA,
HAVE_CURRENT_DATA,
HAVE_FUTURE_DATA,
HAVE_ENOUGH_DATA
} = _Utils.READY_STATES;
if (!this.simpleBufferNotifications) {
switch (readyState) {
case HAVE_NOTHING:
case HAVE_CURRENT_DATA:
case HAVE_FUTURE_DATA:
break;
case HAVE_METADATA:
this.isBuffering = true;
if (!this.simpleBufferNotifications) this.notifyBufferStart(currentTime);
break;
case HAVE_ENOUGH_DATA:
this.isBuffering = false;
if (!this.simpleBufferNotifications) this.notifyBufferStop(currentTime);
break;
default:
break;
}
}
});
}
/**
* handles player waiting event. This event is fired when playback stalls due to
* an low/empty buffer state.
* However this event is thrown in various occasions for technical reasons
* where you may not want to notify the analytics service
*
* waiting is thrown on startup, then initialising the source player.paused is then true
* waiting is thrown when seeking because the buffer is then empty. player.seeking is then true
* waiting is thrown then the buffer is empty during normal playback player.paused and player seeking are false
*/
onWaiting() {
this.addListener(this.player, _Utils.THEO_EVENTS.WAITING, () => {
this.isWaiting = true;
//Only notify then buffer is empty during normal playback.
if (this.simpleBufferNotifications) {
if (!this.player.seeking && !this.player.paused) {
//auditel expects
//this.notifyPause(this.getCurrentTime());
this.notifyBufferStart(this.getCurrentTime());
}
}
});
}
/**
* handles play events once ad playouts (sinde THEO not fires PLAYING events for ads)
*/
onPlay() {
this.addListener(this.player, _Utils.THEO_EVENTS.PLAY, event => {
//console.log('Comscore ignoring playevent');
// handled only once ad plays
//console.log('[THEOBASE] onPlay');
if (!this.adsPlaying()) {
//console.log('[THEOBASE] onPlay return WITHOUT notifyPlay');
return;
}
//console.log('[THEOBASE] onPlay call notifyPlay');
this.notifyPlay(this.getCurrentTime());
});
}
/**
* handles playing event
*/
onPlaying() {
this.addListener(this.player, _Utils.THEO_EVENTS.PLAYING, event => {
//console.log('[THEOBASE] onPlaying');
if (this.isWaiting) {
this.isWaiting = false;
if (this.simpleBufferNotifications) {
this.notifyBufferStop(this.getCurrentTime());
}
}
if (this.adsPlaying()) {
//console.log('[THEOBASE] onPlaying return WITHOUT notifyPlay');
return;
}
//console.log('[THEOBASE] onPlayingnpm run call notifyPlay');
//console.log('NOTIFY PLAY get CURRENT TIME', this.getCurrentTime)
this.notifyPlay(this.getCurrentTime());
});
}
/**
* notifies about Pause
* ignored in case buffering is happening
*/
onPause() {
this.addListener(this.player, _Utils.THEO_EVENTS.PAUSE, event => {
// THEO fires `pause` also when seeking and on the end of playout
const firedOncePlayoutEnds = event.currentTime >= this.player.duration;
if (this.isBuffering || firedOncePlayoutEnds) {
//console.debug('ignoring pause!');
return;
}
this.notifyPause(this.getCurrentTime());
});
}
/**
* notifies about Seeking action
*/
onSeeking() {
this.addListener(this.player, _Utils.THEO_EVENTS.SEEKING, event => {
const {
currentTime
} = event;
// omit notifying for the `seeking` event which theo is firing on preroll ends
if (currentTime === 0) {
return;
}
this.notifySeekStart(this.getCurrentTime());
});
}
/**
* notifies end of stream
*/
onEnded() {
this.addListener(this.player, _Utils.THEO_EVENTS.ENDED, () => {
this.notifyEndOfStream();
this.reset();
});
}
/**
* notities rate change
*/
onRateChange() {
this.addListener(this.player, _Utils.THEO_EVENTS.RATE_CHANGE, event => {
const {
playbackRate
} = event;
this.notifyRateChange(playbackRate);
});
}
/**
* destroy the object
*/
destroy() {
this.removeListeners();
}
/**
* removes all listeners
*/
removeListeners() {
this.listeners.forEach(listener => {
listener.caller.removeEventListener(listener.type, listener.f);
});
this.listeners = [];
}
/**
* adds listener to the array
*/
addListener(caller, type, f) {
this.listeners.push({
caller: caller,
type: type,
f: f
});
caller.addEventListener(type, f);
}
/**
* formats playhead position
*/
getFlooredPlayerPosition() {
return Math.floor(this.player.currentTime);
}
/**
* determines ad type: preroll, midroll, postroll
*/
getAdType() {
let adType = _Utils.AD_TYPES.PREROLL;
if (!this.playheadPosition || !this.playerDuration) return adType;
if (this.playheadPosition < 0.5) {
adType = _Utils.AD_TYPES.PREROLL;
} else if (this.playheadPosition > this.playerDuration - 1.5) {
adType = _Utils.AD_TYPES.POSTROLL;
} else {
adType = _Utils.AD_TYPES.MIDROLL;
}
return adType;
}
getSeekable() {
const seekable = this.player.seekable;
if (seekable && seekable.length) {
return seekable;
}
}
/**
* Check whether the player is playing a DVR live stream.
* @returns {boolean}
*/
isDVR() {
return this.isLive() && this.getDuration() >= MINIMUM_DVR_DURATION;
}
/**
* Check whether the given timestamp is at the live point.
*
* @returns {boolean}
*/
isAtLive() {
if (this.isLive()) {
const seekable = this.getSeekable();
const seekableEnd = seekable[seekable.length - 1].end;
return seekableEnd - this.player.currentTime < LIVE_MARGIN;
}
return false;
}
/**
* The reference time ('zero'), used as base for all time displays.
* - For VOD: the start of the video (i.e. start of seekable range)
* - For live: the current live time (i.e. end of seekable range)
*
* @returns {number}
*/
getReferenceTime() {
const seekable = this.getSeekable();
return this.isLive() ? seekable[seekable.length - 1].end : seekable[0].start;
}
/**
* The playable duration.
* - For VOD: always duration
* - For live: the size of the DVR window (i.e. difference between start and end of seekable range)
*
* @returns {number}
*/
getDuration() {
const seekable = this.getSeekable();
try {
return seekable[seekable.length - 1].end - seekable[0].start;
} catch (e) {
return 0;
}
}
/**
* determines if stream is Live+DVR
*/
isLiveDVR() {
return this.isLive() && this.isDVR();
}
/**
* @returns for Live+DVR -> DVR Window Offset, for non-Live+DVR -> current position
*/
getCurrentTime() {
if (this.adsPlaying()) {
return this.player.currentTime;
}
if (this.isLiveDVR()) {
if (this.isAtLive()) {
return 0;
} else {
return Math.abs(this.player.currentTime - this.getReferenceTime());
}
} else {
return this.player.currentTime;
}
}
}
exports.TheoBase = TheoBase;
//# sourceMappingURL=TheoBase.js.map