H5纯原生播放器 【学习video】

这是一个纯原生的H5播放器,尽管网上有很多第三方库,但是基础打的牢固,一定会帮你走的更远。

大厂也非常重视基础,再说了那些第三方库也是基础一点点搭起来的,所以有兴趣学习的同学可以下载来学习。

代码中的细节我都写了注释了。

也非常欢迎大家进行拓展,有兴趣拓展功能,可以另起一个分支,最好写个文档,说明一下添加了哪些功能。

这是用H5的video标签做的视频播放器

只是学习用的,所以已有的功能可能存在问题,当然功能肯定是不完备的。

但作为学习H5的video,应该是可以的。

index.html是我一开始写播放器的代码

core是我把代码分开的后文件夹。

写好的功能有

  • 播放和暂停
  • 进度条动画,进度条拖拽
  • 播放时间
  • 音量调控
  • 倍数播放
  • 全屏

当然并不是这样就OK了,大家可以在已有的基础上进行添加,一方面可以锻炼自己的思维,另一方面可以磨练自己的代码阅读能力。

我想到的功能

  • 播放状态下,当鼠标悬停太久或移出播放器一段时间,控制器应该消失。
  • 网页全屏
  • 清晰度调节
  • 点击进度条跳转
  • 弹幕
  • 键盘事件,用键盘调整进度和音量

gitee

效果
在这里插入图片描述

如果不想下载文件的话,可以看核心代码
核心代码

