视频播放的那些事

2,381 阅读6分钟
原文链接: yq.aliyun.com
TB1Va8HKXXXXXckXVXXXXXXXXXX-900-500.jpg
  • 视频作为淘宝教育业务的基础服务,本文根据自身在手淘中视频播放的实践,谈谈在手淘中视频播放遇到的问题及其解决方案。

播放器

  • 在手淘过去一年多的历史长河中存在五种类型的播放器。

    • 原生 HTML5 video 标签
    • Android 5.3.2 版本之后的 UC 内核增强 SAC 播放器
    • Android 5.4.9 版本之后的 UC HAC 播放器
    • Android 5.3.2 版本之前的 Glue native 播放器
    • Android 5.3.2 版本及其之后的 PlayBuddy 播放器
  • 下面从支持平台,loading 动画,全屏,模拟全屏和兼容性五个方面对各个播放器做个横向对比。a725c8a24592017c81992de6700301df8de1ff84

接口与事件封装

上面介绍了手淘中可供 WebView 选择的播放器,对于业务方而言迫切需要一个解决方,无需关心底层差异。为此,我们屏蔽移动端不同系统平台、宿主环境、播放器的实现细节和兼容性问题,提供统一的接口和事件,具体如下:

  • 方法
    • play 播放
    • pause 暂停
    • stop 停止
    • show 显示
    • hide 隐藏
    • requestFullscreen 全屏
    • exitFullScreen 退出全屏
    • getCurrentTime 获取当前播放时间
    • setCurrentTime 设置播放时间
    • getDuration 获取视频时长
    • setPoster 设置背景图
    • destory 销毁
    • reset 重置视频
  • 事件

    • timeupdate 进度更新
    • ended 停止
    • error 错误
    • play (专指video)
    • pause 停止(专指video)
    • firstpaint 视频真正开始播放(专指video)
  • controls 播放控件(专指video)

    • 播放
    • 暂停
    • 进度更新
    • 全屏
    • loading

兼容性处理

接下来谈谈在开发过程中遇到的各种小问题及其解决办法。

video

  • 内联播放。iPhone 在视频播放时默认全屏播放,参考

    • WebView 中,可以对 UIWebView 做如下配置,并且在 video 中配置 webkit-playsinline 属性即可:

      webview.allowsInlineMediaPlayback = YES;
                  
    • iPhone Safari 在 IOS >= 8 的系统中,有人也提出了一个方案

  • 自定义播放控件

    • 部分 Android 机型不支持内置控件,或者说内置控件无法正常使用;各个产品都有特定的视觉规范,默认控件的交互和视觉无法满足需求。因此,我们推荐默认不启用默认控件,采用自定义控件。
    • IOS 下播放时还可能还展示系统自带播放按钮,可以配置如下 CSS。
video::-webkit-media-controls-start-playback-button {
    display: none;
    }
  • poster 视频底图

    • 在 iPhone 中视频加载完第一帧数据后会覆盖 Poster 底图展示第一帧画面,这时可以使用 DIV 覆盖在视频上方模拟,监听 timeupdate 事件做隐藏操作。
    • 在 UC WebView 中动态设置 poster 可能会导致手淘 crash,方案跟上方一样。
    • 在使用 Native 播放器时,在播放器未初始化时使用 DIV 替换 video 标签,并设置底图为背景图。
  • 播放首屏:IOS 通过监听 playing 事件可以准确获取视频播放的时间点;Android 中在该事件触发时,还没真正开始播放。我们通过监听 timeupdate 的事件做模拟处理。

_timeUpdate(e) {
 var currentTime = this.getCurrentTime();
 
 // 判断是否为首帧
 if (currentTime !== undefined && currentTime !== 0) {
     this.fire('firstpaint');
  }
  }
_playing() {
  if (Env.os.ios) {
     this.fire('firstpaint');
  }
  }
<
  • 视频切换:在android 4.4 以下版本,在视频切换时存在第一次切换不能正常播放,第二次才能正常播放情况。通过调试人肉分析,发现切换视频的 video 存在以下两个特征:readyState 值为 0,videoWidth 为 0。因此我们判断当两个属性为0时,则切换失败,再次调用播放逻辑。存在误判的可能,但是能保证正常工作。
