Video.js 博客

加里·卡采夫曼2017-03-27

功能聚焦:中间件

中间件是 Video.js 6.0 版本中即将推出的酷炫新功能之一

通过中间件,您现在能够与播放器和技术组件进行交互,并改变它们之间的通信方式。技术组件是 Video.js 对播放器的抽象,它将播放器 API 与播放技术分离。借助技术组件,我们可以在不改变外部 API 或播放器外观的情况下,将 Flash 回退或 YouTube 嵌入等功能集成到 Video.js 中。

Video.js 中间件类似于 Express 中间件,但路由是基于视频 MIME 类型。

许多 Video.js 用户可能熟悉 Express 等项目中的中间件。Video.js 中间件与它们没有太大区别。在这两种情况下,您都将中间件注册到特定路由,以便在触发该路由时调用链。在 Express 中,路由基于 URL 路径。在 Video.js 中,这些路由基于视频 MIME 类型。而且,与 Express 一样,还有匹配所有路由的“星号”(*)中间件。

中间件有两个需要注意的重要部分

  1. 动态源处理

  2. 拦截播放器和技术组件的交互。

视频目录

通过动态源处理,您可以加载具有自定义类型和来源的视频,并异步解析它。一个很好的例子是视频目录系统。页面可以呈现一个特定的目录 ID 和一个特殊的 MIME 类型,如下所示

<video controls class="video-js">
  <source src="123" type="video/my-catalog">
</video>

然后,您可以为该路由注册一个中间件——类型为 video/my-catalog

// middleware methods get the player instance as an argument
videojs.use('video/my-catalog', function(player) {

  // middleware are expected to return an object with the methods on it.
  // It can be a plain object or an instance of something.
  return {

    // setSource allows you to tell Video.js whether you're going to be handling the source or not
    setSource(srcObj, next) {
      const id = srcObj.src;

      videojs.xhr({
        uri: '/getVideo?id=' + id
      }, function(err, res, body) {

        // pass null as the first argument to say everything is going fine and we can handle it.
        next(null, {
          src: body.sourceUrl,
          type: body.sourceType
        })
      });
    }
  };
});

然后,当 Video.js 初始化时,它将遍历并调用为 video/my-catalog 设置的中间件。

服务器端广告插入

服务器端广告插入(SSAI)非常适合中间件。它展示了拦截播放和技术组件交互的能力。例如,在您的 HLS 清单中,有一个 30 秒的广告,接着是一个 5 分钟的视频。您希望时间线在播放广告和内容时分别显示相应的广告时间和内容时间。目前,显示的持续时间将是 5 分钟和 30 秒的总和(5:30)。解决方案是添加一个中间件,它知道何时播放广告,并告诉播放器持续时间为 30 秒;当播放内容时,持续时间为 5 分钟。

// register a star-middleware because HLS has two mimetypes
videojs.use('*', function(player) {
  return {
    setSource(srcObj, next) {
      const type = srcObj.type;

      if (type !== 'application/x-mpegurl' && type !== 'application/vnd.apple.mpegurl') {

        // call next with an error to signal you cannot handle the source
        next(new Error('Source is not an HLS source'));
      } else {

        // in here we know we're playing back an HLS source.
        // We don't want to do anything special for it, so, pass along the source along with a null.
        next(null, srcObj);
      }
    },

    // this method gets called on the tech and then up the middleware chain providing the values as you go along
    duration(durationFromTech) {
      if (areWeCurrentlyPlayingAnAd(durationFromTech)) {
        // since we're now in an ad, return the ad duration
        // in a real example you'd calculate this based on your playlist
        // rather than hardcode a value in here
        return 30;
      } else {
        // we're playing back content, so, return that duration
        return 5 * 60;
      }
    }
  }
});

播放速率调整 - 一个案例研究

一个简单但有趣的中间件是 playbackrate-adjuster。这个中间件会根据当前速率改变控件的时间显示。例如,如果您正在播放一个 20 分钟的视频,并将速率改为 2 倍,控件将调整显示 10 分钟。让我们来看看代码。

videojs.use('*', function(player) {
  /* ... */

  return {
    setSource(srcObj, next) {
      next(null, srcObj);
    },

    duration(dur) {
      return dur / player.playbackRate();
    },

    /* ... */
  };
});

因此,在这里,我们附加了一个星号中间件,因为我们希望它应用于任何视频,无论 MIME 类型如何。在 setSource 中,我们还直接使用 nullsrcObj 调用 next,因为我们希望将此中间件用于所有来源。我们还设置了 duration 方法,以接收来自上一个中间件的持续时间,并将其除以从播放器获得的播放速率。

如果您查看代码,您会看到除了 duration 之外的一些其他方法。它们在那里是为了确保依赖计时的其他方法得到更新。需要注意的两个方法是 currentTimesetCurrentTimecurrentTime 在我们想要知道当前时间时被调用。setCurrentTime 在我们进行查找时被调用。由于用户在偏移的时间中进行查找,我们希望反向应用我们的更改操作。我们不进行除法,而是进行乘法。

    currentTime(ct) {
      return ct / player.playbackRate();
    },

    setCurrentTime(ct) {
      return ct * player.playbackRate();
    },

如果您应用我们目前所做的,您会注意到没有任何变化,控制栏仍然显示 20 分钟的持续时间。这是因为据 Video.js 所知,没有任何改变。因此,我们需要告诉 Video.js 持续时间已经改变。我们可以通过存储 Video.js 在源选择完成后提供给我们的技术组件来做到这一点。

videojs.use('*', function(player) {
  let tech;

  return {
    setTech(newTech) {
      tech = newTech;
    }

    /* ... */
  };
});

然后,当 ratechange 事件触发时,我们告诉 Video.js 持续时间已改变,Video.js 将相应地更新控件。

videojs.use('*', function(player) {
  let tech;

  player.on('ratechange', function() {
    tech.trigger('durationchange');
    tech.trigger('timeupdate');
  });

  return {
   /* ... */
  }
});

您可以在此处查看实时示例,并在此处查看完整代码