123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- <!--
- * @FileDescription: 播放器
- * @Author: 黄琪勇
- * @Date:2024-04-03 18:30:09
- -->
- <template>
- <div
- @keydown="handleVideoKeydown"
- @mousemove="handleVideoMousemove"
- @click="handleVideoClick"
- class="videoPlay"
- :class="{ isHideController: !isShowController }"
- tabindex="-1"
- >
- <video class="videoPlayBox" :id="videoId" preload="auto" playsinline webkit-playsinline></video>
- <div class="videoController" @click.stop @touchstart.stop>
- <div class="sliderSection">
- <div class="timeController">{{ `${formatTime(timeController.currentTime)} / ${formatTime(timeController.duration)}` }}</div>
- <n-slider
- class="sliderController"
- :keyboard="false"
- :value="timeController.currentTimeSilder"
- :tooltip="isShowController"
- :step="0.01"
- @update:value="handleSilderChange"
- :on-dragend="handleTimeChange"
- :max="timeController.duration"
- :format-tooltip="(value:number) => {
- return formatTime(value)
- }"
- />
- </div>
- <div class="playController">
- <div class="leftPlayController">
- <img @click="handlePlay" :src="require(`./img/${playController.type === 'play' ? 'iconPause' : 'iconPlay'}.png`)" />
- <img @click="handleLoop" class="loopImg" :src="require(`./img/${playController.loop ? 'iconLoopActive' : 'iconLoop'}.png`)" />
- <img ref="btnSpendDom" src="./img//iconSpeed.png" />
- <el-popover
- @show="handlePopoverTimeHide"
- ref="popoverSpendDom"
- :virtual-ref="btnSpendDom"
- trigger="click"
- placement="top"
- :teleported="false"
- popper-class="palySpeedPopover"
- virtual-triggering
- >
- <div class="sliderSpeedCon">
- <img @click="handlePalySpeed(playController.speedStep)" src="./img/jia.png" />
- <n-slider
- class="sliderSpeed"
- :tooltip="false"
- :keyboard="false"
- :value="playController.palySpeed"
- @update:value="handlePalySpeedChange"
- vertical
- :step="playController.speedStep"
- :max="playController.maxSpeed"
- :min="playController.minSpeed"
- >
- <template #thumb>
- <div class="thumb">{{ playController.palySpeed.toFixed(1) + "X" }}</div>
- </template>
- </n-slider>
- <img @click="handlePalySpeed(-playController.speedStep)" src="./img/jian.png" />
- </div>
- </el-popover>
- </div>
- <div class="rightPlayController">
- <div class="videoName">
- <!-- <div>{{ videoName || "" }}</div> -->
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import TCPlayer from "tcplayer.js"
- import "tcplayer.js/dist/tcplayer.min.css"
- import { onMounted, onUnmounted, ref, reactive, watch, toRef, watchEffect } from "vue"
- import { UUID } from "@/libs/tools"
- import { formatTime } from "./tools"
- import { NSlider } from "naive-ui"
- import { ElMessage } from "element-plus"
- const props = defineProps<{
- disableEvents?: boolean
- isShowController?: boolean
- autoPlay?: boolean
- }>()
- const emits = defineEmits<{
- (e: "ready"): void //播放器初始化完成
- (e: "ended"): void //播放结束
- (e: "playbackRate"): void //播放速度改动时候
- }>()
- const videoId = "video" + UUID()
- let playerVm: Record<string, any>
- let isReady = false // 是否初始化播放器播放器
- const videoName = ref("")
- /* 时间控制器 */
- const timeController = reactive({
- currentTime: 0, // 当前时间
- duration: 0, // 总时长
- currentTimeSilder: 0,
- isDrag: false
- })
- /* 播放控制器 */
- const btnSpendDom = ref()
- const popoverSpendDom = ref()
- let _popoverSpendTime: any
- // 定时隐藏
- function handlePopoverTimeHide() {
- _popoverSpendTime && clearTimeout(_popoverSpendTime)
- _popoverSpendTime = setTimeout(() => {
- popoverSpendDom.value?.hide()
- }, 5000)
- }
- 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
- })
- watch(
- () => playController.palySpeed,
- () => {
- // 值变化 重新计算隐藏时间
- handlePopoverTimeHide()
- }
- )
- /* 是否显示控制器 */
- const isShowController = ref(true)
- watchEffect(() => {
- isShowController.value = props.isShowController
- })
- let _showTimer: any
- onMounted(() => {
- initVideo()
- })
- onUnmounted(() => {
- playerVm?.dispose()
- })
- /**
- * 初始化播放器
- */
- function initVideo() {
- playerVm = TCPlayer(videoId, {
- controls: false,
- //autoplay: true, // 自动播放前1秒暂停不了,改为loadedmetadata调用播放 实现自动播放
- loop: false
- })
- // 初始化完成
- playerVm.ready(() => {
- console.log("播放器初始化完成")
- isReady = true
- emits("ready")
- })
- // 开始加载数据时
- playerVm.on("loadstart", () => {
- // 重新设置播放倍速 因为切换视频播放倍速会重置
- playerVm.playbackRate(playController.palySpeed)
- })
- playerVm.on("loadedmetadata", () => {
- console.log("loadedmetadata")
- if (props.autoPlay) {
- playerVm.play()
- }
- })
- //总时长变化时候
- playerVm.on("durationchange", () => {
- timeController.duration = playerVm.duration()
- })
- //当前播放时间变化
- playerVm.on("timeupdate", () => {
- timeController.currentTime = 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之后调用
- */
- // 接口请求可能在播放之前 所以这里等待播放器初始化
- let _time: any
- function playVideo({ src, name }: { src: string; name: string }) {
- videoName.value = name
- _time && clearInterval(_time)
- if (isReady) {
- handlePlayVideo(src)
- } else {
- _time = setInterval(() => {
- if (isReady) {
- clearInterval(_time)
- handlePlayVideo(src)
- }
- }, 60)
- }
- }
- function handlePlayVideo(src: string) {
- playerVm?.src(src)
- showController()
- }
- /* 时间控制器 */
- function handleTimeChange(value?: number) {
- playerVm.currentTime(value || timeController.currentTimeSilder)
- timeController.isDrag = false
- }
- function handleSilderChange(value: number) {
- timeController.isDrag = true
- timeController.currentTimeSilder = value
- }
- // 快进或者快退
- function speedCurrentTime(type: "fast" | "slow") {
- handleTimeChange(timeController.currentTime + (type === "fast" ? 5 : -5))
- showController()
- }
- /* 播放控制器 */
- function handlePlay() {
- playController.type === "pause" ? playerVm.play() : playerVm.pause()
- showController()
- }
- // 暂停
- function pauseVideo() {
- playerVm.pause()
- showController()
- }
- // 循环播放
- let message: any = null
- function handleLoop() {
- playController.loop ? playerVm.loop(false) : playerVm.loop(true)
- playController.loop = playerVm.loop()
- if (playController.loop) {
- if (message) {
- message.close()
- }
- message = ElMessage({
- message: "已打开循环播放",
- icon: "",
- customClass: "defaultMessage"
- })
- } else {
- if (message) {
- message.close()
- }
- message = ElMessage({
- message: "已关闭循环播放",
- icon: "",
- customClass: "defaultMessage"
- })
- }
- }
- // 播放速度
- function handlePalySpeedChange(value: number) {
- playController.palySpeed = value
- playerVm.playbackRate(value)
- emits("playbackRate")
- }
- function handlePalySpeed(value: number) {
- const palySpeed = parseFloat((playController.palySpeed + value).toFixed(1))
- if (palySpeed > playController.maxSpeed || palySpeed < playController.minSpeed) {
- return
- }
- handlePalySpeedChange(palySpeed)
- }
- function handleVideoClick() {
- if (props.disableEvents) return
- handlePlay()
- }
- function handleVideoKeydown(e: KeyboardEvent) {
- if (props.disableEvents) return
- const key = e.key
- if (key === " ") {
- handlePlay()
- } else if (key === "ArrowLeft") {
- speedCurrentTime("slow")
- } else if (key === "ArrowRight") {
- speedCurrentTime("fast")
- }
- }
- function handleVideoMousemove() {
- if (props.disableEvents) return
- showController()
- }
- /* 是否显示控制器 */
- function showController() {
- if (props.disableEvents) return
- isShowController.value = true
- _showTimer && clearTimeout(_showTimer)
- _showTimer = setTimeout(tryHideController, 3000)
- }
- function tryHideController() {
- if (playController.type === "play") {
- isShowController.value = false
- }
- }
- defineExpose({
- playVideo,
- pauseVideo,
- handlePlay,
- speedCurrentTime,
- playType: toRef(playController, "type")
- })
- </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: 45px 32px 22px;
- background: linear-gradient(0deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0) 100%);
- color: #fff;
- transition: all 0.5s;
- // &:hover { //取消鼠标移入不隐藏
- // cursor: initial;
- // opacity: initial !important;
- // transform: initial !important;
- // }
- .sliderSection {
- display: flex;
- align-items: center;
- & > :deep(.sliderController.n-slider) {
- --n-rail-color: rgba(255, 255, 255, 0.5) !important;
- --n-fill-color: #ff8057 !important;
- --n-fill-color-hover: #ff8057 !important;
- }
- }
- .timeController {
- font-weight: 500;
- font-size: 20px;
- color: #ffffff;
- line-height: 30px;
- flex-shrink: 0;
- margin-right: 16px;
- }
- .playController {
- display: flex;
- justify-content: space-between;
- padding-top: 4px;
- .leftPlayController {
- margin-left: -16px;
- display: flex;
- & > img {
- cursor: pointer;
- width: 48px;
- height: 48px;
- margin-right: 30px;
- padding: 6px;
- box-sizing: content-box;
- &:hover {
- background-color: rgba(255, 255, 255, 0.2);
- border-radius: 6px;
- }
- }
- .loopImg {
- width: 56px;
- }
- & > :deep(.palySpeedPopover.el-popover.el-popper) {
- min-width: initial;
- width: 59px !important;
- height: 264px;
- background: url("./img/bg.png") no-repeat;
- background-size: 100% 100%;
- box-shadow: none;
- border: none;
- padding: 12px 0 20px;
- .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: 30px;
- height: 31px;
- }
- .sliderSpeed.n-slider {
- flex-grow: 1;
- padding: 6px 0;
- --n-rail-width-vertical: 5px !important;
- --n-rail-color: rgba(255, 255, 255, 0.5) !important;
- --n-fill-color: #ff8057 !important;
- --n-fill-color-hover: #ff8057 !important;
- .thumb {
- height: 22px;
- padding: 0 6px;
- background: #ffffff;
- box-shadow: 0px 2px 4px 0px rgba(102, 102, 102, 0.77);
- border-radius: 11px;
- text-align: center;
- line-height: 22px;
- font-weight: 500;
- font-size: 15px;
- color: #ff8057;
- }
- }
- }
- }
- }
- .rightPlayController {
- display: flex;
- align-items: center;
- .videoName {
- font-weight: 500;
- font-size: 20px;
- color: #ffffff;
- }
- }
- }
- }
- }
- </style>
|