码迷,mamicode.com
首页 > 其他好文 > 详细

模拟video播放器

时间:2016-04-15 20:18:46      阅读:404      评论:0      收藏:0      [点我收藏+]

标签:

 

本以为写一个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部分)

  

 

demo完整代码如下

 

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>
View Code

 

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>
View Code

 

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 }
View Code

 

模拟video播放器

标签:

原文地址:http://www.cnblogs.com/Travel/p/5396402.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!