diff --git a/src/kava-event-model.js b/src/kava-event-model.js index 1c2f662a..932fcd69 100644 --- a/src/kava-event-model.js +++ b/src/kava-event-model.js @@ -46,6 +46,14 @@ export const KavaEventModel: {[event: string]: KavaEvent} = { eventModel.targetBuffer = model.getTargetBuffer(); } + if (model.getNetworkConnectionType() !== '') { + eventModel.networkConnectionType = model.getNetworkConnectionType(); + } + + if (model.getNetworkConnectionOverhead()) { + eventModel.networkConnectionOverhead = model.getNetworkConnectionOverhead(); + } + return eventModel; } }, diff --git a/src/kava-model.js b/src/kava-model.js index 294dcfce..4fe3ce3f 100644 --- a/src/kava-model.js +++ b/src/kava-model.js @@ -29,6 +29,8 @@ class KavaModel { droppedFramesRatio: ?number = null; soundMode: typeof SoundMode; tabMode: typeof TabMode; + maxNetworkConnectionOverhead: number = 0; + networkConnectionType: string; playerJSLoadTime: ?number = null; getActualBitrate: Function; getAverageBitrate: Function; @@ -224,6 +226,27 @@ class KavaModel { return this.tabMode; } + /** + * Gets the effectiveType read-only property of the NetworkInformation interface (from navigator) + * @returns {string} the effective type of the connection meaning one of 'slow-2g', '2g', '3g', or '4g' + * @memberof KavaModel + * @instance + */ + getNetworkConnectionType(): string { + return this.networkConnectionType; + } + + /** + * Gets the max dns+ssl+tcp resolving time over all video segments + * @returns {number} max dns+ssl+tcp in seconds + * @memberof KavaModel + * @instance + */ + getNetworkConnectionOverhead(): number { + // convert ms to seconds in 0.xxx format + return Math.round(this.maxNetworkConnectionOverhead) / 1000; + } + /** * Gets the error code. * @returns {number} - The error code. diff --git a/src/kava.js b/src/kava.js index 2274e95c..71210ab2 100644 --- a/src/kava.js +++ b/src/kava.js @@ -380,7 +380,11 @@ class Kava extends BasePlugin { tabMode: this._isDocumentHidden() ? TabMode.TAB_NOT_FOCUSED : TabMode.TAB_FOCUSED, forwardBufferHealth: this._getForwardBufferHealth(), targetBuffer: this._getTargetBuffer(), - droppedFramesRatio: this._getDroppedFramesRatio() + droppedFramesRatio: this._getDroppedFramesRatio(), + networkConnectionType: + window.navigator && window.navigator.connection && window.navigator.connection.effectiveType + ? window.navigator.connection.effectiveType + : '' }); this._sendAnalytics(KavaEventModel.VIEW); } else { @@ -389,6 +393,9 @@ class Kava extends BasePlugin { this._model.updateModel({ totalSegmentsDownloadTime: 0, totalSegmentsDownloadBytes: 0, + maxManifestDownloadTime: 0, + maxSegmentDownloadTime: 0, + maxNetworkConnectionOverhead: 0, bufferTime: 0 }); } @@ -470,10 +477,17 @@ class Kava extends BasePlugin { _onFragLoaded(event: FakeEvent): void { const seconds = Math.round(event.payload.miliSeconds) / 1000; + const fragResourceTimings = performance && performance.getEntriesByType('resource').filter(entry => entry.name == event.payload.url); + const lastFragResourceTiming: ?Object = + fragResourceTimings && fragResourceTimings.length ? fragResourceTimings[fragResourceTimings.length - 1] : null; + this._model.updateModel({ totalSegmentsDownloadTime: this._model.totalSegmentsDownloadTime + seconds, totalSegmentsDownloadBytes: this._model.totalSegmentsDownloadBytes + event.payload.bytes, - maxSegmentDownloadTime: Math.max(seconds, this._model.maxSegmentDownloadTime) + maxSegmentDownloadTime: Math.max(seconds, this._model.maxSegmentDownloadTime), + maxNetworkConnectionOverhead: lastFragResourceTiming + ? Math.max(this._model.maxNetworkConnectionOverhead, lastFragResourceTiming.connectEnd - lastFragResourceTiming.domainLookupStart) + : 0 }); } diff --git a/test/src/kava-event-model.spec.js b/test/src/kava-event-model.spec.js index d62ba4d5..7a362064 100644 --- a/test/src/kava-event-model.spec.js +++ b/test/src/kava-event-model.spec.js @@ -88,6 +88,14 @@ class FakeModel { getTargetBuffer() { return 30; } + + getNetworkConnectionType() { + return '4g'; + } + + getNetworkConnectionOverhead() { + return '0.12'; + } } describe('KavaEventModel', () => { @@ -111,7 +119,9 @@ describe('KavaEventModel', () => { tabMode: fakeModel.getTabMode(), segmentDownloadTime: fakeModel.getSegmentDownloadTime(), forwardBufferHealth: fakeModel.getForwardBufferHealth(), - targetBuffer: fakeModel.getTargetBuffer() + targetBuffer: fakeModel.getTargetBuffer(), + networkConnectionType: fakeModel.getNetworkConnectionType(), + networkConnectionOverhead: fakeModel.getNetworkConnectionOverhead() }); }); diff --git a/test/src/kava.spec.js b/test/src/kava.spec.js index 7399d70f..38183386 100644 --- a/test/src/kava.spec.js +++ b/test/src/kava.spec.js @@ -163,6 +163,51 @@ describe('KavaPlugin', function() { player.play(); }); + it('should send IMPRESSION event with playerJSLoadTime', done => { + sandbox.stub(window.performance, 'getEntriesByType').callsFake(() => { + return [ + { + name: 'https://qa-apache-php7.dev.kaltura.com/p/1091/sp/109100/embedPlaykitJs/uiconf_id/15215933/partner_id/1091/versions/', + entryType: 'resource', + startTime: 118.6400000001413, + duration: 149.8900000001413, + initiatorType: 'script', + nextHopProtocol: 'http/1.1', + workerStart: 0, + redirectStart: 0, + redirectEnd: 0, + fetchStart: 118.6400000001413, + domainLookupStart: 0, + domainLookupEnd: 0, + connectStart: 0, + connectEnd: 0, + secureConnectionStart: 0, + requestStart: 0, + responseStart: 0, + responseEnd: 268.5300000002826, + transferSize: 0, + encodedBodySize: 0, + decodedBodySize: 0, + serverTiming: [] + } + ]; + }); + sandbox.stub(OVPAnalyticsService, 'trackEvent').callsFake((serviceUrl, params) => { + if (params.eventType == KavaEventModel.IMPRESSION.index) { + validateCommonParams(params, KavaEventModel.IMPRESSION.index); + params.playerJSLoadTime.should.equal(149.89); + done(); + } + return new RequestBuilder(); + }); + + let configClone = JSON.parse(JSON.stringify(config)); + configClone.plugins.kava.uiConfId = 15215933; + setupPlayer(configClone); + kava = getKavaPlugin(); + player.play(); + }); + it('should send PLAY_REQUEST event', done => { sandbox.stub(OVPAnalyticsService, 'trackEvent').callsFake((serviceUrl, params) => { if (params.eventType === KavaEventModel.PLAY_REQUEST.index) { @@ -432,6 +477,8 @@ describe('KavaPlugin', function() { }); it('should send VIEW event', done => { + sandbox.stub(window.navigator.connection, 'effectiveType').value('2g'); + sandbox.stub(OVPAnalyticsService, 'trackEvent').callsFake((serviceUrl, params) => { if (params.eventType === KavaEventModel.VIEW.index) { validateCommonParams(params, KavaEventModel.VIEW.index); @@ -458,8 +505,10 @@ describe('KavaPlugin', function() { 'referrer', 'sessionId', 'soundMode', - 'tabMode' + 'tabMode', + 'networkConnectionType' ); + params.networkConnectionType.should.equal('2g'); params.tabMode.should.equal(TabMode.TAB_FOCUSED); params.soundMode.should.equal(SoundMode.SOUND_ON); done();