use new buffer analytics api
This commit is contained in:
parent
e5b1177644
commit
2f995be794
8 changed files with 82 additions and 21 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()),
|
||||
});
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
12
ui/util/format-bytes.js
Normal 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];
|
||||
}
|
Loading…
Reference in a new issue