logo

多标签页检测实现视频培训防作弊

Dec 10, 2022 · 4 min

写在前面#

转眼间来到现在的这家公司也2年了,作为一家基于企业级教培行业的互联网公司(尤其近3年的疫情)来说,,线上培训相比线下培训开展的比重则大幅增加,因此需要考虑如何防止学员边培训边刷剧等培训业务定义中的作弊行为。
仅以实现方案做思路说明,文章内容基于个人实际项目环境而来,仅供参考,互相学习

项目环境#

React 17.x + xgplayer

学员如何作弊?#

观看视频中离开页面、另外开一个显示器(打开新标签卡)、长时间页面失活

首先了解一下 document.visibilityState 属性,返回值为字符串,分别是 hiddenvisibleprerednder

以上三种作弊方式都会使 document.visibilityState 属性变为 hidden

document.visibilityState 属性值发生变化时,则一定触发 visibilitychange 事件

接下来怎么做?#

在项目中,基于xgplayer封装 pageVideo.jsx 组件,组件内除正常的播放逻辑之外,则在初始化之后针对video添加监听事件

  // pageVideo.jsx
  useEffect(() => {
    document.addEventListener('visibilitychange', handleVisibilitychange);
    return {
      document.removeEventListener('visibilitychange', handleVisibilitychange);
    }
  })

监听到 visiblitychange 事件触发之后,在回调 handleVisiblitychange 中处理,符合作弊标准的,则将视频暂停

  // handleVisiblitychange
  const videoDom = document.getElementsByTagName('video')[0];
  if (document.hidden) {
    videoDom && videoDom.pause();
  }

写到这里,其实利用 visiblitychange 事件进行检测多标签场景已经做完了,看过对应的内容的文档,也就不难理解其原理。接下来,为了达到实际的业务需求,进行一些优化

如何实现更好的交互设计?#

最终呈现如下图,分别有几个要素:

实现以上几点,需要去缓存一下播放的视屏名称,将正在播放的课程标识缓存下来,检测标识是否一致,再进行自定义renderDom展示该弹窗

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();
  });
};

此外在播放页初始化则缓存课程名称

useEffect(() => {
  if (playerState.title) addCacheTag(playerState.title);
}, [playerState.title]);

useEffect(() => {
  window.onbeforeunload = removeCacheTage;
})

参考资料#

> cd ..