技术提升美好事物发生的概率。
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小时内与您取得联系。