From d904eca8e08720391b62ac0a1993116c70159518 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 20 Jun 2014 00:06:24 -0400 Subject: [PATCH] fixes #845 - Support remote control commands to seek with Dlna --- MediaBrowser.Dlna/PlayTo/Device.cs | 18 +- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 233 ++++++++++++++----- 2 files changed, 187 insertions(+), 64 deletions(-) diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 3e5e877cdc..fae0742bc0 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Dlna.PlayTo public int Volume { get; set; } - public TimeSpan Duration { get; set; } + public TimeSpan? Duration { get; set; } private TimeSpan _position = TimeSpan.FromSeconds(0); public TimeSpan Position @@ -270,8 +270,6 @@ namespace MediaBrowser.Dlna.PlayTo public async Task SetAvTransport(string url, string header, string metaData) { - //await SetStop().ConfigureAwait(false); - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); if (command == null) 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 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); } + else + { + Duration = 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; @@ -891,13 +894,14 @@ namespace MediaBrowser.Dlna.PlayTo public event EventHandler PlaybackProgress; public event EventHandler PlaybackStopped; - private uBaseObject _lastMediaInfo; + public uBaseObject CurrentMediaInfo { get; private set; } + private void UpdateMediaInfo(uBaseObject mediaInfo, TRANSPORTSTATE state) { TransportState = state; - var previousMediaInfo = _lastMediaInfo; - _lastMediaInfo = mediaInfo; + var previousMediaInfo = CurrentMediaInfo; + CurrentMediaInfo = mediaInfo; if (previousMediaInfo == null && mediaInfo != null) { diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index f9f90034c5..54045a3d58 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -106,13 +106,13 @@ namespace MediaBrowser.Dlna.PlayTo e.Headers.TryGetValue("NTS", out nts); string usn; - if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty; 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) && - string.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) && + if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) && + String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) && usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 && !_disposed) { @@ -153,53 +153,41 @@ namespace MediaBrowser.Dlna.PlayTo 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; - - var info = GetProgressInfo(streamInfo, e.MediaInfo); - - try - { - await _sessionManager.OnPlaybackStart(info).ConfigureAwait(false); - } - catch (Exception ex) - { - _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) { - var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1); + var info = GetProgressInfo(e.MediaInfo); - if (playlistItem != null) + try { - var streamInfo = playlistItem.StreamInfo; - - var info = GetProgressInfo(streamInfo, e.MediaInfo); - - try - { - await _sessionManager.OnPlaybackProgress(info).ConfigureAwait(false); - } - catch (Exception ex) - { - _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; - if (!streamInfo.IsDirectStream) + var info = StreamParams.ParseFromUrl(mediaInfo.Url, _libraryManager); + + if (!info.IsDirectStream) { - ticks += streamInfo.StartPositionTicks; + ticks += info.StartPositionTicks; } return new PlaybackStartInfo @@ -209,12 +197,14 @@ namespace MediaBrowser.Dlna.PlayTo PositionTicks = ticks, IsMuted = _device.IsMuted, IsPaused = _device.IsPaused, - MediaSourceId = streamInfo.MediaSourceId, - AudioStreamIndex = streamInfo.AudioStreamIndex, - SubtitleStreamIndex = streamInfo.SubtitleStreamIndex, + MediaSourceId = info.MediaSourceId, + AudioStreamIndex = info.AudioStreamIndex, + SubtitleStreamIndex = info.SubtitleStreamIndex, 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 { mediaInfo.MediaType } }; } @@ -225,7 +215,7 @@ namespace MediaBrowser.Dlna.PlayTo { _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(); foreach (string id in command.ItemIds) @@ -264,7 +254,7 @@ namespace MediaBrowser.Dlna.PlayTo _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, _session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false); @@ -288,18 +278,23 @@ namespace MediaBrowser.Dlna.PlayTo return _device.SetPlay(); case PlaystateCommand.Seek: - //var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1); - //if (playlistItem != null && playlistItem.Transcode && _currentItem != null) - //{ - // 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); + { + var media = _device.CurrentMediaInfo; - //} - return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0)); + 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, 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: return SetNext(); @@ -384,6 +379,11 @@ namespace MediaBrowser.Dlna.PlayTo } 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; @@ -395,7 +395,7 @@ namespace MediaBrowser.Dlna.PlayTo ? (user == null ? hasMediaSources.GetMediaSources(true) : hasMediaSources.GetMediaSources(true, user)).ToList() : new List(); - var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId); + var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); playlistItem.StreamInfo.StartPositionTicks = startPostionTicks; playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress); @@ -450,7 +450,7 @@ namespace MediaBrowser.Dlna.PlayTo return null; } - private PlaylistItem GetPlaylistItem(BaseItem item, List mediaSources, DeviceProfile profile, string deviceId) + private PlaylistItem GetPlaylistItem(BaseItem item, List mediaSources, DeviceProfile profile, string deviceId, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex) { var video = item as Video; @@ -464,7 +464,10 @@ namespace MediaBrowser.Dlna.PlayTo MediaSources = mediaSources, Profile = profile, DeviceId = deviceId, - MaxBitrate = profile.MaxBitrate + MaxBitrate = profile.MaxBitrate, + MediaSourceId = mediaSourceId, + AudioStreamIndex = audioStreamIndex, + SubtitleStreamIndex = subtitleStreamIndex }), Profile = profile @@ -483,7 +486,8 @@ namespace MediaBrowser.Dlna.PlayTo MediaSources = mediaSources, Profile = profile, DeviceId = deviceId, - MaxBitrate = profile.MaxBitrate + MaxBitrate = profile.MaxBitrate, + MediaSourceId = mediaSourceId }), Profile = profile @@ -626,7 +630,7 @@ namespace MediaBrowser.Dlna.PlayTo { int volume; - if (int.TryParse(volumeArg, NumberStyles.Any, _usCulture, out volume)) + if (Int32.TryParse(volumeArg, NumberStyles.Any, _usCulture, out volume)) { return _device.SetVolume(volume); } @@ -643,5 +647,120 @@ namespace MediaBrowser.Dlna.PlayTo 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; + } + } } }