摘要:語法部分采用的是標準。那么整個播放器是怎么把播放器的加載到中的呢在的構(gòu)造函數(shù)里可以看到先生成,然后初始化父類遍歷屬性,將中的類實例化并將對應(yīng)的嵌入到的屬性中,最后在的構(gòu)造函數(shù)中直接掛載到標簽的父級上。
video.js 源碼分析(JavaScript)
組織結(jié)構(gòu)
繼承關(guān)系
運行機制
插件的運行機制
插件的定義
插件的運行
控制條是如何運行的
UI與JavaScript對象的銜接
類的掛載方式
存儲
獲取
組織結(jié)構(gòu)以下是video.js的源碼組織結(jié)構(gòu)關(guān)系,涉及控制條、菜單、浮層、進度條、滑動塊、多媒體、音軌字幕、輔助函數(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ū)ο蠓绞絹韺崿F(xiàn)的。基類是Component,所有其他的類都是直接或間接集成此類實現(xiàn)。語法部分采用的是ES6標準。
繼承關(guān)系深入源碼解讀需要了解類與類之間的繼承關(guān)系,直接上圖。
所有的繼承關(guān)系
主要的繼承關(guān)系
首先調(diào)用videojs啟動播放器,videojs方法判斷當前id是否已被實例化,如果沒有實例化新建一個Player對象,因Player繼承Component會自動初始化Component類。如果已經(jīng)實例化直接返回Player對象。
videojs方法源碼如下:
function videojs(id, options, ready) { let tag; // id可以是選擇器也可以是DOM節(jié)點 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節(jié)點 tag = Dom.getEl(id); } else { // 如果是DOM節(jié)點直接返回 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的構(gòu)造函數(shù),代碼如下:
constructor(tag, options, ready) { // 注意這個tag是video原生標簽 tag.id = tag.id || `vjs_video_${Guid.newGUID()}`; // 選項配置的合并 options = assign(Player.getTagSettings(tag), options); // 這個選項要關(guān)掉否則會在父類自動執(zhí)行加載子類集合 options.initChildren = false; // 調(diào)用父類的createEl方法 options.createEl = false; // 在移動端關(guān)掉手勢動作監(jiān)聽 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參數(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?"); } // 存儲當前已被實例化的播放器 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_ = {}; // 設(shè)置播放器的貼片 this.poster_ = options.poster || ""; // 設(shè)置播放器的控制 this.controls_ = !!options.controls; // 默認是關(guān)掉控制 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的構(gòu)造器中有一句super(null, options, ready);實例化父類Component。我們來看下Component的構(gòu)造函數(shù):
constructor(player, options, ready) { // 之前說過所有的類都是繼承Component,不是所有的類需要傳player if (!player && this.play) { // 這里判斷調(diào)用的對象是不是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) { // 這個initChildren方法是將一個類的子類都實例化,一個類都對應(yīng)著自己的el(DOM實例),通過這個方法父類和子類的DOM繼承關(guān)系也就實現(xiàn)了 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的構(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); }控制條是如何運行的
Player.prototype.options_ = { // 此處表示默認使用html5的video標簽 techOrder: ["html5", "flash"], html5: {}, flash: {}, // 默認的音量,官方代碼該配置無效有bug,我們已修復(fù), 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里都是組件化實現(xiàn)的,小到一個按鈕大到一個播放器都是一個繼承了Component類的對象實例,每個對象包含一個el屬性,這個el對應(yīng)一個DOM實例,el是通過createEl生成的DOM實例,在Component基類中包含一個方法createEl方法,子類也可以重寫該方法。類與類的從屬關(guān)系是通過children屬性連接。
那么整個播放器是怎么把播放器的UI加載到HTML中的呢?在Player的構(gòu)造函數(shù)里可以看到先生成el,然后初始化父類遍歷Children屬性,將children中的類實例化并將對應(yīng)的DOM嵌入到player的el屬性中,最后在Player的構(gòu)造函數(shù)中直接掛載到video標簽的父級DOM上。
if (tag.parentNode) { tag.parentNode.insertBefore(el, tag); }
這里的tag指的是video標簽。
類的掛載方式上文有提到過UI的從屬關(guān)系是通過類的children方法連接的,但是所有的類都是關(guān)在Component類上的。這主要是基于對模塊化的考慮,通過這種方式實現(xiàn)了模塊之間的通信。
存儲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里有個靜態(tài)方法是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); })
如有疑問,請留言。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/111624.html
摘要:語法部分采用的是標準。那么整個播放器是怎么把播放器的加載到中的呢在的構(gòu)造函數(shù)里可以看到先生成,然后初始化父類遍歷屬性,將中的類實例化并將對應(yīng)的嵌入到的屬性中,最后在的構(gòu)造函數(shù)中直接掛載到標簽的父級上。 video.js 源碼分析(JavaScript) 組織結(jié)構(gòu) 繼承關(guān)系 運行機制 插件的運行機制 插件的定義 插件的運行 控制條是如何運行的 UI與JavaScript對象的...
摘要:語法部分采用的是標準。那么整個播放器是怎么把播放器的加載到中的呢在的構(gòu)造函數(shù)里可以看到先生成,然后初始化父類遍歷屬性,將中的類實例化并將對應(yīng)的嵌入到的屬性中,最后在的構(gòu)造函數(shù)中直接掛載到標簽的父級上。 video.js 源碼分析(JavaScript) 組織結(jié)構(gòu) 繼承關(guān)系 運行機制 插件的運行機制 插件的定義 插件的運行 控制條是如何運行的 UI與JavaScript對象的...
摘要:目前半島局勢緊張,朝鮮已進行了六次核試驗,被廣泛認為已經(jīng)擁有了核彈頭。另外朝鮮的導彈技術(shù)今年以來快速突破,成功試射了射程可覆蓋美國本土的洲際彈道導彈。這個版的內(nèi)容傳到互聯(lián)網(wǎng)上后,迅速刷屏,引起紛紛議論。 SplderApi2 Node-SplderApi2 第二版 基于Node 的網(wǎng)絡(luò)爬蟲 API接口 包括前端開發(fā)日報、kugou音樂、前端top框架排行、妹紙福利、搞笑視頻、段子笑話、...
閱讀 3474·2021-10-13 09:39
閱讀 1458·2021-10-08 10:05
閱讀 2259·2021-09-26 09:56
閱讀 2274·2021-09-03 10:28
閱讀 2673·2019-08-29 18:37
閱讀 2032·2019-08-29 17:07
閱讀 600·2019-08-29 16:23
閱讀 2191·2019-08-29 11:24