11.21提交

This commit is contained in:
zoujiandong
2025-11-21 17:49:39 +08:00
parent 6b37f0899d
commit 91726b87c5
18 changed files with 2483 additions and 119 deletions
+27
View File
@@ -0,0 +1,27 @@
## 2.0.02024-09-22
`重构`代码,使用了`composition-api``修复`解决个别情况下样式出现错乱问题
## 1.2.12024-08-06
`修复` 多个视频同一页面首次点击会同步播放问题
## 1.2.02024-03-16
`新增`自动跳转到历史位置及提示
## 1.1.82024-03-05
`新增`多个实例方法
## 1.1.72024-01-23
`修复`在VUE3使用APP-NVUE时,全屏状态字号过大问题;
优化部分代码
## 1.1.62024-01-18
修复在vue3中`handleBtn`会自动触发问题
## 1.1.52024-01-18
修改vue3项目中的错误提示
## 1.1.42023-12-14
优化精简代码,修复微信小程序全屏样式问题
## 1.1.32023-11-08
可配置视频宽度,倍速盒子宽度、补充方法`changePause`,事件`fullscreenchange`
## 1.1.22023-10-21
修改微信小程序试看样式问题
## 1.1.12023-10-16
新增补充方法`seek`
## 1.1.02023-10-09
增加视频试看功能
## 1.0.02023-04-25
首次发布
@@ -0,0 +1,189 @@
@use "sass:math";
$primary: #5C91EF;
//css函数
// /* #ifdef APP-NVUE */
// @function tovmin($rpx) {
// //$rpx为需要转换的字号
// @return #{$rpx}rpx;
// }
// /* #endif
// /* #ifndef APP-NVUE */
// @function tovmin($rpx) {
// //$rpx为需要转换的字号
// /* #ifdef VUE2 */
// @return #{math.div($rpx * 100, 750)}vmin;
// /* #endif */
// /* #ifdef VUE3 */
// @return #{calc($rpx * 100 / 750)}vmin;
// /* #endif */
// }
// /* #endif */
.sunny-video {
position: relative;
.banner-view {
position: relative;
z-index: 1;
.imgPal {
position: absolute;
z-index: 2;
}
}
.speedText {
position: absolute;
right: 15px;
z-index: 5;
padding: 5px;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
.text {
display: flex;
width: 40px;
font-size: 16px;
color: $primary;
text-align: center;
}
}
.vertical {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
&.vertical-full {
.speed-box {
padding: 25px 0;
}
}
.vertical_left{
position: absolute;
left: 0;
}
.speed-box {
position: absolute;
top: 0;
right: 0;
bottom: 0;
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: space-between;
/* #ifdef APP-NVUE */
// v1.1.3
padding: 10px 0;
/* #endif */
background-color: rgba(0, 0, 0, 0.7);
.speed-text {
width: 80px;
text-align: center;
font-size: 14px;
color: #fff;
/* #ifdef APP-NVUE */
padding: 5px 0px;
/* #endif */
/* #ifndef APP-NVUE */
// v1.1.3
height: 30px;
line-height: 30px;
box-sizing: border-box;
&:first-child {
margin-top: 10px;
}
&:last-child {
margin-bottom: 15px;
}
/* #endif */
&.act {
color: $primary;
font-size: 16px;
}
}
}
}
// 试看结束盒子
.trialEndBox {
position: absolute;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 999;
// backdrop-filter: blur(14px);
background-color: rgba(0, 0, 0, 0.9);
.tipText {
font-size: 14px;
color: #fff;
}
.closeBtn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 175px;
height: 37.5px;
margin: 16px 0 0;
font-size: 15px;
line-height: 37.5px;
border-radius: 40px;
color: #fff;
text-align: center;
background-color: $primary;
}
}
// 位置提示
.mplayer-toast {
position: absolute;
left: 10px;
bottom: 45px;
display: flex;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
border-radius: 2px;
opacity: 0;
// transition: opacity .3s;
padding: 6px 8px;
&.mplayer-show {
opacity: 1;
}
.text {
display: flex;
align-items: center;
color: #fff;
font-size: 14px;
/* #ifdef APP-PLUS */
margin: 5px 8px 8px;
/* #endif */
/* #ifdef APP-NVUE */
margin: 0;
/* #endif */
.btn_text {
color: $primary;
margin-left: 12px;
}
}
.kong {
width: 8rpx;
height: 1px;
}
}
}
@@ -0,0 +1,105 @@
// @ts-nocheck
import playIcon from '../../static/play.png'
export default {
/** 视频地址 */
src: {
type: String,
default: ''
},
/** 视频标题 */
title: {
type: String,
default: ''
},
/** 视频封面 */
poster: {
type: String,
default: ''
},
/** 视频高度 */
videoHeight: {
type: [String, Number],
default: '230px'
},
/** 视频宽度 v1.1.3 */
videoWidth: {
type: [String, Number],
default: '750rpx'
},
/** v2.0.0 当视频大小与 video 容器大小不一致时,视频的表现形式。contain:包含,fill:填充,cover:覆盖 */
objectFit:{
type: String,
default: 'contain'
},
/** 播放图片按钮宽高 */
playImgHeight: {
type: [String, Number],
default: '70rpx'
},
/** 暂停按钮 */
playImg: {
type: String,
default: () => playIcon
},
/** 是否显示静音按钮 */
showMuteBtn: {
type: Boolean,
default: false
},
/** 播放完毕是否退出全屏 */
isExitFullScreen: {
type: Boolean,
default: true
},
/** 主题色 */
primaryColor: {
type: String,
default: '#5C91EF'
},
/** 试看提示的文字 */
tipText: {
type: String,
default: '试看已结束,本片是会员专享内容'
},
/** 试看按钮的文字 */
btnText: {
type: String,
default: '成为会员免费观看'
},
/** 视频试看时间(秒) */
trialTime: {
type: Number,
default: 0
},
/** 倍速盒子宽度 v1.1.3 */
speedBoxWidth: {
type: [String, Number],
default: '160rpx'
},
/** 是否循环播放 v1.1.3 */
loop: {
type: Boolean,
default: false
},
/** 是否静音播放 v1.1.3 */
muted: {
type: Boolean,
default: false
},
/** 是否自动播放 */
autoplay: {
type: Boolean,
default: false
},
/** 历史观看位置 v1.2.0 */
seekTime: {
type: Number,
default: 0
},
/** 视频唯一ID v1.2.1 */
videoId: {
type: String,
default: 'sunnyVideo'
}
}
@@ -0,0 +1,459 @@
<template>
<video class="sunny-video"
:id="videoId"
:title="title"
:src="src"
:show-center-play-btn="false"
:controls="state.controls"
:enable-play-gesture="state.enablePlayGesture"
:muted="muted"
:show-mute-btn="showMuteBtn"
:autoplay="autoplay"
:object-fit="objectFit"
:style="{width: addUnit(videoWidth), height: addUnit(videoHeight)}"
@play="play"
@pause="emit('pause')"
@ended="ended"
@error="(e) => emit('playError', e)"
@timeupdate="timeupdate"
@fullscreenchange="fullscreenchange"
>
<cover-view v-if="!state.isPlay" class="banner-view">
<cover-image
class="banner"
:style="{width: addUnit(videoWidth), height: addUnit(videoHeight)}"
:src="poster"
@click="changePlay"
/>
<cover-image
class="imgPal"
:style="[playImgStyle]"
:src="playImg"
@click="changePlay"
/>
</cover-view>
<!-- 当前倍速标记 -->
<cover-view
v-if="state.isPlay&&!state.isShowRateBox"
class="speedText"
:style="[speedStyle]"
@click="state.isShowRateBox = true"
>
<!-- #ifdef APP-NVUE -->
<text class="text">{{state.rateText}}X</text>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<cover-view class="text">{{state.rateText}}X</cover-view>
<!-- #endif -->
</cover-view>
<!-- 倍速选择 -->
<cover-view
v-if="state.isShowRateBox"
class="vertical"
:class="{'vertical-full':state.isFullScreen}"
:style="[speedBoxMaskStyle]"
>
<cover-view
class="vertical_left"
:style="[speedBoxLeftStyel]"
@click="state.isShowRateBox=false"
/>
<!-- #ifdef APP-NVUE -->
<view
class="speed-box"
:style="[speedBoxStyle]"
>
<text
class="speed-text"
:class="{act:item.isSelect}"
v-for="(item,index) in rateList"
:key="item.name"
@click="changeRate(item,index)"
>{{item.name}}X</text>
</view>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<cover-view
class="speed-box"
:style="[speedBoxStyle]"
>
<cover-view
class="speed-text"
:class="{act:item.isSelect}"
v-for="(item,index) in rateList"
:key="item.name"
@click="changeRate(item,index)"
>{{item.name}}X</cover-view>
</cover-view>
<!-- #endif -->
</cover-view>
<!-- 试看盒子 -->
<cover-view v-if="state.visibleTrialEndBox" class="trialEndBox" :style="[trialEndBoxStyle]">
<!-- #ifdef APP-NVUE -->
<text class="tipText">{{tipText}}</text>
<text class="closeBtn" @click.stop="handleClickTrialEnd">{{btnText}}</text>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<cover-view class="tipText">{{tipText}}</cover-view>
<cover-view class="closeBtn" @click.stop="handleClickTrialEnd">{{btnText}}</cover-view>
<!-- #endif -->
</cover-view>
<!-- 进度记录跳转提示 -->
<cover-view class="mplayer-toast" :class="{'mplayer-show':state.showMplayerToast}">
<!-- #ifdef H5 -->
<text class="text">
记忆你上次看到{{convertSecondsToHMS(seekTime)}}<text class="btn_text" @click="handelSeek">跳转</text>
</text>
<!-- #endif -->
<!-- #ifndef H5 | APP-NVUE -->
<cover-view v-if="state.showMplayerToast" class="text">
{{'已为您定位至'+convertSecondsToHMS(seekTime)}}
<!-- #ifdef MP-WEIXIN -->
<cover-view class="kong" />
<!-- #endif -->
</cover-view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<text class="text">已为您定位至{{convertSecondsToHMS(seekTime)}}</text>
<!-- #endif -->
</cover-view>
</video>
</template>
<script lang="ts">
// @ts-nocheck
import { defineComponent, ref, toRefs, reactive, computed, onMounted, getCurrentInstance, nextTick } from './vue-composition-api'
import VideoProps from './props'
import {getPx, addUnit, convertSecondsToHMS} from './utils'
import type { StateData, rateListData } from './type'
const name = 'sunny-video'
export default defineComponent({
name,
props: VideoProps,
emits: ['play', 'pause', 'ended', 'timeupdate', 'trialEnd', 'playError', 'changeSeek', 'fullscreenchange', 'handleBtn'],
setup(props, {emit}) {
const { proxy } = getCurrentInstance()
const { screenWidth, screenHeight } = uni.getSystemInfoSync()
const state = reactive<StateData>({
isPlay: false,
controls: false,
enablePlayGesture: false,
countPlay: 0,
isFullScreen: false,
rateText: '1.0',
isShowRateBox: false,
visibleTrialEndBox: false,
showMplayerToast: false
})
const rateList = ref<rateListData[]>([{
name: '0.5',
isSelect: false
}, {
name: '0.8',
isSelect: false
}, {
name: '1.0',
isSelect: true
}, {
name: '1.25',
isSelect: false
}, {
name: '1.5',
isSelect: false
}
// #ifdef MP-WEIXIN
, {
name: '2.0',
isSelect: false
}
// #endif
])
const videoCtx = ref(null)
const {isFullScreen} = toRefs(state)
/** 播放按钮样式 */
const playImgStyle = computed(()=>{
const wh = getPx(props.playImgHeight)
const style = {
width: addUnit(wh),
height: addUnit(wh),
top: `${getPx(props.videoHeight)/2}px`,
left: `${getPx(props.videoWidth)/2}px`,
transform:`translate(-${getPx(wh)/2}px,-${getPx(wh)/2}px)`
}
return style
})
const speedStyle = ref({})
const speedBoxMaskStyle = ref({})
const speedBoxLeftStyel = ref({})
const speedBoxStyle = ref({})
const trialEndBoxStyle = ref({})
/** 设置样式 */
const setStyles = () => {
/** 当前倍速样式 */
(function speedStyleFn (){
let tops = getPx(props.videoHeight) / 2
if(isFullScreen.value){
tops = getPx(props.videoWidth) / 2
}
Object.assign(speedStyle.value, {top: tops - 15 + 'px'})
})();
/** 倍速盒子背景遮罩 */
(function speedBoxMaskStyleFn(){
let style = {
width: addUnit(props.videoWidth),
height: addUnit(props.videoHeight)
}
if(isFullScreen.value){
style = {
width: `${screenHeight}px`,
height: `${screenWidth}px`
}
}
Object.assign(speedBoxMaskStyle.value, style)
})();
/** 倍速盒子左侧遮罩 */
(function speedBoxLeftStyelFn(){
const w = getPx(props.speedBoxWidth)
const pw = getPx(props.videoWidth)
let style = {
width: `${pw - w}px`,
height: addUnit(props.videoHeight)
}
if(isFullScreen.value){
style = {
width: `${screenHeight - w}px`,
height: `${screenWidth}px`
}
}
Object.assign(speedBoxLeftStyel.value, style)
proxy.$forceUpdate()
})();
/** 倍速盒子样式 */
(function speedBoxStyleFn(){
let style = {
width: addUnit(props.speedBoxWidth),
height: addUnit(props.videoHeight)
}
if(isFullScreen.value){
style = {
width: addUnit(props.speedBoxWidth),
height: `${screenWidth}px`
}
}
Object.assign(speedBoxStyle.value, style)
})();
/** 试看样式 */
(function trialEndBoxStyleFn() {
let style = {
width: addUnit(props.videoWidth),
height: addUnit(props.videoHeight)
}
if(isFullScreen.value){
style = {
width: `${screenHeight}px`,
height: `${screenWidth}px`
}
}
Object.assign(trialEndBoxStyle.value, style)
})();
proxy.$forceUpdate()
}
onMounted(()=>{
videoCtx.value = uni.createVideoContext(props.videoId, proxy)
setStyles()
})
/**
* 播放视频
*/
function changePlay() {
nextTick(()=>{
videoCtx.value.play()
if (state.countPlay <= 0 && props.seekTime > 0) {
// 没有播放过并且需要跳转视频
// #ifndef H5
seek(props.seekTime)
// #endif
state.showMplayerToast = true
setTimeout(() => {
state.showMplayerToast = false
}, 3000)
}
state.countPlay++
})
}
/**
* 跳转到指定位置,单位 s
* @param {Object} position
* @description V1.1.1
*/
function seek(position:number | string) {
nextTick(() => {
videoCtx.value.seek(position)
emit('changeSeek', position)
})
}
/**
* 切换倍速
* @param {Object} item 当前点击项
* @param {Object} index 倍速索引
*/
function changeRate(item:rateListData, index:number) {
if (item.isSelect) return state.isShowRateBox = false
rateList.value.forEach((v, i) => {
i == index ? v.isSelect = true : v.isSelect = false
})
videoCtx.value.playbackRate(+item.name)
state.rateText = item.name
state.isShowRateBox = false
}
/**
* 当开始/继续播放时触发play事件 - 会触发emit事件
* @param {EventHandle} e
*/
function play(e) {
state.isPlay = true
state.controls = true
state.enablePlayGesture = true
emit('play', e)
};
/**
* 视频播放进度变化 - 会触发emit事件
* @param {Object} e event.detail = {currentTime, duration}
*/
function timeupdate(e) {
emit('timeupdate', e)
// 是否判断需要视频试看 V1.1.0
if (props.trialTime <= 0) return
if (e.detail.currentTime >= props.trialTime) {
emit('trialEnd')
showTrialEnd()
state.enablePlayGesture = false
state.controls = false
}
};
/**
* 控制试看模块显示 V1.1.0
*/
function showTrialEnd() {
state.visibleTrialEndBox = true
videoCtx.value.pause()
}
/**
* 点击试看结束按钮 V1.1.6
* @param {Event} e
*/
function handleClickTrialEnd(e) {
if (e.hasOwnProperty('touches')) {
emit('handleBtn')
}
}
/**
* H5用户手动跳转指定位置播放 v1.2.0
*/
function handelSeek() {
seek(props.seekTime)
videoCtx.value.play()
state.showMplayerToast = false
}
/**
* 全屏操作 - 会触发emit
*/
function fullscreenchange(e) {
state.isFullScreen = e.detail.fullScreen
setStyles()
emit('fullscreenchange', e.detail)
}
/**
* 监听视频结束 - 会触发emit事件
*/
function ended() {
emit('ended')
if (!props.isExitFullScreen) return
videoCtx.value.exitFullScreen(); //退出全屏
};
/**
* 控制试看模块隐藏 V1.1.0
*/
function closeTrialEnd() {
state.visibleTrialEndBox = false
changePlay()
}
/**
* 手动全屏方法 v1.1.8
* @param {Object} direction direction取为 vertical 或 horizontal
* @description H5和抖音小程序不支持{direction}参数
*/
function requestFullScreen(direction: 'vertical' | 'horizontal' = 'horizontal') {
videoCtx.value.requestFullScreen({
direction
})
}
/**
* 手动退出全屏 v1.1.8
*/
function exitFullScreen() {
videoCtx.value.exitFullScreen()
}
/**
* v1.1.8
* 显示状态栏,仅在iOS全屏下有效
* 平台差异:仅微信小程序、百度小程序、QQ小程序
*/
function showStatusBar() {
videoCtx.value.showStatusBar()
}
/**
* v1.1.8
* 隐藏状态栏,仅在iOS全屏下有效
* 平台差异:仅微信小程序、百度小程序、QQ小程序
*/
function hideStatusBar() {
videoCtx.value.hideStatusBar()
}
/**
* 停止视频 v1.1.8
*/
function handelStop() {
videoCtx.value.stop()
}
return {
emit,
addUnit,
convertSecondsToHMS,
state,
play,
ended,
timeupdate,
changePlay,
speedStyle,
playImgStyle,
speedBoxStyle,
speedBoxMaskStyle,
speedBoxLeftStyel,
rateList,
trialEndBoxStyle,
fullscreenchange,
handleClickTrialEnd,
handelSeek,
changeRate,
closeTrialEnd,
requestFullScreen,
exitFullScreen,
showStatusBar,
hideStatusBar,
handelStop
}
},
})
</script>
<style lang="scss">
@import './index.scss';
</style>
@@ -0,0 +1,32 @@
// @ts-nocheck
export interface Props {
}
export type rateListData = {
/** 名称 */
name: string
/** 是否选中 */
isSelect: boolean
}
export interface StateData {
/** 视频是否播放过 */
isPlay: boolean
/** 是否显示原生控件 */
controls: boolean
/** 是否开启播放手势,即双击切换播放/暂停 */
enablePlayGesture: boolean
/** 视频播放次数 */
countPlay: number | string
/** 全屏状态 */
isFullScreen: boolean
/** 当前倍速 */
rateText: string
/** 是否显示倍速盒子 */
isShowRateBox: boolean
/** 控制试看结束内容显示隐藏 */
visibleTrialEndBox: boolean
/** 控制H5历史播放位置的显示隐藏 */
showMplayerToast: boolean
}
@@ -0,0 +1,61 @@
// @ts-nocheck
/**
* @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
* @param {number|string} value 用户传递值的px值
* @param {boolean} unit
* @returns {number|string}
*/
export const getPx = (value, unit = false)=> {
if (testNumber(value)) {
return unit ? `${value}px` : Number(value)
}
// 如果带有rpx,先取出其数值部分,再转为px值
if (/(rpx|upx)$/.test(value)) {
return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
}
return unit ? `${parseInt(value)}px` : parseInt(value)
}
/**
* @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
* @param {string|number} value 需要添加单位的值
* @param {string} unit 添加的单位名 比如px
*/
export const addUnit =(value = 'auto', unit = 'px')=> {
value = String(value)
// number判断是否为数值
return testNumber(value) ? `${value}${unit}` : value
}
/**
* 验证十进制数字
*/
function testNumber(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
/**
* @description 获取系统信息同步接口
* @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
*/
export const sys=()=> {
return uni.getSystemInfoSync()
}
/**
* 将秒转时分秒
* @param {number} 数字
*/
export const convertSecondsToHMS =(seconds)=> {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
// 在数字小于10时,在前面补零
const minutesStr = minutes < 10 ? "0" + minutes : minutes;
const secondsStr = remainingSeconds < 10 ? "0" + remainingSeconds : remainingSeconds;
if (hours === 0) {
return minutesStr + ":" + secondsStr;
} else {
const hoursStr = hours < 10 ? "0" + hours : hours;
return hoursStr + ":" + minutesStr + ":" + secondsStr;
}
}
@@ -0,0 +1,16 @@
// @ts-nocheck
// #ifdef VUE3
export * from 'vue';
// #endif
// #ifndef VUE3
export * from '@vue/composition-api';
// #ifdef APP-NVUE
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
// #endif
// #endif
+86
View File
@@ -0,0 +1,86 @@
{
"id": "sunny-video",
"displayName": "sunny-video视频倍速试看组件",
"version": "2.0.0",
"description": "sunny-video简单的视频倍速播放插件,基于uni video,增加倍速播放、视频试看,兼容APP及微信小程序。",
"keywords": [
"sunny-video",
"视频播放组件",
"视频倍速",
"video",
"视频试看"
],
"repository": "",
"engines": {
"HBuilderX": "^4.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": "2304493354"
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
+130
View File
@@ -0,0 +1,130 @@
# sunny-video视频倍速播放器
> **组件名:sunny-video**
### 平台差异说明
- 目前已应用到APP(安卓、iOS)、微信(小程序、H5)
- 其它平台未测试
### 安装方式
- 本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`
- **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?id=11982](https://ext.dcloud.net.cn/plugin?id=11982)
### <a id="jump1">基本用法</a>
- APP端需要配置`manifest.json>App模块配置`勾选`VideoPlay(视频播放)`
- App端:3.6.14 以及 手机系统 iOS16 以上video全屏 需要配置应用支持横屏: 在`manifest.json` 文件内 `app-plus` 节点下新增 `screenOrientation` 配置,设置值为`["portrait-primary","portrait-secondary","landscape-primary","landscape-secondary"]`。如下:
```json
"app-plus" : {
"screenOrientation" : [
"portrait-primary",
"portrait-secondary",
"landscape-primary",
"landscape-secondary"
]
}
```
#### <a id="jump2">vue2使用必看</a>
-`2.0.0`开始,本组件使用`composition-api`, 如果你希望在vue2中使用,需要在`main.js`中的`vue2`部分中添加如下关键代码块
```javascript
// vue2
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
```
- 更多说明请查看官方教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)
### 代码演示
```vue
<template>
<view>
<sunny-video
ref="sunnyVideoRef"
title="测试视频"
src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4"
poster="https://ask.dcloud.net.cn/static/images/side/ask_right_unicloud_class.jpg"
:trialTime="20"
:seekTime="5"
@timeupdate="timeupdate"
/>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
timeupdate(e){
}
}
}
</script>
<style>
</style>
```
### Props
|属性名 | 类型 |默认值 | 可选值 | 说明 |
|:-: | :-: |:-: | :-: | :-: |
|src | String | '' | - | 视频播放地址 |
|loop `1.1.3` | Boolean | false | true | 是否循环播放 |
|muted `1.1.3` | Boolean | false | true | 是否静音播放 |
|autoplay | Boolean | false | true | 是否自动播放 |
|title | String | '' | - | 视频标题 |
|poster | String | '' | - | 视频封面 |
|videoHeight | String, Number| 230px | - | 视频高度 |
|videoWidth `1.1.3` | String, Number| 750rpx | - | 视频宽度 |
|playImgHeight | String, Number| 70rpx | - | 播放图标按钮宽高 |
|playImg | String | base64 | - | 播放按钮图标 |
|showMuteBtn | Boolean | false | true | 是否显示静音按钮 |
|isExitFullScreen | Boolean | true | false | 播放完毕是否退出全屏 |
|tipText `1.1.0` | String | '试看已结束,本片是会员专享内容' | - | 试看提示的文字 |
|btnText `1.1.0` | String | '成为会员免费观看' | - | 试看按钮的文字 |
|trialTime `1.1.0` | Number | 0 | - | 视频试看时间(秒), 不需要试看功能则默认为0 |
|speedBoxWidth `1.1.3` | String, Number| 160rpx | - | 倍速盒子宽度 |
|seekTime `1.2.0` | Number | 0 | - | 跳转到历史观看位置(秒), 不需要则默认为0,注:`H5`为被动 |
|videoId `1.2.1` | String | sunnyVideo | - | 视频唯一ID |
|objectFit `2.0.0` | String | contain | - | 当视频大小与 video 容器大小不一致时,视频的表现形式。contain:包含,fill:填充,cover:覆盖 |
### 事件 Events
| 事件名 | 说明 | 返回值 |
|:-: | :-: |:-: |
| play | 监听开始播放 | - |
| pause | 监听视频暂停 | - |
| playError | 视频播放出错时触发 | - |
| videoEnded | 视频播放结束触发 | - |
| timeupdate | 播放进度变化时触发 | event.detail={currentTime, duration}。触发频率 250ms 一次 |
| fullscreenchange `1.1.3` | 当视频进入和退出全屏时触发 | event={fullScreen, direction}direction取为vertical或horizontal |
| handleBtn `1.1.0` | 点击试看按钮时触发 | - |
| trialEnd `1.1.0` | 试看结束时触发 | - |
| changeSeek `1.1.7` | 视频跳转到指定位置时触发 | event= 播放位置单位 s |
### 方法 Methods
- 需要通过ref获取组件才能调用
| 名称 | 参数 | 说明 | 差异 |
|:-: |:-: |:-: |:-: |
| changePlay | | 开始播放视频 | - |
| changePause `1.1.3` | | 暂停播放视频 | - |
| showTrialEnd `1.1.0` | | 控制试看模块显示 | - |
| closeTrialEnd `1.1.0` | | 控制试看模块隐藏 | - |
| seek `1.1.1` | position | 跳转到指定位置,单位 s | - |
| requestFullScreen `1.1.8` | |进入全屏 | - |
| exitFullScreen `1.1.8` | |退出全屏 | - |
| showStatusBar `1.1.8` | |显示状态栏,仅在iOS全屏下有效 |微信小程序、百度小程序、QQ小程序 |
| hideStatusBar `1.1.8` | |隐藏状态栏,仅在iOS全屏下有效 |微信小程序、百度小程序、QQ小程序 |
| handelStop `1.1.8` | |停止视频 |微信小程序 |
### 注意事项
- APP全屏需要按照文档[基本用法](#jump1)进行配置,
- APP端如果需要全屏倍速及试看,需要使用`.nvue`文件
- `vue2`项目中使用,需要按照文档[vue2使用必看](#jump2)进行配置
- 问题反馈交流群:[122150481](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=2_xYi389jXJRZwPseVEICL_9trE4RrPU&authKey=nsOJ%2BQd%2Fy3Irv4oKaNnxP6XUwTtHUbBVIy3Tw66WX%2FXgVTGWD%2FgBFGVajuQkWPru&noverify=0&group_code=122150481)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB