index.vue 33 KB

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