Device.cs 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller.Configuration;
  3. using MediaBrowser.Dlna.Common;
  4. using MediaBrowser.Dlna.Ssdp;
  5. using MediaBrowser.Model.Logging;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Globalization;
  9. using System.Linq;
  10. using System.Security;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using System.Xml.Linq;
  14. namespace MediaBrowser.Dlna.PlayTo
  15. {
  16. public class Device : IDisposable
  17. {
  18. const string ServiceAvtransportType = "urn:schemas-upnp-org:service:AVTransport:1";
  19. const string ServiceRenderingType = "urn:schemas-upnp-org:service:RenderingControl:1";
  20. #region Fields & Properties
  21. private Timer _timer;
  22. public DeviceInfo Properties { get; set; }
  23. private int _muteVol;
  24. public bool IsMuted { get; set; }
  25. private int _volume;
  26. public int Volume
  27. {
  28. get
  29. {
  30. RefreshVolumeIfNeeded();
  31. return _volume;
  32. }
  33. set
  34. {
  35. _volume = value;
  36. }
  37. }
  38. public TimeSpan? Duration { get; set; }
  39. private TimeSpan _position = TimeSpan.FromSeconds(0);
  40. public TimeSpan Position
  41. {
  42. get
  43. {
  44. return _position;
  45. }
  46. set
  47. {
  48. _position = value;
  49. }
  50. }
  51. public TRANSPORTSTATE TransportState { get; private set; }
  52. public bool IsPlaying
  53. {
  54. get
  55. {
  56. return TransportState == TRANSPORTSTATE.PLAYING;
  57. }
  58. }
  59. public bool IsPaused
  60. {
  61. get
  62. {
  63. return TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK;
  64. }
  65. }
  66. public bool IsStopped
  67. {
  68. get
  69. {
  70. return TransportState == TRANSPORTSTATE.STOPPED;
  71. }
  72. }
  73. #endregion
  74. private readonly IHttpClient _httpClient;
  75. private readonly ILogger _logger;
  76. private readonly IServerConfigurationManager _config;
  77. public DateTime DateLastActivity { get; private set; }
  78. public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
  79. {
  80. Properties = deviceProperties;
  81. _httpClient = httpClient;
  82. _logger = logger;
  83. _config = config;
  84. }
  85. private int GetPlaybackTimerIntervalMs()
  86. {
  87. return 1000;
  88. }
  89. private int GetInactiveTimerIntervalMs()
  90. {
  91. return 20000;
  92. }
  93. public void Start()
  94. {
  95. _timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
  96. _timerActive = false;
  97. }
  98. private DateTime _lastVolumeRefresh;
  99. private void RefreshVolumeIfNeeded()
  100. {
  101. if (!_timerActive)
  102. {
  103. return;
  104. }
  105. if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
  106. {
  107. _lastVolumeRefresh = DateTime.UtcNow;
  108. RefreshVolume();
  109. }
  110. }
  111. private async void RefreshVolume()
  112. {
  113. try
  114. {
  115. await GetVolume().ConfigureAwait(false);
  116. await GetMute().ConfigureAwait(false);
  117. }
  118. catch (Exception ex)
  119. {
  120. _logger.ErrorException("Error updating device volume info for {0}", ex, Properties.Name);
  121. }
  122. }
  123. private readonly object _timerLock = new object();
  124. private bool _timerActive;
  125. private void RestartTimer()
  126. {
  127. if (!_timerActive)
  128. {
  129. lock (_timerLock)
  130. {
  131. if (!_timerActive)
  132. {
  133. _logger.Debug("RestartTimer");
  134. _timer.Change(10, GetPlaybackTimerIntervalMs());
  135. }
  136. _timerActive = true;
  137. }
  138. }
  139. }
  140. /// <summary>
  141. /// Restarts the timer in inactive mode.
  142. /// </summary>
  143. private void RestartTimerInactive()
  144. {
  145. if (_timerActive)
  146. {
  147. lock (_timerLock)
  148. {
  149. if (_timerActive)
  150. {
  151. _logger.Debug("RestartTimerInactive");
  152. var interval = GetInactiveTimerIntervalMs();
  153. if (_timer != null)
  154. {
  155. _timer.Change(interval, interval);
  156. }
  157. }
  158. _timerActive = false;
  159. }
  160. }
  161. }
  162. #region Commanding
  163. public Task VolumeDown()
  164. {
  165. var sendVolume = Math.Max(Volume - 5, 0);
  166. return SetVolume(sendVolume);
  167. }
  168. public Task VolumeUp()
  169. {
  170. var sendVolume = Math.Min(Volume + 5, 100);
  171. return SetVolume(sendVolume);
  172. }
  173. public Task ToggleMute()
  174. {
  175. if (IsMuted)
  176. {
  177. return Unmute();
  178. }
  179. return Mute();
  180. }
  181. public async Task Mute()
  182. {
  183. var success = await SetMute(true).ConfigureAwait(true);
  184. if (!success)
  185. {
  186. await SetVolume(0).ConfigureAwait(false);
  187. }
  188. }
  189. public async Task Unmute()
  190. {
  191. var success = await SetMute(false).ConfigureAwait(true);
  192. if (!success)
  193. {
  194. var sendVolume = _muteVol <= 0 ? 20 : _muteVol;
  195. await SetVolume(sendVolume).ConfigureAwait(false);
  196. }
  197. }
  198. private async Task<bool> SetMute(bool mute)
  199. {
  200. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
  201. if (command == null)
  202. return false;
  203. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceRenderingType);
  204. if (service == null)
  205. {
  206. return false;
  207. }
  208. _logger.Debug("Setting mute");
  209. var value = mute ? 1 : 0;
  210. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
  211. .ConfigureAwait(false);
  212. IsMuted = mute;
  213. return true;
  214. }
  215. /// <summary>
  216. /// Sets volume on a scale of 0-100
  217. /// </summary>
  218. public async Task SetVolume(int value)
  219. {
  220. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
  221. if (command == null)
  222. return;
  223. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceRenderingType);
  224. if (service == null)
  225. {
  226. throw new InvalidOperationException("Unable to find service");
  227. }
  228. // Set it early and assume it will succeed
  229. // Remote control will perform better
  230. Volume = value;
  231. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
  232. .ConfigureAwait(false);
  233. }
  234. public async Task Seek(TimeSpan value)
  235. {
  236. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
  237. if (command == null)
  238. return;
  239. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  240. if (service == null)
  241. {
  242. throw new InvalidOperationException("Unable to find service");
  243. }
  244. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
  245. .ConfigureAwait(false);
  246. }
  247. public async Task SetAvTransport(string url, string header, string metaData)
  248. {
  249. _logger.Debug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
  250. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
  251. if (command == null)
  252. return;
  253. var dictionary = new Dictionary<string, string>
  254. {
  255. {"CurrentURI", url},
  256. {"CurrentURIMetaData", CreateDidlMeta(metaData)}
  257. };
  258. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  259. if (service == null)
  260. {
  261. throw new InvalidOperationException("Unable to find service");
  262. }
  263. var post = AvCommands.BuildPost(command, service.ServiceType, url, dictionary);
  264. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
  265. .ConfigureAwait(false);
  266. await Task.Delay(50).ConfigureAwait(false);
  267. try
  268. {
  269. await SetPlay().ConfigureAwait(false);
  270. }
  271. catch
  272. {
  273. // Some devices will throw an error if you tell it to play when it's already playing
  274. // Others won't
  275. }
  276. RestartTimer();
  277. }
  278. private string CreateDidlMeta(string value)
  279. {
  280. if (string.IsNullOrEmpty(value))
  281. return String.Empty;
  282. return SecurityElement.Escape(value);
  283. }
  284. public async Task SetPlay()
  285. {
  286. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
  287. if (command == null)
  288. return;
  289. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  290. if (service == null)
  291. {
  292. throw new InvalidOperationException("Unable to find service");
  293. }
  294. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
  295. .ConfigureAwait(false);
  296. }
  297. public async Task SetStop()
  298. {
  299. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
  300. if (command == null)
  301. return;
  302. var service = Properties.Services.First(s => s.ServiceType == ServiceAvtransportType);
  303. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
  304. .ConfigureAwait(false);
  305. }
  306. public async Task SetPause()
  307. {
  308. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
  309. if (command == null)
  310. return;
  311. var service = Properties.Services.First(s => s.ServiceType == ServiceAvtransportType);
  312. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
  313. .ConfigureAwait(false);
  314. TransportState = TRANSPORTSTATE.PAUSED;
  315. }
  316. #endregion
  317. #region Get data
  318. private int _successiveStopCount;
  319. private async void TimerCallback(object sender)
  320. {
  321. if (_disposed)
  322. return;
  323. const int maxSuccessiveStopReturns = 5;
  324. try
  325. {
  326. var transportState = await GetTransportInfo().ConfigureAwait(false);
  327. DateLastActivity = DateTime.UtcNow;
  328. if (transportState.HasValue)
  329. {
  330. // If we're not playing anything no need to get additional data
  331. if (transportState.Value == TRANSPORTSTATE.STOPPED)
  332. {
  333. UpdateMediaInfo(null, transportState.Value);
  334. }
  335. else
  336. {
  337. var tuple = await GetPositionInfo().ConfigureAwait(false);
  338. var currentObject = tuple.Item2;
  339. if (tuple.Item1 && currentObject == null)
  340. {
  341. currentObject = await GetMediaInfo().ConfigureAwait(false);
  342. }
  343. if (currentObject != null)
  344. {
  345. UpdateMediaInfo(currentObject, transportState.Value);
  346. }
  347. }
  348. if (_disposed)
  349. return;
  350. // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
  351. if (transportState.Value == TRANSPORTSTATE.STOPPED)
  352. {
  353. _successiveStopCount++;
  354. if (_successiveStopCount >= maxSuccessiveStopReturns)
  355. {
  356. RestartTimerInactive();
  357. }
  358. }
  359. else
  360. {
  361. _successiveStopCount = 0;
  362. RestartTimer();
  363. }
  364. }
  365. }
  366. catch (Exception ex)
  367. {
  368. _logger.ErrorException("Error updating device info for {0}", ex, Properties.Name);
  369. _successiveStopCount++;
  370. if (_successiveStopCount >= maxSuccessiveStopReturns)
  371. {
  372. RestartTimerInactive();
  373. }
  374. }
  375. }
  376. private async Task GetVolume()
  377. {
  378. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
  379. if (command == null)
  380. return;
  381. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceRenderingType);
  382. if (service == null)
  383. {
  384. return;
  385. }
  386. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), true)
  387. .ConfigureAwait(false);
  388. if (result == null || result.Document == null)
  389. return;
  390. var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
  391. var volumeValue = volume == null ? null : volume.Value;
  392. if (string.IsNullOrWhiteSpace(volumeValue))
  393. return;
  394. Volume = int.Parse(volumeValue, UsCulture);
  395. if (Volume > 0)
  396. {
  397. _muteVol = Volume;
  398. }
  399. }
  400. private async Task GetMute()
  401. {
  402. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
  403. if (command == null)
  404. return;
  405. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceRenderingType);
  406. if (service == null)
  407. {
  408. return;
  409. }
  410. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), true)
  411. .ConfigureAwait(false);
  412. if (result == null || result.Document == null)
  413. return;
  414. var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse").Select(i => i.Element("CurrentMute")).FirstOrDefault(i => i != null);
  415. var value = valueNode == null ? null : valueNode.Value;
  416. IsMuted = string.Equals(value, "1", StringComparison.OrdinalIgnoreCase);
  417. }
  418. private async Task<TRANSPORTSTATE?> GetTransportInfo()
  419. {
  420. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
  421. if (command == null)
  422. return null;
  423. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  424. if (service == null)
  425. return null;
  426. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType), false)
  427. .ConfigureAwait(false);
  428. if (result == null || result.Document == null)
  429. return null;
  430. var transportState =
  431. result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
  432. var transportStateValue = transportState == null ? null : transportState.Value;
  433. if (transportStateValue != null)
  434. {
  435. TRANSPORTSTATE state;
  436. if (Enum.TryParse(transportStateValue, true, out state))
  437. {
  438. return state;
  439. }
  440. }
  441. return null;
  442. }
  443. private async Task<uBaseObject> GetMediaInfo()
  444. {
  445. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
  446. if (command == null)
  447. return null;
  448. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  449. if (service == null)
  450. {
  451. throw new InvalidOperationException("Unable to find service");
  452. }
  453. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), false)
  454. .ConfigureAwait(false);
  455. if (result == null || result.Document == null)
  456. return null;
  457. var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
  458. if (track == null)
  459. {
  460. return null;
  461. }
  462. var e = track.Element(uPnpNamespaces.items) ?? track;
  463. return UpnpContainer.Create(e);
  464. }
  465. private async Task<Tuple<bool, uBaseObject>> GetPositionInfo()
  466. {
  467. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
  468. if (command == null)
  469. return new Tuple<bool, uBaseObject>(false, null);
  470. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  471. if (service == null)
  472. {
  473. throw new InvalidOperationException("Unable to find service");
  474. }
  475. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), false)
  476. .ConfigureAwait(false);
  477. if (result == null || result.Document == null)
  478. return new Tuple<bool, uBaseObject>(false, null);
  479. var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
  480. var trackUri = trackUriElem == null ? null : trackUriElem.Value;
  481. var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
  482. var duration = durationElem == null ? null : durationElem.Value;
  483. if (!string.IsNullOrWhiteSpace(duration) &&
  484. !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
  485. {
  486. Duration = TimeSpan.Parse(duration, UsCulture);
  487. }
  488. else
  489. {
  490. Duration = null;
  491. }
  492. var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
  493. var position = positionElem == null ? null : positionElem.Value;
  494. if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
  495. {
  496. Position = TimeSpan.Parse(position, UsCulture);
  497. }
  498. var track = result.Document.Descendants("TrackMetaData").FirstOrDefault();
  499. if (track == null)
  500. {
  501. //If track is null, some vendors do this, use GetMediaInfo instead
  502. return new Tuple<bool, uBaseObject>(true, null);
  503. }
  504. var trackString = (string)track;
  505. if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
  506. {
  507. return new Tuple<bool, uBaseObject>(false, null);
  508. }
  509. XElement uPnpResponse;
  510. // Handle different variations sent back by devices
  511. try
  512. {
  513. uPnpResponse = XElement.Parse(trackString);
  514. }
  515. catch (Exception)
  516. {
  517. // first try to add a root node with a dlna namesapce
  518. try
  519. {
  520. uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>");
  521. uPnpResponse = uPnpResponse.Descendants().First();
  522. }
  523. catch (Exception ex)
  524. {
  525. _logger.ErrorException("Unable to parse xml {0}", ex, trackString);
  526. return new Tuple<bool, uBaseObject>(true, null);
  527. }
  528. }
  529. var e = uPnpResponse.Element(uPnpNamespaces.items);
  530. var uTrack = CreateUBaseObject(e, trackUri);
  531. return new Tuple<bool, uBaseObject>(true, uTrack);
  532. }
  533. private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
  534. {
  535. if (container == null)
  536. {
  537. throw new ArgumentNullException("container");
  538. }
  539. var url = container.GetValue(uPnpNamespaces.Res);
  540. if (string.IsNullOrWhiteSpace(url))
  541. {
  542. url = trackUri;
  543. }
  544. return new uBaseObject
  545. {
  546. Id = container.GetAttributeValue(uPnpNamespaces.Id),
  547. ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
  548. Title = container.GetValue(uPnpNamespaces.title),
  549. IconUrl = container.GetValue(uPnpNamespaces.Artwork),
  550. SecondText = "",
  551. Url = url,
  552. ProtocolInfo = GetProtocolInfo(container),
  553. MetaData = container.ToString()
  554. };
  555. }
  556. private static string[] GetProtocolInfo(XElement container)
  557. {
  558. if (container == null)
  559. {
  560. throw new ArgumentNullException("container");
  561. }
  562. var resElement = container.Element(uPnpNamespaces.Res);
  563. if (resElement != null)
  564. {
  565. var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo);
  566. if (info != null && !string.IsNullOrWhiteSpace(info.Value))
  567. {
  568. return info.Value.Split(':');
  569. }
  570. }
  571. return new string[4];
  572. }
  573. #endregion
  574. #region From XML
  575. private async Task GetAVProtocolAsync()
  576. {
  577. var avService = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  578. if (avService == null)
  579. return;
  580. string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
  581. var httpClient = new SsdpHttpClient(_httpClient, _config);
  582. var document = await httpClient.GetDataAsync(url);
  583. AvCommands = TransportCommands.Create(document);
  584. }
  585. private async Task GetRenderingProtocolAsync()
  586. {
  587. var avService = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceRenderingType);
  588. if (avService == null)
  589. return;
  590. string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
  591. var httpClient = new SsdpHttpClient(_httpClient, _config);
  592. var document = await httpClient.GetDataAsync(url);
  593. RendererCommands = TransportCommands.Create(document);
  594. }
  595. private string NormalizeUrl(string baseUrl, string url)
  596. {
  597. // If it's already a complete url, don't stick anything onto the front of it
  598. if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
  599. {
  600. return url;
  601. }
  602. if (!url.Contains("/"))
  603. url = "/dmr/" + url;
  604. if (!url.StartsWith("/"))
  605. url = "/" + url;
  606. return baseUrl + url;
  607. }
  608. private TransportCommands AvCommands
  609. {
  610. get;
  611. set;
  612. }
  613. internal TransportCommands RendererCommands
  614. {
  615. get;
  616. set;
  617. }
  618. public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger)
  619. {
  620. var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
  621. var document = await ssdpHttpClient.GetDataAsync(url.ToString()).ConfigureAwait(false);
  622. var deviceProperties = new DeviceInfo();
  623. var friendlyNames = new List<string>();
  624. var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
  625. if (name != null && !string.IsNullOrWhiteSpace(name.Value))
  626. friendlyNames.Add(name.Value);
  627. var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
  628. if (room != null && !string.IsNullOrWhiteSpace(room.Value))
  629. friendlyNames.Add(room.Value);
  630. deviceProperties.Name = string.Join(" ", friendlyNames.ToArray());
  631. var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
  632. if (model != null)
  633. deviceProperties.ModelName = model.Value;
  634. var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
  635. if (modelNumber != null)
  636. deviceProperties.ModelNumber = modelNumber.Value;
  637. var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
  638. if (uuid != null)
  639. deviceProperties.UUID = uuid.Value;
  640. var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
  641. if (manufacturer != null)
  642. deviceProperties.Manufacturer = manufacturer.Value;
  643. var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
  644. if (manufacturerUrl != null)
  645. deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
  646. var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
  647. if (presentationUrl != null)
  648. deviceProperties.PresentationUrl = presentationUrl.Value;
  649. var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
  650. if (modelUrl != null)
  651. deviceProperties.ModelUrl = modelUrl.Value;
  652. var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
  653. if (serialNumber != null)
  654. deviceProperties.SerialNumber = serialNumber.Value;
  655. var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
  656. if (modelDescription != null)
  657. deviceProperties.ModelDescription = modelDescription.Value;
  658. deviceProperties.BaseUrl = String.Format("http://{0}:{1}", url.Host, url.Port);
  659. var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
  660. if (icon != null)
  661. {
  662. deviceProperties.Icon = CreateIcon(icon);
  663. }
  664. var isRenderer = false;
  665. foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
  666. {
  667. if (services == null)
  668. continue;
  669. var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
  670. if (servicesList == null)
  671. continue;
  672. foreach (var element in servicesList)
  673. {
  674. var service = Create(element);
  675. if (service != null)
  676. {
  677. deviceProperties.Services.Add(service);
  678. if (service.ServiceType == ServiceAvtransportType)
  679. {
  680. isRenderer = true;
  681. }
  682. }
  683. }
  684. }
  685. var device = new Device(deviceProperties, httpClient, logger, config);
  686. if (isRenderer)
  687. {
  688. await device.GetRenderingProtocolAsync().ConfigureAwait(false);
  689. await device.GetAVProtocolAsync().ConfigureAwait(false);
  690. }
  691. return device;
  692. }
  693. #endregion
  694. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  695. private static DeviceIcon CreateIcon(XElement element)
  696. {
  697. if (element == null)
  698. {
  699. throw new ArgumentNullException("element");
  700. }
  701. var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype"));
  702. var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width"));
  703. var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height"));
  704. var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth"));
  705. var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url"));
  706. var widthValue = int.Parse(width, NumberStyles.Any, UsCulture);
  707. var heightValue = int.Parse(height, NumberStyles.Any, UsCulture);
  708. return new DeviceIcon
  709. {
  710. Depth = depth,
  711. Height = heightValue,
  712. MimeType = mimeType,
  713. Url = url,
  714. Width = widthValue
  715. };
  716. }
  717. private static DeviceService Create(XElement element)
  718. {
  719. var type = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceType"));
  720. var id = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceId"));
  721. var scpdUrl = element.GetDescendantValue(uPnpNamespaces.ud.GetName("SCPDURL"));
  722. var controlURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("controlURL"));
  723. var eventSubURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("eventSubURL"));
  724. return new DeviceService
  725. {
  726. ControlUrl = controlURL,
  727. EventSubUrl = eventSubURL,
  728. ScpdUrl = scpdUrl,
  729. ServiceId = id,
  730. ServiceType = type
  731. };
  732. }
  733. public event EventHandler<PlaybackStartEventArgs> PlaybackStart;
  734. public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
  735. public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped;
  736. public event EventHandler<MediaChangedEventArgs> MediaChanged;
  737. public uBaseObject CurrentMediaInfo { get; private set; }
  738. private void UpdateMediaInfo(uBaseObject mediaInfo, TRANSPORTSTATE state)
  739. {
  740. TransportState = state;
  741. var previousMediaInfo = CurrentMediaInfo;
  742. CurrentMediaInfo = mediaInfo;
  743. if (previousMediaInfo == null && mediaInfo != null)
  744. {
  745. if (state != TRANSPORTSTATE.STOPPED)
  746. {
  747. OnPlaybackStart(mediaInfo);
  748. }
  749. }
  750. else if (mediaInfo != null && previousMediaInfo != null && !mediaInfo.Equals(previousMediaInfo))
  751. {
  752. OnMediaChanged(previousMediaInfo, mediaInfo);
  753. }
  754. else if (mediaInfo == null && previousMediaInfo != null)
  755. {
  756. OnPlaybackStop(previousMediaInfo);
  757. }
  758. else if (mediaInfo != null && mediaInfo.Equals(previousMediaInfo))
  759. {
  760. OnPlaybackProgress(mediaInfo);
  761. }
  762. }
  763. private void OnPlaybackStart(uBaseObject mediaInfo)
  764. {
  765. if (PlaybackStart != null)
  766. {
  767. PlaybackStart.Invoke(this, new PlaybackStartEventArgs
  768. {
  769. MediaInfo = mediaInfo
  770. });
  771. }
  772. }
  773. private void OnPlaybackProgress(uBaseObject mediaInfo)
  774. {
  775. if (PlaybackProgress != null)
  776. {
  777. PlaybackProgress.Invoke(this, new PlaybackProgressEventArgs
  778. {
  779. MediaInfo = mediaInfo
  780. });
  781. }
  782. }
  783. private void OnPlaybackStop(uBaseObject mediaInfo)
  784. {
  785. if (PlaybackStopped != null)
  786. {
  787. PlaybackStopped.Invoke(this, new PlaybackStoppedEventArgs
  788. {
  789. MediaInfo = mediaInfo
  790. });
  791. }
  792. }
  793. private void OnMediaChanged(uBaseObject old, uBaseObject newMedia)
  794. {
  795. if (MediaChanged != null)
  796. {
  797. MediaChanged.Invoke(this, new MediaChangedEventArgs
  798. {
  799. OldMediaInfo = old,
  800. NewMediaInfo = newMedia
  801. });
  802. }
  803. }
  804. #region IDisposable
  805. bool _disposed;
  806. public void Dispose()
  807. {
  808. if (!_disposed)
  809. {
  810. _disposed = true;
  811. DisposeTimer();
  812. }
  813. }
  814. private void DisposeTimer()
  815. {
  816. if (_timer != null)
  817. {
  818. _timer.Dispose();
  819. _timer = null;
  820. }
  821. }
  822. #endregion
  823. public override string ToString()
  824. {
  825. return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
  826. }
  827. }
  828. }