[译] 网速敏感的视频延迟加载方案

news/2024/7/5 10:16:06

  • 原文地址:Lazy Loading Video Based on Connection Speed
  • 原文作者:Ben Robertson
  • 译文出自:掘金翻译计划
  • 本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO1/lazy-loading-video-based-on-connection-speed.md
  • 译者:SHERlocked93
  • 校对者:Reaper622、Fengziyin1234

一个大视频的背景,如果做的好,会是一个绝佳的体验!但是,在首页添加一个视频并不仅仅是随便找个人,然后加个 25mb 的视频,那会让你的所有的性能优化都付之一炬。

Lazy pandas love lazy loading. (Photo by Elena Loshina)

我参加过一些团队,他们希望给首页加上类似的全屏视频背景。我通常不愿意那么做,因为这种做法通常会导致性能上的噩梦。老实说,我曾给一个页面加上一个 40mb 大的视频。 ?

上次有人让我这么做的时候,我很好奇应如何将背景视频的加载作为渐进增强(Progressive Enhancement),来提升网络连接状况比较好的用户的体验。除了和我的同事们强调视频体积小和压缩视频的重要性以外,也希望在代码上有一些奇迹发生。

下面是最终的解决方案:

  1. 尝试使用 JavaScript 加载 <source>
  2. 监听 canplaythrough 事件
  3. 如果 canplaythrough 事件没有在 2 秒内触发,那么使用 Promise.race() 将视频加载超时
  4. 如果没有监听到 canplaythrough 事件,那么移除 <source>,并且取消视频加载
  5. 如果监测到 canplaythrough 事件,那么使用淡入效果显示这个视频

标记

这里要注意的问题是,即使我正在 <video> 标签中使用 <source>,但我还没为这些 <source> 设置 src 属性。如果设置了 src 属性,那么浏览器会自动地找到它可以播放的第一个 <source>,并立即开始下载它。

因为在这个例子中,视频是作为渐进增强的对象,默认情况下我们不用真的加载视频。事实上唯一需要加载的,是我们为这个页面设置的预览图片。

  <video class="js-video-loader" poster="<?= $poster; ?>" muted="true" loop="true">
    <source data-src="path/to/video.webm" type="video/webm">
    <source data-src="path/to/video.mp4" type="video/mp4">
  </video>

JavaScript

我编写了一个简单的 JavaScript 类,用于查找带有 .js-video-loader 这个 class 的 video 元素,让我们以后可以在其他视频中复用这个逻辑。完整的源码可以从 Github 上看到。

构造函数是这样的:

  constructor () {
    this.videos = Array.from(document.querySelectorAll('video.js-video-loader'));
    // 将在下面情况下返回
    // - 浏览器不支持 Promise
    // - 没有 video 元素
    // - 如果用户设置了减少动态偏好(prefers reduced motion) 
    // - 在移动设备上
    if (typeof Promise === 'undefined'
      || !this.videos
      || window.matchMedia('(prefers-reduced-motion)').matches
      || window.innerWidth < 992
    ) {
      return;
    }
    this.videos.forEach(this.loadVideo.bind(this));
  }

这里我们所做的就是找到这个页面上所有我们希望延迟加载的视频。如果没有,我们可以返回。当用户开启了减少动态偏好(preference for reduced motion)设置时,我们同样不会加载这样的视频。为了不让某些低网速或低图形处理能力的手机用户担心,在小屏幕手机上也会直接返回。(我在考虑是否可以通过 <source> 元素的媒体查询来做这些,但也不确定。)

然后给每个视频运行这个视频加载逻辑。

loadVideo

loadVideo() 是一个调用其他函数的简单的函数:

  loadVideo(video) {
    this.setSource(video);
    // 加上了视频链接后重新加载视频
    video.load();
    this.checkLoadTime(video);
  }

setSource

setSource() 中,我们找到那些作为数据属性(Data Attributes)插入的视频链接,并且将它们设置为真正的 src 属性。

  /**
    * 找 video 子元素中是 <source> 的,
    * 基于 data-src 属性,
    * 给每个 <source> 设置 src 属性
    *
    * @param {DOM Object} video
    */
    setSource (video) {
      let children = Array.from(video.children);
      children.forEach(child => {
        if (child.tagName === 'SOURCE' && typeof child.dataset.src !== 'undefined') {
          child.setAttribute('src', child.dataset.src);
        }
      });
    }

基本上,我所做的就是遍历每一个 <video> 元素的子元素,找一个定义了 data-src 属性(child.dataset.src)的 <source> 子元素。如果找到了,那就用 setAttribute 将它的 src 属性设置为视频链接。

现在视频链接已经被设置给 <video> 元素了,下面需要让浏览器再次加载视频。我们通过在 loadVideo() 中的 video.load() 来完成这个工作。load() 方法是 HTMLMediaElement API 的一部分,它可以重置媒体元素并且重启加载过程。

checkLoadTime

接下来是见证奇迹的时刻。在 checkLoadTime() 方法中我们创建了两个 Promise。第一个 Promise 将在 <video> 元素的 canplaythrough 事件触发时被 resolve。这个 canplaythrough 事件是浏览器认为这个视频可以在不停下来缓冲的情况下持续播放的时候被触发。我们在这个 Promise 中添加一个这个事件的监听回调,当这个事件触发的时候执行 resolve()

  // 创建一个 Promise,将在
  // video.canplaythrough 事件发生时被 resolve
  let videoLoad = new Promise((resolve) => {
    video.addEventListener('canplaythrough', () => {
      resolve('can play');
    });
  });

