当前位置: 首页 > Web前端 > HTML5

个性化H5视频组件实现与思考

时间:2023-04-05 01:39:56 HTML5

前言为什么要做自定义视频组件,主要是浏览器原生视频的外观比较弱,很难满足c端用户的审美要求,以及无法吸引用户使用所以作者在原生视频的基础上,披上了一层外衣,实现了一个自定义视频播放器组件设计阶段的版本。这里直接把播放器的功能点列在这里,如下图:需要实现的几个东西所有的功能基本都标出来了;除了视频加载失败;先梳理一下函数逻辑:核心函数1.播放器实例初始化2.播放控件3.进度条控件4.错误处理事件处理方案1.使用原生DOM操作获取视频元素,使用addEventListener监听各种状态,以及执行相应的逻辑操作;方案2、利用Vue自带的事件监听管理器,增加逻辑处理;最后,为了快速实现需求,事件管理采用了dom原生事件和vue内置事件的结合,实现了通用视频组件的最终vue版本。打算后面再优化一下,细化成一个支持多框架使用的通用js库。实现阶段组件模板的部分模板比较容易,根据功能点编写相应的DOM结构即可。播放器初始化这里有一个坑是父元素隐藏的时候,就是,display:none,getBoundingClientRect()无法获取元素的大小值。后来查了下MDN文档,按照上面的改了border。renderer的解析规则,发现display:none节点不会被解析到rendertree中,但是visibility等于hidden的元素会显示在这棵树中这可能是获取不到元素的size数据的原因,因为根本没有被浏览器解析。getBoundingClientRect():返回元素的大小及其相对于视口的位置。这个api在计算元素的相对位置时很有用是的,在很多UI组件库和渲染库中经常用到init(){//初始化视频,获取视频元素this.$video=this.$el.getElementsByTagName(“视频”)[0];这个.initPlayer();},//初始化player容器,获取video-player元素//getBoundingClientRect()根据客户端可见区域的左上角计算位置initPlayer(){const$player=this.$el;const$progress=this.$el.getElementsByClassName("progress")[0];//玩家位置this.player.$player=$player;this.progressBar.$progress=$progress;this.player.pos=$player.getBoundingClientRect();this.progressBar.pos=$progress.getBoundingClientRect()this.video.progress.width=Math.round($progress.getBoundingClientRect().width);},Play&&Pause点击这里监听事件都是在视频播放时才放在事件监听中;个人感觉原生监控和Vue监控一起写有点别扭(有待优化)emem...;这里我们需要做一个this.$video.play()的异常处理,防止视频一开始就加载失败。如果视频链接失败,无法调用播放方法会抛出错误。后来我也是用视频的error事件来监听播放播放时出错//点击播放&暂停按钮clickPlayBtn(){if(this.state.isLoading)return;this.isFirstTouch=false;this.state.playing=!this.state.playing;this.state.isEnd=false;if(this.$video){//播放状态if(this.state.playing){try{this.$video.play();this.isPauseTouch=false;//监控缓存进度this.$video.addEventListener("进度",e=>{this.getLoadTime();});//监听播放进度this.$video.addEventListener("timeupdate",throttle(this.getPlayTime,100,1));//MonitorEndthis.$video.addEventListener("ended",e=>{//重置状态this.state.playing=false;this.state.isEnd=true;this.state.controlBtnShow=true;this.video.displayTime="00:00";这个.video.progress.current=0;this.$video.currentTime=0;});}catch(e){//捕获url异常错误}}//停止状态else{this.isPauseTouch=true;这个.$video.pause();}}},这里需要添加两个开关来显示和隐藏视频控制栏;第一次触摸屏幕,暂停触摸屏;只是做一些隐藏和可见的处理//触摸播放区域touchEnterVideo(){if(this.isFirstTouch)return;如果(this.hideTimer){clearTimeout(this.hideTimer);this.hideTimer=null;}this.state.controlBtnShow=true;this.state.controlBarShow=true;},//离开播放区域touchLeaveVideo(){if(this.isFirstTouch)return;如果(this.hideTimer){clearTimeout(this.hideTimer);}//暂停触摸,不隐藏if(this.isPauseTouch){this.state.controlBtnShow=true;this.state.controlBarShow=true;}else{this.hideTimer=setTimeout(()=>{this.state.controlBarShow=false;//只在加载时显示l加载如果(this.state.isLoading){this.state.controlBtnShow=true;}else{this.state.controlBtnShow=false;}this.hideTimer=null;},3000);}}、视频错误处理和等待处理这里的error直接使用error事件,stalled事件用于监听加载时视频阻塞状态,等待数据加载的waiting事件;只显示相应的加载动画//加载动画@keyframesrotate{0%{transform:rotate(0deg);}50%{变换:旋转(180度);}100%{变换:旋转(360deg);}}.loader{宽度:58px;高度:58px;背景:rgba(15、16、17、0.3);边界半径:50%;位置:相对;.ball-clip-rotate{位置:绝对;左:50%;顶部:50%;转换:翻译(-50%,-50%);>div{宽度:15px;高度:15px;边界半径:100%;边距:2px;动画填充模式:两者;边框:2px实心#fff;边框底色:透明;高度:26px;背景:透明;展示:行内块;动画:旋转0.75s0s线性无限;}}}播放时间设置基本上就是video对象的currentTime和duration属性;这里要注意,如果video没有设置preload属性preload,会在video元素中初始化,无法获取时长...那么只能在播放的时候获取//获取播放时间getPlayTime(){constpercent=this.$video.currentTime/this.$video.duration;这。video.progress.current=Math.round(this.video.progress.width*percent);//分配时长this.video.totalTime=timeParse(this.$video.duration);this.video.displayTime=timeParse(this.$video.currentTime);},//获取缓冲时间getLoadTime(){this.video.loaded=(this.$video.buffered.end(0)/this.$video.duration)*100;},手动滑动进度条可以直接通过touch事件控制;注意在touchend中使用了e.changedTouches;因为当手指离开屏幕时,touches和targetTouches中对应的元素会同时被移除,而changedTouches中仍然有元素touches:当前屏幕上所有触摸点的列表;targetTouches:当前对象上所有触摸点的列表;changedTouches:当前(触发)事件涉及的触摸点列表//手动调整播放进度moveStart(e){},moveIng(e){//console.log("Touching...");让currentX=e.targetTouches[0].pageX;让offsetX=currentX-this.progressBar.pos.left;//边界检测if(offsetX<=0){offsetX=0}if(offsetX>=this.video.progress.width){offsetX=this.video.progress.width}this.video.progress.current=offsetX;让百分比=this.video。progress.current/this.video.progress.width;this.$video.duration&&this.setPlayTime(percent,this.$video.duration)},moveEnd(e){letcurrentX=e.changedTouches[0].pageX;让offsetX=currentX-this.progressBar.pos.left;this.video.progress.current=offsetX;//offsetX这里都是正数letpercent=offsetX/this.video.progress.width;这个。$视频。持续时间&&this.setPlayTime(percent,this.$video.duration)},//设置手动播放时间setPlayTime(percent,totalTime){this.$video.currentTime=Math.floor(percent*totalTime);},全屏功能这个功能不同在手机上会有一些兼容性问题,需要改进//设置全屏fullScreen(){console.log('点击全屏...');如果(!this.state.fullScreen){this.state.fullScreen=true;这个.$video.webkitRequestFullScreen();}else{this.state.fullScreen=false;文档.webkitCancelFullScreen();}一些问题总结1.要获取视频预加载时长,需要设置preloadingpreload="auto"2.Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。当父元素设置为display:none时,无法获取size数据。改歌谣可见性:hidden3.play()方法异常捕获try{instance.play}catch(e){}4.Android手机视频兼容性处理,播放视频时,图层设置到最顶层,会影响全局弹出图层的样式。我这里做的处理是在弹出层出现时隐藏视频(宽高都为0,或者直接Remove),在5.ios下用封面图代替全屏处理并设置相应的属性,playsinline的详细代码见:https://github.com/appleguard...