摘要:語(yǔ)法部分采用的是標(biāo)準(zhǔn)。那么整個(gè)播放器是怎么把播放器的加載到中的呢在的構(gòu)造函數(shù)里可以看到先生成,然后初始化父類(lèi)遍歷屬性,將中的類(lèi)實(shí)例化并將對(duì)應(yīng)的嵌入到的屬性中,最后在的構(gòu)造函數(shù)中直接掛載到標(biāo)簽的父級(jí)上。
video.js 源碼分析(JavaScript)
組織結(jié)構(gòu)
繼承關(guān)系
運(yùn)行機(jī)制
插件的運(yùn)行機(jī)制
插件的定義
插件的運(yùn)行
控制條是如何運(yùn)行的
UI與JavaScript對(duì)象的銜接
類(lèi)的掛載方式
存儲(chǔ)
獲取
組織結(jié)構(gòu)以下是video.js的源碼組織結(jié)構(gòu)關(guān)系,涉及控制條、菜單、浮層、進(jìn)度條、滑動(dòng)塊、多媒體、音軌字幕、輔助函數(shù)集合等等。
├── 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部分都是采用面向?qū)ο蠓绞絹?lái)實(shí)現(xiàn)的。基類(lèi)是Component,所有其他的類(lèi)都是直接或間接集成此類(lèi)實(shí)現(xiàn)。語(yǔ)法部分采用的是ES6標(biāo)準(zhǔn)。
繼承關(guān)系深入源碼解讀需要了解類(lèi)與類(lèi)之間的繼承關(guān)系,直接上圖。
所有的繼承關(guān)系
主要的繼承關(guān)系
首先調(diào)用videojs啟動(dòng)播放器,videojs方法判斷當(dāng)前id是否已被實(shí)例化,如果沒(méi)有實(shí)例化新建一個(gè)Player對(duì)象,因Player繼承Component會(huì)自動(dòng)初始化Component類(lèi)。如果已經(jīng)實(shí)例化直接返回Player對(duì)象。
videojs方法源碼如下:
function videojs(id, options, ready) { let tag; // id可以是選擇器也可以是DOM節(jié)點(diǎn) if (typeof id === "string") { if (id.indexOf("#") === 0) { id = id.slice(1); } //檢查播放器是否已被實(shí)例化 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]; } // 如果播放器沒(méi)有實(shí)例化,返回DOM節(jié)點(diǎn) tag = Dom.getEl(id); } else { // 如果是DOM節(jié)點(diǎn)直接返回 tag = id; } if (!tag || !tag.nodeName) { throw new TypeError("The element or ID supplied is not valid. (videojs)"); } // 返回播放器實(shí)例 return tag.player || Player.players[tag.playerId] || new Player(tag, options, ready); } []()
接下來(lái)我們看下Player的構(gòu)造函數(shù),代碼如下:
constructor(tag, options, ready) { // 注意這個(gè)tag是video原生標(biāo)簽 tag.id = tag.id || `vjs_video_${Guid.newGUID()}`; // 選項(xiàng)配置的合并 options = assign(Player.getTagSettings(tag), options); // 這個(gè)選項(xiàng)要關(guān)掉否則會(huì)在父類(lèi)自動(dòng)執(zhí)行加載子類(lèi)集合 options.initChildren = false; // 調(diào)用父類(lèi)的createEl方法 options.createEl = false; // 在移動(dòng)端關(guān)掉手勢(shì)動(dòng)作監(jiān)聽(tīng) options.reportTouchActivity = false; // 檢查播放器的語(yǔ)言配置 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; } } } // 初始化父類(lèi) super(null, options, ready); // 檢查當(dāng)前對(duì)象必須包含techOrder參數(shù) 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?"); } // 存儲(chǔ)當(dāng)前已被實(shí)例化的播放器 this.tag = tag; // 存儲(chǔ)video標(biāo)簽的各個(gè)屬性 this.tagAttributes = tag && Dom.getElAttributes(tag); // 將默認(rèn)的英文切換到指定的語(yǔ)言 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; } // 緩存各個(gè)播放器的各個(gè)屬性. this.cache_ = {}; // 設(shè)置播放器的貼片 this.poster_ = options.poster || ""; // 設(shè)置播放器的控制 this.controls_ = !!options.controls; // 默認(rèn)是關(guān)掉控制 tag.controls = false; this.scrubbing_ = false; this.el_ = this.createEl(); const playerOptionsCopy = mergeOptions(this.options_); // 自動(dòng)加載播放器插件 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的構(gòu)造器中有一句super(null, options, ready);實(shí)例化父類(lèi)Component。我們來(lái)看下Component的構(gòu)造函數(shù):
constructor(player, options, ready) { // 之前說(shuō)過(guò)所有的類(lèi)都是繼承Component,不是所有的類(lèi)需要傳player if (!player && this.play) { // 這里判斷調(diào)用的對(duì)象是不是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的構(gòu)造函數(shù)為啥要設(shè)置initChildren為false了吧 if (options.initChildren !== false) { // 這個(gè)initChildren方法是將一個(gè)類(lèi)的子類(lèi)都實(shí)例化,一個(gè)類(lèi)都對(duì)應(yīng)著自己的el(DOM實(shí)例),通過(guò)這個(gè)方法父類(lèi)和子類(lèi)的DOM繼承關(guān)系也就實(shí)現(xiàn)了 this.initChildren(); } this.ready(ready); if (options.reportTouchActivity !== false) { this.enableTouchActivity(); } }插件的運(yùn)行機(jī)制 插件的定義
import Player from "./player.js"; // 將插件種植到Player的原型鏈 const plugin = function(name, init) { Player.prototype[name] = init; }; // 暴露plugin接口 videojs.plugin = plugin;插件的運(yùn)行
// 在Player的構(gòu)造函數(shù)里判斷是否使用了插件,如果有遍歷執(zhí)行 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); }控制條是如何運(yùn)行的
Player.prototype.options_ = { // 此處表示默認(rèn)使用html5的video標(biāo)簽 techOrder: ["html5", "flash"], html5: {}, flash: {}, // 默認(rèn)的音量,官方代碼該配置無(wú)效有bug,我們已修復(fù), defaultVolume: 0.85, // 用戶(hù)的交互時(shí)長(zhǎng),比如超過(guò)這個(gè)時(shí)間表示失去焦點(diǎn) inactivityTimeout: 2000, playbackRates: [], // 這是控制條各個(gè)組成部分,作為Player的子類(lèi) 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類(lèi)中有個(gè)children配置項(xiàng),這里面是控制條的各個(gè)組成部分的類(lèi)。各個(gè)UI類(lèi)還有子類(lèi),都是通過(guò)children屬性鏈接的。
UI與JavaScript對(duì)象的銜接video.js里都是組件化實(shí)現(xiàn)的,小到一個(gè)按鈕大到一個(gè)播放器都是一個(gè)繼承了Component類(lèi)的對(duì)象實(shí)例,每個(gè)對(duì)象包含一個(gè)el屬性,這個(gè)el對(duì)應(yīng)一個(gè)DOM實(shí)例,el是通過(guò)createEl生成的DOM實(shí)例,在Component基類(lèi)中包含一個(gè)方法createEl方法,子類(lèi)也可以重寫(xiě)該方法。類(lèi)與類(lèi)的從屬關(guān)系是通過(guò)children屬性連接。
那么整個(gè)播放器是怎么把播放器的UI加載到HTML中的呢?在Player的構(gòu)造函數(shù)里可以看到先生成el,然后初始化父類(lèi)遍歷Children屬性,將children中的類(lèi)實(shí)例化并將對(duì)應(yīng)的DOM嵌入到player的el屬性中,最后在Player的構(gòu)造函數(shù)中直接掛載到video標(biāo)簽的父級(jí)DOM上。
if (tag.parentNode) { tag.parentNode.insertBefore(el, tag); }
這里的tag指的是video標(biāo)簽。
類(lèi)的掛載方式上文有提到過(guò)UI的從屬關(guān)系是通過(guò)類(lèi)的children方法連接的,但是所有的類(lèi)都是關(guān)在Component類(lèi)上的。這主要是基于對(duì)模塊化的考慮,通過(guò)這種方式實(shí)現(xiàn)了模塊之間的通信。
存儲(chǔ)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里有個(gè)靜態(tài)方法是registerComponet,所有的組件類(lèi)都注冊(cè)到Componet的components_屬性里。
例如控制條類(lèi)ControlBar就是通過(guò)這個(gè)方法注冊(cè)的。
Component.registerComponent("ControlBar", ControlBar);
在Player的children屬性里包括了controlBar類(lèi),然后通過(guò)getComponet獲取這個(gè)類(lèi)。
.filter((child) => { const c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); return c && !Tech.isTech(c); })
如有疑問(wèn),請(qǐng)留言。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/87953.html
摘要:語(yǔ)法部分采用的是標(biāo)準(zhǔn)。那么整個(gè)播放器是怎么把播放器的加載到中的呢在的構(gòu)造函數(shù)里可以看到先生成,然后初始化父類(lèi)遍歷屬性,將中的類(lèi)實(shí)例化并將對(duì)應(yīng)的嵌入到的屬性中,最后在的構(gòu)造函數(shù)中直接掛載到標(biāo)簽的父級(jí)上。 video.js 源碼分析(JavaScript) 組織結(jié)構(gòu) 繼承關(guān)系 運(yùn)行機(jī)制 插件的運(yùn)行機(jī)制 插件的定義 插件的運(yùn)行 控制條是如何運(yùn)行的 UI與JavaScript對(duì)象的...
摘要:語(yǔ)法部分采用的是標(biāo)準(zhǔn)。那么整個(gè)播放器是怎么把播放器的加載到中的呢在的構(gòu)造函數(shù)里可以看到先生成,然后初始化父類(lèi)遍歷屬性,將中的類(lèi)實(shí)例化并將對(duì)應(yīng)的嵌入到的屬性中,最后在的構(gòu)造函數(shù)中直接掛載到標(biāo)簽的父級(jí)上。 video.js 源碼分析(JavaScript) 組織結(jié)構(gòu) 繼承關(guān)系 運(yùn)行機(jī)制 插件的運(yùn)行機(jī)制 插件的定義 插件的運(yùn)行 控制條是如何運(yùn)行的 UI與JavaScript對(duì)象的...
摘要:目前半島局勢(shì)緊張,朝鮮已進(jìn)行了六次核試驗(yàn),被廣泛認(rèn)為已經(jīng)擁有了核彈頭。另外朝鮮的導(dǎo)彈技術(shù)今年以來(lái)快速突破,成功試射了射程可覆蓋美國(guó)本土的洲際彈道導(dǎo)彈。這個(gè)版的內(nèi)容傳到互聯(lián)網(wǎng)上后,迅速刷屏,引起紛紛議論。 SplderApi2 Node-SplderApi2 第二版 基于Node 的網(wǎng)絡(luò)爬蟲(chóng) API接口 包括前端開(kāi)發(fā)日?qǐng)?bào)、kugou音樂(lè)、前端top框架排行、妹紙福利、搞笑視頻、段子笑話(huà)、...
摘要:原文鏈接前端插件庫(kù)站點(diǎn)前端開(kāi)發(fā)文檔博客前端插件庫(kù)前端插件庫(kù)官網(wǎng)是的函數(shù)庫(kù),目的是強(qiáng)化表格操作如搜索排序,并自動(dòng)加入組件引入表格中,使用非常靈活簡(jiǎn)便。由推出,靈活扎實(shí)的建議列表函數(shù)庫(kù)。 原文鏈接:前端插件庫(kù)站點(diǎn):前端開(kāi)發(fā)文檔博客:前端插件庫(kù) 前端插件庫(kù) DataTables 官網(wǎng):https://www.datatables.net/ DataTables是jQuery的JavaScr...
閱讀 1824·2021-10-20 13:49
閱讀 1363·2019-08-30 15:52
閱讀 2869·2019-08-29 16:37
閱讀 1038·2019-08-29 10:55
閱讀 3072·2019-08-26 12:14
閱讀 1655·2019-08-23 17:06
閱讀 3239·2019-08-23 16:59
閱讀 2549·2019-08-23 15:42