Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEC-8975 QoS data enhancement #35

Merged
merged 20 commits into from
Jun 16, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions src/kava-event-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,41 @@ export const KavaEventModel: {[event: string]: KavaEvent} = {
VIEW: {
type: 'VIEW',
index: 99,
getEventModel: (model: KavaModel) => ({
playTimeSum: model.getPlayTimeSum(),
bufferTime: model.getBufferTime(),
bufferTimeSum: model.getBufferTimeSum(),
actualBitrate: model.getActualBitrate(),
averageBitrate: model.getAverageBitrate(),
audioLanguage: model.getLanguage(),
captionsLanguage: model.getCaption()
})
getEventModel: (model: KavaModel) => {
const retval = {
OrenMe marked this conversation as resolved.
Show resolved Hide resolved
playTimeSum: model.getPlayTimeSum(),
bufferTime: model.getBufferTime(),
bufferTimeSum: model.getBufferTimeSum(),
actualBitrate: model.getActualBitrate(),
averageBitrate: model.getAverageBitrate(),
audioLanguage: model.getLanguage(),
captionsLanguage: model.getCaption(),
soundMode: model.getSoundMode(),
tabMode: model.getTabMode()
};

if (!isNaN(model.getForwardBufferHealth())) {
retval.forwardBufferHealth = model.getForwardBufferHealth();
}
if (model.getMaxManifestDownloadTime() != null) {
retval.manifestDownloadTime = model.getMaxManifestDownloadTime();
}
if (model.getSegmentDownloadTime() != null) {
retval.segmentDownloadTime = model.getSegmentDownloadTime();
}
if (model.getBandwidth()) {
retval.bandwidth = model.getBandwidth();
}
if (model.getDroppedFramesRatio() != null) {
retval.droppedFramesRatio = model.getDroppedFramesRatio();
}

if (!isNaN(model.getTargetBuffer())) {
retval.targetBuffer = model.getTargetBuffer();
}

return retval;
}
},
/**
* @type {string} IMPRESSION
Expand All @@ -30,7 +56,13 @@ export const KavaEventModel: {[event: string]: KavaEvent} = {
IMPRESSION: {
type: 'IMPRESSION',
index: 1,
getEventModel: () => ({})
getEventModel: (model: KavaModel) => {
const retval = {};
if (model.getPlayerJSLoadTime() != null) {
retval.playerJSLoadTime = model.getPlayerJSLoadTime();
}
return retval;
}
},
/**
* @type {string} PLAY_REQUEST
Expand Down
115 changes: 114 additions & 1 deletion src/kava-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ class KavaModel {
joinTime: number;
canPlayTime: number;
targetPosition: number;
targetBuffer: number;
totalSegmentsDownloadTime: number = 0;
totalSegmentsDownloadBytes: number = 0;
maxSegmentDownloadTime: ?number = null;
maxManifestDownloadTime: ?number = null;
forwardBufferHealth: number;
droppedFramesRatio: ?number = null;
soundMode: SoundMode;
tabMode: TabMode;
playerJSLoadTime: ?number = null;
getActualBitrate: Function;
getAverageBitrate: Function;
getPartnerId: Function;
Expand Down Expand Up @@ -80,6 +90,20 @@ class KavaModel {
return this.bufferTimeSum;
}

/**
* Gets the player bundle js load duration time
* @returns {number} - The player js load duration time
* @memberof KavaModel
* @instance
*/
getPlayerJSLoadTime(): ?number {
if (this.playerJSLoadTime) {
return Math.round(this.playerJSLoadTime * 1000) / 1000;
} else {
return null;
}
}

/**
* Gets the join time.
* @returns {number} - The join time.
Expand All @@ -100,6 +124,16 @@ class KavaModel {
return this.targetPosition;
}

/**
* Gets the target buffer
* @returns {number} - The target buffer in seconds.
* @memberof KavaModel
* @instance
*/
getTargetBuffer(): number {
return this.targetBuffer;
}

/**
* Gets an audio language.
* @returns {string} - The audio language.
Expand All @@ -120,6 +154,75 @@ class KavaModel {
return this.caption;
}

/**
* Gets the average bandwidth since last report.
* @returns {number} - The bandwidth in kbps
* @memberof KavaModel
* @instance
*/
getBandwidth(): number {
return this.totalSegmentsDownloadTime > 0 ? Math.round((this.totalSegmentsDownloadBytes * 8) / this.totalSegmentsDownloadTime) / 1000 : 0;
}

/**
* Returns the longest manifest download time in seconds
* @returns {number} - manifest max download time in seconds
* @memberof KavaModel
* @instance
*/
getMaxManifestDownloadTime(): ?number {
return this.maxManifestDownloadTime;
}

/**
* Returns the longest segment download time in seconds
* @returns {number} - segment max download time in seconds
* @memberof KavaModel
* @instance
*/
getSegmentDownloadTime(): ?number {
return this.maxSegmentDownloadTime;
}

/**
* Gets the forward buffer health ratio.
* @returns {number} - the ratio between the available buffer and the target buffer
* @memberof KavaModel
* @instance
*/
getForwardBufferHealth(): number {
return this.forwardBufferHealth;
}
/**
* Gets the dropped frames ratio since last view event.
* @returns {number} - dropped frames ratio since last view event
* @memberof KavaModel
* @instance
*/
getDroppedFramesRatio(): ?number {
return this.droppedFramesRatio;
}

/**
* Gets the sound mode of the player.
* @returns {SoundMode} the state of the sound (muted ot not)
* @memberof KavaModel
* @instance
*/
getSoundMode(): SoundMode {
return this.soundMode;
}

/**
* Gets the Tab mode of the browser.
* @returns {TabMode} the state of the tab (focused or not)
* @memberof KavaModel
* @instance
*/
getTabMode(): TabMode {
return this.tabMode;
}

/**
* Gets the error code.
* @returns {number} - The error code.
Expand Down Expand Up @@ -173,4 +276,14 @@ class KavaModel {
}
}

export {KavaModel};
const SoundMode = {
SOUND_OFF: 1,
SOUND_ON: 2
};

const TabMode = {
TAB_NOT_FOCUSED: 1,
TAB_FOCUSED: 2
};

export {KavaModel, SoundMode, TabMode};
130 changes: 128 additions & 2 deletions src/kava.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {OVPAnalyticsService} from 'playkit-js-providers/dist/playkit-analytics-s
import {KavaEventModel, KavaEventType} from './kava-event-model';
import {KavaRateHandler} from './kava-rate-handler';
import {KavaTimer} from './kava-timer';
import {KavaModel} from './kava-model';
import {KavaModel, SoundMode, TabMode} from './kava-model';

const DIVIDER: number = 1024;

Expand All @@ -30,6 +30,8 @@ class Kava extends BasePlugin {
_timePercentEvent: {[time: string]: boolean};
_isPlaying: boolean;
_loadStartTime: number;
_lastDroppedFrames: number = 0;
_lastTotalFrames: number = 0;

/**
* Default config of the plugin.
Expand Down Expand Up @@ -73,6 +75,14 @@ class Kava extends BasePlugin {
bufferTimeSum: 0.0,
playTimeSum: 0.0
});

// check the Resource Timing API is supported in the browser and we have a uiConfId
if (performance && this.config && this.config.uiConfId) {
OrenMe marked this conversation as resolved.
Show resolved Hide resolved
let entries = performance.getEntriesByType('resource').filter(entry => entry.name.match('embedPlaykitJs.*' + this.config.uiConfId));
OrenMe marked this conversation as resolved.
Show resolved Hide resolved
if (entries.length > 0) {
this._model.updateModel({playerJSLoadTime: entries[entries.length - 1].duration});
}
}
}

/**
Expand Down Expand Up @@ -228,6 +238,8 @@ class Kava extends BasePlugin {
this.eventManager.listen(this.player, this.player.Event.SOURCE_SELECTED, () => this._onSourceSelected());
this.eventManager.listen(this.player, this.player.Event.ERROR, event => this._onError(event));
this.eventManager.listen(this.player, this.player.Event.FIRST_PLAY, () => this._onFirstPlay());
this.eventManager.listen(this.player, this.player.Event.FRAG_LOADED, event => this._onFragLoaded(event));
this.eventManager.listen(this.player, this.player.Event.MANIFEST_LOADED, event => this._onManifestLoaded(event));
this.eventManager.listen(this.player, this.player.Event.TRACKS_CHANGED, () => this._setInitialTracks());
this.eventManager.listen(this.player, this.player.Event.PLAYING, () => this._onPlaying());
this.eventManager.listen(this.player, this.player.Event.FIRST_PLAYING, () => this._onFirstPlaying());
Expand Down Expand Up @@ -273,14 +285,112 @@ class Kava extends BasePlugin {
}
}

/**
* gets the available buffer length
* @returns {number} the remaining buffer length of the current played time range
* @private
*/
_getAvailableBuffer(): number {
let retVal = NaN;
if (this.player.stats) {
retVal = this.player.stats.availableBuffer;
}
return retVal;
}

/**
* calculates the forward buffer health ratio
* @returns {number} the ratio between available buffer and the target buffer
* @private
*/
_getForwardBufferHealth(): number {
let retVal = NaN;
let availableBuffer = this._getAvailableBuffer();
let targetBuffer = this._getTargetBuffer();

if (!isNaN(targetBuffer)) {
// considering playback left to the target calculation
retVal = Math.round((availableBuffer * 1000) / targetBuffer) / 1000;
}

return retVal;
}

/**
* get the target buffer length from the player
* @returns {number} the target buffer in seconds
* @private
*/
_getTargetBuffer(): number {
let retVal = NaN;
if (this.player.stats) {
retVal = this.player.stats.targetBuffer;
}
return retVal;
}

/**
* calculates the dropped frames ratio since last call
* @returns {number} the ratio between dropped frames and the total frames
* @private
*/
_getDroppedFramesRatio(): number {
let retVal = -1;
const droppedAndDecoded: ?[number, number] = this._getDroppedAndDecodedFrames();
if (droppedAndDecoded) {
let droppedFramesDelta: number;
let totalFramesDelta: number;
const lastDroppedFrames = droppedAndDecoded[0];
const lastTotalFrames = droppedAndDecoded[1];
droppedFramesDelta = lastDroppedFrames - this._lastDroppedFrames;
totalFramesDelta = lastTotalFrames - this._lastTotalFrames;
retVal = Math.round((droppedFramesDelta / totalFramesDelta) * 1000) / 1000;

this._lastTotalFrames = lastTotalFrames;
this._lastDroppedFrames = lastDroppedFrames;
}
return retVal;
}

/**
* returns dropped and total frames from the VideoPlaybackQuality Interface of the browser (if supported)
* @returns {number} {number} the number of video frames dropped and total video frames (created and dropped)
* since the creation of the associated HTMLVideoElement
* @private
*/
_getDroppedAndDecodedFrames(): ?[number, number] {
if (typeof this.player.getVideoElement().getVideoPlaybackQuality === 'function') {
const videoPlaybackQuality = this.player.getVideoElement().getVideoPlaybackQuality();
return [videoPlaybackQuality.droppedVideoFrames, videoPlaybackQuality.totalVideoFrames];
} else if (
typeof this.player.getVideoElement().webkitDroppedFrameCount == 'number' &&
typeof this.player.getVideoElement().webkitDecodedFrameCount == 'number'
) {
return [this.player.getVideoElement().webkitDroppedFrameCount, this.player.getVideoElement().webkitDecodedFrameCount];
} else {
return null;
}
}

_onReport(): void {
if (this._viewEventEnabled) {
this._updatePlayTimeSumModel();
this._model.updateModel({
soundMode: this.player.muted || this.player.volume === 0 ? SoundMode.SOUND_OFF : SoundMode.SOUND_ON,
tabMode: document.hasFocus() ? TabMode.TAB_FOCUSED : TabMode.TAB_NOT_FOCUSED,
forwardBufferHealth: this._getForwardBufferHealth(),
targetBuffer: this._getTargetBuffer(),
droppedFramesRatio: this._getDroppedFramesRatio()
});
this._sendAnalytics(KavaEventModel.VIEW);
} else {
this.logger.warn(`VIEW event blocked because server response of viewEventsEnabled=false`);
}
this._model.updateModel({bufferTime: 0});
this._model.updateModel({
totalSegmentsDownloadTime: 0,
totalSegmentsDownloadBytes: 0,
bufferTime: 0
});
}

_onPlaying(): void {
Expand Down Expand Up @@ -358,6 +468,22 @@ class Kava extends BasePlugin {
}
}

_onFragLoaded(event: FakeEvent): void {
const seconds = Math.round(event.payload.miliSeconds) / 1000;
this._model.updateModel({
totalSegmentsDownloadTime: this._model.totalSegmentsDownloadTime + seconds,
totalSegmentsDownloadBytes: this._model.totalSegmentsDownloadBytes + event.payload.bytes,
maxSegmentDownloadTime: Math.max(seconds, this._model.maxSegmentDownloadTime)
});
}

_onManifestLoaded(event: FakeEvent): void {
const seconds = Math.round(event.payload.miliSeconds) / 1000;
this._model.updateModel({
maxManifestDownloadTime: Math.max(seconds, this._model.maxManifestDownloadTime)
});
}

_onVideoTrackChanged(event: FakeEvent): void {
const videoTrack = event.payload.selectedVideoTrack;
this._rateHandler.setCurrent(videoTrack.bandwidth / DIVIDER);
Expand Down
Loading