Send video bitrate and user bandwidth to Watchman #7145

Merged
mayeaux merged 11 commits from send-bitrate-and-bandwidth-to-watchman into master 2021-10-06 20:59:34 +02:00
8 changed files with 90 additions and 7 deletions

View file

@ -43,6 +43,8 @@ module.name_mapper='^web\/page\(.*\)$' -> '<PROJECT_ROOT>/web/page\1'
module.name_mapper='^homepage\(.*\)$' -> '<PROJECT_ROOT>/ui/util/homepage\1' module.name_mapper='^homepage\(.*\)$' -> '<PROJECT_ROOT>/ui/util/homepage\1'
module.name_mapper='^scss\/component\(.*\)$' -> '<PROJECT_ROOT>/ui/scss/component/\1' module.name_mapper='^scss\/component\(.*\)$' -> '<PROJECT_ROOT>/ui/scss/component/\1'
esproposal.optional_chaining=enable
; Extensions ; Extensions
module.file_ext=.js module.file_ext=.js
module.file_ext=.jsx module.file_ext=.jsx
@ -51,4 +53,5 @@ module.file_ext=.css
module.file_ext=.scss module.file_ext=.scss
[strict] [strict]

View file

@ -75,6 +75,7 @@
"@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.3.0", "@babel/plugin-proposal-decorators": "^7.3.0",
"@babel/plugin-proposal-object-rest-spread": "^7.6.2", "@babel/plugin-proposal-object-rest-spread": "^7.6.2",
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-flow-strip-types": "^7.2.3", "@babel/plugin-transform-flow-strip-types": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.4.3", "@babel/plugin-transform-runtime": "^7.4.3",

View file