isWork() {
    if (videoEl.readyState === 0 && videoEl.videoWidth === 0) {
        return false;
    }
        
    return true;
    }
  • 全屏:手淘 IOS 支持竖全屏效果,Android 虽然具有全屏方法,但是被手淘限制,调用全屏方法无效。

    • 方案一:为了支持横全屏,我们使用 css3 的 rotate 对视频区域进行90度旋转,并且调用 bridge 接口隐藏 native 顶部的 navibar,并对自定义控件进行响应优化调整。基本到达 native全屏效果。当然顶部状态栏不能隐藏还是有些小瑕疵。同时旋转之后元素的 z-index失效,导致视频覆盖控件问题,可以通过设置 -webkit-transform: translate3d(0,0,0) 来修复

      requestFullscreen() {
                         var element = this.el[0];
                         var method = FullscreenApi.requestFullscreen;
                         if (method) {
                             element[method]();
                         } else if (element.webkitEnterFullscreen || element.enterFullScreen) {
                             element.webkitEnterFullscreen && element.webkitEnterFullscreen();
                             element.enterFullScreen && element.enterFullScreen();
                         } else {
                             // 模拟全屏
                             // enterFullWindow();
                         }
                         }
                     // 模拟全屏js核心代码
                     _mockFullscreen() {
                         if (curEl.hasClass('normal')) {
                             this.fullscreen = false;
                             playerEl.css({
                                 width: this.originWidth,
                                 height: this.originHeight,
                                 left: 0
                             }).removeClass('fullscreen');
                             wrapperEl.css({
                                 width: this.wrapperOriginWidth,
                                 height: this.wrapperOriginHeight
                             });
                             videoEl.css('height', '100%');
                             curEl.removeClass('normal');
                             contentEl.removeClass('fullscreen');
                         } else {
                             this.fullscreen = true;
                             this.originWidth = playerEl.width();
                             this.originHeight = playerEl.height();
                             this.wrapperOriginWidth = wrapperEl.width();
                             this.wrapperOriginHeight = wrapperEl.height();
                             playerEl.css({
                                 width: $(window).height(),
                                 height: $(window).width(),
                                 left: $(window).width()
                             }).addClass('fullscreen');
                             wrapperEl.css({
                                 width: $(window).height(),
                                 height: $(window).width()
                             });
                             videoEl.css('height', videoEl.height() - controlsHeight);
                             curEl.addClass('normal');
                             contentEl.addClass('fullscreen');
                         }
                         }
                     
    • 效果图: IOS全屏

    • 预览地址(请用手淘扫码): demo
    • 方案二。方案一只是模拟了横全屏效果,对于追求完美的处女座不能忍。还有其他方案吗?有时候只需要转换下思维,问题即可迎刃而解。既然是横屏播放,只需要让 WebView 横屏即可,同时在横屏之后重新调整控件即可,关键手淘提供了打开应用横全屏的接口。注意点:横屏之后需要禁止页面滚动,要不然全屏就露馅了,因为本质还是个 WebView。
