diff --git a/MediaBrowser.Controller/Dlna/CodecProfile.cs b/MediaBrowser.Controller/Dlna/CodecProfile.cs index bff3742988..a4592e654a 100644 --- a/MediaBrowser.Controller/Dlna/CodecProfile.cs +++ b/MediaBrowser.Controller/Dlna/CodecProfile.cs @@ -32,6 +32,12 @@ namespace MediaBrowser.Controller.Dlna public ProfileConditionType Condition { get; set; } public ProfileConditionValue Property { get; set; } public string Value { get; set; } + public bool IsRequired { get; set; } + + public ProfileCondition() + { + IsRequired = true; + } } public enum ProfileConditionType @@ -46,11 +52,14 @@ namespace MediaBrowser.Controller.Dlna { AudioChannels, AudioBitrate, + AudioProfile, Filesize, Width, Height, + Has64BitOffsets, VideoBitrate, VideoFramerate, - VideoLevel + VideoLevel, + VideoProfile } } diff --git a/MediaBrowser.Controller/Dlna/DeviceProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs index 91be73bba8..49568e7d43 100644 --- a/MediaBrowser.Controller/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Controller/Dlna/DeviceProfile.cs @@ -60,6 +60,9 @@ namespace MediaBrowser.Controller.Dlna public int TimelineOffsetSeconds { get; set; } + public bool RequiresPlainVideoItems { get; set; } + public bool RequiresPlainFolders { get; set; } + public DeviceProfile() { DirectPlayProfiles = new DirectPlayProfile[] { }; diff --git a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs index 530a44b8ca..1073f74aaf 100644 --- a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs @@ -11,6 +11,10 @@ namespace MediaBrowser.Controller.Dlna public string VideoCodec { get; set; } public string AudioCodec { get; set; } + public bool EstimateContentLength { get; set; } + + public TranscodeSeekInfo TranscodeSeekInfo { get; set; } + public List Settings { get; set; } public TranscodingProfile() @@ -27,6 +31,13 @@ namespace MediaBrowser.Controller.Dlna public enum TranscodingSettingType { - Profile + Profile = 0, + MaxAudioChannels = 1 + } + + public enum TranscodeSeekInfo + { + Auto = 0, + Bytes = 1 } } diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index be7295e12f..24e9d83a03 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -233,9 +233,26 @@ namespace MediaBrowser.Dlna Name = "Xbox 360", ClientType = "DLNA", + ModelName = "Windows Media Player Sharing", + ModelNumber = "12.0", + ModelUrl = "http://www.microsoft.com/", + Manufacturer = "Microsoft Corporation", + ManufacturerUrl = "http://www.microsoft.com/", + XDlnaDoc = "DMS-1.50", + + TimelineOffsetSeconds = 40, + RequiresPlainFolders = true, + RequiresPlainVideoItems = true, + Identification = new DeviceIdentification { - ModelName = "Xbox 360" + ModelName = "Xbox 360", + + Headers = new List + { + new HttpHeaderInfo{ Name="User-Agent", Value="Xbox", Match= HeaderMatchType.Substring}, + new HttpHeaderInfo{ Name="User-Agent", Value="Xenon", Match= HeaderMatchType.Substring} + } }, TranscodingProfiles = new[] @@ -243,12 +260,31 @@ namespace MediaBrowser.Dlna new TranscodingProfile { Container = "mp3", + AudioCodec = "mp3", Type = DlnaProfileType.Audio }, new TranscodingProfile { - Container = "ts", - Type = DlnaProfileType.Video + Container = "asf", + VideoCodec = "wmv2", + AudioCodec = "wmav2", + Type = DlnaProfileType.Video, + TranscodeSeekInfo = TranscodeSeekInfo.Bytes, + EstimateContentLength = true, + + Settings = new List + { + new TranscodingSetting + { + Name = TranscodingSettingType.MaxAudioChannels, + Value = "6" + } + } + }, + new TranscodingProfile + { + Container = "jpeg", + Type = DlnaProfileType.Photo } }, @@ -256,18 +292,59 @@ namespace MediaBrowser.Dlna { new DirectPlayProfile { - Containers = new[]{"mp3"}, - Type = DlnaProfileType.Audio + Containers = new[]{"avi"}, + VideoCodec = "mpeg4", + AudioCodec = "ac3,mp3", + Type = DlnaProfileType.Video }, new DirectPlayProfile { Containers = new[]{"avi"}, + VideoCodec = "h264", + AudioCodec = "aac", Type = DlnaProfileType.Video }, new DirectPlayProfile { - Containers = new[]{"mp4"}, + Containers = new[]{"mp4", "mov"}, + VideoCodec = "h264,mpeg4", + AudioCodec = "aac,ac3", + Type = DlnaProfileType.Video, + + Conditions = new List + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Has64BitOffsets, Value = "false", IsRequired=false} + } + }, + new DirectPlayProfile + { + Containers = new[]{"asf"}, + VideoCodec = "wmv2,wmv3,vc1", + AudioCodec = "wmav2,wmapro", Type = DlnaProfileType.Video + }, + new DirectPlayProfile + { + Containers = new[]{"asf"}, + AudioCodec = "wmav2,wmapro,wmavoice", + Type = DlnaProfileType.Audio + }, + new DirectPlayProfile + { + Containers = new[]{"mp3"}, + AudioCodec = "mp3", + Type = DlnaProfileType.Audio + }, + new DirectPlayProfile + { + Containers = new[]{"jpeg"}, + Type = DlnaProfileType.Photo, + + Conditions = new List + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Width, Value = "1920"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Height, Value = "1080"} + } } }, @@ -279,6 +356,69 @@ namespace MediaBrowser.Dlna MimeType = "video/avi", Type = DlnaProfileType.Video } + }, + + CodecProfiles = new[] + { + new CodecProfile + { + Type = CodecType.VideoCodec, + Codec = "mpeg4", + Conditions = new List + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Width, Value = "1280"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Height, Value = "720"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.VideoFramerate, Value = "30", IsRequired=false}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.VideoBitrate, Value = "5120000", IsRequired=false} + } + }, + + new CodecProfile + { + Type = CodecType.VideoCodec, + Codec = "h264", + Conditions = new List + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Width, Value = "1920"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Height, Value = "1080"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.VideoLevel, Value = "41", IsRequired=false}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.VideoBitrate, Value = "10240000", IsRequired=false} + } + }, + + new CodecProfile + { + Type = CodecType.VideoCodec, + Codec = "wmv2,wmv3,vc1", + Conditions = new List + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Width, Value = "1920"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Height, Value = "1080"}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.VideoFramerate, Value = "30", IsRequired=false}, + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.VideoBitrate, Value = "15360000", IsRequired=false} + } + }, + + new CodecProfile + { + Type = CodecType.VideoAudioCodec, + Codec = "ac3,wmav2,wmapro", + Conditions = new List + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.AudioChannels, Value = "6", IsRequired=false} + } + }, + + new CodecProfile + { + Type = CodecType.VideoAudioCodec, + Codec = "aac", + Conditions = new List + { + new ProfileCondition{ Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.AudioChannels, Value = "6", IsRequired=false}, + new ProfileCondition{ Condition = ProfileConditionType.Equals, Property = ProfileConditionValue.AudioProfile, Value = "lc", IsRequired=false} + } + } } }); diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs index 6817c4eaa0..5da845d068 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs @@ -36,15 +36,19 @@ namespace MediaBrowser.Dlna.PlayTo 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) + if (profile.CodecProfiles.Where(i => i.Type == CodecType.AudioCodec) + .All(i => IsCodecProfileSupported(i, item.Path, null, audioStream))) { - playlistItem.Transcode = false; - playlistItem.Container = Path.GetExtension(item.Path); + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, audioStream)); - return playlistItem; + if (directPlay != null) + { + playlistItem.Transcode = false; + playlistItem.Container = Path.GetExtension(item.Path); + + return playlistItem; + } } var transcodingProfile = profile.TranscodingProfiles @@ -113,15 +117,19 @@ namespace MediaBrowser.Dlna.PlayTo 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) + if (profile.CodecProfiles.Where(i => i.Type == CodecType.VideoCodec || i.Type == CodecType.VideoAudioCodec) + .All(i => IsCodecProfileSupported(i, item.Path, videoStream, audioStream))) { - playlistItem.Transcode = false; - playlistItem.Container = Path.GetExtension(item.Path); + var directPlay = profile.DirectPlayProfiles + .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, videoStream, audioStream)); - return playlistItem; + if (directPlay != null) + { + playlistItem.Transcode = false; + playlistItem.Container = Path.GetExtension(item.Path); + + return playlistItem; + } } var transcodingProfile = profile.TranscodingProfiles @@ -281,6 +289,24 @@ namespace MediaBrowser.Dlna.PlayTo return true; } + private bool IsCodecProfileSupported(CodecProfile profile, string mediaPath, MediaStream videoStream, MediaStream audioStream) + { + var codecs = profile.GetCodecs(); + var stream = profile.Type == CodecType.VideoCodec ? videoStream : audioStream; + var existingCodec = (stream == null ? null : stream.Codec) ?? string.Empty; + + if (codecs.Count == 0 || codecs.Contains(existingCodec, StringComparer.OrdinalIgnoreCase)) + { + // Check additional conditions + if (!profile.Conditions.Any(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream))) + { + return false; + } + } + + return true; + } + /// /// Determines whether [is condition satisfied] [the specified condition]. /// @@ -292,30 +318,70 @@ namespace MediaBrowser.Dlna.PlayTo /// Unexpected ProfileConditionType private bool IsConditionSatisfied(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream) { - var actualValue = GetConditionValue(condition, mediaPath, videoStream, audioStream); - - if (actualValue.HasValue) + if (condition.Property == ProfileConditionValue.VideoProfile) { - long expected; - if (long.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected)) + var profile = videoStream == null ? null : videoStream.Profile; + + if (!string.IsNullOrWhiteSpace(profile)) { switch (condition.Condition) { case ProfileConditionType.Equals: - return actualValue.Value == expected; - case ProfileConditionType.GreaterThanEqual: - return actualValue.Value >= expected; - case ProfileConditionType.LessThanEqual: - return actualValue.Value <= expected; + return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); case ProfileConditionType.NotEquals: - return actualValue.Value != expected; + return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase); default: throw new InvalidOperationException("Unexpected ProfileConditionType"); } } } - return false; + 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; } ///