PlayToController.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895
  1. using MediaBrowser.Controller.Dlna;
  2. using MediaBrowser.Controller.Drawing;
  3. using MediaBrowser.Controller.Entities;
  4. using MediaBrowser.Controller.Library;
  5. using MediaBrowser.Controller.Persistence;
  6. using MediaBrowser.Controller.Session;
  7. using MediaBrowser.Dlna.Didl;
  8. using MediaBrowser.Dlna.Ssdp;
  9. using MediaBrowser.Model.Dlna;
  10. using MediaBrowser.Model.Dto;
  11. using MediaBrowser.Model.Entities;
  12. using MediaBrowser.Model.Logging;
  13. using MediaBrowser.Model.Session;
  14. using MediaBrowser.Model.System;
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Globalization;
  18. using System.Linq;
  19. using System.Threading;
  20. using System.Threading.Tasks;
  21. namespace MediaBrowser.Dlna.PlayTo
  22. {
  23. public class PlayToController : ISessionController, IDisposable
  24. {
  25. private Device _device;
  26. private readonly SessionInfo _session;
  27. private readonly ISessionManager _sessionManager;
  28. private readonly IItemRepository _itemRepository;
  29. private readonly ILibraryManager _libraryManager;
  30. private readonly ILogger _logger;
  31. private readonly IDlnaManager _dlnaManager;
  32. private readonly IUserManager _userManager;
  33. private readonly IImageProcessor _imageProcessor;
  34. private readonly IUserDataManager _userDataManager;
  35. private readonly DeviceDiscovery _deviceDiscovery;
  36. private readonly string _serverAddress;
  37. public bool IsSessionActive
  38. {
  39. get
  40. {
  41. return _device != null;
  42. }
  43. }
  44. public bool SupportsMediaControl
  45. {
  46. get { return IsSessionActive; }
  47. }
  48. private Timer _updateTimer;
  49. public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery, IUserDataManager userDataManager)
  50. {
  51. _session = session;
  52. _itemRepository = itemRepository;
  53. _sessionManager = sessionManager;
  54. _libraryManager = libraryManager;
  55. _dlnaManager = dlnaManager;
  56. _userManager = userManager;
  57. _imageProcessor = imageProcessor;
  58. _serverAddress = serverAddress;
  59. _deviceDiscovery = deviceDiscovery;
  60. _userDataManager = userDataManager;
  61. _logger = logger;
  62. }
  63. public void Init(Device device)
  64. {
  65. _device = device;
  66. _device.PlaybackStart += _device_PlaybackStart;
  67. _device.PlaybackProgress += _device_PlaybackProgress;
  68. _device.PlaybackStopped += _device_PlaybackStopped;
  69. _device.MediaChanged += _device_MediaChanged;
  70. _device.Start();
  71. _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
  72. _updateTimer = new Timer(updateTimer_Elapsed, null, 60000, 60000);
  73. }
  74. void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
  75. {
  76. string nts;
  77. e.Headers.TryGetValue("NTS", out nts);
  78. string usn;
  79. if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
  80. string nt;
  81. if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
  82. if ( usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
  83. !_disposed)
  84. {
  85. if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
  86. nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
  87. {
  88. try
  89. {
  90. _sessionManager.ReportSessionEnded(_session.Id);
  91. }
  92. catch
  93. {
  94. // Could throw if the session is already gone
  95. }
  96. }
  97. }
  98. }
  99. private void updateTimer_Elapsed(object state)
  100. {
  101. if (DateTime.UtcNow >= _device.DateLastActivity.AddSeconds(120))
  102. {
  103. try
  104. {
  105. // Session is inactive, mark it for Disposal and don't start the elapsed timer.
  106. _sessionManager.ReportSessionEnded(_session.Id);
  107. }
  108. catch (Exception ex)
  109. {
  110. _logger.ErrorException("Error in ReportSessionEnded", ex);
  111. }
  112. }
  113. }
  114. private string GetServerAddress()
  115. {
  116. return _serverAddress;
  117. }
  118. async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
  119. {
  120. try
  121. {
  122. var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager);
  123. var progress = GetProgressInfo(e.OldMediaInfo, streamInfo);
  124. var positionTicks = progress.PositionTicks;
  125. ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks);
  126. streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager);
  127. progress = GetProgressInfo(e.NewMediaInfo, streamInfo);
  128. await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
  129. }
  130. catch (Exception ex)
  131. {
  132. _logger.ErrorException("Error reporting progress", ex);
  133. }
  134. }
  135. async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e)
  136. {
  137. try
  138. {
  139. var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager);
  140. var progress = GetProgressInfo(e.MediaInfo, streamInfo);
  141. var positionTicks = progress.PositionTicks;
  142. ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks);
  143. var duration = streamInfo.MediaSource == null ?
  144. (_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
  145. streamInfo.MediaSource.RunTimeTicks;
  146. var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0);
  147. if (!playedToCompletion && duration.HasValue && positionTicks.HasValue)
  148. {
  149. double percent = positionTicks.Value;
  150. percent /= duration.Value;
  151. playedToCompletion = Math.Abs(1 - percent) <= .1;
  152. }
  153. if (playedToCompletion)
  154. {
  155. await SetPlaylistIndex(_currentPlaylistIndex + 1).ConfigureAwait(false);
  156. }
  157. else
  158. {
  159. Playlist.Clear();
  160. }
  161. }
  162. catch (Exception ex)
  163. {
  164. _logger.ErrorException("Error reporting playback stopped", ex);
  165. }
  166. }
  167. private async void ReportPlaybackStopped(uBaseObject mediaInfo, StreamParams streamInfo, long? positionTicks)
  168. {
  169. try
  170. {
  171. await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
  172. {
  173. ItemId = mediaInfo.Id,
  174. SessionId = _session.Id,
  175. PositionTicks = positionTicks,
  176. MediaSourceId = streamInfo.MediaSourceId
  177. }).ConfigureAwait(false);
  178. }
  179. catch (Exception ex)
  180. {
  181. _logger.ErrorException("Error reporting progress", ex);
  182. }
  183. }
  184. async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e)
  185. {
  186. try
  187. {
  188. var info = GetProgressInfo(e.MediaInfo);
  189. await _sessionManager.OnPlaybackStart(info).ConfigureAwait(false);
  190. }
  191. catch (Exception ex)
  192. {
  193. _logger.ErrorException("Error reporting progress", ex);
  194. }
  195. }
  196. async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
  197. {
  198. try
  199. {
  200. var info = GetProgressInfo(e.MediaInfo);
  201. await _sessionManager.OnPlaybackProgress(info).ConfigureAwait(false);
  202. }
  203. catch (Exception ex)
  204. {
  205. _logger.ErrorException("Error reporting progress", ex);
  206. }
  207. }
  208. private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo)
  209. {
  210. var info = StreamParams.ParseFromUrl(mediaInfo.Url, _libraryManager);
  211. return GetProgressInfo(mediaInfo, info);
  212. }
  213. private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info)
  214. {
  215. var ticks = _device.Position.Ticks;
  216. if (!info.IsDirectStream)
  217. {
  218. ticks += info.StartPositionTicks;
  219. }
  220. return new PlaybackStartInfo
  221. {
  222. ItemId = mediaInfo.Id,
  223. SessionId = _session.Id,
  224. PositionTicks = ticks,
  225. IsMuted = _device.IsMuted,
  226. IsPaused = _device.IsPaused,
  227. MediaSourceId = info.MediaSourceId,
  228. AudioStreamIndex = info.AudioStreamIndex,
  229. SubtitleStreamIndex = info.SubtitleStreamIndex,
  230. VolumeLevel = _device.Volume,
  231. CanSeek = info.MediaSource == null ? _device.Duration.HasValue : info.MediaSource.RunTimeTicks.HasValue,
  232. PlayMethod = info.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode,
  233. QueueableMediaTypes = new List<string> { mediaInfo.MediaType }
  234. };
  235. }
  236. #region SendCommands
  237. public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
  238. {
  239. _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
  240. var user = String.IsNullOrEmpty(command.ControllingUserId) ? null : _userManager.GetUserById(command.ControllingUserId);
  241. var items = new List<BaseItem>();
  242. foreach (string id in command.ItemIds)
  243. {
  244. AddItemFromId(Guid.Parse(id), items);
  245. }
  246. var playlist = new List<PlaylistItem>();
  247. var isFirst = true;
  248. var serverAddress = GetServerAddress();
  249. foreach (var item in items)
  250. {
  251. if (isFirst && command.StartPositionTicks.HasValue)
  252. {
  253. playlist.Add(CreatePlaylistItem(item, user, command.StartPositionTicks.Value, serverAddress));
  254. isFirst = false;
  255. }
  256. else
  257. {
  258. playlist.Add(CreatePlaylistItem(item, user, 0, serverAddress));
  259. }
  260. }
  261. _logger.Debug("{0} - Playlist created", _session.DeviceName);
  262. if (command.PlayCommand == PlayCommand.PlayLast)
  263. {
  264. Playlist.AddRange(playlist);
  265. }
  266. if (command.PlayCommand == PlayCommand.PlayNext)
  267. {
  268. Playlist.AddRange(playlist);
  269. }
  270. if (!String.IsNullOrWhiteSpace(command.ControllingUserId))
  271. {
  272. await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
  273. _session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false);
  274. }
  275. await PlayItems(playlist).ConfigureAwait(false);
  276. }
  277. public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
  278. {
  279. switch (command.Command)
  280. {
  281. case PlaystateCommand.Stop:
  282. Playlist.Clear();
  283. return _device.SetStop();
  284. case PlaystateCommand.Pause:
  285. return _device.SetPause();
  286. case PlaystateCommand.Unpause:
  287. return _device.SetPlay();
  288. case PlaystateCommand.Seek:
  289. {
  290. return Seek(command.SeekPositionTicks ?? 0);
  291. }
  292. case PlaystateCommand.NextTrack:
  293. return SetPlaylistIndex(_currentPlaylistIndex + 1);
  294. case PlaystateCommand.PreviousTrack:
  295. return SetPlaylistIndex(_currentPlaylistIndex - 1);
  296. }
  297. return Task.FromResult(true);
  298. }
  299. private async Task Seek(long newPosition)
  300. {
  301. var media = _device.CurrentMediaInfo;
  302. if (media != null)
  303. {
  304. var info = StreamParams.ParseFromUrl(media.Url, _libraryManager);
  305. if (info.Item != null && !info.IsDirectStream)
  306. {
  307. var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null;
  308. var newItem = CreatePlaylistItem(info.Item, user, newPosition, GetServerAddress(), info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
  309. await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false);
  310. if (newItem.StreamInfo.IsDirectStream)
  311. {
  312. await _device.Seek(TimeSpan.FromTicks(newPosition)).ConfigureAwait(false);
  313. }
  314. return;
  315. }
  316. await _device.Seek(TimeSpan.FromTicks(newPosition)).ConfigureAwait(false);
  317. }
  318. }
  319. public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
  320. {
  321. return Task.FromResult(true);
  322. }
  323. public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken)
  324. {
  325. return Task.FromResult(true);
  326. }
  327. public Task SendServerRestartNotification(CancellationToken cancellationToken)
  328. {
  329. return Task.FromResult(true);
  330. }
  331. public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  332. {
  333. return Task.FromResult(true);
  334. }
  335. public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  336. {
  337. return Task.FromResult(true);
  338. }
  339. public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  340. {
  341. return Task.FromResult(true);
  342. }
  343. public Task SendServerShutdownNotification(CancellationToken cancellationToken)
  344. {
  345. return Task.FromResult(true);
  346. }
  347. public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
  348. {
  349. return Task.FromResult(true);
  350. }
  351. #endregion
  352. #region Playlist
  353. private int _currentPlaylistIndex;
  354. private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
  355. private List<PlaylistItem> Playlist
  356. {
  357. get
  358. {
  359. return _playlist;
  360. }
  361. }
  362. private void AddItemFromId(Guid id, List<BaseItem> list)
  363. {
  364. var item = _libraryManager.GetItemById(id);
  365. if (item.IsFolder)
  366. {
  367. foreach (var childId in _itemRepository.GetChildren(item.Id))
  368. {
  369. AddItemFromId(childId, list);
  370. }
  371. }
  372. else
  373. {
  374. if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
  375. {
  376. list.Add(item);
  377. }
  378. }
  379. }
  380. private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string serverAddress)
  381. {
  382. return CreatePlaylistItem(item, user, startPostionTicks, serverAddress, null, null, null);
  383. }
  384. private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string serverAddress, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
  385. {
  386. var deviceInfo = _device.Properties;
  387. var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
  388. _dlnaManager.GetDefaultProfile();
  389. var hasMediaSources = item as IHasMediaSources;
  390. var mediaSources = hasMediaSources != null
  391. ? (user == null ? hasMediaSources.GetMediaSources(true) : hasMediaSources.GetMediaSources(true, user)).ToList()
  392. : new List<MediaSourceInfo>();
  393. var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
  394. playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
  395. playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
  396. var itemXml = new DidlBuilder(profile, user, _imageProcessor, serverAddress, _userDataManager).GetItemDidl(item, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
  397. playlistItem.Didl = itemXml;
  398. return playlistItem;
  399. }
  400. private string GetDlnaHeaders(PlaylistItem item)
  401. {
  402. var profile = item.Profile;
  403. var streamInfo = item.StreamInfo;
  404. if (streamInfo.MediaType == DlnaProfileType.Audio)
  405. {
  406. return new ContentFeatureBuilder(profile)
  407. .BuildAudioHeader(streamInfo.Container,
  408. streamInfo.AudioCodec,
  409. streamInfo.TargetAudioBitrate,
  410. streamInfo.TargetAudioSampleRate,
  411. streamInfo.TargetAudioChannels,
  412. streamInfo.IsDirectStream,
  413. streamInfo.RunTimeTicks,
  414. streamInfo.TranscodeSeekInfo);
  415. }
  416. if (streamInfo.MediaType == DlnaProfileType.Video)
  417. {
  418. var list = new ContentFeatureBuilder(profile)
  419. .BuildVideoHeader(streamInfo.Container,
  420. streamInfo.VideoCodec,
  421. streamInfo.AudioCodec,
  422. streamInfo.TargetWidth,
  423. streamInfo.TargetHeight,
  424. streamInfo.TargetVideoBitDepth,
  425. streamInfo.TargetVideoBitrate,
  426. streamInfo.TargetAudioChannels,
  427. streamInfo.TargetAudioBitrate,
  428. streamInfo.TargetTimestamp,
  429. streamInfo.IsDirectStream,
  430. streamInfo.RunTimeTicks,
  431. streamInfo.TargetVideoProfile,
  432. streamInfo.TargetVideoLevel,
  433. streamInfo.TargetFramerate,
  434. streamInfo.TargetPacketLength,
  435. streamInfo.TranscodeSeekInfo,
  436. streamInfo.IsTargetAnamorphic,
  437. streamInfo.TargetRefFrames);
  438. return list.FirstOrDefault();
  439. }
  440. return null;
  441. }
  442. private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
  443. {
  444. if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
  445. {
  446. return new PlaylistItem
  447. {
  448. StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
  449. {
  450. ItemId = item.Id.ToString("N"),
  451. MediaSources = mediaSources,
  452. Profile = profile,
  453. DeviceId = deviceId,
  454. MaxBitrate = profile.MaxStreamingBitrate,
  455. MediaSourceId = mediaSourceId,
  456. AudioStreamIndex = audioStreamIndex,
  457. SubtitleStreamIndex = subtitleStreamIndex
  458. }),
  459. Profile = profile
  460. };
  461. }
  462. if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
  463. {
  464. return new PlaylistItem
  465. {
  466. StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
  467. {
  468. ItemId = item.Id.ToString("N"),
  469. MediaSources = mediaSources,
  470. Profile = profile,
  471. DeviceId = deviceId,
  472. MaxBitrate = profile.MaxStreamingBitrate,
  473. MediaSourceId = mediaSourceId
  474. }),
  475. Profile = profile
  476. };
  477. }
  478. if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
  479. {
  480. return new PlaylistItemFactory().Create((Photo)item, profile);
  481. }
  482. throw new ArgumentException("Unrecognized item type.");
  483. }
  484. /// <summary>
  485. /// Plays the items.
  486. /// </summary>
  487. /// <param name="items">The items.</param>
  488. /// <returns></returns>
  489. private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
  490. {
  491. Playlist.Clear();
  492. Playlist.AddRange(items);
  493. _logger.Debug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count);
  494. await SetPlaylistIndex(0).ConfigureAwait(false);
  495. return true;
  496. }
  497. private async Task SetPlaylistIndex(int index)
  498. {
  499. if (index < 0 || index >= Playlist.Count)
  500. {
  501. Playlist.Clear();
  502. await _device.SetStop();
  503. return;
  504. }
  505. _currentPlaylistIndex = index;
  506. var currentitem = Playlist[index];
  507. await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl);
  508. var streamInfo = currentitem.StreamInfo;
  509. if (streamInfo.StartPositionTicks > 0 && streamInfo.IsDirectStream)
  510. await _device.Seek(TimeSpan.FromTicks(streamInfo.StartPositionTicks));
  511. }
  512. #endregion
  513. private bool _disposed;
  514. public void Dispose()
  515. {
  516. if (!_disposed)
  517. {
  518. _disposed = true;
  519. _device.PlaybackStart -= _device_PlaybackStart;
  520. _device.PlaybackProgress -= _device_PlaybackProgress;
  521. _device.PlaybackStopped -= _device_PlaybackStopped;
  522. _device.MediaChanged -= _device_MediaChanged;
  523. _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
  524. DisposeUpdateTimer();
  525. _device.Dispose();
  526. }
  527. }
  528. private void DisposeUpdateTimer()
  529. {
  530. if (_updateTimer != null)
  531. {
  532. _updateTimer.Dispose();
  533. _updateTimer = null;
  534. }
  535. }
  536. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  537. public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
  538. {
  539. GeneralCommandType commandType;
  540. if (Enum.TryParse(command.Name, true, out commandType))
  541. {
  542. switch (commandType)
  543. {
  544. case GeneralCommandType.VolumeDown:
  545. return _device.VolumeDown();
  546. case GeneralCommandType.VolumeUp:
  547. return _device.VolumeUp();
  548. case GeneralCommandType.Mute:
  549. return _device.Mute();
  550. case GeneralCommandType.Unmute:
  551. return _device.Unmute();
  552. case GeneralCommandType.ToggleMute:
  553. return _device.ToggleMute();
  554. case GeneralCommandType.SetAudioStreamIndex:
  555. {
  556. string arg;
  557. if (command.Arguments.TryGetValue("Index", out arg))
  558. {
  559. int val;
  560. if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out val))
  561. {
  562. return SetAudioStreamIndex(val);
  563. }
  564. throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
  565. }
  566. throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
  567. }
  568. case GeneralCommandType.SetSubtitleStreamIndex:
  569. {
  570. string arg;
  571. if (command.Arguments.TryGetValue("Index", out arg))
  572. {
  573. int val;
  574. if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out val))
  575. {
  576. return SetSubtitleStreamIndex(val);
  577. }
  578. throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
  579. }
  580. throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
  581. }
  582. case GeneralCommandType.SetVolume:
  583. {
  584. string arg;
  585. if (command.Arguments.TryGetValue("Volume", out arg))
  586. {
  587. int volume;
  588. if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out volume))
  589. {
  590. return _device.SetVolume(volume);
  591. }
  592. throw new ArgumentException("Unsupported volume value supplied.");
  593. }
  594. throw new ArgumentException("Volume argument cannot be null");
  595. }
  596. default:
  597. return Task.FromResult(true);
  598. }
  599. }
  600. return Task.FromResult(true);
  601. }
  602. private async Task SetAudioStreamIndex(int? newIndex)
  603. {
  604. var media = _device.CurrentMediaInfo;
  605. if (media != null)
  606. {
  607. var info = StreamParams.ParseFromUrl(media.Url, _libraryManager);
  608. var progress = GetProgressInfo(media, info);
  609. if (info.Item != null)
  610. {
  611. var newPosition = progress.PositionTicks ?? 0;
  612. var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null;
  613. var newItem = CreatePlaylistItem(info.Item, user, newPosition, GetServerAddress(), info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
  614. await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false);
  615. if (newItem.StreamInfo.IsDirectStream)
  616. {
  617. await _device.Seek(TimeSpan.FromTicks(newPosition)).ConfigureAwait(false);
  618. }
  619. }
  620. }
  621. }
  622. private async Task SetSubtitleStreamIndex(int? newIndex)
  623. {
  624. var media = _device.CurrentMediaInfo;
  625. if (media != null)
  626. {
  627. var info = StreamParams.ParseFromUrl(media.Url, _libraryManager);
  628. var progress = GetProgressInfo(media, info);
  629. if (info.Item != null)
  630. {
  631. var newPosition = progress.PositionTicks ?? 0;
  632. var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null;
  633. var newItem = CreatePlaylistItem(info.Item, user, newPosition, GetServerAddress(), info.MediaSourceId, info.AudioStreamIndex, newIndex);
  634. await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false);
  635. if (newItem.StreamInfo.IsDirectStream)
  636. {
  637. await _device.Seek(TimeSpan.FromTicks(newPosition)).ConfigureAwait(false);
  638. }
  639. }
  640. }
  641. }
  642. private class StreamParams
  643. {
  644. public string ItemId { get; set; }
  645. public bool IsDirectStream { get; set; }
  646. public long StartPositionTicks { get; set; }
  647. public int? AudioStreamIndex { get; set; }
  648. public int? SubtitleStreamIndex { get; set; }
  649. public string DeviceProfileId { get; set; }
  650. public string DeviceId { get; set; }
  651. public string MediaSourceId { get; set; }
  652. public BaseItem Item { get; set; }
  653. public MediaSourceInfo MediaSource { get; set; }
  654. private static string GetItemId(string url)
  655. {
  656. var parts = url.Split('/');
  657. for (var i = 0; i < parts.Length; i++)
  658. {
  659. var part = parts[i];
  660. if (string.Equals(part, "audio", StringComparison.OrdinalIgnoreCase) ||
  661. string.Equals(part, "videos", StringComparison.OrdinalIgnoreCase))
  662. {
  663. if (parts.Length > i + 1)
  664. {
  665. return parts[i + 1];
  666. }
  667. }
  668. }
  669. return null;
  670. }
  671. public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager)
  672. {
  673. var request = new StreamParams
  674. {
  675. ItemId = GetItemId(url)
  676. };
  677. if (string.IsNullOrWhiteSpace(request.ItemId))
  678. {
  679. return request;
  680. }
  681. const string srch = "params=";
  682. var index = url.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
  683. if (index == -1) return request;
  684. var vals = url.Substring(index + srch.Length).Split(';');
  685. for (var i = 0; i < vals.Length; i++)
  686. {
  687. var val = vals[i];
  688. if (string.IsNullOrWhiteSpace(val))
  689. {
  690. continue;
  691. }
  692. if (i == 0)
  693. {
  694. request.DeviceProfileId = val;
  695. }
  696. else if (i == 1)
  697. {
  698. request.DeviceId = val;
  699. }
  700. else if (i == 2)
  701. {
  702. request.MediaSourceId = val;
  703. }
  704. else if (i == 3)
  705. {
  706. request.IsDirectStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
  707. }
  708. else if (i == 6)
  709. {
  710. request.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
  711. }
  712. else if (i == 7)
  713. {
  714. request.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
  715. }
  716. else if (i == 14)
  717. {
  718. request.StartPositionTicks = long.Parse(val, CultureInfo.InvariantCulture);
  719. }
  720. }
  721. request.Item = string.IsNullOrWhiteSpace(request.ItemId)
  722. ? null
  723. : libraryManager.GetItemById(new Guid(request.ItemId));
  724. var hasMediaSources = request.Item as IHasMediaSources;
  725. request.MediaSource = hasMediaSources == null ?
  726. null :
  727. hasMediaSources.GetMediaSources(false).FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase));
  728. return request;
  729. }
  730. }
  731. }
  732. }