index.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973
  1. <template>
  2. <div
  3. id="videoCy"
  4. :style="{
  5. backgroundImage: `url(${$tools.splitImgHost(goodsData.coverUrl, false)})`,
  6. }"
  7. >
  8. <div id="player" v-show="!HideVideo || viewSign == 1"></div>
  9. <div class="recordStyle" v-if="showRecordStatus">
  10. 您上次看{{
  11. $tools.secondToTime(this.activeSection.videoCurrentTime || 0)
  12. }},正在自动续播
  13. <span class="videoCurrentTime_style">|</span>
  14. <span class="btn_sty" @click="seekVideo0">从头播放</span>
  15. </div>
  16. <takePicture
  17. ref="takePicture"
  18. @returnParameter="returnParameter"
  19. ></takePicture>
  20. <count-down ref="countDown" @againSubmit="postStudyRecord(1)"></count-down>
  21. </div>
  22. </template>
  23. <script>
  24. import takePicture from "@/components/takePicture/index.vue";
  25. import countDown from "@/components/countDown/index.vue";
  26. export default {
  27. components: { takePicture, countDown },
  28. inject: ["getGoodsData"],
  29. props: {
  30. activeSection: {
  31. type: Object,
  32. default: () => {
  33. return {};
  34. }, //当前节数据
  35. },
  36. },
  37. data() {
  38. return {
  39. vodPlayerJs: "https://player.polyv.net/script/player.js",
  40. player: null,
  41. HideVideo: false, //是否隐藏播放器
  42. photoList: [], //抓拍时间拍照数组
  43. photoHistoryList: [], //历史和已拍照数据
  44. photoIndex: 0, //当前拍照对应索引
  45. showRecordStatus: false, //是否显示从头播放提示
  46. showRecordSetTimeOut: null, //从头播放提示计时器函数
  47. openPhotoStatus: 0, //暂存学习状态
  48. commitTime: null, //暂存时间-节流
  49. commitTimePhoto: null, //判断拍照时刻-节流
  50. timeEventStatus: false, //双重保障状态
  51. timeEventStatusTimeout: null, //双重保障定时器
  52. videoPauseSetTimeout: null, //定时器停留太久触发
  53. failToRegister: false, //报名推送不通过
  54. videoPauseSetTimeStatus: false,
  55. player_tencent: null,
  56. firstPlay: true, //是否初次播放
  57. viewSign: null,
  58. };
  59. },
  60. computed: {
  61. goodsData() {
  62. return this.getGoodsData();
  63. },
  64. },
  65. watch: {
  66. // HideVideo: function(newVal, oldVal) {
  67. // if (newVal) {
  68. // document.getElementById("player-tencent").setAttribute("style","visibility:hidden");
  69. // } else {
  70. // document.getElementById("player-tencent").setAttribute("style","visibility:visible");
  71. // }
  72. // },
  73. //因为刚开始获取不到goodsData的数据 所以需要监听
  74. goodsData: function (newVal, oldVal) {
  75. if (newVal) {
  76. this.getBeforeWork(); //处理前置任务
  77. }
  78. },
  79. },
  80. created() {
  81. if (!window.polyvPlayer) {
  82. const myScript = document.createElement("script");
  83. myScript.setAttribute("src", this.vodPlayerJs);
  84. myScript.onload = () => {
  85. console.log("加载成功");
  86. };
  87. document.body.appendChild(myScript);
  88. }
  89. },
  90. mounted() {
  91. this.$bus.$on("toPlay", async (item) => {
  92. this.viewSign = this.goodsData.viewSign;
  93. if (this.player) {
  94. this.player.HTML5 &&
  95. this.player.HTML5.video.removeEventListener(
  96. "timeupdate",
  97. this.timeEvent
  98. ); //监听器
  99. this.player.destroy(); //初始化播放器
  100. }
  101. if (this.player_tencent) {
  102. this.player_tencent.dispose(); //初始化播放器
  103. }
  104. this.initData(); //初始化参数
  105. await this.getRecordHistoryPhoto(); //获取拍照历史记录
  106. await this.getRecordLast(); //获取播放记录
  107. let viewSign = this.viewSign || 1;
  108. if (viewSign == 2) {
  109. await this.loadPlayer_tencent(); //加载播放内容
  110. } else {
  111. await this.loadPlayer(); //加载播放内容
  112. }
  113. this[viewSign == 2 ? "player_tencent" : "player"].on(
  114. viewSign == 2 ? "loadstart" : "s2j_onPlayerInitOver",
  115. () => {
  116. this[viewSign == 2 ? "player_tencent" : "player"].on(
  117. viewSign == 2 ? "pause" : "s2j_onVideoPause",
  118. this.onVideoPause
  119. ); //视频暂停时触发
  120. // this[viewSign == 2 ? "player_tencent" : "player"].on(
  121. // viewSign == 2 ? "playing" : "s2j_onVideoPlay",
  122. // this.onVideoPlay
  123. // );
  124. if (viewSign == 2) {
  125. this.player_tencent.on("playing", this.onVideoPlay);
  126. } else {
  127. this.player.on("s2j_onPlayStart", this.onVideoPlay);
  128. this.player.on("s2j_onVideoPlay", this.onVideoPlay);
  129. }
  130. //视频播放或由暂停恢复播放时触发
  131. this[viewSign == 2 ? "player_tencent" : "player"].on(
  132. viewSign == 2 ? "ended" : "s2j_onPlayOver",
  133. this.onPlayOver
  134. ); //当前视频播放完毕时触发
  135. // this[viewSign == 2 ? "player_tencent" : "player"].on(
  136. // viewSign == 2 ? "error" : "s2j_onPlayerError",
  137. // this.onPlayerError
  138. // ); //播放出现错误时触发
  139. if (viewSign != 2) {
  140. this.player.on("serverError", this.serverError); //发生业务逻辑错误时触发,比如授权验证失败、域名黑白名单验证不通过等错误。参数返回事件名称和错误代码。
  141. }
  142. }
  143. ); //播放器初始化完毕时触发。播放器提供的方法需要在此事件发生后才可以调用。
  144. });
  145. // document.addEventListener("visibilitychange", this.pauseVideo);
  146. },
  147. methods: {
  148. getBeforeWork() {},
  149. //初始化参数
  150. initData() {
  151. this.timeEventStatus = false;
  152. this.commitTime = null;
  153. this.commitTimePhoto = null;
  154. this.openPhotoStatus = 0;
  155. this.videoPauseSetTimeStatus = false;
  156. this.firstPlay = true;
  157. clearTimeout(this.videoPauseSetTimeout); //删除暂停计算拍照定时器
  158. clearTimeout(this.timeEventStatusTimeout); //删除双重保障定时器
  159. },
  160. //获取播放记录
  161. getRecordLast() {
  162. return new Promise((resolve) => {
  163. clearTimeout(this.showRecordSetTimeOut);
  164. let data = {
  165. orderGoodsId: this.goodsData.orderGoodsId,
  166. gradeId: this.goodsData.gradeId,
  167. goodsId: this.goodsData.goodsId,
  168. courseId: this.activeSection.courseId,
  169. moduleId: this.activeSection.moduleId,
  170. chapterId: this.activeSection.chapterId,
  171. sectionId: this.activeSection.sectionId,
  172. };
  173. this.$request.recordLast(data).then((res) => {
  174. if (
  175. res.data &&
  176. res.data.videoCurrentTime &&
  177. res.data.videoCurrentTime - 3 >= 0
  178. ) {
  179. this.activeSection.videoCurrentTime = res.data.videoCurrentTime - 3;
  180. }
  181. resolve();
  182. });
  183. });
  184. },
  185. //从头播放
  186. seekVideo0() {
  187. if (this.viewSign == 2) {
  188. this.player_tencent.currentTime(0);
  189. } else {
  190. this.player.j2s_seekVideo(0);
  191. }
  192. this.showRecordStatus = false;
  193. },
  194. //获取拍照历史记录
  195. getRecordHistoryPhoto() {
  196. return new Promise((resolve) => {
  197. var data = {
  198. sectionId: this.activeSection.sectionId,
  199. goodsId: this.goodsData.goodsId,
  200. courseId: this.activeSection.courseId,
  201. gradeId: this.goodsData.gradeId,
  202. orderGoodsId: this.goodsData.orderGoodsId,
  203. chapterId: this.activeSection.chapterId,
  204. moduleId: this.activeSection.moduleId,
  205. };
  206. this.$request.getPhotoLastRecord(data).then((res) => {
  207. //清空历史数据
  208. this.photoList = [];
  209. this.photoHistoryList = [];
  210. this.photoIndex = 0;
  211. for (let i = 0; i < res.data.length; i++) {
  212. //-2存储随机拍照数组
  213. if (res.data[i].photoIndex == -2 && res.data[i].timeInterval) {
  214. this.photoList = res.data[i].timeInterval.split(",");
  215. } else {
  216. this.photoHistoryList.push(res.data[i].photoIndex);
  217. }
  218. }
  219. resolve();
  220. });
  221. });
  222. },
  223. //计算拍照逻辑
  224. photoLogic() {
  225. if (this.photoList.length > 0 || this.activeSection.learning == 1) return; //已从历史拍照数据获得
  226. if (this.viewSign == 2) {
  227. var polyvPlayerContext = this.player_tencent;
  228. var totalVideoTime = polyvPlayerContext.duration();
  229. var duration =
  230. Number(this.activeSection.videoCurrentTime) ||
  231. polyvPlayerContext.currentTime() ||
  232. 0;
  233. } else {
  234. var polyvPlayerContext = this.player;
  235. var totalVideoTime = polyvPlayerContext.j2s_getDuration();
  236. var duration =
  237. Number(this.activeSection.videoCurrentTime) ||
  238. polyvPlayerContext.j2s_getCurrentTime();
  239. }
  240. if (this.goodsData.erJianErZao) {
  241. this.photoList = this.randomConfig(totalVideoTime, duration);
  242. } else if (this.goodsData.jjShiGongYuan && this.goodsData.orderYears) {
  243. this.photoList = this.ShiPhotoList(totalVideoTime, duration);
  244. } else if (this.goodsData.goodsPhotographConfig.photoNum > 0) {
  245. this.photoList = this.getPhotoList(
  246. totalVideoTime,
  247. this.goodsData.goodsPhotographConfig.photoNum
  248. );
  249. }
  250. //兼容已有观看历史
  251. for (let i = 0; i < this.photoList.length - 1; i++) {
  252. if (this.photoList[i] < duration && this.photoList[i + 1] > duration) {
  253. this.photoIndex = i + 1;
  254. break;
  255. }
  256. if (duration > this.photoList[this.photoList.length - 1]) {
  257. this.photoIndex = this.photoList.length - 1; //取最后一个下标
  258. break;
  259. }
  260. }
  261. },
  262. //普通拍照
  263. getPhotoList(totalVideoTime, photoNum) {
  264. let photoList = [];
  265. if (totalVideoTime >= 900) {
  266. //大于15分钟
  267. if (photoNum == 1) {
  268. //开头拍1张
  269. photoList.push(1);
  270. } else if (photoNum == 3) {
  271. //拍3张
  272. photoList.push(0); //开头拍一张
  273. let centerTime = Math.floor(totalVideoTime / 2); //获取中间时间
  274. let centerMinTime = centerTime - 300; //前后5分钟
  275. let centerMaxTime = centerTime + 300;
  276. let centerTakeTime = this.randomNum(centerMinTime, centerMaxTime);
  277. photoList.push(centerTakeTime); //中间拍一张
  278. let endMaxTime = totalVideoTime - 60;
  279. let endMinTime = totalVideoTime - 300;
  280. let endTakeTime = this.randomNum(endMinTime, endMaxTime);
  281. photoList.push(endTakeTime); //最后拍一张
  282. }
  283. } else {
  284. //小于15分钟,只拍前后各一张
  285. if (photoNum == 1) {
  286. //开头拍1张
  287. photoList.push(1);
  288. } else if (photoNum == 3) {
  289. photoList.push(1);
  290. let centerTime = this.randomNum(
  291. (1 / 3) * totalVideoTime,
  292. (2 / 3) * totalVideoTime
  293. );
  294. photoList.push(centerTime);
  295. let endTakeTime = this.randomNum(
  296. (2 / 3) * totalVideoTime,
  297. totalVideoTime
  298. );
  299. photoList.push(endTakeTime);
  300. }
  301. }
  302. this.postCoursePhotoRecord(true); //提交随机拍照时间数组
  303. return photoList;
  304. },
  305. //施工继教
  306. ShiPhotoList(totalVideoTime) {
  307. //施工继教带年份的订单拍照设置
  308. if (totalVideoTime < 2760) {
  309. var time1 = 46 * 60 - 1; //拍照间隔多久一张 46分钟
  310. } else {
  311. var time1 = 45 * 60 - 1; //拍照间隔多久一张 45分钟
  312. }
  313. let num = Math.ceil(totalVideoTime / time1); //拍照数量
  314. let photoList = [];
  315. if (num == 1) {
  316. photoList.push(parseInt(totalVideoTime / 2));
  317. } else {
  318. for (let i = 0; i < num; i++) {
  319. photoList.push(parseInt((totalVideoTime / (num + 1)) * (i + 1)));
  320. }
  321. }
  322. return photoList;
  323. },
  324. // 随机拍摄时间(二建)
  325. randomConfig(totalVideoTime, duration) {
  326. this.photoHistoryList = [];
  327. let photoList = [duration];
  328. let pre = duration;
  329. if (totalVideoTime > 300) {
  330. while (pre <= totalVideoTime) {
  331. pre += this.randomNum(780, 900);
  332. pre <= totalVideoTime && photoList.push(pre);
  333. }
  334. if (totalVideoTime - 300 > photoList.slice(-1)[0]) {
  335. photoList.push(this.randomNum(totalVideoTime - 180, totalVideoTime));
  336. }
  337. }
  338. return photoList;
  339. },
  340. //postTime = true 只提交随机时间 false 提交拍照
  341. postCoursePhotoRecord(postTime = false, photoUrl) {
  342. return new Promise((resolve, reject) => {
  343. if (this.viewSign == 2) {
  344. var currentTime = this.player_tencent.currentTime();
  345. } else {
  346. var currentTime = this.player.j2s_getCurrentTime();
  347. }
  348. let data = {
  349. goodsId: this.goodsData.goodsId,
  350. gradeId: this.goodsData.gradeId,
  351. orderGoodsId: this.goodsData.orderGoodsId,
  352. courseId: this.activeSection.courseId,
  353. moduleId: this.activeSection.moduleId,
  354. chapterId: this.activeSection.chapterId,
  355. sectionId: this.activeSection.sectionId,
  356. photo: postTime ? "" : photoUrl,
  357. photoTime: parseInt(currentTime > 0 ? currentTime : 0),
  358. photoIndex: postTime ? -2 : parseInt(this.photoIndex), //从0算起,-2只提交随机时间
  359. photoNum: parseInt(this.goodsData.goodsPhotographConfig.photoNum),
  360. timeInterval: postTime ? this.photoList.join(",") : "",
  361. };
  362. this.$request
  363. .coursePhotoRecord(data)
  364. .then((res) => {
  365. resolve(res);
  366. })
  367. .catch((err) => {
  368. reject();
  369. });
  370. });
  371. },
  372. //随机拍摄时间
  373. randomNum(minNum, maxNum) {
  374. switch (arguments.length) {
  375. case 1:
  376. return parseInt(Math.random() * minNum + 1, 10);
  377. break;
  378. case 2:
  379. return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
  380. break;
  381. default:
  382. return 0;
  383. break;
  384. }
  385. },
  386. // 播放视频
  387. loadPlayer() {
  388. return new Promise((resolve) => {
  389. var self = this;
  390. const polyvPlayer = window.polyvPlayer;
  391. self.$request
  392. .obtainpolyvvideosign(self.activeSection.recordingUrl)
  393. .then((res) => {
  394. const autoPlay = self.goodsData.goodsPlayConfig
  395. ? self.goodsData.goodsPlayConfig.autoPlay
  396. : true;
  397. const isAllowSeek =
  398. self.activeSection.learning == 1
  399. ? "off"
  400. : self.goodsData.goodsPlayConfig
  401. ? self.goodsData.goodsPlayConfig.isAllowSeek
  402. : "on";
  403. const playbackRate =
  404. self.activeSection.learning == 1
  405. ? true
  406. : self.goodsData.goodsPlayConfig
  407. ? self.goodsData.goodsPlayConfig.playbackRate
  408. : false;
  409. self.player = polyvPlayer({
  410. wrap: "#player",
  411. width: 810,
  412. height: 455,
  413. preventKeyboardEvent: true, //是否屏蔽键盘事件,为true时屏蔽。
  414. showLine: true, //是否显示线路选择按钮
  415. ban_history_time: "on", //是否禁用续播功能,取值:{on,off}。
  416. vid: self.activeSection.recordingUrl,
  417. autoplay: autoPlay, // 是否自动播放。
  418. ban_seek: isAllowSeek, //是否禁止拖拽进度条,取值:{on,off}。
  419. speed: playbackRate, //当speed参数值为boolean类型时,代表是否显示倍速切换的按钮。
  420. teaser_show: 1, //是否播放片头:0 不播放,1 播放。片头可在管理后台进行设置。
  421. tail_show: 1, //是否播放片尾:0 不播放,1 播放。片尾可在管理后台进行设置。
  422. hideSwitchPlayer: true, //是否隐藏H5和Flash播放器的切换按钮。
  423. watchStartTime: self.activeSection.videoCurrentTime || 0, // 播放开始时间,表示视频从第几秒开始播放,参数值需小于视频时长。
  424. ts: res.data.ts, //移动播放加密视频需传入的时间戳。
  425. sign: res.data.sign, //移动端播放加密视频所需的签名。
  426. playsafe: function (vid, next) {
  427. self.$request.obtainpolyvvideopcsign(vid).then((res) => {
  428. next(res.data);
  429. });
  430. }, //PC端播放加密视频所需的授权凭证。
  431. });
  432. self.$emit("videoScript", this.player); //抛出播放实例
  433. resolve();
  434. });
  435. });
  436. },
  437. //播放视频-腾讯
  438. loadPlayer_tencent() {
  439. return new Promise(async (resolve) => {
  440. try {
  441. let player_tencent_demo = document.createElement("video");
  442. player_tencent_demo.id = "player-tencent";
  443. document
  444. .getElementById("player")
  445. .insertAdjacentElement("afterend", player_tencent_demo);
  446. const { data } = await this.$request.vodvidesignid(
  447. this.activeSection.recordingUrl
  448. );
  449. const autoPlay = this.goodsData.goodsPlayConfig
  450. ? this.goodsData.goodsPlayConfig.autoPlay
  451. : true;
  452. const isAllowSeek =
  453. this.activeSection.learning == 1
  454. ? true
  455. : this.goodsData.goodsPlayConfig
  456. ? this.goodsData.goodsPlayConfig.isAllowSeek == "off"
  457. ? true
  458. : false
  459. : false;
  460. const playbackRate =
  461. this.activeSection.learning == 1
  462. ? true
  463. : this.goodsData.goodsPlayConfig
  464. ? this.goodsData.goodsPlayConfig.playbackRate
  465. : false;
  466. this.player_tencent = TCPlayer("player-tencent", {
  467. width: 810,
  468. height: 455,
  469. preload: "auto",
  470. autoplay: autoPlay,
  471. controlBar: {
  472. progressControl: isAllowSeek,
  473. playbackRateMenuButton: playbackRate,
  474. },
  475. // player-tencent 为播放器容器 ID,必须与 html 中一致
  476. fileID: this.activeSection.recordingUrl, // 请传入需要播放的视频 fileID(必须)
  477. appID: data.appID, // 请传入点播账号的 appID(必须)
  478. licenseUrl: data.licenseUrl, // 请传入点播账号的 appID(必须)
  479. //私有加密播放需填写 psign, psign 即播放器签名,签名介绍和生成方式参见链接:https://cloud.tencent.com/document/product/266/42436
  480. psign: data.psign,
  481. });
  482. this.$emit("videoScript", this.player_tencent); //抛出播放实例
  483. resolve();
  484. } catch (error) {
  485. console.log(error, "error");
  486. }
  487. });
  488. },
  489. //监听器
  490. timeEvent() {
  491. console.log("监听器执行中~");
  492. this.timeEventStatus = true; //双重保障
  493. // 定时提交学习记录
  494. this.submitStudyRecords();
  495. //拍照监听执行
  496. let time = new Date().getTime();
  497. if (time >= this.commitTimePhoto) {
  498. this.watchPhoto();
  499. this.commitTimePhoto = time + 1000;
  500. }
  501. },
  502. //拍照监听执行
  503. watchPhoto() {
  504. if (
  505. this.photoList.length == 0 ||
  506. this.activeSection.learning == 1 ||
  507. this.$refs.takePicture.takePhotoModal ||
  508. this.failToRegister
  509. )
  510. return;
  511. if (this.viewSign == 2) {
  512. var videoTime = this.player_tencent.currentTime();
  513. } else {
  514. var videoTime = this.player.j2s_getCurrentTime();
  515. }
  516. let photoTime = 0; //获取拍照秒数
  517. for (let i = 0; i < this.photoList.length; i++) {
  518. photoTime = Number(this.photoList[i]); //获取拍照秒数
  519. if (photoTime < videoTime && photoTime > videoTime - 8) {
  520. //3秒区间内才触发拍照,避免拉动滚动条
  521. if (
  522. this.photoHistoryList.indexOf(i) < 0 &&
  523. this.activeSection.learning != 1
  524. ) {
  525. //不存在拍照历史,没有重修过,没有学过,则拍照
  526. if (this.viewSign == 2) {
  527. this.player_tencent.pause(); //暂停
  528. } else {
  529. this.player.j2s_pauseVideo(); //暂停
  530. }
  531. this.photoIndex = i;
  532. this.openPhoto(); //启动拍照
  533. }
  534. }
  535. }
  536. },
  537. // 定时提交学习记录
  538. submitStudyRecords() {
  539. let time = new Date().getTime();
  540. if (time >= this.commitTime) {
  541. if (!this.commitTime) {
  542. //防止发生获取不到视频播放时长
  543. this.commitTime = time + 3000;
  544. return;
  545. }
  546. this.postStudyRecord(0, null, null);
  547. this.commitTime = time + 15000;
  548. }
  549. },
  550. //视频暂停时触发
  551. onVideoPause() {
  552. if (
  553. this.activeSection.learning != 1 &&
  554. this.goodsData.erJianErZao &&
  555. !this.failToRegister
  556. ) {
  557. console.log("拍照停留过长计时开始");
  558. this.videoPauseSetTimeout = setTimeout(() => {
  559. if (this.isFullScreen()) {
  560. this.exitFullscreen();
  561. }
  562. this.videoPauseSetTimeStatus = true;
  563. this.$confirm(
  564. "检测播放暂停或拍照停留时间过长,刷新当前页面",
  565. "提示",
  566. {
  567. confirmButtonText: "确定",
  568. cancelButtonText: "取消",
  569. showCancelButton: false,
  570. closeOnClickModal: false,
  571. closeOnPressEscape: false,
  572. showClose: false,
  573. type: "warning",
  574. }
  575. )
  576. .then(() => {
  577. this.$router.go(0);
  578. })
  579. .catch(() => {});
  580. }, 300000); //300000
  581. }
  582. },
  583. //视频恢复播放时触发
  584. onVideoPlay() {
  585. console.log("触发一下吧");
  586. if (this.firstPlay) {
  587. this.firstPlay = false;
  588. //计算拍照逻辑
  589. this.photoLogic();
  590. //开启上次播放位置提示
  591. if (this.activeSection.videoCurrentTime) {
  592. this.showRecordStatus = true;
  593. if (this.viewSign == 2) {
  594. this.player_tencent.currentTime(
  595. Number(this.activeSection.videoCurrentTime) || 0
  596. );
  597. }
  598. this.showRecordSetTimeOut = setTimeout(() => {
  599. this.showRecordStatus = false;
  600. }, 5000);
  601. }
  602. if (this.viewSign == 2) {
  603. this.player_tencent.on("timeupdate", this.timeEvent); //当前视频播放中触发
  604. } else {
  605. this.player.HTML5.video.addEventListener(
  606. "timeupdate",
  607. this.timeEvent
  608. ); //监听器
  609. }
  610. this.timeEventStatusTimeout = setTimeout(() => {
  611. if (!this.timeEventStatus) {
  612. if (this.viewSign == 2) {
  613. this.player_tencent.pause();
  614. } else {
  615. this.player.j2s_pauseVideo();
  616. }
  617. this.$confirm("播放器监听异常,刷新当前页面", "提示", {
  618. confirmButtonText: "确定",
  619. cancelButtonText: "取消",
  620. showCancelButton: false,
  621. closeOnClickModal: false,
  622. closeOnPressEscape: false,
  623. showClose: false,
  624. type: "warning",
  625. })
  626. .then(() => {
  627. this.$router.go(0);
  628. })
  629. .catch(() => {});
  630. }
  631. }, 5000);
  632. }
  633. clearTimeout(this.videoPauseSetTimeout);
  634. console.log("视频恢复播放时触发");
  635. },
  636. //当前视频播放完毕时触发
  637. onPlayOver() {
  638. this.$message({
  639. type: "success",
  640. message: "播放完毕",
  641. });
  642. this.isFullScreen();
  643. clearTimeout(this.videoPauseSetTimeout); //删除暂停计算拍照定时器
  644. this.postStudyRecord(1);
  645. console.log("当前视频播放完毕时触发");
  646. },
  647. //判断是全屏则退出全屏
  648. isFullScreen() {
  649. if (!!(document.webkitIsFullScreen || this.fullele())) {
  650. try {
  651. var de = document;
  652. if (de.exitFullscreen) {
  653. de.exitFullscreen();
  654. } else if (de.mozCancelFullScreen) {
  655. de.mozCancelFullScreen();
  656. } else if (de.webkitCancelFullScreen) {
  657. de.webkitCancelFullScreen();
  658. }
  659. } catch (err) {}
  660. }
  661. },
  662. fullele() {
  663. return (
  664. document.fullscreenElement ||
  665. document.webkitFullscreenElement ||
  666. document.msFullscreenElement ||
  667. document.mozFullScreenElement ||
  668. null
  669. );
  670. },
  671. //播放出现错误时触发
  672. onPlayerError(title, msg) {
  673. this.$router.go(-1);
  674. this.$notify.error({
  675. duration: 0,
  676. title: title || "错误",
  677. message: msg || "视频播放错误,请及时反馈教务人员处理",
  678. });
  679. },
  680. //发生业务逻辑错误
  681. serverError() {
  682. clearTimeout(this.videoPauseSetTimeout); //删除暂停计算拍照定时器
  683. this.$message.error("视频错误或解码失败");
  684. },
  685. //启动拍照
  686. openPhoto() {
  687. if (this.isFullScreen()) {
  688. this.exitFullscreen();
  689. }
  690. setTimeout(() => {
  691. if (this.viewSign == 2) {
  692. this.player_tencent.pause(); //暂停
  693. } else {
  694. this.player.j2s_pauseVideo(); //暂停
  695. }
  696. }, 1000);
  697. this.$refs.takePicture.openPhoto();
  698. this.HideVideo = true;
  699. },
  700. //拍照成功回显 url
  701. async returnParameter(url) {
  702. let file = this.$tools.convertBase64UrlToBlob(url);
  703. try {
  704. var photoUrl = await this.$upload.upload(file, 0, {
  705. gradeId: this.goodsData.gradeId,
  706. orderGoodsId: this.goodsData.orderGoodsId,
  707. });
  708. } catch (err) {
  709. this.$message({
  710. type: "warning",
  711. message: "上传接口报错,请重新拍照上传",
  712. });
  713. setTimeout(() => {
  714. this.openPhoto();
  715. }, 1500);
  716. return;
  717. }
  718. let compareFaceData = await this.faceRecognition(photoUrl);
  719. if (compareFaceData >= 70) {
  720. this.HideVideo = false;
  721. this.postCoursePhotoRecord(false, photoUrl)
  722. .then(async (res) => {
  723. this.photoHistoryList.push(this.photoIndex);
  724. const STATUS = await this.postStudyRecord(
  725. 0,
  726. photoUrl,
  727. compareFaceData
  728. ); //提交记录
  729. //恢复播放
  730. if (STATUS) {
  731. if (this.viewSign == 2) {
  732. var polyvPlayerContext = this.player_tencent;
  733. if (polyvPlayerContext && this.openPhotoStatus !== 1) {
  734. polyvPlayerContext.play();
  735. }
  736. } else {
  737. var polyvPlayerContext = this.player;
  738. if (polyvPlayerContext && this.openPhotoStatus !== 1) {
  739. polyvPlayerContext.j2s_resumeVideo();
  740. }
  741. }
  742. }
  743. })
  744. .catch((err) => {
  745. console.log(err, "err");
  746. this.$message({
  747. type: "warning",
  748. message: "上传接口报错,请重新拍照上传",
  749. });
  750. setTimeout(() => {
  751. this.openPhoto();
  752. }, 1500);
  753. });
  754. } else {
  755. this.$message({
  756. type: "warning",
  757. message: "人脸匹配不通过,请重新拍照上传",
  758. });
  759. setTimeout(() => {
  760. this.openPhoto();
  761. }, 1500);
  762. }
  763. },
  764. /**
  765. * 提交观看记录
  766. * status 1 学完 0未学完
  767. */
  768. postStudyRecord(status = 0, imgUrl, compareFaceData) {
  769. return new Promise((resolve, reject) => {
  770. let currentTime = 0;
  771. let PlayDuration = 0;
  772. if (this.viewSign == 2) {
  773. var polyvPlayerContext = this.player_tencent;
  774. if (polyvPlayerContext) {
  775. currentTime = polyvPlayerContext.currentTime(); //当前视频播放时刻
  776. PlayDuration = currentTime; //本次看的时长--与保利威的不一致
  777. }
  778. } else {
  779. var polyvPlayerContext = this.player;
  780. if (polyvPlayerContext) {
  781. currentTime = polyvPlayerContext.j2s_getCurrentTime(); //当前视频播放时刻
  782. PlayDuration = polyvPlayerContext.j2s_realPlayVideoTime(); //本次看的时长
  783. }
  784. }
  785. let data = {
  786. orderGoodsId: parseInt(this.goodsData.orderGoodsId),
  787. goodsId: parseInt(this.goodsData.goodsId),
  788. gradeId: parseInt(this.goodsData.gradeId),
  789. courseId: this.activeSection.courseId,
  790. moduleId: this.activeSection.moduleId,
  791. chapterId: this.activeSection.chapterId,
  792. sectionId: this.activeSection.sectionId,
  793. fromPlat: 2, //来源平台 1小程序 2网站
  794. photo: imgUrl || "",
  795. studyDuration: parseInt(PlayDuration > 0 ? PlayDuration : 0),
  796. videoCurrentTime: parseInt(currentTime > 0 ? currentTime : 0),
  797. erJianErZao: this.goodsData.erJianErZao,
  798. };
  799. if (imgUrl) {
  800. data.similarity = compareFaceData; // 相似度
  801. }
  802. if (status > 0) {
  803. data.status = status;
  804. }
  805. // /study/record 学习记录
  806. this.$request
  807. .studyRecord(data)
  808. .then((res) => {
  809. if (status > 0) {
  810. this.openPhotoStatus = 0;
  811. this.$message.success("学习完成");
  812. this.$bus.$emit("BackVideoFunc", this.activeSection);
  813. resolve(false);
  814. }
  815. if (this.openPhotoStatus === 1) {
  816. this.postStudyRecord(1);
  817. }
  818. if (status == 0 && this.openPhotoStatus !== 1) {
  819. resolve(true);
  820. }
  821. })
  822. .catch((err) => {
  823. if (err.code === 600) {
  824. if (this.viewSign == 2) {
  825. polyvPlayerContext.pause();
  826. } else {
  827. polyvPlayerContext.j2s_pauseVideo();
  828. }
  829. this.failToRegister = true; //报名推送不通过
  830. this.$confirm(`您的信息正在推送中,请稍后再进入学习!`, "提示", {
  831. confirmButtonText: "确定",
  832. closeOnClickModal: false,
  833. closeOnPressEscape: false,
  834. distinguishCancelAndClose: false,
  835. showCancelButton: false,
  836. showClose: false,
  837. })
  838. .then((_) => {
  839. //停止执行-退出页面
  840. this.$router.back(-1);
  841. })
  842. .catch((_) => {
  843. //停止执行-退出页面
  844. this.$router.back(-1);
  845. });
  846. } else if (err.code === 559) {
  847. console.log("拍照不够触发");
  848. this.$message.error(err.msg);
  849. this.openPhotoStatus = 1;
  850. setTimeout(() => {
  851. this.openPhoto();
  852. }, 1500);
  853. } else if (err.code === 558) {
  854. this.$refs.countDown.openBoxs(parseInt(err.msg.split(",")[1]));
  855. } else if (err.code === 601) {
  856. this.onPlayerError("提交异常", err.msg);
  857. } else {
  858. this.$message.error(err.msg || "未知错误");
  859. }
  860. });
  861. });
  862. },
  863. //人脸校验
  864. faceRecognition(url) {
  865. return new Promise((resolve) => {
  866. this.$request
  867. .faceCertificationCompareFace({
  868. urlA: url,
  869. // imageA: url,
  870. orderGoodsId: this.goodsData.orderGoodsId,
  871. gradeId: this.goodsData.gradeId,
  872. })
  873. .then((res) => {
  874. resolve(Number(res.data));
  875. })
  876. .catch((err) => {
  877. if (err.toString().indexOf("timeout") != -1) {
  878. err = {
  879. msg: "拍照超时,请重新拍照",
  880. };
  881. }
  882. this.$message({
  883. type: "warning",
  884. message: err.msg,
  885. });
  886. setTimeout(() => {
  887. this.openPhoto();
  888. }, 1500);
  889. });
  890. });
  891. },
  892. //页面显示隐藏逻辑
  893. pauseVideo() {
  894. if (
  895. this.$refs.takePicture.takePhotoModal ||
  896. this.$refs.takePicture.photoBadStatus ||
  897. this.failToRegister ||
  898. this.videoPauseSetTimeStatus
  899. ) {
  900. return;
  901. }
  902. let _p = this.player;
  903. if (document.visibilityState === "hidden") {
  904. _p && _p.j2s_pauseVideo();
  905. } else if (
  906. _p &&
  907. parseInt(_p.j2s_getCurrentTime()) < _p.j2s_getDuration()
  908. ) {
  909. this.player.j2s_resumeVideo();
  910. }
  911. },
  912. },
  913. beforeDestroy() {
  914. this.$bus.$off("toPlay");
  915. clearTimeout(this.videoPauseSetTimeout); //删除暂停计算拍照定时器
  916. // document.removeEventListener("visibilitychange", this.pauseVideo);
  917. if (this.player) {
  918. this.player.HTML5.video.removeEventListener("timeupdate", this.timeEvent); //监听器
  919. this.player.destroy(); //初始化播放器
  920. }
  921. if (this.player_tencent) {
  922. this.player_tencent.off();
  923. this.player_tencent.dispose(); //初始化播放器
  924. }
  925. this.timeEventStatus = false;
  926. clearTimeout(this.timeEventStatusTimeout); //删除双重保障定时器
  927. try {
  928. this.$msgbox.close();
  929. } catch (error) {
  930. console.log(error, "element ui - msgBox close error");
  931. }
  932. },
  933. };
  934. </script>
  935. <style lang="scss" scoped>
  936. #videoCy {
  937. width: 100%;
  938. height: 100%;
  939. background: url() no-repeat center center;
  940. background-size: contain;
  941. position: relative;
  942. }
  943. .recordStyle {
  944. position: absolute;
  945. bottom: 60px;
  946. padding: 6px 12px;
  947. left: 8px;
  948. background-color: rgba(0, 0, 0, 0.4);
  949. color: #fff;
  950. border-radius: 24px;
  951. user-select: none;
  952. .videoCurrentTime_style {
  953. display: inline-block;
  954. width: 50px;
  955. text-align: center;
  956. }
  957. .btn_sty {
  958. cursor: pointer;
  959. }
  960. }
  961. </style>