|
@@ -24,6 +24,9 @@
|
|
|
<el-slider class="volume-slider" :value="volume" :min="0" :max="1" :step="0.05"
|
|
<el-slider class="volume-slider" :value="volume" :min="0" :max="1" :step="0.05"
|
|
|
@input="setVolume"></el-slider>
|
|
@input="setVolume"></el-slider>
|
|
|
</span>
|
|
</span>
|
|
|
|
|
+ <span class="pip-control">
|
|
|
|
|
+ <span class="pip-icon" :class="{ 'is-active': isPiP }" @click="togglePiP">{{ isPiP ? '退出画中画' : '画中画' }}</span>
|
|
|
|
|
+ </span>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="6" class="media-select">
|
|
<el-col :span="6" class="media-select">
|
|
|
<div style="margin-top:0px;float: right;">
|
|
<div style="margin-top:0px;float: right;">
|
|
@@ -119,7 +122,7 @@
|
|
|
options: {
|
|
options: {
|
|
|
controls: true,
|
|
controls: true,
|
|
|
autoplay: true,
|
|
autoplay: true,
|
|
|
- muted: true,
|
|
|
|
|
|
|
+ muted: false,
|
|
|
loop: false,
|
|
loop: false,
|
|
|
preload: "auto",
|
|
preload: "auto",
|
|
|
language: 'zh-CN',
|
|
language: 'zh-CN',
|
|
@@ -150,7 +153,10 @@
|
|
|
mediaType: 'hls',
|
|
mediaType: 'hls',
|
|
|
hasSeeked: false,
|
|
hasSeeked: false,
|
|
|
hideFinished: false,
|
|
hideFinished: false,
|
|
|
- showPanel: true
|
|
|
|
|
|
|
+ showPanel: true,
|
|
|
|
|
+ hiddenStartTime: 0,
|
|
|
|
|
+ hiddenBasePosition: 0,
|
|
|
|
|
+ isPiP: false
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
components: {
|
|
components: {
|
|
@@ -224,6 +230,45 @@
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
|
|
+ handleVisibilityChange() {
|
|
|
|
|
+ if (document.hidden) {
|
|
|
|
|
+ this.enterPiP()
|
|
|
|
|
+ let myPlayer = this.$refs.videoPlayer && this.$refs.videoPlayer.player
|
|
|
|
|
+ if (myPlayer && myPlayer.currentTime) {
|
|
|
|
|
+ this.hiddenStartTime = Date.now()
|
|
|
|
|
+ this.hiddenBasePosition = myPlayer.currentTime() || 0
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.exitPiP()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onEnterPiP() {
|
|
|
|
|
+ this.isPiP = true
|
|
|
|
|
+ },
|
|
|
|
|
+ onLeavePiP() {
|
|
|
|
|
+ this.isPiP = false
|
|
|
|
|
+ },
|
|
|
|
|
+ async enterPiP() {
|
|
|
|
|
+ if (!document.pictureInPictureEnabled || this.isPiP) return
|
|
|
|
|
+ let player = this.$refs.videoPlayer && this.$refs.videoPlayer.player
|
|
|
|
|
+ if (!player) return
|
|
|
|
|
+ let video = player.tech_ && player.tech_.el_
|
|
|
|
|
+ if (video && video.requestPictureInPicture) {
|
|
|
|
|
+ try { await video.requestPictureInPicture() } catch (e) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ async exitPiP() {
|
|
|
|
|
+ if (document.pictureInPictureElement) {
|
|
|
|
|
+ try { await document.exitPictureInPicture() } catch (e) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ togglePiP() {
|
|
|
|
|
+ if (document.pictureInPictureElement) {
|
|
|
|
|
+ this.exitPiP()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.enterPiP()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
setVolume(val) {
|
|
setVolume(val) {
|
|
|
this.volume = val
|
|
this.volume = val
|
|
|
this.isMuted = val === 0
|
|
this.isMuted = val === 0
|
|
@@ -269,8 +314,13 @@
|
|
|
that.reportErr("play", '' + err.message)
|
|
that.reportErr("play", '' + err.message)
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- playerReadied(audio) {
|
|
|
|
|
|
|
+ playerReadied(player) {
|
|
|
this.isReady = true
|
|
this.isReady = true
|
|
|
|
|
+ let video = player.tech_ && player.tech_.el_
|
|
|
|
|
+ if (video) {
|
|
|
|
|
+ video.addEventListener('enterpictureinpicture', this.onEnterPiP)
|
|
|
|
|
+ video.addEventListener('leavepictureinpicture', this.onLeavePiP)
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
onPlayerError(event) {
|
|
onPlayerError(event) {
|
|
|
console.error("Video error:", event)
|
|
console.error("Video error:", event)
|
|
@@ -375,6 +425,26 @@
|
|
|
reportErr(action, msg) {
|
|
reportErr(action, msg) {
|
|
|
httpServer("course.report", { action, msg }, true)
|
|
httpServer("course.report", { action, msg }, true)
|
|
|
},
|
|
},
|
|
|
|
|
+ getPlayerTime() {
|
|
|
|
|
+ if (this.hiddenStartTime) {
|
|
|
|
|
+ let elapsed = (Date.now() - this.hiddenStartTime) / 1000
|
|
|
|
|
+ let estimated = this.hiddenBasePosition + elapsed
|
|
|
|
|
+ let myPlayer = this.$refs.videoPlayer && this.$refs.videoPlayer.player
|
|
|
|
|
+ if (myPlayer && myPlayer.currentTime) {
|
|
|
|
|
+ let actual = myPlayer.currentTime()
|
|
|
|
|
+ if (actual >= estimated - 0.5) {
|
|
|
|
|
+ this.hiddenStartTime = 0
|
|
|
|
|
+ return actual
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return Math.min(estimated, this.media.duration || 1/0)
|
|
|
|
|
+ }
|
|
|
|
|
+ let myPlayer = this.$refs.videoPlayer && this.$refs.videoPlayer.player
|
|
|
|
|
+ if (myPlayer && myPlayer.currentTime) {
|
|
|
|
|
+ return myPlayer.currentTime()
|
|
|
|
|
+ }
|
|
|
|
|
+ return 0
|
|
|
|
|
+ },
|
|
|
tick(force = false) {
|
|
tick(force = false) {
|
|
|
let media = this.media;
|
|
let media = this.media;
|
|
|
this.tickNum++
|
|
this.tickNum++
|
|
@@ -383,7 +453,8 @@
|
|
|
}
|
|
}
|
|
|
let myPlayer = this.$refs.videoPlayer.player;
|
|
let myPlayer = this.$refs.videoPlayer.player;
|
|
|
if (!myPlayer) return;
|
|
if (!myPlayer) return;
|
|
|
- let curTimes = parseInt(myPlayer.currentTime());
|
|
|
|
|
|
|
+ let curTimes = parseInt(this.getPlayerTime());
|
|
|
|
|
+ this.curTimes = curTimes || 0
|
|
|
if (curTimes < 4) {
|
|
if (curTimes < 4) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -420,12 +491,12 @@
|
|
|
}
|
|
}
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- if (!skip) {
|
|
|
|
|
- let curTimes = parseInt(myPlayer.currentTime())
|
|
|
|
|
- if (position < curTimes + 5) {
|
|
|
|
|
- this.setposition(position)
|
|
|
|
|
|
|
+ if (!skip) {
|
|
|
|
|
+ let curTimes = parseInt(this.getPlayerTime())
|
|
|
|
|
+ if (position < curTimes + 5) {
|
|
|
|
|
+ this.setposition(position)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
Object.assign(this.media, res.data)
|
|
Object.assign(this.media, res.data)
|
|
|
if( res.data.isFinish == 1){
|
|
if( res.data.isFinish == 1){
|
|
|
this.playNext()
|
|
this.playNext()
|
|
@@ -453,6 +524,7 @@
|
|
|
this.curTimes = 0
|
|
this.curTimes = 0
|
|
|
this.onPlay = false
|
|
this.onPlay = false
|
|
|
this.hasSeeked = false
|
|
this.hasSeeked = false
|
|
|
|
|
+ this.hiddenStartTime = 0
|
|
|
this.stopTick()
|
|
this.stopTick()
|
|
|
this.options = {
|
|
this.options = {
|
|
|
...this.options,
|
|
...this.options,
|
|
@@ -467,6 +539,7 @@
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
created() {
|
|
created() {
|
|
|
|
|
+ document.addEventListener('visibilitychange', this.handleVisibilityChange)
|
|
|
let { uid, token, courseId, mediaId } = this.$route.query
|
|
let { uid, token, courseId, mediaId } = this.$route.query
|
|
|
this.courseId = +courseId || +this.$route.params.courseId
|
|
this.courseId = +courseId || +this.$route.params.courseId
|
|
|
this.mediaId = mediaId || this.$route.query.mediaId || 0
|
|
this.mediaId = mediaId || this.$route.query.mediaId || 0
|
|
@@ -477,7 +550,17 @@
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
beforeDestroy() {
|
|
beforeDestroy() {
|
|
|
|
|
+ document.removeEventListener('visibilitychange', this.handleVisibilityChange)
|
|
|
|
|
+ this.exitPiP()
|
|
|
this.stopTick()
|
|
this.stopTick()
|
|
|
|
|
+ let player = this.$refs.videoPlayer && this.$refs.videoPlayer.player
|
|
|
|
|
+ if (player) {
|
|
|
|
|
+ let video = player.tech_ && player.tech_.el_
|
|
|
|
|
+ if (video) {
|
|
|
|
|
+ video.removeEventListener('enterpictureinpicture', this.onEnterPiP)
|
|
|
|
|
+ video.removeEventListener('leavepictureinpicture', this.onLeavePiP)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
this.reportErr("play", 'destroy')
|
|
this.reportErr("play", 'destroy')
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -823,6 +906,33 @@
|
|
|
width: 12px;
|
|
width: 12px;
|
|
|
height: 12px;
|
|
height: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
+.pip-control {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-left: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.pip-icon {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ user-select: none;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
|
+}
|
|
|
|
|
+.pip-icon:hover {
|
|
|
|
|
+ color: #409EFF;
|
|
|
|
|
+ border-color: #c6e2ff;
|
|
|
|
|
+ background: #ecf5ff;
|
|
|
|
|
+}
|
|
|
|
|
+.pip-icon.is-active {
|
|
|
|
|
+ color: #409EFF;
|
|
|
|
|
+ border-color: #409EFF;
|
|
|
|
|
+ background: #ecf5ff;
|
|
|
|
|
+}
|
|
|
.media-select {
|
|
.media-select {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|