摘要:語法部分采用的是標準。那么整個播放器是怎么把播放器的加載到中的呢在的構造函數里可以看到先生成,然后初始化父類遍歷屬性,將中的類實例化并將對應的嵌入到的屬性中,最后在的構造函數中直接掛載到標簽的父級上。
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標準。
繼承關系深入源碼解讀需要了解類與類之間的繼承關系,直接上圖。
所有的繼承關系
主要的繼承關系
首先調用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); })
如有疑問,請留言。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/50133.html
摘要:語法部分采用的是標準。那么整個播放器是怎么把播放器的加載到中的呢在的構造函數里可以看到先生成,然后初始化父類遍歷屬性,將中的類實例化并將對應的嵌入到的屬性中,最后在的構造函數中直接掛載到標簽的父級上。 video.js 源碼分析(JavaScript) 組織結構 繼承關系 運行機制 插件的運行機制 插件的定義 插件的運行 控制條是如何運行的 UI與JavaScript對象的...
摘要:語法部分采用的是標準。那么整個播放器是怎么把播放器的加載到中的呢在的構造函數里可以看到先生成,然后初始化父類遍歷屬性,將中的類實例化并將對應的嵌入到的屬性中,最后在的構造函數中直接掛載到標簽的父級上。 video.js 源碼分析(JavaScript) 組織結構 繼承關系 運行機制 插件的運行機制 插件的定義 插件的運行 控制條是如何運行的 UI與JavaScript對象的...
摘要:目前半島局勢緊張,朝鮮已進行了六次核試驗,被廣泛認為已經擁有了核彈頭。另外朝鮮的導彈技術今年以來快速突破,成功試射了射程可覆蓋美國本土的洲際彈道導彈。這個版的內容傳到互聯網上后,迅速刷屏,引起紛紛議論。 SplderApi2 Node-SplderApi2 第二版 基于Node 的網絡爬蟲 API接口 包括前端開發日報、kugou音樂、前端top框架排行、妹紙福利、搞笑視頻、段子笑話、...
閱讀 3136·2021-11-11 16:54
閱讀 2290·2021-09-04 16:48
閱讀 3219·2019-08-29 16:08
閱讀 641·2019-08-29 15:13
閱讀 1343·2019-08-29 15:09
閱讀 2660·2019-08-29 12:45
閱讀 1926·2019-08-29 12:12
閱讀 444·2019-08-26 18:27