基于vue3开发的视频播放器组件

106 阅读1分钟

<template>
    <div class="videoPlayer" ref="videoPlayer" @contextmenu.prevent="" @mouseleave="onMouseleave"
        @mousemove="onMousemove">
        <video class="fl_video" ref="flVideo" @mouseenter="onMouseenter" @canplay="getVidDur">
            <source :src="Url" type='video/mp4'>
            <source :src="Url" type='video/ogg'>
        </video>
        <div class="control" v-if="state.isController">
            <div class="play" @click="onPlay">
                <img v-if="state.isPlay" src="@/assets/img/new/video_suspend.png" alt="">
                <img v-else src="@/assets/img/new/video_play.png" alt="">
            </div>
            <div class="progress">
                <div class="slider-demo-block">
                    <el-slider v-model="state.progressValue" :show-tooltip="false" @change="handleChangeProgress" />
                </div>
            </div>
            <div class="right">
                <div class="time">
                    <span>{{state.currentTime}}</span><em>/</em><span>{{state.totalDuration}}</span>
                </div>
                <div class="volume">
                    <img v-if="state.isMute" src="@/assets/img/new/video_volume.png" alt="" @click="onVolume">
                    <img v-else src="@/assets/img/new/video_mute.png" alt="" @click="onVolume">
                    <div class="volume_slider">
                        <el-slider v-model="state.volume" vertical height="100px" @change="onChangeVolume" />
                    </div>
                </div>
                <div class="fullscreen" @click="onFullscreen">
                    <img v-if="state.isFullscreen" src="@/assets/img/new/video_quit_fullscreen.png" alt="" srcset="">
                    <img v-else src="@/assets/img/new/video_fullscreen.png" alt="" srcset="">
                </div>
                <slot></slot>
            </div>
        </div>
    </div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref, toRefs } from "vue";
import XEUtils from "xe-utils";

type TProps = {
    Url: string
}

type TState = {
    videoUrl: string
    volume: number
    isVolume: boolean
    isMute: boolean
    isPlay: boolean
    isFullscreen: boolean
    totalDuration: string
    currentTime: string
    timer: number
    progressValue: number
    percent: number
    moveTimer: number
    isController: boolean
    mouseCoord: number
    lastvalue: number
    globalTimer: number
}

const state = reactive<TState>({
    videoUrl: '',
    volume: 50,
    isVolume: false,
    isMute: true,
    isPlay: false,
    isFullscreen: false,
    totalDuration: '',
    currentTime: '00:00',
    timer: 0,
    progressValue: 0,
    percent: 0,
    moveTimer: 0,
    isController: true,
    mouseCoord: 0,
    lastvalue: 0,
    globalTimer: 0
})


const videoPlayer = ref()
const flVideo = ref()

const emit = defineEmits(['played'])


onMounted(() => {
    flVideo.value.volume = 0.5
    loopTimer()
    resizeListener()
})
const props = withDefaults(defineProps<TProps>(), {})
const Props = reactive(props)
const { Url } = toRefs(Props)

// 函数

const getVidDur = () => {
    getDuration()
}
//鼠标移入
const onMouseenter = (e: MouseEvent) => {
    state.isController = true
    clearTimeout(state.moveTimer)
}
//鼠标移出
const onMouseleave = () => {
    state.moveTimer = setTimeout(() => {
        state.isController = false
    }, 2000)
}
//循环监听鼠标事件
const loopTimer = () => {
    state.globalTimer = setInterval(() => {
        if (state.lastvalue == state.mouseCoord) {
            state.isController = false
        }
        state.lastvalue = state.mouseCoord
    }, 3000)
}

//鼠标移动事件
const onMousemove = XEUtils.throttle((e: MouseEvent) => {
    state.mouseCoord = e.pageX
    state.isController = true
}, 300)


//是否全屏
const onFullscreen = () => {
    fullScreenToggle()
    /* videoPlayer.value.requestFullscreen()
    state.isFullscreen = true
    if (!state.isFullscreen) {
        videoPlayer.value.requestFullscreen()
        state.isFullscreen = true
    } else {
        //document.webkitCancelFullScreen()
        const doc: any = document
        doc.webkitCancelFullScreen()
        state.isFullscreen = false
    } */
}
//
const resizeListener = () => {
    window.addEventListener('resize', function () {
        if (!isFullScreen()) {
            if (!isFullScreen()) {
                state.isFullscreen = false
            }
        }
    })
}

//是否静音
const onVolume = () => {
    if (state.isMute) {
        flVideo.value.muted = true
        state.isMute = false
    } else {
        flVideo.value.muted = false
        state.isMute = true
    }
}

//播放/暂停
const onPlay = () => {
    if (!state.isPlay) {
        flVideo.value.play()
        state.isPlay = true
        state.timer = setInterval(() => {
            handleCurrentTime()
            if (state.progressValue === 100) {
                clearInterval(state.timer)
                flVideo.value.pause()
                state.isPlay = false
                handlePlayedCallback()
            }
        }, 1000)
    } else {
        flVideo.value.pause()
        state.isPlay = false
        clearInterval(state.timer)
    }
}

//设置音量
const onChangeVolume = (val: number) => {
    state.volume = val
    flVideo.value.volume = state.volume / 100
}
//获取总时长
const getDuration = () => {
    if (isNaN(flVideo.value.duration)) {
        state.totalDuration = secondTurnTime(0)
    } else {
        state.totalDuration = secondTurnTime(flVideo.value.duration)
    }
}

