|
@@ -4,19 +4,18 @@
|
|
|
<div class="weixin-content-box">
|
|
<div class="weixin-content-box">
|
|
|
<div class="weixin-title">{{media.name|| tpl.name}}</div>
|
|
<div class="weixin-title">{{media.name|| tpl.name}}</div>
|
|
|
<div class="weixin-content">
|
|
<div class="weixin-content">
|
|
|
- <video-player
|
|
|
|
|
- id="myVideo1"
|
|
|
|
|
- ref="videoPlayer"
|
|
|
|
|
- :playsinline="true"
|
|
|
|
|
- @pause="onPlayerPause($event)"
|
|
|
|
|
- @timeupdate="onPlayerTimeupdate($event)"
|
|
|
|
|
- @play="onPlayerStart($event)"
|
|
|
|
|
- @ready="playerReadied"
|
|
|
|
|
- @canplay="canpay($event)"
|
|
|
|
|
- @ended="onPlayerEnded($event)"
|
|
|
|
|
- :globalOptions="{controls:true}"
|
|
|
|
|
- :options="options">
|
|
|
|
|
- </video-player>
|
|
|
|
|
|
|
+ <video-player id="myVideo"
|
|
|
|
|
+ class="video-player-box"
|
|
|
|
|
+ ref="videoPlayer" :playsinline="true"
|
|
|
|
|
+ @pause="onPlayerPause($event)"
|
|
|
|
|
+ @timeupdate="onPlayerTimeupdate($event)"
|
|
|
|
|
+ @play="onPlayerStart($event)"
|
|
|
|
|
+ @ready="playerReadied"
|
|
|
|
|
+ @ended="onPlayerEnded($event)"
|
|
|
|
|
+ @error="onPlayerError($event)"
|
|
|
|
|
+ :globalOptions="{controls:true}"
|
|
|
|
|
+ :options="options">
|
|
|
|
|
+ </video-player>
|
|
|
<div style="font-size: 16px;; margin: 10px auto;text-align: center;">
|
|
<div style="font-size: 16px;; margin: 10px auto;text-align: center;">
|
|
|
<span>{{curTimes|useTime}}</span>
|
|
<span>{{curTimes|useTime}}</span>
|
|
|
<strong>/</strong>
|
|
<strong>/</strong>
|
|
@@ -29,7 +28,7 @@
|
|
|
|
|
|
|
|
<!-- <div style="width: 100%; margin: 0px auto;"> -->
|
|
<!-- <div style="width: 100%; margin: 0px auto;"> -->
|
|
|
<div class="weixin-content-box" style="padding: 4px;">
|
|
<div class="weixin-content-box" style="padding: 4px;">
|
|
|
- <div class="weixin-content" v-for="item in list" v-if="item.isFinish==0">
|
|
|
|
|
|
|
+ <div class="weixin-content" v-for="item in list" :key="item.id" v-if="item.isFinish==0">
|
|
|
<div class="weixin-course-name">
|
|
<div class="weixin-course-name">
|
|
|
<el-button @click="loadMedia(item)" type="text">{{item.name}} </el-button>
|
|
<el-button @click="loadMedia(item)" type="text">{{item.name}} </el-button>
|
|
|
</div>
|
|
</div>
|
|
@@ -54,7 +53,7 @@
|
|
|
import {
|
|
import {
|
|
|
videoPlayer
|
|
videoPlayer
|
|
|
} from 'vue-video-player';
|
|
} from 'vue-video-player';
|
|
|
- // import 'video.js/dist/video-js.css'
|
|
|
|
|
|
|
+ import 'video.js/dist/video-js.css'
|
|
|
import {
|
|
import {
|
|
|
httpServer
|
|
httpServer
|
|
|
} from "@/components/httpServer/httpServer.js";
|
|
} from "@/components/httpServer/httpServer.js";
|
|
@@ -110,6 +109,18 @@
|
|
|
return hour + ":" + min + ":" + sec
|
|
return hour + ":" + min + ":" + sec
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ beforeDestroy() {
|
|
|
|
|
+ this.stopTick()
|
|
|
|
|
+ if (this._seekTimer) clearTimeout(this._seekTimer)
|
|
|
|
|
+ this.reportErr("play", 'destroy');
|
|
|
|
|
+ document.removeEventListener("visibilitychange", this._onVisibilityChange)
|
|
|
|
|
+ document.removeEventListener("keydown", this._onKeyDown)
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ player() {
|
|
|
|
|
+ return this.$refs.videoPlayer.player
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
methods: {
|
|
methods: {
|
|
|
...mapActions("user", ["loadBaseInfo"]),
|
|
...mapActions("user", ["loadBaseInfo"]),
|
|
|
getCourse() {
|
|
getCourse() {
|
|
@@ -123,11 +134,13 @@
|
|
|
item.percent = getPercent(item)||0;
|
|
item.percent = getPercent(item)||0;
|
|
|
return item;
|
|
return item;
|
|
|
})
|
|
})
|
|
|
- for( let i in this,list){
|
|
|
|
|
- if( this.list[i].mediaId == this.userInfo.mediaId){
|
|
|
|
|
- this.loadMedia( this.list[i])
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // this.options.poster = tpl.tb
|
|
|
|
|
+ // for(let i=0; i<this.list.length; i++){
|
|
|
|
|
+ // if( this.list[i].mediaId == this.userInfo.mediaId){
|
|
|
|
|
+ // this.loadMedia( this.list[i])
|
|
|
|
|
+ // break;
|
|
|
|
|
+ // }
|
|
|
|
|
+ // }
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
@@ -147,36 +160,71 @@
|
|
|
that.reportErr("play", '' + err.message)
|
|
that.reportErr("play", '' + err.message)
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- playerReadied(audio) {
|
|
|
|
|
|
|
+ playerReadied(player) {
|
|
|
|
|
+ let that = this;
|
|
|
this.isReady = true
|
|
this.isReady = true
|
|
|
- let myPlayer = this.$refs.videoPlayer.player;
|
|
|
|
|
- myPlayer && myPlayer.play()
|
|
|
|
|
|
|
+
|
|
|
|
|
+ setTimeout(() => this.fixTouchEvents(player), 0);
|
|
|
|
|
+
|
|
|
|
|
+ if (this.media.position > 5) {
|
|
|
|
|
+ player.one('loadedmetadata', function () {
|
|
|
|
|
+ let pos = that.media.position
|
|
|
|
|
+ if (pos < player.duration()) {
|
|
|
|
|
+ that.setposition(pos)
|
|
|
|
|
+ that._seekTimer = setTimeout(function () {
|
|
|
|
|
+ if (!player.el_) return
|
|
|
|
|
+ player.currentTime(0)
|
|
|
|
|
+ player.play()
|
|
|
|
|
+ }, 15000)
|
|
|
|
|
+ player.one('seeked', function () {
|
|
|
|
|
+ clearTimeout(that._seekTimer)
|
|
|
|
|
+ that._seekTimer = null
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ if (that.player && that.player.volume) {
|
|
|
|
|
+ that.player.volume(that.volume)
|
|
|
|
|
+ that.player.muted(that.isMuted)
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 1000)
|
|
|
|
|
+ },
|
|
|
|
|
+ fixTouchEvents(player) {
|
|
|
|
|
+ let slider = player.controlBar &&
|
|
|
|
|
+ player.controlBar.progressControl &&
|
|
|
|
|
+ player.controlBar.progressControl.seekBar
|
|
|
|
|
+ if (!slider) return
|
|
|
|
|
+ if (slider.handleTouchStart_) {
|
|
|
|
|
+ document.removeEventListener('touchstart', slider.handleTouchStart_)
|
|
|
|
|
+ document.addEventListener('touchstart', slider.handleTouchStart_, { passive: false })
|
|
|
|
|
+ }
|
|
|
|
|
+ if (slider.handleTouchMove_) {
|
|
|
|
|
+ document.removeEventListener('touchmove', slider.handleTouchMove_)
|
|
|
|
|
+ document.addEventListener('touchmove', slider.handleTouchMove_, { passive: false })
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
onPlayerTimeupdate(player) {
|
|
onPlayerTimeupdate(player) {
|
|
|
- let myPlayer = this.$refs.videoPlayer.player;
|
|
|
|
|
|
|
+ let myPlayer = this.$refs.videoPlayer && this.$refs.videoPlayer.player;
|
|
|
|
|
+ if (!myPlayer) return;
|
|
|
let curTimes = player.cache_.currentTime;
|
|
let curTimes = player.cache_.currentTime;
|
|
|
this.curTimes = curTimes || 0
|
|
this.curTimes = curTimes || 0
|
|
|
- if (this.media.isFinish) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- canpay(player){
|
|
|
|
|
- console.log("player", this.media.position )
|
|
|
|
|
- if( this.media.position > this.curTimes){
|
|
|
|
|
- player.currentTime(this.media.position+3);
|
|
|
|
|
- setTimeout( ()=>{player.play()}, 100)
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
},
|
|
},
|
|
|
setposition(position) {
|
|
setposition(position) {
|
|
|
- if (position > this.media.duration) position = this.media.duration;
|
|
|
|
|
- let player = this.$refs.videoPlayer.player;
|
|
|
|
|
- player.currentTime(position);
|
|
|
|
|
- player.play()
|
|
|
|
|
|
|
+ console.log("setposition", position)
|
|
|
|
|
+ let player = this.$refs.videoPlayer && this.$refs.videoPlayer.player;
|
|
|
|
|
+ if (!player || !player.el_) return;
|
|
|
|
|
+ let dur = this.media.duration
|
|
|
|
|
+ if (position > dur) position = dur;
|
|
|
|
|
+ console.log("currentTime", position)
|
|
|
if (this.media.isFinish) return;
|
|
if (this.media.isFinish) return;
|
|
|
- if (this.media.position >= this.media.duration - 10 && !this.media.isFinish) {
|
|
|
|
|
|
|
+ if (position >= dur - 10 && !this.media.isFinish) {
|
|
|
this.tick(true)
|
|
this.tick(true)
|
|
|
|
|
+ }else{
|
|
|
|
|
+ player.currentTime(position);
|
|
|
}
|
|
}
|
|
|
|
|
+ // this.onPlay = true
|
|
|
},
|
|
},
|
|
|
onPlayerPause(event) {
|
|
onPlayerPause(event) {
|
|
|
this.reportErr("play", 'pause');
|
|
this.reportErr("play", 'pause');
|
|
@@ -193,28 +241,54 @@
|
|
|
this.$emit("close")
|
|
this.$emit("close")
|
|
|
},
|
|
},
|
|
|
doPause() {
|
|
doPause() {
|
|
|
- // console.log("doPause")
|
|
|
|
|
|
|
+ console.log("doPause")
|
|
|
this.stopTick()
|
|
this.stopTick()
|
|
|
this.onPlay = false
|
|
this.onPlay = false
|
|
|
let myPlayer = this.$refs.videoPlayer.player;
|
|
let myPlayer = this.$refs.videoPlayer.player;
|
|
|
myPlayer && myPlayer.pause()
|
|
myPlayer && myPlayer.pause()
|
|
|
},
|
|
},
|
|
|
doPlay() {
|
|
doPlay() {
|
|
|
- console.log("doPlay")
|
|
|
|
|
this.onPlay = true
|
|
this.onPlay = true
|
|
|
this.startTick();
|
|
this.startTick();
|
|
|
if (!this.$refs.videoPlayer || !this.$refs.videoPlayer.player) return;
|
|
if (!this.$refs.videoPlayer || !this.$refs.videoPlayer.player) return;
|
|
|
if (!this.dialog) return this.doPause();
|
|
if (!this.dialog) return this.doPause();
|
|
|
let myPlayer = this.$refs.videoPlayer.player;
|
|
let myPlayer = this.$refs.videoPlayer.player;
|
|
|
- myPlayer && myPlayer.play()
|
|
|
|
|
|
|
+ let p = myPlayer && myPlayer.play()
|
|
|
|
|
+ if (p && p.catch) p.catch(function() {
|
|
|
|
|
+ console.log('doPay', p)
|
|
|
|
|
+ })
|
|
|
this.tickNum = 0
|
|
this.tickNum = 0
|
|
|
},
|
|
},
|
|
|
|
|
+ setVolume(val) {
|
|
|
|
|
+ this.volume = val
|
|
|
|
|
+ this.isMuted = val === 0
|
|
|
|
|
+ if (this.player && this.player.volume) {
|
|
|
|
|
+ this.player.volume(val)
|
|
|
|
|
+ this.player.muted(this.isMuted)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleMute() {
|
|
|
|
|
+ this.isMuted = !this.isMuted
|
|
|
|
|
+ if (this.player && this.player.volume) {
|
|
|
|
|
+ this.player.muted(this.isMuted)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
onPlayerStart() {
|
|
onPlayerStart() {
|
|
|
- console.log("onPlayerStart")
|
|
|
|
|
|
|
+ // console.log("onPlayerStart")
|
|
|
this.reportErr("play", 'start');
|
|
this.reportErr("play", 'start');
|
|
|
this.startTick();
|
|
this.startTick();
|
|
|
this.onPlay = true
|
|
this.onPlay = true
|
|
|
},
|
|
},
|
|
|
|
|
+ startMonitor() {
|
|
|
|
|
+ this._onVisibilityChange = () => {
|
|
|
|
|
+ if (document.visibilityState == "hidden") {
|
|
|
|
|
+ this.reportErr("play", 'hidden');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.reportErr("play", 'show');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ document.addEventListener("visibilitychange", this._onVisibilityChange);
|
|
|
|
|
+ },
|
|
|
reportErr(action, msg) {
|
|
reportErr(action, msg) {
|
|
|
httpServer("course.report", {
|
|
httpServer("course.report", {
|
|
|
action,
|
|
action,
|
|
@@ -224,25 +298,42 @@
|
|
|
tick(force = false) {
|
|
tick(force = false) {
|
|
|
let media = this.media;
|
|
let media = this.media;
|
|
|
this.tickNum++
|
|
this.tickNum++
|
|
|
|
|
+ // 检测卡死:两次 tick 无进度则跳过 5 秒
|
|
|
|
|
+ let myPlayer = this.$refs.videoPlayer && this.$refs.videoPlayer.player;
|
|
|
|
|
+ if (myPlayer) {
|
|
|
|
|
+ let now = parseInt(myPlayer.currentTime())
|
|
|
|
|
+ if (this._lastCurTime !== undefined && now === this._lastCurTime && this.onPlay) {
|
|
|
|
|
+ this._stuckCount = (this._stuckCount || 0) + 1
|
|
|
|
|
+ if (this._stuckCount >= 2) {
|
|
|
|
|
+ this._stuckCount = 0
|
|
|
|
|
+ let skip = now + 5
|
|
|
|
|
+ console.log("stuck skip to", skip)
|
|
|
|
|
+ myPlayer.currentTime(skip)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this._stuckCount = 0
|
|
|
|
|
+ }
|
|
|
|
|
+ this._lastCurTime = now
|
|
|
|
|
+ }
|
|
|
|
|
+ // 已经完成
|
|
|
if (this.media.isFinish) {
|
|
if (this.media.isFinish) {
|
|
|
console.log("finish")
|
|
console.log("finish")
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
// 主动暂停
|
|
// 主动暂停
|
|
|
- let myPlayer = this.$refs.videoPlayer.player;
|
|
|
|
|
- let curTimes = parseInt(myPlayer.currentTime());
|
|
|
|
|
- // 后退无心跳
|
|
|
|
|
- if (this.media.position > curTimes && !force) return;
|
|
|
|
|
|
|
+ if (!myPlayer) return;
|
|
|
|
|
+
|
|
|
|
|
+ let curTimes = this._lastCurTime
|
|
|
if (curTimes < 4) {
|
|
if (curTimes < 4) {
|
|
|
console.log("curTimes")
|
|
console.log("curTimes")
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- let isFinish = force ? 1 : 0
|
|
|
|
|
- if (curTimes >= media.duration) isFinish = 1;
|
|
|
|
|
|
|
+ let isFinish = media.isFinish||0;
|
|
|
|
|
+ if (curTimes >= media.duration-15) isFinish = 1;
|
|
|
// 拉到后面
|
|
// 拉到后面
|
|
|
if (!isFinish) {
|
|
if (!isFinish) {
|
|
|
if (!this.onPlay) return;
|
|
if (!this.onPlay) return;
|
|
|
|
|
+ if(curTimes <= media.position) return;
|
|
|
}
|
|
}
|
|
|
if(window.navigator.webdriver){
|
|
if(window.navigator.webdriver){
|
|
|
this.reportErr("play", 'webdriver');
|
|
this.reportErr("play", 'webdriver');
|
|
@@ -253,29 +344,37 @@
|
|
|
let param = {
|
|
let param = {
|
|
|
id: media.id,
|
|
id: media.id,
|
|
|
position: curTimes,
|
|
position: curTimes,
|
|
|
- isFinish
|
|
|
|
|
|
|
+ isFinish,
|
|
|
|
|
+ force
|
|
|
};
|
|
};
|
|
|
httpServer("course.tick", param, true).then(res => {
|
|
httpServer("course.tick", param, true).then(res => {
|
|
|
- if (res.code == 200) {
|
|
|
|
|
- let {
|
|
|
|
|
- skip,
|
|
|
|
|
- position,
|
|
|
|
|
- pause,
|
|
|
|
|
- closed
|
|
|
|
|
- } = res.data
|
|
|
|
|
- if (pause || closed) {
|
|
|
|
|
- this.doPause();
|
|
|
|
|
- this.$emit("close")
|
|
|
|
|
- if (closed) {
|
|
|
|
|
- this.$message.errorMsg("禁止多处同时学习", 5);
|
|
|
|
|
- } else if (pause) {
|
|
|
|
|
- this.$message.errorMsg("禁止多处同时学习", 5);
|
|
|
|
|
- }
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ if (res.code != 200) return;
|
|
|
|
|
+ let {
|
|
|
|
|
+ skip,
|
|
|
|
|
+ position,
|
|
|
|
|
+ pause,
|
|
|
|
|
+ msg,
|
|
|
|
|
+ closed
|
|
|
|
|
+ } = res.data
|
|
|
|
|
+ if (pause || closed) {
|
|
|
|
|
+ this.doPause();
|
|
|
|
|
+ this.$emit("close")
|
|
|
|
|
+ if (closed) {
|
|
|
|
|
+ this.$message.errorMsg(msg||"禁止多处同时学习", 5);
|
|
|
|
|
+ } else if (pause) {
|
|
|
|
|
+ this.$message.errorMsg(msg||"禁止多处同时学习", 5);
|
|
|
|
|
+ }
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!skip) {
|
|
|
|
|
+ let diff = Math.abs(position - curTimes);
|
|
|
|
|
+ if (diff > 5) {
|
|
|
|
|
+ this.setposition(position);
|
|
|
}
|
|
}
|
|
|
- if (!skip) this.setposition(position);
|
|
|
|
|
- Object.assign(this.media, res.data)
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ Object.assign(param, res.data)
|
|
|
|
|
+ this.$emit("update", param)
|
|
|
|
|
+
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
loadMedia( item ) {
|
|
loadMedia( item ) {
|
|
@@ -287,11 +386,18 @@
|
|
|
this.media.position = position;
|
|
this.media.position = position;
|
|
|
this.media.duration = duration;
|
|
this.media.duration = duration;
|
|
|
this.media.id = id;
|
|
this.media.id = id;
|
|
|
- this.options.sources = [{src:this.mediaUrl,type: "application/x-mpegURL"}];
|
|
|
|
|
- this.options.playtimes = position||1;
|
|
|
|
|
- this.options.autoplay = position>0;
|
|
|
|
|
- this.curTimes = 0
|
|
|
|
|
- this.$refs.player && this.$refs.player.preload()
|
|
|
|
|
|
|
+ this.options = {
|
|
|
|
|
+ ...this.options,
|
|
|
|
|
+ sources: [{src:mediaUrl,type: "application/x-mpegURL"}],
|
|
|
|
|
+ playtimes: position||0,
|
|
|
|
|
+ html5: {
|
|
|
|
|
+ hls: {
|
|
|
|
|
+ overrideNative: true
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ autoplay: true
|
|
|
|
|
+ }
|
|
|
|
|
+ this.curTimes = 0;
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -313,4 +419,20 @@
|
|
|
</script>
|
|
</script>
|
|
|
<style>
|
|
<style>
|
|
|
@import url("./index.css");
|
|
@import url("./index.css");
|
|
|
|
|
+ .video-js {
|
|
|
|
|
+ touch-action: none;
|
|
|
|
|
+ .vjs-control-bar {
|
|
|
|
|
+ .vjs-icon-custombutton {
|
|
|
|
|
+ font-family: VideoJS;
|
|
|
|
|
+ font-weight: normal;
|
|
|
|
|
+ font-style: normal;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .vjs-icon-custombutton:before {
|
|
|
|
|
+ content: "\f108";
|
|
|
|
|
+ font-size: 1.8em;
|
|
|
|
|
+ line-height: 1.67;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
</style>
|
|
</style>
|