iOS 上的新 <video> 策略

早在宇宙中烈日燃烧、您所在的种族诞生之前,iOS 上的 Safari 就要求用户手势才能在 <video><audio> 元素中播放媒体。当 Safari 首次在 iPhoneOS 3 中支持 <video> 时,媒体数据仅在用户与页面交互时加载。但为了将更多媒体播放控制权交还给网页开发者,我们在 iOS 8 中放宽了此限制:Safari 开始遵循 preload="metadata" 属性,允许 <video><audio> 元素加载足够的媒体数据以确定媒体的大小、时长和可用轨道。对于 iOS 10 中的 Safari,我们将进一步放宽对静音 <video> 元素的用户手势要求。

动机

事实证明,如今人们非常喜欢 GIF。但与 H.264 等现代视频编解码器相比,GIF 格式在编码动画图像方面成本非常高。我们发现 GIF 在带宽方面可能高出十二倍的成本,在能耗方面高出两倍。由于成本如此之高,许多大型 GIF 提供商已开始放弃 GIF,转向使用 <video> 元素。由于这些 GIF 中的大部分最初都是视频片段,然后被转换为动画 GIF,再被转换回视频片段,可以说这是一个完整的循环。

虽然此举可以节省网站的带宽成本并延长用户电池续航,但它也带来了可用性成本。在 iOS 9 上,<video> 只有在用户手势触发下才会开始播放。因此,将 <img> 替换为 <video> 的页面需要用户手势才能显示其动画内容,并且在 iPhone 上,<video> 会在开始播放时进入全屏模式。

关于用户手势要求的一个说明:当我们说某个操作必须是“用户手势的结果”时,我们指的是导致调用 video.play() 的 JavaScript(例如)必须直接来源于 touchendclickdoubleclickkeydown 事件的处理程序。因此,button.addEventListener('click', () => { video.play(); }) 将满足用户手势要求。video.addEventListener('canplaythrough', () => { video.play(); }) 则不会。

同样,网页开发者正在通过将 <video> 元素集成到其页面呈现中来做一些非常棒的事情。然而,由于用户手势要求,这些页面要么在 iOS 上完全无法工作,要么 <video> 元素通过全屏播放动画背景图像而完全遮挡了页面的呈现。

WebKit 的新视频策略

从 iOS 10 开始,WebKit 放宽了其内联和自动播放策略,以使这些呈现成为可能,但仍兼顾了网站的带宽和用户的电池续航。

默认情况下,WebKit 将采用以下策略:

  • 现在,<video autoplay> 元素将遵循 autoplay 属性,但需满足以下条件:
    • 如果 <video> 元素的源媒体不包含音轨,则允许其在没有用户手势的情况下 autoplay(自动播放)。
    • <video muted> 元素也允许在没有用户手势的情况下自动播放。
    • 如果 <video> 元素在没有用户手势的情况下添加了音轨或取消了静音,播放将暂停。
    • <video autoplay> 元素仅在屏幕上可见时才会开始播放,例如当它们滚动进入视口、通过 CSS 变得可见以及插入到 DOM 中时。
    • 如果 <video autoplay> 元素变得不可见,例如滚动出视口,它们将暂停播放。
  • 现在,<video> 元素将遵循 play() 方法,但需满足以下条件:
    • 如果 <video> 元素的源媒体不包含音轨,或者其 muted 属性设置为 true,则允许其在没有用户手势的情况下 play()(播放)。
    • 如果 <video> 元素在没有用户手势的情况下添加了音轨或取消了静音,播放将暂停。
    • <video> 元素在屏幕上不可见或超出视口时,也允许 play()(播放)。
    • video.play() 将返回一个 Promise,如果这些条件中的任何一个未满足,该 Promise 将被拒绝。
  • 在 iPhone 上,<video playsinline> 元素现在允许内联播放,并且在播放开始时不会自动进入全屏模式。
    没有 playsinline 属性的 <video> 元素在 iPhone 上仍将需要全屏模式才能播放。
    当通过捏合手势退出全屏时,没有 playsinline 属性的 <video> 元素将继续内联播放。

对于 iOS 上 WebKit 框架的客户端,这些策略仍然可以通过 API 控制,并且使用现有 API 控制这些策略的客户端将不会看到任何变化。如需更精细地控制自动播放策略,请参阅新的 WKWebViewConfiguration 属性 mediaTypesRequiringUserActionForPlayback。iOS 10 上的 Safari 将使用 WebKit 的默认策略。

关于 playsinline 属性的一个说明:此属性最近已添加到 HTML 规范中,WebKit 已通过取消前缀其旧版 webkit-playsinline 属性来采用此新属性。此旧版属性自 iPhoneOS 4.0 以来一直受支持,根据我们更新的取消前缀策略,我们很高兴能够取消 webkit-playsinline 的前缀。不幸的是,此更改未能赶上 iOS 10 Developer Seed 2 的截止日期。如果您想在 iOS Developer Seed 2 中试用这项新策略,带有前缀的属性将起作用,但我们鼓励您在未来的某个种子版本中支持无前缀属性时,过渡到使用无前缀属性。

示例

那么,普通的网页开发者将如何利用这些新策略呢?假设某人有一篇包含许多 GIF 的博客文章或文章,而他们更愿意将其作为 <video> 元素来提供。这是一个简单的 GIF 替换示例:

<video autoplay loop muted playsinline>
  <source src="image.mp4">
  <source src="image.webm" onerror="fallback(parentNode)">
  <img src="image.gif">
</video>
function fallback(video)
{
  var img = video.querySelector('img');
  if (img)
    video.parentNode.replaceChild(img, video);
}

在 iOS 10 上,这提供了与直接使用 GIF 相同的用户体验,如果 <video> 的任何源不受支持,则优雅地回退到该 GIF。事实上,这段代码就是用来向您展示那个精彩 GIF 的。

如果您的页面设计需要根据是否允许内联播放或需要全屏播放来采用不同行为,请使用 -webkit-video-playable-inline 媒体查询来区分两者:

<div id="either-gif-or-video">
  <video src="image.mp4" autoplay loop muted playsinline></video>
  <img src="image.gif">
</div>
#either-gif-or-video video { display: none; }
@media (-webkit-video-playable-inline) {
    #either-gif-or-video img { display: none; }
    #either-gif-or-video video { display: initial; }
}

这些新策略意味着现在可以更高级地使用 <video> 元素,例如将正在播放的 <video> 绘制到 <canvas> 上,而无需将该 <video> 进入全屏模式。

  var video;
  var canvas;

  function startPlayback()
  {
    if (!video) {
      video = document.createElement('video');
      video.src = 'image.mp4';
      video.loop = true;
      video.addEventListener('playing', paintVideo);
    }
    video.play();
  }

  function paintVideo()
  {
    if (!canvas) {
      canvas = document.createElement('canvas');
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      document.body.appendChild(canvas);
    }
    canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
    if (!video.paused)
      requestAnimationFrame(paintVideo);
  }
<button onclick="startPlayback()">Start Playback</button> 

同样的技巧可以用于渲染到 WebGL 上下文。请注意,在此示例中,需要用户手势(即 click 事件),因为 <video> 元素不在 DOM 中,因此不可见。对于 <video style="display:none"><video style="visibility:hidden"> 也是如此。

我们相信这些新策略确实使视频成为设计现代、引人注目的网站的更有用工具,同时不会消耗用户的带宽或电池。如需更多信息,请通过 @jernoble 联系我,通过 @jonathandavis 联系 Apple 网页技术布道师 Jonathan Davis,或通过 @WebKit 联系 WebKit 团队。