整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Babel 插件:30分鐘從入門到實戰

          Babel 插件:30分鐘從入門到實戰

          abel 是一個 source to source(源碼到源碼)的 JavaScript 編譯器,簡單來說,你為 Babel 提供一些 JavaScript 代碼,Babel 可以更改這些代碼,然后返回給你新生成的代碼。Babel 主要用于將 ECMAScript 2015+ 代碼轉換為能夠向后兼容的 JavaScript 版本。Babel 使用插件系統進行代碼轉換,因此任何人都可以為 babel 編寫自己的轉換插件,以支持實現廣泛的功能。

          Babel 編譯流程

          Babel 的編譯流程主要分為三個部分:解析(parse),轉換(transform),生成(generate)。

          code -> AST -> transformed AST -> transformed code
          • 解析 Parse

          將源碼轉換成抽象語法樹(AST, Abstract Syntax Tree)。

          比如:

          function square(n) {
            return n * n;
          }

          以上的程序可以被轉換成類似這樣的抽象語法樹:

          - FunctionDeclaration:
            - id:
              - Identifier:
                - name: square
            - params [1]
              - Identifier
                - name: n
            - body:
              - BlockStatement
                - body [1]
                  - ReturnStatement
                    - argument
                      - BinaryExpression
                        - operator: *
                        - left
                          - Identifier
                            - name: n
                        - right
                          - Identifier
                            - name: n
          • 轉換 Transform

          轉換階段接受一個 AST 并遍歷它,在遍歷的過程中對樹的節點進行增刪改。這也是運行 Babel 插件的階段。

          • 生成 Generate

          將經過一系列轉換之后的 AST 轉換成字符串形式的代碼,同時還會創建 sourcemap。

          你會用到的一些工具庫

          對于每一個階段,Babel 都提供了一些工具庫:

          • Parse 階段可以使用 @babel/parser 將源碼轉換成 AST。
          • Transform 階段可以使用 @babel/traverse 調用 visitor 函數遍歷 AST,期間可以使用 @babel/types 創建 AST 和檢查 AST 節點的類型,批量創建 AST 的場景下可以使用 @babel/template 中途還可以使用 @babel/code-frame 打印報錯信息。
          • Generate 階段可以使用 @babel/generator 根據 AST 生成代碼字符串和 sourcemap。

          以上提及的包都是 @babel/core 的 dependencies,所以只需要安裝 @babel/core 就能訪問到它們。

          除了上面提到的工具庫,以下工具庫也比較常用:

          • @babel/helper-plugin-utils:如果插件使用者的 Babel 版本沒有您的插件所需的 API,它能給用戶提供明確的錯誤信息。
          • babel-plugin-tester:用于幫助測試 Babel 插件的實用工具,通常配合 jest 使用。

          本文不會深入討論它們的詳細用法,當你在編寫插件的時候,可以根據功能需求找到它們,我們后文也會涉及到部分用法。

          認識 Babel 插件

          接下來讓我們開始認識 Babel 插件吧。

          babel 插件是一個簡單的函數,它必須返回一個匹配以下接口的對象。如果 Babel 發現未知屬性,它將拋出錯誤。

          以下是一個簡單的插件示例:

          export default function(api, options, dirname) {
            return {
              visitor: {
                StringLiteral(path, state) {},
              }
            };
          };

          Babel 插件接受 3 個參數:

          • api:一個對象,包含了 types (@babel/types)、traverse (@babel/traverse)、template(@babel/template) 等實用方法,我們能從這個對象中訪問到 @babel/core dependecies 中包含的方法。
          • options:插件參數。
          • dirname:目錄名。

          返回的對象有 name、manipulateOptions、pre、visitor、post、inherits 等屬性:

          • name:插件名字。
          • inherits:指定繼承某個插件,通過 Object.assign 的方式,和當前插件的 options 合并。
          • visitor:指定 traverse 時調用的函數。
          • pre 和 post 分別在遍歷前后調用,可以做一些插件調用前后的邏輯,比如可以往 file(表示文件的對象,在插件里面通過 state.file 拿到)中放一些東西,在遍歷的過程中取出來。
          • manipulateOptions:用于修改 options,是在插件里面修改配置的方式。

          我們上面提到了一些陌生的概念:visitor、path、state,現在讓我們一起來認識它們:

          • visitor 訪問者

          這個名字來源于設計模式中的訪問者模式(https://en.wikipedia.org/wiki/Visitor_pattern)。簡單的說它就是一個對象,指定了在遍歷 AST 過程中,訪問指定節點時應該被調用的方法。

          • 假如我們有這樣一段程序:
          function foo() {
          	return 'string'
          }
          • 這段代碼對應的 AST 如下:
          - Program
          	- FunctionDeclaration (body[0])
          		- Identifier (id)
          		- BlockStatement (body)
          			- ReturnStatement (body[0])
          			- StringLiteral (arugument)
          • 當我們對這顆 AST 進行深度優先遍歷時,每次訪問 StringLiteral 都會調用 visitor.StringLiteral。

          當 visitor.StringLiteral 是一個函數時,它將在向下遍歷的過程中被調用(即進入階段)。當 visitor.StringLiteral 是一個對象時({ enter(path, state) {}, exit(path, state) {} }),visitor.StringLiteral.enter 將在向下遍歷的過程中被調用(進入階段),visitor.StringLiteral.exit 將在向上遍歷的過程中被調用(退出階段)。

          • Path 路徑

          Path 用于表示兩個節點之間連接的對象,這是一個可操作和訪問的巨大可變對象。

          Path 之間的關系如圖所示:

          除了能在 Path 對象上訪問到當前 AST 節點、父級 AST 節點、父級 Path 對象,還能訪問到添加、更新、移動和刪除節點等其他方法,這些方法提高了我們對 AST 增刪改的效率。

          • State 狀態

          在實際編寫插件的過程中,某一類型節點的處理可能需要依賴其他類型節點的處理結果,但由于 visitor 屬性之間互不關聯,因此需要 state 幫助我們在不同的 visitor 之間傳遞狀態。

          一種處理方式是使用遞歸,并將狀態往下層傳遞:

          const anotherVisitor={
          	Identifier(path) {
          		console.log(this.someParam) //=> 'xxx'
          	}
          };
          
          const MyVisitor={
          	FunctionDeclaration(path, state) {
          		// state.cwd: 當前執行目錄
          		// state.opts: 插件 options
          		// state.filename: 當前文件名(絕對路徑)
          		// state.file: BabelFile 對象,包含當前整個 ast,當前文件內容 code,etc.
          		// state.key: 當前插件名字
          		path.traverse(anotherVisitor, { someParam: 'xxx' });
          	}
          };

          另外一種傳遞狀態的辦法是將狀態直接設置到 this 上,Babel 會給 visitor 上的每個方法綁定 this。在 Babel 插件中,this 通常會被用于傳遞狀態:從 pre 到 visitor 再到 post。

          export default function({ types: t }) {
          	return {
          		pre(state) {
          			this.cache=new Map();
          		},
          		visitor: {
          			StringLiteral(path) {
          				this.cache.set(path.node.value, 1);
          			}
          		},
          		post(state) {
          			console.log(this.cache);
          		}
          	};
          }

          常用的 API

          Babel 沒有完整的文檔講解所有的 api,因此下面會列舉一些可能還算常用的 api(并不是所有,主要是 path 和 types 上的方法或屬性),我們并不需要全部背下來,在你需要用的時候,能找到對應的方法即可。

          你可以通過 babel 的 typescript 類型定義找到以下列舉的屬性和方法,還可以通過 Babel Handbook 找到它們的具體使用方法。

          Babel Handbook:https://astexplorer.net/

          • 查詢
            • path.node:訪問當前節點
            • path.get():獲取屬性內部的 path
            • path.inList:判斷路徑是否有同級節點
            • path.key:獲取路徑所在容器的索引
            • path.container:獲取路徑的容器(包含所有同級節點的數組)
            • path.listKey:獲取容器的key
            • path.getSibling():獲得同級路徑
            • path.findParent():對于每一個父路徑調用 callback 并將其 NodePath 當作參數,當 callback 返回真值時,則將其 NodePath 返回
            • path.find():與 path.findParent 的區別是,該方法會遍歷當前節點
          • 遍歷
            • path.stop():跳過遍歷當前路徑的子路徑
            • path.skip():完全停止遍歷
          • 判斷
            • types.isXxx():檢查節點的類型,如 types.isStringLiteral(path.node)
            • path.isReferencedIdentifier():檢查標識符(Identifier)是否被引用
          • 增刪改
            • path.replaceWith():替換單個節點
            • path.replaceWithMultiple():用多節點替換單節點
            • path.replaceWithSourceString():用字符串源碼替換節點
            • path.insertBefore() / path.insertAfter():插入兄弟節點
            • path.get('listKey').unshiftContainer() / path.get('listKey').pushContainer():插入一個節點到數組中,如 body
            • path.remove():刪除一個節點
          • 作用域
            • path.scope.hasBinding(): 從當前作用域開始向上查找變量
            • path.scope.hasOwnBinding():僅在當前作用域中查找變量
            • path.scope.generateUidIdentifier():生成一個唯一的標識符,不會與任何本地定義的變量相沖突
            • path.scope.generateUidIdentifierBasedOnNode():基于某個節點創建唯一的標識符
            • path.scope.rename():重命名綁定及其引用

          AST Explorer

          在 @babel/types 的類型定義中,可以找到所有 AST 節點類型。我們不需要記住所有節點類型,社區內有一個 AST 可視化工具能夠幫助我們分析 AST:axtexplorer.net。

          在這個網站的左側,可以輸入我們想要分析的代碼,在右側會自動生成對應的 AST。當我們在左側代碼區域點擊某一個節點,比如函數名 foo,右側 AST 會自動跳轉到對應的 Identifier AST 節點,并高亮展示。

          我們還可以修改要 parse 的語言、使用的 parser、parser 參數等。

          自己實現一個插件吧

          現在讓我們來實現一個簡單的插件吧!以下是插件需要實現的功能:

          1. 將代碼里重復的字符串字面量(StringLiteral)提升到頂層作用域。
          2. 接受一個參數 minCount,它是 number 類型,如果某個字符串字面量重復次數大于等于 minCount 的值,則將它提升到頂層作用域,否則不做任何處理。

          因此,對于以下輸入:

          const s1="foo";
          const s2="foo";
          
          const s3="bar";
          
          function f1() {
            const s4="baz";
            if (true) {
              const s5="baz";
            }
          }
          

          應該輸出以下代碼:

          var _foo="foo",
            _baz="baz";
          const s1=_foo;
          const s2=_foo;
          const s3="bar";
          
          function f1() {
            const s4=_baz;
          
            if (true) {
              const s5=_baz;
            }
          }
          

          通過 https://astexplorer.net/,我們發現代碼里的字符串在 AST 上對應的節點叫做 StringLiteral,如果想要拿到代碼里所有的字符串并且統計每種字符串的數量,就需要遍歷 StringLiteral 節點。

          我們需要一個對象用于存儲所有 StringLiteral,key 是 StringLiteral 節點的 value 屬性值,value 是一個數組,用于存儲擁有相同 path.node.value 的所有 path 對象,最后把這個對象存到 state 對象上,以便于在遍歷結束時能統計相同字符串的重復次數,從而可以判斷哪些節點需要被替換為一個標識符。

          export default function() {
            return {
              visitor: {
                StringLiteral(path, state) {
                  state.stringPathMap=state.stringPathMap || {};
                  const nodes=state.stringPathMap[path.node.value] || [];
                  nodes.push(path);
                  state.stringPathMap[path.node.value]=nodes;
                }
              }
            };
          }
          

          通過 https://astexplorer.net/,我們發現如果想要往頂層作用域中插入一個變量,其實就是往 Program 節點的 body 上插入 AST 節點。Program 節點也是 AST 的頂層節點,在遍歷過程的退出階段,Program 節點是最后一個被處理的,因此我們需要做的事情是:根據收集到的字符串字面量,分別創建一個位于頂層作用域的變量,并將它們統一插入到 Program 的 body 中,同時將代碼中的字符串替換為對應的變量。

          export default function() {
            return {
              visitor: {
                StringLiteral(path, state) { /** ... */ },
                Program: {
                  exit(path, state) {
                    const { minCount=2 }=state.opts || {};
                
                    for (const [string, paths] of Object.entries(state.stringPathMap || {})) {
                      if (paths.length < minCount) {
                        continue;
                      }
                
                      const id=path.scope.generateUidIdentifier(string);
                
                      paths.forEach(p=> {
                        p.replaceWith(id);
                      });
                
                      path.scope.push({ id, init: types.stringLiteral(string) });
                    }
                  },
                },
              }
            };
          }
          

          完整代碼

          import { PluginPass, NodePath } from '@babel/core';
          import { declare } from '@babel/helper-plugin-utils';
          
          interface Options {
            /**
             * 當字符串字面量的重復次數大于或小于 minCount,將會被提升到頂層作用域
             */
            minCount?: number;
          }
          
          type State=PluginPass & {
            // 以 StringLiteral 節點的 value 屬性值為 key,存放所有 StringLiteral 的 Path 對象
            stringPathMap?: Record<string, NodePath[]>;
          };
          
          const HoistCommonString=declare<Options>(({ assertVersion, types }, options)=> {
            // 判斷當前 Babel 版本是否為 7
            assertVersion(7);
          
            return {
              // 插件名字
              name: 'hoist-common-string',
          
              visitor: {
                StringLiteral(path, state: State) {
                  // 將所有 StringLiteral 節點對應的 path 對象收集起來,存到 state 對象里,
                  // 以便于在遍歷結束時能統計相同字符串的重復次數
                  state.stringPathMap=state.stringPathMap || {};
          
                  const nodes=state.stringPathMap[path.node.value] || [];
                  nodes.push(path);
          
                  state.stringPathMap[path.node.value]=nodes;
                },
          
                Program: {
                  // 將在遍歷過程的退出階段被調用
                  // Program 節點是頂層 AST 節點,可以認為 Program.exit 是最后一個執行的 visitor 函數
                  exit(path, state: State) {
                    // 插件參數。還可以通過 state.opts 拿到插件參數
                    const { minCount=2 }=options || {};
          
                    for (const [string, paths] of Object.entries(state.stringPathMap || {})) {
                      // 對于重復次數少于 minCount 的 Path,不做處理
                      if (paths.length < minCount) {
                        continue;
                      }
          
                      // 基于給定的字符串創建一個唯一的標識符
                      const id=path.scope.generateUidIdentifier(string);
          
                      // 將所有相同的字符串字面量替換為上面生成的標識符
                      paths.forEach(p=> {
                        p.replaceWith(id);
                      });
          
                      // 將標識符添加到頂層作用域中
                      path.scope.push({ id, init: types.stringLiteral(string) });
                    }
                  },
                },
              },
            };
          });
          

          測試插件

          測試 Babel 插件有三種常用的方法:

          • 測試轉換后的 AST 結果,檢查是否符合預期
          • 測試轉換后的代碼字符串,檢查是否符合預期(通常使用快照測試)
          • 執行轉換后的代碼,檢查執行結果是否符合預期

          我們一般使用第二種方法,配合 babel-plugin-tester 可以很好地幫助我們完成測試工作。配合 babel-plugin-tester,我們可以對比輸入輸出的字符串、文件、快照。

          import pluginTester from 'babel-plugin-tester';
          import xxxPlugin from './xxxPlugin';
          
          pluginTester({
            plugin: xxxPlugin,
            fixtures: path.join(__dirname, '__fixtures__'),
            tests: {
              // 1. 對比轉換前后的字符串
              // 1.1 輸入輸出完全一致時,可以簡寫
              'does not change code with no identifiers': '"hello";',
              // 1.2 輸入輸出不一致
              'changes this code': {
                code: 'var hello="hi";',
                output: 'var olleh="hi";',
              },
              // 2. 對比轉換前后的文件
              'using fixtures files': {
                fixture: 'changed.js',
                outputFixture: 'changed-output.js',
              },
              // 3. 與上一次生成的快照做對比
              'using jest snapshots': {
                code: `
                  function sayHi(person) {
                    return 'Hello ' + person + '!'
                  }
                `,
                snapshot: true,
              },
            },
          });
          

          本文將以快照測試為例,以下是測試我們插件的示例代碼:

          import pluginTester from 'babel-plugin-tester';
          import HoistCommonString from '../index';
          
          pluginTester({
            // 插件
            plugin: HoistCommonString,
            // 插件名,可選
            pluginName: 'hoist-common-string',
            // 插件參數,可選
            pluginOptions: {
              minCount: 2,
            },
            tests: {
              'using jest snapshots': {
                // 輸入
                code: `const s1="foo";
                const s2="foo";
          
                const s3="bar";
          
                function f1() {
                  const s4="baz";
                  if (true) {
                    const s5="baz";
                  }
                }`,
                // 使用快照測試
                snapshot: true,
              },
            },
          });
          

          當我們運行 jest 后(更多關于 jest 的介紹,可以查看 jest 官方文檔https://jestjs.io/docs/getting-started),會生成一個 snapshots 目錄:

          有了快照以后,每次迭代插件都可以跑一下單測以快速檢查功能是否正常。快照的更新也很簡單,只需要執行 jest --updateSnapshot

          使用插件

          如果想要使用 Babel 插件,需要在配置文件里添加 plugins 選項,plugins 選項接受一個數組,值為字符串或者數組。以下是一些例子:

          // .babelrc
          {
              "plugins": [
                  "babel-plugin-myPlugin1",
                  ["babel-plugin-myPlugin2"],
                  ["babel-plugin-myPlugin3", { /** 插件 options */ }],
                  "./node_modules/asdf/plugin"
              ]
          }
          

          Babel 對插件名字的格式有一定的要求,比如最好包含 babel-plugin,如果不包含的話也會自動補充。以下是 Babel 插件名字的自動補全規則:

          到這里,Babel 插件的學習就告一段落了,如果大家想繼續深入學習 Babel 插件,可以訪問 Babel 的倉庫(https://github.com/babel/babel/tree/main/packages)這是一個 monorepo,里面包含了很多真實的插件,通過閱讀這些插件,相信你一定能對 Babel 插件有更深入的理解!

          參考文檔

          Babel plugin handbook:https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md

          Babel 官方文檔:https://babeljs.io/docs/en/

          Babel 插件通關秘籍:https://juejin.cn/book/6946117847848321055

          里云播放器SDK(ApsaraVideo for Player SDK)是阿里視頻云端到云到端服務的重要一環,除了支持點播和直播的基礎播放功能外,還深度融合視頻云業務,支持視頻的加密播放、安全下載、首屏秒開、低延時等業務場景,為用戶提供簡單、快速、安全、穩定的視頻播放服務。本文銜接上文,詳細介紹web播放器的功能及實現。

          一、基本概況及功能

          • 播放器架構

          Aliplayer Web播放器分為H5和Flash兩個,Flash播放器隨著技術的發展會逐漸被邊緣化,所以我們以后只做維護,不會更新功能了,重點會放在H5播放器上。H5播放器架構主要分四層,底層H5 Video,播放能力和H5原生Video緊密相關。第二層是基礎播放器,它不依賴于具體業務,通過URL的方式來播放。第三層是為各種業務場景準備的不同的播放器,可以很容易的擴展,相互隔離不依賴。最上面一層是適配的播放器,會根據終端類型、瀏覽器類型、播放格式和用戶指定來進行智能適配。

          • 播放器功能

          最近,我們在播放器端上也實現了截圖、國際化、變速、UI自定義、微信同層播放、自適應播放、加密播放、H5播放flv、自定義插件等功能。后續,我們還會通過插件的形式實現彈幕、廣告等功能,并會開源到github上,也會支持用戶根據自己業務需求來自定義SDK包。

          • 播放器支持視頻格式

          • 適配播放

          我們整個視頻播放的基本原則是H5優先,能用H5播放的肯定不用Flash去播放。所以在移動端,我們肯定是用H5來播放的,PC端也依照這個原則盡量使用H5。同時,我們會判斷瀏覽器類型支持哪種播放格式,比如m3u8在PC端IE11以上的瀏覽器才能播放,如果遇到IE11以下的瀏覽器,我們自動會選擇Flash播放。在視頻格式方面,假設視頻是rtmp和flv,我們會自動選擇Flash播放。另外,如果用戶自主設置useH5Prism和useFlashPrism屬性,那我們也會依照用戶的選擇。

          • 瀏覽器支持情況

          FLASH支持IE8以上,在瀏覽器上啟動允許FLASH運行即可;H5支持IE9以上,m3u8需要在IE11以上才可以運行;其他瀏覽器都也都是可以支持的。

          • 兩種播放方式

          1. source,通過url 去播放

          2. 通過點播vid+playauth去播放,第二種方式和視頻云結合比較緊密

          • 點播播放格式的選擇

          點播服務中轉碼生成的視頻格式有很多,包括m3u8、flv、mp4等。播放器有自己的一套邏輯去選擇播放格式。對于H5來說,默認播放低清版本來節省流量,如果用戶使用了切換清晰度的功能,那我們會默認打開他選擇的版本。格式方面,則默認播放mp4,用戶也可以設置qualitySort來優先播放高清的的版本。對于Flash來說,默認格式順序是m3u8、flv、mp4。

          二、功能介紹及啟用

          • 創建播放器

          1. 引用正確的JS和CSS文件

          2. 添加播放器容器 需要設置容器的id屬性,另外2.0.1之前的版本要添加prism-player類型。

          • New Aliplayer創建播放

          • 在線配置,用戶可以預先體驗下播放器的情況

          • Aliplayer-Cli創建演示例子

          用戶需要演示例子的時候,不需要寫很多代碼,通過這個命令,就可以創建例子,直接體驗AliPlayer。

          • PC端支持m3u8

          播放域名啟用允許跨域訪問

          • 訂閱和取消事件

          • 清晰度切換

          H5 1.9.9以后的版本和id+playauth播放方式才支持清晰度切換;支持記憶選擇的清晰度,當選擇的清晰度不能播放時,自動選擇下一個清晰度播放。

          • 手動切換視頻-H5

          這個功能播放器內比較常見。我們把它分成兩種情況去處理,如果是地址播放,我們通過loadByUrl來播放;如果是vid+playauth播放,我們通過replayByVidAndPlayAuth的方法來播放。

          • 手動切換視頻-flash

          地址播放方法與H5的方法一樣,vid+playauth播放則需要先銷毀播放器,再重新創建播放。

          • 不同地址格式的切換

          只能先銷毀播放器,再重新選擇正確的播放器播放。Github地址看simple demo:https://github.com/alilmq/aliplayer-simple-demo

          ![b_3_7]

          • UI自定義

          很多用戶有這個需求,所以我們的UI是可以隱藏掉的。提供了一個skinLayout的屬性,當這個屬性沒有指定值的時候,UI組件是全部顯示。如果是空數組的時候,UI組件全部不顯示。并且可以自定義組件的顯示和位置,在默認UI基礎上去裁剪,2.3.0版本以后,用戶也可以通過自定義插件的方式自定義自己的UI。

          • 截屏

          H5啟用:

          FLASH啟用:snapshot:true

          H5播放器,播放域名需添加允許跨域訪問的header

          支持訂閱snapshoted事件,獲取截屏的時間點和數據:

          支持設置截圖的大小和質量:

          支持添加文字水印:

          • 邊轉變播功能

          邊轉邊播是MTS的功能,播放器可以支持這種場景的播放。第一次觀看的時候調用MTS API啟動轉碼,邊轉碼邊播放,而且可以設置延遲播放。轉碼中使用直播播放器,轉碼完成后使用點播方式播放。

          • H5 android微信同層播放

          因為H5在android端微信打開時,會自動全屏播放,覆蓋Dom元素。

          同層播放一般有兩種業務場景,一種是點播的,視頻在某個地方播放,下面的評論、播放列表等,demo地址:https://github.com/alilmq/h5demo

          還要一種場景是直播場景,視頻需要全屏。可以通過設置x5_type:h5啟用同層播放。Demo 地址:https://github.com/alilmq/h5livedemo

          另外H5微信同層播放,有兩篇文章可以參考:

          http://player.alicdn.com/aliplayer/docs/blogs/how-to-handle-h5-same-layer.html

          http://player.alicdn.com/aliplayer/docs/blogs/how-to-handle-h5-same-layer.html

          • 國際化

          提供language屬性,用于啟用各種語言,默認為zh-cn,可選值為zh-cn or en-us。

          • 倍速播放

          提供UI的版本,只提供了0.5、1、1.5、2四種倍速播放;而setspeed方法,可以隨意設置倍速播放。這個可能會有一些限制,移動端有的瀏覽器會不支持,比如android微信。

          • 對于直播播放失敗的處理

          在播放失敗時候,會嘗試重新播放,觸發onM3u8Retry事件,事件里可以做一些提示,比如主播離開請稍等;如果幾次嘗試后還是失敗,會出發livestreamstop事件,事件里做一些直播失敗或結束的提示。


          三、其他輔助功能及工具

          我們也做了一些輔助工具,方便用戶去接入和排查問題。

          • 診斷工具

          通過錯誤碼描述的映射關系,大概能知道用戶的錯誤所在;

          通過vid知道用戶播放的是哪個視頻;

          通過uuid這個唯一標識,可以在日志系統中查到用戶的播放狀態;

          通過requestid和播放時間,可以定位到用戶的錯誤是哪次播放的錯誤和具體的播放時間。

          這里還有一個診斷的功能,可以知道用戶環境的具體信息,省去手工獲取視頻的繁瑣,可以快速診斷問題。

          地址:http://player.alicdn.com/detection.html

          • 檢測工具

          關于視頻播放失敗,我們提供了三種方式,原生H5、阿里云H5、阿里云Flash。我們把播放的日志調出來,通過日志來情況來判斷播放失敗的原因。舉個例子,如果用戶剛開始請求數據時就失敗的話,那我們會猜測存在鑒權失敗的情況;如果加載數據出錯,那可能是用戶的網絡的原因;如果是開始播放后出錯,可能就問題就出在解析或播放器不支持等方面。

          • ffmpeg查看視頻信息

          有的用戶只有畫面,沒有聲音。我們可以通過ffmpeg可以看下視頻的格式、流的情況、碼率、幀率等。

          最后,阿里云播放器的所有情況都聚合在以下的網站上:

          http://player.alicdn.com/detection.html,其中包括幫助文檔、在線配置、診斷工具、產品demo等,大家可以登錄了解詳情。

          們都知道Maven本質上是一個插件框架,它的核心并不執行任何具體的構建任務,所有這些任務都交給插件來完成,例如編譯源代碼是由maven- compiler-plugin完成的。進一步說,每個任務對應了一個插件目標(goal),每個插件會有一個或者多個目標,例如maven- compiler-plugin的compile目標用來編譯位于src/main/java/目錄下的主源碼,testCompile目標用來編譯位于src/test/java/目錄下的測試源碼。


          用戶可以通過兩種方式調用Maven插件目標。第一種方式是將插件目標與生命周期階段(lifecycle phase)綁定,這樣用戶在命令行只是輸入生命周期階段而已,例如Maven默認將maven-compiler-plugin的compile目標與 compile生命周期階段綁定,因此命令mvn compile實際上是先定位到compile這一生命周期階段,然后再根據綁定關系調用maven-compiler-plugin的compile目標。第二種方式是直接在命令行指定要執行的插件目標,例如mvn archetype:generate 就表示調用maven-archetype-plugin的generate目標,這種帶冒號的調用方式與生命周期無關。


          認識上述Maven插件的基本概念能幫助你理解Maven的工作機制,不過要想更高效率地使用Maven,了解一些常用的插件還是很有必要的,這可 以幫助你避免一不小心重新發明輪子。多年來Maven社區積累了大量的經驗,并隨之形成了一個成熟的插件生態圈。Maven官方有兩個插件列表,第一個列 表的GroupId為org.apache.maven.plugins,這里的插件最為成熟,具體地址為:http://maven.apache.org/plugins/index.html。第二個列表的GroupId為org.codehaus.mojo,這里的插件沒有那么核心,但也有不少十分有用,其地址為:http://mojo.codehaus.org/plugins.html。


          接下來筆者根據自己的經驗介紹一些最常用的Maven插件,在不同的環境下它們各自都有其出色的表現,熟練地使用它們能讓你的日常構建工作事半功倍。



          # maven-antrun-plugin


          http://maven.apache.org/plugins/maven-antrun-plugin/


          maven-antrun-plugin能讓用戶在Maven項目中運行Ant任務。用戶可以直接在該插件的配置以Ant的方式編寫Target, 然后交給該插件的run目標去執行。在一些由Ant往Maven遷移的項目中,該插件尤其有用。此外當你發現需要編寫一些自定義程度很高的任務,同時又覺得

          Maven不夠靈活時,也可以以Ant的方式實現之。maven-antrun-plugin的run目標通常與生命周期綁定運行。


          # maven-archetype-plugin


          http://maven.apache.org/archetype/maven-archetype-plugin/


          Archtype指項目的骨架,Maven初學者最開始執行的Maven命令可能就是mvn archetype:generate,這實際上就是讓maven-archetype-plugin生成一個很簡單的項目骨架,幫助開發者快速上手。可能也有人看到一些文檔寫了mvn archetype:create, 但實際上create目標已經被棄用了,取而代之的是generate目標,該目標使用交互式的方式提示用戶輸入必要的信息以創建項目,體驗更好。maven-archetype-plugin還有一些其他目標幫助用戶自己定義項目原型,例如你有一個產品需要交付給很多客戶進行二次開發,你就可以為 他們提供一個Archtype,幫助他們快速上手。



          # maven-assembly-plugin


          http://maven.apache.org/plugins/maven-assembly-plugin/


          maven-assembly-plugin的用途是制作項目分發包,該分發包可能包含了項目的可執行文件、源代碼、readme、平臺腳本等等。maven-assembly-plugin支持各種主流的格式如zip、tar.gz、jar和war等,具體打包哪些文件是高度可控的,例如用戶可以 按文件級別的粒度、文件集級別的粒度、模塊級別的粒度、以及依賴級別的粒度控制打包,此外,包含和排除配置也是支持的。maven-assembly- plugin要求用戶使用一個名為assembly.xml的元數據文件來表述打包,它的single目標可以直接在命令行調用,也可以被綁定至生命周期。


          # maven-dependency-plugin


          http://maven.apache.org/plugins/maven-dependency-plugin/


          maven-dependency-plugin最大的用途是幫助分析項目依賴,dependency:list能夠列出項目最終解析到的依賴列表,dependency:tree能進一步的描繪項目依賴樹,dependency:analyze可以告訴你項目依賴潛在的問題,如果你有直接使用到的卻未聲明的依賴,該目標就會發出警告。maven-dependency-plugin還有很多目標幫助你操作依賴文件,例如dependency:copy-dependencies能將項目依賴從本地Maven倉庫復制到某個特定的文件夾下面。


          # maven-enforcer-plugin


          http://maven.apache.org/plugins/maven-enforcer-plugin/


          在一個稍大一點的組織或團隊中,你無法保證所有成員都熟悉Maven,那他們做一些比較愚蠢的事情就會變得很正常,例如給項目引入了外部的 SNAPSHOT依賴而導致構建不穩定,使用了一個與大家不一致的Maven版本而經常抱怨構建出現詭異問題。maven-enforcer- plugin能夠幫助你避免之類問題,它允許你創建一系列規則強制大家遵守,包括設定Java版本、設定Maven版本、禁止某些依賴、禁止 SNAPSHOT依賴。只要在一個父POM配置規則,然后讓大家繼承,當規則遭到破壞的時候,Maven就會報錯。除了標準的規則之外,你還可以擴展該插 件,編寫自己的規則。maven-enforcer-plugin的enforce目標負責檢查規則,它默認綁定到生命周期的validate階段。


          # maven-help-plugin


          http://maven.apache.org/plugins/maven-help-plugin/


          maven-help-plugin是一個小巧的輔助工具,最簡單的help:system可以打印所有可用的環境變量和Java系統屬性。help:effective-pom和help:effective-settings最 為有用,它們分別打印項目的有效POM和有效settings,有效POM是指合并了所有父POM(包括Super POM)后的XML,當你不確定POM的某些信息從何而來時,就可以查看有效POM。有效settings同理,特別是當你發現自己配置的 settings.xml沒有生效時,就可以用help:effective-settings來驗證。此外,maven-help-plugin的describe目標可以幫助你描述任何一個Maven插件的信息,還有all-profiles目標和active-profiles目標幫助查看項目的Profile。


          # maven-release-plugin


          http://maven.apache.org/plugins/maven-release-plugin/


          maven-release-plugin的用途是幫助自動化項目版本發布,它依賴于POM中的SCM信息。release:prepare用來準備版本發布,具體的工作包括檢查是否有未提交代碼、檢查是否有SNAPSHOT依賴、升級項目的SNAPSHOT版本至RELEASE版本、為項目打標簽等等。release:perform則 是遷出標簽中的RELEASE源碼,構建并發布。版本發布是非常瑣碎的工作,它涉及了各種檢查,而且由于該工作僅僅是偶爾需要,因此手動操作很容易遺漏一 些細節,maven-release-plugin讓該工作變得非常快速簡便,不易出錯。maven-release-plugin的各種目標通常直接在 命令行調用,因為版本發布顯然不是日常構建生命周期的一部分。


          # maven-resources-plugin


          http://maven.apache.org/plugins/maven-resources-plugin/


          為了使項目結構更為清晰,Maven區別對待Java代碼文件和資源文件,maven-compiler-plugin用來編譯Java代碼,maven-resources-plugin則用來處理資源文件。默認的主資源文件目錄是src/main/resources,很多用戶會需要添加額外的資源文件目錄,這個時候就可以通過配置maven-resources-plugin來實現。此外,資源文件過濾也是Maven的一大特性,你可以在資源文件中使用${propertyName}形式的Maven屬性,然后配置maven-resources-plugin開啟對資源文件的過濾,之后就可以針對不同環境通過命令行或者Profile傳入屬性的值,以實現更為靈活的構建。


          # maven-surefire-plugin


          http://maven.apache.org/plugins/maven-surefire-plugin/


          可能是由于歷史的原因,Maven 2/3中用于執行測試的插件不是maven-test-plugin,而是maven-surefire-plugin。其實大部分時間內,只要你的測試 類遵循通用的命令約定(以Test結尾、以TestCase結尾、或者以Test開頭),就幾乎不用知曉該插件的存在。然而在當你想要跳過測試、排除某些 測試類、或者使用一些TestNG特性的時候,了解maven-surefire-plugin的一些配置選項就很有用了。例如 mvn test -Dtest=FooTest 這樣一條命令的效果是僅運行FooTest測試類,這是通過控制maven-surefire-plugin的test參數實現的。


          # build-helper-maven-plugin


          http://mojo.codehaus.org/build-helper-maven-plugin/


          Maven默認只允許指定一個主Java代碼目錄和一個測試Java代碼目錄,雖然這其實是個應當盡量遵守的約定,但偶爾你還是會希望能夠指定多個 源碼目錄(例如為了應對遺留項目),build-helper-maven-plugin的add-source目標就是服務于這個目的,通常它被綁定到 默認生命周期的generate-sources階段以添加額外的源碼目錄。需要強調的是,這種做法還是不推薦的,因為它破壞了 Maven的約定,而且可能會遇到其他嚴格遵守約定的插件工具無法正確識別額外的源碼目錄。

          build-helper-maven-plugin的另一個非常有用的目標是attach-artifact,使用該目標你可以以classifier的形式選取部分項目文件生成附屬構件,并同時install到本地倉庫,也可以deploy到遠程倉庫。


          # exec-maven-plugin


          http://mojo.codehaus.org/exec-maven-plugin/


          exec-maven-plugin很好理解,顧名思義,它能讓你運行任何本地的系統程序,在某些特定情況下,運行一個Maven外部的程序可能就是最簡單的問題解決方案,這就是exec:exec的 用途,當然,該插件還允許你配置相關的程序運行參數。除了exec目標之外,exec-maven-plugin還提供了一個java目標,該目標要求你 提供一個mainClass參數,然后它能夠利用當前項目的依賴作為classpath,在同一個JVM中運行該mainClass。有時候,為了簡單的 演示一個命令行Java程序,你可以在POM中配置好exec-maven-plugin的相關運行參數,然后直接在命令運行 mvn exec:java 以查看運行效果。


          # jetty-maven-plugin


          http://wiki.eclipse.org/Jetty/Feature/Jetty_Maven_Plugin


          在進行Web開發的時候,打開瀏覽器對應用進行手動的測試幾乎是無法避免的,這種測試方法通常就是將項目打包成war文件,然后部署到Web容器 中,再啟動容器進行驗證,這顯然十分耗時。為了幫助開發者節省時間,jetty-maven-plugin應運而生,它完全兼容 Maven項目的目錄結構,能夠周期性地檢查源文件,一旦發現變更后自動更新到內置的Jetty Web容器中。做一些基本配置后(例如Web應用的contextPath和自動掃描變更的時間間隔),你只要執行 mvn jetty:run ,然后在IDE中修改代碼,代碼經IDE自動編譯后產生變更,再由jetty-maven-plugin偵測到后更新至Jetty容器,這時你就可以直接 測試Web頁面了。需要注意的是,jetty-maven-plugin并不是宿主于Apache或Codehaus的官方插件,因此使用的時候需要額外 的配置settings.xml的pluginGroups元素,將org.mortbay.jetty這個pluginGroup加入。

          # versions-maven-plugin


          http://mojo.codehaus.org/versions-maven-plugin/


          很多Maven用戶遇到過這樣一個問題,當項目包含大量模塊的時候,為他們集體更新版本就變成一件煩人的事情,到底有沒有自動化工具能幫助完成這件 事情呢?(當然你可以使用sed之類的文本操作工具,不過不在本文討論范圍)答案是肯定的,versions-maven- plugin提供了很多目標幫助你管理Maven項目的各種版本信息。例如最常用的,命令 mvn versions:set -DnewVersion=1.1-SNAPSHOT 就能幫助你把所有模塊的版本更新到1.1-SNAPSHOT。該插件還提供了其他一些很有用的目標,display-dependency- updates能告訴你項目依賴有哪些可用的更新;類似的display-plugin-updates能告訴你可用的插件更新;然后use- latest-versions能自動幫你將所有依賴升級到最新版本。最后,如果你對所做的更改滿意,則可以使用 mvn versions:commit 提交,不滿意的話也可以使用 mvn versions:revert 進行撤銷。

          # 小結


          本文介紹了一些最常用的Maven插件,這里指的“常用”是指經常需要進行配置的插件,事實上我們用Maven的時候很多其它插件也是必須的,例如 默認的編譯插件maven-compiler-plugin和默認的打包插件maven-jar-plugin,但因為很少需要對它們進行配置,因此不在 本文討論范圍。了解常用的Maven插件能幫助你事倍功半地完成項目構建任務,反之你就可能會因為經常遇到一些難以解決的問題而感到沮喪。本文介紹的插件 基本能覆蓋大部分Maven用戶的日常使用需要,如果你真有非常特殊的需求,自行編寫一個Maven插件也不是難事,更何況還有這么多開放源代碼的插件供 你參考。


          本文的這個插件列表并不是一個完整列表,讀者有興趣的話也可以去仔細瀏覽一下Apache和Codehaus Mojo的Maven插件列表,以得到一個更為全面的認識。最后,在線的Maven倉庫搜索引擎如http://search.maven.org/也能幫助你快速找到自己感興趣的Maven插件。


          來源:http://sina.lt/gkxW

          主站蜘蛛池模板: 日韩欧美一区二区三区免费观看 | 久久久久人妻一区精品性色av| 精品国产一区在线观看| 精品一区二区在线观看| 亚洲AV美女一区二区三区| 国产一区二区三区乱码| 精品伦精品一区二区三区视频 | 亚洲AV无码一区二区三区性色| 丝袜美腿一区二区三区| 一区二区三区中文| 亚洲综合无码AV一区二区| 少妇激情av一区二区| 国模精品一区二区三区视频| 好湿好大硬得深一点动态图91精品福利一区二区 | 日本一区二区三区高清| 人妻天天爽夜夜爽一区二区| 日本国产一区二区三区在线观看| 国产在线精品观看一区| 国产一区二区三区高清视频| 久久精品一区二区三区资源网| 亚洲码欧美码一区二区三区| 视频在线观看一区| 亚洲天堂一区二区三区| 无码人妻AⅤ一区二区三区水密桃| 亚洲午夜福利AV一区二区无码| 国产精品电影一区| 合区精品久久久中文字幕一区 | 极品少妇一区二区三区四区| 国产午夜精品一区理论片| 性色av闺蜜一区二区三区| 亚洲AV色香蕉一区二区| 无码人妻精品一区二 | 日产精品久久久一区二区| 久久成人国产精品一区二区| 国产一区高清视频| 麻豆国产在线不卡一区二区| 国产91久久精品一区二区| 精品一区二区三区免费| 制服中文字幕一区二区| 亚洲AV无码一区二三区| 亚洲免费一区二区|