提交 e47ab814 编写于 作者: Q qiang

feat(App): video

上级 a8a51d3d
import { getStatusbarHeight } from './statusBar'
import { NAVBAR_HEIGHT } from '@dcloudio/uni-shared'
export function getNavigationBarHeight() {
const webview = plus.webview.currentWebview()
const style = webview.getStyle()
const titleNView = style && style.titleNView
if (titleNView && titleNView.type === 'default') {
return NAVBAR_HEIGHT + getStatusbarHeight()
}
return 0
}
import {
Ref,
reactive,
ref,
watch,
computed,
provide,
inject,
onMounted,
} from 'vue'
import { getNavigationBarHeight } from './navigationBar'
import { getRealPath } from '../platform/getRealPath'
type Prop = 'top' | 'left' | 'width' | 'height'
export interface Position extends Record<Prop, string> {
position: 'static' | 'absolute'
}
const onDrawKey = Symbol('onDraw')
type OnDrawCallback = (parentPosition: Position) => any
type OnDraw = (callback: OnDrawCallback) => any
function getFixed($el: HTMLElement | null) {
let fixed
while ($el) {
const style = getComputedStyle($el)
const transform = style.transform || style.webkitTransform
fixed = transform && transform !== 'none' ? false : fixed
fixed = style.position === 'fixed' ? true : fixed
$el = $el.parentElement
}
return fixed
}
export function useNativeAttrs(props: Record<string, any>, ignore?: string[]) {
return computed(() => {
const object: Record<string, any> = {}
Object.keys(props).forEach((key) => {
if (ignore && ignore.includes(key)) {
return
}
let val = props[key]
val = key === 'src' ? getRealPath(val) : val
object[key.replace(/[A-Z]/g, (str) => '-' + str.toLowerCase())] = val
})
return object
})
}
export function useNative(rootRef: Ref<HTMLElement | null>) {
const position: Position = reactive({
top: '0px',
left: '0px',
width: '0px',
height: '0px',
position: 'static',
})
const hidden = ref(false)
function updatePosition() {
const el = rootRef.value as HTMLElement
const rect = el.getBoundingClientRect()
hidden.value = rect.width === 0 || rect.height === 0
if (!hidden.value) {
position.position = getFixed(el) ? 'absolute' : 'static'
const keys: Prop[] = ['top', 'left', 'width', 'height']
keys.forEach((key) => {
let val = rect[key]
val =
key === 'top'
? val +
(position.position === 'static'
? document.documentElement.scrollTop ||
document.body.scrollTop ||
0
: getNavigationBarHeight())
: val
position[key] = val + 'px'
})
}
}
let request: null | number = null
function requestPositionUpdate() {
if (request) {
cancelAnimationFrame(request)
}
request = requestAnimationFrame(() => {
request = null
updatePosition()
})
}
window.addEventListener('updateview', requestPositionUpdate)
const onDrawCallbacks: OnDrawCallback[] = []
/**
* 父组件绘制完毕,开始绘制当前组件原生部分
* @param callback
*/
function onParentReady(callback: OnDrawCallback) {
const onDraw: OnDraw | undefined = inject(onDrawKey)
const newCallback: OnDrawCallback = (parentPosition) => {
callback(parentPosition)
onDrawCallbacks.forEach((callback) => callback(position))
onDrawCallbacks.length = 0
}
if (onDraw) {
onDraw(newCallback)
} else {
onMounted(() =>
newCallback({
top: '0px',
left: '0px',
width: Number.MAX_SAFE_INTEGER + 'px',
height: Number.MAX_SAFE_INTEGER + 'px',
position: 'static',
})
)
}
}
const onDraw: OnDraw = function (callback: OnDrawCallback) {
onDrawCallbacks.push(callback)
}
provide(onDrawKey, onDraw)
watch(() => rootRef.value, updatePosition)
return {
position,
hidden,
onParentReady,
}
}
import { Ref, ref, watch, onBeforeUnmount } from 'vue'
import {
defineBuiltInComponent,
useCustomEvent,
EmitEvent,
useSubscribe,
useContextInfo,
} from '@dcloudio/uni-components'
import { useNativeAttrs, useNative } from '../../../helpers/useNative'
const props = {
id: {
type: String,
default: '',
},
src: {
type: String,
default: '',
},
duration: {
type: [Number, String],
default: '',
},
controls: {
type: [Boolean, String],
default: true,
},
danmuList: {
type: Array,
default() {
return []
},
},
danmuBtn: {
type: [Boolean, String],
default: false,
},
enableDanmu: {
type: [Boolean, String],
default: false,
},
autoplay: {
type: [Boolean, String],
default: false,
},
loop: {
type: [Boolean, String],
default: false,
},
muted: {
type: [Boolean, String],
default: false,
},
objectFit: {
type: String,
default: 'contain',
},
poster: {
type: String,
default: '',
},
direction: {
type: [String, Number],
default: '',
},
showProgress: {
type: Boolean,
default: true,
},
initialTime: {
type: [String, Number],
default: 0,
},
showFullscreenBtn: {
type: [Boolean, String],
default: true,
},
pageGesture: {
type: [Boolean, String],
default: false,
},
enableProgressGesture: {
type: [Boolean, String],
default: true,
},
showPlayBtn: {
type: [Boolean, String],
default: true,
},
enablePlayGesture: {
type: [Boolean, String],
default: true,
},
showCenterPlayBtn: {
type: [Boolean, String],
default: true,
},
showLoading: {
type: [Boolean, String],
default: true,
},
codec: {
type: String,
default: 'hardware',
},
httpCache: {
type: [Boolean, String],
default: false,
},
playStrategy: {
type: [Number, String],
default: 0,
},
header: {
type: Object,
default() {
return {}
},
},
advanced: {
type: Array,
default() {
return []
},
},
}
type EventName =
| 'play'
| 'pause'
| 'ended'
| 'timeupdate'
| 'fullscreenchange'
| 'fullscreenclick'
| 'waiting'
| 'error'
const emits: EventName[] = [
'play',
'pause',
'ended',
'timeupdate',
'fullscreenchange',
'fullscreenclick',
'waiting',
'error',
]
type Method =
| 'play'
| 'pause'
| 'stop'
| 'seek'
| 'sendDanmu'
| 'playbackRate'
| 'requestFullScreen'
| 'exitFullScreen'
const methods: Method[] = [
'play',
'pause',
'stop',
'seek',
'sendDanmu',
'playbackRate',
'requestFullScreen',
'exitFullScreen',
]
export default /*#__PURE__*/ defineBuiltInComponent({
name: 'Video',
props,
emits,
setup(props, { emit, slots }) {
const rootRef: Ref<HTMLElement | null> = ref(null)
const trigger = useCustomEvent<EmitEvent<typeof emit>>(rootRef, emit)
const containerRef: Ref<HTMLElement | null> = ref(null)
const attrs = useNativeAttrs(props, ['id'])
const { position, hidden, onParentReady } = useNative(containerRef)
let video: ReturnType<typeof plus.video.createVideoPlayer>
onParentReady(() => {
video = plus.video.createVideoPlayer(
'video' + Date.now(),
Object.assign({}, attrs.value, position)
)
plus.webview.currentWebview().append(video as any)
if (hidden.value) {
video.hide()
}
emits.forEach((key) => {
video.addEventListener(key, (event) => {
trigger(key, {} as Event, event.detail)
})
})
watch(
() => attrs.value,
(attrs) => video.setStyles(attrs as any),
{ deep: true }
)
watch(
() => position,
(position) => video.setStyles(position),
{ deep: true }
)
watch(
() => hidden.value,
(val) => {
video[val ? 'hide' : 'show']()
// iOS 隐藏状态设置 setStyles 不生效
if (!val) {
video.setStyles(position)
}
}
)
})
const id = useContextInfo()
useSubscribe(
(type: string, data: any) => {
if (methods.includes(type as Method)) {
let options
switch (type) {
case 'seek':
options = data.position
break
case 'sendDanmu':
options = data
break
case 'playbackRate':
options = data.rate
break
}
if (video) {
video[type as Method](options)
}
}
},
id,
true
)
onBeforeUnmount(() => {
if (video) {
video.close()
}
})
return () => {
return (
<uni-video ref={rootRef} id={props.id}>
<div ref={containerRef} class="uni-video-container" />
<div class="uni-video-slot">{slots.default && slots.default()}</div>
</uni-video>
)
}
},
})
uni-video {
width: 300px;
height: 225px;
display: inline-block;
line-height: 0;
overflow: hidden;
position: relative;
}
uni-video[hidden] {
display: none;
}
.uni-video-container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
background-color: black;
}
.uni-video-slot {
position: absolute;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
}
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册