diff options
Diffstat (limited to 'MediaBrowser.Dlna')
| -rw-r--r-- | MediaBrowser.Dlna/DlnaManager.cs | 18 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 1 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/DidlBuilder.cs | 1 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/DlnaController.cs | 104 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 8 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs | 18 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/PlaylistItem.cs | 35 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs | 439 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/PlayTo/StreamHelper.cs | 69 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/Server/ControlHandler.cs | 741 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs | 2 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs | 12 | ||||
| -rw-r--r-- | MediaBrowser.Dlna/Server/SsdpHandler.cs | 2 |
13 files changed, 660 insertions, 790 deletions
diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 83e3df798b..9993b0e21f 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Dlna.Profiles; using MediaBrowser.Dlna.Server; @@ -27,8 +28,11 @@ namespace MediaBrowser.Dlna private readonly IJsonSerializer _jsonSerializer; private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; + private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; + private readonly IUserDataManager _userDataManager; - public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager) + public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; @@ -37,6 +41,9 @@ namespace MediaBrowser.Dlna _jsonSerializer = jsonSerializer; _userManager = userManager; _libraryManager = libraryManager; + _dtoService = dtoService; + _imageProcessor = imageProcessor; + _userDataManager = userDataManager; //DumpProfiles(); } @@ -502,7 +509,14 @@ namespace MediaBrowser.Dlna public ControlResponse ProcessControlRequest(ControlRequest request) { - return new ControlHandler(_logger, _userManager, _libraryManager) + var profile = GetProfile(request.Headers) + ?? GetDefaultProfile(); + + var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId); + + var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase)); + + return new ControlHandler(_logger, _userManager, _libraryManager, profile, serverAddress, _dtoService, _imageProcessor, _userDataManager) .ProcessControlRequest(request); } diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index bf38f19dcf..016cac4d66 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -81,7 +81,6 @@ <Compile Include="Ssdp\SsdpHelper.cs" /> <Compile Include="PlayTo\SsdpHttpClient.cs" /> <Compile Include="Common\StateVariable.cs" /> - <Compile Include="PlayTo\StreamHelper.cs" /> <Compile Include="PlayTo\TransportCommands.cs" /> <Compile Include="PlayTo\TransportStateEventArgs.cs" /> <Compile Include="PlayTo\uBaseObject.cs" /> diff --git a/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs b/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs index 853064c606..0304008fc2 100644 --- a/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs +++ b/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs @@ -39,6 +39,7 @@ namespace MediaBrowser.Dlna.PlayTo /// <param name="serverAddress">The server address.</param> /// <param name="streamUrl">The stream URL.</param> /// <param name="streams">The streams.</param> + /// <param name="includeImageRes">if set to <c>true</c> [include image resource].</param> /// <returns>System.String.</returns> public static string Build(BaseItem dto, string userId, string serverAddress, string streamUrl, IEnumerable<MediaStream> streams, bool includeImageRes) { diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 75667d54e9..183a20447d 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -1,12 +1,14 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Session; @@ -32,6 +34,7 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IDlnaManager _dlnaManager; private readonly IUserManager _userManager; private readonly IServerApplicationHost _appHost; + private readonly IDtoService _dtoService; private bool _playbackStarted; private const int UpdateTimerIntervalMs = 1000; @@ -52,7 +55,7 @@ namespace MediaBrowser.Dlna.PlayTo } } - public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost) + public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService) { _session = session; _itemRepository = itemRepository; @@ -62,6 +65,7 @@ namespace MediaBrowser.Dlna.PlayTo _dlnaManager = dlnaManager; _userManager = userManager; _appHost = appHost; + _dtoService = dtoService; _logger = logger; } @@ -172,20 +176,23 @@ namespace MediaBrowser.Dlna.PlayTo if (playlistItem != null) { + var streamInfo = playlistItem.StreamInfo; + if (!_playbackStarted) { await _sessionManager.OnPlaybackStart(new PlaybackStartInfo { ItemId = _currentItem.Id.ToString("N"), SessionId = _session.Id, - CanSeek = true, + CanSeek = streamInfo.RunTimeTicks.HasValue, QueueableMediaTypes = new List<string> { _currentItem.MediaType }, - MediaSourceId = playlistItem.MediaSourceId, - AudioStreamIndex = playlistItem.AudioStreamIndex, - SubtitleStreamIndex = playlistItem.SubtitleStreamIndex, + MediaSourceId = streamInfo.MediaSourceId, + AudioStreamIndex = streamInfo.AudioStreamIndex, + SubtitleStreamIndex = streamInfo.SubtitleStreamIndex, IsMuted = _device.IsMuted, IsPaused = _device.IsPaused, - VolumeLevel = _device.Volume + VolumeLevel = _device.Volume, + PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode }).ConfigureAwait(false); @@ -196,9 +203,9 @@ namespace MediaBrowser.Dlna.PlayTo { var ticks = _device.Position.Ticks; - if (playlistItem.Transcode) + if (!streamInfo.IsDirectStream) { - ticks += playlistItem.StartPositionTicks; + ticks += streamInfo.StartPositionTicks; } await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo @@ -208,11 +215,12 @@ namespace MediaBrowser.Dlna.PlayTo PositionTicks = ticks, IsMuted = _device.IsMuted, IsPaused = _device.IsPaused, - MediaSourceId = playlistItem.MediaSourceId, - AudioStreamIndex = playlistItem.AudioStreamIndex, - SubtitleStreamIndex = playlistItem.SubtitleStreamIndex, + MediaSourceId = streamInfo.MediaSourceId, + AudioStreamIndex = streamInfo.AudioStreamIndex, + SubtitleStreamIndex = streamInfo.SubtitleStreamIndex, VolumeLevel = _device.Volume, - CanSeek = true + CanSeek = streamInfo.RunTimeTicks.HasValue, + PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode }).ConfigureAwait(false); } @@ -411,46 +419,43 @@ namespace MediaBrowser.Dlna.PlayTo private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress) { - var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery - { - ItemId = item.Id - - }).ToList(); - var deviceInfo = _device.Properties; var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ?? _dlnaManager.GetDefaultProfile(); - var playlistItem = GetPlaylistItem(item, streams, profile); - playlistItem.StartPositionTicks = startPostionTicks; - playlistItem.DeviceProfileId = profile.Id; + var mediaSources = item is Audio || item is Video + ? _dtoService.GetMediaSources(item) + : new List<MediaSourceInfo>(); - if (playlistItem.MediaType == DlnaProfileType.Audio) - { - playlistItem.StreamUrl = StreamHelper.GetAudioUrl(deviceInfo, playlistItem, streams, serverAddress); - } - else - { - playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress); - } + var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId); + playlistItem.StreamInfo.StartPositionTicks = startPostionTicks; + + playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress); - playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams, profile.EnableAlbumArtInDidl); + var mediaStreams = mediaSources + .Where(i => string.Equals(i.Id, playlistItem.StreamInfo.MediaSourceId)) + .SelectMany(i => i.MediaStreams) + .ToList(); + + playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, mediaStreams, profile.EnableAlbumArtInDidl); return playlistItem; } private string GetDlnaHeaders(PlaylistItem item) { - var orgOp = item.Transcode ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01"; + var streamInfo = item.StreamInfo; + + var orgOp = !streamInfo.IsDirectStream ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01"; - var orgCi = item.Transcode ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1"; + var orgCi = !streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1"; const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000"; string contentFeatures; - var container = item.Container.TrimStart('.'); + var container = streamInfo.Container.TrimStart('.'); if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase)) { @@ -488,7 +493,7 @@ namespace MediaBrowser.Dlna.PlayTo { contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; } - else if (item.MediaType == DlnaProfileType.Video) + else if (streamInfo.MediaType == DlnaProfileType.Video) { // Default to AVI for video contentFeatures = "DLNA.ORG_PN=AVI"; @@ -502,20 +507,38 @@ namespace MediaBrowser.Dlna.PlayTo return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); } - private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaStream> mediaStreams, DeviceProfile profile) + private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId) { var video = item as Video; if (video != null) { - return new PlaylistItemFactory().Create(video, mediaStreams, profile); + return new PlaylistItem + { + StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions + { + ItemId = item.Id.ToString("N"), + MediaSources = mediaSources, + Profile = profile, + DeviceId = deviceId + }) + }; } var audio = item as Audio; if (audio != null) { - return new PlaylistItemFactory().Create(audio, mediaStreams, profile); + return new PlaylistItem + { + StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions + { + ItemId = item.Id.ToString("N"), + MediaSources = mediaSources, + Profile = profile, + DeviceId = deviceId + }) + }; } var photo = item as Photo; @@ -578,8 +601,9 @@ namespace MediaBrowser.Dlna.PlayTo await _device.SetAvTransport(nextTrack.StreamUrl, dlnaheaders, nextTrack.Didl); - if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode) - await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks)); + var streamInfo = nextTrack.StreamInfo; + if (streamInfo.StartPositionTicks > 0 && streamInfo.IsDirectStream) + await _device.Seek(TimeSpan.FromTicks(streamInfo.StartPositionTicks)); return true; } @@ -602,7 +626,7 @@ namespace MediaBrowser.Dlna.PlayTo return Task.FromResult(false); prevTrack.PlayState = 1; - return _device.SetAvTransport(prevTrack.StreamUrl, GetDlnaHeaders(prevTrack), prevTrack.Didl); + return _device.SetAvTransport(prevTrack.StreamInfo.ToDlnaUrl(GetServerAddress()), GetDlnaHeaders(prevTrack), prevTrack.Didl); } #endregion diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index 5cc3f13e54..655a11693f 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -2,11 +2,11 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Dlna.Ssdp; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Session; using System; @@ -37,8 +37,9 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IDlnaManager _dlnaManager; private readonly IServerConfigurationManager _config; private readonly IServerApplicationHost _appHost; + private readonly IDtoService _dtoService; - public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost) + public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService) { _locations = new ConcurrentDictionary<string, DateTime>(); _tokenSource = new CancellationTokenSource(); @@ -52,6 +53,7 @@ namespace MediaBrowser.Dlna.PlayTo _userManager = userManager; _dlnaManager = dlnaManager; _appHost = appHost; + _dtoService = dtoService; _config = config; } @@ -248,7 +250,7 @@ namespace MediaBrowser.Dlna.PlayTo if (controller == null) { - sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost); + sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService); controller.Init(device); diff --git a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs index 91b03bc238..c0af1295fb 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; @@ -24,8 +25,9 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IUserManager _userManager; private readonly IDlnaManager _dlnaManager; private readonly IServerApplicationHost _appHost; + private readonly IDtoService _dtoService; - public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost) + public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService) { _config = config; _sessionManager = sessionManager; @@ -36,6 +38,7 @@ namespace MediaBrowser.Dlna.PlayTo _userManager = userManager; _dlnaManager = dlnaManager; _appHost = appHost; + _dtoService = dtoService; _logger = logManager.GetLogger("PlayTo"); } @@ -72,7 +75,18 @@ namespace MediaBrowser.Dlna.PlayTo { try { - _manager = new PlayToManager(_logger, _config, _sessionManager, _httpClient, _itemRepo, _libraryManager, _networkManager, _userManager, _dlnaManager, _appHost); + _manager = new PlayToManager(_logger, + _config, + _sessionManager, + _httpClient, + _itemRepo, + _libraryManager, + _networkManager, + _userManager, + _dlnaManager, + _appHost, + _dtoService); + _manager.Start(); } catch (Exception ex) diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs index be6e9a0c94..95167cfe92 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs @@ -4,45 +4,12 @@ namespace MediaBrowser.Dlna.PlayTo { public class PlaylistItem { - public string ItemId { get; set; } - - public string MediaSourceId { get; set; } - - public bool Transcode { get; set; } - - public DlnaProfileType MediaType { get; set; } - - public string Container { get; set; } - public int PlayState { get; set; } public string StreamUrl { get; set; } public string Didl { get; set; } - public long StartPositionTicks { get; set; } - - public string VideoCodec { get; set; } - - public string AudioCodec { get; set; } - - public int? AudioStreamIndex { get; set; } - - public int? SubtitleStreamIndex { get; set; } - - public int? MaxAudioChannels { get; set; } - - public int? AudioBitrate { get; set; } - - public int? VideoBitrate { get; set; } - - public int? VideoLevel { get; set; } - - public int? MaxWidth { get; set; } - public int? MaxHeight { get; set; } - - public int? MaxFramerate { get; set; } - - public string DeviceProfileId { get; set; } + public StreamInfo StreamInfo { get; set; } } }
\ No newline at end of file diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs index 34394fa32d..29d44bca0e 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs @@ -1,10 +1,6 @@ -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Entities; using System; -using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -15,235 +11,40 @@ namespace MediaBrowser.Dlna.PlayTo { private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public PlaylistItem Create(Audio item, List<MediaStream> mediaStreams, DeviceProfile profile) + public PlaylistItem Create(Photo item, DeviceProfile profile) { var playlistItem = new PlaylistItem { - ItemId = item.Id.ToString("N"), - MediaType = DlnaProfileType.Audio - }; - - var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - - var directPlay = profile.DirectPlayProfiles - .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, audioStream)); - - if (directPlay != null) - { - var audioCodec = audioStream == null ? null : audioStream.Codec; - - // Make sure audio codec profiles are satisfied - if (!string.IsNullOrEmpty(audioCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec)) - .All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream))) + StreamInfo = new StreamInfo { - playlistItem.Transcode = false; - playlistItem.Container = Path.GetExtension(item.Path); - - return playlistItem; + ItemId = item.Id.ToString("N"), + MediaType = DlnaProfileType.Photo, } - } - - var transcodingProfile = profile.TranscodingProfiles - .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); - - if (transcodingProfile != null) - { - playlistItem.Transcode = true; - playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); - playlistItem.AudioCodec = transcodingProfile.AudioCodec; - - var audioTranscodingConditions = profile.CodecProfiles - .Where(i => i.Type == CodecType.Audio && i.ContainsCodec(transcodingProfile.AudioCodec)) - .Take(1) - .SelectMany(i => i.Conditions); - - ApplyTranscodingConditions(playlistItem, audioTranscodingConditions); - } - - return playlistItem; - } - - public PlaylistItem Create(Photo item, DeviceProfile profile) - { - var playlistItem = new PlaylistItem - { - ItemId = item.Id.ToString("N"), - MediaType = DlnaProfileType.Photo }; var directPlay = profile.DirectPlayProfiles - .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item)); + .FirstOrDefault(i => i.Type == DlnaProfileType.Photo && IsSupported(i, item)); if (directPlay != null) { - playlistItem.Transcode = false; - playlistItem.Container = Path.GetExtension(item.Path); + playlistItem.StreamInfo.IsDirectStream = true; + playlistItem.StreamInfo.Container = Path.GetExtension(item.Path); return playlistItem; } var transcodingProfile = profile.TranscodingProfiles - .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); + .FirstOrDefault(i => i.Type == DlnaProfileType.Photo); if (transcodingProfile != null) { - playlistItem.Transcode = true; - playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); + playlistItem.StreamInfo.IsDirectStream = true; + playlistItem.StreamInfo.Container = "." + transcodingProfile.Container.TrimStart('.'); } return playlistItem; } - public PlaylistItem Create(Video item, List<MediaStream> mediaStreams, DeviceProfile profile) - { - var playlistItem = new PlaylistItem - { - ItemId = item.Id.ToString("N"), - MediaType = DlnaProfileType.Video - }; - - var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - - var directPlay = profile.DirectPlayProfiles - .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, videoStream, audioStream)); - - if (directPlay != null) - { - var videoCodec = videoStream == null ? null : videoStream.Codec; - - // Make sure video codec profiles are satisfied - if (!string.IsNullOrEmpty(videoCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Video && i.ContainsCodec(videoCodec)) - .All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream))) - { - var audioCodec = audioStream == null ? null : audioStream.Codec; - - // Make sure audio codec profiles are satisfied - if (string.IsNullOrEmpty(audioCodec) || profile.CodecProfiles.Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec)) - .All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream))) - { - playlistItem.Transcode = false; - playlistItem.Container = Path.GetExtension(item.Path); - - return playlistItem; - } - } - } - - var transcodingProfile = profile.TranscodingProfiles - .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); - - if (transcodingProfile != null) - { - playlistItem.Transcode = true; - playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); - playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',').FirstOrDefault(); - playlistItem.VideoCodec = transcodingProfile.VideoCodec; - - var videoTranscodingConditions = profile.CodecProfiles - .Where(i => i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec)) - .Take(1) - .SelectMany(i => i.Conditions); - - ApplyTranscodingConditions(playlistItem, videoTranscodingConditions); - - var audioTranscodingConditions = profile.CodecProfiles - .Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(transcodingProfile.AudioCodec)) - .Take(1) - .SelectMany(i => i.Conditions); - - ApplyTranscodingConditions(playlistItem, audioTranscodingConditions); - } - - return playlistItem; - } - - private void ApplyTranscodingConditions(PlaylistItem item, IEnumerable<ProfileCondition> conditions) - { - foreach (var condition in conditions - .Where(i => !string.IsNullOrEmpty(i.Value))) - { - var value = condition.Value; - - switch (condition.Property) - { - case ProfileConditionValue.AudioBitrate: - { - int num; - if (int.TryParse(value, NumberStyles.Any, _usCulture, out num)) - { - item.AudioBitrate = num; - } - break; - } - case ProfileConditionValue.AudioChannels: - { - int num; - if (int.TryParse(value, NumberStyles.Any, _usCulture, out num)) - { - item.MaxAudioChannels = num; - } - break; - } - case ProfileConditionValue.AudioProfile: - case ProfileConditionValue.Has64BitOffsets: - case ProfileConditionValue.VideoBitDepth: - case ProfileConditionValue.VideoProfile: - { - // Not supported yet - break; - } - case ProfileConditionValue.Height: - { - int num; - if (int.TryParse(value, NumberStyles.Any, _usCulture, out num)) - { - item.MaxHeight = num; - } - break; - } - case ProfileConditionValue.VideoBitrate: - { - int num; - if (int.TryParse(value, NumberStyles.Any, _usCulture, out num)) - { - item.VideoBitrate = num; - } - break; - } - case ProfileConditionValue.VideoFramerate: - { - int num; - if (int.TryParse(value, NumberStyles.Any, _usCulture, out num)) - { - item.MaxFramerate = num; - } - break; - } - case ProfileConditionValue.VideoLevel: - { - int num; - if (int.TryParse(value, NumberStyles.Any, _usCulture, out num)) - { - item.VideoLevel = num; - } - break; - } - case ProfileConditionValue.Width: - { - int num; - if (int.TryParse(value, NumberStyles.Any, _usCulture, out num)) - { - item.MaxWidth = num; - } - break; - } - default: - throw new ArgumentException("Unrecognized ProfileConditionValue"); - } - } - } - private bool IsSupported(DirectPlayProfile profile, Photo item) { var mediaPath = item.Path; @@ -260,223 +61,5 @@ namespace MediaBrowser.Dlna.PlayTo return true; } - - private bool IsSupported(DirectPlayProfile profile, Audio item, MediaStream audioStream) - { - var mediaPath = item.Path; - - if (profile.Container.Length > 0) - { - // Check container type - var mediaContainer = Path.GetExtension(mediaPath); - if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - - return true; - } - - private bool IsSupported(DirectPlayProfile profile, Video item, MediaStream videoStream, MediaStream audioStream) - { - if (item.VideoType != VideoType.VideoFile) - { - return false; - } - - var mediaPath = item.Path; - - if (profile.Container.Length > 0) - { - // Check container type - var mediaContainer = Path.GetExtension(mediaPath); - if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - - // Check video codec - var videoCodecs = profile.GetVideoCodecs(); - if (videoCodecs.Count > 0) - { - var videoCodec = videoStream == null ? null : videoStream.Codec; - if (string.IsNullOrWhiteSpace(videoCodec) || !videoCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - - var audioCodecs = profile.GetAudioCodecs(); - if (audioCodecs.Count > 0) - { - // Check audio codecs - var audioCodec = audioStream == null ? null : audioStream.Codec; - if (string.IsNullOrWhiteSpace(audioCodec) || !audioCodecs.Contains(audioCodec, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - - return true; - } - - private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Audio item) - { - // Placeholder for future conditions - return true; - } - - private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Photo item) - { - // Placeholder for future conditions - return true; - } - - private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Video item) - { - // Placeholder for future conditions - return true; - } - - private bool AreConditionsSatisfied(IEnumerable<ProfileCondition> conditions, string mediaPath, MediaStream videoStream, MediaStream audioStream) - { - return conditions.All(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream)); - } - - /// <summary> - /// Determines whether [is condition satisfied] [the specified condition]. - /// </summary> - /// <param name="condition">The condition.</param> - /// <param name="mediaPath">The media path.</param> - /// <param name="videoStream">The video stream.</param> - /// <param name="audioStream">The audio stream.</param> - /// <returns><c>true</c> if [is condition satisfied] [the specified condition]; otherwise, <c>false</c>.</returns> - /// <exception cref="System.InvalidOperationException">Unexpected ProfileConditionType</exception> - private bool IsConditionSatisfied(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) - { - if (condition.Property == ProfileConditionValue.Has64BitOffsets) - { - // TODO: Determine how to evaluate this - } - - if (condition.Property == ProfileConditionValue.VideoProfile) - { - var profile = videoStream == null ? null : videoStream.Profile; - - if (!string.IsNullOrWhiteSpace(profile)) - { - switch (condition.Condition) - { - case ProfileConditionType.Equals: - return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); - case ProfileConditionType.NotEquals: - return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); - default: - throw new InvalidOperationException("Unexpected ProfileConditionType"); - } - } - } - - else if (condition.Property == ProfileConditionValue.AudioProfile) - { - var profile = audioStream == null ? null : audioStream.Profile; - - if (!string.IsNullOrWhiteSpace(profile)) - { - switch (condition.Condition) - { - case ProfileConditionType.Equals: - return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); - case ProfileConditionType.NotEquals: - return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); - default: - throw new InvalidOperationException("Unexpected ProfileConditionType"); - } - } - } - - else - { - var actualValue = GetConditionValue(condition, mediaPath, videoStream, audioStream); - - if (actualValue.HasValue) - { - long expected; - if (long.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected)) - { - switch (condition.Condition) - { - case ProfileConditionType.Equals: - return actualValue.Value == expected; - case ProfileConditionType.GreaterThanEqual: - return actualValue.Value >= expected; - case ProfileConditionType.LessThanEqual: - return actualValue.Value <= expected; - case ProfileConditionType.NotEquals: - return actualValue.Value != expected; - default: - throw new InvalidOperationException("Unexpected ProfileConditionType"); - } - } - } - } - - // Value doesn't exist in metadata. Fail it if required. - return !condition.IsRequired; - } - - /// <summary> - /// Gets the condition value. - /// </summary> - /// <param name="condition">The condition.</param> - /// <param name="mediaPath">The media path.</param> - /// <param name="videoStream">The video stream.</param> - /// <param name="audioStream">The audio stream.</param> - /// <returns>System.Nullable{System.Int64}.</returns> - /// <exception cref="System.InvalidOperationException">Unexpected Property</exception> - private long? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) - { - switch (condition.Property) - { - case ProfileConditionValue.AudioBitrate: - return audioStream == null ? null : audioStream.BitRate; - case ProfileConditionValue.AudioChannels: - return audioStream == null ? null : audioStream.Channels; - case ProfileConditionValue.VideoBitrate: - return videoStream == null ? null : videoStream.BitRate; - case ProfileConditionValue.VideoFramerate: - return videoStream == null ? null : (ConvertToLong(videoStream.AverageFrameRate ?? videoStream.RealFrameRate)); - case ProfileConditionValue.Height: - return videoStream == null ? null : videoStream.Height; - case ProfileConditionValue.Width: - return videoStream == null ? null : videoStream.Width; - case ProfileConditionValue.VideoLevel: - return videoStream == null ? null : ConvertToLong(videoStream.Level); - default: - throw new InvalidOperationException("Unexpected Property"); - } - } - - /// <summary> - /// Converts to long. - /// </summary> - /// <param name="val">The value.</param> - /// <returns>System.Nullable{System.Int64}.</returns> - private long? ConvertToLong(float? val) - { - return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null; - } - - /// <summary> - /// Converts to long. - /// </summary> - /// <param name="val">The value.</param> - /// <returns>System.Nullable{System.Int64}.</returns> - private long? ConvertToLong(double? val) - { - return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null; - } } } diff --git a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs deleted file mode 100644 index b65e94fd11..0000000000 --- a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs +++ /dev/null @@ -1,69 +0,0 @@ -using MediaBrowser.Model.Entities; -using System.Collections.Generic; -using System.Globalization; - -namespace MediaBrowser.Dlna.PlayTo -{ - class StreamHelper - { - /// <summary> - /// Gets the audio URL. - /// </summary> - /// <param name="deviceProperties">The device properties.</param> - /// <param name="item">The item.</param> - /// <param name="streams">The streams.</param> - /// <param name="serverAddress">The server address.</param> - /// <returns>System.String.</returns> - internal static string GetAudioUrl(DeviceInfo deviceProperties, PlaylistItem item, List<MediaStream> streams, string serverAddress) - { - var dlnaCommand = BuildDlnaUrl(deviceProperties, item); - - return string.Format("{0}/audio/{1}/stream{2}?{3}", serverAddress, item.ItemId, "." + item.Container.TrimStart('.'), dlnaCommand); - } - - /// <summary> - /// Gets the video URL. - /// </summary> - /// <param name="deviceProperties">The device properties.</param> - /// <param name="item">The item.</param> - /// <param name="streams">The streams.</param> - /// <param name="serverAddress">The server address.</param> - /// <returns>The url to send to the device</returns> - internal static string GetVideoUrl(DeviceInfo deviceProperties, PlaylistItem item, List<MediaStream> streams, string serverAddress) - { - var dlnaCommand = BuildDlnaUrl(deviceProperties, item); - - return string.Format("{0}/Videos/{1}/stream{2}?{3}", serverAddress, item.ItemId, item.Container, dlnaCommand); - } - - /// <summary> - /// Builds the dlna URL. - /// </summary> - private static string BuildDlnaUrl(DeviceInfo deviceProperties, PlaylistItem item) - { - var usCulture = new CultureInfo("en-US"); - - var list = new List<string> - { - item.DeviceProfileId ?? string.Empty, - deviceProperties.UUID ?? string.Empty, - item.MediaSourceId ?? string.Empty, - (!item.Transcode).ToString().ToLower(), - item.VideoCodec ?? string.Empty, - item.AudioCodec ?? string.Empty, - item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(usCulture) : string.Empty, - item.SubtitleStreamIndex.HasValue ? item.SubtitleStreamIndex.Value.ToString(usCulture) : string.Empty, - item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(usCulture) : string.Empty, - item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(usCulture) : string.Empty, - item.MaxAudioChannels.HasValue ? item.MaxAudioChannels.Value.ToString(usCulture) : string.Empty, - item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(usCulture) : string.Empty, - item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(usCulture) : string.Empty, - item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(usCulture) : string.Empty, - item.StartPositionTicks.ToString(usCulture), - item.VideoLevel.HasValue ? item.VideoLevel.Value.ToString(usCulture) : string.Empty - }; - - return string.Format("Params={0}", string.Join(";", list.ToArray())); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Dlna/Server/ControlHandler.cs b/MediaBrowser.Dlna/Server/ControlHandler.cs index d05062f383..c3a3c0bf00 100644 --- a/MediaBrowser.Dlna/Server/ControlHandler.cs +++ b/MediaBrowser.Dlna/Server/ControlHandler.cs @@ -1,15 +1,23 @@ using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; +using System.Threading; using System.Xml; namespace MediaBrowser.Dlna.Server @@ -19,7 +27,12 @@ namespace MediaBrowser.Dlna.Server private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; - private DeviceProfile _profile; + private readonly DeviceProfile _profile; + private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; + private readonly IUserDataManager _userDataManager; + + private readonly string _serverAddress; private const string NS_DC = "http://purl.org/dc/elements/1.1/"; private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; @@ -31,11 +44,16 @@ namespace MediaBrowser.Dlna.Server private int systemID = 0; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public ControlHandler(ILogger logger, IUserManager userManager, ILibraryManager libraryManager) + public ControlHandler(ILogger logger, IUserManager userManager, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager) { _logger = logger; _userManager = userManager; _libraryManager = libraryManager; + _profile = profile; + _serverAddress = serverAddress; + _dtoService = dtoService; + _imageProcessor = imageProcessor; + _userDataManager = userDataManager; } public ControlResponse ProcessControlRequest(ControlRequest request) @@ -46,6 +64,8 @@ namespace MediaBrowser.Dlna.Server } catch (Exception ex) { + _logger.ErrorException("Error processing control request", ex); + return GetErrorResponse(ex); } } @@ -120,7 +140,8 @@ namespace MediaBrowser.Dlna.Server var controlResponse = new ControlResponse { - Xml = env.OuterXml + Xml = env.OuterXml, + IsSuccessful = true }; controlResponse.Headers.Add("EXT", string.Empty); @@ -153,7 +174,8 @@ namespace MediaBrowser.Dlna.Server return new ControlResponse { - Xml = env.OuterXml + Xml = env.OuterXml, + IsSuccessful = false }; } @@ -161,7 +183,16 @@ namespace MediaBrowser.Dlna.Server { var id = sparams["ObjectID"]; - var newbookmark = long.Parse(sparams["PosSecond"]); + var item = _libraryManager.GetItemById(new Guid(id)); + + var newbookmark = int.Parse(sparams["PosSecond"], _usCulture); + + var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + + userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks; + + _userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed, + CancellationToken.None); return new Headers(); } @@ -209,13 +240,13 @@ namespace MediaBrowser.Dlna.Server var id = sparams["ObjectID"]; var flag = sparams["BrowseFlag"]; - int requested = 20; var provided = 0; + int requested = 0; int start = 0; if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0) { - requested = 20; + requested = 0; } if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0) { @@ -232,11 +263,11 @@ namespace MediaBrowser.Dlna.Server didl.SetAttribute("xmlns:sec", NS_SEC); result.AppendChild(didl); - var folder = string.IsNullOrWhiteSpace(id) + var folder = string.IsNullOrWhiteSpace(id) || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase) ? user.RootFolder : (Folder)_libraryManager.GetItemById(new Guid(id)); - var children = folder.GetChildren(user, true).ToList(); + var children = GetChildrenSorted(folder, user).ToList(); if (string.Equals(flag, "BrowseMetadata")) { @@ -245,40 +276,29 @@ namespace MediaBrowser.Dlna.Server } else { - foreach (var i in children.OfType<Folder>()) + if (start > 0) { - if (start > 0) - { - start--; - continue; - } - - var childCount = i.GetChildren(user, true).Count(); - - Browse_AddFolder(result, i, childCount); - - if (++provided == requested) - { - break; - } + children = children.Skip(start).ToList(); + } + if (requested > 0) + { + children = children.Take(requested).ToList(); } - if (provided != requested) + provided = children.Count; + + foreach (var i in children) { - foreach (var i in children.Where(i => !i.IsFolder)) + if (i.IsFolder) { - if (start > 0) - { - start--; - continue; - } + var f = (Folder)i; + var childCount = GetChildrenSorted(f, user).Count(); + Browse_AddFolder(result, f, childCount); + } + else + { Browse_AddItem(result, i, user); - - if (++provided == requested) - { - break; - } } } } @@ -294,10 +314,23 @@ namespace MediaBrowser.Dlna.Server }; } + private IEnumerable<BaseItem> GetChildrenSorted(Folder folder, User user) + { + var children = folder.GetChildren(user, true).Where(i => i.LocationType != LocationType.Virtual); + + if (folder is Series || folder is Season || folder is BoxSet) + { + return children; + } + + return _libraryManager.Sort(children, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending); + } + private void Browse_AddFolder(XmlDocument result, Folder f, int childCount) { var container = result.CreateElement(string.Empty, "container", NS_DIDL); container.SetAttribute("restricted", "0"); + container.SetAttribute("searchable", "1"); container.SetAttribute("childCount", childCount.ToString(_usCulture)); container.SetAttribute("id", f.Id.ToString("N")); @@ -311,20 +344,28 @@ namespace MediaBrowser.Dlna.Server container.SetAttribute("parentID", parent.Id.ToString("N")); } - var title = result.CreateElement("dc", "title", NS_DC); - title.InnerText = f.Name; - container.AppendChild(title); + AddCommonFields(f, container); - var date = result.CreateElement("dc", "date", NS_DC); - date.InnerText = f.DateModified.ToString("o"); - container.AppendChild(date); + AddCover(f, container); - var objectClass = result.CreateElement("upnp", "class", NS_UPNP); - objectClass.InnerText = "object.container.storageFolder"; - container.AppendChild(objectClass); + container.AppendChild(CreateObjectClass(result, f)); result.DocumentElement.AppendChild(container); } + private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri) + { + try + { + var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri); + date.InnerText = value; + elem.AppendChild(date); + } + catch (XmlException) + { + //_logger.Error("Error adding xml value: " + value); + } + } + private void Browse_AddItem(XmlDocument result, BaseItem item, User user) { var element = result.CreateElement(string.Empty, "item", NS_DIDL); @@ -342,52 +383,222 @@ namespace MediaBrowser.Dlna.Server AddGeneralProperties(item, element); - AddActors(item, element); + // refID? + // storeAttribute(itemNode, object, ClassProperties.REF_ID, false); - var title = result.CreateElement("dc", "title", NS_DC); - title.InnerText = item.Name; - element.AppendChild(title); - - var res = result.CreateElement(string.Empty, "res", NS_DIDL); - - //res.InnerText = String.Format( - // "http://{0}:{1}{2}file/{3}", - // request.LocalEndPoint.Address, - // request.LocalEndPoint.Port, - // prefix, - // resource.Id - // ); - - //if (props.TryGetValue("SizeRaw", out prop)) - //{ - // res.SetAttribute("size", prop); - //} - //if (props.TryGetValue("Resolution", out prop)) - //{ - // res.SetAttribute("resolution", prop); - //} - //if (props.TryGetValue("Duration", out prop)) - //{ - // res.SetAttribute("duration", prop); - //} - - //res.SetAttribute("protocolInfo", String.Format( - // "http-get:*:{1}:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}", - // resource.PN, DlnaMaps.Mime[resource.Type], DlnaMaps.DefaultStreaming - // )); + var audio = item as Audio; + if (audio != null) + { + AddAudioResource(element, audio); + } - element.AppendChild(res); + var video = item as Video; + if (video != null) + { + AddVideoResource(element, video); + } AddCover(item, element); result.DocumentElement.AppendChild(element); } + private string GetDeviceId() + { + return "erer"; + } + + private void AddVideoResource(XmlElement container, Video video) + { + var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); + + var sources = _dtoService.GetMediaSources(video); + + int? maxBitrateSetting = null; + + var streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions + { + ItemId = video.Id.ToString("N"), + MediaSources = sources, + Profile = _profile, + DeviceId = GetDeviceId(), + MaxBitrate = maxBitrateSetting + }); + + var url = streamInfo.ToDlnaUrl(_serverAddress); + res.InnerText = url; + + var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId)); + + if (mediaSource.RunTimeTicks.HasValue) + { + res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + } + + if (streamInfo.IsDirectStream && mediaSource.Size.HasValue) + { + res.SetAttribute("size", mediaSource.Size.Value.ToString(_usCulture)); + } + + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video && !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)); + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + var targetAudioBitrate = streamInfo.AudioBitrate ?? (audioStream == null ? null : audioStream.BitRate); + var targetSampleRate = audioStream == null ? null : audioStream.SampleRate; + var targetChannels = streamInfo.MaxAudioChannels ?? (audioStream == null ? null : audioStream.Channels); + + var targetWidth = streamInfo.MaxWidth ?? (videoStream == null ? null : videoStream.Width); + var targetHeight = streamInfo.MaxHeight ?? (videoStream == null ? null : videoStream.Height); + + var targetVideoCodec = streamInfo.IsDirectStream + ? (videoStream == null ? null : videoStream.Codec) + : streamInfo.VideoCodec; + + var targetAudioCodec = streamInfo.IsDirectStream + ? (audioStream == null ? null : audioStream.Codec) + : streamInfo.AudioCodec; + + var targetBitrate = maxBitrateSetting ?? mediaSource.Bitrate; + + if (targetChannels.HasValue) + { + res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + } + + if (targetWidth.HasValue && targetHeight.HasValue) + { + res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value)); + } + + if (targetSampleRate.HasValue) + { + res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + } + + if (targetAudioBitrate.HasValue) + { + res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); + } + + var formatProfile = new MediaFormatProfileResolver().ResolveVideoFormat(streamInfo.Container, + targetVideoCodec, + targetAudioCodec, + targetWidth, + targetHeight, + targetBitrate, + TransportStreamTimestamp.NONE); + + var filename = url.Substring(0, url.IndexOf('?')); + + var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo); + + var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1"; + + res.SetAttribute("protocolInfo", String.Format( + "http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}", + MimeTypes.GetMimeType(filename), + formatProfile, + orgOpValue, + orgCi, + DlnaMaps.DefaultStreaming + )); + + container.AppendChild(res); + } + + private void AddAudioResource(XmlElement container, Audio audio) + { + var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); + + var sources = _dtoService.GetMediaSources(audio); + + var streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions + { + ItemId = audio.Id.ToString("N"), + MediaSources = sources, + Profile = _profile, + DeviceId = GetDeviceId() + }); + + var url = streamInfo.ToDlnaUrl(_serverAddress); + res.InnerText = url; + + var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId)); + + if (mediaSource.RunTimeTicks.HasValue) + { + res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + } + + if (streamInfo.IsDirectStream && mediaSource.Size.HasValue) + { + res.SetAttribute("size", mediaSource.Size.Value.ToString(_usCulture)); + } + + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + var targetAudioBitrate = streamInfo.AudioBitrate ?? (audioStream == null ? null : audioStream.BitRate); + var targetSampleRate = audioStream == null ? null : audioStream.SampleRate; + var targetChannels = streamInfo.MaxAudioChannels ?? (audioStream == null ? null : audioStream.Channels); + + if (targetChannels.HasValue) + { + res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + } + + if (targetSampleRate.HasValue) + { + res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + } + + if (targetAudioBitrate.HasValue) + { + res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); + } + + var formatProfile = new MediaFormatProfileResolver().ResolveAudioFormat(streamInfo.Container, targetAudioBitrate, targetSampleRate, targetChannels); + + var filename = url.Substring(0, url.IndexOf('?')); + + var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo); + + var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1"; + + res.SetAttribute("protocolInfo", String.Format( + "http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}", + MimeTypes.GetMimeType(filename), + formatProfile, + orgOpValue, + orgCi, + DlnaMaps.DefaultStreaming + )); + + container.AppendChild(res); + } + private XmlElement CreateObjectClass(XmlDocument result, BaseItem item) { var objectClass = result.CreateElement("upnp", "class", NS_UPNP); - if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + if (item.IsFolder) + { + string classType = null; + + if (!_profile.RequiresPlainFolders) + { + if (item is MusicAlbum) + { + classType = "object.container.musicAlbum"; + } + if (item is MusicArtist) + { + classType = "object.container.musicArtist"; + } + } + + objectClass.InnerText = classType ?? "object.container.storageFolder"; + } + else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { objectClass.InnerText = "object.item.audioItem.musicTrack"; } @@ -397,7 +608,14 @@ namespace MediaBrowser.Dlna.Server } else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { - objectClass.InnerText = "object.item.videoItem.movie"; + if (!_profile.RequiresPlainVideoItems && item is Movie) + { + objectClass.InnerText = "object.item.videoItem.movie"; + } + else + { + objectClass.InnerText = "object.item.videoItem"; + } } else { @@ -407,152 +625,259 @@ namespace MediaBrowser.Dlna.Server return objectClass; } - private void AddActors(BaseItem item, XmlElement element) + private void AddPeople(BaseItem item, XmlElement element) { foreach (var actor in item.People) { - var e = element.OwnerDocument.CreateElement("upnp", "actor", NS_UPNP); - e.InnerText = actor.Name; - element.AppendChild(e); + AddValue(element, "upnp", (actor.Type ?? PersonType.Actor).ToLower(), actor.Name, NS_UPNP); } } private void AddBookmarkInfo(BaseItem item, User user, XmlElement element) { - //var bookmark = bookmarkable.Bookmark; - //if (bookmark.HasValue) - //{ - // var dcmInfo = item.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC); - // dcmInfo.InnerText = string.Format("BM={0}", bookmark.Value); - // item.AppendChild(dcmInfo); - //} + var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + + if (userdata.PlaybackPositionTicks > 0) + { + var dcmInfo = element.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC); + dcmInfo.InnerText = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture)); + element.AppendChild(dcmInfo); + } } - private void AddGeneralProperties(BaseItem item, XmlElement element) + /// <summary> + /// Adds fields used by both items and folders + /// </summary> + /// <param name="item"></param> + /// <param name="element"></param> + private void AddCommonFields(BaseItem item, XmlElement element) { - //var prop = string.Empty; - //if (props.TryGetValue("DateO", out prop)) - //{ - // var e = item.OwnerDocument.CreateElement("dc", "date", NS_DC); - // e.InnerText = prop; - // item.AppendChild(e); - //} - //if (props.TryGetValue("Genre", out prop)) - //{ - // var e = item.OwnerDocument.CreateElement("upnp", "genre", NS_UPNP); - // e.InnerText = prop; - // item.AppendChild(e); - //} + if (item.PremiereDate.HasValue) + { + AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC); + } + + if (item.Genres.Count > 0) + { + AddValue(element, "upnp", "genre", item.Genres[0], NS_UPNP); + } + + if (item.Studios.Count > 0) + { + AddValue(element, "upnp", "publisher", item.Studios[0], NS_UPNP); + } + + AddValue(element, "dc", "title", item.Name, NS_DC); if (!string.IsNullOrWhiteSpace(item.Overview)) { - var e = element.OwnerDocument.CreateElement("dc", "description", NS_DC); - e.InnerText = item.Overview; - element.AppendChild(e); - } - - //if (props.TryGetValue("Artist", out prop)) - //{ - // var e = item.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP); - // e.SetAttribute("role", "AlbumArtist"); - // e.InnerText = prop; - // item.AppendChild(e); - //} - //if (props.TryGetValue("Performer", out prop)) - //{ - // var e = item.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP); - // e.SetAttribute("role", "Performer"); - // e.InnerText = prop; - // item.AppendChild(e); - // e = item.OwnerDocument.CreateElement("dc", "creator", NS_DC); - // e.InnerText = prop; - // item.AppendChild(e); - //} - //if (props.TryGetValue("Album", out prop)) - //{ - // var e = item.OwnerDocument.CreateElement("upnp", "album", NS_UPNP); - // e.InnerText = prop; - // item.AppendChild(e); - //} - //if (props.TryGetValue("Track", out prop)) - //{ - // var e = item.OwnerDocument.CreateElement("upnp", "originalTrackNumber", NS_UPNP); - // e.InnerText = prop; - // item.AppendChild(e); - //} - //if (props.TryGetValue("Creator", out prop)) - //{ - // var e = item.OwnerDocument.CreateElement("dc", "creator", NS_DC); - // e.InnerText = prop; - // item.AppendChild(e); - //} - - //if (props.TryGetValue("Director", out prop)) - //{ - // var e = item.OwnerDocument.CreateElement("upnp", "director", NS_UPNP); - // e.InnerText = prop; - // item.AppendChild(e); - //} + AddValue(element, "dc", "description", item.Overview, NS_DC); + } + + if (!string.IsNullOrEmpty(item.OfficialRating)) + { + AddValue(element, "dc", "rating", item.OfficialRating, NS_DC); + } + + AddPeople(item, element); + } + + private void AddGeneralProperties(BaseItem item, XmlElement element) + { + AddCommonFields(item, element); + + var audio = item as Audio; + + if (audio != null) + { + if (audio.Artists.Count > 0) + { + AddValue(element, "upnp", "artist", audio.Artists[0], NS_UPNP); + } + + if (!string.IsNullOrEmpty(audio.Album)) + { + AddValue(element, "upnp", "album", audio.Album, NS_UPNP); + } + + if (!string.IsNullOrEmpty(audio.AlbumArtist)) + { + AddValue(element, "upnp", "albumArtist", audio.AlbumArtist, NS_UPNP); + } + } + + var album = item as MusicAlbum; + + if (album != null) + { + if (!string.IsNullOrEmpty(album.AlbumArtist)) + { + AddValue(element, "upnp", "artist", album.AlbumArtist, NS_UPNP); + AddValue(element, "upnp", "albumArtist", album.AlbumArtist, NS_UPNP); + } + } + + var musicVideo = item as MusicVideo; + + if (musicVideo != null) + { + if (!string.IsNullOrEmpty(musicVideo.Artist)) + { + AddValue(element, "upnp", "artist", musicVideo.Artist, NS_UPNP); + } + + if (!string.IsNullOrEmpty(musicVideo.Album)) + { + AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP); + } + } + + if (item.IndexNumber.HasValue) + { + AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP); + } } private void AddCover(BaseItem item, XmlElement element) { - //var result = item.OwnerDocument; - //var cover = resource as IMediaCover; - //if (cover == null) - //{ - // return; - //} - //try - //{ - // var c = cover.Cover; - // var curl = String.Format( - // "http://{0}:{1}{2}cover/{3}", - // request.LocalEndPoint.Address, - // request.LocalEndPoint.Port, - // prefix, - // resource.Id - // ); - // var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP); - // var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA); - // profile.InnerText = "JPEG_TN"; - // icon.SetAttributeNode(profile); - // icon.InnerText = curl; - // item.AppendChild(icon); - // icon = result.CreateElement("upnp", "icon", NS_UPNP); - // profile = result.CreateAttribute("dlna", "profileID", NS_DLNA); - // profile.InnerText = "JPEG_TN"; - // icon.SetAttributeNode(profile); - // icon.InnerText = curl; - // item.AppendChild(icon); - - // var res = result.CreateElement(string.Empty, "res", NS_DIDL); - // res.InnerText = curl; - - // res.SetAttribute("protocolInfo", string.Format( - // "http-get:*:{1}:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}", - // c.PN, DlnaMaps.Mime[c.Type], DlnaMaps.DefaultStreaming - // )); - // var width = c.MetaWidth; - // var height = c.MetaHeight; - // if (width.HasValue && height.HasValue) - // { - // res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value)); - // } - // else - // { - // res.SetAttribute("resolution", "200x200"); - // } - // res.SetAttribute("protocolInfo", string.Format( - // "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_CI=1;DLNA.ORG_FLAGS={0}", - // DlnaMaps.DefaultInteractive - // )); - // item.AppendChild(res); - //} - //catch (Exception) - //{ - // return; - //} + var imageInfo = GetImageInfo(item); + + if (imageInfo == null) + { + return; + } + + var result = element.OwnerDocument; + + var curl = GetImageUrl(imageInfo); + + var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP); + var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA); + profile.InnerText = "JPEG_TN"; + icon.SetAttributeNode(profile); + icon.InnerText = curl; + element.AppendChild(icon); + + icon = result.CreateElement("upnp", "icon", NS_UPNP); + profile = result.CreateAttribute("dlna", "profileID", NS_DLNA); + profile.InnerText = "JPEG_TN"; + icon.SetAttributeNode(profile); + icon.InnerText = curl; + element.AppendChild(icon); + + if (!_profile.EnableAlbumArtInDidl) + { + return; + } + + var res = result.CreateElement(string.Empty, "res", NS_DIDL); + res.InnerText = curl; + + int? width = imageInfo.Width; + int? height = imageInfo.Height; + + var mediaProfile = new MediaFormatProfileResolver().ResolveImageFormat("jpg", width, height); + + res.SetAttribute("protocolInfo", string.Format( + "http-get:*:{1}DLNA.ORG_PN=:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}", + mediaProfile, "image/jpeg", DlnaMaps.DefaultStreaming + )); + + if (width.HasValue && height.HasValue) + { + res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value)); + } + else + { + // TODO: Devices need to see something here? + res.SetAttribute("resolution", "200x200"); + } + + element.AppendChild(res); + } + + private ImageDownloadInfo GetImageInfo(BaseItem item) + { + if (item.HasImage(ImageType.Primary)) + { + return GetImageInfo(item, ImageType.Primary); + } + if (item.HasImage(ImageType.Thumb)) + { + return GetImageInfo(item, ImageType.Thumb); + } + + if (item is Audio || item is Episode) + { + item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary)); + + if (item != null) + { + return GetImageInfo(item, ImageType.Primary); + } + } + + return null; + } + + private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type) + { + var imageInfo = item.GetImageInfo(type, 0); + string tag = null; + + try + { + var guid = _imageProcessor.GetImageCacheTag(item, ImageType.Primary); + + tag = guid.HasValue ? guid.Value.ToString("N") : null; + } + catch + { + + } + + int? width = null; + int? height = null; + + try + { + var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified); + + width = Convert.ToInt32(size.Width); + height = Convert.ToInt32(size.Height); + } + catch + { + + } + + return new ImageDownloadInfo + { + ItemId = item.Id.ToString("N"), + Type = ImageType.Primary, + ImageTag = tag, + Width = width, + Height = height + }; + } + + class ImageDownloadInfo + { + internal string ItemId; + internal string ImageTag; + internal ImageType Type; + + internal int? Width; + internal int? Height; + } + + private string GetImageUrl(ImageDownloadInfo info) + { + return string.Format("{0}/Items/{1}/Images/{2}?tag={3}&format=jpg", + _serverAddress, + info.ItemId, + info.Type, + info.ImageTag); } } } diff --git a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs index ee7fae6f93..33286a474d 100644 --- a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs +++ b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs @@ -176,7 +176,7 @@ namespace MediaBrowser.Dlna.Server ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1", ServiceId = "urn:upnp-org:serviceId:ContentDirectory", ScpdUrl = "/mediabrowser/dlna/contentdirectory.xml", - ControlUrl = "/mediabrowser/dlna/control" + ControlUrl = "/mediabrowser/dlna/" + _serverUdn + "/control" }); return list; diff --git a/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs b/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs index 702aa6f995..050d89f496 100644 --- a/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs +++ b/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common; +using System.Linq; +using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -18,8 +19,12 @@ namespace MediaBrowser.Dlna.Server private readonly IApplicationHost _appHost; private readonly INetworkManager _network; + public static DlnaServerEntryPoint Instance; + public DlnaServerEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network) { + Instance = this; + _config = config; _appHost = appHost; _network = network; @@ -86,6 +91,11 @@ namespace MediaBrowser.Dlna.Server } } + public UpnpDevice GetServerUpnpDevice(string uuid) + { + return _ssdpHandler.Devices.FirstOrDefault(i => string.Equals(uuid, i.Uuid.ToString("N"), StringComparison.OrdinalIgnoreCase)); + } + private void DisposeServer() { lock (_syncLock) diff --git a/MediaBrowser.Dlna/Server/SsdpHandler.cs b/MediaBrowser.Dlna/Server/SsdpHandler.cs index c908eb4a8e..0c3e6c735b 100644 --- a/MediaBrowser.Dlna/Server/SsdpHandler.cs +++ b/MediaBrowser.Dlna/Server/SsdpHandler.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Dlna.Server Start(); } - private IEnumerable<UpnpDevice> Devices + public IEnumerable<UpnpDevice> Devices { get { |
