From c5c62a2698dc9a22e4bed7d7a81aa416934473f2 Mon Sep 17 00:00:00 2001 From: seanyesmunt Date: Fri, 3 Aug 2018 11:54:10 -0400 Subject: [PATCH] include time to start if available in file_view analytics --- src/renderer/analytics.js | 12 ++- .../component/fileViewer/internal/player.jsx | 13 +++- src/renderer/component/fileViewer/view.jsx | 76 ++++++++++++++++++- src/renderer/redux/actions/content.js | 54 ++++++------- 4 files changed, 119 insertions(+), 36 deletions(-) diff --git a/src/renderer/analytics.js b/src/renderer/analytics.js index a4fd24258..808106bfc 100644 --- a/src/renderer/analytics.js +++ b/src/renderer/analytics.js @@ -44,13 +44,19 @@ const analytics: Analytics = { } analyticsEnabled = enabled; }, - apiLogView: (uri: string, outpoint: string, claimId: string): void => { + apiLogView: (uri: string, outpoint: string, claimId: string, timeToStart?: number): void => { if (analyticsEnabled) { - Lbryio.call('file', 'view', { + const params = { uri, outpoint, claim_id: claimId, - }).catch(() => {}); + }; + + if (timeToStart) { + params.time_to_start = timeToStart; + } + + Lbryio.call('file', 'view', params).catch(() => {}); } }, }; diff --git a/src/renderer/component/fileViewer/internal/player.jsx b/src/renderer/component/fileViewer/internal/player.jsx index 9ae490b66..14fa1bda9 100644 --- a/src/renderer/component/fileViewer/internal/player.jsx +++ b/src/renderer/component/fileViewer/internal/player.jsx @@ -34,10 +34,21 @@ class VideoPlayer extends React.PureComponent { componentDidMount() { const container = this.media; - const { downloadCompleted, contentType, changeVolume, volume, position, claim } = this.props; + const { + downloadCompleted, + contentType, + changeVolume, + volume, + position, + claim, + startedPlayingCb, + } = this.props; const loadedMetadata = () => { this.setState({ hasMetadata: true, startedPlaying: true }); + if (startedPlayingCb) { + startedPlayingCb(); + } this.media.children[0].play(); }; diff --git a/src/renderer/component/fileViewer/view.jsx b/src/renderer/component/fileViewer/view.jsx index 406c25186..242ef6667 100644 --- a/src/renderer/component/fileViewer/view.jsx +++ b/src/renderer/component/fileViewer/view.jsx @@ -1,6 +1,7 @@ // @flow import React from 'react'; import classnames from 'classnames'; +import analytics from 'analytics'; import type { Claim } from 'types/claim'; import LoadingScreen from 'component/common/loading-screen'; import Player from './internal/player'; @@ -48,17 +49,48 @@ type Props = { class FileViewer extends React.PureComponent { constructor() { super(); - (this: any).playContent = this.playContent.bind(this); (this: any).handleKeyDown = this.handleKeyDown.bind(this); + (this: any).logTimeToStart = this.logTimeToStart.bind(this); + (this: any).startedPlayingCb = undefined; + + // Don't add these variables to state because we don't need to re-render when their values change + (this: any).startTime = undefined; + (this: any).playTime = undefined; } componentDidMount() { + const { fileInfo } = this.props; + if (!fileInfo) { + this.startedPlayingCb = this.logTimeToStart; + } + this.handleAutoplay(this.props); window.addEventListener('keydown', this.handleKeyDown); } componentDidUpdate(prev: Props) { + const { fileInfo } = this.props; + + if (this.props.uri !== prev.uri) { + // User just directly navigated to another piece of content + if (this.startTime && !this.playTime) { + // They started playing a file but it didn't start streaming + // Fire the analytics event with the previous file + this.fireAnalyticsEvent(prev.claim); + } + + this.startTime = null; + this.playTime = null; + + // If this new file is already downloaded, remove the startedPlayingCallback + if (fileInfo && this.startedPlayingCb) { + this.startedPlayingCb = null; + } else if (!fileInfo && !this.startedPlayingCb) { + this.startedPlayingCb = this.logTimeToStart; + } + } + if ( this.props.autoplay !== prev.autoplay || this.props.fileInfo !== prev.fileInfo || @@ -73,6 +105,14 @@ class FileViewer extends React.PureComponent { } componentWillUnmount() { + const { claim } = this.props; + + if (this.startTime && !this.playTime) { + // The user is navigating away before the file started playing, or a play time was never set + // Currently will not be set for files that don't use render-media + this.fireAnalyticsEvent(claim); + } + this.props.cancelPlay(); window.removeEventListener('keydown', this.handleKeyDown); } @@ -107,9 +147,42 @@ class FileViewer extends React.PureComponent { playContent() { const { play, uri } = this.props; + + if (this.startedPlayingCb) { + this.startTime = Date.now(); + } + play(uri); } + logTimeToStart() { + const { claim } = this.props; + + if (this.startTime) { + this.playTime = Date.now(); + this.fireAnalyticsEvent(claim, this.startTime, this.playTime); + } + } + + fireAnalyticsEvent = (claim, startTime, playTime) => { + const { name, claim_id: claimId, txid, nout } = claim; + + // ideally outpoint would exist inside of claim information + // we can use it after https://github.com/lbryio/lbry/issues/1306 is addressed + const outpoint = `${txid}:${nout}`; + + let timeToStart; + if (playTime && startTime) { + timeToStart = playTime - startTime; + } + + analytics.apiLogView(`${name}#${claimId}`, outpoint, claimId, timeToStart); + }; + + startedPlayingCb: ?() => void; + startTime: ?number; + playTime: ?number; + render() { const { metadata, @@ -178,6 +251,7 @@ class FileViewer extends React.PureComponent { uri={uri} paused={mediaPaused} position={mediaPosition} + startedPlayingCb={this.startedPlayingCb} /> )} diff --git a/src/renderer/redux/actions/content.js b/src/renderer/redux/actions/content.js index 04808b6e1..d757eef40 100644 --- a/src/renderer/redux/actions/content.js +++ b/src/renderer/redux/actions/content.js @@ -31,7 +31,6 @@ import { import { makeSelectClientSetting } from 'redux/selectors/settings'; import setBadge from 'util/setBadge'; import setProgressBar from 'util/setProgressBar'; -import analytics from 'analytics'; const DOWNLOAD_POLL_INTERVAL = 250; @@ -224,13 +223,6 @@ export function doStartDownload(uri, outpoint) { export function doDownloadFile(uri, streamInfo) { return dispatch => { dispatch(doStartDownload(uri, streamInfo.outpoint)); - - analytics.apiLogView( - `${streamInfo.claim_name}#${streamInfo.claim_id}`, - streamInfo.outpoint, - streamInfo.claim_id - ); - dispatch(doClaimEligiblePurchaseRewards()); }; } @@ -244,6 +236,29 @@ export function doSetPlayingUri(uri) { }; } +function handleLoadVideoError(uri, errorType = '') { + return (dispatch, getState) => { + // suppress error when another media is playing + const { playingUri } = getState().content; + if (playingUri && playingUri === uri) { + dispatch({ + type: ACTIONS.LOADING_VIDEO_FAILED, + data: { uri }, + }); + dispatch(doSetPlayingUri(null)); + if (errorType === 'timeout') { + doNotify({ id: MODALS.FILE_TIMEOUT }, { uri }); + } else { + dispatch( + doAlertError( + `Failed to download ${uri}, please try again. If this problem persists, visit https://lbry.io/faq/support for support.` + ) + ); + } + } + }; +} + export function doLoadVideo(uri) { return dispatch => { dispatch({ @@ -270,29 +285,6 @@ export function doLoadVideo(uri) { }; } -function handleLoadVideoError(uri, errorType = '') { - return (dispatch, getState) => { - // suppress error when another media is playing - const { playingUri } = getState().content; - if (playingUri && playingUri === uri) { - dispatch({ - type: ACTIONS.LOADING_VIDEO_FAILED, - data: { uri }, - }); - dispatch(doSetPlayingUri(null)); - if (errorType === 'timeout') { - doNotify({ id: MODALS.FILE_TIMEOUT }, { uri }); - } else { - dispatch( - doAlertError( - `Failed to download ${uri}, please try again. If this problem persists, visit https://lbry.io/faq/support for support.` - ) - ); - } - } - }; -} - export function doPurchaseUri(uri, specificCostInfo) { return (dispatch, getState) => { const state = getState();