87c94e3c1c
* add mobile plugin back on ios * further touchups and fix ios * finish mobile functionality * dont show big play button on mobile * remove logs * proof of concept * dont go full screen on rotate * add back functionality * replace dispose event with navigate away * bugfix * turn off show if you liked button and nag only on homepage * add back old functionality * ending event not working * test here * working but needs cleanup * more player touchups * bugfix * add settings button on mobile * more touchups * more cleanups * touchup loading functionality * fix hover thumbnails * touchup and eslint fix * fix repopulation bug * change recsys event name * bugfix events * change the way buttons are removed and added * finish chapters button * refactor to use videojs methods * refactor to fix autoplay next * ux touchups * seems to be behaving properly * control bar behaving how it should * fix control bar on ios * working on flow and eslint errors * bugfix and flow fixes * bring back nudge * fix playlist button bug * remove chapter markers properly * show big play button * bugfix recsys closed event * fix analytics bug * fix embeds * bugfix * possible bugfix for kp * bugfix playlist buttons * fix issue with mobile ui plugin * fix firefox autoplay issue * fix bug for play on floating player closed * bugfix volume control for ios * instantiate new player if switching between claim types * fix flow and lint errors * fix control bar not showing up when switching sources * dispose old player if recreating * bugfix save position * reset recsys data between videos * fix audio upload posters * clear claimSrcVhs on reload * bugfix errant image previews showing up * reset player value of having already switched quality * fix watch position not being used * bugfix switching between sources not perserving position * fix save position bug * fix playlist buttons * bugfix * code cleanup and add back 5 second feature
197 lines
5.9 KiB
JavaScript
197 lines
5.9 KiB
JavaScript
// Created by xander on 6/21/2021
|
|
import videojs from 'video.js';
|
|
|
|
import RecSys from 'extras/recsys/recsys';
|
|
const VERSION = '0.0.1';
|
|
|
|
/* RecSys */
|
|
const PlayerEvent = {
|
|
event: {
|
|
start: 0, // event types
|
|
stop: 1,
|
|
scrub: 2,
|
|
speed: 3,
|
|
ended: 4,
|
|
},
|
|
};
|
|
|
|
function newRecsysPlayerEvent(eventType, offset, arg) {
|
|
if (arg) {
|
|
return {
|
|
event: eventType,
|
|
offset: offset,
|
|
arg: arg,
|
|
};
|
|
} else {
|
|
return {
|
|
event: eventType,
|
|
offset: offset,
|
|
};
|
|
}
|
|
}
|
|
|
|
const defaults = {
|
|
videoId: null,
|
|
userId: 0,
|
|
debug: false,
|
|
embedded: false,
|
|
};
|
|
|
|
const Component = videojs.getComponent('Component');
|
|
const registerPlugin = videojs.registerPlugin || videojs.plugin;
|
|
|
|
class RecsysPlugin extends Component {
|
|
constructor(player, options) {
|
|
super(player, options);
|
|
|
|
if (options.debug) {
|
|
this.log(`Created recsys plugin for: videoId:${options.videoId}`);
|
|
}
|
|
|
|
// To help with debugging, we'll add a global vjs object with the video js player
|
|
window.vjs = player;
|
|
|
|
this.player = player;
|
|
this.lastTimeUpdate = null;
|
|
this.currentTimeUpdate = null;
|
|
this.inPause = false;
|
|
this.watchedDuration = { total: 0, lastTimestamp: -1 };
|
|
|
|
// 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('playerClosed', (event) => this.onDispose(event));
|
|
}
|
|
|
|
onPlay(event) {
|
|
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.start, this.player.currentTime());
|
|
this.log('onPlay', recsysEvent);
|
|
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent, this.options_.embedded);
|
|
|
|
this.inPause = false;
|
|
this.lastTimeUpdate = recsysEvent.offset;
|
|
}
|
|
|
|
onPause(event) {
|
|
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.stop, this.player.currentTime());
|
|
this.log('onPause', recsysEvent);
|
|
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
|
|
|
this.inPause = true;
|
|
}
|
|
|
|
onEnded(event) {
|
|
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.ended, this.player.currentTime());
|
|
this.log('onEnded', recsysEvent);
|
|
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
|
}
|
|
|
|
onRateChange(event) {
|
|
const recsysEvent = newRecsysPlayerEvent(
|
|
PlayerEvent.event.speed,
|
|
this.player.currentTime(),
|
|
this.player.playbackRate()
|
|
);
|
|
this.log('onRateChange', recsysEvent);
|
|
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
|
}
|
|
|
|
onTimeUpdate(event) {
|
|
const nextCurrentTime = this.player.currentTime();
|
|
|
|
if (!this.inPause && Math.abs(this.lastTimeUpdate - nextCurrentTime) < 0.5) {
|
|
// Don't update lastTimeUpdate if we are in a pause segment.
|
|
//
|
|
// However, if we aren't in a pause and the time jumped
|
|
// the onTimeUpdate event probably fired before the pause and seek.
|
|
// Don't update in that case, either.
|
|
this.lastTimeUpdate = this.currentTimeUpdate;
|
|
}
|
|
|
|
this.currentTimeUpdate = nextCurrentTime;
|
|
|
|
// --- Log watch duration (PR 1317) ---
|
|
// - Total playing time includes scrubbed durations, i.e. can technically be
|
|
// greater than length of the video if scrubbed back to re-watch parts.
|
|
// - Factors out playback speed, i.e. if the user watches two minutes of
|
|
// video at 2x, it's recorded as two minutes, not the browsers clock time
|
|
// of one minute.
|
|
// - Excludes jumping frames, e.g. jumping incrementally from start to end
|
|
// while paused will not contribute to the total despite user having
|
|
// "watched" the static frames.
|
|
const curTimeSec = nextCurrentTime.toFixed(0);
|
|
if (this.watchedDuration.lastTimestamp !== curTimeSec && !this.inPause) {
|
|
this.watchedDuration.total += 1;
|
|
this.watchedDuration.lastTimestamp = curTimeSec;
|
|
}
|
|
}
|
|
|
|
onSeeked(event) {
|
|
const curTime = this.player.currentTime();
|
|
|
|
// There are three patterns for seeking:
|
|
//
|
|
// Assuming the video is playing,
|
|
//
|
|
// 1. Dragging the player head emits: onPause -> onSeeked -> onSeeked -> ... -> onPlay
|
|
// 2. Key press left right emits: onSeeked -> onPlay
|
|
// 3. Clicking a position emits: onPause -> onSeeked -> onPlay
|
|
//
|
|
// If the video is NOT playing,
|
|
//
|
|
// 1. Dragging the player head emits: onSeeked
|
|
// 2. Key press left right emits: onSeeked
|
|
// 3. Clicking a position emits: onSeeked
|
|
const fromTime = this.lastTimeUpdate;
|
|
|
|
if (fromTime !== curTime) {
|
|
// This removes duplicates that aren't useful.
|
|
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.scrub, fromTime, curTime);
|
|
this.log('onSeeked', recsysEvent);
|
|
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
|
}
|
|
}
|
|
|
|
onDispose(event) {
|
|
// Some browsers don't send onEnded event when closing player/browser, force it here
|
|
const recsysEvent = newRecsysPlayerEvent(PlayerEvent.event.stop, this.player.currentTime());
|
|
RecSys.onRecsysPlayerEvent(this.options_.videoId, recsysEvent);
|
|
RecSys.onPlayerDispose(this.options_.videoId, this.options_.embedded, this.watchedDuration.total);
|
|
}
|
|
|
|
log(...args) {
|
|
if (this.options_.debug) {
|
|
console.log(`Recsys Player 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;
|