// 传入视频资源路径,返回一个videoWrapperDOM
function createVideo({
    videoUrl,
    width,
    height,
    dragSvg = ""
}) {
    const videoWrapperDOM = initWrapperDOM(width, height);

    const video = document.createElement('video');
    video.classList.add("own-video")
    video.src = videoUrl;

    videoWrapperDOM.appendChild(video);


    function initWrapperDOM(width, height) { // 初始化外层DOM
        const videoWrapperDOM = document.createElement("div");
        videoWrapperDOM.classList.add("own-video-wrapper")
        videoWrapperDOM.style.width = width + "px";
        videoWrapperDOM.style.height = height + "px";
        return videoWrapperDOM;
    }

    let timeFrameId = null; // 播放时间展示动画

    const videoControl = {
        paused: true,//那个video自带的那个还不是好用
        videoDOM: null,
        play(playBtn) {
            this.videoDOM.play();
            updateTime();
            playBtn.classList.remove("pause");
            playBtn.classList.add("play");
            this.paused = false;
        },
        pause(playBtn) {
            this.videoDOM.pause();
            cancelAnimationFrame(timeFrameId);
            playBtn.classList.remove("play");
            playBtn.classList.add("pause");
            this.paused = true;
        },
        setVolume(v) {
            v = v < 0 ? 0 : v;
            this.videoDOM.volume = v;
        },
        w: 960, // 最开始播放器的大小
        h: 540,
        setVideoDOM(video, w, h) {
            // 看似很奇怪
            // 这个应该分一个模块,然后私有化,对外暴露方法,但作为练习我就没那么做了
            this.videoDOM = video;
            this.w = w;
            this.h = h;
            this.setVolume(.5);
        },
        setFullScreen(isFullScreen) {
            if (isFullScreen) {
                this.videoDOM.parentNode.classList.add("video-full-screen");
            } else {
                this.videoDOM.parentNode.classList.remove("video-full-screen");
            }
        }
    }
    videoControl.setVideoDOM(video, width, height);

    let playBtn = document.createElement("div");
    function createPlayBtn(video) {
        // 播放按钮
        playBtn.classList.add("btn", "play-btn", "pause");
        playBtn.addEventListener("click", function () {
            if (videoControl.paused) {
                // 视频状态暂停播放
                console.log("paused");
                videoControl.play(this);
            } else {
                console.log("play");
                videoControl.pause(this);
            }

        })
        return playBtn;
    }

    const current = document.createElement("span"); // 当前播放时间
    const total = document.createElement("span"); // 总时间
    function updateTime() {
        const totalTime = parseTime(video.duration);
        const currentTime = parseTime(video.currentTime);
        current.innerText = currentTime;
        total.innerText = totalTime;

        timeFrameId = requestAnimationFrame(updateTime);
    }
    function createTimeDisplay(video) {
        // 01:00 / 5:20
        const timeDisplayWrapper = document.createElement("div");
        timeDisplayWrapper.classList.add("time-wrapper")

        timeDisplayWrapper.appendChild(current);
        timeDisplayWrapper.appendChild(total);


        video.addEventListener("canplaythrough", function () {
            // 视频加载完成更新最新的播放时间
            const totalTime = parseTime(video.duration);
            const currentTime = parseTime(video.currentTime);
            current.innerText = currentTime;
            total.innerText = totalTime;
        })


        return timeDisplayWrapper

    }


    function exitFullscreen() {
        if (document.exitFullscreen) {
            document.exitFullscreen();
        } else if (document.msExitFullscreen) {
            document.msExitFullscreen();
        } else if (document.mozCancelFullScreen) {
            document.mozCancelFullScreen();
        } else if (document.webkitExitFullscreen) {
            document.webkitExitFullscreen();
        }
    }
    function createFullScreen() {
        const fullScreenWrapper = document.createElement("div");
        fullScreenWrapper.classList.add("full-screen-wrapper");
        const arr = ["全屏", "小屏"];
        fullScreenWrapper.innerText = arr[0];
        let status = "normal"; // 浏览器的一个状态 full normal
        document.addEventListener("fullscreenchange", function () {
            console.log(document.fullscreenElement);
            if (document.fullscreenElement !== null) {
                // 进入全屏模式
                status = "full";
            } else {
                status = 'normal';
                exitFullscreen();
                videoControl.setFullScreen(false);
                fullScreenWrapper.innerText = arr[0];
            }
        });
        fullScreenWrapper.addEventListener("click", function () {
            if (status === 'normal') {
                document.documentElement.requestFullscreen();
                videoControl.setFullScreen(true);
                this.innerText = arr[1];
            }
            if (status === 'full') {
                exitFullscreen();
                videoControl.setFullScreen(false);
                this.innerText = arr[0];
            }

        })

        return fullScreenWrapper;
    }

    function createVolumeWrapper(video) {
        const volumeWrapper = document.createElement('div');
        const volumeIco = document.createElement("div");
        const volumeBarWrapper = document.createElement('div');
        volumeWrapper.classList.add("volume-wrapper");
        volumeIco.classList.add("volume-ico");
        volumeBarWrapper.classList.add("volume-bar-wrapper");

        const bgBar = document.createElement("div");
        bgBar.classList.add("volume-bg-bar");
        const innerBar = document.createElement("div");
        innerBar.classList.add("volume-inner-bar");
        const circle = document.createElement("span");
        circle.classList.add("volume-circle");


        volumeBarWrapper.appendChild(bgBar);
        bgBar.appendChild(innerBar);
        bgBar.appendChild(circle);
        volumeWrapper.appendChild(volumeIco);
        volumeWrapper.appendChild(volumeBarWrapper);


        let clicked = false;
        let clientY, clickY;
        let barHeight = 0;
        circle.addEventListener("mousedown", function (e) {
            barHeight = bgBar.clientHeight; // 最大拖拽范围
            // 在父元素display:none,尺寸不准确
            clicked = true; // 代表可以进行拖拽了
            clickY = e.pageY
            clientY = innerBar.clientHeight;
        })
        volumeWrapper.addEventListener("mousemove", function (e) {
            if (!clicked) {
                return; // 点击之后才能进行拖拽
            }
            const moveY = clickY - e.pageY; // 这条公式要注意一下
            // 因为这个相比进度条拖拽它就是一个单向动画,只需要计算出当前的音量大小就好了
            let h = clientY + moveY;
            h = h < 0 ? 0 : h; // 边界处理
            h = h > barHeight ? barHeight : h;
            innerBar.style.height = h + "px";
            circle.style.bottom = h + "px";
            const v = h / barHeight; // 音量 
            // 为了可阅读性,我尽量没有都挤在一行写,这是一个人的职业素养
            videoControl.setVolume(v);

        })
        const stopDragVolumeBar = function () {
            if (clicked) {
                // 正在拖拽
                clicked = false; // 取消拖拽状态
            }
        }
        document.addEventListener("mouseup", stopDragVolumeBar)
        volumeWrapper.addEventListener("mouseleave", stopDragVolumeBar);


        return volumeWrapper;
    }



    function createDragBar(video, dragSvg) {
        // 播放进度条
        const barWrapper = document.createElement("div");
        barWrapper.classList.add("bar-wrapper");
        const innerBar = document.createElement("div");
        innerBar.classList.add("inner-bar");
        barWrapper.appendChild(innerBar);
        const circle = document.createElement("span");
        circle.classList.add("circle");
        if (dragSvg) {
            // 自定义svg拖拽图标
            circle.innerHTML = dragSvg;
            circle.classList.add("svg-circle")
        } else {
            circle.classList.add("normal");
        }

        window.circle = circle;
        barWrapper.appendChild(circle);
        let frameId = null; // 拖拽的时候需要取消动画


        // 拖拽功能
        // 我需要知道总的进度条的长度
        // 还需要知道拖动的距离
        // 拖动的距离 / 总的进度条长度 = currentTime / totalTime

        let totalWidth = 0;
        let totalTime = 0;
        video.addEventListener("canplaythrough", function () {
            // 视频资源加载完成
            totalTime = this.duration;
            let clicked = false;
            let clientX, clickX;
            circle.addEventListener("mousedown", function (e) {
                // 如果获取元素属性为最开始,那么全屏缩放会出现BUG
                totalWidth = barWrapper.offsetWidth; // 为了适应元素的最新大小
                // 暂停播放
                // 点击时记住点击瞬间的位置
                cancelAnimationFrame(frameId); // 取消播放动画 优化性能减少不必要的麻烦
                // 不然你操控不了元素
                videoControl.pause(playBtn);
                clientX = this.offsetLeft;
                clickX = e.pageX;
                clicked = true; // 进入拖拽状态

            })
            document.addEventListener("mousemove", function (e) {
                if (!clicked) {
                    return; // 非拖拽状态
                }
                cancelAnimationFrame(frameId);
                let moveX = e.pageX - clickX;
                let per = (clientX + moveX) / totalWidth;
                per = per < 0 ? 0 : per; // 做边界处理
                per = per > 1 ? 1 : per;
                per = per * 100 + "%";
                circle.style.left = per;
                innerBar.style.width = per;
            })
            document.addEventListener("mouseup", function (e) {
                if (clicked) {
                    clicked = false;
                    let moveX = e.pageX - clickX;
                    let per = (clientX + moveX) / totalWidth;
                    video.currentTime = getCurrentTime(video, per);
                    per = per > 1 ? 1 : per;
                    per = per < 0 ? 0 : per;
                    per = per * 100 + "%";
                    circle.style.left = per;
                    innerBar.style.width = per;
                    videoControl.play(playBtn);
                    barAnimate();
                }

            })
        })


        function barAnimate() {
            const per = getPercentage(video) * 100 + "%";

            circle.style.left = per;
            innerBar.style.width = per;
            frameId = requestAnimationFrame(barAnimate);
        }
        barAnimate();

        return barWrapper;
    }



    function createPlayRate(video) {
        // 倍速播放
        const rateWrapper = document.createElement("div");
        rateWrapper.classList.add("rate-wrapper");
        const currentRateWrapper = document.createElement('div');

        currentRateWrapper.innerText = "1X";
        const ul = document.createElement("ul");
        ul.classList.add("rateSelectorWrapper");
        ul.innerHTML = `
            <li>2X</li>
            <li>1.5X</li>
            <li>1X</li>
            <li>.5X</li>    
        `
        ul.addEventListener("click", function (e) {
            if (e.target.tagName === "LI") {
                currentRateWrapper.innerText = e.target.innerText;
                const rate = parseFloat(e.target.innerText);
                video.playbackRate = rate;
            }
        })
        rateWrapper.appendChild(currentRateWrapper);
        rateWrapper.appendChild(ul);
        return rateWrapper;

    }

    function initBar() {
        const bar = document.createElement("div");
        bar.classList.add("own-video-control-bar");
        return bar;
    }


    const bar = initBar();
    const timeDisplayWrapper = createTimeDisplay(video);
    const dragBar = createDragBar(video, dragSvg);
    const rateWrapper = createPlayRate(video);

    // 为了将右边的按钮聚起来而已,我觉得那样会更好看
    const container = document.createElement("div");
    container.classList.add("video-btn-container");
    container.appendChild(rateWrapper);
    container.appendChild(createVolumeWrapper(video));
    container.appendChild(createFullScreen());


    bar.appendChild(createPlayBtn(video));
    bar.appendChild(timeDisplayWrapper);
    bar.appendChild(dragBar);
    bar.appendChild(container);
    
    videoWrapperDOM.appendChild(bar);

    return videoWrapperDOM;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的Vue原生兼容安卓和H5的短视频滑动播放器的实现: 1. 首先需要安装 `vue-touch` 插件,它可以让我们方便地处理触摸事件。可以使用以下命令安装: ``` npm install vue-touch ``` 2. 在Vue组件中引入 `vue-touch` 插件: ```javascript import VueTouch from 'vue-touch'; Vue.use(VueTouch); ``` 3. 定义一个 `SwipePlayer` 组件,该组件包含一个视频播放器和一个滑动条: ```html <template> <div class="swipe-player"> <video :src="source" ref="videoRef"></video> <div ref="sliderRef" class="slider"></div> </div> </template> ``` 4. 在组件的 `mounted` 方法中,初始化滑动条,并添加滑动事件处理程序: ```javascript mounted() { // 初始化滑动条 const slider = this.$refs.sliderRef; slider.style.width = '0%'; // 添加滑动事件处理程序 this.$swipeRight(this.handleSwipeRight); this.$swipeLeft(this.handleSwipeLeft); }, methods: { // 处理向右滑动事件 handleSwipeRight() { const video = this.$refs.videoRef; const slider = this.$refs.sliderRef; // 获取当前视频播放进度 const currentTime = video.currentTime; const duration = video.duration; // 计算新的播放进度 const newTime = Math.max(currentTime - 5, 0); const percent = (newTime / duration) * 100; // 更新视频播放进度和滑动条 video.currentTime = newTime; slider.style.width = `${percent}%`; }, // 处理向左滑动事件 handleSwipeLeft() { const video = this.$refs.videoRef; const slider = this.$refs.sliderRef; // 获取当前视频播放进度 const currentTime = video.currentTime; const duration = video.duration; // 计算新的播放进度 const newTime = Math.min(currentTime + 5, duration); const percent = (newTime / duration) * 100; // 更新视频播放进度和滑动条 video.currentTime = newTime; slider.style.width = `${percent}%`; } } ``` 5. 在 `data` 中定义视频的源地址: ```javascript data() { return { source: 'http://example.com/video.mp4' }; } ``` 6. 最后,样式可以根据需要进行自定义: ```css .swipe-player { position: relative; width: 100%; height: 300px; } .swipe-player video { width: 100%; height: 100%; object-fit: cover; } .swipe-player .slider { position: absolute; bottom: 0; left: 0; width: 100%; height: 5px; background-color: #ddd; z-index: 1; } ``` 这样就完成了一个简单的Vue原生兼容安卓和H5的短视频滑动播放器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值