if (this.transverseFullScreen) {
  if (curEl.hasClass('normal')) {
    curEl.removeClass('normal');
    this._transverseFullScreen(false).then(() => {
      $('body').removeClass('co-fullscreen').attr({ height: 'auto' });
      this.videoWrapperEl.height(this.videoOriginHeight).removeClass('fullscreen');
      this.player.fire('transversefullscreen', { fullscreen: false });
      this.resize();
  } else {
    curEl.addClass('normal');
    this._transverseFullScreen(true).then(() => {
      $('body').addClass('co-fullscreen').attr({ height: win.height() });
      this.videoWrapperEl.height(win.height()).addClass('fullscreen');
      this.player.fire('transversefullscreen', { fullscreen: true });
      this.resize();
    });
  }
  return;
  }
  • Demo: fullscreen
  • 手淘 IOS 扫码:
    • 方案3。在 UC HAC 方案视频提供全屏接口 UCSettings.setVideoViewFullscreenByDefault(true),开启后,视频全屏默认为横屏
  • 自动播放
    • 出于用户节省用户流量考虑,iPhone 下播放视频需要用户手动触发,即使配置了 autoplay 属性也是无效的。在业务中,特定场景还是需要视频能够自动播放,对此我们可以监听页面的 touchstart 事件,做如下处理:
if (this.auoplay && env.app.TB && env.network.wifi) {
if (player.getCurrentTime() > 0 && !player.isPause()) {
  return;
  }
if (this.hasAutoPlay) {
  return;
  }
this.hasAutoPlay = true;
startEl.trigger('click');
function autoplay() {
    doc.detach('touchstart', autoplay);
    if (player.getCurrentTime() > 0) {
        return;
    }
  startEl.trigger('click');
 }
 doc.on('touchstart', autoplay);
 }
  • 其他
    • 部分机型手淘低版本使用 video 播放时,会出现有声音没画面的问题,升级手淘后即恢复。例如,小米4 手淘 4.2.0
    • IOS 5.1 和部分 android 手机暂停和开始按钮不触发点击事件(元素的 :after 为iconfont)。通过父元素添加background即可
    • Android UC 内核的播放器,在未设置 source 资源时,设置 poster 无效
    • Android UC 内核的播放器无法自定义控件和样式操作,但是可以正常的监听事件。
    • 直接替换 source 不会改变当前正在播放的视频,需要调用 load 方法。
    • UC 浏览器中 video 标签会被 UC 的播放器插件替换
    • 使用 m3u8 和 mp4 基本可以兼容所有机型
    • 在 IOS 视频初始化后设置 currrentTime 无效,在 loadedmetadata 事件触发后,设置 currentTime 即可。

native播放器

  • destroy:

    • Glue:Glue native 播放器在页面跳转,WebView 后退等操作时,不会自动析构,好的情况是视频依然在背后播放,有时候会直接导致手淘 crash。
    • PlayBuddy:在页面跳转时依然会继续播放

      处理方式:页面跳转时需要手动的销毁native播放器。

document.addEventListener('WV.Event.Page.Refresh', $.proxy(this.destory, this), false);
document.addEventListener('WV.Event.Key.Back', $.proxy(this.destory, this), false);
win.on('unload', $.proxy(this.destory, this));
win.on('beforeunload', $.proxy(this.destory, this));
  • 定位
    • Glue 播放器使用 dip 作为播放器的定位单位,rem 布局会对页面进行缩放,导致定位位置和视频大小错误。同时定位时参数有小数点会导致播放器错误。
/__
 * 返回值需要是整数,否则会有异常
 */
 _getVedioPos(isDpr) {
    var el = this.el,
        offset = el.offset(),
        dpr = 1;
    if (isDpr) {
        dpr = this._getDpr();
    }
    return {
        x: parseInt(offset.left / dpr),
        y: parseInt(offset.top / dpr),
        w: parseInt(el.width() / dpr),
        h: parseInt(el.height() / dpr)
    };
    }
  • 视频源地址:Glue 播放器不支持以 // 开始的视频资源,例如 //video.xxx
  • PlayBuddy 播放器不会随着页面滚动而滚动。

总结

  • 如果业务中需要在手淘中播放视频,IOS 直接使用原生 video 即可。在 Android 中较为复杂,没有完全兼容的方案。建议使用 video,对于 Android 低版本建议使用native 播放器。随着uc内核接入,未来完全抛弃 native 方案也是可行的。
  • 本文基于过去一年在手淘视频播放过程中遇到问题的小结,后续会整理视频监控和视频娱乐化相关内容。

附:手淘同学播放器兼容性表

品牌 机型 手淘版本 操作系统版本 播放器控件 视频列表切换 试看控制 观看进度同步 切换模式 问题
苹果 6 plus 5.2.7 8.11 *
苹果 6 5.2.7 8.11 *
苹果 5s 5.2.7 8.11 *
苹果 5 5.2.7 8.11 *
苹果 4s 5.2.7 8.11 *
苹果 4 * * *
google nexus 5 * yun os 3 × *
google nexus 5 * 安卓 5 × *
三星 N7100 4.9 * × *
三星 NOTE4 4.9 * 模式二点最大化crash
三星 NOTE3 4.9 * × *
三星 S4 5.2.7.3 * 模式二,播放有问题
三星 I9300 4.3 * *
三星 S3 5.2.8.2 * *
三星 S5 5.2.7.3 * *
魅族 MX2 * * * *
魅族 MX4 PRO * * *
魅族 魅蓝Note * * *
魅族 MX3(安装不上) * * *
华为 荣耀6 * * 模式二可能播放不了
华为 mate7 * * 模式二可能播放不了
华为 c8816 * * 进度条拖动会跳
华为 荣耀3c * 4.4.2 ×一直展示loading的图片 模式二播放不了
华为 C8813 * 4.1.1 高清视频不能播放
HTC MAX * * * *
HTC 816w * * *
VIVO Find5 * 4.1.1 *
VIVO X3 * 4.2.2 *
小米 2S * 4.3 * 高清的播放不了
小米 3 * * * * *
小米 4 * 4.4.4 * *
索尼 M512 * 4.4.2 *
索尼 xperia 36l * 4.1.2 * *
nubia nx403 * 4.2.2 × * 模式一,模式二播放均有问题
锤子 * * * *
oppo x907 * 4.0.3 很难点到 *
nexus5 * * * 第二种模式crash
转载自:http://taobaofed.org/blog/2016/05/03/promise-anti-patterns/ 作者:永霸