63537 - Heartbeat and metrics WiP

This commit is contained in:
Juraldinio
2025-03-09 22:10:07 +03:00
parent f44feb7fd6
commit 8c33482ff7
6 changed files with 785 additions and 7 deletions
+6 -2
View File
@@ -2,14 +2,18 @@
FROM arm64v8/node:23.6.1-alpine3.21 AS arm
#FROM node:23.6.1-alpine3.21 AS intel
# Global npm dependencies for correct caching docker layers
RUN <<EOF
npm install -g nodemon;
npm install -g lite-server;
EOF
WORKDIR /usr/voka
COPY package*.json .
COPY develop.sh .
RUN <<EOF
npm install -g nodemon;
npm install -g lite-server;
npm install;
chmod +x develop.sh;
EOF
+1 -1
View File
@@ -4,6 +4,6 @@
"VokaStatisticsPlugin": false,
"VokaQualityPlugin": false,
"VokaCaptionsPlugin": false,
"VokaKeyboard": true
"VokaKeyboardPlugin": true
}
}
+4 -2
View File
@@ -10,7 +10,7 @@ import '@/internal/player/native/mp4/tech/VokaMp4Tech'
import '@/internal/player/native/apple/tech/VokaAppleTech'
import '@/internal/player/native/hls/tech/VokaHlsTech'
import '@/internal/player/native/dash/tech/VokaDashTech'
import '@/plugins/VokaKeyboard'
import '@/plugins/VokaKeyboardPlugin'
import '@/components/Skin'
@@ -95,7 +95,9 @@ export class VokaPlayerImpl implements IVokaPlayer {
//plugins.vokaStatisticsPlugin = {}
//plugins.vokaAdvertisementPlugin = {}
//plugins.vokaCaptionsPlugin = {}
plugins.vokaKeyboard = {}
plugins.vokaKeyboardPlugin = {}
plugins.vokaHeartbeatPlugin = {} // Хартбит Player.Heartbeat
plugins.vokaMetricsPlugin = {} // Метрики Player.Metrics
const childrenComponents = [
// Non-visual component, not in UI layer list.
+336
View File
@@ -0,0 +1,336 @@
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
import VokaEvent from "@/constants/VokaEvent"
const Plugin = videojs.getPlugin('plugin')
export class VokaHeartbeatPlugin extends Plugin {
private player!: VideoJsPlayer
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
super(player, options)
this.player = player
// this.setupListeners()
}
}
videojs.registerPlugin('vokaHeartbeatPlugin', VokaHeartbeatPlugin)
/*
Player.Heartbeat = function (_global, _options, _player) {
'use strict';
var global = _global;
var options = _options;
var player = _player;
var playerListenerInitialized = false;
var streamOptions = null;
var playbackStarted = false;
var progressRequest = null;
var startRequest = null;
var endRequest = null;
var scheduledProgress = undefined;
function abortProgressRequest() {
if (!progressRequest) {
return;
}
progressRequest.abort();
progressRequest = null;
}
function abortStartRequest() {
if (!startRequest) {
return;
}
startRequest.abort();
startRequest = null;
}
function detachEndRequest() {
if (!endRequest) {
return;
}
endRequest.onload = null;
endRequest.onerror = null;
endRequest.ontimeout = null;
endRequest = null;
}
function createRequest(reqType) {
var action;
var needTime, needDuration;
var time, duration;
var url;
var firstParam;
var request;
function addParam(name, value) {
if (firstParam && url.indexOf('?') >= 0) {
firstParam = false;
}
if (firstParam) {
url += '?';
firstParam = false;
} else {
url += '&';
}
url += encodeURIComponent(name);
url += '=';
url += encodeURIComponent(value);
}
needTime = false;
needDuration = false;
switch (reqType) {
case 'start':
action = 'start';
needTime = true;
needDuration = true;
break;
case 'end':
action = 'end';
needTime = true;
needDuration = true;
break;
case 'progress':
action = 'watch';
needTime = true;
needDuration = true;
break;
default:
action = null;
break;
}
if (!action) {
return;
}
if (needTime) {
time = player.getCurrentTime();
} else {
time = NaN;
}
if (needDuration && !player.getIsLive()) {
duration = player.getDuration();
} else {
duration = NaN;
}
request = new XMLHttpRequest();
switch (reqType) {
case 'start':
startRequest = request;
break;
case 'end':
endRequest = request;
break;
case 'progress':
default:
progressRequest = request;
break;
}
request.onload = onRequestLoad;
request.onerror = onRequestError;
request.ontimeout = onRequestTimeout;
url = streamOptions.heartbeat.url;
firstParam = true;
if (!isNaN(streamOptions.heartbeat.version)) {
addParam('v', streamOptions.heartbeat.version.toString());
}
addParam('action', action);
if (!isNaN(time)) {
addParam('timestamp', Math.round(time * 1000).toString());
}
if (!isNaN(duration)) {
addParam('duration', Math.round(duration * 1000).toString());
}
request.open('GET', url, true);
//request.withCredentials = true;
request.timeout = 60000;
request.send();
}
function createProgressRequest() {
abortProgressRequest();
progressRequest = createRequest('progress');
}
function scheduleProgressRequest() {
unscheduleProgressRequest();
scheduledProgress = Utils.setInterval(createProgressRequest, streamOptions.heartbeat.interval * 1000);
}
function unscheduleProgressRequest() {
if (scheduledProgress === undefined) {
return;
}
clearInterval(scheduledProgress);
scheduledProgress = undefined;
}
function removeRequest(e) {
var request;
if (!e) {
return;
}
request = e.target;
if (!request) {
return;
}
if (request === progressRequest) {
progressRequest = null;
} else if (request === startRequest) {
startRequest = null;
} else if (request === endRequest) {
endRequest = null;
}
}
function onRequestLoad(e) {
removeRequest(e);
}
function onRequestError(e) {
removeRequest(e);
}
function onRequestTimeout(e) {
removeRequest(e);
}
function onSourceAttached(e) {
onPlaybackEnded(null);
streamOptions = e.streamOptions;
playbackStarted = false;
}
function onPlaybackStarted(e) {
if (!streamOptions || !streamOptions.heartbeat.url) {
return;
}
if (playbackStarted) {
return;
}
playbackStarted = true;
createRequest('start');
scheduleProgressRequest();
}
function onPlaybackEnded(e) {
if (!streamOptions || !streamOptions.heartbeat.url) {
return;
}
if (!playbackStarted) {
return;
}
playbackStarted = false;
createRequest('end');
unscheduleProgressRequest();
}
function onBeforeUnload(e) {
onPlaybackEnded(null);
}
function registerEventListeners() {
if (playerListenerInitialized) {
return;
}
if (!player) {
return;
}
playerListenerInitialized = true;
player.addEventListener('sourceAttached', onSourceAttached);
player.addEventListener('play', onPlaybackStarted);
player.addEventListener('ended', onPlaybackEnded);
window.addEventListener('unload', onBeforeUnload, false);
}
function unregisterEventListeners() {
if (!playerListenerInitialized) {
return;
}
playerListenerInitialized = false;
if (!player) {
return;
}
player.removeEventListener('sourceAttached', onSourceAttached);
player.removeEventListener('play', onPlaybackStarted);
player.removeEventListener('ended', onPlaybackEnded);
window.removeEventListener('unload', onBeforeUnload, false);
}
function initialize() {
registerEventListeners();
}
function destroy() {
unregisterEventListeners();
unscheduleProgressRequest();
abortProgressRequest();
abortStartRequest();
detachEndRequest();
streamOptions = null;
playbackStarted = false;
global = null;
options = null;
player = null;
}
return {
initialize: initialize,
destroy: destroy
};
};
* */
@@ -3,7 +3,7 @@ import keycode from 'keycode'
import VokaEvent from "@/constants/VokaEvent"
const Plugin = videojs.getPlugin('plugin')
export class VokaKeyboard extends Plugin {
export class VokaKeyboardPlugin extends Plugin {
private player!: VideoJsPlayer
@@ -64,4 +64,4 @@ export class VokaKeyboard extends Plugin {
}
}
videojs.registerPlugin('vokaKeyboard', VokaKeyboard)
videojs.registerPlugin('vokaKeyboardPlugin', VokaKeyboardPlugin)
+436
View File
@@ -0,0 +1,436 @@
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
import VokaEvent from "@/constants/VokaEvent"
const Plugin = videojs.getPlugin('plugin')
export class VokaMetricsPlugin extends Plugin {
private player!: VideoJsPlayer
constructor(player: VideoJsPlayer, options: VideoJsPlayerOptions) {
super(player, options)
this.player = player
// this.setupListeners()
}
}
videojs.registerPlugin('vokaMetricsPlugin', VokaMetricsPlugin)
/*
Player.Metrics = function (_global, _options, _player) {
'use strict';
var defaultApiHost = '127.0.0.1';
var global = _global;
var options = _options;
var player = _player;
var playerListenerInitialized = false;
var streamOptions = null;
var playbackInitialized = false;
var playbackStarted = false;
var prevPlayerState = '';
var prevStateTime = 0;
var timeSpent = null;
var scheduledRequest = undefined;
var defaultWatchSessionId = null;
function getApiUrl() {
var result;
if (!streamOptions) {
return null;
}
// if (streamOptions.metrics.apiUrl) {
// return streamOptions.metrics.apiUrl;
// }
//
// result = streamOptions.metrics.apiHost;
// if (!result) {
result = defaultApiHost;
// }
if (result.indexOf('//') < 0) {
result = 'https://' + result;
}
result = result + '/v2/player';
return result;
}
function getWatchSessionId() {
if (streamOptions && streamOptions.metrics.params.watch_session_id) {
return streamOptions.metrics.params.watch_session_id;
}
return defaultWatchSessionId;
}
function getPlayerState() {
if (!player) {
return '';
}
if (!playbackInitialized) {
return 'initial_buffering';
}
if (!playbackStarted) {
return 'paused';
}
if (player.getPaused()) {
return 'paused';
}
if (player.getBufferingState()) {
return 'freezed';
}
return 'playing';
}
function updateTimeSpent() {
var currTime;
var diffTime;
var propName;
if (!timeSpent) {
timeSpent = {
initializing_player: 0,
initializing_buffer: 0,
playing: 0,
paused: 0,
buffering: 0
};
}
currTime = Utils.getCurrTimeMS();
if (!prevStateTime) {
prevStateTime = currTime;
}
diffTime = currTime - prevStateTime;
prevStateTime = currTime;
switch (prevPlayerState)
{
case 'initialization':
propName = 'initializing_player';
break;
case 'initial_buffering':
propName = 'initializing_buffer';
break;
case 'paused':
propName = 'paused';
break;
case 'freezed':
propName = 'buffering';
break;
case 'playing':
propName = 'playing';
break;
default:
propName = null;
break;
}
if (!propName) {
return;
}
timeSpent[propName] += diffTime;
}
function createRequest(state) {
var url;
var request;
var data;
var timeout;
var playerState;
var osVersion;
var deviceOS;
var bandwidth;
if (!global || !options || !player || !streamOptions) {
return;
}
switch (state)
{
case 'init':
playerState = 'initialization';
break;
case 'buffering':
playerState = 'initial_buffering';
break;
case 'play':
playerState = 'playing';
break;
case 'pause':
playerState = 'paused';
break;
case 'periodic':
case 'update':
default:
playerState = getPlayerState();
break;
}
if (state === 'update' && playerState === prevPlayerState) {
return;
}
updateTimeSpent();
prevPlayerState = playerState;
if (state !== 'periodic') {
return;
}
if (state === 'periodic') {
timeout = streamOptions.metrics.interval * 2 * 1000;
} else {
timeout = 20000;
}
osVersion = Utils.getOSVersion();
deviceOS = osVersion.name.toLowerCase();
switch (deviceOS) {
case 'ios':
case 'android':
case 'windows':
case 'macos':
case 'linux':
break;
default:
deviceOS = 'other';
break;
}
bandwidth = player.getNetworkBandwidth();
if (isNaN(bandwidth)) {
bandwidth = 0;
} else {
bandwidth *= 1000;
}
data = {
application_id: streamOptions.metrics.params.application_id,
application_version: streamOptions.metrics.params.application_version,
user_type: streamOptions.metrics.params.user_type,
user_id: streamOptions.metrics.params.user_id,
device_id: global.getDeviceId(),
device_os: deviceOS,
device_type: 'browser',
device_player_type: 'native',
application_session_id: player.getAppSessionId(),
watch_session_id: getWatchSessionId(),
resource_uid: streamOptions.metrics.params.resource_uid,
resource_type: streamOptions.metrics.params.resource_type,
buffered_duration: Math.floor(player.getBufferLength() * 1000),
bandwidth: Math.floor(bandwidth),
//player_state: playerState,
time_spent: timeSpent,
network_type: 'unknown'
};
data = JSON.stringify(data);
url = getApiUrl();
// request = new XMLHttpRequest();
// request.open('POST', url, true);
//request.withCredentials = true;
// request.timeout = timeout;
// request.send(data);
timeSpent = null;
}
function periodicRequest() {
createRequest('periodic');
}
function scheduleRequest() {
unscheduleRequest();
if (!streamOptions) {
return;
}
scheduledRequest = Utils.setInterval(periodicRequest, streamOptions.metrics.interval * 1000);
}
function unscheduleRequest() {
if (scheduledRequest === undefined) {
return;
}
clearInterval(scheduledRequest);
scheduledRequest = undefined;
}
function scheduleRequestIfNeeded() {
if (scheduledRequest !== undefined) {
return;
}
scheduleRequest();
}
function onSourceAttached(e) {
onPlaybackEnded(null);
streamOptions = e.streamOptions;
playbackInitialized = false;
playbackStarted = false;
prevPlayerState = '';
prevStateTime = 0;
timeSpent = null;
defaultWatchSessionId = Utils.generateGuid();
scheduleRequestIfNeeded();
createRequest('init');
createRequest('buffering');
}
function onPlayerCanplay(e) {
if (!streamOptions) {
return;
}
playbackInitialized = true;
createRequest('update');
scheduleRequestIfNeeded();
}
function onPlaybackStarted(e) {
if (!streamOptions) {
return;
}
playbackInitialized = true;
playbackStarted = true;
scheduleRequestIfNeeded();
createRequest('play');
}
function onPlaybackPaused(e) {
if (!streamOptions) {
return;
}
scheduleRequestIfNeeded();
createRequest('pause');
}
function onPlaybackEnded(e) {
unscheduleRequest();
playbackStarted = false;
}
function onPlayerBufferingUpdate(e) {
if (!streamOptions) {
return;
}
createRequest('update');
}
function registerEventListeners() {
if (playerListenerInitialized) {
return;
}
if (!player) {
return;
}
playerListenerInitialized = true;
player.addEventListener('sourceAttached', onSourceAttached);
player.addEventListener('canplay', onPlayerCanplay);
player.addEventListener('play', onPlaybackStarted);
player.addEventListener('pause', onPlaybackPaused);
player.addEventListener('ended', onPlaybackEnded);
player.addEventListener('bufferingUpdate', onPlayerBufferingUpdate);
}
function unregisterEventListeners() {
if (!playerListenerInitialized) {
return;
}
playerListenerInitialized = false;
if (!player) {
return;
}
player.removeEventListener('sourceAttached', onSourceAttached);
player.removeEventListener('canplay', onPlayerCanplay);
player.removeEventListener('play', onPlaybackStarted);
player.removeEventListener('pause', onPlaybackPaused);
player.removeEventListener('ended', onPlaybackEnded);
player.removeEventListener('bufferingUpdate', onPlayerBufferingUpdate);
}
function initialize() {
registerEventListeners();
}
function destroy() {
unregisterEventListeners();
unscheduleRequest();
defaultWatchSessionId = null;
streamOptions = null;
playbackInitialized = false;
playbackStarted = false;
prevPlayerState = '';
prevStateTime = 0;
timeSpent = null;
global = null;
options = null;
player = null;
}
return {
initialize: initialize,
destroy: destroy
};
};
**/