未加星标

video.js 源码分析(JavaScript)

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二05 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

video.js 源码分析(javascript

组织结构

继承关系

运行机制

插件的运行机制

插件的定义

插件的运行

控制条是如何运行的

UI与JavaScript对象的衔接

类的挂载方式

存储

获取

组织结构

以下是video.js的源码组织结构关系,涉及控制条、菜单、浮层、进度条、滑动块、多媒体、音轨字幕、辅助函数集合等等。

├── control-bar │ ├── audio-track-controls │ │ ├── audio-track-button.js │ │ └── audio-track-menu-item.js │ ├── playback-rate-menu │ │ ├── playback-rate-menu-button.js │ │ └── playback-rate-menu-item.js │ ├── progress-control │ │ ├── load-progress-bar.js │ │ ├── mouse-time-display.js │ │ ├── play-progress-bar.js │ │ ├── progress-control.js │ │ ├── seek-bar.js │ │ └── tooltip-progress-bar.js │ ├── spacer-controls │ │ ├── custom-control-spacer.js │ │ └── spacer.js │ ├── text-track-controls │ │ ├── caption-settings-menu-item.js │ │ ├── captions-button.js │ │ ├── chapters-button.js │ │ ├── chapters-track-menu-item.js │ │ ├── descriptions-button.js │ │ ├── off-text-track-menu-item.js │ │ ├── subtitles-button.js │ │ ├── text-track-button.js │ │ └── text-track-menu-item.js │ ├── time-controls │ │ ├── current-time-display.js │ │ ├── duration-display.js │ │ ├── remaining-time-display.js │ │ └── time-divider.js │ ├── volume-control │ │ ├── volume-bar.js │ │ ├── volume-control.js │ │ └── volume-level.js │ ├── control-bar.js │ ├── fullscreen-toggle.js │ ├── live-display.js │ ├── mute-toggle.js │ ├── play-toggle.js │ ├── track-button.js │ └── volume-menu-button.js ├── menu │ ├── menu-button.js │ ├── menu-item.js │ └── menu.js ├── popup │ ├── popup-button.js │ └── popup.js ├── progress-bar │ ├── progress-control │ │ ├── load-progress-bar.js │ │ ├── mouse-time-display.js │ │ ├── play-progress-bar.js │ │ ├── progress-control.js │ │ ├── seek-bar.js │ │ └── tooltip-progress-bar.js │ └── progress-bar.js ├── slider │ └── slider.js ├── tech │ ├── flash-rtmp.js │ ├── flash.js │ ├── html5.js │ ├── loader.js │ └── tech.js ├── tracks │ ├── audio-track-list.js │ ├── audio-track.js │ ├── html-track-element-list.js │ ├── html-track-element.js │ ├── text-track-cue-list.js │ ├── text-track-display.js │ ├── text-track-list-converter.js │ ├── text-track-list.js │ ├── text-track-settings.js │ ├── text-track.js │ ├── track-enums.js │ ├── track-list.js │ ├── track.js │ ├── video-track-list.js │ └── video-track.js ├── utils │ ├── browser.js │ ├── buffer.js │ ├── dom.js │ ├── events.js │ ├── fn.js │ ├── format-time.js │ ├── guid.js │ ├── log.js │ ├── merge-options.js │ ├── stylesheet.js │ ├── time-ranges.js │ ├── to-title-case.js │ └── url.js ├── big-play-button.js ├── button.js ├── clickable-component.js ├── close-button.js ├── component.js ├── error-display.js ├── event-target.js ├── extend.js ├── fullscreen-api.js ├── loading-spinner.js ├── media-error.js ├── modal-dialog.js ├── player.js ├── plugins.js ├── poster-image.js ├── setup.js └── video.js

video.js的JavaScript部分都是采用面向对象方式来实现的。基类是Component,所有其他的类都是直接或间接集成此类实现。语法部分采用的是ES6标准。

继承关系

深入源码解读需要了解类与类之间的继承关系,直接上图。

所有的继承关系


video.js 源码分析(JavaScript)

主要的继承关系


video.js 源码分析(JavaScript)
运行机制

首先调用videojs启动播放器,videojs方法判断当前id是否已被实例化,如果没有实例化新建一个Player对象,因Player继承Component会自动初始化Component类。如果已经实例化直接返回Player对象。

videojs方法源码如下:

function videojs(id, options, ready) { let tag; // id可以是选择器也可以是DOM节点 if (typeof id === 'string') { if (id.indexOf('#') === 0) { id = id.slice(1); } //检查播放器是否已被实例化 if (videojs.getPlayers()[id]) { if (options) { log.warn(`Player "${id}" is already initialised. Options will not be applied.`); } if (ready) { videojs.getPlayers()[id].ready(ready); } return videojs.getPlayers()[id]; } // 如果播放器没有实例化,返回DOM节点 tag = Dom.getEl(id); } else { // 如果是DOM节点直接返回 tag = id; } if (!tag || !tag.nodeName) { throw new TypeError('The element or ID supplied is not valid. (videojs)'); } // 返回播放器实例 return tag.player || Player.players[tag.playerId] || new Player(tag, options, ready); } []()

接下来我们看下Player的构造函数,代码如下:

constructor(tag, options, ready) { // 注意这个tag是video原生标签 tag.id = tag.id || `vjs_video_${Guid.newGUID()}`; // 选项配置的合并 options = assign(Player.getTagSettings(tag), options); // 这个选项要关掉否则会在父类自动执行加载子类集合 options.initChildren = false; // 调用父类的createEl方法 options.createEl = false; // 在移动端关掉手势动作监听 options.reportTouchActivity = false; // 检查播放器的语言配置 if (!options.language) { if (typeof tag.closest === 'function') { const closest = tag.closest('[lang]'); if (closest) { options.language = closest.getAttribute('lang'); } } else { let element = tag; while (element && element.nodeType === 1) { if (Dom.getElAttributes(element).hasOwnProperty('lang')) { options.language = element.getAttribute('lang'); break; } element = element.parentNode; } } } // 初始化父类 super(null, options, ready); // 检查当前对象必须包含techOrder参数 if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) { throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); } // 存储当前已被实例化的播放器 this.tag = tag; // 存储video标签的各个属性 this.tagAttributes = tag && Dom.getElAttributes(tag); // 将默认的英文切换到指定的语言 this.language(this.options_.language); if (options.languages) { const languagesToLower = {}; Object.getOwnPropertyNames(options.languages).forEach(function(name) { languagesToLower[name.toLowerCase()] = options.languages[name]; }); this.languages_ = languagesToLower; } else { this.languages_ = Player.prototype.options_.languages; } // 缓存各个播放器的各个属性. this.cache_ = {}; // 设置播放器的贴片 this.poster_ = options.poster || ''; // 设置播放器的控制 this.controls_ = !!options.controls; // 默认是关掉控制 tag.controls = false; this.scrubbing_ = false; this.el_ = this.createEl(); const playerOptionsCopy = mergeOptions(this.options_); // 自动加载播放器插件 if (options.plugins) { const plugins = options.plugins; Object.getOwnPropertyNames(plugins).forEach(function(name) { if (typeof this[name] === 'function') { this[name](plugins[name]); } else { log.error('Unable to find plugin:', name); } }, this); } this.options_.playerOptions = playerOptionsCopy; this.initChildren(); // 判断是不是音频 this.isAudio(tag.nodeName.toLowerCase() === 'audio'); if (this.controls()) { this.addClass('vjs-controls-enabled'); } else { this.addClass('vjs-controls-disabled'); } this.el_.setAttribute('role', 'region'); if (this.isAudio()) { this.el_.setAttribute('aria-label', 'audio player'); } else { this.el_.setAttribute('aria-label', 'video player'); } if (this.isAudio()) { this.addClass('vjs-audio'); } if (this.flexNotSupported_()) { this.addClass('vjs-no-flex'); } if (!browser.IS_IOS) { this.addClass('vjs-workinghover'); } Player.players[this.id_] = this; this.userActive(true); this.reportUserActivity(); this.listenForUserActivity_(); this.on('fullscreenchange', this.handleFullscreenChange_); this.on('stageclick', this.handleStageClick_); }

在Player的构造器中有一句 super(null, options, ready); 实例化父类Component。我们来看下Component的构造函数:

constructor(player, options, ready) { // 之前说过所有的类都是继承Component,不是所有的类需要传player if (!player && this.play) { // 这里判断调用的对象是不是Player本身,是本身只需要返回自己 this.player_ = player = this; // eslint-disable-line } else { this.player_ = player; } this.options_ = mergeOptions({}, this.options_); options = this.options_ = mergeOptions(this.options_, options); this.id_ = options.id || (options.el && options.el.id); if (!this.id_) { const id = player && player.id && player.id() || 'no_player'; this.id_ = `${id}_component_${Guid.newGUID()}`; } this.name_ = options.name || null; if (options.el) { this.el_ = options.el; } else if (options.createEl !== false) { this.el_ = this.createEl(); } this.children_ = []; this.childIndex_ = {}; this.childNameIndex_ = {}; // 知道Player的构造函数为啥要设置initChildren为false了吧 if (options.initChildren !== false) { // 这个initChildren方法是将一个类的子类都实例化,一个类都对应着自己的el(DOM实例),通过这个方法父类和子类的DOM继承关系也就实现了 this.initChildren(); } this.ready(ready); if (options.reportTouchActivity !== false) { this.enableTouchActivity(); } } 插件的运行机制 插件的定义 import Player from './player.js'; // 将插件种植到Player的原型链 const plugin = function(name, init) { Player.prototype[name] = init; }; // 暴露plugin接口 videojs.plugin = plugin; 插件的运行 // 在Player的构造函数里判断是否使用了插件,如果有遍历执行 if (options.plugins) { const plugins = options.plugins; Object.getOwnPropertyNames(plugins).forEach(function(name) { if (typeof this[name] === 'function') { this[name](plugins[name]); } else { log.error('Unable to find plugin:', name); } }, this); } 控制条是如何运行的 Player.prototype.options_ = { // 此处表示默认使用html5的video标签 techOrder: ['html5', 'flash'], html5: {}, flash: {}, // 默认的音量,官方代码该配置无效有bug,我们已修复, defaultVolume: 0.85, // 用户的交互时长,比如超过这个时间表示失去焦点 inactivityTimeout: 2000, playbackRates: [], // 这是控制条各个组成部分,作为Player的子类 children: [ 'mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'progressBar', 'controlBar', 'errorDisplay', 'textTrackSettings' ], language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', languages: {}, notSupportedMessage: 'No compatible source was found for this media.' };

Player类中有个children配置项,这里面是控制条的各个组成部分的类。各个UI类还有子类,都是通过children属性链接的。

UI与JavaScript对象的衔接

video.js里都是组件化实现的,小到一个按钮大到一个播放器都是一个继承了Component类的对象实例,每个对象包含一个el属性,这个el对应一个DOM实例,el是通过createEl生成的DOM实例,在Component基类中包含一个方法createEl方法,子类也可以重写该方法。类与类的从属关系是通过children属性连接。

那么整个播放器是怎么把播放器的UI加载到HTML中的呢?在Player的构造函数里可以看到先生成el,然后初始化父类遍历Children属性,将children中的类实例化并将对应的DOM嵌入到player的el属性中,最后在Player的构造函数中直接挂载到video标签的父级DOM上。

if (tag.parentNode) { tag.parentNode.insertBefore(el, tag); }

这里的tag指的是video标签。

类的挂载方式

上文有提到过UI的从属关系是通过类的children方法连接的,但是所有的类都是关在Component类上的。这主要是基于对模块化的考虑,通过这种方式实现了模块之间的通信。

存储 static registerComponent(name, comp) { if (!Component.components_) { Component.components_ = {}; } Component.components_[name] = comp; return comp; } 获取 static getComponent(name) { if (Component.components_ && Component.components_[name]) { return Component.components_[name]; } if (window && window.videojs && window.videojs[name]) { log.warn(`The ${name} component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)`); return window.videojs[name]; } }

在Componet里有个静态方法是registerComponet,所有的组件类都注册到Componet的components_属性里。

例如控制条类ControlBar就是通过这个方法注册的。

Component.registerComponent('ControlBar', ControlBar);

在Player的children属性里包括了controlBar类,然后通过getComponet获取这个类。

.filter((child) => { const c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); return c && !Tech.isTech(c); })

如有疑问,请留言。

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

主题: JavaScriptJavaHTML多媒体模块化
分页:12
转载请注明
本文标题:video.js 源码分析(JavaScript)
本站链接:http://www.codesec.net/view/481265.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(96)