@ -10,6 +10,15 @@ import ElectronCookies from '@exponent/electron-cookies';
import { generateInitialUrl } from 'util/url'; import { generateInitialUrl } from 'util/url';
// @endif // @endif
import { MATOMO_ID, MATOMO_URL } from 'config'; import { MATOMO_ID, MATOMO_URL } from 'config';
// import getConnectionSpeed from 'util/detect-user-bandwidth';
// let userDownloadBandwidthInBitsPerSecond;
// async function getUserBandwidth() {
// userDownloadBandwidthInBitsPerSecond = await getConnectionSpeed();
// }
// get user bandwidth every minute, starting after an initial one minute wait
// setInterval(getUserBandwidth, 1000 * 60);
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev'); const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev');
@ -40,7 +49,7 @@ type Analytics = {
tagFollowEvent: (string, boolean, ?string) => void, tagFollowEvent: (string, boolean, ?string) => void,
playerLoadedEvent: (?boolean) => void, playerLoadedEvent: (?boolean) => void,
playerStartedEvent: (?boolean) => void, playerStartedEvent: (?boolean) => void,
videoStartEvent: (string, number, string, number, string, any) => void, videoStartEvent: (string, number, string, number, string, any, number) => void,
videoIsPlaying: (boolean, any) => void, videoIsPlaying: (boolean, any) => void,
videoBufferEvent: ( videoBufferEvent: (
StreamClaim, StreamClaim,
@ -111,7 +120,7 @@ function getDeviceType() {
// variables initialized for watchman // variables initialized for watchman
let amountOfBufferEvents = 0; let amountOfBufferEvents = 0;
let amountOfBufferTimeInMS = 0; let amountOfBufferTimeInMS = 0;
let videoType, userId, claimUrl, playerPoweredBy, videoPlayer; let videoType, userId, claimUrl, playerPoweredBy, videoPlayer, bitrateAsBitsPerSecond;
let lastSentTime; let lastSentTime;
// calculate data for backend, send them, and reset buffer data for next interval // calculate data for backend, send them, and reset buffer data for next interval
@ -130,6 +139,9 @@ async function sendAndResetWatchmanData() {
let protocol; let protocol;
if (videoType === 'application/x-mpegURL') { if (videoType === 'application/x-mpegURL') {
protocol = 'hls'; protocol = 'hls';
// get bandwidth if it exists from the texttrack (so it's accurate if user changes quality)
// $FlowFixMe
bitrateAsBitsPerSecond = videoPlayer.textTracks?.().tracks_[0]?.activeCues[0]?.value?.bandwidth;
} else { } else {
protocol = 'stb'; protocol = 'stb';
} }
@ -152,6 +164,9 @@ async function sendAndResetWatchmanData() {
user_id: userId.toString(), user_id: userId.toString(),
position: Math.round(positionInVideo), position: Math.round(positionInVideo),
rel_position: Math.round((positionInVideo / (totalDurationInSeconds * 1000)) * 100), rel_position: Math.round((positionInVideo / (totalDurationInSeconds * 1000)) * 100),
bitrate: bitrateAsBitsPerSecond,
bandwidth: undefined,
// ...(userDownloadBandwidthInBitsPerSecond && {bandwidth: userDownloadBandwidthInBitsPerSecond}), // add bandwidth if populated
}; };
// post to watchman // post to watchman
@ -202,7 +217,7 @@ async function sendWatchmanData(body) {
} }
const analytics: Analytics = { const analytics: Analytics = {
// receive buffer events from tracking plugin and jklj // receive buffer events from tracking plugin and save buffer amounts and times for backend call
videoBufferEvent: async (claim, data) => { videoBufferEvent: async (claim, data) => {
amountOfBufferEvents = amountOfBufferEvents + 1; amountOfBufferEvents = amountOfBufferEvents + 1;
amountOfBufferTimeInMS = amountOfBufferTimeInMS + data.bufferDuration; amountOfBufferTimeInMS = amountOfBufferTimeInMS + data.bufferDuration;
@ -240,7 +255,7 @@ const analytics: Analytics = {
startWatchmanIntervalIfNotRunning(); startWatchmanIntervalIfNotRunning();
} }
}, },
videoStartEvent: (claimId, duration, poweredBy, passedUserId, canonicalUrl, passedPlayer) => { videoStartEvent: (claimId, duration, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
// populate values for watchman when video starts // populate values for watchman when video starts
userId = passedUserId; userId = passedUserId;
claimUrl = canonicalUrl; claimUrl = canonicalUrl;
@ -248,6 +263,7 @@ const analytics: Analytics = {
videoType = passedPlayer.currentSource().type; videoType = passedPlayer.currentSource().type;
videoPlayer = passedPlayer; videoPlayer = passedPlayer;
bitrateAsBitsPerSecond = videoBitrate;
sendPromMetric('time_to_start', duration); sendPromMetric('time_to_start', duration);
sendMatomoEvent('Media', 'TimeToStart', claimId, duration); sendMatomoEvent('Media', 'TimeToStart', claimId, duration);

View file

@ -168,9 +168,17 @@ function VideoViewer(props: Props) {
} }
analytics.playerStartedEvent(embedded); analytics.playerStartedEvent(embedded);
// convert bytes to bits, and then divide by seconds
const contentInBits = Number(claim.value.source.size) * 8;
const durationInSeconds = claim.value.video && claim.value.video.duration;
let bitrateAsBitsPerSecond;
if (durationInSeconds) {
bitrateAsBitsPerSecond = Math.round(contentInBits / durationInSeconds);
}
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => { fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
let playerPoweredBy = response.headers.get('x-powered-by') || ''; let playerPoweredBy = response.headers.get('x-powered-by') || '';
analytics.videoStartEvent(claimId, timeToStart, playerPoweredBy, userId, claim.canonical_url, this); analytics.videoStartEvent(claimId, timeToStart, playerPoweredBy, userId, claim.canonical_url, this, bitrateAsBitsPerSecond);
}); });
doAnalyticsView(uri, timeToStart).then(() => { doAnalyticsView(uri, timeToStart).then(() => {

View file

@ -505,6 +505,7 @@ export function doAnalyticsBuffer(uri, bufferData) {
const fileSizeInBits = fileSize * 8; const fileSizeInBits = fileSize * 8;
const bitRate = parseInt(fileSizeInBits / fileDurationInSeconds); const bitRate = parseInt(fileSizeInBits / fileDurationInSeconds);
const userId = user && user.id.toString(); const userId = user && user.id.toString();
// if there's a logged in user, send buffer event data to watchman
if (userId) { if (userId) {
analytics.videoBufferEvent(claim, { analytics.videoBufferEvent(claim, {
timeAtBuffer, timeAtBuffer,

View file

@ -0,0 +1,20 @@
const imageAddr = 'https://upload.wikimedia.org/wikipedia/commons/b/b9/Pizigani_1367_Chart_1MB.jpg';
const downloadSize = 1093957; // this must match with the image above
let startTime, endTime;
async function measureConnectionSpeed() {
startTime = (new Date()).getTime();
const cacheBuster = '?nnn=' + startTime;
const download = new Image();
download.src = imageAddr + cacheBuster;
// this returns when the image is finished downloading
await download.decode();
endTime = (new Date()).getTime();
const duration = (endTime - startTime) / 1000;
const bitsLoaded = downloadSize * 8;
const speedBps = (bitsLoaded / duration).toFixed(2);
return Math.round(Number(speedBps));
}
module.exports = measureConnectionSpeed;

View file

@ -149,7 +149,7 @@ const webConfig = {
test: /\.jsx?$/, test: /\.jsx?$/,
options: { options: {
presets: ['@babel/env', '@babel/react', '@babel/flow'], presets: ['@babel/env', '@babel/react', '@babel/flow'],
plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'], plugins: ['@babel/plugin-proposal-optional-chaining', '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'],
}, },
}, },
{ {

View file

@ -329,6 +329,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
"@babel/helper-plugin-utils@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9"
integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==
"@babel/helper-remap-async-to-generator@^7.12.1": "@babel/helper-remap-async-to-generator@^7.12.1":
version "7.12.1" version "7.12.1"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz#8c4dbbf916314f6047dc05e6a2217074238347fd" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz#8c4dbbf916314f6047dc05e6a2217074238347fd"
@ -390,6 +395,13 @@
dependencies: dependencies:
"@babel/types" "^7.12.1" "@babel/types" "^7.12.1"
"@babel/helper-skip-transparent-expression-wrappers@^7.14.5":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.15.4.tgz#707dbdba1f4ad0fa34f9114fc8197aec7d5da2eb"
integrity sha512-BMRLsdh+D1/aap19TycS4eD1qELGrCBJwzaY9IE8LrpJtJb+H7rQkPIdsfgnMtLBA6DJls7X9z93Z4U8h7xw0A==
dependencies:
"@babel/types" "^7.15.4"
"@babel/helper-split-export-declaration@^7.10.1": "@babel/helper-split-export-declaration@^7.10.1":
version "7.10.1" version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f"
@ -426,6 +438,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
"@babel/helper-validator-identifier@^7.14.9":
version "7.15.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
"@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11": "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11":
version "7.12.11" version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f"
@ -590,6 +607,15 @@
"@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
"@babel/plugin-syntax-optional-chaining" "^7.8.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0"
"@babel/plugin-proposal-optional-chaining@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz#fa83651e60a360e3f13797eef00b8d519695b603"
integrity sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ==
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
"@babel/helper-skip-transparent-expression-wrappers" "^7.14.5"
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
"@babel/plugin-proposal-private-methods@^7.12.1": "@babel/plugin-proposal-private-methods@^7.12.1":
version "7.12.1" version "7.12.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz#86814f6e7a21374c980c10d38b4493e703f4a389" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz#86814f6e7a21374c980c10d38b4493e703f4a389"
@ -694,7 +720,7 @@
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.8.0" "@babel/helper-plugin-utils" "^7.8.0"
"@babel/plugin-syntax-optional-chaining@^7.8.0": "@babel/plugin-syntax-optional-chaining@^7.8.0", "@babel/plugin-syntax-optional-chaining@^7.8.3":
version "7.8.3" version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
@ -1230,6 +1256,14 @@
lodash "^4.17.19" lodash "^4.17.19"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@babel/types@^7.15.4":
version "7.15.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f"
integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==
dependencies:
"@babel/helper-validator-identifier" "^7.14.9"
to-fast-properties "^2.0.0"
"@datapunt/matomo-tracker-js@^0.1.4": "@datapunt/matomo-tracker-js@^0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@datapunt/matomo-tracker-js/-/matomo-tracker-js-0.1.4.tgz#1226f0964d2c062bf9392e9c2fd89838262b10df" resolved "https://registry.yarnpkg.com/@datapunt/matomo-tracker-js/-/matomo-tracker-js-0.1.4.tgz#1226f0964d2c062bf9392e9c2fd89838262b10df"