Port recsys to master; enabled for SIMPLE_SITE only
This commit is contained in:
parent
099667a4bb
commit
5766dc34b5
4 changed files with 243 additions and 19 deletions
|
@ -9,13 +9,14 @@ import { withRouter } from 'react-router';
|
|||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||
import { makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
||||
import { toggleVideoTheaterMode, doSetClientSetting } from 'redux/actions/settings';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { search } = props.location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const autoplay = urlParams.get('autoplay');
|
||||
const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(props.uri)(state);
|
||||
const userId = selectUser(state) && selectUser(state).id;
|
||||
|
||||
return {
|
||||
autoplayIfEmbedded: Boolean(autoplay),
|
||||
|
@ -29,6 +30,7 @@ const select = (state, props) => {
|
|||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
homepageData: selectHomepageData(state),
|
||||
authenticated: selectUserVerifiedEmail(state),
|
||||
userId: userId,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
// Created by xander on 6/21/2021
|
||||
import videojs from 'video.js/dist/video.min.js';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
const VERSION = '0.0.1';
|
||||
|
||||
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
|
||||
const recsysId = 'lighthouse-v0';
|
||||
|
||||
/* RecSys */
|
||||
const RecsysData = {
|
||||
event: {
|
||||
start: 0,
|
||||
stop: 1,
|
||||
scrub: 2,
|
||||
speed: 3,
|
||||
},
|
||||
};
|
||||
|
||||
function createRecsys(claimId, userId, events, loadedAt, isEmbed) {
|
||||
// TODO: use a UUID generator
|
||||
const uuid = uuidV4();
|
||||
const pageLoadedAt = loadedAt;
|
||||
const pageExitedAt = Date.now();
|
||||
return {
|
||||
uuid: uuid,
|
||||
parentUuid: null,
|
||||
uid: userId,
|
||||
claimId: claimId,
|
||||
pageLoadedAt: pageLoadedAt,
|
||||
pageExitedAt: pageExitedAt,
|
||||
recsysId: recsysId,
|
||||
recClaimIds: null,
|
||||
recClickedVideoIdx: null,
|
||||
events: events,
|
||||
isEmbed: isEmbed,
|
||||
};
|
||||
}
|
||||
|
||||
function newRecsysEvent(eventType, offset, arg) {
|
||||
if (arg) {
|
||||
return {
|
||||
event: eventType,
|
||||
offset: offset,
|
||||
arg: arg,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
event: eventType,
|
||||
offset: offset,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function sendRecsysEvents(recsys) {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'text/plain' }, // application/json
|
||||
body: JSON.stringify(recsys),
|
||||
};
|
||||
|
||||
try {
|
||||
fetch(recsysEndpoint, requestOptions)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
// console.log(`Recsys response data:`, data);
|
||||
});
|
||||
} catch (error) {
|
||||
// console.error(`Recsys Error`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
endpoint: recsysEndpoint,
|
||||
recsysId: recsysId,
|
||||
videoId: null,
|
||||
userId: 0,
|
||||
debug: false,
|
||||
};
|
||||
|
||||
const Component = videojs.getComponent('Component');
|
||||
const registerPlugin = videojs.registerPlugin || videojs.plugin;
|
||||
|
||||
class RecsysPlugin extends Component {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
// Plugin started
|
||||
if (options.debug) {
|
||||
this.log(`Created recsys plugin for: videoId:${options.videoId}, userId:${options.userId}`);
|
||||
}
|
||||
|
||||
// To help with debugging, we'll add a global vjs object with the video js player
|
||||
window.vjs = player;
|
||||
|
||||
this.player = player;
|
||||
|
||||
this.recsysEvents = [];
|
||||
this.lastTimeUpdate = null;
|
||||
this.currentTimeUpdate = null;
|
||||
this.loadedAt = Date.now();
|
||||
|
||||
// Plugin event listeners
|
||||
player.on('playing', (event) => this.onPlay(event));
|
||||
player.on('pause', (event) => this.onPause(event));
|
||||
player.on('ended', (event) => this.onEnded(event));
|
||||
player.on('ratechange', (event) => this.onRateChange(event));
|
||||
player.on('timeupdate', (event) => this.onTimeUpdate(event));
|
||||
player.on('seeked', (event) => this.onSeeked(event));
|
||||
|
||||
// Event trigger to send recsys event
|
||||
player.on('dispose', (event) => this.onDispose(event));
|
||||
}
|
||||
|
||||
addRecsysEvent(recsysEvent) {
|
||||
this.recsysEvents.push(recsysEvent);
|
||||
}
|
||||
|
||||
getRecsysEvents() {
|
||||
return this.recsysEvents;
|
||||
}
|
||||
|
||||
sendRecsysEvents() {
|
||||
const event = createRecsys(
|
||||
this.options_.videoId,
|
||||
this.options_.userId,
|
||||
this.getRecsysEvents(),
|
||||
this.loadedAt,
|
||||
false
|
||||
);
|
||||
sendRecsysEvents(event);
|
||||
}
|
||||
|
||||
onPlay(event) {
|
||||
const recsysEvent = newRecsysEvent(RecsysData.event.start, this.player.currentTime());
|
||||
this.log('onPlay', recsysEvent);
|
||||
this.addRecsysEvent(recsysEvent);
|
||||
}
|
||||
|
||||
onPause(event) {
|
||||
const recsysEvent = newRecsysEvent(RecsysData.event.stop, this.player.currentTime());
|
||||
this.log('onPause', recsysEvent);
|
||||
this.addRecsysEvent(recsysEvent);
|
||||
}
|
||||
|
||||
onEnded(event) {
|
||||
const recsysEvent = newRecsysEvent(RecsysData.event.stop, this.player.currentTime());
|
||||
this.log('onEnded', recsysEvent);
|
||||
this.addRecsysEvent(recsysEvent);
|
||||
}
|
||||
|
||||
onRateChange(event) {
|
||||
const recsysEvent = newRecsysEvent(RecsysData.event.speed, this.player.currentTime());
|
||||
this.log('onRateChange', recsysEvent);
|
||||
this.addRecsysEvent(recsysEvent);
|
||||
}
|
||||
|
||||
onTimeUpdate(event) {
|
||||
this.lastTimeUpdate = this.currentTimeUpdate;
|
||||
this.currentTimeUpdate = this.player.currentTime();
|
||||
}
|
||||
|
||||
onSeeked(event) {
|
||||
const recsysEvent = newRecsysEvent(RecsysData.event.scrub, this.lastTimeUpdate, this.player.currentTime());
|
||||
this.log('onSeeked', recsysEvent);
|
||||
this.addRecsysEvent(recsysEvent);
|
||||
}
|
||||
|
||||
onDispose(event) {
|
||||
this.sendRecsysEvents();
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
if (this.options_.debug) {
|
||||
// console.log(`Recsys Debug:`, JSON.stringify(args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerComponent('recsys', RecsysPlugin);
|
||||
|
||||
const onPlayerReady = (player, options) => {
|
||||
player.recsys = new RecsysPlugin(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('recsys', plugin);
|
||||
|
||||
export default plugin;
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { SIMPLE_SITE } from 'config';
|
||||
import Button from 'component/button';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import classnames from 'classnames';
|
||||
|
@ -9,6 +10,7 @@ import eventTracking from 'videojs-event-tracking';
|
|||
import * as OVERLAY from './overlays';
|
||||
import './plugins/videojs-mobile-ui/plugin';
|
||||
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
|
||||
import recsys from './plugins/videojs-recsys/plugin';
|
||||
import qualityLevels from 'videojs-contrib-quality-levels';
|
||||
import isUserTyping from 'util/detect-typing';
|
||||
|
||||
|
@ -50,6 +52,8 @@ type Props = {
|
|||
autoplay: boolean,
|
||||
toggleVideoTheaterMode: () => void,
|
||||
adUrl: ?string,
|
||||
claimId: ?string,
|
||||
userId: ?number,
|
||||
};
|
||||
|
||||
type VideoJSOptions = {
|
||||
|
@ -121,6 +125,10 @@ if (!Object.keys(videojs.getPlugins()).includes('qualityLevels')) {
|
|||
videojs.registerPlugin('qualityLevels', qualityLevels);
|
||||
}
|
||||
|
||||
if (!Object.keys(videojs.getPlugins()).includes('recsys')) {
|
||||
videojs.registerPlugin('recsys', recsys);
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
// LbryVolumeBarClass
|
||||
// ****************************************************************************
|
||||
|
@ -180,6 +188,8 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
onPlayerReady,
|
||||
toggleVideoTheaterMode,
|
||||
adUrl,
|
||||
claimId,
|
||||
userId,
|
||||
} = props;
|
||||
|
||||
const [reload, setReload] = useState('initial');
|
||||
|
@ -583,6 +593,14 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
displayCurrentQuality: true,
|
||||
});
|
||||
|
||||
// Add recsys plugin
|
||||
if (SIMPLE_SITE) {
|
||||
player.recsys({
|
||||
videoId: claimId,
|
||||
userId: userId,
|
||||
});
|
||||
}
|
||||
|
||||
// Update player source
|
||||
player.src({
|
||||
src: finalSource,
|
||||
|
|
|
@ -48,6 +48,7 @@ type Props = {
|
|||
toggleVideoTheaterMode: () => void,
|
||||
setVideoPlaybackRate: (number) => void,
|
||||
authenticated: boolean,
|
||||
userId: number,
|
||||
homepageData: {
|
||||
PRIMARY_CONTENT_CHANNEL_IDS?: Array<string>,
|
||||
ENLIGHTENMENT_CHANNEL_IDS?: Array<string>,
|
||||
|
@ -89,6 +90,7 @@ function VideoViewer(props: Props) {
|
|||
setVideoPlaybackRate,
|
||||
homepageData,
|
||||
authenticated,
|
||||
userId,
|
||||
} = props;
|
||||
const {
|
||||
PRIMARY_CONTENT_CHANNEL_IDS = [],
|
||||
|
@ -179,13 +181,22 @@ function VideoViewer(props: Props) {
|
|||
}
|
||||
}, [embedded, setIsEndededEmbed, autoplaySetting, setShowAutoplayCountdown, adUrl, setAdUrl]);
|
||||
|
||||
function onPlay() {
|
||||
function onPlay(player) {
|
||||
setIsLoading(false);
|
||||
setIsPlaying(true);
|
||||
setShowAutoplayCountdown(false);
|
||||
setIsEndededEmbed(false);
|
||||
}
|
||||
|
||||
function onPause(player) {
|
||||
setIsPlaying(false);
|
||||
handlePosition(player);
|
||||
}
|
||||
|
||||
function onDispose(player) {
|
||||
handlePosition(player);
|
||||
}
|
||||
|
||||
function handlePosition(player) {
|
||||
if (player.ended()) {
|
||||
clearPosition(uri);
|
||||
|
@ -223,17 +234,11 @@ function VideoViewer(props: Props) {
|
|||
|
||||
Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
||||
if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') {
|
||||
if (player.autoplay() && !player.muted()) {
|
||||
player.muted(true);
|
||||
}
|
||||
// Autoplay disallowed by browser
|
||||
}
|
||||
|
||||
if (PLAY_TIMEOUT_ERROR) {
|
||||
const retryPlayPromise = player.play();
|
||||
Promise.race([retryPlayPromise, timeoutPromise]).catch((error) => {
|
||||
setIsLoading(false);
|
||||
setIsPlaying(false);
|
||||
});
|
||||
// Autoplay failed
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setIsPlaying(false);
|
||||
|
@ -252,11 +257,10 @@ function VideoViewer(props: Props) {
|
|||
player.on('tracking:buffered', doTrackingBuffered);
|
||||
player.on('tracking:firstplay', doTrackingFirstPlay);
|
||||
player.on('ended', onEnded);
|
||||
player.on('play', onPlay);
|
||||
player.on('pause', () => {
|
||||
setIsPlaying(false);
|
||||
handlePosition(player);
|
||||
});
|
||||
player.on('play', () => onPlay(player));
|
||||
player.on('pause', () => onPause(player));
|
||||
player.on('dispose', () => onDispose(player));
|
||||
|
||||
player.on('error', () => {
|
||||
const error = player.error();
|
||||
if (error) {
|
||||
|
@ -283,10 +287,7 @@ function VideoViewer(props: Props) {
|
|||
if (position) {
|
||||
player.currentTime(position);
|
||||
}
|
||||
player.on('dispose', () => {
|
||||
handlePosition(player);
|
||||
});
|
||||
}, playerReadyDependencyList);
|
||||
}, playerReadyDependencyList); // eslint-disable-line
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -344,6 +345,8 @@ function VideoViewer(props: Props) {
|
|||
startMuted={autoplayIfEmbedded}
|
||||
toggleVideoTheaterMode={toggleVideoTheaterMode}
|
||||
autoplay={!embedded || autoplayIfEmbedded}
|
||||
claimId={claimId}
|
||||
userId={userId}
|
||||
/>
|
||||
</React.Suspense>
|
||||
)}
|
||||
|
|
Loading…
Reference in a new issue