fixes #845 - Support remote control commands to seek with Dlna

This commit is contained in:
Luke Pulverenti 2014-06-20 00:06:24 -04:00
parent 03e25d3b0e
commit d904eca8e0
2 changed files with 187 additions and 64 deletions

View File

@ -30,7 +30,7 @@ namespace MediaBrowser.Dlna.PlayTo
public int Volume { get; set; } public int Volume { get; set; }
public TimeSpan Duration { get; set; } public TimeSpan? Duration { get; set; }
private TimeSpan _position = TimeSpan.FromSeconds(0); private TimeSpan _position = TimeSpan.FromSeconds(0);
public TimeSpan Position public TimeSpan Position
@ -270,8 +270,6 @@ namespace MediaBrowser.Dlna.PlayTo
public async Task SetAvTransport(string url, string header, string metaData) public async Task SetAvTransport(string url, string header, string metaData)
{ {
//await SetStop().ConfigureAwait(false);
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null) if (command == null)
return; return;
@ -598,10 +596,15 @@ namespace MediaBrowser.Dlna.PlayTo
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null); var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
var duration = durationElem == null ? null : durationElem.Value; var duration = durationElem == null ? null : durationElem.Value;
if (!string.IsNullOrWhiteSpace(duration) && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) if (!string.IsNullOrWhiteSpace(duration) &&
!string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
{ {
Duration = TimeSpan.Parse(duration, UsCulture); Duration = TimeSpan.Parse(duration, UsCulture);
} }
else
{
Duration = null;
}
var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null); var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
var position = positionElem == null ? null : positionElem.Value; var position = positionElem == null ? null : positionElem.Value;
@ -891,13 +894,14 @@ namespace MediaBrowser.Dlna.PlayTo
public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress; public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped; public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped;
private uBaseObject _lastMediaInfo; public uBaseObject CurrentMediaInfo { get; private set; }
private void UpdateMediaInfo(uBaseObject mediaInfo, TRANSPORTSTATE state) private void UpdateMediaInfo(uBaseObject mediaInfo, TRANSPORTSTATE state)
{ {
TransportState = state; TransportState = state;
var previousMediaInfo = _lastMediaInfo; var previousMediaInfo = CurrentMediaInfo;
_lastMediaInfo = mediaInfo; CurrentMediaInfo = mediaInfo;
if (previousMediaInfo == null && mediaInfo != null) if (previousMediaInfo == null && mediaInfo != null)
{ {

View File

@ -106,13 +106,13 @@ namespace MediaBrowser.Dlna.PlayTo
e.Headers.TryGetValue("NTS", out nts); e.Headers.TryGetValue("NTS", out nts);
string usn; string usn;
if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
string nt; string nt;
if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
if (string.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) && if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) &&
string.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) && String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) &&
usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 && usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
!_disposed) !_disposed)
{ {
@ -153,53 +153,41 @@ namespace MediaBrowser.Dlna.PlayTo
async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e) async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e)
{ {
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1); var info = GetProgressInfo(e.MediaInfo);
if (playlistItem != null) try
{ {
var streamInfo = playlistItem.StreamInfo; await _sessionManager.OnPlaybackStart(info).ConfigureAwait(false);
}
var info = GetProgressInfo(streamInfo, e.MediaInfo); catch (Exception ex)
{
try _logger.ErrorException("Error reporting progress", ex);
{
await _sessionManager.OnPlaybackStart(info).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error reporting progress", ex);
}
} }
} }
async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e) async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
{ {
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1); var info = GetProgressInfo(e.MediaInfo);
if (playlistItem != null) try
{ {
var streamInfo = playlistItem.StreamInfo; await _sessionManager.OnPlaybackProgress(info).ConfigureAwait(false);
}
var info = GetProgressInfo(streamInfo, e.MediaInfo); catch (Exception ex)
{
try _logger.ErrorException("Error reporting progress", ex);
{
await _sessionManager.OnPlaybackProgress(info).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error reporting progress", ex);
}
} }
} }
private PlaybackStartInfo GetProgressInfo(StreamInfo streamInfo, uBaseObject mediaInfo) private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo)
{ {
var ticks = _device.Position.Ticks; var ticks = _device.Position.Ticks;
if (!streamInfo.IsDirectStream) var info = StreamParams.ParseFromUrl(mediaInfo.Url, _libraryManager);
if (!info.IsDirectStream)
{ {
ticks += streamInfo.StartPositionTicks; ticks += info.StartPositionTicks;
} }
return new PlaybackStartInfo return new PlaybackStartInfo
@ -209,12 +197,14 @@ namespace MediaBrowser.Dlna.PlayTo
PositionTicks = ticks, PositionTicks = ticks,
IsMuted = _device.IsMuted, IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused, IsPaused = _device.IsPaused,
MediaSourceId = streamInfo.MediaSourceId, MediaSourceId = info.MediaSourceId,
AudioStreamIndex = streamInfo.AudioStreamIndex, AudioStreamIndex = info.AudioStreamIndex,
SubtitleStreamIndex = streamInfo.SubtitleStreamIndex, SubtitleStreamIndex = info.SubtitleStreamIndex,
VolumeLevel = _device.Volume, VolumeLevel = _device.Volume,
CanSeek = streamInfo.RunTimeTicks.HasValue,
PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode, CanSeek = info.MediaSource == null ? _device.Duration.HasValue : info.MediaSource.RunTimeTicks.HasValue,
PlayMethod = info.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode,
QueueableMediaTypes = new List<string> { mediaInfo.MediaType } QueueableMediaTypes = new List<string> { mediaInfo.MediaType }
}; };
} }
@ -225,7 +215,7 @@ namespace MediaBrowser.Dlna.PlayTo
{ {
_logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand); _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
var user = string.IsNullOrEmpty(command.ControllingUserId) ? null : _userManager.GetUserById(new Guid(command.ControllingUserId)); var user = String.IsNullOrEmpty(command.ControllingUserId) ? null : _userManager.GetUserById(new Guid(command.ControllingUserId));
var items = new List<BaseItem>(); var items = new List<BaseItem>();
foreach (string id in command.ItemIds) foreach (string id in command.ItemIds)
@ -264,7 +254,7 @@ namespace MediaBrowser.Dlna.PlayTo
_logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count); _logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
if (!string.IsNullOrWhiteSpace(command.ControllingUserId)) if (!String.IsNullOrWhiteSpace(command.ControllingUserId))
{ {
await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId, await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
_session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false); _session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false);
@ -288,18 +278,23 @@ namespace MediaBrowser.Dlna.PlayTo
return _device.SetPlay(); return _device.SetPlay();
case PlaystateCommand.Seek: case PlaystateCommand.Seek:
//var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1); {
//if (playlistItem != null && playlistItem.Transcode && _currentItem != null) var media = _device.CurrentMediaInfo;
//{
// var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
// playlistItem.StartPositionTicks = newItem.StartPositionTicks;
// playlistItem.StreamUrl = newItem.StreamUrl;
// playlistItem.Didl = newItem.Didl;
// return _device.SetAvTransport(playlistItem.StreamUrl, GetDlnaHeaders(playlistItem), playlistItem.Didl);
//} if (media != null)
return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0)); {
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, command.SeekPositionTicks ?? 0, GetServerAddress(), info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
return _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl);
}
}
return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
}
case PlaystateCommand.NextTrack: case PlaystateCommand.NextTrack:
return SetNext(); return SetNext();
@ -384,6 +379,11 @@ namespace MediaBrowser.Dlna.PlayTo
} }
private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string serverAddress) 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)
{ {
var deviceInfo = _device.Properties; var deviceInfo = _device.Properties;
@ -395,7 +395,7 @@ namespace MediaBrowser.Dlna.PlayTo
? (user == null ? hasMediaSources.GetMediaSources(true) : hasMediaSources.GetMediaSources(true, user)).ToList() ? (user == null ? hasMediaSources.GetMediaSources(true) : hasMediaSources.GetMediaSources(true, user)).ToList()
: new List<MediaSourceInfo>(); : new List<MediaSourceInfo>();
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks; playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress); playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
@ -450,7 +450,7 @@ namespace MediaBrowser.Dlna.PlayTo
return null; return null;
} }
private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId) private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
{ {
var video = item as Video; var video = item as Video;
@ -464,7 +464,10 @@ namespace MediaBrowser.Dlna.PlayTo
MediaSources = mediaSources, MediaSources = mediaSources,
Profile = profile, Profile = profile,
DeviceId = deviceId, DeviceId = deviceId,
MaxBitrate = profile.MaxBitrate MaxBitrate = profile.MaxBitrate,
MediaSourceId = mediaSourceId,
AudioStreamIndex = audioStreamIndex,
SubtitleStreamIndex = subtitleStreamIndex
}), }),
Profile = profile Profile = profile
@ -483,7 +486,8 @@ namespace MediaBrowser.Dlna.PlayTo
MediaSources = mediaSources, MediaSources = mediaSources,
Profile = profile, Profile = profile,
DeviceId = deviceId, DeviceId = deviceId,
MaxBitrate = profile.MaxBitrate MaxBitrate = profile.MaxBitrate,
MediaSourceId = mediaSourceId
}), }),
Profile = profile Profile = profile
@ -626,7 +630,7 @@ namespace MediaBrowser.Dlna.PlayTo
{ {
int volume; int volume;
if (int.TryParse(volumeArg, NumberStyles.Any, _usCulture, out volume)) if (Int32.TryParse(volumeArg, NumberStyles.Any, _usCulture, out volume))
{ {
return _device.SetVolume(volume); return _device.SetVolume(volume);
} }
@ -643,5 +647,120 @@ namespace MediaBrowser.Dlna.PlayTo
return Task.FromResult(true); return Task.FromResult(true);
} }
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;
}
}
} }
} }