163 lines
3.3 KiB
Vue
163 lines
3.3 KiB
Vue
<template>
|
|
<view class="control-bar" :class="{ 'control-bar--visible': showControls }" @click.stop>
|
|
<view class="control-bar__top">
|
|
<!-- 进度条 -->
|
|
<ProgressBar
|
|
:current="currentTime"
|
|
:duration="duration"
|
|
@seek="handleSeek"
|
|
/>
|
|
</view>
|
|
|
|
<view class="control-bar__bottom">
|
|
<!-- 播放/暂停按钮 -->
|
|
<view class="control-button" @click="togglePlay">
|
|
<text class="icon">{{ isPlaying ? '⏸' : '▶️' }}</text>
|
|
</view>
|
|
|
|
<!-- 时间显示 -->
|
|
<view class="time-display">
|
|
<text>{{ formatTime(currentTime) }} / {{ formatTime(duration) }}</text>
|
|
</view>
|
|
|
|
<view class="control-bar__right">
|
|
<!-- 速率控制 -->
|
|
<SpeedControl
|
|
:rate="playbackRate"
|
|
@change="handleSpeedChange"
|
|
/>
|
|
|
|
<!-- 音量控制 -->
|
|
<VolumeControl
|
|
:volume="volume"
|
|
@change="handleVolumeChange"
|
|
/>
|
|
|
|
<!-- 全屏按钮 -->
|
|
<view class="control-button" @click="toggleFullscreen">
|
|
<text class="icon">{{ isFullscreen ? '⛶' : '⛶' }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import ProgressBar from './ProgressBar.vue'
|
|
import SpeedControl from './SpeedControl.vue'
|
|
import VolumeControl from './VolumeControl.vue'
|
|
|
|
interface Props {
|
|
currentTime: number
|
|
duration: number
|
|
isPlaying: boolean
|
|
isFullscreen: boolean
|
|
playbackRate: number
|
|
volume: number
|
|
showControls: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
currentTime: 0,
|
|
duration: 0,
|
|
isPlaying: false,
|
|
isFullscreen: false,
|
|
playbackRate: 1.0,
|
|
volume: 100,
|
|
showControls: true
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'toggle-play': []
|
|
'seek': [time: number]
|
|
'toggle-fullscreen': []
|
|
'speed-change': [rate: number]
|
|
'volume-change': [volume: number]
|
|
}>()
|
|
|
|
const formatTime = (seconds: number): string => {
|
|
if (!seconds || isNaN(seconds)) return '00:00'
|
|
|
|
const mins = Math.floor(seconds / 60)
|
|
const secs = Math.floor(seconds % 60)
|
|
|
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
|
}
|
|
|
|
const togglePlay = () => {
|
|
emit('toggle-play')
|
|
}
|
|
|
|
const handleSeek = (time: number) => {
|
|
emit('seek', time)
|
|
}
|
|
|
|
const toggleFullscreen = () => {
|
|
emit('toggle-fullscreen')
|
|
}
|
|
|
|
const handleSpeedChange = (rate: number) => {
|
|
emit('speed-change', rate)
|
|
}
|
|
|
|
const handleVolumeChange = (volume: number) => {
|
|
emit('volume-change', volume)
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.control-bar {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
|
|
padding: 20rpx;
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
z-index: 10;
|
|
|
|
&--visible {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.control-bar__top {
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.control-bar__bottom {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.control-bar__right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.control-button {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
|
|
.icon {
|
|
font-size: 40rpx;
|
|
color: #fff;
|
|
}
|
|
}
|
|
|
|
.time-display {
|
|
color: #fff;
|
|
font-size: 24rpx;
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|