概述
DLNA投屏简介
本文关于DLNA的简介只限于控制端的实现,未涉及接收端和服务端。
文章目录
- DLNA投屏简介
- 常见投屏方案
- DLNA
- AirPlay
- Miracast
- 常用名词解释
- DLNA投屏流程
- 设备发现
- 设备控制
- 事件处理
- 轮询
- 事件订阅
- 遇到的问题
最近项目中提了一个投屏的需求,之前完全没有接触过这一块,经过一段时间的努力对DLNA有了初步了解,并顺利实现了业务需求,现将自己浅显的理解记录一下,希望能帮助对此有兴趣、有需要的同学更快速的了解其原理,本菜才疏学浅,见识不多,纰漏之处还请指正。
为什么自己实现一套,而不是使用第三方库(Android平台)?
其实开始使用Cling做了一下,第三方库大而全,虽然兼容性一般不错,却难以很方便地维护,有时候牵一发而动全身,不花大量时间分析实现逻辑难以对其改造,最严重的是引入Cling会让应用增加1.2M左右,而自己实现只有100k(aar),轻巧易维护,适合自己的项目使用。
常见投屏方案
常见的投屏方案主要有以下几种:
DLNA
DLNA的全称是DIGITAL LIVING NETWORK ALLIANCE(数字生活网络联盟)。DLNA委员会已经于2017年1月5日正式解散,原因是旧的标准已经无法满足新设备的发展趋势,DLNA标准将来也不会再更新。但是DLNA协议的使用依然比较广泛,短时间内不会退出历史舞台,在某些情况下依然是最好的解决方案之一。
DLNA不是技术,而是一种方案,一种大家可以遵守的规范,其各种技术和协议都是目前所应用很广泛的技术和协议(SSDP、SOAP等)。
在我看来,DLNA协议栈为设备之间信息交流提供了一种彼此听得懂的语言工具。
AirPlay
AirPlay于DLNA类似,例如两种都是基于组播实现的设备发现,只不过DLNA基于SSDP(简单服务发现协议),而AirPlay基于mDNS(multicast DNS),甚至苹果曾经也是DLNA委员会的成员。相对DLNA,AirPlay提供了一套完善的官方标准实现,开发者只需要按照文档调用API即可,当然如果需要在第三方设备上实现AirPlay功能,需要自己实现一套与AirPlay兼容的功能,网上就有通过分析抓包实现的第三方AirPlay兼容库,包括发送端和接收端。
Miracast
以Wi-Fi Direct(和UPnP都是局域网P2P)为基础的无线显示标准,出现时间晚(2012),使用范围相对较小。支持此标准的设备可通过无线方式分享视频画面。与DLNA有较大差异的在于DLNA设备服务端(DMS,Digital Media Server)基于文件的方式提供服务,文件解码由接收端完成(DMR,Digital Media Render),因此DMR需要支持较多格式以保证兼容性;而Miracast则是由服务端完成解码并重新编码为H.264传输到接收端,接收端只需要对H.264解码即可。
基于以上对比来看,DLNA使用广泛,在主流的电视、智能机顶盒中都有支持,而且终端工作量小,是不错的方案。
常用名词解释
概念是很无聊的,容易让人失去兴趣,略作了解即可,无需深究。
- DMS
Digital Media Server 数位媒体服务器: 提供了媒体档案的获取、录制、储存以及作为源头的装置。我的理解,简而言之约等于一个文件服务器。 - DMR
Digital Media Renderer 数字媒体喧染器: 主要的功能是用来播放由DMC从DMS中所指定的数字媒体,例如:数字电视、智能机顶盒、电脑上实现了DLNA协议的视频播放器。 - DMP
Digital Media Player 数位媒体播放器: 可寻找并播放或输出任何由DMS所提供的媒体档案的装置。DMR与DMP的区别在于DMR只有接受媒体和播放功能,而没查找有浏览媒体的功能。比如显示器、音箱等。 - 对于移动设备还有M-DMS、M-DMR、M-DMP等
- DMC
Digital Media Controller 数字媒体控制器,查找DMS的内容并建立DMS与DMR之间的连接并控制媒体的播放。 - ControlPoint 控制点,这个概念在不少实现中都有,但我认为这是一个虚拟的称谓,即对控制模块的称谓,资料显示控制点是对控制Action发出者的称呼,谁能发出谁就是CP,我的实现中将每个设备抽象为一个Device,MRDevice继承Device,并提供对应的控制接口,没有直接用到这个看起来很合理却又容易把人绕晕的”控制点“概念。
- UPnP
通用即插即用协议,使用了SSDP(简单设备发现协议)和SOAP(简单对象访问协议)等几个协议。可以说DLNA很大程度上是基于UPnP的。
DLNA投屏流程
投屏的过程主要分为 设备发现 和 设备控制 两个阶段
设备发现
设备发现也分为两步,第一步是获取设备基本描述,第二步是获取设备详细描述。
第一步有两种方式:主动发现和被动发现。
- 主动发现设备
主动发现是指设备主动通过UDP发出Search组播到指定地址和端口,ipv4为239.255.255.250:1900,ipv6为[FF0x::C]:1900,目标设备收到组播后会通过UDP单播发送设备基本信息(所以终端需要用Socket绑定search发送的那个随机端口,receive单播回包),然后根据基本信息中的设备描述地址获取设备的详细信息。
search包内容如下:
M-SEARCH * HTTP/1.1
ST: upnp:rootdevice
HOST: 239.255.255.250:1900
MX: 3
MAN: "ssdp:discover"
其中ST
是Search Type,常见的ST有ssdp:all
、upnp:rootdevice
、uuid:device-某UUID
、urn:schemas-upnp-org:device:device-Type:version
等,投屏这里使用的是upnp:rootdevice,
HOST
为组播地址,
MX
为最大等待时间,
MAN
为固定格式。
设备回包内容如:
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Fri, 23 Nov 2018 11:26:00 GMT
EXT:
LOCATION: http://192.168.2.3:49153/description.xml
SERVER: SHP, UPnP/1.0, Samsung UPnP SDK/1.0
ST: upnp:rootdevice
USN: uuid:ecf9f8c1-e1a3-459e-a33e-1f6413af9aef::upnp:rootdevice
Content-Length: 0
其中最重要的是LOCATION
,其中包含了目标设备的ip、upnp服务的端口、设备详细描述地址,有了这个地址就可以获取设备的详细信息,具体内容见下文;USN作为服务的唯一识别ID,在设备详细描述中还有,可以暂时忽略。
- 被动发现设备
被动发现是指目标设备通过UDP发送Notify组播到局域网(所以终端需要启动一个MulticastSocket joinGroup到上述组播地址监听组播),设备收到组播后可以得到设备描述地址获取设备的详细信息。
Notify报文的内容如下:
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=66
LOCATION: http://192.168.2.3:49153/description.xml
NT: upnp:rootdevice
NTS: ssdp:alive
SERVER: Linux/3.10.79, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
USN: uuid:F7CA5454-3F48-4390-8009-2c3aed46c9a9::upnp:rootdevice
其中也包含了LOCATION
,详细信息就不愁啦;还有两个需要关注的值:NT
和NTS
,前者是Notify Type(与Search中的ST类似),后者表示NT的子类型,其值只可以是ssdp:alive
或ssdp:byebye
,目标设备会在生命周期中定期发送alive组播,在正常退出时发送byebye组播,也有实现会在目标设备上线时先发送byebye然后发送alive,便于控制端及时更新设备信息。NT有较多类型,我们只关注upnp:rootdevice类型的Notify即可。
- 获取设备详细信息
至此,无论前面通过何种方式,我们都已经得到了一个重要的信息:LOCATION,向该地址发送一个简单的HTTP请求,即可得到详细的设备信息,无论是做投屏还是做基于DLNA的打印机,原理都是一样的,尤其是前面的部分,一模一样,而后面的部分也是换汤不换药,换成了打印相关的服务而已,设备描述示例如下。
head
HTTP/1.1 200 OK
CONTENT-LENGTH: 2506
CONTENT-TYPE: text/xml
DATE: Mon, 07 Jan 2019 11:26:00 GMT
LAST-MODIFIED: Mon, 07 Jan 2019 11:25:17 GMT
SERVER: Linux/3.10.65, UPnP/1.0, Portable SDK for UPnP devices/1.6.13
X-User-Agent: redsonic
CONNECTION: close
Head中没有什么需要特别关心的信息,重点看body内容
body
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<device>
<deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
<presentationURL>/</presentationURL>
<friendlyName>成精了的电视</friendlyName>
<manufacturer>XXXX</manufacturer>
<manufacturerURL>http://www.xxx.com</manufacturerURL>
<modelDescription>xxx Media Render</modelDescription>
<modelName>xxxxx</modelName>
<modelURL>http://www.xxx.com</modelURL>
<UDN>uuid:F7CA5454-3F48-4390-8009-dce3a07b5e48</UDN>
<UID>-1254112285</UID>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
<serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
<SCPDURL>/dlna/Render/AVTransport_scpd.xml</SCPDURL>
<controlURL>_urn:schemas-upnp-org:service:AVTransport_control</controlURL>
<eventSubURL>_urn:schemas-upnp-org:service:AVTransport_event</eventSubURL>
</service>
<service>
<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
<SCPDURL>/dlna/Render/ConnectionManager_scpd.xml</SCPDURL>
<controlURL>_urn:schemas-upnp-org:service:ConnectionManager_control</controlURL>
<eventSubURL>_urn:schemas-upnp-org:service:ConnectionManager_event</eventSubURL>
</service>
<service>
<serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
<serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
<SCPDURL>/dlna/Render/RenderingControl_scpd.xml</SCPDURL>
<controlURL>_urn:schemas-upnp-org:service:RenderingControl_control</controlURL>
<eventSubURL>_urn:schemas-upnp-org:service:RenderingControl_event</eventSubURL>
</service>
</serviceList>
<av:X_RController_DeviceInfo xmlns:av="urn:mi-com:av">
<av:X_RController_Version>1.0</av:X_RController_Version>
<av:X_RController_ServiceList>
<av:X_RController_Service>
<av:X_RController_ServiceType>controller</av:X_RController_ServiceType>
<av:X_RController_ActionList_URL>http://192.168.2.3:6095/</av:X_RController_ActionList_URL>
</av:X_RController_Service>
<av:X_RController_Service>
<av:X_RController_ServiceType>data</av:X_RController_ServiceType>
<av:X_RController_ActionList_URL>http://api.tv.xx.com/bolt/3party/</av:X_RController_ActionList_URL>
</av:X_RController_Service>
</av:X_RController_ServiceList>
</av:X_RController_DeviceInfo>
</device>
<URLBase>http://192.168.2.3:49152/</URLBase>
</root>
在长长的信息中,我们需要关注的有device
标签下的deviceType
、friendlyName
和UDN
,其中friendlyName是设备的展示名,即给人看的名字,UDN是根据UUID生成的时间无关的设备失败码,其中包含了UUID,我们可以以此为设备id区分不同设备、处理设备的掉线和重连等。
紧接着,在serviceList
中列出了设备提供的服务列表service
,service标签下有serviceType
、serviceId
、SCPDURL
、controlURL
和eventSubURL
5个子标签,其中serviceType是判断设备提供的服务类型的依据,对于支持投屏的设备,一般有AVTransport
、RenderingControl
、ConnectionManager
三种服务,投屏过程中主要使用前两种,每个服务的支持的控制指令可以通过SCPDURL查看,或继续浏览下文用法;serviceId没啥好说的;SCPDURL为服务描述地址,请求会返回该服务的详细描述,包括服务支持的指令及其参数等,因为投屏使用的是比较标准的服务和指令,所以可以不需要请求服务的详细说明也能正常使用;controlURL是服务的控制地址,指令的发送就是往这个地址发的;eventSubURL是用来向目标设备订阅该服务相关的事件回调的,需要控制端运行一个ServerSocket监听tcp请求。
设备控制
前面已经提到,设备控制就是望设备的对应服务的controlURL发送HTTP请求,这里以POST方式为例,向TV的AVTransport服务发送SetAVTransportURI指令,作用是告诉TV需要播放的直播流的地址,内容如下:
head
POST /_urn:schemas-upnp-org:service:AVTransport_control HTTP/1.1
Connection: close
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"
Content-Type: text/xml;charset="utf-8"
Content-Length: 1464
Host: 192.168.2.3:49152
User-Agent:
其中有两个参数用法说明一下(服务类型记即上面服务列表中服务的serviceType,控制地址为controlURL)
POST 控制地址 HTTP/1.1
SOAPACTION: "服务类型#Action"
body
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<CurrentURI>http://xxx.xxx.com/xxx.m3u8?bizid=xxx&txSecret=fcexxxxxab4cf1b8bbee6efbe6668bd4&txTime=5c3c5de1&uid=0</CurrentURI>
<CurrentURIMetaData><DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sec="http://www.sec.co.kr/"><item id="123" parentID="-1" restricted="1"><upnp:storageMedium>UNKNOWN</upnp:storageMedium><upnp:writeStatus>UNKNOWN</upnp:writeStatus><dc:title>Video</dc:title><dc:creator>QGame</dc:creator><upnp:class>object.item.videoItem</upnp:class><res protocolInfo="http-get:*:video/*:DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000">http://xxx.xxx.com/xxx.m3u8?bizid=xxx&amp;txSecret=fcexxxxxab4cf1b8bbee6efbe6668bd4&amp;txTime=5c3c5de1&amp;uid=0</res></item></DIDL-Lite></CurrentURIMetaData>
</u:SetAVTransportURI>
</s:Body>
</s:Envelope>
看起来一堆东西,其实大部分都是固定格式,只有其中的少部分参数需要说明一下(Action为服务提供的Action,可以从服务描述地址获取详细说明,下文也有常用Action的列表)
<u:Action xmlns:u="服务类型">
<参数名1>参数值1</参数名1>
<参数名2>参数值2</参数名2>
...
</u:Action>
元数据中包含了协议的相关数据,没有特殊需求的话,套用常用的元数据内容即可,值得注意的是,元数据中res标签包含了转义过的视频地址(反转义一下就明显看出来了),而元数据也经过转义才放到body中的CurrentURIMetaData
标签下,也就是说元数据中的视频URL经过了两次转义,&
将转义为&amp;
,该转义为常用的xml转义,同样在处理upnp回包时,也要留意upnp需要转义的情况,具体规则为
原字符 | 转义字符 |
---|---|
& | & |
" | " |
< | < |
> | > |
空格 | |
’ | ' |
转义这里不可以偷懒,以免造成兼容性问题,在已测的设备中,绝大多数电视取视频URL都是使用CurrentURI
标签提供的URL,而三星电视则是从CurrentURIMetaData
标签取得视频URL,如果元数据设置不对的话,很可能导致三星这种电视无法正常播放。
播放过程中不同的控制Action都是类似的,前后都是固定格式,稍作调整就成了另外一个控制Action,如PlayAction
head
POST /_urn:schemas-upnp-org:service:AVTransport_control HTTP/1.1
Connection: close
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Play"
Content-Type: text/xml;charset="utf-8"
Content-Length: 327
Host: 192.168.2.3:49152
User-Agent:
body
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<Speed>1</Speed>
</u:Play>
</s:Body>
</s:Envelope>
具体每个Action的参数参考所属服务的详细描述,这里列出常用Action及其对应的服务和参数
Action | Service | 参数 | 常用值 | 说明 |
---|---|---|---|---|
SetAVTransportURI | AVTransport | InstanceID、CurrentURI、CurrentURIMetaData | 0、转义的视频地址、见上文 | 设置视频地址 |
Play | AVTransport | InstanceID、Speed | 0、1 | 播放 |
Pause | AVTransport | InstanceID | 0 | 暂停 |
Stop | AVTransport | InstanceID | 0 | 停止 |
Seek | AVTransport | InstanceID、Unit、Target | 0、见备注、见备注 | 跳转[1] |
SetMute | RenderingControl | InstanceID、Channel、DesiredMute | 0、Master、1/0 | 静音/取消[2] |
SetVolume | RenderingControl | InstanceID、Channel、DesiredVolume | 0、Master、0-100 | 设置音量 |
GetVolume | RenderingControl | InstanceID、Channel | 0、Master | 获取音量 |
GetCurrentTransportActions | AVTransport | InstanceID | 0 | 获取Action列表[3] |
GetMediaInfo | AVTransport | InstanceID | 0 | 获取媒体信息 |
GetPositionInfo | AVTransport | instanceID | 0 | 获取进度 |
GetTransportInfo | AVTransport | instanceID | 0 | 获取传输状态[4] |
[1] Unit
有TRACK_NR
、ABS_TIME
、ABS_COUNT
、REL_COUNT
、CHANNEL_FREQ
、TAPE_INDEX
、FRAME
7种取值,参见微软定义,这里使用REL_TIME
。 Target
格式因Unit
而定,如果Unit=REL_TIME,则格式为 “00:11:26”,表示跳转到某个进度,如果Unit=TRACK_NR,则格式为一个整数i,跳转到第i个视频(应该是的,未做验证)。
[2] DesiredMute
设置为1表示静音,0表示取消静音。
[3] GetCurrentTransportActions
返回结果不太准。
[4] GetTransportInfo
可以获取当前传输状态,如STOPPED、PLAYING等,但也不准确,有的设备已经用遥控器停止播放了,获取到的还是PLAYING。
Action的成功与否主要通过POST请求返回的状态码判断,如果是 200 OK,那应该就是成功的了,大多数Action回包内容都非常简单,没有需要处理的返回值,部分Action如GetVolume
具有返回值,需要解析回包。如果失败,根据状态码(如500等)及body中说明的错误信息定位问题,对比可以正常投屏的其它应用的请求内容,分析问题原因。
事件处理
当控制端通过SetAVTransportURI
、Play
让目标设备开始播放视频时,设备会进行加载缓冲,并开始播放,或者用户通过遥控器暂停/继续播放,甚至其它控制端抢占了TV等,都是我们需要关心的事件,以便控制端进行状态处理。获取目标设备的状态变化有两种方式,
- 轮询
GetTransportInfo
、GetMediaInfo
等Action, - 向目标设备注册订阅。
两种方式各有优缺点,因为目标设备实现存在差异,每个设备的在状态的处理上不完全一致。前面已经提到过,GetTransportInfo返回结果不太靠谱;第二种方式,结果较为准确,但是对控制端抢占TV导致更换URL等情况大多不会告知订阅者;所以结合两种方式,可以达到较好的效果。
轮询
没啥好说的,不端发送获取想要状态信息的请求就行了。
事件订阅
-
订阅
设备描述的服务列表中,每个服务都有一个eventSubURL
,我们可以在控制端运行一个ServerSocket绑定一个端口(记为端口A),通过accept监听tcp请求,并将本机ip和端口A和自定义回调路径拼接为url通过SUBSCRIBE
Action发送给目标设备,即可完成订阅,在必要的时候,通过SUBSCRIBE
Action(与订阅使用同一个Action,但参数不同)续订,通过UNSUBSCRIBE
Action取消订阅。
参数格式如下表:
| Action | 参数 | 常用值 | 说明 |
| ----------- | :-------------------: | :---------------------------------------: | :------: |
| SUBSCRIBE | Nt、Timeout、Callback | upnp:event、Second-时间、<自定义回调地址> | 订阅[1] |
| SUBSCRIBE | SID、Timeout | 订阅ID、Second-时间 | 续订[2] |
| UNSUBSCRIBE | SID | 订阅ID | 取消订阅 |
[1]Callback
的值URL用<>
包裹。
[2]SID
为SUBSCRIBE ID,是订阅Action返回的值。 -
回调处理
ServerSocket接收Socket链接,并读取回调内容,后面过程与处理Action回包类似,解析upnp内容即可(upnp是XML子集),其中需要关注的只有lastchange标签里的内容,以播放事件为例,播放事件的LastChange标签内容为:
<Event xmlns = "urn:schemas-upnp-org:metadata-1-0/AVT/">
<InstanceID val="0">
<TransportState val="PLAYING"/>
<TransportStatus val="OK"/>
</InstanceID>
</Event>
遇到的问题
在实现的过程中遇到了一系列的问题,解决了一部分。
- 应用收不到组播
某些ROM定制的厂商不接收UDP组播包(可能是为了省电),为了保险起见,需要在开始接收组播之前如下设置
WifiManager.MulticastLock multicastLock = null;
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wm != null) {
multicastLock = wm.createMulticastLock(TAG);
multicastLock.setReferenceCounted(true);
multicastLock.acquire();
}
结束的时候调用
multicastLock.release();
-
Sony电视投屏失败(业务层)
测试兼容性的时候发现,Sony电视在开机之后我们的应用无法投屏,但是在使用腾讯视频等其它应用投屏一次之后,我们就可以正常投屏了。我们在业务层使用的投屏策略是投屏之前先Stop
一下,然后SetAVTransportURI
,再Play
,常用的三步走策略,每个目标设备实现都存在差异,可能有一些设备不Stop就无法播放新的内容,也有的电视SetAVTransportURI就开始播放了,而有的需要Play才会开始播放,三步走可以保证目标设备可以正常投屏。调试发现,Sony电视在开机之后,未投屏之前直接Stop会返回失败(500错误码),一旦开始投屏之后,无论是否正在投屏,Stop都会正常返回。当时业务逻辑写的Stop成功才会继续后续两步,Stop失败就导致了整个流程中断,后面改为Stop失败也继续往下走,问题解决。 -
视频流兼容问题
- LG
直播使用的是没m3u8的流,LG电视无法播放我们的m3u8流,后做了失败重试逻辑,可以自动/手动切换flv流,可以成功投屏(但flv也有一定的失败率)。 - 创维企鹅盒子1v
这个盒子使用flv播放一切正常,但是使用m3u8会crash(盒子的DLNA服务crash并重启服务),所以在业务层做了设备更新管理。当检测到有相同uuid的设备出现在局域网,但ip或端口发生变化时,认为该设备发生了变化(重启或重连网络等),设备管理器(自己实现的负责维护设备状态的类)会移除原来的设备并添加新设备,业务层会收到对应事件,并判断如果该设备是已选中的正在投屏的设备,会自动重试(切流重试),此类问题得到解决。另外,在大概2018年11月的一次更新(盒子投屏版本4.01.41)已经修复了该问题。
- LG
-
三星电视(流校验问题)
一般电视等支持投屏的设备在从SetAVTransportURI
拿到视频流的URL之后就直接返回200 OK,并开始加载和播放了,但是三星电视在SetAVTransportURI返回之前会向流地址发送一个Head请求:
HEAD 流Path HTTP/1.1
Host: 流Host
getcontentFeatures.dlna.org: 1
getCaptionInfo.sec: 1
流地址需要回包:
HTTP/1.1 200 OK
Server: httpserver
Content-Length: 204698197
Accept-Ranges: bytes
Connection: Keep-Alive
Keep-Alive: timeout=60, max=100
Content-Type: video/mp4
Cache-Control: max-age=7200
Client-Ip: 59.37.125.xxx
X-ServerIp: 113.106.207.xxx
X-RespTime: 7/Mon/2019:11:26:00 +0800
E-tag: 9ea6259368108444e72082ef7db728b88b8xxxxxx
X-Cache-Lookup: 2-225908140
X-Cache-UUID: 6dc6696f-c936-4b7c-bd63-cd5d5f46f4ae
三星电视才会接受地址并开始播放,否则向控制端返回500错误码。所以联合后台申请了支持Head请求的新地址才之后,三星电视可以正常投屏了,但尚存在小部分三星依然投不了。
设备描述中,有一个presentationURL
是通过web页面控制目标设备的UI,UI目标设备自己实现的,但很多设备并未提供。如果希望能够让电视播放手机中的文件,需要实现DMS的功能(可以借助NanoHTTPD)。DLNA还有很多深层次的东西值得探索,限于时间精力能力的限制,适可而止了。
至此,简易DLNA投屏的实现方法已经梳理完毕,如有疑问或纠错,欢迎留言,谢谢。
最后
以上就是酷酷酸奶为你收集整理的DLNA投屏简介DLNA投屏简介的全部内容,希望文章能够帮你解决DLNA投屏简介DLNA投屏简介所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复