不会吧不会吧!不会还有人不知道HTMLVideoElement的load事件会被浏览器拦截吧!

2,679 阅读3分钟

如何优雅的解决Video元素的`load事件被浏览器拦截?

画面回顾

  • RD:我要加载视频。
  • WeChatBrowser:不,你不行!
  • HTMLVideoElement:啊这...
  • RD:???浏览器不让我加载视频!
  • PM:能解决吗?
  • 。。。

开始烹饪

记录Video在微信浏览器加载策略踩坑经历

本期仅分享解决方案,原理见文末其他作者解析。
👇🏿👇🏿👇🏿👇🏿👇🏿👇🏿 暂且跟x5内核无关 ( 已修改测试过Phaser源码的video加载属性

    video.setAttribute('x5-playsinline', 'true')
    video.setAttribute('playsinline', 'true')
    video.setAttribute('x5-video-player-type', 'h5')

现在进入正题

「观感度:🌟🌟🌟🌟🌟」

「享用时间:5min」

需求产生情形复现

某一天产品拍了我的肩膀,微笑着说:“小程序中的H5-Phaser3游戏场景中可以加视频吗?”;
我相顾一笑、一个坚定的眼神:“我做个技术调研,其他的走正常排期。” ( 因为是PhaserAPI熟练的搬运工,此番言论回答的.非..常..自...信....... 此处省略1w字.......

问题出现及排查

当距离提测还有一天的节点,于是乎我分别在安卓和iOS真机测试了一下,大事不妙!
“好家伙,我直呼好家伙!”
安卓可以正常使用,但这!!iOS的加载进度条却一直卡在100%

  • 1.查看Networkcode:200Blob正常加载了视频;
  • 2.查看Phaser的Game实例:cache.video 这个Map对象没有被set任何元素( 奇怪点1;
  • 3.load事件没有触发'complete'方法 ( 奇怪点2;
  • 4.load事件没有触发'onLoad'方法 ( 奇怪点3;

通过这四个点初步定位到了卡顿是发生了Load事件但是其回调函数某不知名的事件拦截,接下来就是如何解的问题了。

问题搜索

“iOS是点击播放后才会去加载视频流。android下会执行canplay事件回调,但视频流也是边下边播。所以无法准确获得视频可加载时间点。”

上面引文描述和我的情形一样,没有用户行为的加载都没有loadComplete回调,固无法对Map()进行set,所以它的size为0。

  // Phaser生命周期 - 预加载事件放在preload中
  preload(){
    this.load.video('key' ,'url' ,'canplay' ,true ,false ) // 参数具体看附件文档
  }

  // Phaser生命周期 - create渲染场景,load回调可以在此监听
  create(){
    this.load.once('complete', (e) => {
      // PC、Android 都能够正常打印该对象size:1
      // iOS 无法执行complete回调,没有任何打印
      console.error(this.cache.video.entries) 
    )}
  }

左图为适配后,右图为适配前

解决思路

最佳方案:

触发wx浏览器中video对象的load事件时,在wxAPI的回调中强行触发Video原型 load方法,于是乎有了当通过代码(非用户行为)调用load时,借用weixin-js-sdk包的异步api进行暴力load事件:

  /**
   * 微信环境下的加载
   * @param  {...any} args - 参数
   * @returns {void}
   */
  const wx = require('weixin-js-sdk');
  let _load = window.HTMLVideoElement.prototype.load
  window.HTMLVideoElement.prototype.load = function (...args): void {
     wx.getNetworkType({
       success: (res) => {
         _load.call(this, ...args)
       }
     })
  }

野路子:

上述是优化后的代码,也可以通过wx自带浏览器window对象下的WeixinJSBridge.invoke(()=>{})处理该事件: 但是值得注意的是,微信可能未来取消在内置浏览器挂在window(ts:globalThis)下的WeixinJSBridge对象,建议通过引入'weixin-js-sdk'包或者引入script标签jweixin.js 来支持该事件。

  let _load = window.HTMLVideoElement.prototype.load
  function wxLoad(...args) {
    const self = this
    if(globalThis.WeixinJSBridge){
      globalThis.WeixinJSBridge.invoke('getNetworkType', {}, function (e) {
        _load.call(self, ...args)
      })
    }
  }

  window.HTMLVideoElement.prototype.load = function (...args):void {
      _load.call(this, ...args)
  }

文档贡献

Phaser3视频文件文档:Phaser3 Class: VideoFile
站内文章:H5 Video踩坑记录
站外文章:H5音频踩坑与填坑
GitHub:JavaScript判断是否微信内置浏览器
视频播放--踩坑小计