组件(Components)

点击下载教程相关资源

注意:本指南主要针对 Video.js 8+。它不再提及 videojs.extend() 方法的使用,该方法已在 8.0 中移除。有关详细信息,请参阅我们的迁移指南

Video.js 播放器的架构以组件为中心。

播放器类以及代表播放器控件和其他 UI 元素的所有类都继承自 Component 类。通过这种架构,我们可以很容易地以树状结构构建 Video.js 播放器的用户界面,这种结构与 DOM 类似。

什么是组件?

组件是一种 JavaScript 对象,具有以下特性:

  • 几乎在所有情况下都与 DOM 元素相关联。

  • 与播放器对象相关联。

  • 能够管理任意数量的子组件。

  • 能够监听和触发事件。

  • 初始化和销毁的生命周期。

有关组件编程接口的更多细节,请参阅组件 API 文档

添加组件

一个组件的实例可以用 new Component() 创建。

// 创建按钮实例
var player = videojs('some-video-id');
var Button = videojs.getComponent('Button');
var button = new Button(player, {
  clickHandler: function(event) {
    videojs.log('Clicked');
  }
});

console.log(button.el());

上述代码将输出:

<button class="vjs-control vjs-button" type="button" aria-disabled="false">
  <span class="vjs-icon-placeholder" aria-hidden="true"></span>
  <span class="vjs-control-text" aria-live="polite"></span>
</button>

请注意,这虽然创建了按钮,但并未将其添加到 DOM 中。将其作为组件的子组件添加到 DOM 中。

player.getChild('ControlBar').addChild(button);

或者,通过向 addChild 传递一个组件的名称,就能在一个步骤中创建并添加该组件的新实例。

// 创建按钮实例并将其添加到播放器的控制栏中
var player = videojs('some-video-id');
var button = player.getChild('ControlBar').addChild('button', {
  clickHandler: function(event) {
    videojs.log('Clicked');
  }
});

console.log(button.el());
// 将产生与上例相同的 HTML 结果

按钮应有文字。默认按钮不显示文字,但 screenreaders 会显示文字,浏览器也会以工具提示的形式显示。按钮文本可以作为选项设置:

const myButton = player.getChild('ControlBar').addChild('button', {
  controlText: 'My button'
});

要显示控件文本,请为按钮添加 vjs-visibile-text 类。

const myButton = player.getChild('ControlBar').addChild('button', {
  controlText: 'My button',
  className: 'vjs-visible-text'
});

还可以在创建按钮后设置类和控制文本。

myButton.controlText('My button');
myButton.addClass('vjs-visible-text');

在播放器上启用 experimentalSvgIcons 选项后,可以使用 setIcon 方法在任何组件上添加或替换图标。

sandbox/svg-icons.html.example 重命名为 sandbox/svg-icons.html,使用 npm run build 构建 Video.js,然后在所选浏览器中打开 sandbox/svg-icons.html,即可查看所有可用图标。

myButton.setIcon('play');

创建组件

Video.js 组件是 es6 类,可以使用类语法进行扩展,并在 Video.js 中注册,从而为播放器添加新功能和用户界面。

有几个函数用于创建和注册组件:

  • videojs.getComponent(String name): 从 Video.js 检索组件类。

  • videojs.registerComponent(String name, Function Comp):向 Video.js 注册组件类。

此示例创建了一个标题栏组件,作为 Component 的扩展类。

// 从 Video.js 获取组件基类
const Component = videojs.getComponent('Component');

class TitleBar extends Component {

  // 组件的构造函数会收到两个参数:将与之关联的播放器和一个选项对象。
  constructor(player, options = {}) {

    // 重要的是要先调用超类,这样才能获得组件的所有功能!
    super(player, options);

    // 如果传入了 `text` 选项,则更新组件的文本内容。
    if (options.text) {
      this.updateTextContent(options.text);
    }
  }

  // 组件的 `createEl` 函数创建其 DOM 元素。
  createEl() {
    return videojs.dom.createEl('div', {
      // 在播放器中以 "vjs-"作为元素类的前缀是 Video.js 中使用的惯例。
      className: 'vjs-title-bar'
    });
  }

  // 该函数可随时调用,以更新组件的文本内容。
  updateTextContent(text) {

    // 如果没有提供文本,则默认为 "Title Unknown"。
    if (typeof text !== 'string') {
      text = 'Title Unknown';
    }

    // 使用 Video.js 实用 DOM 方法来操作组件元素的内容。
    videojs.emptyEl(this.el());
    videojs.appendContent(this.el(), text);
  }
}

