media.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. <template>
  2. <div>
  3. <!-- -->
  4. <div class="v-wrap-marks"></div>
  5. <video-player id="myVideo" class="video-player-box" ref="videoPlayer" :playsinline="true"
  6. @pause="onPlayerPause($event)" @timeupdate="onPlayerTimeupdate($event)" @play="onPlayerStart($event)"
  7. @ready="playerReadied" @ended="onPlayerEnded($event)" @error="onPlayerError($event)"
  8. :globalOptions="{controls:true}" :options="options">
  9. </video-player>
  10. <div class="dialog-footer pt30">
  11. <el-row class="media-footer">
  12. <el-col :span="8" class="media-time">
  13. <span>{{curTimes|useTime}}</span>
  14. <strong>/</strong>
  15. <span>{{media.duration|useTime}}</span>
  16. </el-col>
  17. <el-col :span="4" class="media-center">
  18. <el-button class="bicon" v-if="!onPlay" type="primary" @click="doPlay" icon="el-icon-video-play"
  19. circle></el-button>
  20. <el-button class="bicon" v-else type="warning" @click="doPause" icon="el-icon-video-pause" circle></el-button>
  21. </el-col>
  22. <el-col :span="6" class="media-center">
  23. <span class="volume-control">
  24. <span class="vol-icon" @click="toggleMute">音量</span>
  25. <el-slider class="volume-slider" :value="volume" :min="0" :max="1" :step="0.05"
  26. @input="setVolume"></el-slider>
  27. </span>
  28. </el-col>
  29. <el-col :span="6" class="media-select">
  30. <el-button class="bicon" type="danger" icon="el-icon-close" @click="onClose" style="float: right;">
  31. </el-button>
  32. <div style="margin-top:0px;float: right;">
  33. <el-select placeholder="流畅" :value="mediaType" class="media-el-select"
  34. @change="(val)=>{$emit('changeMedia', val)}">
  35. <el-option label="流畅" value="ld"></el-option>
  36. <el-option label="标清" value="hls"></el-option>
  37. </el-select>
  38. </div>
  39. </el-col>
  40. </el-row>
  41. </div>
  42. </div>
  43. </template>
  44. <script>
  45. import {
  46. httpServer
  47. } from "@/components/httpServer/httpServer.js";
  48. import md5 from 'js-md5';
  49. import {
  50. videoPlayer
  51. } from 'vue-video-player';
  52. import 'video.js/dist/video-js.css'
  53. import {
  54. MessageBox
  55. } from "element-ui";
  56. export default {
  57. name: "Index",
  58. data() {
  59. return {
  60. timer: false,
  61. tickNum: 0,
  62. prevTime: 0,
  63. isReady: false,
  64. curTimes: 0,
  65. onPlay: false,
  66. volume: 0.5,
  67. isMuted: false
  68. }
  69. },
  70. components: {
  71. videoPlayer
  72. },
  73. watch: {
  74. mediaType() {
  75. if (!this.mediaType) return;
  76. },
  77. dialog(showDilog) {
  78. if (!showDilog) {
  79. this.doPause()
  80. this.isReady = false
  81. }
  82. }
  83. },
  84. props: {
  85. mediaType: {
  86. type: String,
  87. default: ''
  88. },
  89. media: {
  90. type: Object,
  91. default: () => {
  92. return {
  93. id: '',
  94. percent: 0
  95. }
  96. }
  97. },
  98. dialog: {
  99. type: Boolean,
  100. default: true
  101. },
  102. options: {
  103. type: Object,
  104. default: () => {
  105. return {}
  106. }
  107. }
  108. },
  109. filters: {
  110. useTime(val) {
  111. let timestr = ""
  112. let hour = parseInt(val / 3600);
  113. let min = parseInt(val / 60 % 60);
  114. let sec = parseInt(val % 60);
  115. if (hour < 10) hour = "0" + hour;
  116. if (min < 10) min = "0" + min;
  117. if (sec < 10) sec = "0" + sec;
  118. return hour + ":" + min + ":" + sec
  119. }
  120. },
  121. beforeDestroy() {
  122. this.stopTick()
  123. this.reportErr("play", 'destroy');
  124. },
  125. computed: {
  126. player() {
  127. return this.$refs.videoPlayer.player
  128. },
  129. },
  130. created() {
  131. this.startMonitor()
  132. },
  133. methods: {
  134. setMarks() {
  135. console.log("setMarks")
  136. if (!this.options.marks) return;
  137. var div = document.getElementById('myVideo')
  138. var div1 = div.firstChild
  139. var div3 = document.createElement("div");
  140. div3.setAttribute("class", "resize-drag");
  141. if (this.mediaType == 'ld') {
  142. div3.style.cssText =
  143. "position:absolute;top:52px;left:10px; background-color: #f4f4f4;width:50px; height:50px;border-radius: 50%;";
  144. } else {
  145. div3.style.cssText =
  146. "position:absolute;top:80px;left:18px; background-color: #f4f4f4;width:50px; height:50px;border-radius: 50%;";
  147. }
  148. div1.appendChild(div3)
  149. },
  150. startTick() {
  151. let tick = this.tryTick;
  152. if (this.timer) clearInterval(this.timer);
  153. this.timer = setInterval(tick, 5 * 1000);
  154. },
  155. stopTick() {
  156. if (this.timer) clearInterval(this.timer);
  157. },
  158. tryTick() {
  159. let that = this;
  160. try {
  161. that.tick()
  162. } catch (err) {
  163. that.reportErr("play", '' + err.message)
  164. }
  165. },
  166. playerReadied(player) {
  167. let that = this;
  168. this.isReady = true
  169. setTimeout(() => this.setMarks(), 600);
  170. if (this.media.position > 5 && this.media.position < this.media.duration) {
  171. setTimeout(function() {
  172. that.setposition(that.media.position)
  173. }, 300)
  174. }
  175. setTimeout(() => {
  176. if (that.player && that.player.volume) {
  177. that.player.volume(that.volume)
  178. that.player.muted(that.isMuted)
  179. }
  180. }, 1000)
  181. },
  182. onPlayerTimeupdate(player) {
  183. let myPlayer = this.$refs.videoPlayer.player;
  184. let curTimes = player.cache_.currentTime;
  185. this.curTimes = curTimes || 0
  186. if (this.media.isFinish) {
  187. return;
  188. }
  189. },
  190. setposition(position) {
  191. console.log("setposition", position)
  192. if (position > this.media.duration) position = this.media.duration;
  193. let player = this.$refs.videoPlayer.player;
  194. player.currentTime(position);
  195. if (this.media.isFinish) return;
  196. if (this.media.position >= this.media.duration - 10 && !this.media.isFinish) {
  197. this.tick(true)
  198. }
  199. // this.onPlay = true
  200. },
  201. onPlayerPause(event) {
  202. this.reportErr("play", 'pause');
  203. this.stopTick()
  204. this.onPlay = false
  205. },
  206. onPlayerError(event) {
  207. this.reportErr("play", 'error');
  208. },
  209. onPlayerEnded(event) {
  210. this.reportErr("play", 'end');
  211. this.tick(true)
  212. },
  213. onClose() {
  214. this.reportErr("play", 'close')
  215. this.doPause()
  216. this.$emit("close")
  217. },
  218. doPause() {
  219. // console.log("doPause")
  220. this.stopTick()
  221. this.onPlay = false
  222. let myPlayer = this.$refs.videoPlayer.player;
  223. myPlayer && myPlayer.pause()
  224. },
  225. doPlay() {
  226. this.onPlay = true
  227. this.startTick();
  228. if (!this.$refs.videoPlayer || !this.$refs.videoPlayer.player) return;
  229. if (!this.dialog) return this.doPause();
  230. let myPlayer = this.$refs.videoPlayer.player;
  231. let p = myPlayer && myPlayer.play()
  232. if (p && p.catch) p.catch(function() {})
  233. this.tickNum = 0
  234. },
  235. setVolume(val) {
  236. this.volume = val
  237. this.isMuted = val === 0
  238. if (this.player && this.player.volume) {
  239. this.player.volume(val)
  240. this.player.muted(this.isMuted)
  241. }
  242. },
  243. toggleMute() {
  244. this.isMuted = !this.isMuted
  245. if (this.player && this.player.volume) {
  246. this.player.muted(this.isMuted)
  247. }
  248. },
  249. onPlayerStart() {
  250. // console.log("onPlayerStart")
  251. this.reportErr("play", 'start');
  252. this.startTick();
  253. this.onPlay = true
  254. },
  255. startMonitor() {
  256. let that = this
  257. document.addEventListener("visibilitychange", function() {
  258. // || document.hidden
  259. if (document.visibilityState == "hidden") {
  260. // that.doPause( )
  261. that.reportErr("play", 'hidden');
  262. } else {
  263. that.reportErr("play", 'show');
  264. // that.doPlay()
  265. }
  266. });
  267. // 监听F12
  268. document.addEventListener("keydown", function(e) {
  269. if (e.key == "F12") {
  270. that.reportErr("play", 'keydownF12');
  271. try { e.preventDefault() } catch (err) {}
  272. }
  273. });
  274. // setInterval(function() { check() }, 1000);
  275. // var check = function() {
  276. // function doCheck(a) {
  277. // if (("" + a / a)["length"] !== 1 || a % 20 === 0) {
  278. // (function() {}
  279. // ["constructor"]("debugger")())
  280. // } else {
  281. // (function() {}
  282. // ["constructor"]("debugger")())
  283. // }
  284. // doCheck(++a)
  285. // }
  286. // try {
  287. // doCheck(0)
  288. // } catch (err) {}
  289. // };
  290. },
  291. // startMonitor() {
  292. // let that = this
  293. // window.onblur = function() {
  294. // that.doPause()
  295. // }
  296. // window.onfocus = function () {
  297. // that.doPlay()
  298. // }
  299. // },
  300. tickWait() {
  301. this.doPause()
  302. let that = this
  303. MessageBox({
  304. title: "连续学习超15分钟",
  305. message: "是否继续学习",
  306. showCancelButton: true,
  307. confirmButtonText: "确定",
  308. cancelButtonText: "取消",
  309. beforeClose: (action, instance, done) => {
  310. if (action === "confirm") {
  311. that.doPlay();
  312. done();
  313. } else {
  314. done();
  315. }
  316. }
  317. })
  318. },
  319. reportErr(action, msg) {
  320. httpServer("course.report", {
  321. action,
  322. msg
  323. })
  324. },
  325. tick(force = false) {
  326. let media = this.media;
  327. this.tickNum++
  328. // 已经完成
  329. if (this.media.isFinish) {
  330. console.log("finish")
  331. return;
  332. }
  333. // 主动暂停
  334. let myPlayer = this.$refs.videoPlayer.player;
  335. let curTimes = parseInt(myPlayer.currentTime());
  336. if (curTimes < 4) {
  337. console.log("curTimes")
  338. return;
  339. }
  340. let isFinish = force ? 1 : 0
  341. if (curTimes >= media.duration-15) isFinish = 1;
  342. // 拉到后面
  343. if (!isFinish) {
  344. if (!this.onPlay) return;
  345. if(curTimes <= media.position) return;
  346. }
  347. if(window.navigator.webdriver){
  348. this.reportErr("play", 'webdriver');
  349. this.doPause()
  350. return
  351. }
  352. // 强制完成
  353. let param = {
  354. id: media.id,
  355. position: curTimes,
  356. isFinish
  357. };
  358. httpServer("course.tick", param, true).then(res => {
  359. if (res.code == 200) {
  360. let {
  361. skip,
  362. position,
  363. pause,
  364. closed
  365. } = res.data
  366. if (pause || closed) {
  367. this.doPause();
  368. this.$emit("close")
  369. if (closed) {
  370. this.$message.errorMsg("禁止多处同时学习", 5);
  371. } else if (pause) {
  372. this.$message.errorMsg("禁止多处同时学习", 5);
  373. }
  374. return
  375. }
  376. if (!skip) {
  377. let diff = Math.abs(position - curTimes);
  378. if (diff > 3) {
  379. this.setposition(position);
  380. }
  381. }
  382. Object.assign(param, res.data)
  383. this.$emit("update", param)
  384. }
  385. })
  386. }
  387. }
  388. }
  389. </script>
  390. <style>
  391. .video-js {
  392. touch-action: none;
  393. .vjs-control-bar {
  394. .vjs-icon-custombutton {
  395. font-family: VideoJS;
  396. font-weight: normal;
  397. font-style: normal;
  398. }
  399. .vjs-icon-custombutton:before {
  400. content: "\f108";
  401. font-size: 1.8em;
  402. line-height: 1.67;
  403. }
  404. }
  405. }
  406. .p-process {
  407. width: 100%;
  408. margin: 20px auto;
  409. height: 30px;
  410. }
  411. .media-footer {
  412. padding: 0px 30px;
  413. text-align: left;
  414. line-height: 40px !important;
  415. bottom: -10px;
  416. }
  417. .media-center {
  418. text-align: center;
  419. padding: 0px;
  420. display: flex;
  421. align-items: center;
  422. justify-content: center;
  423. gap: 8px;
  424. }
  425. .volume-control {
  426. display: inline-flex;
  427. align-items: center;
  428. gap: 4px;
  429. }
  430. .vol-icon {
  431. font-size: 12px;
  432. font-weight: bold;
  433. cursor: pointer;
  434. color: #606266;
  435. user-select: none;
  436. line-height: 1;
  437. }
  438. .vol-icon:hover {
  439. color: #409eff;
  440. }
  441. .volume-slider {
  442. width: 80px !important;
  443. }
  444. .volume-slider .el-slider__runway {
  445. height: 4px;
  446. }
  447. .volume-slider .el-slider__bar {
  448. height: 4px;
  449. }
  450. .volume-slider .el-slider__button {
  451. width: 12px;
  452. height: 12px;
  453. }
  454. .media-time {
  455. font-size: 18px;
  456. vertical-align: center;
  457. }
  458. .media-select {
  459. white-space: nowrap;
  460. text-align: right;
  461. line-height: 40px !important;
  462. float: right;
  463. margin: 0px !important;
  464. }
  465. .bicon {
  466. font-size: 28px !important;
  467. padding: 4px !important;
  468. }
  469. .media-el-select {
  470. font-size: 28px !important;
  471. width: 80px;
  472. padding: -4px auto !important;
  473. }
  474. </style>