1145 lines
27 KiB
Plaintext
1145 lines
27 KiB
Plaintext
<template>
|
||
<view class="skin-wrap">
|
||
<view class="plv-player-comp-box" :style="{
|
||
'width': (holeWidth) + 'px'
|
||
}">
|
||
<view class="plv-player-longpress-speed" v-if="isInLongPress && !isLock">
|
||
<image class="plv-player-longpress-speed__image" src="@/static/skin/forward.png"></image>
|
||
<text class="plv-player-longpress-speed__text">快进x2.0</text>
|
||
</view>
|
||
<drag-tips class="plv-player-skin-drag-tips" v-if="showGestureSeekTips" :duration="duration" :changeTime="gestureChangeSeekTime" :currentTime="currentTime"></drag-tips>
|
||
</view>
|
||
<gesture class="plv-player-mask"
|
||
:holeWidth="holeWidth"
|
||
:duration="duration"
|
||
:currentTime="currentTime"
|
||
@onLongPress="handleLongPress"
|
||
@onTouchCancel="handleTouchCancel"
|
||
@onGestureClick="handleMask"
|
||
@onTouchEnd="handleTouchEnd"
|
||
@onGestureSeekTo="handleSeekTo"
|
||
@onGestureEvent="handleGEvent">
|
||
</gesture>
|
||
<light class="plv-player-light" :imgUrl="lightImg" :precent="lightPrecent"></light>
|
||
<volume class="plv-player-volume" :imgUrl="volumeImg" :muteImgUrl="volumeMuteImg" :value="volumeValue"></volume>
|
||
<view class="plv-player-subtitle-view" v-if="isShowSubtitle">
|
||
<text v-if="isShowDoubleSubTitle" :style="{ fontWeight: topSubtitle.isBold? 'bold' : 'normal',
|
||
fontStyle: topSubtitle.isItalic? 'italic' : 'normal', color: topSubtitle.fontColor,
|
||
backgroundColor: topSubtitle.bgColor, fontSize: topSubtitle.fontSize }">{{topSubtitle.text}}</text>
|
||
<text v-if="isShowDoubleSubTitle" :style="{ fontWeight: bottomSubtitle.isBold? 'bold' : 'normal',
|
||
fontStyle: bottomSubtitle.isItalic? 'italic' : 'normal', color: bottomSubtitle.fontColor,
|
||
backgroundColor: bottomSubtitle.bgColor, fontSize: bottomSubtitle.fontSize }">{{bottomSubtitle.text}}</text>
|
||
<text v-else="isShowSingleSubTitle" :style="{ fontWeight: singleSubtitle.isBold? 'bold' : 'normal',
|
||
fontStyle: singleSubtitle.isItalic? 'italic' : 'normal', color: singleSubtitle.fontColor,
|
||
backgroundColor: singleSubtitle.bgColor, fontSize: singleSubtitle.fontSize }">{{singleSubtitle.text}}</text>
|
||
</view>
|
||
<view class="plv-player-bar" v-if="!selectStatus && showBar && !isLock">
|
||
<image class="plv-player-bar__bg" :src="bottomBar"></image>
|
||
<view class="plv-player-bar__box">
|
||
<view class="plv-player-mr-left"></view>
|
||
<view class="plv-player-middle">
|
||
<view class="plv-player-progress">
|
||
<view class="plv-player-progress__play__wrap">
|
||
<view class="plv-player-progress__wrap__played" :style="'flex:' + progressPrecent"></view>
|
||
<view class="plv-player-progress__wrap__surplus" :style="'flex:' + progressLeftOverPrecent"></view>
|
||
</view>
|
||
<view class="plv-player-progress__dot__wrap">
|
||
<view class="plv-player-progress__dot__box">
|
||
<view class="plv-player-progress__dot__played" :style="'flex:' + (progressPrecent || 1)">
|
||
<view class="plv-player-progress__dot"></view>
|
||
</view>
|
||
<view class="plv-player-progress__dot__surplus" :style="'flex:' + progressLeftOverPrecent"></view>
|
||
</view>
|
||
</view>
|
||
<view class="plv-player-progress__mask"
|
||
@touchstart="touchstart"
|
||
@touchmove="touchmove"
|
||
@touchend="touchend"></view>
|
||
</view>
|
||
<view class="plv-player-bar-bottom">
|
||
<image class="plv-btn plv-player-play-btn" :src=playBtnLink @click="handlePlay"></image>
|
||
<view class="plv-player-time">
|
||
<text class="plv-player-btn__text">{{ currentTimeText }}</text>
|
||
<text class="plv-player-btn__text"> / </text>
|
||
<text class="plv-player-btn__text">{{ durationText }}</text>
|
||
</view>
|
||
<image class="plv-btn plv-player-full-btn" :src="fullBtnLink" @click="handleFull"></image>
|
||
<text class="plv-btn plv-player-hd-btn plv-player-btn__text" @click="handleBtn(types.LEVEL)">{{ currentLevelText }}</text>
|
||
<text class="plv-btn plv-player-rate-btn plv-player-btn__text" @click="handleBtn(types.SPEED)">{{ currentSpeedText }}</text>
|
||
<text class="plv-btn plv-player-scaling-btn plv-player-btn__text" @click="handleBtn(types.SCALING)">画面</text>
|
||
<text class="plv-btn plv-player-subtitle-btn plv-player-btn__text"
|
||
v-if="isEnableSubTitle" @click="handleBtn(types.SUBTITLE)">字幕</text>
|
||
</view>
|
||
</view>
|
||
<view class="plv-player-mr-right"></view>
|
||
</view>
|
||
</view>
|
||
<view class="plv-player-select" v-if="selectStatus">
|
||
<view class="plv-player-select__bg"></view>
|
||
<view class="plv-player-select__option">
|
||
<text v-for=" (item, index) in selects"
|
||
:key="index"
|
||
class="plv-player-select__option__item"
|
||
:class="{ 'select-option__text': true, active: index === currentOptionIndex}"
|
||
@click="selectOption(index)">
|
||
{{ item[0] }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view class="plv-player-screenshot" v-if="!selectStatus && showBar && !isLock" :style="'bottom:' + screenShotY">
|
||
<image class="plv-player-screenshot__btn" :src="screenShotImg" @click="$emit('onScreenShot')"></image>
|
||
</view>
|
||
<view class="plv-player-lock" v-if="!selectStatus && showBar" :style="'bottom:' + screenShotY">
|
||
<image class="plv-player-lock__btn" :src="lockImgSrc" @click="isLock = !isLock"></image>
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||
import Gesture from './gesture.nvue';
|
||
import Light from './light.nvue';
|
||
import Volume from './volume.nvue';
|
||
import DragTips from '@/components/plv-player-skin/drag-tips.nvue';
|
||
import LockImg from '@/static/skin/lock.png';
|
||
import UnLockImg from '@/static/skin/unlock.png';
|
||
// 部分img 放到了static/skin目录下
|
||
const imgLink = {
|
||
play: 'https://play1.polyv.net/player2/demo/uniapp/pause-left-btn.png',
|
||
pause: 'https://play1.polyv.net/player2/demo/uniapp/play-left-btn.png',
|
||
full: 'https://play1.polyv.net/player2/demo/uniapp/fullscreen-btn.png',
|
||
normal: 'https://play1.polyv.net/player2/demo/uniapp/normalscreen-btn.png',
|
||
bottomBar: 'https://play1.polyv.net/player2/demo/uniapp/bottombar.png',
|
||
light: 'https://play1.polyv.net/player2/demo/uniapp/light.png',
|
||
volume: 'https://play1.polyv.net/player2/demo/uniapp/volume.png',
|
||
mute: 'https://play1.polyv.net/player2/demo/uniapp/mute.png',
|
||
screenShot: 'https://play1.polyv.net/player2/demo/uniapp/screenShot.png'
|
||
};
|
||
|
||
const emit = defineEmits([
|
||
'onScreenShot',
|
||
'onPlayBtnClick',
|
||
'onFullBtnClick',
|
||
'onHdBtnClick',
|
||
'onRateBtnClick',
|
||
'onScalingBtnClick',
|
||
'onOpenSRT',
|
||
'onChangeSRTSingleMode',
|
||
'onChangeSRTBtnClick',
|
||
'onToSeek',
|
||
'onVolumeChanged'
|
||
]);
|
||
|
||
// data 原有状态
|
||
const showBar = ref(true);
|
||
const showBarTime = ref(10);
|
||
const playStatus = ref(true);
|
||
const playBtnLink = ref(imgLink.play);
|
||
const fullStatus = ref(false);
|
||
const fullBtnLink = ref(imgLink.full);
|
||
const bottomBar = ref(imgLink.bottomBar);
|
||
const timeShowBlock = ref(null);
|
||
const currentTime = ref(null);
|
||
const currentTimeText = ref('00:00');
|
||
const duration = ref(null);
|
||
const durationText = ref(null);
|
||
const precent = ref(0);
|
||
const progressPrecent = ref(0);
|
||
const progressLeftOverPrecent = ref(100);
|
||
const isDraging = ref(false);
|
||
const selects = ref([]);
|
||
const selectStatus = ref(false);
|
||
const types = {
|
||
LEVEL: 'level',
|
||
SPEED: 'speed',
|
||
SCALING: 'scaling',
|
||
SUBTITLE: 'subtitle'
|
||
};
|
||
const type = ref(null);
|
||
const levels = ref([
|
||
['自动', 0],
|
||
['流畅', 1],
|
||
['高清', 2],
|
||
['超清', 3]
|
||
]);
|
||
const level = ref(0);
|
||
const selectedHdAuto = ref(false);
|
||
const speeds = ref([
|
||
['0.5x', 0.5],
|
||
['1x', 1],
|
||
['1.5x', 1.5],
|
||
['2x', 2]
|
||
]);
|
||
const speed = ref(1);
|
||
const lightImg = ref(imgLink.light);
|
||
const lightPrecent = ref(0);
|
||
const volumeImg = ref(imgLink.volume);
|
||
const volumeMuteImg = ref(imgLink.mute);
|
||
const volumeValue = ref(0.5);
|
||
const screenShotImg = ref(imgLink.screenShot);
|
||
const screenShotY = ref('200rpx');
|
||
|
||
const showGestureSeekTips = ref(false);
|
||
const gestureChangeSeekTime = ref(0);
|
||
|
||
const seekedNeedPlay = ref(false);
|
||
const isLock = ref(false);
|
||
const scalings = ref([['居中', 0], ['适应', 1], ['填充', 2], ['拉伸', 3]]);
|
||
const scaling = ref(undefined);
|
||
const isInLongPress = ref(false);
|
||
const isEnableSubTitle = ref(true);
|
||
const isEnableDoubleSubTitle = ref(false);
|
||
const subtitles = ref([['不显示', 0]]);
|
||
const isShowSubtitle = ref(false);
|
||
const isShowSingleSubTitle = ref(false);
|
||
const isShowDoubleSubTitle = ref(false);
|
||
const subtitle = ref(0);
|
||
|
||
const topSubtitle = reactive({
|
||
text: '',
|
||
isBold: false,
|
||
isItalic: false,
|
||
fontColor: '#fb12d4',
|
||
bgColor: '#67f933',
|
||
fontSize: 14
|
||
});
|
||
|
||
const bottomSubtitle = reactive({
|
||
text: '',
|
||
isBold: false,
|
||
isItalic: false,
|
||
fontColor: '#ffff00',
|
||
bgColor: '#00f3ff',
|
||
fontSize: 14
|
||
});
|
||
|
||
const singleSubtitle = reactive({
|
||
text: '',
|
||
isBold: false,
|
||
isItalic: false,
|
||
fontColr: '#ffffff',
|
||
bgColor: '#000000',
|
||
fontSize: 14
|
||
});
|
||
|
||
// 额外在 methods 中用到但 data 里没显式声明的字段
|
||
const holeWidth = ref(0);
|
||
const clock = ref(null);
|
||
const initTouchPageX = ref(0);
|
||
const finalPrecent = ref(0);
|
||
const enableSubTitleDouble = ref(false);
|
||
|
||
let gestureTimeout;
|
||
|
||
// watch
|
||
watch(type, () => {
|
||
const key = `${type.value}s`;
|
||
const source = (key in { levels, speeds, scalings, subtitles } ? {
|
||
levels,
|
||
speeds,
|
||
scalings,
|
||
subtitles
|
||
}[key] : null);
|
||
if (source && source.value) {
|
||
selects.value = source.value;
|
||
}
|
||
});
|
||
|
||
// computed
|
||
const currentLevelText = computed(() => {
|
||
const list = levels.value;
|
||
return list[level.value]?.[0] ?? '';
|
||
});
|
||
|
||
const currentSpeedText = computed(() => {
|
||
const list = speeds.value;
|
||
return list[speed.value]?.[0] ?? '';
|
||
});
|
||
|
||
const currentOptionIndex = computed(() => {
|
||
switch (type.value) {
|
||
case types.LEVEL:
|
||
return level.value;
|
||
case types.SPEED:
|
||
return speed.value;
|
||
case types.SCALING:
|
||
return scaling.value;
|
||
case types.SUBTITLE:
|
||
return subtitle.value;
|
||
default:
|
||
return 0;
|
||
}
|
||
});
|
||
|
||
const lockImgSrc = computed(() => (isLock.value ? LockImg : UnLockImg));
|
||
|
||
onMounted(() => {
|
||
countHoleWidth();
|
||
barHide();
|
||
});
|
||
|
||
/**
|
||
* 计算进度条总长度
|
||
*/
|
||
function countHoleWidth() {
|
||
const height = plus.display.resolutionHeight;
|
||
const width = plus.display.resolutionWidth;
|
||
|
||
if (fullStatus.value) {
|
||
holeWidth.value = height > width ? height : width;
|
||
} else {
|
||
holeWidth.value = width < height ? width : height;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Toogle修改播放按钮状态,传入forceStatus以forceStatus为主
|
||
*/
|
||
function changePlayStatus(forceStatus) {
|
||
let newStatus = !playStatus.value;
|
||
if (typeof forceStatus !== 'undefined') {
|
||
newStatus = forceStatus;
|
||
}
|
||
playStatus.value = newStatus;
|
||
playBtnLink.value = newStatus ? imgLink.play : imgLink.pause;
|
||
}
|
||
|
||
function pause() {
|
||
playStatus.value = true;
|
||
handlePlay();
|
||
}
|
||
|
||
function play() {
|
||
playStatus.value = false;
|
||
handlePlay();
|
||
}
|
||
|
||
/**
|
||
* 播放按钮点击
|
||
*/
|
||
function handlePlay() {
|
||
changePlayStatus();
|
||
emit('onPlayBtnClick', playStatus.value);
|
||
}
|
||
|
||
/**
|
||
* Toogle修改全屏按钮状态
|
||
*/
|
||
function changeFullStatus() {
|
||
let newStatus = !fullStatus.value;
|
||
fullStatus.value = newStatus;
|
||
fullBtnLink.value = newStatus ? imgLink.normal : imgLink.full;
|
||
}
|
||
|
||
/**
|
||
* 全屏按钮点击
|
||
*/
|
||
function handleFull() {
|
||
changeFullStatus();
|
||
emit('onFullBtnClick', fullStatus.value);
|
||
countHoleWidth();
|
||
screenShotY.value = fullStatus.value ? '300rpx' : '200rpx';
|
||
}
|
||
|
||
/**
|
||
* 点击清晰度/倍速按钮
|
||
*/
|
||
function handleBtn(t) {
|
||
type.value = t;
|
||
selectStatus.value = !selectStatus.value;
|
||
}
|
||
|
||
/**
|
||
* 选择切换清晰度/倍速
|
||
*/
|
||
function selectOption(value) {
|
||
const currentType = type.value;
|
||
const currentTypes = types;
|
||
const currentSpeeds = speeds.value;
|
||
const currentSubtitles = subtitles.value;
|
||
|
||
if (currentType === currentTypes.LEVEL) {
|
||
level.value = value;
|
||
} else if (currentType === currentTypes.SPEED) {
|
||
speed.value = value;
|
||
} else if (currentType === currentTypes.SCALING) {
|
||
scaling.value = value;
|
||
} else if (currentType === currentTypes.SUBTITLE) {
|
||
subtitle.value = value;
|
||
}
|
||
|
||
selectStatus.value = false;
|
||
|
||
switch (currentType) {
|
||
case currentTypes.LEVEL:
|
||
selectedHdAuto.value = value === 0;
|
||
emit('onHdBtnClick', value);
|
||
break;
|
||
case currentTypes.SPEED:
|
||
emit('onRateBtnClick', currentSpeeds[value][1]);
|
||
break;
|
||
case currentTypes.SCALING:
|
||
emit('onScalingBtnClick', value);
|
||
break;
|
||
case currentTypes.SUBTITLE:
|
||
if (value === 0) {
|
||
isShowSubtitle.value = false;
|
||
emit('onOpenSRT', false);
|
||
isShowDoubleSubTitle.value = false;
|
||
isShowSingleSubTitle.value = false;
|
||
break;
|
||
}
|
||
if (currentSubtitles[value][0] === '双语') {
|
||
console.log('===1 切换双语');
|
||
isShowSubtitle.value = true;
|
||
isShowDoubleSubTitle.value = true;
|
||
isShowSingleSubTitle.value = false;
|
||
emit('onChangeSRTSingleMode', false);
|
||
emit('onOpenSRT', true);
|
||
break;
|
||
}
|
||
isShowSubtitle.value = true;
|
||
isShowDoubleSubTitle.value = false;
|
||
isShowSingleSubTitle.value = true;
|
||
emit('onChangeSRTSingleMode', true);
|
||
emit('onOpenSRT', true);
|
||
emit('onChangeSRTBtnClick', currentSubtitles[value][0]);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 点击空白区域
|
||
*/
|
||
function handleMask() {
|
||
if (selectStatus.value) {
|
||
selectStatus.value = false;
|
||
return;
|
||
}
|
||
|
||
if (showBar.value) {
|
||
showBar.value = false;
|
||
} else {
|
||
showBar.value = true;
|
||
barHide();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 当前播放时间更新
|
||
*/
|
||
function timeUpdate(time) {
|
||
currentTime.value = time;
|
||
currentTimeText.value = timeFormat(time);
|
||
updateProgress();
|
||
}
|
||
|
||
/**
|
||
* 是否有字幕功能
|
||
*/
|
||
function setEnableSubtitle(enableSubTitle, enableSubTitleDoubleArg) {
|
||
isEnableSubTitle.value = enableSubTitle;
|
||
enableSubTitleDouble.value = enableSubTitleDoubleArg;
|
||
if (enableSubTitleDoubleArg) {
|
||
subtitles.value.push(['双语', subtitles.value.length]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置字幕的样式
|
||
*/
|
||
function setSRTTextConfig(config) {
|
||
const position = config.position;
|
||
console.log('===1 setSRTTextConfig position: ' + config.position);
|
||
console.log('===1 setSRTTextConfig color: ' + config.backgroundColor);
|
||
const color = convertARGBtoRGBA(config.backgroundColor);
|
||
console.log('===1 change color: ' + color);
|
||
|
||
if (position === 'bottom') {
|
||
bottomSubtitle.fontColor = convertARGBtoRGBA(config.fontColor);
|
||
bottomSubtitle.fontSize = config.fontSize;
|
||
bottomSubtitle.bgColor = convertARGBtoRGBA(config.backgroundColor);
|
||
bottomSubtitle.isBold = config.fontBold;
|
||
bottomSubtitle.isItalic = config.fontItalics;
|
||
}
|
||
|
||
if (position === 'top') {
|
||
topSubtitle.fontColor = convertARGBtoRGBA(config.fontColor);
|
||
topSubtitle.fontSize = config.fontSize;
|
||
topSubtitle.bgColor = convertARGBtoRGBA(config.backgroundColor);
|
||
topSubtitle.isBold = config.fontBold;
|
||
topSubtitle.isItalic = config.fontItalics;
|
||
}
|
||
|
||
if (position === null || position === '') {
|
||
singleSubtitle.fontColor = convertARGBtoRGBA(config.fontColor);
|
||
singleSubtitle.fontSize = config.fontSize;
|
||
singleSubtitle.bgColor = convertARGBtoRGBA(config.backgroundColor);
|
||
singleSubtitle.isBold = config.fontBold;
|
||
singleSubtitle.isItalic = config.fontItalics;
|
||
}
|
||
}
|
||
|
||
function convertARGBtoRGBA(argbValue) {
|
||
if (!argbValue.startsWith('#') || argbValue.length !== 9) {
|
||
return argbValue;
|
||
}
|
||
const hexValue = argbValue.replace('#', '');
|
||
const alpha = parseInt(hexValue.slice(0, 2), 16) / 255;
|
||
const red = parseInt(hexValue.slice(2, 4), 16);
|
||
const green = parseInt(hexValue.slice(4, 6), 16);
|
||
const blue = parseInt(hexValue.slice(6, 8), 16);
|
||
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
|
||
}
|
||
|
||
/**
|
||
* 插入当前有多少可选的字幕
|
||
*/
|
||
function setSRTTitle(titles) {
|
||
console.log('===1 title is ' + titles);
|
||
const baseLen = subtitles.value.length;
|
||
const newItems = titles.map((lang, idx) => [lang, idx + baseLen + 1]);
|
||
subtitles.value.push(...newItems);
|
||
}
|
||
|
||
/**
|
||
* 字幕
|
||
*/
|
||
function setSRTText(srt) {
|
||
singleSubtitle.text = '';
|
||
topSubtitle.text = '';
|
||
bottomSubtitle.text = '';
|
||
|
||
console.info('====3 srtList: ' + srt);
|
||
|
||
const data = JSON.parse(srt);
|
||
if (!data) {
|
||
return;
|
||
}
|
||
data.strList.forEach(item => {
|
||
const position = item.position;
|
||
if (position === 'singleSubtitles') {
|
||
singleSubtitle.text = item.srtText;
|
||
}
|
||
if (position === 'bottomSubtitles') {
|
||
bottomSubtitle.text = item.srtText;
|
||
}
|
||
if (position === 'topSubtitles') {
|
||
topSubtitle.text = item.srtText;
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 视频总时长更新
|
||
*/
|
||
function updateDuration(durationVal) {
|
||
duration.value = durationVal;
|
||
timeShowBlock.value = durationVal >= 3600 ? 2 : 1;
|
||
durationText.value = timeFormat(durationVal);
|
||
}
|
||
|
||
/**
|
||
* 更新进度条显示
|
||
*/
|
||
function updateProgress(precentVal) {
|
||
let _precent;
|
||
|
||
const changeProgressWidth = (_p) => {
|
||
progressPrecent.value = parseInt(_p * 100);
|
||
progressLeftOverPrecent.value = 100 - progressPrecent.value;
|
||
};
|
||
|
||
if (precentVal !== undefined) {
|
||
_precent = limit(precentVal, 0, 1);
|
||
changeProgressWidth(_precent);
|
||
} else {
|
||
if (!duration.value || isDraging.value) return;
|
||
_precent = currentTime.value / duration.value;
|
||
precent.value = _precent;
|
||
changeProgressWidth(_precent);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新视频画面缩放模式
|
||
*/
|
||
function updateScaling(mode) {
|
||
scaling.value = mode;
|
||
}
|
||
|
||
/**
|
||
* 监听进度条拖拽seek
|
||
*/
|
||
function handleTouch(t, e) {
|
||
switch (t) {
|
||
case 'start':
|
||
initTouchPageX.value = e.touches[0].pageX * 2;
|
||
barShow();
|
||
break;
|
||
case 'move':
|
||
isDraging.value = true;
|
||
const touchPageX = e.touches[0].pageX * 2;
|
||
const changedX = getChangedX(touchPageX, initTouchPageX.value);
|
||
calcPrecentChange(changedX);
|
||
break;
|
||
case 'end':
|
||
if (isDraging.value) {
|
||
toSeek(finalPrecent.value);
|
||
isDraging.value = false;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算变化百分比
|
||
*/
|
||
function calcPrecentChange(changedX) {
|
||
const changedPrecent = changedX / holeWidth.value;
|
||
let fp = precent.value + changedPrecent;
|
||
fp = limit(fp, 0, 1);
|
||
updateProgress(fp);
|
||
finalPrecent.value = fp;
|
||
}
|
||
|
||
/**
|
||
* 获取变化的X坐标
|
||
*/
|
||
function getChangedX(touchPageX, initTouchPageXVal) {
|
||
return touchPageX - initTouchPageXVal;
|
||
}
|
||
|
||
function touchstart(e) {
|
||
handleTouch('start', e);
|
||
}
|
||
|
||
function touchmove(e) {
|
||
handleTouch('move', e);
|
||
}
|
||
|
||
function touchend(e) {
|
||
handleTouch('end', e);
|
||
}
|
||
|
||
function toSeek(precentVal) {
|
||
const seekTime = parseInt(precentVal * duration.value);
|
||
if (!isNaN(seekTime)) emit('onToSeek', seekTime);
|
||
}
|
||
|
||
function handleSeekTo(time) {
|
||
if (!isNaN(time)) {
|
||
emit('onToSeek', time);
|
||
setTimeout(() => {
|
||
if (seekedNeedPlay.value) {
|
||
play();
|
||
seekedNeedPlay.value = false;
|
||
}
|
||
}, 100);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新可选码率
|
||
*/
|
||
function updateLevels(num) {
|
||
if (!num) return;
|
||
const realNum = num + 1;
|
||
const allLevels = [
|
||
['自动', 0],
|
||
['流畅', 1],
|
||
['高清', 2],
|
||
['超清', 3]
|
||
];
|
||
levels.value = allLevels.slice(0, realNum);
|
||
if (type.value === types.LEVEL) {
|
||
selects.value = levels.value;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新当前码率
|
||
*/
|
||
function updateCurrentLevel(levelVal) {
|
||
if (selectedHdAuto.value) return;
|
||
level.value = levelVal;
|
||
}
|
||
|
||
/**
|
||
* 显示控制栏
|
||
*/
|
||
function barShow() {
|
||
if (clock.value) {
|
||
clearTimeout(clock.value);
|
||
clock.value = null;
|
||
}
|
||
showBar.value = true;
|
||
}
|
||
|
||
/**
|
||
* 隐藏控制栏
|
||
*/
|
||
function barHide() {
|
||
// 保持与原逻辑一致:直接 return,不自动隐藏
|
||
return;
|
||
// 如需开启自动隐藏,可以取消上面的 return,并使用以下代码:
|
||
// if (clock.value) {
|
||
// clearTimeout(clock.value);
|
||
// clock.value = null;
|
||
// }
|
||
// clock.value = setTimeout(() => {
|
||
// showBar.value = false;
|
||
// }, showBarTime.value * 1000);
|
||
}
|
||
|
||
/**
|
||
* 处理手势控制
|
||
*/
|
||
function handleGEvent(data) {
|
||
switch (data.type) {
|
||
case 'SEEK_TIME_UPDATE': {
|
||
if (playStatus.value) {
|
||
seekedNeedPlay.value = true;
|
||
pause();
|
||
}
|
||
const { finalTime } = data;
|
||
if (isNaN(finalTime)) {
|
||
return;
|
||
}
|
||
gestureChangeSeekTime.value = finalTime;
|
||
showGestureSeekTips.value = true;
|
||
clearTimeout(gestureTimeout);
|
||
|
||
gestureTimeout = setTimeout(() => {
|
||
showGestureSeekTips.value = false;
|
||
gestureChangeSeekTime.value = 0;
|
||
}, 600);
|
||
break;
|
||
}
|
||
case 'LEFT_UP':
|
||
uni.getScreenBrightness({
|
||
success: (res) => {
|
||
setBrightness(res.value, data.position, 1);
|
||
}
|
||
});
|
||
break;
|
||
case 'LEFT_DOWN':
|
||
uni.getScreenBrightness({
|
||
success: (res) => {
|
||
setBrightness(res.value, data.position, -1);
|
||
}
|
||
});
|
||
break;
|
||
case 'RIGHT_UP':
|
||
volumeChanged(data.position, 1);
|
||
break;
|
||
case 'RIGHT_DOWN':
|
||
volumeChanged(data.position, -1);
|
||
break;
|
||
case 'DOUBLE_CLICK':
|
||
console.log('DOUBLE_CLICK');
|
||
if (playStatus.value) {
|
||
pause();
|
||
} else {
|
||
play();
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置亮度
|
||
*/
|
||
function setBrightness(currentValue, changedValue, isDown) {
|
||
changedValue = Math.abs(changedValue / 6) / 100 * isDown;
|
||
let realValue = currentValue + changedValue;
|
||
realValue = limit(realValue, 0, 1);
|
||
lightPrecent.value = parseInt(realValue * 100);
|
||
|
||
uni.setScreenBrightness({
|
||
value: realValue
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 音量变化
|
||
*/
|
||
function volumeChanged(changedValue, isDown) {
|
||
changedValue = Math.abs(changedValue / 10) / 100 * isDown;
|
||
emit('onVolumeChanged', changedValue);
|
||
}
|
||
|
||
/**
|
||
* 更新音量变化显示
|
||
*/
|
||
function updateVolumeValue(value) {
|
||
volumeValue.value = value;
|
||
}
|
||
|
||
/**
|
||
* 时间(秒)格式化
|
||
*/
|
||
function timeFormat(result) {
|
||
const h = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600);
|
||
const m = Math.floor((result / 60 % 60)) < 10 ? '0' + Math.floor((result / 60 % 60)) : Math.floor((result / 60 % 60));
|
||
const s = Math.floor((result % 60)) < 10 ? '0' + Math.floor((result % 60)) : Math.floor((result % 60));
|
||
|
||
if (timeShowBlock.value === 2) {
|
||
return h + ':' + m + ':' + s;
|
||
} else {
|
||
return m + ':' + s;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 控制最小值,最大值
|
||
*/
|
||
function limit(num, min, max) {
|
||
if (num < min) return min;
|
||
if (num > max) return max;
|
||
return num;
|
||
}
|
||
|
||
function handleTouchCancel() {
|
||
if (isLock.value) return;
|
||
if (isInLongPress.value) {
|
||
isInLongPress.value = false;
|
||
emit('onRateBtnClick', 1);
|
||
}
|
||
}
|
||
|
||
// 特殊情况如果不触发touchcancel, 触发touchend时取消长按状态
|
||
function handleTouchEnd() {
|
||
handleTouchCancel();
|
||
}
|
||
|
||
function handleLongPress() {
|
||
if (isLock.value) return;
|
||
isInLongPress.value = true;
|
||
play();
|
||
emit('onRateBtnClick', 2);
|
||
}
|
||
|
||
// 暴露给父组件调用的方法(等价于 Vue2 的 this.xxx)
|
||
defineExpose({
|
||
pause,
|
||
play,
|
||
timeUpdate,
|
||
updateDuration,
|
||
updateLevels,
|
||
updateCurrentLevel,
|
||
updateScaling,
|
||
updateVolumeValue,
|
||
setEnableSubtitle,
|
||
setSRTTextConfig,
|
||
setSRTTitle,
|
||
setSRTText
|
||
});
|
||
</script>
|
||
|
||
<style>
|
||
.plv-player-mask {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 120rpx;
|
||
}
|
||
|
||
.plv-player-bar__bg {
|
||
width: 1624rpx;
|
||
height: 120rpx;
|
||
}
|
||
|
||
.plv-player-bar {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
height: 120rpx;
|
||
bottom: 0;
|
||
}
|
||
|
||
.plv-player-bar__box {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
height: 120rpx;
|
||
bottom: 0;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.plv-player-mr-left {
|
||
flex: 10;
|
||
}
|
||
|
||
.plv-player-mr-right {
|
||
flex: 10;
|
||
}
|
||
|
||
.plv-player-middle {
|
||
flex: 80;
|
||
}
|
||
|
||
.plv-player-progress {
|
||
flex: 40;
|
||
}
|
||
|
||
.plv-player-subtitle-view {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
height: 150rpx;
|
||
bottom: 50rpx;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.plv-subtitle-top-text {
|
||
background-color: greenyellow;
|
||
}
|
||
|
||
.plv-subtitle-bottom-text {
|
||
background-color: darkred;
|
||
}
|
||
|
||
.plv-player-progress__play__wrap {
|
||
flex-direction: row;
|
||
flex: 1;
|
||
}
|
||
|
||
.plv-player-progress__wrap__played {
|
||
top: 20rpx;
|
||
height: 8rpx;
|
||
background-color: #0A98D5;
|
||
}
|
||
|
||
.plv-player-progress__wrap__surplus {
|
||
top: 20rpx;
|
||
height: 8rpx;
|
||
background-color: #FFFFFF;
|
||
}
|
||
|
||
.plv-player-progress__dot__wrap {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 1;
|
||
}
|
||
|
||
.plv-player-progress__dot__box {
|
||
flex: 1;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.plv-player-progress__dot__played {
|
||
flex: 20;
|
||
}
|
||
|
||
.plv-player-progress__dot__surplus {
|
||
flex: 80;
|
||
}
|
||
|
||
.plv-player-progress__mask {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
left: 0;
|
||
bottom: 0;
|
||
border-radius: 4rpx;
|
||
z-index: 2;
|
||
}
|
||
|
||
.plv-player-progress__dot {
|
||
position: absolute;
|
||
width: 10rpx;
|
||
height: 30rpx;
|
||
top: 8rpx;
|
||
right: 0;
|
||
border-radius: 4rpx;
|
||
background-color: #FFFFFF;
|
||
/* transform: translateX(15rpx); */
|
||
}
|
||
|
||
.plv-player-bar-bottom {
|
||
flex-direction: row;
|
||
flex: 60;
|
||
}
|
||
|
||
.plv-btn {
|
||
position: relative;
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
top: 8rpx;
|
||
bottom: 0;
|
||
margin-right: 10rpx;
|
||
}
|
||
|
||
.plv-player-full-btn {
|
||
position: absolute;
|
||
right: 0;
|
||
}
|
||
|
||
.plv-player-hd-btn {
|
||
position: absolute;
|
||
right: 64rpx;
|
||
}
|
||
|
||
.plv-player-rate-btn {
|
||
position: absolute;
|
||
right: 128rpx;
|
||
}
|
||
|
||
.plv-player-scaling-btn {
|
||
position: absolute;
|
||
right: 210rpx;
|
||
}
|
||
|
||
.plv-player-subtitle-btn {
|
||
position: absolute;
|
||
right: 278rpx;
|
||
}
|
||
|
||
.plv-player-time {
|
||
position: relative;
|
||
top: 8rpx;
|
||
flex-direction: row
|
||
}
|
||
|
||
.plv-player-select {
|
||
position: absolute;
|
||
width: 200rpx;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
z-index: 1;
|
||
}
|
||
|
||
.plv-player-select__bg {
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #0A98D5;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.plv-player-select__option {
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 20rpx;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.plv-player-select__option__item {
|
||
padding: 18rpx;
|
||
}
|
||
|
||
|
||
.plv-player-btn__text {
|
||
color: #FFFFFF;
|
||
font-size: 28rpx;
|
||
line-height: 64rpx;
|
||
}
|
||
|
||
.select-option__text {
|
||
color: #FFFFFF;
|
||
font-size: 28rpx;
|
||
line-height: 20rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.active {
|
||
color: #0A98D5;
|
||
}
|
||
|
||
.plv-player-light {
|
||
flex: 1;
|
||
align-items: center;
|
||
}
|
||
|
||
.plv-player-volume {
|
||
position: absolute;
|
||
top: 50rpx;
|
||
left: 0;
|
||
right: 0;
|
||
height: 80rpx;
|
||
}
|
||
|
||
.plv-player-screenshot {
|
||
position: absolute;
|
||
margin-left: 80rpx;
|
||
}
|
||
|
||
.plv-player-screenshot__btn {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
}
|
||
|
||
.plv-player-lock {
|
||
position: absolute;
|
||
right: 80rpx;
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
background: rgba(0,0,0, .5);
|
||
border-radius: 40rpx;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.plv-player-lock__btn {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
}
|
||
|
||
.plv-player-skin-drag-tips {
|
||
position: absolute;
|
||
top: 60rpx;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-direction: row;
|
||
/* justify-content: center;
|
||
align-items: center; */
|
||
}
|
||
|
||
.plv-player-longpress-speed {
|
||
position: absolute;
|
||
width: 175rpx;
|
||
padding: 10rpx;
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
align-items: center;
|
||
top: 40rpx;
|
||
border-radius: 15px;
|
||
background: rgba(0,0,0,.8);
|
||
}
|
||
|
||
|
||
.plv-player-longpress-speed__image {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
.plv-player-longpress-speed__text {
|
||
color: #FFFFFF;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.plv-player-comp-box {
|
||
position: absolute;
|
||
height: 300rpx;
|
||
left: 0;
|
||
top: 0;
|
||
z-index: 0;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
</style>
|