Create plugin to hold code for recsys

This commit is contained in:
DispatchCommit 2021-06-21 08:17:12 -07:00 committed by jessopb
parent 153eae1a41
commit d781dead32
4 changed files with 158 additions and 54 deletions

View file

@ -9,13 +9,14 @@ import { withRouter } from 'react-router';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards'; import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings'; import { makeSelectClientSetting, selectHomepageData } from 'redux/selectors/settings';
import { toggleVideoTheaterMode, doSetClientSetting } from 'redux/actions/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 select = (state, props) => {
const { search } = props.location; const { search } = props.location;
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const autoplay = urlParams.get('autoplay'); const autoplay = urlParams.get('autoplay');
const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(props.uri)(state); const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(props.uri)(state);
const userId = selectUser(state).id;
return { return {
autoplayIfEmbedded: Boolean(autoplay), autoplayIfEmbedded: Boolean(autoplay),
@ -29,6 +30,7 @@ const select = (state, props) => {
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
homepageData: selectHomepageData(state), homepageData: selectHomepageData(state),
authenticated: selectUserVerifiedEmail(state), authenticated: selectUserVerifiedEmail(state),
userId: userId,
}; };
}; };

View file

@ -0,0 +1,136 @@
// Created by xander on 6/21/2021
import videojs from 'video.js/dist/video.min.js';
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 newRecsysEvent(eventType, offset, arg) {
if (arg) {
return {
event: eventType,
offset: offset,
arg: arg,
};
} else {
return {
event: eventType,
offset: offset,
};
}
}
function sendRecsysEvent(recsysEvent) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(recsysEvent),
};
fetch(recsysEndpoint, requestOptions)
.then((response) => response.json())
.then((data) => {
console.log(`Recsys response data:`, data);
});
}
const defaults = {
endpoint: recsysEndpoint,
recsysId: recsysId,
videoId: '0',
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
console.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;
// 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('seeking', (event) => this.onSeeking(event));
}
onPlay(event) {
const recsysEvent = newRecsysEvent(RecsysData.event.start, this.player.currentTime());
this.log('onPlay', recsysEvent);
sendRecsysEvent(recsysEvent);
}
onPause(event) {
const recsysEvent = newRecsysEvent(RecsysData.event.stop, this.player.currentTime());
this.log('onPause', recsysEvent);
sendRecsysEvent(recsysEvent);
}
onEnded(event) {
const recsysEvent = newRecsysEvent(RecsysData.event.stop, this.player.currentTime());
this.log('onEnded', recsysEvent);
sendRecsysEvent(recsysEvent);
}
onRateChange(event) {
const recsysEvent = newRecsysEvent(RecsysData.event.speed, this.player.currentTime());
this.log('onRateChange', recsysEvent);
sendRecsysEvent(recsysEvent);
}
onSeeking(event) {
const recsysEvent = newRecsysEvent(RecsysData.event.scrub, this.player.currentTime());
this.log('onSeeking', recsysEvent);
sendRecsysEvent(recsysEvent);
}
log(...args) {
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;

View file

@ -9,6 +9,7 @@ import eventTracking from 'videojs-event-tracking';
import * as OVERLAY from './overlays'; import * as OVERLAY from './overlays';
import './plugins/videojs-mobile-ui/plugin'; import './plugins/videojs-mobile-ui/plugin';
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/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 qualityLevels from 'videojs-contrib-quality-levels';
import isUserTyping from 'util/detect-typing'; import isUserTyping from 'util/detect-typing';
@ -50,6 +51,7 @@ type Props = {
autoplay: boolean, autoplay: boolean,
toggleVideoTheaterMode: () => void, toggleVideoTheaterMode: () => void,
adUrl: ?string, adUrl: ?string,
claimId: ?string,
}; };
// type VideoJSOptions = { // type VideoJSOptions = {
@ -121,6 +123,10 @@ if (!Object.keys(videojs.getPlugins()).includes('qualityLevels')) {
videojs.registerPlugin('qualityLevels', qualityLevels); videojs.registerPlugin('qualityLevels', qualityLevels);
} }
if (!Object.keys(videojs.getPlugins()).includes('recsys')) {
videojs.registerPlugin('recsys', recsys);
}
// **************************************************************************** // ****************************************************************************
// LbryVolumeBarClass // LbryVolumeBarClass
// **************************************************************************** // ****************************************************************************
@ -180,6 +186,8 @@ export default React.memo<Props>(function VideoJs(props: Props) {
onPlayerReady, onPlayerReady,
toggleVideoTheaterMode, toggleVideoTheaterMode,
adUrl, adUrl,
claimId,
userId,
} = props; } = props;
const [reload, setReload] = useState('initial'); const [reload, setReload] = useState('initial');
@ -578,6 +586,12 @@ export default React.memo<Props>(function VideoJs(props: Props) {
displayCurrentQuality: true, displayCurrentQuality: true,
}); });
// Add recsys plugin
player.recsys({
videoId: claimId,
userId: userId,
});
// Update player source // Update player source
player.src({ player.src({
src: finalSource, src: finalSource,

View file

@ -24,48 +24,6 @@ import { useHistory } from 'react-router';
const PLAY_TIMEOUT_ERROR = 'play_timeout_error'; const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
const PLAY_TIMEOUT_LIMIT = 2000; const PLAY_TIMEOUT_LIMIT = 2000;
/* TODO: Move constants elsewhere */
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
const recsysId = 'lighthouse-v0';
/* RecSys */
const Recsys = {
event: {
start: 0,
stop: 1,
scrub: 2,
speed: 3,
},
};
function newRecsysEvent(eventType, offset, arg) {
if (arg) {
return {
event: eventType,
offset: offset,
arg: arg,
};
} else {
return {
event: eventType,
offset: offset,
};
}
}
function sendRecsysEvent(recsysEvent) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(recsysEvent),
};
fetch(recsysEndpoint, requestOptions)
.then((response) => response.json())
.then((data) => {
console.log(`Recsys response data:`, data);
});
}
type Props = { type Props = {
position: number, position: number,
changeVolume: (number) => void, changeVolume: (number) => void,
@ -89,6 +47,7 @@ type Props = {
toggleVideoTheaterMode: () => void, toggleVideoTheaterMode: () => void,
setVideoPlaybackRate: (number) => void, setVideoPlaybackRate: (number) => void,
authenticated: boolean, authenticated: boolean,
userId: number,
homepageData: { homepageData: {
PRIMARY_CONTENT_CHANNEL_IDS?: Array<string>, PRIMARY_CONTENT_CHANNEL_IDS?: Array<string>,
ENLIGHTENMENT_CHANNEL_IDS?: Array<string>, ENLIGHTENMENT_CHANNEL_IDS?: Array<string>,
@ -130,6 +89,7 @@ function VideoViewer(props: Props) {
setVideoPlaybackRate, setVideoPlaybackRate,
homepageData, homepageData,
authenticated, authenticated,
userId,
} = props; } = props;
const { const {
PRIMARY_CONTENT_CHANNEL_IDS = [], PRIMARY_CONTENT_CHANNEL_IDS = [],
@ -186,14 +146,6 @@ function VideoViewer(props: Props) {
}; };
}, [embedded, videoPlaybackRate]); }, [embedded, videoPlaybackRate]);
// Used to detect and send recsys events
useEffect(() => {
history.listen((location) => {
console.log(`You changed the page to: ${location.pathname}`);
// todo: recsys videoid change goes here
});
}, [history]);
function doTrackingBuffered(e: Event, data: any) { function doTrackingBuffered(e: Event, data: any) {
fetch(source, { method: 'HEAD' }).then((response) => { fetch(source, { method: 'HEAD' }).then((response) => {
data.playerPoweredBy = response.headers.get('x-powered-by'); data.playerPoweredBy = response.headers.get('x-powered-by');
@ -249,8 +201,6 @@ function VideoViewer(props: Props) {
clearPosition(uri); clearPosition(uri);
} else { } else {
savePosition(uri, player.currentTime()); savePosition(uri, player.currentTime());
const rsevent = newRecsysEvent(Recsys.event.scrub, player.currentTime());
sendRecsysEvent(rsevent);
} }
} }
@ -306,7 +256,7 @@ function VideoViewer(props: Props) {
player.on('tracking:buffered', doTrackingBuffered); player.on('tracking:buffered', doTrackingBuffered);
player.on('tracking:firstplay', doTrackingFirstPlay); player.on('tracking:firstplay', doTrackingFirstPlay);
player.on('ended', onEnded); player.on('ended', onEnded);
player.on('play', onPlay); player.on('play', () => onPlay(player));
player.on('pause', () => onPause(player)); player.on('pause', () => onPause(player));
player.on('dispose', () => onDispose(player)); player.on('dispose', () => onDispose(player));
@ -393,6 +343,8 @@ function VideoViewer(props: Props) {
startMuted={autoplayIfEmbedded} startMuted={autoplayIfEmbedded}
toggleVideoTheaterMode={toggleVideoTheaterMode} toggleVideoTheaterMode={toggleVideoTheaterMode}
autoplay={!embedded || autoplayIfEmbedded} autoplay={!embedded || autoplayIfEmbedded}
claimId={claimId}
userId={userId}
/> />
)} )}
</div> </div>