use new buffer analytics api

This commit is contained in:
Sean Yesmunt 2020-08-07 16:59:20 -04:00
parent e5b1177644
commit 2f995be794
8 changed files with 82 additions and 21 deletions

View file

@ -8,6 +8,7 @@ WEBPACK_ELECTRON_PORT=9091
WEB_SERVER_PORT=1337
LBRY_WEB_API=https://api.lbry.tv
LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
WELCOME_VERSION=1.0
# Custom Site info

View file

@ -9,6 +9,7 @@ const config = {
WEB_SERVER_PORT: process.env.WEB_SERVER_PORT,
LBRY_WEB_API: process.env.LBRY_WEB_API, //api.lbry.tv',
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //cdn.lbryplayer.xyz',
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
WELCOME_VERSION: process.env.WELCOME_VERSION,
DOMAIN: process.env.DOMAIN,
URL: process.env.URL,

View file

@ -9,7 +9,7 @@ import Native from 'native';
import ElectronCookies from '@exponent/electron-cookies';
import { generateInitialUrl } from 'util/url';
// @endif
import { MATOMO_ID, MATOMO_URL } from 'config';
import { MATOMO_ID, MATOMO_URL, LBRY_WEB_BUFFER_API } from 'config';
const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL;
@ -36,7 +36,10 @@ type Analytics = {
apiSyncTags: ({}) => void,
tagFollowEvent: (string, boolean, ?string) => void,
videoStartEvent: (string, number) => void,
videoBufferEvent: (string, number) => void,
videoBufferEvent: (
StreamClaim,
{ timeAtBuffer: number, bufferDuration: number, bitRate: number, duration: number, userIdHash: string }
) => void,
emailProvidedEvent: () => void,
emailVerifiedEvent: () => void,
rewardEligibleEvent: () => void,
@ -182,9 +185,32 @@ const analytics: Analytics = {
sendPromMetric('time_to_start', duration);
sendMatomoEvent('Media', 'TimeToStart', claimId, duration);
},
videoBufferEvent: (claimId, currentTime) => {
videoBufferEvent: (claim, data) => {
// @if TARGET='web'
sendPromMetric('buffer');
sendMatomoEvent('Media', 'BufferTimestamp', claimId, currentTime * 1000);
// @endif
sendMatomoEvent('Media', 'BufferTimestamp', claim.claim_id, data.timeAtBuffer);
fetch(LBRY_WEB_BUFFER_API, {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
device: 'web',
type: 'buffering',
client: data.userIdHash,
data: {
url: claim.canonical_url,
position: data.timeAtBuffer,
duration: data.bufferDuration,
stream_duration: data.duration,
stream_bitrate: data.bitRate,
},
}),
});
},
tagFollowEvent: (tag, following) => {
sendMatomoEvent('Tag', following ? 'Tag-Follow' : 'Tag-Unfollow', tag);

View file

@ -3,6 +3,7 @@ import React, { Fragment, PureComponent } from 'react';
import Button from 'component/button';
import path from 'path';
import Card from 'component/common/card';
import { formatBytes } from 'util/format-bytes';
type Props = {
claim: StreamClaim,
@ -102,17 +103,5 @@ class FileDetails extends PureComponent<Props> {
);
}
}
// move this with other helper functions when we re-use it
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return __('0 Bytes');
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = [__('Bytes'), __('KB'), __('MB'), __('GB'), __('TB'), __('PB'), __('EB'), __('ZB'), __('YB')];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
export default FileDetails;

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri, makeSelectFileInfoForUri, makeSelectThumbnailForUri, SETTINGS } from 'lbry-redux';
import { doChangeVolume, doChangeMute, doAnalyticsView } from 'redux/actions/app';
import { doChangeVolume, doChangeMute, doAnalyticsView, doAnalyticsBuffer } from 'redux/actions/app';
import { selectVolume, selectMute } from 'redux/selectors/app';
import { savePosition, clearPosition } from 'redux/actions/content';
import { makeSelectContentPositionForUri } from 'redux/selectors/content';
@ -33,6 +33,7 @@ const perform = dispatch => ({
clearPosition: uri => dispatch(clearPosition(uri)),
changeMute: muted => dispatch(doChangeMute(muted)),
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
});

