create watchman plugin
create skeleton boilerplate add plugin logic add x-powered-by extraction logic
This commit is contained in:
parent
248bb406e9
commit
4dbb506e77
2 changed files with 217 additions and 0 deletions
|
@ -0,0 +1,209 @@
|
||||||
|
// Created by xander on 7/10/2021
|
||||||
|
import videojs from 'video.js';
|
||||||
|
const VERSION = '0.0.1';
|
||||||
|
|
||||||
|
const watchmanEndpoint = 'https://watchman.na-backend.dev.odysee.com';
|
||||||
|
|
||||||
|
let previousEventTime = Date.now();
|
||||||
|
|
||||||
|
/* Watchman */
|
||||||
|
function createWatchmanData(
|
||||||
|
url,
|
||||||
|
duration,
|
||||||
|
position,
|
||||||
|
relPosition,
|
||||||
|
rebufCount,
|
||||||
|
rebufDuration,
|
||||||
|
format,
|
||||||
|
player,
|
||||||
|
userId,
|
||||||
|
device
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
url: url,
|
||||||
|
duration: duration,
|
||||||
|
rel_position: relPosition,
|
||||||
|
rebuf_count: rebufCount,
|
||||||
|
rebuf_duration: rebufDuration,
|
||||||
|
format: format,
|
||||||
|
player: player,
|
||||||
|
user_id: userId,
|
||||||
|
device: device,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendWatchmanData(watchmanData) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'text/plain' }, // application/json
|
||||||
|
body: JSON.stringify(watchmanData),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
fetch(watchmanEndpoint, requestOptions)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
// Response data
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Response error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin default options
|
||||||
|
const defaults = {
|
||||||
|
endpoint: watchmanEndpoint,
|
||||||
|
reportRate: 15,
|
||||||
|
videoUrl: null,
|
||||||
|
userId: 0,
|
||||||
|
debug: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Component = videojs.getComponent('Component');
|
||||||
|
const registerPlugin = videojs.registerPlugin || videojs.plugin;
|
||||||
|
|
||||||
|
class WatchmanPlugin extends Component {
|
||||||
|
constructor(player, options) {
|
||||||
|
super(player, options);
|
||||||
|
|
||||||
|
// Plugin started
|
||||||
|
if (options.debug) {
|
||||||
|
this.log(`Created watchman plugin for: videoUrl:${options.videoUrl}, userId:${options.userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin variables
|
||||||
|
this.player = player;
|
||||||
|
this.loadedAt = Date.now();
|
||||||
|
this.watchmanIntervalId = null;
|
||||||
|
this.bufferEventData = [];
|
||||||
|
this.poweredBy = '';
|
||||||
|
|
||||||
|
// Plugin event listeners
|
||||||
|
player.on('tracking:firstplay', (event, data) => this.onTrackingFirstPlay(event, data));
|
||||||
|
player.on('tracking:buffered', (event, data) => this.onTrackingBuffered(event, data));
|
||||||
|
|
||||||
|
// Event trigger to send recsys event
|
||||||
|
player.on('dispose', (event) => this.onDispose(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendWatchmanData() {
|
||||||
|
const processedData = this.bufferEventData.reduce(
|
||||||
|
(accumulator, current) => {
|
||||||
|
// Always update with the most current player x-powered-by header
|
||||||
|
if (current.hasOwnProperty('playerPoweredBy') && current.playerPoweredBy) {
|
||||||
|
this.poweredBy = current.playerPoweredBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
secondsToLoad: accumulator.secondsToLoad + current.secondsToLoad,
|
||||||
|
bufferCount: accumulator.bufferCount + current.bufferCount,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
secondsToLoad: 0,
|
||||||
|
bufferCount: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const timeSinceLastEvent = Date.now() - previousEventTime;
|
||||||
|
previousEventTime = Date.now();
|
||||||
|
|
||||||
|
const currentTime = this.player.currentTime();
|
||||||
|
const totalTime = this.player.duration();
|
||||||
|
|
||||||
|
const event = createWatchmanData(
|
||||||
|
this.options_.videoUrl,
|
||||||
|
timeSinceLastEvent,
|
||||||
|
currentTime,
|
||||||
|
currentTime / totalTime,
|
||||||
|
processedData.bufferCount,
|
||||||
|
processedData.secondsToLoad * 1000,
|
||||||
|
this.player.currentSource().type,
|
||||||
|
this.poweredBy,
|
||||||
|
this.options_.userId,
|
||||||
|
navigator.userAgent
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.options_.debug) {
|
||||||
|
console.log('[watchman] Data Payload', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendWatchmanData(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTrackingFirstPlay(event, data) {
|
||||||
|
// data attr: secondsToLoad
|
||||||
|
if (this.options_.debug) {
|
||||||
|
console.log(`[watchman] First Play Data:`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start analytics interval here
|
||||||
|
this.watchmanIntervalId = setInterval(() => this.onWatchmanInterval(), this.options_.reportRate * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTrackingBuffered(event, data) {
|
||||||
|
const duration = this.player.duration();
|
||||||
|
|
||||||
|
const reportData = {
|
||||||
|
...data,
|
||||||
|
relativePosition: data.currentTime / duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
// data attr: currentTime, readyState, secondsToLoad, bufferCount
|
||||||
|
if (this.options_.debug) {
|
||||||
|
console.log(`[watchman] Buffer Data:`, reportData);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bufferEventData.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWatchmanInterval() {
|
||||||
|
// don't report while player is paused
|
||||||
|
if (this.player.paused()) return;
|
||||||
|
|
||||||
|
// process and send watchman data here
|
||||||
|
if (this.options_.debug) {
|
||||||
|
console.log('[watchman] interval', this.bufferEventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendWatchmanData();
|
||||||
|
|
||||||
|
// clear processed data
|
||||||
|
this.bufferEventData = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
onDispose(event) {
|
||||||
|
// Stop analytics interval
|
||||||
|
clearInterval(this.watchmanIntervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(...args) {
|
||||||
|
if (this.options_.debug) {
|
||||||
|
console.log(`[watchman] Debug:`, JSON.stringify(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videojs.registerComponent('watchman', WatchmanPlugin);
|
||||||
|
|
||||||
|
const onPlayerReady = (player, options) => {
|
||||||
|
player.recsys = new WatchmanPlugin(player, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin.
|
||||||
|
*
|
||||||
|
* @function plugin
|
||||||
|
* @param {Object} [options={}]
|
||||||
|
*/
|
||||||
|
const plugin = function (options) {
|
||||||
|
this.ready(() => {
|
||||||
|
onPlayerReady(this, videojs.mergeOptions(defaults, options));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin.VERSION = VERSION;
|
||||||
|
|
||||||
|
registerPlugin('watchman', plugin);
|
||||||
|
|
||||||
|
export default plugin;
|
|
@ -11,6 +11,7 @@ import * as OVERLAY from './overlays';
|
||||||
import './plugins/videojs-mobile-ui/plugin';
|
import './plugins/videojs-mobile-ui/plugin';
|
||||||
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
|
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
|
||||||
import recsys from './plugins/videojs-recsys/plugin';
|
import recsys from './plugins/videojs-recsys/plugin';
|
||||||
|
import './plugins/videojs-watchman/plugin';
|
||||||
import qualityLevels from 'videojs-contrib-quality-levels';
|
import qualityLevels from 'videojs-contrib-quality-levels';
|
||||||
import isUserTyping from 'util/detect-typing';
|
import isUserTyping from 'util/detect-typing';
|
||||||
// import './plugins/videojs-aniview/plugin';
|
// import './plugins/videojs-aniview/plugin';
|
||||||
|
@ -570,6 +571,13 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
player.watchman({
|
||||||
|
reportRate: 15,
|
||||||
|
videoUrl: claimId,
|
||||||
|
userId: userId,
|
||||||
|
debug: true,
|
||||||
|
});
|
||||||
|
|
||||||
// set playsinline for mobile
|
// set playsinline for mobile
|
||||||
// TODO: make this better
|
// TODO: make this better
|
||||||
player.children_[0].setAttribute('playsinline', '');
|
player.children_[0].setAttribute('playsinline', '');
|
||||||
|
|
Loading…
Reference in a new issue