提交 c342ba26 编写于 作者: Q qiang

fix: 优化H5视频全屏播放效果

上级 83d52ceb
...@@ -22,3 +22,8 @@ export { ...@@ -22,3 +22,8 @@ export {
default as keyboard default as keyboard
} }
from './keyboard' from './keyboard'
export {
default as interact
}
from './interact'
import {
supportsPassive
} from 'uni-shared'
const passiveOptions = supportsPassive ? {
passive: true,
capture: true
} : true
const vms = []
let userInteract = 0
let inited
function addInteractListener (vm) {
if (!inited) {
const eventNames = ['touchstart', 'touchmove', 'touchend', 'mousedown', 'mouseup']
eventNames.forEach(eventName => {
document.addEventListener(eventName, function () {
vms.forEach(vm => {
vm.userInteract = true
userInteract++
setTimeout(() => {
userInteract--
if (!userInteract) {
vm.userInteract = false
}
}, 0)
})
}, passiveOptions)
})
}
vms.push(vm)
}
function removeInteractListener (vm) {
const index = vms.indexOf(vm)
if (index >= 0) {
vms.splice(index, 1)
}
}
export default {
data () {
return {
/**
* 是否用户交互行为
*/
userInteract: false
}
},
mounted () {
addInteractListener(this)
},
beforeDestroy () {
removeInteractListener(this)
}
}
<template> <template>
<uni-video <uni-video
:id="id" :id="id"
v-on="$listeners" v-on="$listeners">
>
<div <div
ref="container" ref="container"
:class="{'uni-video-type-fullscreen':fullscreen,'uni-video-type-rotate-left':rotateType==='left','uni-video-type-rotate-right':rotateType==='right'}"
:style="{width:fullscreen?width:'100%',height:fullscreen?height:'100%'}"
class="uni-video-container" class="uni-video-container"
@click="triggerControls" @click="triggerControls"
@touchstart="touchstart($event)" @touchstart="touchstart"
@touchend="touchend($event)" @touchend="touchend"
@touchmove="touchmove($event)" @touchmove="touchmove"
@fullscreenchange.stop="onFullscreenChange"
@webkitfullscreenchange.stop="onFullscreenChange($event,true)"
> >
<video <video
ref="video" ref="video"
...@@ -20,10 +19,24 @@ ...@@ -20,10 +19,24 @@
:loop="loop" :loop="loop"
:src="srcSync" :src="srcSync"
:poster="poster" :poster="poster"
:autoplay="autoplay"
v-bind="$attrs" v-bind="$attrs"
class="uni-video-video" class="uni-video-video"
webkit-playsinline webkit-playsinline
playsinline playsinline
@durationchange="onDurationChange"
@loadedmetadata="onLoadedMetadata"
@progress="onProgress"
@waiting="onWaiting"
@error="onVideoError"
@play="onPlay"
@pause="onPause"
@ended="onEnded"
@timeupdate="onTimeUpdate"
@webkitbeginfullscreen="emitFullscreenChange(true)"
@x5videoenterfullscreen="emitFullscreenChange(true)"
@webkitendfullscreen="emitFullscreenChange(false)"
@x5videoexitfullscreen="emitFullscreenChange(false)"
/> />
<div <div
v-show="controlsShow" v-show="controlsShow"
...@@ -66,7 +79,7 @@ ...@@ -66,7 +79,7 @@
v-show="showFullscreenBtn" v-show="showFullscreenBtn"
:class="{'uni-video-type-fullscreen':fullscreen}" :class="{'uni-video-type-fullscreen':fullscreen}"
class="uni-video-fullscreen" class="uni-video-fullscreen"
@click.stop="triggerFullscreen" @click.stop="triggerFullscreen(!fullscreen)"
/> />
</div> </div>
<div <div
...@@ -128,7 +141,8 @@ ...@@ -128,7 +141,8 @@
</template> </template>
<script> <script>
import { import {
subscriber subscriber,
interact
} from 'uni-mixins' } from 'uni-mixins'
import { import {
supportsPassive supportsPassive
...@@ -149,20 +163,20 @@ export default { ...@@ -149,20 +163,20 @@ export default {
name: 'Video', name: 'Video',
filters: { filters: {
getTime (time) { getTime (time) {
var h = Math.floor(time / 3600) let h = Math.floor(time / 3600)
var m = Math.floor(time % 3600 / 60) let m = Math.floor(time % 3600 / 60)
var s = Math.floor(time % 3600 % 60) let s = Math.floor(time % 3600 % 60)
h = (h < 10 ? '0' : '') + h h = (h < 10 ? '0' : '') + h
m = (m < 10 ? '0' : '') + m m = (m < 10 ? '0' : '') + m
s = (s < 10 ? '0' : '') + s s = (s < 10 ? '0' : '') + s
var str = m + ':' + s let str = m + ':' + s
if (h !== '00') { if (h !== '00') {
str = h + ':' + str str = h + ':' + str
} }
return str return str
} }
}, },
mixins: [subscriber], mixins: [subscriber, interact],
props: { props: {
id: { id: {
type: String, type: String,
...@@ -216,7 +230,7 @@ export default { ...@@ -216,7 +230,7 @@ export default {
}, },
direction: { direction: {
type: [String, Number], type: [String, Number],
default: 360 default: ''
}, },
showProgress: { showProgress: {
type: Boolean, type: Boolean,
...@@ -254,11 +268,7 @@ export default { ...@@ -254,11 +268,7 @@ export default {
enableDanmuSync: Boolean(this.enableDanmu), enableDanmuSync: Boolean(this.enableDanmu),
controlsVisible: true, controlsVisible: true,
fullscreen: false, fullscreen: false,
width: '0',
height: '0',
fullscreenTriggering: false,
controlsTouching: false, controlsTouching: false,
directionSync: Number(this.direction),
touchStartOrigin: { touchStartOrigin: {
x: 0, x: 0,
y: 0 y: 0
...@@ -268,9 +278,8 @@ export default { ...@@ -268,9 +278,8 @@ export default {
currentTimeNew: 0, currentTimeNew: 0,
volumeOld: null, volumeOld: null,
volumeNew: null, volumeNew: null,
isIOS: false,
buffered: 0, buffered: 0,
rotateType: '' isSafari: /^Apple/.test(navigator.vendor)
} }
}, },
computed: { computed: {
...@@ -295,38 +304,9 @@ export default { ...@@ -295,38 +304,9 @@ export default {
this.autoHideEnd() this.autoHideEnd()
} }
}, },
fullscreen (val) {
var container = this.$refs.container
var playing = this.playing
this.fullscreenTriggering = true
container.remove()
if (val) {
this.resize()
document.body.appendChild(container)
} else {
this.$el.appendChild(container)
}
this.$trigger('fullscreenchange', {}, {
fullScreen: val
})
if (playing) {
this.play()
}
setTimeout(() => {
this.fullscreenTriggering = false
}, 0)
},
direction (val) {
this.directionSync = Number(val)
},
srcSync (val) { srcSync (val) {
this.playing = false this.playing = false
this.currentTime = 0 this.currentTime = 0
if (val && this.autoplay) {
this.$nextTick(() => {
this.$refs.video.play()
})
}
}, },
currentTime () { currentTime () {
this.updateProgress() this.updateProgress()
...@@ -344,134 +324,40 @@ export default { ...@@ -344,134 +324,40 @@ export default {
}, },
hideTiming: null hideTiming: null
} }
var danmuList = this.otherData.danmuList = JSON.parse(JSON.stringify(this.danmuList || [])) const danmuList = this.otherData.danmuList = JSON.parse(JSON.stringify(this.danmuList || []))
danmuList.sort(function (a, b) { danmuList.sort(function (a, b) {
return (a.time || 0) - (a.time || 0) return (a.time || 0) - (a.time || 0)
}) })
this.width = window.innerWidth + 'px'
this.height = window.innerHeight + 'px'
}, },
mounted () { mounted () {
var self = this const self = this
var otherData = this.otherData let originX
var video = this.$refs.video let originY
var ball = this.$refs.ball let moveOnce = true
video.addEventListener('durationchange', function (event) { let originProgress
self.durationTime = video.duration const ball = this.$refs.ball
}) ball.addEventListener('touchstart', (event) => {
video.addEventListener('loadedmetadata', function (event) { this.controlsTouching = true
var initialTime = Number(self.initialTime) || 0 const toucher = event.targetTouches[0]
if (initialTime > 0) {
video.currentTime = initialTime
}
})
video.addEventListener('progress', function (event) {
var buffered = video.buffered
if (buffered.length) {
self.buffered = buffered.end(buffered.length - 1) / video.duration
}
})
video.addEventListener('waiting', function ($event) {
self.$trigger('waiting', $event, {})
})
video.addEventListener('error', function ($event) {
self.playing = false
self.$trigger('error', $event, {})
})
video.addEventListener('play', function ($event) {
self.start = true
self.playing = true
if (self.fullscreenTriggering) {
return
}
self.$trigger('play', $event, {})
})
video.addEventListener('pause', function ($event) {
self.playing = false
if (self.fullscreenTriggering) {
return
}
self.$trigger('pause', $event, {})
})
video.addEventListener('ended', function ($event) {
self.playing = false
self.$trigger('ended', $event, {})
})
video.addEventListener('timeupdate', function ($event) {
var currentTime = self.currentTime = video.currentTime
var duration = video.duration
var oldDanmuIndex = otherData.danmuIndex
var danmuIndex = {
time: currentTime,
index: oldDanmuIndex.index
}
var danmuList = otherData.danmuList
if (currentTime > oldDanmuIndex.time) {
for (let index = oldDanmuIndex.index + 1; index < danmuList.length; index++) {
let element = danmuList[index]
if (currentTime >= (element.time || 0)) {
danmuIndex.index = index
if (self.playing && self.enableDanmuSync) {
self.playDanmu(element)
}
} else {
break
}
}
} else if (currentTime < oldDanmuIndex.time) {
for (let index = oldDanmuIndex.index - 1; index > -1; index--) {
let element = danmuList[index]
if (currentTime <= (element.time || 0)) {
danmuIndex.index = index - 1
} else {
break
}
}
}
otherData.danmuIndex = danmuIndex
self.$trigger('timeupdate', $event, {
currentTime,
duration
})
})
video.addEventListener('x5videoenterfullscreen', function ($event) {
self.$trigger('fullscreenchange', $event, {
fullScreen: true
})
})
video.addEventListener('x5videoexitfullscreen', function ($event) {
self.$trigger('fullscreenchange', $event, {
fullScreen: false
})
})
var originX
var originY
var moveOnce = true
var originProgress
ball.addEventListener('touchstart', function (event) {
self.controlsTouching = true
var toucher = self.getScreenXY(event.targetTouches[0])
originX = toucher.pageX originX = toucher.pageX
originY = toucher.pageY originY = toucher.pageY
originProgress = self.progress originProgress = this.progress
moveOnce = true moveOnce = true
self.touching = true this.touching = true
ball.addEventListener('touchmove', touchmove, passiveOptions) ball.addEventListener('touchmove', touchmove, passiveOptions)
}) })
function touchmove (event) { function touchmove (event) {
var toucher = self.getScreenXY(event.targetTouches[0]) const toucher = event.targetTouches[0]
var pageX = toucher.pageX const pageX = toucher.pageX
var pageY = toucher.pageY const pageY = toucher.pageY
if (moveOnce && Math.abs(pageX - originX) < Math.abs(pageY - originY)) { if (moveOnce && Math.abs(pageX - originX) < Math.abs(pageY - originY)) {
touchend() touchend()
return return
} }
moveOnce = false moveOnce = false
var w = self.$refs.progress.offsetWidth const w = self.$refs.progress.offsetWidth
var progress = originProgress + (pageX - originX) / w * 100 let progress = originProgress + (pageX - originX) / w * 100
if (progress < 0) { if (progress < 0) {
progress = 0 progress = 0
} else if (progress > 100) { } else if (progress > 100) {
...@@ -496,13 +382,9 @@ export default { ...@@ -496,13 +382,9 @@ export default {
} }
ball.addEventListener('touchend', touchend) ball.addEventListener('touchend', touchend)
ball.addEventListener('touchcancel', touchend) ball.addEventListener('touchcancel', touchend)
if (String(this.srcSync).length && this.autoplay) {
video.play()
}
}, },
beforeDestroy () { beforeDestroy () {
this.$refs.container.remove() this.triggerFullscreen(false)
clearTimeout(this.otherData.hideTiming) clearTimeout(this.otherData.hideTiming)
}, },
methods: { methods: {
...@@ -510,56 +392,21 @@ export default { ...@@ -510,56 +392,21 @@ export default {
type, type,
data = {} data = {}
}) { }) {
const methods = ['play', 'pause', 'seek', 'sendDanmu', 'playbackRate', 'requestFullScreen', 'exitFullScreen']
let options
switch (type) { switch (type) {
case 'play':
this.play()
break
case 'pause':
this.pause()
break
case 'seek': case 'seek':
this.seek(data.position) options = data.position
break break
case 'sendDanmu': case 'sendDanmu':
this.sendDanmu(data) options = data
break break
case 'playbackRate': case 'playbackRate':
this.$refs.video.playbackRate = data.rate options = data.rate
break break
case 'requestFullScreen':
this.enterFullscreen()
break
case 'exitFullScreen':
this.leaveFullscreen()
break
}
},
resize () {
var w = window.innerWidth
var h = window.innerHeight
var direction = Math.abs(this.directionSync)
if (direction === 0) {
if (w > h) {
this.rotateType = 'left'
} else {
this.rotateType = ''
}
} else if (direction === 90) {
if (w > h) {
this.rotateType = ''
} else {
this.rotateType = 'right'
}
} else {
this.rotateType = ''
} }
if (!this.rotateType) { if (methods.indexOf(type) >= 0) {
this.width = w + 'px' this[type](options)
this.height = h + 'px'
} else {
this.width = h + 'px'
this.height = w + 'px'
} }
}, },
trigger () { trigger () {
...@@ -583,15 +430,15 @@ export default { ...@@ -583,15 +430,15 @@ export default {
} }
}, },
clickProgress (event) { clickProgress (event) {
var x = event.offsetX const $progress = this.$refs.progress
var _progress = this.$refs.progress let element = event.target
var element = event.target let x = event.offsetX
while (element !== _progress) { while (element !== $progress) {
x += element.offsetLeft x += element.offsetLeft
element = element.parentNode element = element.parentNode
} }
var w = _progress.offsetWidth const w = $progress.offsetWidth
var progress = 0 let progress = 0
if (x >= 0 && x <= w) { if (x >= 0 && x <= w) {
progress = x / w progress = x / w
this.seek(this.$refs.video.duration * progress) this.seek(this.$refs.video.duration * progress)
...@@ -601,10 +448,10 @@ export default { ...@@ -601,10 +448,10 @@ export default {
this.enableDanmuSync = !this.enableDanmuSync this.enableDanmuSync = !this.enableDanmuSync
}, },
playDanmu (danmu) { playDanmu (danmu) {
var p = document.createElement('p') const p = document.createElement('p')
p.className = 'uni-video-danmu-item' p.className = 'uni-video-danmu-item'
p.innerText = danmu.text p.innerText = danmu.text
var style = `bottom: ${Math.random() * 100}%;color: ${danmu.color};` let style = `bottom: ${Math.random() * 100}%;color: ${danmu.color};`
p.setAttribute('style', style) p.setAttribute('style', style)
this.$refs.danmu.appendChild(p) this.$refs.danmu.appendChild(p)
setTimeout(function () { setTimeout(function () {
...@@ -616,31 +463,144 @@ export default { ...@@ -616,31 +463,144 @@ export default {
}, 17) }, 17)
}, },
sendDanmu (danmu) { sendDanmu (danmu) {
var otherData = this.otherData const otherData = this.otherData
otherData.danmuList.splice(otherData.danmuIndex.index + 1, 0, { otherData.danmuList.splice(otherData.danmuIndex.index + 1, 0, {
text: String(danmu.text), text: String(danmu.text),
color: danmu.color, color: danmu.color,
time: this.$refs.video.currentTime || 0 time: this.$refs.video.currentTime || 0
}) })
}, },
triggerFullscreen () { playbackRate (rate) {
this.fullscreen = !this.fullscreen this.$refs.video.playbackRate = rate
},
triggerFullscreen (val) {
const container = this.$refs.container
const video = this.$refs.video
let mockFullScreen
if (val) {
if ((document.fullscreenEnabled || document.webkitFullscreenEnabled) && (!this.isSafari || this.userInteract)) {
container[document.fullscreenEnabled ? 'requestFullscreen' : 'webkitRequestFullscreen']()
} else if (video.webkitEnterFullScreen) {
video.webkitEnterFullScreen()
} else {
mockFullScreen = true
container.remove()
container.classList.add('uni-video-type-fullscreen')
document.body.appendChild(container)
}
} else {
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
document[document.fullscreenEnabled ? 'exitFullscreen' : 'webkitExitFullscreen']()
} else if (video.webkitExitFullScreen) {
video.webkitExitFullScreen()
} else {
mockFullScreen = true
container.remove()
container.classList.remove('uni-video-type-fullscreen')
this.$el.appendChild(container)
}
}
if (mockFullScreen) {
this.emitFullscreenChange(val)
}
},
onFullscreenChange ($event, webkit) {
if (webkit && document.fullscreenEnabled) {
return
}
this.emitFullscreenChange(!!(document.fullscreenElement || document.webkitFullscreenElement))
},
emitFullscreenChange (val) {
this.fullscreen = val
this.$trigger('fullscreenchange', {}, {
fullScreen: val,
direction: 'vertical'
})
},
requestFullScreen () {
this.triggerFullscreen(true)
},
exitFullScreen () {
this.triggerFullscreen(false)
},
onDurationChange ({ target }) {
this.durationTime = target.duration
}, },
enterFullscreen (direction) { onLoadedMetadata ({ target }) {
var directionSync = Number(direction) const initialTime = Number(this.initialTime) || 0
if (!isNaN(NaN)) { if (initialTime > 0) {
this.directionSync = directionSync target.currentTime = initialTime
} }
this.fullscreen = true
}, },
leaveFullscreen () { onProgress ({ target }) {
this.fullscreen = false const buffered = target.buffered
if (buffered.length) {
this.buffered = buffered.end(buffered.length - 1) / target.duration
}
},
onWaiting ($event) {
this.$trigger('waiting', $event, {})
},
onVideoError ($event) {
this.playing = false
this.$trigger('error', $event, {})
},
onPlay ($event) {
this.start = true
this.playing = true
this.$trigger('play', $event, {})
},
onPause ($event) {
this.playing = false
this.$trigger('pause', $event, {})
},
onEnded ($event) {
this.playing = false
this.$trigger('ended', $event, {})
},
onTimeUpdate ($event) {
const video = $event.target
const otherData = this.otherData
const currentTime = this.currentTime = video.currentTime
const oldDanmuIndex = otherData.danmuIndex
const danmuIndex = {
time: currentTime,
index: oldDanmuIndex.index
}
const danmuList = otherData.danmuList
if (currentTime > oldDanmuIndex.time) {
for (let index = oldDanmuIndex.index + 1; index < danmuList.length; index++) {
let element = danmuList[index]
if (currentTime >= (element.time || 0)) {
danmuIndex.index = index
if (this.playing && this.enableDanmuSync) {
this.playDanmu(element)
}
} else {
break
}
}
} else if (currentTime < oldDanmuIndex.time) {
for (let index = oldDanmuIndex.index - 1; index > -1; index--) {
let element = danmuList[index]
if (currentTime <= (element.time || 0)) {
danmuIndex.index = index - 1
} else {
break
}
}
}
otherData.danmuIndex = danmuIndex
this.$trigger('timeupdate', $event, {
currentTime,
duration: video.duration
})
}, },
triggerControls () { triggerControls () {
this.controlsVisible = !this.controlsVisible this.controlsVisible = !this.controlsVisible
}, },
touchstart (event) { touchstart (event) {
var toucher = this.getScreenXY(event.targetTouches[0]) const toucher = event.targetTouches[0]
this.touchStartOrigin = { this.touchStartOrigin = {
x: toucher.pageX, x: toucher.pageX,
y: toucher.pageY y: toucher.pageY
...@@ -657,14 +617,14 @@ export default { ...@@ -657,14 +617,14 @@ export default {
if (this.fullscreen) { if (this.fullscreen) {
stop() stop()
} }
var gestureType = this.gestureType const gestureType = this.gestureType
if (gestureType === GestureType.STOP) { if (gestureType === GestureType.STOP) {
return return
} }
var toucher = this.getScreenXY(event.targetTouches[0]) const toucher = event.targetTouches[0]
var pageX = toucher.pageX const pageX = toucher.pageX
var pageY = toucher.pageY const pageY = toucher.pageY
var origin = this.touchStartOrigin const origin = this.touchStartOrigin
if (gestureType === GestureType.PROGRESS) { if (gestureType === GestureType.PROGRESS) {
this.changeProgress(pageX - origin.x) this.changeProgress(pageX - origin.x)
} else if (gestureType === GestureType.VOLUME) { } else if (gestureType === GestureType.VOLUME) {
...@@ -706,8 +666,8 @@ export default { ...@@ -706,8 +666,8 @@ export default {
this.gestureType = GestureType.NONE this.gestureType = GestureType.NONE
}, },
changeProgress (x) { changeProgress (x) {
var duration = this.$refs.video.duration const duration = this.$refs.video.duration
var currentTimeNew = x / 600 * duration + this.currentTimeOld let currentTimeNew = x / 600 * duration + this.currentTimeOld
if (currentTimeNew < 0) { if (currentTimeNew < 0) {
currentTimeNew = 0 currentTimeNew = 0
} else if (currentTimeNew > duration) { } else if (currentTimeNew > duration) {
...@@ -716,8 +676,8 @@ export default { ...@@ -716,8 +676,8 @@ export default {
this.currentTimeNew = currentTimeNew this.currentTimeNew = currentTimeNew
}, },
changeVolume (y) { changeVolume (y) {
var valueOld = this.volumeOld const valueOld = this.volumeOld
var value let value
if (typeof valueOld === 'number') { if (typeof valueOld === 'number') {
value = valueOld - y / 200 value = valueOld - y / 200
if (value < 0) { if (value < 0) {
...@@ -735,35 +695,12 @@ export default { ...@@ -735,35 +695,12 @@ export default {
}, 3000) }, 3000)
}, },
autoHideEnd () { autoHideEnd () {
var otherData = this.otherData const otherData = this.otherData
if (otherData.hideTiming) { if (otherData.hideTiming) {
clearTimeout(otherData.hideTiming) clearTimeout(otherData.hideTiming)
otherData.hideTiming = null otherData.hideTiming = null
} }
}, },
getScreenXY (dataOrigin) {
var rotateType = this.rotateType
if (!this.fullscreen || !rotateType) {
return dataOrigin
}
var w = screen.width
var h = screen.height
var x = dataOrigin.pageX
var y = dataOrigin.pageY
var pageX
var pageY
if (rotateType === 'left') {
pageX = h - y
pageY = x
} else {
pageX = y
pageY = w - x
}
return {
pageX,
pageY
}
},
updateProgress () { updateProgress () {
if (!this.touching) { if (!this.touching) {
this.progress = this.currentTime / this.durationTime * 100 this.progress = this.currentTime / this.durationTime * 100
...@@ -801,20 +738,9 @@ uni-video[hidden] { ...@@ -801,20 +738,9 @@ uni-video[hidden] {
.uni-video-container.uni-video-type-fullscreen { .uni-video-container.uni-video-type-fullscreen {
position: fixed; position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999; z-index: 999;
} }
.uni-video-container.uni-video-type-fullscreen.uni-video-type-rotate-left {
transform: translate(-50%, -50%) rotate(-90deg);
}
.uni-video-container.uni-video-type-fullscreen.uni-video-type-rotate-right {
transform: translate(-50%, -50%) rotate(90deg);
}
.uni-video-video { .uni-video-video {
width: 100%; width: 100%;
height: 100%; height: 100%;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册