View file

@ -23,7 +23,7 @@ type Props = {
source: string,
contentType: string,
thumbnail: string,
claim: Claim,
claim: StreamClaim,
muted: boolean,
volume: number,
uri: string,
@ -31,6 +31,7 @@ type Props = {
autoplayIfEmbedded: boolean,
desktopPlayStartTime?: number,
doAnalyticsView: (string, number) => Promise<any>,
doAnalyticsBuffer: (string, any) => void,
claimRewards: () => void,
savePosition: (string, number) => void,
clearPosition: string => void,
@ -56,6 +57,7 @@ function VideoViewer(props: Props) {
autoplaySetting,
autoplayIfEmbedded,
doAnalyticsView,
doAnalyticsBuffer,
claimRewards,
savePosition,
clearPosition,
@ -85,7 +87,7 @@ function VideoViewer(props: Props) {
}, [uri, previousUri]);
function doTrackingBuffered(e: Event, data: any) {
analytics.videoBufferEvent(claimId, data.currentTime);
doAnalyticsBuffer(uri, data);
}
function doTrackingFirstPlay(e: Event, data: any) {

View file

@ -53,6 +53,8 @@ import analytics, { SHARE_INTERNAL } from 'analytics';
import { doSignOutCleanup, deleteSavedPassword, getSavedPassword } from 'util/saved-passwords';
import { doSocketConnect } from 'redux/actions/websocket';
import { stringifyServerParam, shouldSetSetting } from 'util/sync-settings';
import sha256 from 'crypto-js/sha256';
import Base64 from 'crypto-js/enc-base64';
// @if TARGET='app'
const { autoUpdater } = remote.require('electron-updater');
@ -467,6 +469,32 @@ export function doAnalyticsView(uri, timeToStart) {
};
}
export function doAnalyticsBuffer(uri, bufferData) {
return (dispatch, getState) => {
const state = getState();
const claim = makeSelectClaimForUri(uri)(state);
const user = selectUser(state);
const {
value: { video, audio, source },
} = claim;
const timeAtBuffer = bufferData.currentTime * 1000;
const bufferDuration = bufferData.secondsToLoad * 1000;
const fileDurationInSeconds = (video && video.duration) || (audio && audio.duration);
const fileSize = source.size; // size in bytes
const fileSizeInBits = fileSize * 8;
const bitRate = parseInt(fileSizeInBits / fileDurationInSeconds);
const userIdHash = Base64.stringify(sha256(user.id));
analytics.videoBufferEvent(claim, {
timeAtBuffer,
bufferDuration,
bitRate,
userIdHash,
duration: fileDurationInSeconds,
});
};
}
export function doAnalyticsTagSync() {
return (dispatch, getState) => {
const state = getState();
@ -560,11 +588,12 @@ export function doGetAndPopulatePreferences() {
return (dispatch, getState) => {
const state = getState();
let preferenceKey;
// @if TARGET='app'
const preferenceKey = state.user && state.user.user && state.user.user.has_verified_email ? 'shared' : 'anon';
preferenceKey = state.user && state.user.user && state.user.user.has_verified_email ? 'shared' : 'anon';
// @endif
// @if TARGET='web'
const preferenceKey = 'shared';
preferenceKey = 'shared';
// @endif
function successCb(savedPreferences) {

12
ui/util/format-bytes.js Normal file
View file

@ -0,0 +1,12 @@
// @flow
export function formatBytes(bytes: number, decimals?: number = 2) {
if (bytes === 0) return __('0 Bytes');
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = [__('Bytes'), __('KB'), __('MB'), __('GB'), __('TB'), __('PB'), __('EB'), __('ZB'), __('YB')];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}