jellyfin/MediaBrowser.Dlna/PlayTo/PlayToController.cs

900 lines
32 KiB
C#
Raw Normal View History

2014-04-30 15:07:02 +00:00
using MediaBrowser.Controller.Dlna;
2014-04-23 16:29:21 +00:00
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
2014-09-24 01:44:05 +00:00
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Session;
2014-04-23 03:18:01 +00:00
using MediaBrowser.Dlna.Didl;
2014-04-27 03:42:05 +00:00
using MediaBrowser.Dlna.Ssdp;
2014-04-01 22:23:07 +00:00
using MediaBrowser.Model.Dlna;
2014-04-18 05:03:01 +00:00
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.System;
using System;
using System.Collections.Generic;
2014-04-16 02:17:48 +00:00
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Dlna.PlayTo
{
public class PlayToController : ISessionController, IDisposable
{
private Device _device;
private readonly SessionInfo _session;
private readonly ISessionManager _sessionManager;
private readonly IItemRepository _itemRepository;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
2014-03-13 19:08:02 +00:00
private readonly IDlnaManager _dlnaManager;
private readonly IUserManager _userManager;
2014-04-23 16:29:21 +00:00
private readonly IImageProcessor _imageProcessor;
private readonly IUserDataManager _userDataManager;
2014-09-24 01:44:05 +00:00
private readonly ILocalizationManager _localization;
2014-03-17 04:25:11 +00:00
2014-07-22 01:29:06 +00:00
private readonly DeviceDiscovery _deviceDiscovery;
2014-04-30 15:07:02 +00:00
private readonly string _serverAddress;
2014-04-27 03:42:05 +00:00
public bool IsSessionActive
{
get
{
2014-04-26 02:55:07 +00:00
return _device != null;
}
}
2014-05-17 18:37:40 +00:00
public bool SupportsMediaControl
{
get { return IsSessionActive; }
}
2014-05-07 02:28:19 +00:00
private Timer _updateTimer;
2014-09-24 01:44:05 +00:00
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization)
{
_session = session;
_itemRepository = itemRepository;
_sessionManager = sessionManager;
_libraryManager = libraryManager;
2014-03-13 21:00:17 +00:00
_dlnaManager = dlnaManager;
_userManager = userManager;
2014-04-23 16:29:21 +00:00
_imageProcessor = imageProcessor;
2014-04-30 15:07:02 +00:00
_serverAddress = serverAddress;
2014-07-22 01:29:06 +00:00
_deviceDiscovery = deviceDiscovery;
_userDataManager = userDataManager;
2014-09-24 01:44:05 +00:00
_localization = localization;
_logger = logger;
}
2014-03-13 19:08:02 +00:00
public void Init(Device device)
{
_device = device;
2014-04-22 17:25:54 +00:00
_device.PlaybackStart += _device_PlaybackStart;
_device.PlaybackProgress += _device_PlaybackProgress;
_device.PlaybackStopped += _device_PlaybackStopped;
2014-06-20 17:02:34 +00:00
_device.MediaChanged += _device_MediaChanged;
_device.Start();
2014-07-22 01:29:06 +00:00
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
2014-05-07 02:28:19 +00:00
_updateTimer = new Timer(updateTimer_Elapsed, null, 60000, 60000);
}
2014-07-22 01:29:06 +00:00
void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
2014-04-27 03:42:05 +00:00
{
string nts;
e.Headers.TryGetValue("NTS", out nts);
string usn;
if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
2014-04-27 03:42:05 +00:00
string nt;
if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
2014-07-22 01:29:06 +00:00
if ( usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
2014-04-30 15:07:02 +00:00
!_disposed)
2014-04-27 03:42:05 +00:00
{
2014-04-30 15:07:02 +00:00
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
2014-04-27 03:42:05 +00:00
{
2014-04-30 15:07:02 +00:00
try
{
_sessionManager.ReportSessionEnded(_session.Id);
2014-04-30 15:07:02 +00:00
}
catch
2014-04-27 03:42:05 +00:00
{
2014-04-30 15:07:02 +00:00
// Could throw if the session is already gone
2014-04-27 03:42:05 +00:00
}
}
}
}
2014-07-22 01:29:06 +00:00
private void updateTimer_Elapsed(object state)
{
if (DateTime.UtcNow >= _device.DateLastActivity.AddSeconds(120))
{
try
{
// Session is inactive, mark it for Disposal and don't start the elapsed timer.
_sessionManager.ReportSessionEnded(_session.Id);
}
catch (Exception ex)
{
_logger.ErrorException("Error in ReportSessionEnded", ex);
}
}
}
private string GetServerAddress()
{
return _serverAddress;
}
2014-06-22 05:52:31 +00:00
async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
2014-06-20 17:02:34 +00:00
{
2014-06-23 16:05:19 +00:00
try
{
var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager);
var progress = GetProgressInfo(e.OldMediaInfo, streamInfo);
2014-06-20 17:02:34 +00:00
2014-06-23 16:05:19 +00:00
var positionTicks = progress.PositionTicks;
2014-06-20 17:02:34 +00:00
2014-06-23 16:05:19 +00:00
ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks);
2014-06-22 05:52:31 +00:00
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager);
progress = GetProgressInfo(e.NewMediaInfo, streamInfo);
await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error reporting progress", ex);
}
2014-06-20 17:02:34 +00:00
}
2014-04-22 17:25:54 +00:00
async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e)
{
2014-06-23 16:05:19 +00:00
try
{
var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager);
var progress = GetProgressInfo(e.MediaInfo, streamInfo);
2014-06-23 16:05:19 +00:00
var positionTicks = progress.PositionTicks;
2014-06-20 17:02:34 +00:00
2014-06-23 16:05:19 +00:00
ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks);
2014-06-20 17:02:34 +00:00
2014-06-23 16:05:19 +00:00
var duration = streamInfo.MediaSource == null ?
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
streamInfo.MediaSource.RunTimeTicks;
2014-06-20 17:02:34 +00:00
2014-06-23 16:05:19 +00:00
var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0);
2014-06-20 17:02:34 +00:00
2014-06-23 16:05:19 +00:00
if (!playedToCompletion && duration.HasValue && positionTicks.HasValue)
{
double percent = positionTicks.Value;
percent /= duration.Value;
2014-04-22 17:25:54 +00:00
2014-06-23 16:05:19 +00:00
playedToCompletion = Math.Abs(1 - percent) <= .1;
}
2014-06-23 16:05:19 +00:00
if (playedToCompletion)
{
await SetPlaylistIndex(_currentPlaylistIndex + 1).ConfigureAwait(false);
}
else
{
Playlist.Clear();
}
}
2014-06-23 16:05:19 +00:00
catch (Exception ex)
{
2014-06-23 16:05:19 +00:00
_logger.ErrorException("Error reporting playback stopped", ex);
}
}
2014-06-20 17:02:34 +00:00
private async void ReportPlaybackStopped(uBaseObject mediaInfo, StreamParams streamInfo, long? positionTicks)
{
try
{
await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
{
ItemId = mediaInfo.Id,
SessionId = _session.Id,
PositionTicks = positionTicks,
MediaSourceId = streamInfo.MediaSourceId
}).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error reporting progress", ex);
}
}
2014-04-22 17:25:54 +00:00
async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e)
{
try
2014-04-22 17:25:54 +00:00
{
2014-06-23 16:05:19 +00:00
var info = GetProgressInfo(e.MediaInfo);
await _sessionManager.OnPlaybackStart(info).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error reporting progress", ex);
}
}
2014-04-22 17:25:54 +00:00
async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
{
try
2014-03-17 04:25:11 +00:00
{
2014-06-23 16:05:19 +00:00
var info = GetProgressInfo(e.MediaInfo);
await _sessionManager.OnPlaybackProgress(info).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error reporting progress", ex);
2014-03-17 04:25:11 +00:00
}
}
private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo)
{
var info = StreamParams.ParseFromUrl(mediaInfo.Url, _libraryManager);
2014-06-20 17:02:34 +00:00
return GetProgressInfo(mediaInfo, info);
}
private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info)
{
var ticks = _device.Position.Ticks;
if (!info.IsDirectStream)
2014-04-22 17:25:54 +00:00
{
ticks += info.StartPositionTicks;
2014-04-22 17:25:54 +00:00
}
2014-04-16 02:17:48 +00:00
2014-04-22 17:25:54 +00:00
return new PlaybackStartInfo
{
2014-04-22 17:25:54 +00:00
ItemId = mediaInfo.Id,
SessionId = _session.Id,
PositionTicks = ticks,
IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused,
MediaSourceId = info.MediaSourceId,
AudioStreamIndex = info.AudioStreamIndex,
SubtitleStreamIndex = info.SubtitleStreamIndex,
2014-04-22 17:25:54 +00:00
VolumeLevel = _device.Volume,
CanSeek = info.MediaSource == null ? _device.Duration.HasValue : info.MediaSource.RunTimeTicks.HasValue,
PlayMethod = info.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode,
2014-04-22 17:25:54 +00:00
QueueableMediaTypes = new List<string> { mediaInfo.MediaType }
};
}
2014-04-18 05:03:01 +00:00
#region SendCommands
public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{
_logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
2014-09-14 15:10:51 +00:00
var user = String.IsNullOrEmpty(command.ControllingUserId) ? null : _userManager.GetUserById(command.ControllingUserId);
var items = new List<BaseItem>();
foreach (string id in command.ItemIds)
{
AddItemFromId(Guid.Parse(id), items);
}
var playlist = new List<PlaylistItem>();
var isFirst = true;
var serverAddress = GetServerAddress();
foreach (var item in items)
{
if (isFirst && command.StartPositionTicks.HasValue)
{
playlist.Add(CreatePlaylistItem(item, user, command.StartPositionTicks.Value, serverAddress));
isFirst = false;
}
else
{
playlist.Add(CreatePlaylistItem(item, user, 0, serverAddress));
}
}
_logger.Debug("{0} - Playlist created", _session.DeviceName);
if (command.PlayCommand == PlayCommand.PlayLast)
{
2014-04-22 17:25:54 +00:00
Playlist.AddRange(playlist);
}
if (command.PlayCommand == PlayCommand.PlayNext)
{
2014-04-22 17:25:54 +00:00
Playlist.AddRange(playlist);
}
if (!String.IsNullOrWhiteSpace(command.ControllingUserId))
{
await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
_session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false);
}
await PlayItems(playlist).ConfigureAwait(false);
}
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
{
switch (command.Command)
{
case PlaystateCommand.Stop:
Playlist.Clear();
return _device.SetStop();
case PlaystateCommand.Pause:
return _device.SetPause();
case PlaystateCommand.Unpause:
return _device.SetPlay();
case PlaystateCommand.Seek:
{
return Seek(command.SeekPositionTicks ?? 0);
}
case PlaystateCommand.NextTrack:
return SetPlaylistIndex(_currentPlaylistIndex + 1);
case PlaystateCommand.PreviousTrack:
return SetPlaylistIndex(_currentPlaylistIndex - 1);
}
return Task.FromResult(true);
}
private async Task Seek(long newPosition)
{
var media = _device.CurrentMediaInfo;
if (media != null)
{
var info = StreamParams.ParseFromUrl(media.Url, _libraryManager);
if (info.Item != null && !info.IsDirectStream)
{
var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, GetServerAddress(), info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false);
if (newItem.StreamInfo.IsDirectStream)
{
await _device.Seek(TimeSpan.FromTicks(newPosition)).ConfigureAwait(false);
}
return;
}
await _device.Seek(TimeSpan.FromTicks(newPosition)).ConfigureAwait(false);
}
}
public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
2014-05-18 19:58:42 +00:00
public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendServerRestartNotification(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
2014-04-06 17:53:23 +00:00
public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
#endregion
#region Playlist
private int _currentPlaylistIndex;
2014-04-22 17:25:54 +00:00
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
private List<PlaylistItem> Playlist
{
get
{
return _playlist;
}
}
private void AddItemFromId(Guid id, List<BaseItem> list)
{
var item = _libraryManager.GetItemById(id);
if (item.IsFolder)
{
foreach (var childId in _itemRepository.GetChildren(item.Id))
{
AddItemFromId(childId, list);
}
}
else
{
if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
{
list.Add(item);
}
}
}
private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string serverAddress)
{
return CreatePlaylistItem(item, user, startPostionTicks, serverAddress, null, null, null);
}
private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string serverAddress, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
{
2014-03-13 19:08:02 +00:00
var deviceInfo = _device.Properties;
2014-03-26 15:17:36 +00:00
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
_dlnaManager.GetDefaultProfile();
2014-03-23 16:42:02 +00:00
var hasMediaSources = item as IHasMediaSources;
var mediaSources = hasMediaSources != null
? (user == null ? hasMediaSources.GetMediaSources(true) : hasMediaSources.GetMediaSources(true, user)).ToList()
2014-04-18 05:03:01 +00:00
: new List<MediaSourceInfo>();
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
2014-04-18 05:03:01 +00:00
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
2014-09-24 01:44:05 +00:00
var itemXml = new DidlBuilder(profile, user, _imageProcessor, serverAddress, _userDataManager, _localization)
.GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
2014-04-18 05:03:01 +00:00
2014-04-23 16:29:21 +00:00
playlistItem.Didl = itemXml;
return playlistItem;
}
2014-03-23 05:10:33 +00:00
private string GetDlnaHeaders(PlaylistItem item)
{
2014-04-23 02:47:46 +00:00
var profile = item.Profile;
2014-04-18 05:03:01 +00:00
var streamInfo = item.StreamInfo;
2014-04-23 02:47:46 +00:00
if (streamInfo.MediaType == DlnaProfileType.Audio)
2014-03-23 05:10:33 +00:00
{
2014-04-23 02:47:46 +00:00
return new ContentFeatureBuilder(profile)
.BuildAudioHeader(streamInfo.Container,
streamInfo.AudioCodec,
streamInfo.TargetAudioBitrate,
streamInfo.TargetAudioSampleRate,
streamInfo.TargetAudioChannels,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks,
streamInfo.TranscodeSeekInfo);
2014-03-23 05:10:33 +00:00
}
2014-04-23 02:47:46 +00:00
if (streamInfo.MediaType == DlnaProfileType.Video)
2014-03-23 05:10:33 +00:00
{
2014-07-31 02:09:23 +00:00
var list = new ContentFeatureBuilder(profile)
2014-04-23 02:47:46 +00:00
.BuildVideoHeader(streamInfo.Container,
streamInfo.VideoCodec,
streamInfo.AudioCodec,
streamInfo.TargetWidth,
streamInfo.TargetHeight,
2014-04-24 05:08:10 +00:00
streamInfo.TargetVideoBitDepth,
streamInfo.TargetVideoBitrate,
streamInfo.TargetAudioChannels,
streamInfo.TargetAudioBitrate,
2014-04-23 02:47:46 +00:00
streamInfo.TargetTimestamp,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks,
2014-04-24 05:08:10 +00:00
streamInfo.TargetVideoProfile,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate,
streamInfo.TargetPacketLength,
2014-06-22 16:25:47 +00:00
streamInfo.TranscodeSeekInfo,
2014-09-09 01:15:31 +00:00
streamInfo.IsTargetAnamorphic,
streamInfo.TargetRefFrames);
2014-07-31 02:09:23 +00:00
return list.FirstOrDefault();
2014-03-23 05:10:33 +00:00
}
2014-04-23 02:47:46 +00:00
return null;
2014-03-23 05:10:33 +00:00
}
2014-04-06 18:34:47 +00:00
private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
{
2014-06-23 16:05:19 +00:00
if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
2014-04-18 05:03:01 +00:00
return new PlaylistItem
{
StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
{
ItemId = item.Id.ToString("N"),
MediaSources = mediaSources,
Profile = profile,
2014-06-14 23:13:09 +00:00
DeviceId = deviceId,
2014-07-27 22:01:29 +00:00
MaxBitrate = profile.MaxStreamingBitrate,
MediaSourceId = mediaSourceId,
AudioStreamIndex = audioStreamIndex,
SubtitleStreamIndex = subtitleStreamIndex
2014-04-23 02:47:46 +00:00
}),
Profile = profile
2014-04-18 05:03:01 +00:00
};
}
2014-06-23 16:05:19 +00:00
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
2014-04-18 05:03:01 +00:00
return new PlaylistItem
{
StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
{
ItemId = item.Id.ToString("N"),
MediaSources = mediaSources,
Profile = profile,
2014-06-14 23:13:09 +00:00
DeviceId = deviceId,
2014-07-27 22:01:29 +00:00
MaxBitrate = profile.MaxStreamingBitrate,
MediaSourceId = mediaSourceId
2014-04-23 02:47:46 +00:00
}),
Profile = profile
2014-04-18 05:03:01 +00:00
};
}
2014-06-23 16:05:19 +00:00
if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{
2014-06-23 16:05:19 +00:00
return new PlaylistItemFactory().Create((Photo)item, profile);
}
throw new ArgumentException("Unrecognized item type.");
}
/// <summary>
/// Plays the items.
/// </summary>
/// <param name="items">The items.</param>
/// <returns></returns>
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
{
Playlist.Clear();
Playlist.AddRange(items);
_logger.Debug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count);
await SetPlaylistIndex(0).ConfigureAwait(false);
return true;
}
private async Task SetPlaylistIndex(int index)
{
if (index < 0 || index >= Playlist.Count)
{
Playlist.Clear();
await _device.SetStop();
2014-04-26 02:55:07 +00:00
return;
}
2014-03-23 05:10:33 +00:00
_currentPlaylistIndex = index;
var currentitem = Playlist[index];
2014-03-23 05:10:33 +00:00
2014-07-31 02:09:23 +00:00
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl);
2014-04-06 18:34:47 +00:00
var streamInfo = currentitem.StreamInfo;
2014-04-18 05:03:01 +00:00
if (streamInfo.StartPositionTicks > 0 && streamInfo.IsDirectStream)
await _device.Seek(TimeSpan.FromTicks(streamInfo.StartPositionTicks));
}
#endregion
private bool _disposed;
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
2014-04-26 02:55:07 +00:00
2014-04-22 17:25:54 +00:00
_device.PlaybackStart -= _device_PlaybackStart;
_device.PlaybackProgress -= _device_PlaybackProgress;
_device.PlaybackStopped -= _device_PlaybackStopped;
2014-06-20 17:02:34 +00:00
_device.MediaChanged -= _device_MediaChanged;
2014-07-22 01:29:06 +00:00
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
2014-04-26 02:55:07 +00:00
2014-05-07 02:28:19 +00:00
DisposeUpdateTimer();
_device.Dispose();
}
}
2014-03-28 19:58:18 +00:00
2014-05-07 02:28:19 +00:00
private void DisposeUpdateTimer()
{
if (_updateTimer != null)
{
_updateTimer.Dispose();
_updateTimer = null;
}
}
2014-04-15 03:54:52 +00:00
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
2014-04-16 02:17:48 +00:00
2014-03-31 21:04:22 +00:00
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
2014-03-28 19:58:18 +00:00
{
2014-03-31 21:04:22 +00:00
GeneralCommandType commandType;
2014-04-14 06:42:51 +00:00
if (Enum.TryParse(command.Name, true, out commandType))
2014-03-31 21:04:22 +00:00
{
switch (commandType)
{
case GeneralCommandType.VolumeDown:
return _device.VolumeDown();
case GeneralCommandType.VolumeUp:
return _device.VolumeUp();
case GeneralCommandType.Mute:
2014-04-22 17:25:54 +00:00
return _device.Mute();
2014-03-31 21:04:22 +00:00
case GeneralCommandType.Unmute:
2014-04-22 17:25:54 +00:00
return _device.Unmute();
2014-03-31 21:04:22 +00:00
case GeneralCommandType.ToggleMute:
return _device.ToggleMute();
case GeneralCommandType.SetAudioStreamIndex:
{
string arg;
if (command.Arguments.TryGetValue("Index", out arg))
{
int val;
if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out val))
{
return SetAudioStreamIndex(val);
}
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
}
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
}
case GeneralCommandType.SetSubtitleStreamIndex:
{
string arg;
if (command.Arguments.TryGetValue("Index", out arg))
{
int val;
if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out val))
{
return SetSubtitleStreamIndex(val);
}
throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
}
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
}
2014-04-15 03:54:52 +00:00
case GeneralCommandType.SetVolume:
{
string arg;
2014-04-15 03:54:52 +00:00
if (command.Arguments.TryGetValue("Volume", out arg))
2014-04-15 03:54:52 +00:00
{
2014-04-16 02:17:48 +00:00
int volume;
if (Int32.TryParse(arg, NumberStyles.Any, _usCulture, out volume))
2014-04-16 02:17:48 +00:00
{
return _device.SetVolume(volume);
}
throw new ArgumentException("Unsupported volume value supplied.");
2014-04-15 03:54:52 +00:00
}
2014-04-16 02:17:48 +00:00
throw new ArgumentException("Volume argument cannot be null");
2014-04-15 03:54:52 +00:00
}
2014-03-31 21:04:22 +00:00
default:
return Task.FromResult(true);
}
}
return Task.FromResult(true);
2014-03-28 19:58:18 +00:00
}
private async Task SetAudioStreamIndex(int? newIndex)
{
var media = _device.CurrentMediaInfo;
if (media != null)
{
var info = StreamParams.ParseFromUrl(media.Url, _libraryManager);
2014-06-20 17:02:34 +00:00
var progress = GetProgressInfo(media, info);
if (info.Item != null)
{
2014-06-20 17:02:34 +00:00
var newPosition = progress.PositionTicks ?? 0;
var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, GetServerAddress(), info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false);
if (newItem.StreamInfo.IsDirectStream)
{
await _device.Seek(TimeSpan.FromTicks(newPosition)).ConfigureAwait(false);
}
}
}
}
private async Task SetSubtitleStreamIndex(int? newIndex)
{
var media = _device.CurrentMediaInfo;
if (media != null)
{
var info = StreamParams.ParseFromUrl(media.Url, _libraryManager);
2014-06-20 17:02:34 +00:00
var progress = GetProgressInfo(media, info);
if (info.Item != null)
{
2014-06-20 17:02:34 +00:00
var newPosition = progress.PositionTicks ?? 0;
var user = _session.UserId.HasValue ? _userManager.GetUserById(_session.UserId.Value) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, GetServerAddress(), info.MediaSourceId, info.AudioStreamIndex, newIndex);
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl).ConfigureAwait(false);
if (newItem.StreamInfo.IsDirectStream)
{
await _device.Seek(TimeSpan.FromTicks(newPosition)).ConfigureAwait(false);
}
}
}
}
private class StreamParams
{
public string ItemId { get; set; }
public bool IsDirectStream { get; set; }
public long StartPositionTicks { get; set; }
public int? AudioStreamIndex { get; set; }
public int? SubtitleStreamIndex { get; set; }
public string DeviceProfileId { get; set; }
public string DeviceId { get; set; }
public string MediaSourceId { get; set; }
public BaseItem Item { get; set; }
public MediaSourceInfo MediaSource { get; set; }
private static string GetItemId(string url)
{
var parts = url.Split('/');
for (var i = 0; i < parts.Length; i++)
{
var part = parts[i];
if (string.Equals(part, "audio", StringComparison.OrdinalIgnoreCase) ||
string.Equals(part, "videos", StringComparison.OrdinalIgnoreCase))
{
if (parts.Length > i + 1)
{
return parts[i + 1];
}
}
}
return null;
}
public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager)
{
var request = new StreamParams
{
ItemId = GetItemId(url)
};
if (string.IsNullOrWhiteSpace(request.ItemId))
{
return request;
}
const string srch = "params=";
var index = url.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
if (index == -1) return request;
var vals = url.Substring(index + srch.Length).Split(';');
for (var i = 0; i < vals.Length; i++)
{
var val = vals[i];
if (string.IsNullOrWhiteSpace(val))
{
continue;
}
if (i == 0)
{
request.DeviceProfileId = val;
}
else if (i == 1)
{
request.DeviceId = val;
}
else if (i == 2)
{
request.MediaSourceId = val;
}
else if (i == 3)
{
request.IsDirectStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
else if (i == 6)
{
request.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 7)
{
request.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 14)
{
request.StartPositionTicks = long.Parse(val, CultureInfo.InvariantCulture);
}
}
request.Item = string.IsNullOrWhiteSpace(request.ItemId)
? null
: libraryManager.GetItemById(new Guid(request.ItemId));
var hasMediaSources = request.Item as IHasMediaSources;
request.MediaSource = hasMediaSources == null ?
null :
hasMediaSources.GetMediaSources(false).FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase));
return request;
}
}
}
}