index.vue 32 KB

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