请注意,由于 extend() 函数已被移除,在转译为 ES5 时,要扩展一个组件,需要使用以下语法。

// 我的 ES5 构造函数扩展了 Component
function MyComponent(arguments) {
  Component.call(this, arguments);
}

MyComponent.prototype = Object.create(Component.prototype);

创建组件后,就可以在播放器中注册和使用。

// 在 Video.js 中注册该组件,以便在播放器中使用。
videojs.registerComponent('TitleBar', TitleBar);

// 创建播放器
var player = videojs('my-player');

// 将 TitleBar 添加为播放器的后代,并在其选项中提供一些文本。
player.addChild('TitleBar', {text: 'The Title of The Video!'});

这个 JSBin 就是一个活生生的例子。

子组件

关于管理组件结构可用方法的完整细节,请再次参阅组件API 文档

基本示例

将子组件添加到父组件时,Video.js 会将子组件的元素插入父组件的元素中。例如,添加这样一个组件:

// 为播放器添加一个 "BigPlayButton" 组件。它的元素将追加到播放器的元素中。
player.addChild('BigPlayButton');

结果是一个 DOM,看起来像这样:

<!-- Player 元素 -->
<div class="video-js">
  <!-- BigPlayButton 元素 -->
  <div class="vjs-big-play-button"></div>
</div>

相反,删除子组件会从 DOM 中删除子组件的元素:

player.removeChild('BigPlayButton');

结果是一个 DOM,看起来像这样:

<!-- Player 元素 -->
<div class="video-js">
</div>

使用选项

通过传入子构造函数的选项和 children 选项。

var player = videojs('some-vid-id');
var Component = videojs.getComponent('Component');
var myComponent = new Component(player);
var myButton = myComponent.addChild('MyButton', {
  text: 'Press Me',
  buttonChildExample: {
    buttonChildOption: true
  }
});

也可以在组件初始化时通过选项添加子组件。

注意:包含一个 "name" 键,如果两个相同类型的子组件需要不同的选项,则使用该键。

// MyComponent 源自上述示例
var myComp = new MyComponent(player, {
  children: ['button', {
    name: 'button',
    someOtherOption: true
  }, {
    name: 'button',
    someOtherOption: false
  }]
});

事件监听

使用 on

var player = videojs('some-player-id');
var Component = videojs.getComponent('Component');
var myComponent = new Component(player);
var myFunc = function() {
  var myComponent = this;
  console.log('myFunc called');
};

myComponent.on('eventType', myFunc);
myComponent.trigger('eventType');
// logs 'myFunc called'

除非已绑定,否则 myFunc 的上下文将是 myComponent。您可以将监听器添加到另一个元素或组件中。

var otherComponent = new Component(player);

// myComponent/myFunc is from the above example
myComponent.on(otherComponent.el(), 'eventName', myFunc);
myComponent.on(otherComponent, 'eventName', myFunc);

otherComponent.trigger('eventName');
// logs 'myFunc called' twice

使用 off

var player = videojs('some-player-id');
var Component = videojs.getComponent('Component');
var myComponent = new Component(player);
var myFunc = function() {
  var myComponent = this;
  console.log('myFunc called');
};
myComponent.on('eventType', myFunc);
myComponent.trigger('eventType');
// logs 'myFunc called'

myComponent.off('eventType', myFunc);
myComponent.trigger('eventType');
// does nothing

如果 myFunc 被排除,该事件类型的所有监听器都将被移除。如果 eventType 被排除,组件中的所有监听器都将被移除。您可以使用 off 功能移除添加到其他元素或组件的监听器:

