技術提升美好事物發生的概率。
Technologically, for greater probability to be happy.
從chrome57開始,MediaSession就可以在chrome中被使用了。MediaSession讓我們可以對通知欄進行自定義操作,方便了用戶直接在通知欄對媒體的控制。在有了MediaSession后,即使在鎖屏界面,我們也可以看到媒體的信息,以及控制播放狀態。對于提供了播放列表的網頁,我們還可以直接切換至另一個媒體。下圖為官方提供的樣圖。
一、調起MediaSession的經過
在Chromium運行時,有一個Browser進程,若干個Render進程和一個GPU進程。后面會涉及到Browser進程以及Render進程。通常情況下,一個Tab或者多個Tab對應一個Render進程。
MediaSession的代碼在browser端,且在c++端和java端都有相對應的部分。接下來先分析一下調起MediaSession的代碼實現。
看播放視頻的時候是如何調起MediaSession的(多種情況,此處只舉出一種)。情景為點擊播放媒體按鍵,開始創建播放器與MediaSession。下面是主要的流程:
下面的是相對應的代碼:
最初,會創建一個HTMLMediaElement的DOM節點。該代碼在 HTMLMediaElement.cpp中。在這里會調用play(),若調用成功媒體能正常播放,則promise為完成態,否則為拒絕態。
接下來進入到剛剛被調用的play()方法中:
若視頻能播放,則調用PlayInternal(),否則就返回錯誤信息。代碼也在HTMLMediaElement.cpp中。
PlayInternal()中設置好當前是否暫停的狀態與能否自動播放后繼續調用UpdatePlayState(). UpdatePlayState()中在開始播放前設置好播放速率和聲音,然后調用GetWebMediaPlayer的Play()開始播放。代碼都在HTMLMediaElement.cpp里面。
同樣,這里也是調用UpdatePlayState()來更新播放狀態。此處與前面提到的play()與UpdatePlayState()有什么關系呢?前面的我們理解為在Webkit層的播放器,而后者理解為Content層的播放器。此處代碼在webmediaplay_impl.cc中。
UpdateSate()里調用SetDelegateState()來處理媒體狀態,然后在SetDelegateState()中處理了三種狀態,即關閉、播放與暫停。播放的話,則調用DidPlay()。代碼在webmediaplayer_impl.cc。
DidPlay()中主要向Browser端發送了IPC消息,于是播放媒體的消息就傳給了Browser端了。代碼在renderer_webmediaplayer_delegate.cc里面。下面的代碼就直接到Browser部分了。
這里Browser端收到IPC消息后執行OnMediaPlaying(),此處看到203行,已經調用了MediaSessionControllersManager的RequestPlay(),后面部分開始已經到MediaSession的主體了。代碼在media_web_contents_observer.cc。
MediaSessionControllersManager 控制所有的MediaSessionController。在RequestPlay()的時候會創建一個MediaSessionController,在構造控制器的時候還會設置好對應的 MediaSession,一個MediaSessionController控制一個MediaSession。通過controller初始化MediaSession里的播放器。
代碼在media_session_controller.cc里面。
二、C++端整體邏輯
MediaSessionControllersManager:由MediaWebContentsObserver調用,通過MediaSessionController來控制MediaSession。上面就是一個map,通過MediaPlayerId來對應不同的Controller,這里也可以看出一個播放器對應了一個Controller。
MediaSessionController:當我們在通知中對媒體進行操作時,Java端會發送消息過來,在Controller中收到后向Renderer發送IPC消息,控制Renderer里面的WebMediaPlayer。在構造MediaSessionController的時候還會給他對應的MediaSessionImpl賦值,此處可以看出每個MediaSessionController控制一個MediaSessionImpl。
MediaSessionImpl:整個MediaSession的中心,處理所有媒體與通知有關的事項。主要是播放狀態與焦點狀態的控制。其中的NotifyAboutStateChange()提供了能否控制與是否暫停兩個狀態,這兩個值對控制播放的非常重要。能否控制表示我們是否能通過通知欄控制這個播放器,如果不能控制,則通知欄中不會出現。
MediaSessionServiceImpl:提供一些服務,主要是設置播放狀態與播放支持的動作,且這兩項決定了通知欄所展現的界面。代碼中setMetadata()也就是設置元數據,里面包含圖片,aritst,title等數據。Action部分也就是與支持的動作有關,如播放、暫停、前進、后退這些。
MediaMetadataSanitizer:功能性的類,MediaMetadataSanitizer檢查與處理元數據格式的合理性,調用了Sanitize()。
AudioFocusDelegateAndroid:RequestAudioFocus()和AbandonAudioFocus()分別為通過JNI獲取與丟失焦點。當系統要求MediaSession進行暫停、繼續等操作的時候,從Java端發送消息給MediaSession,如電話(系統級別),上面給出OnSuspend()的例子。AudioFocus相關簡介下面提到一些。
MediaSessionAndroid:MediaSession通過JNI主要向Java端發送的是三個狀態變化,即MediaSessionStateChanged、MediaSessionMetadataChanged和MediaSessionActionsChanged。用戶在通知欄進行操作時消息會從Java端傳過來(UI級別),如上面例子給出的Suspend()。
Audio Focus
三、Java端整體邏輯
AudioFocusDelegate.java:主要有requestAudioFocus()和abandonAudioFocus(),分別用來獲取與丟棄焦點。這一部分通過SDK里面的AudioManager,最終調用了API來獲取與丟棄系統的音頻焦點。
MediaSessionImpl.java:主要有mediaSessionStateChanged(), mediaSessionMetadataChanged(), mediaSessionActionsChanged()。都是native端傳給Java端的消息,分別表示媒體會話的狀態、元數據、動作的改變。此處動作表示網頁支持的操作集合。
MediaSessionTabHelper.java :一方面處理native端傳過來的信息,令一方面對各種操作都基本調用MediaNotificationManager來處理,相當于傳遞了控制信息。
到這里整個MediaSession的整個流程就走完了,MediaSession從播放器創建的時候創建出來, 并溝通了瀏覽器與通知欄,同時對媒體焦點的支持,讓其成為一個非常好的特性。
檔對象模型DOM(Document Object Model)定義訪問和處理HTML文檔的標準方法。DOM 將HTML文檔呈現為帶有元素、屬性和文本的樹結構(節點樹).
將HTML代碼分解為DOM節點層次圖:
HTML文檔可以說由節點構成的集合,DOM節點有:
1. 元素節點:上圖中<html>、<body>、<p>等都是元素節點,即標簽。
2. 文本節點:向用戶展示的內容,如<li>...</li>中的JavaScript、DOM、CSS等文本。
3. 屬性節點:元素屬性,如<a>標簽的鏈接屬性href="https://www.google.com"。
1. 頂層API
documment.getElementById() 返回一個元素;
document.getElementsByName() 返回一個dom數組,具有相同name值的;
domdocument.getElementsByTagName() 返回一個dom數組,具有相同的標簽名;
2. 通過父節點獲取
parent.firstChild;
parent.lastChild;
parent.childNodes;
parent.children;
parent.getElementsByTagName;
3. 通過子節點獲取childNode.parentNode;
4. 通過臨近節點獲取;
neighbour.previousSibling;
neighbour.nextSibling;
appendChild()
insertBefore();
replaceChild(替換節點, 被替換的節點)
removeChild(被移除的節點)
cloneNode(bool)bool為true時,深復制,復制節點以及節點的所有子節點;
bool為false時,淺復制,只復制節點本身;
OM可以將任何HTML描繪成一個由多層節點構成的結構。節點分為12種不同類型,每種類型分別表示文檔中不同的信息及標記。每個節點都擁有各自的特點、數據和方法,也與其他節點存在某種關系。節點之間的關系構成了層次,而所有頁面標記則表現為一個以特定節點為根節點的樹形結構。
節點中的各種關系可以用傳統的家族關系來描述,相當于把文檔樹比喻成家譜。接下來,將把DOM節點關系分為屬性和方法兩部分進行詳細說明
屬性
父級屬性parentNode
每個節點都有一個parentNode屬性,該屬性指向文檔樹中的父節點。對于一個節點來說,它的父節點只可能是三種類型:element節點、document節點和documentfragment節點。如果不存在,則返回null
parentElement
parentNode跟parentElement除了前者是w3c標準,后者只ie支持(chrome現在都支持)
當父節點的nodeType不是1,即不是element節點的話,它的parentElement就會是null
一般情況parentNode可以取代parentElement的所有功能
parentElement匹配的是parent為element的情況,而parentNode匹配的則是parent為node的情況。element是包含在node里的,它的nodeType是1
<div id="myDiv"></div> <script> console.log(myDiv.parentElement);//body console.log(document.body.parentElement);//html console.log(document.documentElement.parentElement);//null console.log(document.parentElement);//null </script>
[注意]在IE瀏覽器中,只有Element元素節點才有該屬性,其他瀏覽器則是所有類型的節點都有該屬性
<div id="test">123</div> <script> //IE瀏覽器返回undefined,其他瀏覽器返回<div id="test">123</div> console.log(test.firstChild.parentElement); //所有瀏覽器都返回<body> console.log(test.parentElement); </script>
childNodes
childNodes是一個只讀的類數組對象NodeList對象,它保存著該節點的第一層子節點
<ul id="myUl"> <li><div></div></li> </ul> <script> var myUl=document.getElementById('myUl'); //結果是只包含一個li元素的類數組對象[li] console.log(myUl.childNodes); </script>
children
children是一個只讀的類數組對象HTMLCollection對象,但它保存的是該節點的第一層元素子節點
<div id="myDiv">123</div> <script> var myDiv=document.getElementById('myDiv'); //childNodes包含所有類型的節點,所以輸出[text] console.log(myDiv.childNodes); //children只包含元素節點,所以輸出[] console.log(myDiv.children); </script>
childElementCount
返回子元素節點的個數,相當于children.length
[注意]IE8-瀏覽器不支持
<ul id="myUl"> <li></li> <li></li> </ul> <script> var myUl=document.getElementById('myUl'); console.log(myUl.childNodes.length);//5,IE8-瀏覽器返回2,因為不包括空文本節點 console.log(myUl.children.length);//2 console.log(myUl.childElementCount);//2,IE8-瀏覽器返回undefined </script>
firstChild
第一個子節點
lastChild
最后一個子節點
firstElementChild
第一個元素子節點
lastElementChild
最后一個元素子節點
上面四個屬性,IE8-瀏覽器和標準瀏覽器的表現并不一致。IE8-瀏覽器不考慮空白文本節點,且不支持firstElementChild和lastElementChild
//ul標簽和li標簽之間有兩個空白文本節點,所以按照標準來說,ul的子節點包括[空白文本節點、li元素節點、空白文本節點]。但在IE8-瀏覽器中,ul的子節點只包括[li元素節點] <ul> <li></li> </ul>
<ul id="list"> <li>1</li> <li>2</li> <li>3</li> </ul> <script> console.log(list.firstChild);//標準瀏覽器中返回空白文本節點,IE8-瀏覽器中返回<li>1</li> console.log(list.lastChild);//標準瀏覽器中返回空白文本節點,IE8-瀏覽器中返回<li>3</li> console.log(list.firstElementChild);//標準瀏覽器中<li>1</li>,IE8-瀏覽器中返回undefined console.log(list.lastElementChild);//標準瀏覽器中<li>3</li>,IE8-瀏覽器中返回undefined </script>
nextSibling
后一個節點
previousSibling
前一個節點
nextElementSibling
后一個元素節點
previousElementSibling
前一個元素節點
與子級屬性類似,上面四個屬性,IE8-瀏覽器和標準瀏覽器的表現并不一致。IE8-瀏覽器不考慮空白文本節點,且不支持nextElementSibling和previousElementSibling
<ul> <li>1</li> <li id="myLi">2</li> <li>3</li> </ul> <script> var myLi=document.getElementById('myLi'); console.log(myLi.nextSibling);//空白節點,IE8-瀏覽器返回<li>3</li> console.log(myLi.nextElementSibling);//<li>3</li>,IE8-瀏覽器返回undefined console.log(myLi.previousSibling);//空白節點,IE8-瀏覽器返回<li>1</li> console.log(myLi.previousElementSibling);//<li>1</li>,IE8-瀏覽器返回undefined </script>
包含方法hasChildNodes()
hasChildNodes()方法在包含一個或多個子節點時返回true,比查詢childNodes列表的length屬性更簡單
<div id="myDiv">123</div> <script> var myDiv=document.getElementById('myDiv'); console.log(myDiv.childNodes.length);//1 console.log(myDiv.hasChildNodes());//true </script>
<div id="myDiv"></div> <script> var myDiv=document.getElementById('myDiv'); console.log(myDiv.childNodes.length);//0 console.log(myDiv.hasChildNodes());//false </script>
contains()
contains方法接受一個節點作為參數,返回一個布爾值,表示參數節點是否為當前節點的后代節點。參數為后代節點即可,不一定是第一層子節點
<div id="myDiv"> <ul id="myUl"> <li id="myLi"></li> <li></li> </ul> </div> <script> console.log(myDiv.contains(myLi));//true console.log(myDiv.contains(myUl));//true console.log(myDiv.contains(myDiv));//true </script>
[注意]IE和safari不支持document.contains()方法,只支持元素節點的contains()方法
//IE和safari報錯,其他瀏覽器返回true console.log(document.contains(document.body));
compareDocumentPosition()
compareDocumentPosition方法用于確定節點間的關系,返回一個表示該關系的位掩碼
000000 0 兩個節點相同
000001 1 兩個節點不在同一個文檔(即有一個節點不在當前文檔)
000010 2 參數節點在當前節點的前面
000100 4 參數節點在當前節點的后面
001000 8 參數節點包含當前節點
010000 16 當前節點包含參數節點
100000 32 瀏覽器的私有用途
<div id="myDiv"> <ul id="myUl"> <li id="myLi1"></li> <li id="myLi2"></li> </ul> </div> <script> //20=16+4,因為myUl節點被myDiv節點包含,也位于myDiv節點的后面 console.log(myDiv.compareDocumentPosition(myUl)); //10=8+2,因為myDiv節點包含myUl節點,也位于myUl節點的前面 console.log(myUl.compareDocumentPosition(myDiv)); //0,兩個節點相同 console.log(myDiv.compareDocumentPosition(myDiv)); //4,myLi2在myLi1節點的后面 console.log(myLi1.compareDocumentPosition(myLi2)); //2,myLi1在myLi2節點的前面 console.log(myLi2.compareDocumentPosition(myLi1)); </script>
isSameNode()和isEqualNode()
這兩個方法都接受一個節點參數,并在傳入節點與引用節點相同或相等時返回true
所謂相同(same),指的是兩個節點引用的是同一個對象
所謂相等(equal),指的是兩個節點是相同的類型,具有相等的屬性(nodeName、nodeValue等等),而且它們的attributes和childNodes屬性也相等(相同位置包含相同的值)
[注意]firefox不支持isSameNode()方法,而IE8-瀏覽器兩個方法都不支持
<script> var div1=document.createElement('div'); div1.setAttribute("title","test"); var div2=document.createElement('div'); div2.setAttribute("title","test"); console.log(div1.isSameNode(div1));//true console.log(div1.isEqualNode(div2));//true console.log(div1.isSameNode(div2));//false </script>
寫在后面
這里列的很多方法大家可能都沒見過,或者學過JQuery之后對這些方法既熟悉又陌生,大家可以把我當這些小例子運行一下,就能更直觀的區分這些方法之間的關聯和區別,這對我們后面的分享和學習有一定的理解。
1、JavaScript設計模式之策略模式(Strategy Pattern)
2、JavaScript設計模式之職責鏈模式(Chain of Responsibility...)
3、JavaScript設計模式之享元模式(flyweight Pattern)
4、JavaScript設計模式之裝飾者模式(Decorator Pattern)
5、JavaScript設計模式之代理模式(Proxy Pattern)
6、JavaScript設計模式之組合模式(Composite Pattern)
7、JavaScript設計模式之工廠模式(Factory Method Pattern)
8、JavaScript設計模式之中介者模式(Mediator Pattern)
....
參考文章:
https://www.cnblogs.com/xiaohuochai/p/5785297.html
http://www.voidcn.com/article/p-gasrrkzi-bpb.html
https://www.cnblogs.com/zhishaofei/p/4091865.html
*請認真填寫需求信息,我們會在24小時內與您取得聯系。