DlnaController.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller;
  3. using MediaBrowser.Controller.Dlna;
  4. using MediaBrowser.Controller.Dto;
  5. using MediaBrowser.Controller.Entities;
  6. using MediaBrowser.Controller.Entities.Audio;
  7. using MediaBrowser.Controller.Library;
  8. using MediaBrowser.Controller.Persistence;
  9. using MediaBrowser.Controller.Session;
  10. using MediaBrowser.Model.Dlna;
  11. using MediaBrowser.Model.Dto;
  12. using MediaBrowser.Model.Entities;
  13. using MediaBrowser.Model.Logging;
  14. using MediaBrowser.Model.Session;
  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 BaseItem _currentItem;
  27. private readonly SessionInfo _session;
  28. private readonly ISessionManager _sessionManager;
  29. private readonly IItemRepository _itemRepository;
  30. private readonly ILibraryManager _libraryManager;
  31. private readonly INetworkManager _networkManager;
  32. private readonly ILogger _logger;
  33. private readonly IDlnaManager _dlnaManager;
  34. private readonly IUserManager _userManager;
  35. private readonly IServerApplicationHost _appHost;
  36. private readonly IDtoService _dtoService;
  37. private bool _playbackStarted;
  38. private const int UpdateTimerIntervalMs = 1000;
  39. public bool SupportsMediaRemoteControl
  40. {
  41. get { return true; }
  42. }
  43. public bool IsSessionActive
  44. {
  45. get
  46. {
  47. if (_device == null || _device.UpdateTime == default(DateTime))
  48. return false;
  49. return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
  50. }
  51. }
  52. public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService)
  53. {
  54. _session = session;
  55. _itemRepository = itemRepository;
  56. _sessionManager = sessionManager;
  57. _libraryManager = libraryManager;
  58. _networkManager = networkManager;
  59. _dlnaManager = dlnaManager;
  60. _userManager = userManager;
  61. _appHost = appHost;
  62. _dtoService = dtoService;
  63. _logger = logger;
  64. }
  65. public void Init(Device device)
  66. {
  67. _device = device;
  68. _device.PlaybackChanged += Device_PlaybackChanged;
  69. _device.CurrentIdChanged += Device_CurrentIdChanged;
  70. _device.Start();
  71. _updateTimer = new Timer(updateTimer_Elapsed, null, UpdateTimerIntervalMs, UpdateTimerIntervalMs);
  72. }
  73. #region Device EventHandlers & Update Timer
  74. Timer _updateTimer;
  75. async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
  76. {
  77. if (_currentItem == null)
  78. return;
  79. if (e.State == TRANSPORTSTATE.STOPPED)
  80. await ReportProgress().ConfigureAwait(false);
  81. else if (e.State == TRANSPORTSTATE.STOPPED && _playbackStarted)
  82. {
  83. _playbackStarted = false;
  84. await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
  85. {
  86. ItemId = _currentItem.Id.ToString("N"),
  87. SessionId = _session.Id,
  88. PositionTicks = _device.Position.Ticks
  89. }).ConfigureAwait(false);
  90. await SetNext().ConfigureAwait(false);
  91. }
  92. }
  93. async void Device_CurrentIdChanged(object sender, CurrentIdEventArgs e)
  94. {
  95. if (!string.IsNullOrWhiteSpace(e.Id))
  96. {
  97. Guid guid;
  98. if (Guid.TryParse(e.Id, out guid))
  99. {
  100. if (_currentItem != null && _currentItem.Id == guid)
  101. {
  102. return;
  103. }
  104. var item = _libraryManager.GetItemById(guid);
  105. if (item != null)
  106. {
  107. _logger.Debug("{0} - CurrentId {1}", _session.DeviceName, item.Id);
  108. _currentItem = item;
  109. _playbackStarted = false;
  110. await ReportProgress().ConfigureAwait(false);
  111. }
  112. }
  113. }
  114. }
  115. /// <summary>
  116. /// Handles the Elapsed event of the updateTimer control.
  117. /// </summary>
  118. /// <param name="state">The state.</param>
  119. private async void updateTimer_Elapsed(object state)
  120. {
  121. if (_disposed)
  122. return;
  123. if (IsSessionActive)
  124. {
  125. await ReportProgress().ConfigureAwait(false);
  126. }
  127. else
  128. {
  129. _updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
  130. try
  131. {
  132. // Session is inactive, mark it for Disposal and don't start the elapsed timer.
  133. await _sessionManager.ReportSessionEnded(_session.Id);
  134. }
  135. catch (Exception ex)
  136. {
  137. _logger.ErrorException("Error in ReportSessionEnded", ex);
  138. }
  139. }
  140. }
  141. /// <summary>
  142. /// Reports the playback progress.
  143. /// </summary>
  144. /// <returns></returns>
  145. private async Task ReportProgress()
  146. {
  147. if (_currentItem == null || _device.IsStopped)
  148. return;
  149. var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
  150. if (playlistItem != null)
  151. {
  152. var streamInfo = playlistItem.StreamInfo;
  153. if (!_playbackStarted)
  154. {
  155. await _sessionManager.OnPlaybackStart(new PlaybackStartInfo
  156. {
  157. ItemId = _currentItem.Id.ToString("N"),
  158. SessionId = _session.Id,
  159. CanSeek = streamInfo.RunTimeTicks.HasValue,
  160. QueueableMediaTypes = new List<string> { _currentItem.MediaType },
  161. MediaSourceId = streamInfo.MediaSourceId,
  162. AudioStreamIndex = streamInfo.AudioStreamIndex,
  163. SubtitleStreamIndex = streamInfo.SubtitleStreamIndex,
  164. IsMuted = _device.IsMuted,
  165. IsPaused = _device.IsPaused,
  166. VolumeLevel = _device.Volume,
  167. PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode
  168. }).ConfigureAwait(false);
  169. _playbackStarted = true;
  170. }
  171. if ((_device.IsPlaying || _device.IsPaused))
  172. {
  173. var ticks = _device.Position.Ticks;
  174. if (!streamInfo.IsDirectStream)
  175. {
  176. ticks += streamInfo.StartPositionTicks;
  177. }
  178. await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
  179. {
  180. ItemId = _currentItem.Id.ToString("N"),
  181. SessionId = _session.Id,
  182. PositionTicks = ticks,
  183. IsMuted = _device.IsMuted,
  184. IsPaused = _device.IsPaused,
  185. MediaSourceId = streamInfo.MediaSourceId,
  186. AudioStreamIndex = streamInfo.AudioStreamIndex,
  187. SubtitleStreamIndex = streamInfo.SubtitleStreamIndex,
  188. VolumeLevel = _device.Volume,
  189. CanSeek = streamInfo.RunTimeTicks.HasValue,
  190. PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode
  191. }).ConfigureAwait(false);
  192. }
  193. }
  194. }
  195. #endregion
  196. #region SendCommands
  197. public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
  198. {
  199. _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
  200. var items = new List<BaseItem>();
  201. foreach (string id in command.ItemIds)
  202. {
  203. AddItemFromId(Guid.Parse(id), items);
  204. }
  205. var playlist = new List<PlaylistItem>();
  206. var isFirst = true;
  207. var serverAddress = GetServerAddress();
  208. foreach (var item in items)
  209. {
  210. if (isFirst && command.StartPositionTicks.HasValue)
  211. {
  212. playlist.Add(CreatePlaylistItem(item, command.StartPositionTicks.Value, serverAddress));
  213. isFirst = false;
  214. }
  215. else
  216. {
  217. playlist.Add(CreatePlaylistItem(item, 0, serverAddress));
  218. }
  219. }
  220. _logger.Debug("{0} - Playlist created", _session.DeviceName);
  221. if (command.PlayCommand == PlayCommand.PlayLast)
  222. {
  223. AddItemsToPlaylist(playlist);
  224. }
  225. if (command.PlayCommand == PlayCommand.PlayNext)
  226. {
  227. AddItemsToPlaylist(playlist);
  228. }
  229. _logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
  230. if (!string.IsNullOrWhiteSpace(command.ControllingUserId))
  231. {
  232. var userId = new Guid(command.ControllingUserId);
  233. var user = _userManager.GetUserById(userId);
  234. await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
  235. _session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false);
  236. }
  237. await PlayItems(playlist).ConfigureAwait(false);
  238. }
  239. public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
  240. {
  241. switch (command.Command)
  242. {
  243. case PlaystateCommand.Stop:
  244. Playlist.Clear();
  245. return _device.SetStop();
  246. case PlaystateCommand.Pause:
  247. return _device.SetPause();
  248. case PlaystateCommand.Unpause:
  249. return _device.SetPlay();
  250. case PlaystateCommand.Seek:
  251. //var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
  252. //if (playlistItem != null && playlistItem.Transcode && _currentItem != null)
  253. //{
  254. // var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
  255. // playlistItem.StartPositionTicks = newItem.StartPositionTicks;
  256. // playlistItem.StreamUrl = newItem.StreamUrl;
  257. // playlistItem.Didl = newItem.Didl;
  258. // return _device.SetAvTransport(playlistItem.StreamUrl, GetDlnaHeaders(playlistItem), playlistItem.Didl);
  259. //}
  260. return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
  261. case PlaystateCommand.NextTrack:
  262. _currentItem = null;
  263. return SetNext();
  264. case PlaystateCommand.PreviousTrack:
  265. _currentItem = null;
  266. return SetPrevious();
  267. }
  268. return Task.FromResult(true);
  269. }
  270. public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
  271. {
  272. return Task.FromResult(true);
  273. }
  274. public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
  275. {
  276. return Task.FromResult(true);
  277. }
  278. public Task SendServerRestartNotification(CancellationToken cancellationToken)
  279. {
  280. return Task.FromResult(true);
  281. }
  282. public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  283. {
  284. return Task.FromResult(true);
  285. }
  286. public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  287. {
  288. return Task.FromResult(true);
  289. }
  290. public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  291. {
  292. return Task.FromResult(true);
  293. }
  294. public Task SendServerShutdownNotification(CancellationToken cancellationToken)
  295. {
  296. return Task.FromResult(true);
  297. }
  298. public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
  299. {
  300. return Task.FromResult(true);
  301. }
  302. public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
  303. {
  304. return Task.FromResult(true);
  305. }
  306. #endregion
  307. #region Playlist
  308. private List<PlaylistItem> _playlist = new List<PlaylistItem>();
  309. private List<PlaylistItem> Playlist
  310. {
  311. get
  312. {
  313. return _playlist;
  314. }
  315. set
  316. {
  317. _playlist = value;
  318. }
  319. }
  320. private void AddItemFromId(Guid id, List<BaseItem> list)
  321. {
  322. var item = _libraryManager.GetItemById(id);
  323. if (item.IsFolder)
  324. {
  325. foreach (var childId in _itemRepository.GetChildren(item.Id))
  326. {
  327. AddItemFromId(childId, list);
  328. }
  329. }
  330. else
  331. {
  332. if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
  333. {
  334. list.Add(item);
  335. }
  336. }
  337. }
  338. private string GetServerAddress()
  339. {
  340. return string.Format("{0}://{1}:{2}/mediabrowser",
  341. "http",
  342. _networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost",
  343. _appHost.HttpServerPort
  344. );
  345. }
  346. private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
  347. {
  348. var deviceInfo = _device.Properties;
  349. var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
  350. _dlnaManager.GetDefaultProfile();
  351. var mediaSources = item is Audio || item is Video
  352. ? _dtoService.GetMediaSources(item)
  353. : new List<MediaSourceInfo>();
  354. var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId);
  355. playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
  356. playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
  357. var mediaStreams = mediaSources
  358. .Where(i => string.Equals(i.Id, playlistItem.StreamInfo.MediaSourceId))
  359. .SelectMany(i => i.MediaStreams)
  360. .ToList();
  361. playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, mediaStreams, profile.EnableAlbumArtInDidl);
  362. return playlistItem;
  363. }
  364. private string GetDlnaHeaders(PlaylistItem item)
  365. {
  366. var streamInfo = item.StreamInfo;
  367. var orgOp = !streamInfo.IsDirectStream ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
  368. var orgCi = !streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
  369. const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
  370. string contentFeatures;
  371. var container = streamInfo.Container.TrimStart('.');
  372. if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
  373. {
  374. contentFeatures = "DLNA.ORG_PN=MP3";
  375. }
  376. else if (string.Equals(container, "wma", StringComparison.OrdinalIgnoreCase))
  377. {
  378. contentFeatures = "DLNA.ORG_PN=WMABASE";
  379. }
  380. else if (string.Equals(container, "wmw", StringComparison.OrdinalIgnoreCase))
  381. {
  382. contentFeatures = "DLNA.ORG_PN=WMVMED_BASE";
  383. }
  384. else if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
  385. {
  386. contentFeatures = "DLNA.ORG_PN=WMVMED_BASE";
  387. }
  388. else if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase))
  389. {
  390. contentFeatures = "DLNA.ORG_PN=AVI";
  391. }
  392. else if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
  393. {
  394. contentFeatures = "DLNA.ORG_PN=MATROSKA";
  395. }
  396. else if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
  397. {
  398. contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC";
  399. }
  400. else if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
  401. {
  402. contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
  403. }
  404. else if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
  405. {
  406. contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
  407. }
  408. else if (streamInfo.MediaType == DlnaProfileType.Video)
  409. {
  410. // Default to AVI for video
  411. contentFeatures = "DLNA.ORG_PN=AVI";
  412. }
  413. else
  414. {
  415. // Default to MP3 for audio
  416. contentFeatures = "DLNA.ORG_PN=MP3";
  417. }
  418. return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
  419. }
  420. private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId)
  421. {
  422. var video = item as Video;
  423. if (video != null)
  424. {
  425. return new PlaylistItem
  426. {
  427. StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
  428. {
  429. ItemId = item.Id.ToString("N"),
  430. MediaSources = mediaSources,
  431. Profile = profile,
  432. DeviceId = deviceId
  433. })
  434. };
  435. }
  436. var audio = item as Audio;
  437. if (audio != null)
  438. {
  439. return new PlaylistItem
  440. {
  441. StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
  442. {
  443. ItemId = item.Id.ToString("N"),
  444. MediaSources = mediaSources,
  445. Profile = profile,
  446. DeviceId = deviceId
  447. })
  448. };
  449. }
  450. var photo = item as Photo;
  451. if (photo != null)
  452. {
  453. return new PlaylistItemFactory().Create(photo, profile);
  454. }
  455. throw new ArgumentException("Unrecognized item type.");
  456. }
  457. /// <summary>
  458. /// Plays the items.
  459. /// </summary>
  460. /// <param name="items">The items.</param>
  461. /// <returns></returns>
  462. private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
  463. {
  464. Playlist.Clear();
  465. Playlist.AddRange(items);
  466. await SetNext();
  467. return true;
  468. }
  469. /// <summary>
  470. /// Adds the items to playlist.
  471. /// </summary>
  472. /// <param name="items">The items.</param>
  473. private void AddItemsToPlaylist(IEnumerable<PlaylistItem> items)
  474. {
  475. Playlist.AddRange(items);
  476. }
  477. private async Task<bool> SetNext()
  478. {
  479. if (!Playlist.Any() || Playlist.All(i => i.PlayState != 0))
  480. {
  481. return true;
  482. }
  483. var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
  484. if (currentitem != null)
  485. {
  486. currentitem.PlayState = 2;
  487. }
  488. var nextTrack = Playlist.FirstOrDefault(i => i.PlayState == 0);
  489. if (nextTrack == null)
  490. {
  491. await _device.SetStop();
  492. return true;
  493. }
  494. nextTrack.PlayState = 1;
  495. var dlnaheaders = GetDlnaHeaders(nextTrack);
  496. _logger.Debug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", _device.Properties.Name, nextTrack.StreamUrl, dlnaheaders);
  497. await _device.SetAvTransport(nextTrack.StreamUrl, dlnaheaders, nextTrack.Didl);
  498. var streamInfo = nextTrack.StreamInfo;
  499. if (streamInfo.StartPositionTicks > 0 && streamInfo.IsDirectStream)
  500. await _device.Seek(TimeSpan.FromTicks(streamInfo.StartPositionTicks));
  501. return true;
  502. }
  503. public Task<bool> SetPrevious()
  504. {
  505. if (!Playlist.Any() || Playlist.All(i => i.PlayState != 2))
  506. return Task.FromResult(false);
  507. var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
  508. var prevTrack = Playlist.LastOrDefault(i => i.PlayState == 2);
  509. if (currentitem != null)
  510. {
  511. currentitem.PlayState = 0;
  512. }
  513. if (prevTrack == null)
  514. return Task.FromResult(false);
  515. prevTrack.PlayState = 1;
  516. return _device.SetAvTransport(prevTrack.StreamInfo.ToDlnaUrl(GetServerAddress()), GetDlnaHeaders(prevTrack), prevTrack.Didl);
  517. }
  518. #endregion
  519. private bool _disposed;
  520. public void Dispose()
  521. {
  522. if (!_disposed)
  523. {
  524. _disposed = true;
  525. _updateTimer.Dispose();
  526. _device.Dispose();
  527. _logger.Log(LogSeverity.Debug, "Controller disposed");
  528. }
  529. }
  530. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  531. public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
  532. {
  533. GeneralCommandType commandType;
  534. if (Enum.TryParse(command.Name, true, out commandType))
  535. {
  536. switch (commandType)
  537. {
  538. case GeneralCommandType.VolumeDown:
  539. return _device.VolumeDown();
  540. case GeneralCommandType.VolumeUp:
  541. return _device.VolumeUp();
  542. case GeneralCommandType.Mute:
  543. return _device.VolumeDown(true);
  544. case GeneralCommandType.Unmute:
  545. return _device.VolumeUp(true);
  546. case GeneralCommandType.ToggleMute:
  547. return _device.ToggleMute();
  548. case GeneralCommandType.SetVolume:
  549. {
  550. string volumeArg;
  551. if (command.Arguments.TryGetValue("Volume", out volumeArg))
  552. {
  553. int volume;
  554. if (int.TryParse(volumeArg, NumberStyles.Any, _usCulture, out volume))
  555. {
  556. return _device.SetVolume(volume);
  557. }
  558. throw new ArgumentException("Unsupported volume value supplied.");
  559. }
  560. throw new ArgumentException("Volume argument cannot be null");
  561. }
  562. default:
  563. return Task.FromResult(true);
  564. }
  565. }
  566. return Task.FromResult(true);
  567. }
  568. }
  569. }