myComponent.on(otherComponent...

在这种情况下,事件类型和监听器功能都是必须的。

var otherComponent = new Component(player);

// myComponent/myFunc 来自上述示例
myComponent.on(otherComponent.el(), 'eventName', myFunc);
myComponent.on(otherComponent, 'eventName', myFunc);

otherComponent.trigger('eventName');
// logs 'myFunc called' twice
myComponent.off(otherComponent.el(), 'eventName', myFunc);
myComponent.off(otherComponent, 'eventName', myFunc);
otherComponent.trigger('eventName');
// does nothing

使用 one

var player = videojs('some-player-id');
var Component = videojs.getComponent('Component');
var myComponent = new Component(player);
var myFunc = function() {
  var myComponent = this;
  console.log('myFunc called');
};
myComponent.one('eventName', myFunc);
myComponent.trigger('eventName');
// logs 'myFunc called'

myComponent.trigger('eventName');
// does nothing

您还可以在另一个元素或组件中添加监听器,只触发一次。

var otherComponent = new Component(player);

// myComponent/myFunc 来自上述示例
myComponent.one(otherComponent.el(), 'eventName', myFunc);
myComponent.one(otherComponent, 'eventName', myFunc);

otherComponent.trigger('eventName');
// logs 'myFunc called' twice

otherComponent.trigger('eventName');
// does nothing

使用 trigger

var player = videojs('some-player-id');
var Component = videojs.getComponent('Component');
var myComponent = new Component(player);
var myFunc = function(data) {
  var myComponent = this;
  console.log('myFunc called');
  console.log(data);
};
myComponent.one('eventName', myFunc);
myComponent.trigger('eventName');
// logs 'myFunc called' and 'undefined'

myComponent.trigger({'type':'eventName'});
// logs 'myFunc called' and 'undefined'

myComponent.trigger('eventName', {data: 'some data'});
// logs 'myFunc called' and "{data: 'some data'}"

myComponent.trigger({'type':'eventName'}, {data: 'some data'});
// logs 'myFunc called' and "{data: 'some data'}"

默认组件树

Video.js 播放器的默认组件结构如下所示:

Player
├── MediaLoader (has no DOM element)
├── PosterImage
├── TextTrackDisplay
├── LoadingSpinner
├── BigPlayButton
├── LiveTracker (has no DOM element)
├─┬ ControlBar
│ ├── PlayToggle
│ ├── VolumePanel
│ ├── CurrentTimeDisplay (hidden by default)
│ ├── TimeDivider (hidden by default)
│ ├── DurationDisplay (hidden by default)
│ ├─┬ ProgressControl (hidden during live playback, except when liveui: true)
│ │ └─┬ SeekBar
│ │   ├── LoadProgressBar
│ │   ├── MouseTimeDisplay
│ │   └── PlayProgressBar
│ ├── LiveDisplay (hidden during VOD playback)
│ ├── SeekToLive (hidden during VOD playback)
│ ├── RemainingTimeDisplay
│ ├── CustomControlSpacer (has no UI)
│ ├── PlaybackRateMenuButton (hidden, unless playback tech supports rate changes)
│ ├── ChaptersButton (hidden, unless there are relevant tracks)
│ ├── DescriptionsButton (hidden, unless there are relevant tracks)
│ ├── SubsCapsButton (hidden, unless there are relevant tracks)
│ ├── AudioTrackButton (hidden, unless there are relevant tracks)
│ ├── PictureInPictureToggle
│ └── FullscreenToggle
├── ErrorDisplay (hidden, until there is an error)
├── TextTrackSettings
└── ResizeManager (hidden)

具体组件细节

播放切换

PlayToggle 有一个重放选项,可以显示或隐藏重放图标。可通过 {replay: false} 设置该选项,因为默认情况下重放图标会在视频播放结束后显示。

隐藏重放图标示例:

let player = videojs('myplayer', {
  controlBar: {
    playToggle: {
      replay: false
    }
  }
});

音量面板

VolumePanel 包括 MuteToggle 和 VolumeControl 组件,如果不支持音量更改,这些组件将被隐藏。VolumePanel 有一个重要选项,可以使音量控制垂直显示在静音切换器上。可通过 VolumePanel {inline: false} 设置该选项,因为默认情况下音量控制是水平的,且带有 {inline: true}。

垂直音量控制示例:

let player = videojs('myplayer', {
  controlBar: {
    volumePanel: {
      inline: false
    }
  }
});

文本轨道设置

文本轨道设置组件仅在使用模拟文本轨道时可用。

调整大小管理器

这个新组件负责在玩家大小发生变化时触发玩家调整大小事件。如果 ResizeObserver 可用或提供了多重填充,它就会使用 ResizeObserver。使用 ResizeObserver 时,它没有任何元素。如果 ResizeObserver 不可用,它就会回退到 iframe 元素,并通过去掉处理程序监听其调整大小事件。

ResizeObserver 填充程序可以这样传递:

var player = videojs('myplayer', {
  resizeManager: {
    ResizeObserver: ResizeObserverPoylfill
  }
});

要强制使用 iframe 回退,请将 null 作为 ResizeObserver 传入:

var player = videojs('myplayer', {
  resizeManager: {
    ResizeObserver: null
  }
});

也可以像这样禁用 ResizeManager:

var player = videojs('myplayer', {
  resizeManager: false
});
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号