Foreword
As an Internet company based on the enterprise-level education and training industry (especially the epidemic in the past 3 years), the proportion of online training compared to offline training has increased significantly, so it is necessary to consider how to prevent cheating in the definition of training business such as training while brushing drama.
This document provides only a conceptual outline of the implementation; its content is based on my own real-world project experience and is intended for reference and mutual learning.
Project Environment
React 17.x + xgplayer
How Do Students Cheat?
Watching the video while leaving the page, opening another monitor (or a new tab), or having the page inactive for an extended period.
First, let’s take a look at the document.visibilityState property. Its return value is a string, which can be one of the following: 'hidden', 'visible', or 'prerender'.
All three of the aforementioned cheating methods will cause the document.visibilityState property to become hidden.
When the value of the ‘document.visibilityState’ property changes, the ‘visibilitychange’ event is always triggered.
What’s next?
In the project, we encapsulated an pageVideo.jsx component based on xgplayer. In addition to the standard playback logic, after initialization, we add event listeners to the video element.
// pageVideo.jsx
useEffect(() => {
document.addEventListener('visibilitychange', handleVisibilitychange);
return {
document.removeEventListener('visibilitychange', handleVisibilitychange);
}
})
After detecting that the ‘visibilitychange’ event has been triggered, handle it in the ‘handleVisibilityChange’ callback; if the conditions for cheating are met, pause the video.
// handleVisiblitychange
const videoDom = document.getElementsByTagName('video')[0];
if (document.hidden) {
videoDom && videoDom.pause();
}
At this point, the implementation for detecting multi-tab scenarios using the visibilitychange event is essentially complete. After reviewing the relevant documentation, the underlying principles become readily apparent. Next, to meet the actual business requirements, we will implement some optimizations.
How Can We Achieve Better Interaction Design?
Several factors need to be considered: -Reminds learners of the course they are currently studying. -Pause video playback -Offer the choice to close this video or continue playing it.
To achieve the above points, it is necessary to cache the name of the playing video screen, cache the course logo being played, check whether the logo is consistent, and then customize the renderDom to display the pop-up window.
export const createTagCheckDom = (continuePlayCallback) => {
if (document.getElementById('tagCheck')) return;
const modalBoxDom = document.createElement('div');
modalBoxDom.setAttribute('id', 'tagCheck');
modalBoxDom.className = `${style.modalBoxSmall}`;
const courseName = getStorage('CUR_COURSE_NAME');
modalBoxDom.innerHTML =
`<div class=${style.checkTagTip}>
<div style="color: #fff">您正在学习另一个课程【${courseName}】,请将另一个课程【${courseName}】学习页关闭后再学习。</div>
<div class=${style.interventionsBtnBox} style="margin-top: 10px">
<div id="close" class=${style.interventionsBtn} style='padding: 6px 10px'>
关闭此课程
</div>
<div id="continueStudy" class=${style.interventionsBtn} style='padding: 6px 10px'>
继续学习
</div>
</div>
</div>`;
document.getElementById('xg-player-video').appendChild(modalBoxDom);
const closeBtnDom = document.getElementById('close');
const continueBtnDom = document.getElementById('continueStudy');
closeBtnDom.addEventListener('click', () => {
if (navigator.userAgent.includes('Firefox') || navigator.userAgent.includes('Chrome')) {
window.location.href = 'about:blank';
window.close();
} else {
window.opener = null;
window.open('', '_self');
window.close();
}
modalBoxDom.remove();
});
continueBtnDom.addEventListener('click', () => {
continuePlayCallback();
modalBoxDom.remove();
});
};
In addition, the course name is cached during playback page initialization.
useEffect(() => {
if (playerState.title) addCacheTag(playerState.title);
}, [playerState.title]);
useEffect(() => {
window.onbeforeunload = removeCacheTage;
})