UNPKG

@theoplayer/react-native-analytics-comscore

Version:
640 lines (582 loc) 15.7 kB
"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