//处理播放的当前时间
const handleCurrentTime = () => {
    state.currentTime = secondTurnTime(flVideo.value.currentTime)
    state.progressValue = (flVideo.value.currentTime / flVideo.value.duration) * 100
}

//进度条拖动
const handleChangeProgress = () => {
    flVideo.value.currentTime = (state.progressValue / 100) * flVideo.value.duration
    state.currentTime = secondTurnTime((state.progressValue / 100) * flVideo.value.duration)
}

//处理视频播放完成的回调函数
const handlePlayedCallback = () => {
    emit('played')
}

//==工具函数==

//秒转成时间
const secondTurnTime = (s: number) => {
    let secondTime = Math.trunc(s)
    let min = 0// 初始化分
    let h = 0// 初始化小时
    let result = ''
    if (secondTime >= 60) {//如果秒数大于等于60,将秒数转换成整数
        min = Math.trunc(secondTime / 60)//获取分钟,除以60取整数,得到整数分钟
        secondTime = Math.trunc(secondTime % 60)//获取秒数,秒数取佘,得到整数秒数
        if (min >= 60) {//如果分钟大于等于60,将分钟转换成小时
            h = Math.trunc(min / 60)//获取小时,获取分钟除以60,得到整数小时
            min = Math.trunc(min % 60) //获取小时后取佘的分,获取分钟除以60取佘的分
        }
    }
    if (h === 0) {
        result = `${min.toString().padStart(2, '0')}:${secondTime.toString().padStart(2, '0')}`
    } else {
        result = `${h.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}:${secondTime.toString().padStart(2, '0')}`
    }
    return result
}

const fullScreen = () => {
    const rfs = videoPlayer.value.requestFullScreen || videoPlayer.value.webkitRequestFullScreen || videoPlayer.value.mozRequestFullScreen || videoPlayer.value.msRequestFullscreen;
    if (typeof rfs !== 'undefined' && rfs) {
        rfs.call(videoPlayer.value);
    }
}

const exitFullScreen = () => {
    let doc: any = document
    if (doc.exitFullscreen) {
        doc.exitFullscreen();
    } else if (doc.mozCancelFullScreen) {
        doc.mozCancelFullScreen();
    } else if (doc.webkitCancelFullScreen) {
        doc.webkitCancelFullScreen();
    } else if (doc.msExitFullscreen) {
        doc.msExitFullscreen();
    }
}

const isFullScreen = () => {
    let doc: any = document
    return doc.isFullScreen || doc.mozIsFullScreen || doc.webkitIsFullScreen
}

const fullScreenToggle = () => {
    if (state.isFullscreen) {
        exitFullScreen()
        state.isFullscreen = false
    } else {
        fullScreen()
        state.isFullscreen = true
    }
}


onUnmounted(() => {
    console.log('销毁');
    clearTimeout(state.globalTimer)
})

</script>

<style lang="scss" scoped>
.videoPlayer {
    width: 1209px;
    height: 690px;
    position: relative;

    .fl_video {
        background-color: #c5c4c4;
        width: 100%;
        height: 100%;
        position: absolute;
        z-index: 0;
    }

    .control {
        width: 94%;
        height: 56px;
        position: absolute;
        bottom: 30px;
        left: 3%;
        z-index: 10;
        background: rgba(0, 0, 0, .4);
        border-radius: 48px;
        display: flex;
        align-items: center;
        padding: 10px 20px;

        .play {
            img {
                height: 25px;
            }
        }

        .progress {
            width: 100%;
            margin: 0 20px;
            display: flex;
            align-items: center;

            .slider-demo-block {
                width: 100%;

                .el-slider {
                    width: 100%;
                }
            }

            .total_progress {
                position: relative;
                width: 100%;
                height: 6px;
                border-radius: 3px;
                background-color: #A5A5A5;
                z-index: 100;

                .buffer_progress {
                    position: absolute;
                    width: 50%;
                    height: 6px;
                    border-radius: 3px;
                    background-color: #CBCBCB;
                    z-index: 101;
                }

                .played_progress {
                    position: absolute;
                    width: 20%;
                    height: 6px;
                    border-radius: 3px;
                    background-color: #5182FF;
                    z-index: 102;
                }

                img {
                    position: absolute;
                    left: 20%;
                    top: -6px;
                    z-index: 103;
                    transform: translate(-50%);
                }
            }
        }





        .right {
            display: flex;
            flex-direction: row;
            align-items: center;
            margin-left: auto;

            .time {
                display: flex;
                flex-direction: row;
                align-items: center;
                margin-left: 16px;

                em {
                    font-size: 16px;
                    color: #FFFFFF;
                    padding: 0 4px;
                }

                span {
                    font-size: 13px;
                    color: #FFFFFF;
                }
            }

            .volume {
                margin-left: 16px;
                position: relative;

                .volume_slider {
                    position: absolute;
                    bottom: 0;
                    left: -3px;
                    display: none;
                    margin-bottom: 36px;

                    :deep(.el-slider__button) {
                        border: solid 2px #A5A5A5
                    }
                }

                img {
                    width: 32px;
                    height: 32px;
                }

                &:hover {
                    .volume_slider {
                        display: block;
                    }
                }
            }

            .fullscreen {
                margin-left: 16px;

                img {
                    width: 26px;
                    height: 26px;
                }
            }
        }

    }

}

video::-webkit-media-controls {
    display: none;
}
</style>