2026-02-02 17:44:10 +08:00

1145 lines
27 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>