标签:
本以为写一个video播放器不难,可写着写着坑就越挖越大了。
先看一下播放器大概是长这样的:
在开发过程中这几个问题深深地困扰着我:
1、各机器兼容问题
2、关于视频播放过程中进度条显示情况
3、关于改变播放进度和音量大小的操作问题
1、各机器兼容问题
除了chrome很乖支持良好外,其中UC获取不到duration属性,也就获取不到视频的总长度了,但这部分可以由后台传参数解决,但是不能禁用系统播放器这个就不太好了。。。。
抛开其它浏览器,来看看腾讯X5内核的浏览器的支持情况:http://x5.tencent.com/guide?id=2009 UC有的他都有,UC没有的他还有。。。。
因为种种原因最后并没有把模拟播放器运用到项目中,但是坑挖开了还得填好是不,再怎么说也要先写好呀,万一以后用得上了咧。
解决剩下问题前先了解一下video中常用的一些方法、属性和事件:
duration:返回当前音频/视频的长度(以秒计)
currentTime:设置或返回音频/视频中的当前播放位置(以秒计)
play(): 开始播放音频/视频
pause(): 暂停当前播放的音频/视频
监听timeupdate事件,获得当前视频播放的进度。
嗯,了解了这些之后也大概明白播放器是怎样工作的了,看着API撸代码就行了:http://www.w3school.com.cn/tags/html_ref_audio_video_dom.asp
先来看看第一个问题:
2、关于视频播放过程中进度条显示情况
可能问题描述得不太清楚,这里主要指的是:进度条和拖放按钮需要按照视频的播放情况而进行调整在开发中遇到的问题。
本来进度条的计算方法是显而易见的:
已播放进度 = 进度条长度 / (总时长 / 已播放时间)
但是因为进度条多了一个拖放按钮,而且拖放按钮的定位是跟随视频的播放而改变的。
如果按照上面的算法,很有可能在视频播放完成的时候,拖放按钮会超出进度条的范围,变成了这样:
也考虑过当按钮的宽度 + 按钮的position.left
>= 进度条长度的时候,拖放按钮不再移动。但这样就又产生了一个问题了:视频并未播放完,但拖放按钮已经到了尽头,很容易给用户造成以为视频已经播放完毕的错觉:
( 在已播放时间 < 总时长的情况下,拖放按钮已经到尽头了)
花了好多时间,地思来想去才灵光一闪犹如有神来之笔一般想到解决方案:
为啥不在视频播放过程中把正在向前移动的按钮一点点地往后退呢:
是时候展示我高超的画技了(*^__^*) 嘻嘻……
如上图所示,涂黑的部分根据百分比慢慢地增加(为方便起见给上图中涂黑的部分设一个变量: iconDetract;)根据视频已播放的时间占总时长的百分比,计算出占按钮宽度的百分比iconDetract。然后再用按钮原来的position.left - iconDetract,得出按钮的实际定位。
由于一点点地减去iconDetract,直到iconDetract == 按钮的总宽度为止。(这时候视频也播放完毕了):
先计算出 按钮原来的定位(activeTol) = 进度条长度 / (总时长 / 已播放时间)
再根据视频的播放进度计算出 iconDetract = 按钮宽度 / (总时长 / 已播放时间)
最后得出 按钮的实际定位 = activeTol - iconDetract
嗯,第一个问题就这样妥妥地解决掉了,如果你有更好的方法,请告诉我。因为智商有限,目前只想到了这么一个解决方法 ~~~~(>_<)~~~~
3、关于改变播放时间点和音量大小的操作问题
改变视频的时间点有两种方法:
1:拖动拖放按钮,慢慢改变视频的播放时间点(慢)
2:点击进度条的某一个位置,让视频直接跳到相对应的时间点(快)
改变音量的两种方法:
1:拖动拖放按钮,一点点慢慢改变音量的大小(慢)
2:点击音量条的某一个位置,让音量直接跳到相对应的位置上(快)
它们的共同的地方都有拖动按钮操作,以及点击进度条,按钮直接到相对于的位置上。唯一不同的是,操作的是视频的进度条抑或是音量条。
怎样可以各自调用一套代码实现各自不同的功能咧,这是目前所要解决的。
实现方法并不难,只是繁琐。
因为拖放按钮的时候需要先点击拖放按钮,但由于冒泡机制,会同时点击进度条。这时候是就会把上面列出来的1和2的操作都一起做了,并不符合现实所需。
所以,调用的方法的时候传一个参数moveing,当moveing == true的时候,当前对象是按钮,而不是它的父级进度条,并且阻止冒泡 e.stopPropagation();
但由于拖放按钮,和点击进度条使得改变视频的播放时间点以及改变音量,需要获取到焦点的X、Y坐标,这时候除了区分当前操作的是视频的进度抑或是调整音量的大小外还需要区分this的指向到底是当前操作对象还是它的父级。(代码中liveEvent部分)
css:
1 <style type="text/css"> 2 .control_bar{background-color:black;height:30px;line-height:30px;} 3 .control_bar span{ height:20px;line-height:20px; vertical-align:middle; display:inline-block;background-color:#fff;opacity:0.8;margin:0 10px;cursor:pointer;} 4 .control_bar #progress_bar,.control_bar #volume_bar{width:32%;border-radius:5px;height:10px; line-height:10px; position:relative;} 5 #progress_bar em,#volume_bar em{background-color:#5CA66A;height:100%; display:inline-block;} 6 .control_bar #time{color:#fff;font-size:14px;background:none;} 7 .control_bar #volume_bar{width:100px;} 8 #progress_bar_icon,#volume_bar_icon{position:absolute;top:-3px;left:0px; border-radius:50%;height:20px;width:20px;display:block;background:#964E4E;} 9 </style>
html:
1 <body> 2 <div style="width:600px;"> 3 <div class="videoPlayer" id="videoContainer"> 4 <video id="video" width="600" height="360"> 5 <source src="test.mp4" type=‘video/mp4‘> 6 </video> 7 <div class="control_bar"> 8 <span id="pause">暂停</span> 9 <span id="progress_bar"><em></em><i id="progress_bar_icon"></i></span> 10 <span id="time"></span> 11 <span id="volume">音量</span> 12 <span id="volume_bar"><em></em><i id="volume_bar_icon"></i></span> 13 <span id="fullscreen">放大</span> 14 </div> 15 </div> 16 </div> 17 <script type="text/javascript" src="index.js"></script> 18 <script type="text/javascript"> 19 20 var videoSet = new VideoContainer({ 21 volumeTol: 0.5, //默认音量 22 video: document.getElementById("video"), 23 pause: document.getElementById("pause"), 24 timer: document.getElementById("time"), 25 progressBar: document.getElementById("progress_bar"), 26 progressBarIcon:document.getElementById("progress_bar_icon"), 27 volume: document.getElementById("volume"), 28 volumeBar: document.getElementById("volume_bar"), 29 volumeBarIcon:document.getElementById("volume_bar_icon"), 30 fullscreen: document.getElementById("fullscreen") 31 }); 32 33 </script> 34 </body>
js:
1 ‘use strict‘; 2 3 //检测设备类型 4 var startWhen, endWhen, moveWhen; 5 var u = navigator.userAgent; 6 7 if ( u.match(/\b(Windows\sNT|Macintosh)\b/) ) { 8 // 鼠标 9 startWhen = ‘mousedown‘; 10 endWhen = ‘mouseup‘; 11 moveWhen = ‘mousemove‘; 12 } else { 13 // 触摸屏 14 startWhen = ‘touchstart‘; 15 endWhen = ‘touchend‘; 16 moveWhen = ‘touchmove‘; 17 } 18 19 // 原生的JavaScript事件绑定函数 20 function bindEvent(ele, eventName, func){ 21 if(window.addEventListener){ 22 ele.addEventListener(eventName, func); 23 } 24 else{ 25 ele.attachEvent(‘on‘ + eventName, func); 26 } 27 } 28 29 function VideoContainer(options) { 30 var t = this; 31 32 t.volumeTol = options.volumeTol; 33 t.video = options.video; 34 t.pauseBtn = options.pause; 35 t.progressBar = options.progressBar; 36 t.timer = options.timer; 37 t.volume = options.volume; 38 t.volumeBar = options.volumeBar; 39 t.volumeBarIcon = options.volumeBarIcon; 40 t.progressBarIcon = options.progressBarIcon; 41 t.fullScreenBtn = options.fullscreen; 42 43 //载入视频 44 //获取视频总时长 45 t.video.addEventListener("canplaythrough", function(){ 46 47 t.tolTime = t.videoTime(t.video.duration); 48 49 t.timer.innerHTML = ‘00:00/‘ + t.tolTime.minutes_str + ‘:‘ + t.tolTime.seconds_str; 50 }); 51 52 //播放时间点更新 53 t.video.addEventListener("timeupdate", function(){ 54 55 t.timeupdate = t.videoTime(t.video.currentTime); 56 57 //更新时间 58 t.timer.innerHTML = t.timeupdate.minutes_str + ‘:‘ + t.timeupdate.seconds_str + ‘/‘ + t.tolTime.minutes_str + ‘:‘ + t.tolTime.seconds_str; 59 60 //根据视频的进度计算icon的定位 61 var activeTol = t.progressBar.offsetWidth / ( t.video.duration / t.video.currentTime ); 62 63 //根据视频的进度计算相对于占icon宽的多少 64 var init = t.progressBarIcon.offsetWidth / (t.video.duration / t.video.currentTime); 65 66 t.progressBarIcon.style.left = activeTol - init + ‘px‘; 67 t.progressBar.children[0].style.width = activeTol + ‘px‘; 68 }); 69 70 //设置默认音量 71 t.volumeTol = t.volumeTol ? t.volumeTol : 0.5; 72 t.video.volume = t.volumeTol; 73 t.volumeBar.children[0].style.width = t.volumeTol * 100 + ‘%‘; 74 t.volumeBarIcon.style.left = t.volumeTol * 100 + ‘%‘; 75 76 //各绑定事件 77 //暂停 78 t.pauseBtn.onclick = function() { 79 t.videoPaused(); 80 } 81 //音量 82 t.volume.onclick = function() { 83 t.videoMuted(); 84 } 85 //全屏 86 t.fullScreenBtn.onclick = function() { 87 88 t.videoFullscreen(this); 89 } 90 91 t.liveEvent.call(t.progressBar, { _this: this, attribute: ‘progressBar‘, moveing: false}); 92 t.liveEvent.call(t.volumeBar, { _this: this, attribute: ‘volumeBar‘, moveing: false}); 93 t.liveEvent.call(t.progressBarIcon, { _this: this, attribute: ‘progress_bar_icon‘, moveing: true}); 94 t.liveEvent.call(t.volumeBarIcon, { _this: this, attribute: ‘volume_bar_icon‘, moveing: true}); 95 96 bindEvent(document, endWhen, function(e) { 97 t._draging = false; 98 }); 99 } 100 101 VideoContainer.prototype.liveEvent = function(options) { 102 103 var t = options._this; 104 105 //区分当前操作元素是拖动按钮还是它的父级 106 var _this = options.moveing ? this.parentNode : this; 107 var _parentWidth = _this.offsetWidth; 108 109 //检测设备类型 110 var _ua = function(e) { 111 var Pos = null; 112 113 if ( u.match(/\b(Windows\sNT|Macintosh)\b/) ) { 114 e = e || window.event; 115 116 Pos = { 117 left : e.pageX, 118 top: e.pageY 119 } 120 121 } else { 122 var touch = e.targetTouches[0] || e.changedTouches[0] 123 Pos = { 124 left : touch.pageX , 125 top: touch.pageY 126 } 127 } 128 return Pos; 129 }; 130 131 //区分拖动的是进度条还是音量条 132 function playStep() { 133 134 if (options.attribute == ‘progress_bar_icon‘ || options.attribute == ‘progressBar‘) { 135 136 //根据拖放的进度计算相对于占icon宽的多少 137 var init = t.progressBarIcon.offsetWidth / (_parentWidth / t._durCount.left); 138 139 //拖放按钮是否超出范围 140 if ( t._durCount.left + t.progressBarIcon.offsetWidth - init >= _parentWidth ){ 141 142 t.video.currentTime = t.video.duration; 143 144 t.progressBar.children[0].style.width = _parentWidth + ‘px‘; 145 146 } else{ 147 t.video.currentTime = t.video.duration / (_parentWidth / (t._durCount.left + init)); 148 t.progressBar.children[0].style.width = t._durCount.left + ‘px‘; 149 } 150 151 t.progressBarIcon.style.left = t._durCount.left + ‘px‘; 152 153 } else{ 154 155 if ( t._durCount.left + t.volumeBarIcon.offsetWidth >= _parentWidth ){ 156 t.volumeTol = 1; 157 } else if( t._durCount.left < 0 ){ 158 t.volumeTol = 0; 159 } else { 160 t.volumeTol = 1 / (_parentWidth / t._durCount.left); 161 } 162 163 //拖放按钮是否超出范围 164 if (t.volumeTol == 1) { 165 t.volumeBarIcon.style.left = _parentWidth - t.volumeBarIcon.offsetWidth + ‘px‘; 166 } else{ 167 t.volumeBarIcon.style.left = t.volumeTol * 100 + ‘%‘; 168 } 169 170 t.video.volume = t.volumeTol; 171 t.volumeBar.children[0].style.width = t.volumeTol * 100 + ‘%‘; 172 } 173 } 174 175 //仅限拖动按钮可移动 176 if ( options.moveing === true) { 177 178 bindEvent(this, moveWhen, function(e) { 179 180 if (t._draging) { 181 182 //鼠标移动了多少距离 183 t._mousePos = { 184 left: (_ua(e).left - _this.offsetLeft) - t._startPos.left, 185 top: (_ua(e).top - _this.offsetTop) - t._startPos.top 186 } 187 188 t._motion = true; 189 190 //鼠标是否在拖动范围内 191 if (0 <= t._mousePos.left <= _parentWidth || 0 < t._mousePos.top <= _this.offsetHeight){ 192 193 //移动后的坐标 = 上次记录的定位 + 鼠标移动了的距离; 194 t._durCount = { 195 left: t._oldPos.left + t._mousePos.left, 196 top: t._oldPos.top + t._mousePos.top, 197 }; 198 199 playStep(); 200 201 } else { 202 t._draging = false; 203 } 204 } 205 }); 206 } 207 208 bindEvent(this, startWhen, function(e) { 209 210 // 防止选择文字、拖动页面(触摸屏)鼠标移动过快松开抛锚 211 e.preventDefault(); 212 213 if ( this.setCapture ) { 214 this.setCapture(); 215 } 216 217 //如果当前对象是拖动按钮,阻止冒泡 218 if (options.moveing === true) { 219 e.stopPropagation(); 220 } 221 222 t._draging = true; 223 t._motion = false; 224 225 //记录按下鼠标到背景图片的距离 226 t._startPos = { 227 left: _ua(e).left - _this.offsetLeft, 228 top: _ua(e).top - _this.offsetTop 229 } 230 231 //当前拖动按钮的定位 232 t._oldPos = { 233 left: this.offsetLeft, 234 top: this.offsetTop 235 }; 236 237 //本次拖动的距离 238 t._durCount = t._startPos; 239 }); 240 241 bindEvent(this, endWhen, function(e) { 242 243 t._draging = false; 244 245 //如果进行的是拖动操作,则不必再更新视频的进度 246 if( !t._motion) { 247 248 playStep(); 249 250 } 251 252 // 防止选择文字、拖动页面(触摸屏)鼠标移动过快松开抛锚 253 if (this.releaseCapture) { 254 this.releaseCapture(); 255 } 256 257 delete t._draging; //是否拖动 258 delete t._startPos; //鼠标按下坐标 259 delete t._oldPos; //拖动按钮的定位 260 delete t._mousePos; //鼠标移动的距离 261 }); 262 } 263 //转换时间单位 264 VideoContainer.prototype.videoTime = function(time) { 265 var timeId = {} 266 267 var seconds = time > 0 ? parseInt(time) : 0; 268 269 var minutes = parseInt(time / 60); 270 seconds = seconds - minutes * 60; 271 272 timeId.minutes_str = minutes < 10 ? ‘0‘ + minutes : minutes; 273 timeId.seconds_str = seconds < 10 ? ‘0‘ + seconds : seconds; 274 275 return timeId; 276 } 277 278 //是否全屏 279 VideoContainer.prototype.videoFullscreen = function() { 280 281 var t = this; 282 283 var element = t.video; 284 285 //反射調用 286 var invokeFieldOrMethod = function(element, method) { 287 var usablePrefixMethod; 288 ["webkit", "moz", "ms", "o", ""].forEach(function(prefix) { 289 if (usablePrefixMethod) return; 290 if (prefix === "") { 291 // 无前缀,方法首字母小写 292 method = method.slice(0,1).toLowerCase() + method.slice(1); 293 } 294 var typePrefixMethod = typeof element[prefix + method]; 295 if (typePrefixMethod + "" !== "undefined") { 296 if (typePrefixMethod === "function") { 297 usablePrefixMethod = element[prefix + method](); 298 } else { 299 usablePrefixMethod = element[prefix + method]; 300 } 301 } 302 }); 303 304 return usablePrefixMethod; 305 }; 306 307 if( invokeFieldOrMethod(document,‘FullScreen‘) || invokeFieldOrMethod(document,‘IsFullScreen‘) || document.IsFullScreen ){ 308 //退出全屏 309 if ( document.exitFullscreen ) { 310 document.exitFullscreen(); 311 } else if ( document.msExitFullscreen ) { 312 document.msExitFullscreen(); 313 } else if ( document.mozCancelFullScreen ) { 314 document.mozCancelFullScreen(); 315 } else if( document.oRequestFullscreen ){ 316 document.oCancelFullScreen(); 317 } else if ( document.webkitExitFullscreen ){ 318 document.webkitExitFullscreen(); 319 } else{ 320 var docHtml = document.documentElement; 321 var docBody = document.body; 322 var videobox = element.parentNode; 323 docHtml.style.cssText = ""; 324 docBody.style.cssText = ""; 325 videobox.style.cssText = ""; 326 document.IsFullScreen = false; 327 } 328 } else { 329 330 //進入全屏 331 //此方法不可以在異步任務中執行,否則火狐無法全屏 332 if(element.requestFullscreen) { 333 element.requestFullscreen(); 334 } else if(element.mozRequestFullScreen) { 335 element.mozRequestFullScreen(); 336 } else if(element.msRequestFullscreen){ 337 element.msRequestFullscreen(); 338 } else if(element.oRequestFullscreen){ 339 element.oRequestFullscreen(); 340 } else if(element.webkitRequestFullscreen){ 341 element.webkitRequestFullScreen(); 342 }else{ 343 var docHtml = document.documentElement; 344 var docBody = document.body; 345 var videobox = element.parentNode; 346 var cssText = ‘width:100%;height:100%;overflow:hidden;‘; 347 docHtml.style.cssText = cssText; 348 docBody.style.cssText = cssText; 349 videobox.style.cssText = cssText+‘;‘+‘margin:0px;padding:0px;‘; 350 document.IsFullScreen = true; 351 } 352 } 353 } 354 //是否暂停 355 VideoContainer.prototype.videoPaused = function() { 356 var t = this; 357 358 if(t.video.paused) { 359 t.video.play(); 360 } else{ 361 t.video.pause(); 362 } 363 } 364 //调整音量 365 VideoContainer.prototype.videoMuted = function() { 366 var t = this; 367 368 if ( !t.video.defaultMuted ){ 369 370 t.video.volume = 0; 371 t.video.defaultMuted = true; 372 373 t.volumeBar.children[0].style.width = 0 + ‘%‘; 374 t.volumeBarIcon.style.left = 0 + ‘%‘; 375 } else { 376 t.video.volume = t.volumeTol; 377 t.video.defaultMuted = false; 378 t.volumeBar.children[0].style.width = t.volumeTol * 100 + ‘%‘; 379 380 //拖放按钮是否超出范围 381 if (t.volumeTol == 1) { 382 t.volumeBarIcon.style.left = t.volumeBar.offsetWidth - t.volumeBarIcon.offsetWidth + ‘px‘; 383 } else{ 384 t.volumeBarIcon.style.left = t.volumeTol * 100 + ‘%‘; 385 } 386 } 387 }
标签:
原文地址:http://www.cnblogs.com/Travel/p/5396402.html