javascript模拟点击事件--实现视频自动播放

背景
     这两年一直在做浏览器开发相关的工作,我们所做的浏览器禁用了视频自动播放功能,以实现视频预加载功能:浏览器在视频播放时为了实现视频秒开效果(就是一点击页面里的播放按钮就立即开始播放,而不出现loading界面),禁用了页面视频自动播放的功能,包括javascript调用video.play或video元素上的autoplay属性,而需要用户点击播放才会进行真正的播放,而从页面打开到用户点击之间显然是有一个时间间隔的,而播放器就可以利用这段时间对视频进行预加载。
     然而视频专区需要自动播放功能。视频专区收集视频的各个,点击下图中的”立即播放“将会打开原视频页面(如下图中所示将会打开腾讯视频的页面,进行视频播放)。那是不是简单的放开这种情况下的视频自动播放功能就行了呢?显然不,部份网站的视频页面打开后,视频打开,页面并不会自动播放,依然需要通过点击才能播放。

     

正常情况下打开腾讯视频,视频不会自动播放,需要点击后才会播放。



目标

由视频专区打开的视频页面,实现视频自动播放,主要解决部份视频网站,需要点击播放视频的问题,减少用户多一次点击的操作,提高用户体验。

另:方案应该能够适应网页改版的情况




方案选择
实现视频播放一般流程
1.创建video标签
2.设置视频地址(video的src属性,或video的子节点source)
3.把视频video显示在可见区域中
3.响应点击事件,调用video的play方法进行播放或不拦截点击事件,video默认处理点击事件就会play视频。


注:由于本功能是针对那些不会自动播放的网站,所以对于此已经自动播放的视频网站不在本讨论范围中。


方案一:直接找到视频地址进行播放
可以在页面加载完成后找到video元素,取出视频播放地址,直接播放。
问题:1.video的元素的创建时机不确定, 可能需要点击页面上的播放按钮后才会创建。
          2.视频地址设置时机不确定,可能在点击后才设置,如youku视频
          3.视频显示区域设置不确定,如腾讯视频,初始时video是不可见的,在video上显示了div poster浮层。
          4.很多视频都是分段视频,比如先播放一段广告再播放视频正片,而这个替换过程是需要页面javascript参与的,如果直接拿到视频地址播放,就只能播放第一段视频了。

基于以上原因:直接取视频地址进行播放,显然是不行了。


方案二:通过模拟点击事件进行播放

所有这种非自动播放页面都有一个共同点,就是需要点击后才会播放(感觉像是废话)。如果我们能够模拟出这个点击事件,不就解决了。
下面就主要考虑模拟点击事件这个能够怎么实现:
方法1:直接往View上发送模拟touch事件
问题
     1.什么时候发送这个模拟事件?
          容易想到的一个点:页面加载完成,这个事件很容易收到,可以通过WebViewClient里的 onPageFinished方法接收这个通知。但这个通知只表示页面加载完成,并不表示页面排版完成,如果页面没有排版成最终状态,像指定区域发送点击不光起不效果,反而会触发误操作(如误点击到一个链接上进行跳转)
     2.在加载过程中,用户对页面进行滚动了?

方法二:通过javascript模拟点击事件
     1.在页面加载完成onPageFinished后,往页面中注入一段javascript
     2.根据不同网站,找到不同的事件触发元素,往其发送对应的事件
     3.监听视频是否成功播放,如果没有,重复2,3几次。
     
     优点:采用脚本实现,后台可配置,页面变更不用更改客户端代码
  

目前我们的浏览器视频专区立即打开这个功能就是采用这种方案实现的。


关键点:如何使用javascript模拟发送事件

实现
针对视频来说,点击事件,一般有两种:1.页面直接处理onclick之类的事件,2,页面拦截touchstart, touchmove, touchend等touch事件,自己判断出点击事件。
所以本功能就出现发送click事件或touch事件。

特征元素:指的是页面通过这个元素接收事件,以触发视频播放。

以腾讯视频为例,分析实现过程。

1.找到特征元素
首完将ua切换到相应浏览器UA
在Chrome中,可以通过,这个分析过程是个体力活,好在难度不大,就不在这里分析了。因为不同UA得到页面是不一样的


将各个需要点击才能播放的视频站点的特征元素的特征数据分析出来生成一个映射map
  setUpClickMapInfo: function()
  {
    var map = {};
    map[" m.v.qq.com "] = new this.ClickItemInfo('div[class="tvp_overlay_play"]', 'div[class="tvp_overlay_play"]', video_utils.ClickType.Touch);
    map[" v.youku.com "] = new this.ClickItemInfo('div[class="x-video-button"]', 'div[class="x-video-button"]', video_utils.ClickType.Touch);
    map[" m.fun.tv "] = new this.ClickItemInfo('video[id="html-video-fun-2"]', 'video[id="html-video-fun-2"]', video_utils.ClickType.Click);
    map[" info.3g.qq.com "] = new this.ClickItemInfo('video', 'video', video_utils.ClickType.Click);
    map[" www.tudou.com "] = new this.ClickItemInfo('div[class="touch"]', 'div[class="touch"]', video_utils.ClickType.Click);
  
    return map;
  },

