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 | 暫停 | |
Stop | AVTransport | InstanceID | 停止 | |
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 | 擷取Action清單[3] | |
GetMediaInfo | AVTransport | InstanceID | 擷取媒體資訊 | |
GetPositionInfo | AVTransport | instanceID | 擷取進度 | |
GetTransportInfo | AVTransport | instanceID | 擷取傳輸狀态[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
等Action,GetMediaInfo
-
向目标裝置注冊訂閱。
兩種方式各有優缺點,因為目标裝置實作存在差異,每個裝置的在狀态的處理上不完全一緻。前面已經提到過,GetTransportInfo傳回結果不太靠譜;第二種方式,結果較為準确,但是對控制端搶占TV導緻更換URL等情況大多不會告知訂閱者;是以結合兩種方式,可以達到較好的效果。
輪詢
沒啥好說的,不端發送擷取想要狀态資訊的請求就行了。
事件訂閱
-
訂閱
裝置描述的服務清單中,每個服務都有一個
,我們可以在控制端運作一個ServerSocket綁定一個端口(記為端口A),通過accept監聽tcp請求,并将本機ip和端口A和自定義回調路徑拼接為url通過eventSubURL
Action發送給目标裝置,即可完成訂閱,在必要的時候,通過SUBSCRIBE
Action(與訂閱使用同一個Action,但參數不同)續訂,通過SUBSCRIBE
UNSUBSCRIBE
Action取消訂閱。
參數格式如下表:
| Action | 參數 | 常用值 | 說明 |
| ----------- | :-------------------: | :---------------------------------------: | :------: |
| SUBSCRIBE | Nt、Timeout、Callback | upnp:event、Second-時間、<自定義回調位址> | 訂閱[1] |
| SUBSCRIBE | SID、Timeout | 訂閱ID、Second-時間 | 續訂[2] |
| UNSUBSCRIBE | SID | 訂閱ID | 取消訂閱 |
[1]
的值URL用Callback
<>
包裹。
[2]
為SUBSCRIBE ID,是訂閱Action傳回的值。SID
-
回調處理
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();
}
結束的時候調用
-
Sony電視投屏失敗(業務層)
測試相容性的時候發現,Sony電視在開機之後我們的應用無法投屏,但是在使用騰訊視訊等其它應用投屏一次之後,我們就可以正常投屏了。我們在業務層使用的投屏政策是投屏之前先
一下,然後Stop
,再SetAVTransportURI
,常用的三步走政策,每個目标裝置實作都存在差異,可能有一些裝置不Stop就無法播放新的内容,也有的電視SetAVTransportURI就開始播放了,而有的需要Play才會開始播放,三步走可以保證目标裝置可以正常投屏。調試發現,Sony電視在開機之後,未投屏之前直接Stop會傳回失敗(500錯誤碼),一旦開始投屏之後,無論是否正在投屏,Stop都會正常傳回。當時業務邏輯寫的Stop成功才會繼續後續兩步,Stop失敗就導緻了整個流程中斷,後面改為Stop失敗也繼續往下走,問題解決。Play
- 視訊流相容問題
-
LG
直播使用的是沒m3u8的流,LG電視無法播放我們的m3u8流,後做了失敗重試邏輯,可以自動/手動切換flv流,可以成功投屏(但flv也有一定的失敗率)。
-
創維企鵝盒子1v
這個盒子使用flv播放一切正常,但是使用m3u8會crash(盒子的DLNA服務crash并重新開機服務),是以在業務層做了裝置更新管理。當檢測到有相同uuid的裝置出現在區域網路,但ip或端口發生變化時,認為該裝置發生了變化(重新開機或重連網絡等),裝置管理器(自己實作的負責維護裝置狀态的類)會移除原來的裝置并添加新裝置,業務層會收到對應事件,并判斷如果該裝置是已選中的正在投屏的裝置,會自動重試(切流重試),此類問題得到解決。另外,在大概2018年11月的一次更新(盒子投屏版本4.01.41)已經修複了該問題。
-
-
三星電視(流校驗問題)
一般電視等支援投屏的裝置在從
拿到視訊流的URL之後就直接傳回200 OK,并開始加載和播放了,但是三星電視在SetAVTransportURI傳回之前會向流位址發送一個Head請求:SetAVTransportURI
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投屏的實作方法已經梳理完畢,如有疑問或糾錯,歡迎留言,謝謝。