123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- <!--
- * @FileDescription: 播放器
- * @Author: 黄琪勇
- * @Date:2024-04-03 18:30:09
- -->
- <template>
- <div
- @keydown="props.listenWinEvents || handleVideoKeydown"
- @mousemove="handleVideoMousemove"
- class="videoPlay"
- :class="{ isHideController: !isShowController }"
- tabindex="-1"
- >
- <video class="videoPlayBox" @click="handlePlay" :id="videoId" preload="auto" playsinline webkit-playsinline></video>
- <div class="videoController" @click.stop>
- <div class="timeController">{{ `${formatTime(timeController.currentTime)} / ${formatTime(timeController.duration)}` }}</div>
- <el-slider
- class="sliderController"
- @change="handleTimeChange"
- @mousedown="handleSilderMousedown"
- v-model="timeController.currentTimeSilder"
- :max="timeController.duration"
- :format-tooltip="
- (value:number) => {
- return formatTime(value)
- }
- "
- />
- <div class="playController">
- <div class="leftPlayController">
- <img @click="handlePlay" :src="require(`./img/${playController.type === 'play' ? 'iconPause' : 'iconPlay'}.png`)" />
- <img @click="handleLoop" :src="require(`./img/${playController.loop ? 'iconLoopActive' : 'iconLoop'}.png`)" />
- <el-popover placement="top" trigger="click" :teleported="false" popper-class="palySpeedPopover">
- <template #reference>
- <img src="./img//iconSpeed.png" />
- </template>
- <div class="sliderSpeedCon">
- <img @click="handlePalySpeed(playController.speedStep)" src="./img/jia.png" />
- <el-slider
- class="sliderSpeed"
- @change="handlePalySpeedChange"
- v-model="playController.palySpeed"
- vertical
- :step="playController.speedStep"
- :max="playController.maxSpeed"
- :min="playController.minSpeed"
- :format-tooltip="(num:number) => {
- return num.toFixed(1)
- }"
- />
- <img @click="handlePalySpeed(-playController.speedStep)" src="./img/jian.png" />
- </div>
- </el-popover>
- </div>
- <div class="rightPlayController"></div>
- </div>
- </div>
- <slot></slot>
- </div>
- </template>
- <script setup lang="ts">
- import TCPlayer from "tcplayer.js"
- import "tcplayer.js/dist/tcplayer.min.css"
- import { onMounted, onUnmounted, ref, reactive } from "vue"
- import { UUID } from "@/libs/tools"
- import { formatTime } from "./tools"
- const props = defineProps<{
- listenWinEvents?: boolean
- }>()
- const emits = defineEmits<{
- (e: "ready"): void //播放器初始化完成
- (e: "ended"): void //播放结束
- }>()
- const videoId = "video" + UUID()
- let playerVm: Record<string, any>
- /* 时间控制器 */
- const timeController = reactive({
- currentTime: 0, // 当前时间
- duration: 0, // 总时长
- currentTimeSilder: 0,
- isDrag: false
- })
- /* 播放控制器 */
- const playController = reactive<{
- type: "play" | "pause"
- loop: boolean
- minSpeed: number
- maxSpeed: number
- speedStep: number
- palySpeed: number
- }>({
- type: "pause", //play|pause
- loop: false,
- minSpeed: 0.5,
- maxSpeed: 1.5,
- speedStep: 0.1,
- palySpeed: 1
- })
- /* 是否显示控制器 */
- const isShowController = ref(true)
- let _showTimer: any
- onMounted(() => {
- initVideo()
- if (props.listenWinEvents) {
- document.addEventListener("keydown", handleVideoKeydown)
- }
- })
- onUnmounted(() => {
- if (props.listenWinEvents) {
- document.removeEventListener("keydown", handleVideoKeydown)
- }
- playerVm?.dispose()
- })
- /**
- * 初始化播放器
- */
- function initVideo() {
- playerVm = TCPlayer(videoId, {
- controls: false,
- autoplay: true,
- loop: false
- })
- // 初始化完成
- playerVm.on("ready", () => {
- console.log("播放器初始化完成")
- emits("ready")
- })
- // 开始加载数据时
- playerVm.on("loadstart", () => {
- // 重新设置播放倍速 因为切换视频播放倍速会重置
- playerVm.playbackRate(playController.palySpeed)
- })
- //总时长变化时候
- playerVm.on("durationchange", () => {
- //duration和currentTime 时间向上取整Math.ceil
- timeController.duration = Math.ceil(playerVm.duration())
- })
- //当前播放时间变化
- playerVm.on("timeupdate", () => {
- //duration和currentTime 时间向上取整Math.ceil
- timeController.currentTime = Math.ceil(playerVm.currentTime())
- if (!timeController.isDrag) {
- timeController.currentTimeSilder = timeController.currentTime
- }
- })
- playerVm.on("play", () => {
- playController.type = "play"
- })
- playerVm.on("pause", () => {
- playController.type = "pause"
- })
- // 播放结束
- playerVm.on("ended", () => {
- emits("ended")
- })
- }
- /**
- * 播放 需要在ready之后调用
- */
- function playVideo({ src }: { src: string }) {
- playerVm?.src(src)
- showController()
- }
- /* 时间控制器 */
- function handleTimeChange(value: number | number[]) {
- playerVm.currentTime(value || 0)
- timeController.isDrag = false
- }
- function handleSilderMousedown() {
- timeController.isDrag = true
- }
- /* 播放控制器 */
- function handlePlay() {
- playController.type === "pause" ? playerVm.play() : playerVm.pause()
- showController()
- }
- // 暂停
- function pauseVideo() {
- playerVm.pause()
- showController()
- }
- function handleLoop() {
- playController.loop ? playerVm.loop(false) : playerVm.loop(true)
- playController.loop = playerVm.loop()
- }
- function handlePalySpeedChange(value: number | number[]) {
- playerVm.playbackRate(value)
- }
- function handlePalySpeed(value: number) {
- const palySpeed = parseFloat((playController.palySpeed + value).toFixed(1))
- if (palySpeed > playController.maxSpeed || palySpeed < playController.minSpeed) {
- return
- }
- playController.palySpeed = palySpeed
- handlePalySpeedChange(palySpeed)
- }
- /* 是否显示控制器 */
- function handleVideoKeydown(e: KeyboardEvent) {
- console.log("handleVideoKeydown")
- const key = e.key
- if (key === " ") {
- handlePlay()
- }
- }
- function handleVideoMousemove() {
- console.log("handleVideoMousemove")
- showController()
- }
- function showController() {
- isShowController.value = true
- _showTimer && clearTimeout(_showTimer)
- _showTimer = setTimeout(tryHideController, 3000)
- }
- function tryHideController() {
- if (playController.type === "play") {
- isShowController.value = false
- }
- }
- defineExpose({
- playVideo,
- pauseVideo
- })
- </script>
- <style lang="scss" scoped>
- .videoPlay {
- width: 100%;
- height: 100%;
- position: relative;
- overflow: hidden;
- :deep(.videoPlayBox) {
- &.tcp-skin .tcp-right-click-popup-menu {
- display: none;
- }
- }
- .videoPlayBox {
- width: 100%;
- height: 100%;
- }
- &.isHideController {
- cursor: none;
- .videoController {
- opacity: 0;
- transform: translateY(100%);
- }
- }
- .videoController {
- position: absolute;
- width: 100%;
- left: 0;
- bottom: 0;
- padding: 0 30px 15px;
- background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
- color: #fff;
- transition: all 0.5s;
- .timeController {
- font-weight: 500;
- font-size: 22px;
- color: #ffffff;
- line-height: 30px;
- }
- & > :deep(.sliderController.el-slider) {
- --el-slider-button-wrapper-offset: -12px;
- --el-slider-button-wrapper-size: 28px;
- --el-slider-button-size: 20px;
- --el-slider-height: 4px;
- --el-slider-border-radius: 2px;
- --el-slider-main-bg-color: #ff8057;
- --el-slider-runway-bg-color: rgba(255, 255, 255, 0.5);
- height: 28px;
- .el-slider__button {
- border: none;
- &:hover {
- transform: scale(1.1);
- }
- }
- }
- .playController {
- display: flex;
- justify-content: space-between;
- .leftPlayController {
- margin-left: -10px;
- display: flex;
- & > img {
- cursor: pointer;
- width: 48px;
- height: 48px;
- margin-right: 26px;
- }
- & > :deep(.palySpeedPopover.el-popover.el-popper) {
- min-width: initial;
- width: 65px !important;
- height: 293px;
- background: url("./img/bg.png") no-repeat;
- background-size: 100% 100%;
- box-shadow: none;
- border: none;
- padding: 15px 0 22px;
- .el-popper__arrow {
- display: none;
- }
- .sliderSpeedCon {
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- flex-direction: column;
- & > img {
- cursor: pointer;
- flex-shrink: 0;
- width: 33px;
- height: 35px;
- }
- .sliderSpeed.el-slider {
- flex-grow: 1;
- padding: 14px 0;
- --el-slider-button-wrapper-offset: -10px;
- --el-slider-button-wrapper-size: 26px;
- --el-slider-button-size: 18px;
- --el-slider-height: 6px;
- --el-slider-border-radius: 4px;
- --el-slider-main-bg-color: #ff8057;
- --el-slider-runway-bg-color: rgba(255, 255, 255, 0.5);
- .el-slider__button {
- border: none;
- &:hover {
- transform: scale(1.1);
- }
- }
- }
- }
- }
- }
- }
- }
- }
- </style>
|