Quellcode durchsuchen

开启画中画

y595705120 vor 5 Tagen
Ursprung
Commit
069dd1171e
1 geänderte Dateien mit 119 neuen und 9 gelöschten Zeilen
  1. 119 9
      src/containers/center/play/play.vue

+ 119 - 9
src/containers/center/play/play.vue

@@ -24,6 +24,9 @@
                   <el-slider class="volume-slider" :value="volume" :min="0" :max="1" :step="0.05"
                     @input="setVolume"></el-slider>
                 </span>
+                <span class="pip-control">
+                  <span class="pip-icon" :class="{ 'is-active': isPiP }" @click="togglePiP">{{ isPiP ? '退出画中画' : '画中画' }}</span>
+                </span>
               </el-col>
               <el-col :span="6" class="media-select">
                 <div style="margin-top:0px;float: right;">
@@ -119,7 +122,7 @@
         options: {
           controls: true,
           autoplay: true,
-          muted: true,
+          muted: false,
           loop: false,
           preload: "auto",
           language: 'zh-CN',
@@ -150,7 +153,10 @@
         mediaType: 'hls',
         hasSeeked: false,
         hideFinished: false,
-        showPanel: true
+        showPanel: true,
+        hiddenStartTime: 0,
+        hiddenBasePosition: 0,
+        isPiP: false
       };
     },
     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) {
         this.volume = val
         this.isMuted = val === 0
@@ -269,8 +314,13 @@
           that.reportErr("play", '' + err.message)
         }
       },
-      playerReadied(audio) {
+      playerReadied(player) {
         this.isReady = true
+        let video = player.tech_ && player.tech_.el_
+        if (video) {
+          video.addEventListener('enterpictureinpicture', this.onEnterPiP)
+          video.addEventListener('leavepictureinpicture', this.onLeavePiP)
+        }
       },
       onPlayerError(event) {
         console.error("Video error:", event)
@@ -375,6 +425,26 @@
       reportErr(action, msg) {
         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) {
         let media = this.media;
         this.tickNum++
@@ -383,7 +453,8 @@
         }
         let myPlayer = this.$refs.videoPlayer.player;
         if (!myPlayer) return;
-        let curTimes = parseInt(myPlayer.currentTime());
+        let curTimes = parseInt(this.getPlayerTime());
+        this.curTimes = curTimes || 0
         if (curTimes < 4) {
           return;
         }
@@ -420,12 +491,12 @@
               }
               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)
             if( res.data.isFinish == 1){
               this.playNext()
@@ -453,6 +524,7 @@
           this.curTimes = 0
           this.onPlay = false
           this.hasSeeked = false
+          this.hiddenStartTime = 0
           this.stopTick()
           this.options = {
             ...this.options,
@@ -467,6 +539,7 @@
       }
     },
     created() {
+      document.addEventListener('visibilitychange', this.handleVisibilityChange)
       let { uid, token, courseId, mediaId } = this.$route.query
       this.courseId = +courseId || +this.$route.params.courseId
       this.mediaId = mediaId || this.$route.query.mediaId || 0
@@ -477,7 +550,17 @@
       }
     },
     beforeDestroy() {
+      document.removeEventListener('visibilitychange', this.handleVisibilityChange)
+      this.exitPiP()
       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')
     }
   }
@@ -823,6 +906,33 @@
   width: 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 {
   display: flex;
   align-items: center;