diff --git a/Dockerfile b/Dockerfile index ec8add2..49d26d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 <= 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 + }; + +}; + +* */ \ No newline at end of file diff --git a/src/plugins/VokaKeyboard.ts b/src/plugins/VokaKeyboardPlugin.ts similarity index 93% rename from src/plugins/VokaKeyboard.ts rename to src/plugins/VokaKeyboardPlugin.ts index 6f2bfc3..85e16d1 100644 --- a/src/plugins/VokaKeyboard.ts +++ b/src/plugins/VokaKeyboardPlugin.ts @@ -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) \ No newline at end of file +videojs.registerPlugin('vokaKeyboardPlugin', VokaKeyboardPlugin) \ No newline at end of file diff --git a/src/plugins/VokaMetricsPlugin.ts b/src/plugins/VokaMetricsPlugin.ts new file mode 100644 index 0000000..34c9fc5 --- /dev/null +++ b/src/plugins/VokaMetricsPlugin.ts @@ -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 + }; + +}; + +**/ \ No newline at end of file