我们同时创建另一个 Promise 作为计时器。在这个 Promise 中,当经过一个设定好的时间后,我们使用 setTimeout 来将这个 Promise 给 resolve 掉,我这设置了一个 2 秒的时延(2000毫秒)。

  // 创建一个 Promise 将在
  // 特定时间(2s)后被 resolve
  let videoTimeout = new Promise((resolve) => {
    setTimeout(() => {
      resolve('The video timed out.');
    }, 2000);
  });

现在我们有了两个 Promise,我们可以通过 Promise.race() 看他们谁先完成。

  // 将 promises 进行 Race 看看哪个先被 resolves
  Promise.race([videoLoad, videoTimeout]).  then(data => {
    if (data === 'can play') {
      video.play();
      setTimeout(() => {
        video.classList.add('video-loaded');
      }, 3000);
    } else {
      this.cancelLoad(video);
    }
  });

在这个 .then() 的回调中我们等着拿到最先被 resolve 的那个 Promise 传回来的信息。如果这个视频可以播放,那么我就会拿到之前传的 can play,然后试一下是否可以播放这个视频。video.play() 是使用 HTMLMediaElement 提供的 play() 方法来触发视频播放。

3 秒后,setTimeout() 将会给这个标签加上 .video-loaded 类,这将有助于视频文件更巧妙的淡入自动循环播放。

如果我们没接收到 can play 字符串,那么我们将取消这个视频的加载。

cancelLoad

cancelLoad() 方法做的基本上跟 loadVideo() 方法相反。它从每个 source 标签移除 src 属性,并且触发 video.load() 来重置视频元素。

如果我们不这么做,这个视频元素将会在后台保持加载状态,即使我们都没将它显示出来。

  /**
    * 通过移除所有的 <source> 来取消视频加载
    * 然后触发 video.load().
    *
    * @param {DOM object} video
    */
    cancelLoad (video) {
      let children = Array.from(video.children);
      children.forEach(child => {
        if (child.tagName === 'SOURCE' && typeof child.dataset.src !== 'undefined') {
          child.parentNode.removeChild(child);
        }
      });
      // 重新加载没有 <source> 标签的 video
      // 这样它会停止下载
      video.load();
    }

总结

这个方法的缺点是,我们仍然试图通过一个不一定靠谱的链接来下载一个可能比较大的文件,但是通过提供一个超时时间,我们希望能够给某些网速慢的用户节约一些流量并且获得更好的性能。根据我在 Chrome Dev Tools 里将网速节流到慢 3G 条件下的测试,这个方法将在超时之前加载了 512kb 的视频。即使是一个 3-5mb 的视频,对于一些网速慢的用户来说,这也带来了显著的流量节省。

你觉得怎么样?如果有改进的建议,欢迎在评论里分享!


Originally published at benrobertson.io.

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。

掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、 iOS、 前端、 后端、 区块链、 产品、 设计、 人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、 官方微博、 知乎专栏。

http://www.niftyadmin.cn/n/4594170.html

相关文章

【ArcGIS Pro二次开发】(46):要素类从上到下、从左到右排序

要素类经过编辑之后&#xff0c;【OBJECTID】字段会变得不规律。应部分网友要求&#xff0c;做了这个从上到下、从左到右排序的工具。 不过后来在ArcGIS Pro中发现了一个【排序】工具&#xff0c;已经可以完美实现这个功能需求&#xff0c;发现自己做了个白工。 不过做了不能白…

飞思卡尔MC9S12X Time输出比较

今天带着大家看下飞思卡尔MC9S12 Time模块的输出比较。具体代码如下/*************************************************************//* 初始化锁相环 *//* 使用外部晶振&#xff1a;16MHz …

统计图钻取的明细报表在非模态窗口中显示

润乾报表的统计图可以在图例 / 图形上设置超链接&#xff0c;从而实现钻取到明细报表的操作&#xff0c;钻取得到的报表可以在新窗口或者当前窗口中打开。关于如何在统计图的图形 / 图例上设置超链接&#xff0c;在用户手册中有常用示例的介绍。今天小编要给大家介绍的是如何在…

splunk数据搜索引擎软件的功能

转载于:https://blog.51cto.com/51fixcom/280229

非常值得一看—九种滤波算法C语言实现

关注“嵌入式软件开发学习圈”免费获取更多学习教程今天带着大家学习滤波算法c语言&#xff08;九种滤波算法&#xff09;实现&#xff0c;以及代码&#xff0c;大家可以学习了解下。。。。1.限幅滤波算法&#xff08;程序判断滤波算法&#xff09; 方法解析&#xff1a; 根据经…

DLNg[结构化ML项目]第二周迁移学习+多任务学习

1.迁移学习 比如要训练一个放射科图片识别系统&#xff0c;但是图片非常少&#xff0c;那么可以先在有大量其他图片的训练集上进行训练&#xff0c;比如猫狗植物等的图片&#xff0c;这样训练好模型之后就可以转移到放射科图片上&#xff0c;模型已经从其他图片中学习到了低层的…

C语言 七大查找算法学习

【点击上方蓝字关注「嵌入式软件开发学习圈」每晚推送】 今天带着大家学习七大查找算法&#xff0c;剩下的四大算法。斐波那契查找、树表查找、查找树、红黑树。① 斐波那契查找 在介绍斐波那契查找算法之前&#xff0c;我们先介绍一下很它紧密相连并且大家都熟知的一个概念——…

mysql增加索引但查询速度慢,查看sql执行计划索引使用情况(sql优化解决方案)...

使用explain sql可以查看sql中的索引使用情况&#xff08;在下的表为中文表&#xff0c;见笑&#xff09; 上图为索引没有命中的情况 该图是索引使用后情况 1. possible_keys&#xff1a;显示可能应用在这张表中的索引&#xff0c;一个或多个。查询涉及到的字段上若存在索引&a…