注:上面的ClickType.Touch/Click表示这个特征元素接收的是Touch事件还是click事件

2.根据特征元素的特征找到这个特征元素
根据特征值调用css查询方法document.querySelectorAll找到对应特征元素
  getNode: function(nodeAttr)
  {
    if(nodeAttr.constructor == String)
    {
      return document.querySelectorAll(nodeAttr)[0];
    }
  
    return nodeAttr;
  }


3.获得特征元素的在文档中的中心点位置
此中心位置就是touch点击事件的点击的位置
  getElementPosition: function(e) {
    var orignalEle = e;
    var x = 0, y = 0;
    while (e != null) {
     x += e.offsetLeft;
     y += e.offsetTop;
     e = e.offsetParent;
    }
  
    var width = orignalEle.clientLeft + orignalEle.clientWidth + orignalEle.offsetLeft;
    var height = orignalEle.clientTop + orignalEle.clientHeight + orignalEle.offsetTop;
  
    x += width / 2;
    y += height /2;
  
    return { x: x, y: y };
  }


4.以指定元素,指定点击位置创建touch事件
创建touchstart事件
createTouchStartEvent: function(ele, x, y)
  {
      console.log("createTouchStartEvent for ele:" + ele + "x= " + x + ",y = "  + y);
      var evt = document.createEvent('TouchEvent');

      var touch = document.createTouch(window, ele, 0, x, y, x, y);
    var touches = document.createTouchList(touch);
    var targetTouches = document.createTouchList(touch);
    var changedTouches = document.createTouchList(touch);


    evt.initTouchEvent(touches, targetTouches, changedTouches, "touchstart", window, 0, 0, 0, 0, 0, 0, 0, 0);
    //evt.initTouchEvent("touchstart", true, true, window, null, 0, 0, 0, 0, false, false, false, false,
      //touches, targetTouches, changedTouches, 1, 0);
    return evt;
  },

注:touchend事件可以同样方式创建

5.将创建的事件发送给指定结点
    root.dispatchEvent(this.createTouchStartEvent(ele, obj.x, obj.y));
    root.dispatchEvent(this.createTouchEndEvent(ele, obj.x, obj.y));

到此页面会自动判断出这个touch事件序列,为一个点击事件,就会进入到真实的播放流程

6.多次尝试
上面的事件模拟并不能保证调用时机一定正确,可能注入的JS在执行时,对应特征元素并没有创建好,或事件处理函数并没有准备好(如,还没有注册事件监听函数),此时发送模拟点击事件不能将视频播放起来,需要检测视频播放的结果
需要循环一次数,查看视频是否播放起来
window.setTimeout(function(){
         tencent_log_print("detect video played xxx.getAutoPlayNextVideoFlag() = " + xxx.getAutoPlayNextVideoFlag() + ", kMaxTry = " + kMaxTry);
      if(xxx.getAutoPlayNextVideoFlag() && --kMaxTry > 0){
            log_print("video not play automatic resend click event");
            window.play_video();
      }
      else
      {
log_print("video has played automatic");
          
      }
    }, 600);


总结
基本流程
1.调用xxx.setAutoPlayNextVideo接口,表明下个视频需要自动播放。
          视频专区在点击”立即播放“将会调用这个 js api扩展接口
2.页面加载完成的,检测1所设置的标志,如果设置就注入上在的讲述的javscript代码
3.注入的javascript发送模拟点击事件,触发视频播放,并定时检测视频否成功播放,通过xxx.getAutoPlayNextVideoFlag()来判断
4.视频播放后,将xxx.setAutoPlayNextVideo设置的标志清除



其它说明:
上面出现xxx.getAutoPlayNextVideoFlag()之类的调用,xxx是我们浏览器中提供的一些特有javascript api以AddJavascriptInterface添加的java对象实现。在调用此类功能时有权限判断,目前就是域名作为判断条件,只对自有业务开放。

主要用到一些的js api
document.createTouch
document.createTouchList
elem .initTouchEvent
elem ..dispatchEvent 
document.querySelectorAll


其它处理:
1.后台配置javascript脚本,以处理页面改版后的情况。

遗留问题
1.通过查看上面的的实现过程,需要对每个页面都需要分析特征元素
2.不同UA得到的页面还不同,自然特征元素也可能不同





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值