• 鼠标悬浮在进度条上时视频预览的实现


    CSDN话题挑战赛第2期
    参赛话题:前端技术分享


    ✨✨✨前言

    这是我模仿B站所写项目遇到的第二个很有挑战性的部分,网上的资料比较缺乏,还是大佬室友换了个关键词才搜到比较有用的资料hh。

    之前没有写过视频相关的项目,所以视频相关的API都十分陌生。从最开始的一无所知,一步步查资料,理解代码,进行修改,解决代码,最后实现的时候感觉非常有成就感。

    网上能找到的相关插件是用angular.js实现(现在基本被vue,react替代),跟我使用技术栈冲突,无法直接引用插件,而且很多技术细节没有文字解释,阅读比较困难。所以写下这篇博客,方便自己重看代码,同时也希望能帮到在实现这一功能受阻的程序猿们o( ̄▽ ̄)ブ



    ✨✨✨前置准备

    • 对VUE,vite框架,hls.js插件有基本的掌握
    • 对同步异步,promise有基本的了解
    • 熟悉vue3-video-play源代码 以及video相关API
    • 了解canvas画布的使用


    ✨✨✨概述


    整个过程大致可分成5步:


    1.预处理,创建一个video标签,视频源和当前视频源一致,并监听canplay事件

    2.将整个视频划成若干段,对于每一段,canplay事件触发时,创建一个canvas画布,并修改video的currentTime

    3.利用监听函数和promise函数,保证视频截图成功绘制到canvas画布上

    4.将canvas转为blob,并用一个数组将其存下来,预处理完成

    5.当鼠标悬浮在进度条上时,根据其位置展示对应缩略图


    ✨✨✨实现过程及代码

    一、

    本来是想在函数里创造video标签的,但我将hls.js插件初始化设计在Mounted时期,所以就直接在HTML中添加了。

    <video id="Vvideo" ref="Vvideo" src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8" controls="true"
          preload="auto" crossOrigin="anonymous" width="400" height="200" v-show="false">
          您的浏览器不支持 video 标签。
    video>
    

    ngx-thumbnail-video插件监听的是loadeddata,但我尝试的时候发现这样canvas只绘制出黑屏,所以我监听canplay事件,但canplay事件在视频准备好后会一直触发,这好解决,只需要在触发后立刻移除监听函数就可。

    const preloadVideoThumbs = () => {
      state.Vvideo.addEventListener('canplay', setFrontEndPreload);
    }
    '
    运行
    const setFrontEndPreload = async () => {
      state.Vvideo.removeEventListener('canplay', setFrontEndPreload);
      // 接下来代码见后文二~四
    }
    '
    运行

    二、

    根据自己的需求设置间隔,不过太长太短都不好,太长会导致用户点击后与预览图很可能不一致,太短会导致图片过多从而占用内存较大,我这里设置的3

    for (let i = 0; i <= state.Vvideo.duration; i += 3) {
      let canvas = document.createElement("canvas");
      const context = canvas.getContext('2d');
      state.Vvideo.currentTime = i;
      //接下来代码见后文三
    }
    
    

    三、

    这里需要等到 currentTime 对应帧 canplay 后再进行绘制,否则会导致黑屏。但addEventListener是异步操作,等到事件触发时currentTime 就变为最后一个循环的 currentTime ,这显然不是我们想要的。
    namo,闭包能解决问题吗?答案是不能,闭包就无法避免currentTime 设置后立刻绘制。

    namo 我们能否强制让 addEventListener变成同步呢,promise能解决这个问题.
    new promise本身是同步的,只有当遇到resolve / reject 时才会结束,否则将一直阻塞,我们可以将resolve 函数设置在被监听事件的末尾,这样就能达到目的了


    await new Promise(function (rsv) {
      const event = function () {
        context?.drawImage(state.Vvideo, 0, 0, 300, 150);
        //代码见后文四
        state.Vvideo.removeEventListener('canplay', event);
        rsv(null);
      };
      state.Vvideo.addEventListener('canplay', event);
    
    });
    

    四、

    将绘制出来的canvas转为img图片有两种,toDataURL和toBlob两种,网上实现视频预览这一部分大多都是前者,但我查到的资料了解到toBlob更优,详见博客链接:
    https://qa.1r1g.com/sf/ask/3855198931/


    canvas.toBlob(function (blob) {
      state.Thumbnails.push(URL.createObjectURL(blob as Blob));
    }, "image/jpeg");
    

    五、

    若当前帧已经加载出来,则显示当前帧,否则显示loading。namo如何判断当前帧是否加载完成呢?
    我们可以将当前帧应该在数组中位置与数组大小进行比较,若小于,则加载完成。


    <img class="d-slider__img"  ref="refImg"   :style="{ left: state.hoverImgLeft }"
    :src="state.imgIndex" 
    alt="图片加载失败">
    

    细节处理,防止缩略图显示在边框之外

    const mousemoveHandle = (ev: MouseEvent) => {
      if (!props.hover) return;
      let val = getPosition(ev);
      emits('onMousemove', ev, val);
      state.hoverPosition = val;
      if (props.vertical) return;
      state.imgIndex=(val*props.totalTimeNumber);
      state.imgIndex/=3;
      state.imgIndex=~~state.imgIndex;
      //获取dom
      let refSliderEl = (refSlider.value as HTMLButtonElement);
      // 提示宽的一半宽度
      let refImgWidth = (refImg.value as HTMLButtonElement).clientWidth / 2;
      let movePositon = ev.clientX - refSliderEl.getBoundingClientRect().left;
      // 如果当前往左的偏移量大于提示框宽度
      if (movePositon < refImgWidth) {
        state.hoverImgLeft = (refImgWidth - movePositon) + 'px'
      } else if ((refSliderEl.clientWidth - movePositon) < refImgWidth) {
        // 如果当前往右的偏移量大于提示框宽度  (总宽度-当前移动位置)< Img一半的宽度
        state.hoverImgLeft = (refSliderEl.clientWidth - movePositon) - refImgWidth + 'px'
      } else {
        state.hoverImgLeft = '50%'
      }
    
    }
    
    



    ✨✨✨效果展示

    截图

    在这里插入图片描述


    在这里插入图片描述


    视频(加载有些小满,请稍等一下~)




    ✨✨✨资料源

    vue3-video-play:https://blog.csdn.net/xdlumia/article/details/11986500


    ngx-thumbnail-videohttps://levelup.gitconnected.com/build-youtube-like-stylish-video-player-with-thumbnail-preview-on-progress-bar-hovered-53b9074acd75



    ✨✨✨github链接

    想要看完整版代码以及页面效果,请移步github哦
    https://github.com/Ki-Wi-Berry/bilibili-videos


    ⛄码字不易,如果觉得对您有帮助的话,麻烦点个免费的赞~⛄

  • 相关阅读:
    食品级接触材料的检测标准有哪些?
    Node.js详解(--模块内容详解(同步与异步,fs,url))
    基于Go-Zero + Vue3 + TypeScript + Element-Plus开发的简单高效权限管理系统(附源码)
    Java 8 Stream API可以怎么玩?
    桃花峪滑雪场租赁系统 JAVA开源项目 毕业设计
    NodeJS 读写文件
    《孙哥说Spring5》笔记汇总
    【Linux常用命令12】搜索命令及特殊字符的使用
    玩机进阶教程------修改gpt.bin分区表地址段 完全屏蔽系统更新 fast刷写分区表 操作步骤解析【二】
    【计算机网络】第一章、计算机网络体系结构
  • 原文地址:https://blog.csdn.net/m0_51270992/article/details/127033353