فهرست منبع

Merge pull request #1497 from MediaBrowser/dev

Dev
Luke 9 سال پیش
والد
کامیت
f55d6ded74

+ 0 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -80,7 +80,6 @@
     <Compile Include="FilterService.cs" />
     <Compile Include="IHasDtoOptions.cs" />
     <Compile Include="Library\ChapterService.cs" />
-    <Compile Include="PinLoginService.cs" />
     <Compile Include="Playback\Dash\ManifestBuilder.cs" />
     <Compile Include="Playback\Dash\MpegDashService.cs" />
     <Compile Include="Playback\MediaInfoService.cs" />

+ 0 - 243
MediaBrowser.Api/PinLoginService.cs

@@ -1,243 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Globalization;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Connect;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Session;
-using ServiceStack;
-
-namespace MediaBrowser.Api
-{
-    [Route("/Auth/Pin", "POST", Summary = "Creates a pin request")]
-    public class CreatePinRequest : IReturn<PinCreationResult>
-    {
-        [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string DeviceId { get; set; }
-        [ApiMember(Name = "AppName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string AppName { get; set; }
-    }
-
-    [Route("/Auth/Pin", "GET", Summary = "Gets pin status")]
-    public class GetPinStatusRequest : IReturn<PinStatusResult>
-    {
-        [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string DeviceId { get; set; }
-        [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string Pin { get; set; }
-    }
-
-    [Route("/Auth/Pin/Exchange", "POST", Summary = "Exchanges a pin")]
-    public class ExchangePinRequest : IReturn<PinExchangeResult>
-    {
-        [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string DeviceId { get; set; }
-        [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Pin { get; set; }
-    }
-
-    [Route("/Auth/Pin/Validate", "POST", Summary = "Validates a pin")]
-    [Authenticated]
-    public class ValidatePinRequest : IReturn<SessionInfoDto>
-    {
-        [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Pin { get; set; }
-    }
-
-    public class PinLoginService : BaseApiService
-    {
-        private static readonly ConcurrentDictionary<string, MyPinStatus> _activeRequests = new ConcurrentDictionary<string, MyPinStatus>(StringComparer.OrdinalIgnoreCase);
-        private readonly ISessionManager _sessionManager;
-        private readonly IUserManager _userManager;
-
-        public PinLoginService(ISessionManager sessionManager, IUserManager userManager)
-        {
-            _sessionManager = sessionManager;
-            _userManager = userManager;
-        }
-
-        public object Post(CreatePinRequest request)
-        {
-            if (string.IsNullOrWhiteSpace(request.DeviceId))
-            {
-                throw new ArgumentNullException("DeviceId");
-            }
-            if (string.IsNullOrWhiteSpace(request.AppName))
-            {
-                throw new ArgumentNullException("AppName");
-            }
-
-            var pin = GetNewPin();
-
-            var value = new MyPinStatus
-            {
-                CreationTimeUtc = DateTime.UtcNow,
-                IsConfirmed = false,
-                IsExpired = false,
-                Pin = pin,
-                DeviceId = request.DeviceId,
-                AppName = request.AppName
-            };
-
-            _activeRequests.AddOrUpdate(pin, value, (k, v) => value);
-
-            return ToOptimizedResult(new PinCreationResult
-            {
-                DeviceId = request.DeviceId,
-                IsConfirmed = false,
-                IsExpired = false,
-                Pin = pin
-            });
-        }
-
-        public object Get(GetPinStatusRequest request)
-        {
-            MyPinStatus status;
-
-            if (!_activeRequests.TryGetValue(request.Pin, out status))
-            {
-                Logger.Debug("Pin {0} not found.", request.Pin);
-                throw new ResourceNotFoundException();
-            }
-
-            EnsureValid(request.DeviceId, status);
-
-            return ToOptimizedResult(new PinStatusResult
-            {
-                Pin = status.Pin,
-                IsConfirmed = status.IsConfirmed,
-                IsExpired = status.IsExpired
-            });
-        }
-
-        public async Task<object> Post(ExchangePinRequest request)
-        {
-            MyPinStatus status;
-
-            if (!_activeRequests.TryGetValue(request.Pin, out status))
-            {
-                Logger.Debug("Pin {0} not found.", request.Pin);
-                throw new ResourceNotFoundException();
-            }
-
-            EnsureValid(request.DeviceId, status);
-
-            if (!status.IsConfirmed)
-            {
-                throw new ResourceNotFoundException();
-            }
-
-            var auth = AuthorizationContext.GetAuthorizationInfo(Request);
-            var user = _userManager.GetUserById(status.UserId);
-
-            var result = await _sessionManager.CreateNewSession(new AuthenticationRequest
-            {
-                App = auth.Client,
-                AppVersion = auth.Version,
-                DeviceId = auth.DeviceId,
-                DeviceName = auth.Device,
-                RemoteEndPoint = Request.RemoteIp,
-                Username = user.Name
-
-            }).ConfigureAwait(false);
-
-            return ToOptimizedResult(result);
-        }
-
-        public object Post(ValidatePinRequest request)
-        {
-            MyPinStatus status;
-
-            if (!_activeRequests.TryGetValue(request.Pin, out status))
-            {
-                throw new ResourceNotFoundException();
-            }
-
-            EnsureValid(status);
-
-            status.IsConfirmed = true;
-            status.UserId = AuthorizationContext.GetAuthorizationInfo(Request).UserId;
-
-            return ToOptimizedResult(new ValidatePinResult
-            {
-                AppName = status.AppName
-            });
-        }
-
-        private void EnsureValid(string requestedDeviceId, MyPinStatus status)
-        {
-            if (!string.Equals(requestedDeviceId, status.DeviceId, StringComparison.OrdinalIgnoreCase))
-            {
-                Logger.Debug("Pin device Id's do not match. requestedDeviceId: {0}, status.DeviceId: {1}", requestedDeviceId, status.DeviceId);
-                throw new ResourceNotFoundException();
-            }
-
-            EnsureValid(status);
-        }
-
-        private void EnsureValid(MyPinStatus status)
-        {
-            if ((DateTime.UtcNow - status.CreationTimeUtc).TotalMinutes > 10)
-            {
-                status.IsExpired = true;
-            }
-
-            if (status.IsExpired)
-            {
-                Logger.Debug("Pin {0} is expired", status.Pin);
-                throw new ResourceNotFoundException();
-            }
-        }
-
-        private string GetNewPin()
-        {
-            var pin = GetNewPinInternal();
-
-            while (IsPinActive(pin))
-            {
-                pin = GetNewPinInternal();
-            }
-
-            return pin;
-        }
-
-        private string GetNewPinInternal()
-        {
-            return new Random().Next(10000, 99999).ToString(CultureInfo.InvariantCulture);
-        }
-
-        private bool IsPinActive(string pin)
-        {
-            MyPinStatus status;
-
-            if (!_activeRequests.TryGetValue(pin, out status))
-            {
-                return false;
-            }
-
-            if (status.IsExpired)
-            {
-                return false;
-            }
-
-            return true;
-        }
-
-        public class MyPinStatus : PinStatusResult
-        {
-            public DateTime CreationTimeUtc { get; set; }
-            public string DeviceId { get; set; }
-            public string UserId { get; set; }
-            public string AppName { get; set; }
-        }
-    }
-
-    public class ValidatePinResult
-    {
-        public string AppName { get; set; }
-    }
-}

+ 7 - 0
MediaBrowser.Controller/Library/ILibraryMonitor.cs

@@ -32,5 +32,12 @@ namespace MediaBrowser.Controller.Library
         /// </summary>
         /// <param name="path">The path.</param>
         void ReportFileSystemChanged(string path);
+
+        /// <summary>
+        /// Determines whether [is path locked] [the specified path].
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns><c>true</c> if [is path locked] [the specified path]; otherwise, <c>false</c>.</returns>
+        bool IsPathLocked(string path);
     }
 }

+ 6 - 0
MediaBrowser.Controller/LiveTv/ChannelInfo.cs

@@ -25,6 +25,12 @@ namespace MediaBrowser.Controller.LiveTv
         /// <value>The id of the channel.</value>
         public string Id { get; set; }
 
+        /// <summary>
+        /// Gets or sets the tuner host identifier.
+        /// </summary>
+        /// <value>The tuner host identifier.</value>
+        public string TunerHostId { get; set; }
+        
         /// <summary>
         /// Gets or sets the type of the channel.
         /// </summary>

+ 243 - 13
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs

@@ -7,7 +7,10 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
+using System.Text;
+using System.Xml;
 using CommonIO;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
 
@@ -81,12 +84,29 @@ namespace MediaBrowser.MediaEncoding.Probing
             }
 
             FetchGenres(info, tags);
-            var overview = FFProbeHelpers.GetDictionaryValue(tags, "description");
+            var shortOverview = FFProbeHelpers.GetDictionaryValue(tags, "description");
+            var overview = FFProbeHelpers.GetDictionaryValue(tags, "synopsis");
+
+            if (string.IsNullOrWhiteSpace(overview))
+            {
+                overview = shortOverview;
+                shortOverview = null;
+            }
+            if (string.IsNullOrWhiteSpace(overview))
+            {
+                overview = FFProbeHelpers.GetDictionaryValue(tags, "desc");
+            }
+
             if (!string.IsNullOrWhiteSpace(overview))
             {
                 info.Overview = overview;
             }
 
+            if (!string.IsNullOrWhiteSpace(shortOverview))
+            {
+                info.ShortOverview = shortOverview;
+            }
+
             var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
             if (!string.IsNullOrWhiteSpace(title))
             {
@@ -105,13 +125,15 @@ namespace MediaBrowser.MediaEncoding.Probing
             {
                 SetAudioRuntimeTicks(data, info);
 
-                // tags are normally located under data.format, but we've seen some cases with ogg where they're part of the audio stream
+                // tags are normally located under data.format, but we've seen some cases with ogg where they're part of the info stream
                 // so let's create a combined list of both
 
                 SetAudioInfoFromTags(info, tags);
             }
             else
             {
+                FetchStudios(info, tags, "copyright");
+
                 var iTunEXTC = FFProbeHelpers.GetDictionaryValue(tags, "iTunEXTC");
                 if (!string.IsNullOrWhiteSpace(iTunEXTC))
                 {
@@ -124,13 +146,13 @@ namespace MediaBrowser.MediaEncoding.Probing
                         info.OfficialRatingDescription = parts[3];
                     }
                 }
-                
+
                 var itunesXml = FFProbeHelpers.GetDictionaryValue(tags, "iTunMOVI");
                 if (!string.IsNullOrWhiteSpace(itunesXml))
                 {
                     FetchFromItunesInfo(itunesXml, info);
                 }
-                
+
                 if (data.format != null && !string.IsNullOrEmpty(data.format.duration))
                 {
                     info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
@@ -151,13 +173,221 @@ namespace MediaBrowser.MediaEncoding.Probing
 
         private void FetchFromItunesInfo(string xml, MediaInfo info)
         {
+            // Make things simpler and strip out the dtd
+            xml = xml.Substring(xml.IndexOf("<plist", StringComparison.OrdinalIgnoreCase));
+            xml = "<?xml version=\"1.0\"?>" + xml;
+
             // <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>cast</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Blender Foundation</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Janus Bager Kristensen</string>\n\t\t</dict>\n\t</array>\n\t<key>directors</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Sacha Goedegebure</string>\n\t\t</dict>\n\t</array>\n\t<key>studio</key>\n\t<string>Blender Foundation</string>\n</dict>\n</plist>\n
+            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
+            {
+                using (var streamReader = new StreamReader(stream))
+                {
+                    // Use XmlReader for best performance
+                    using (var reader = XmlReader.Create(streamReader))
+                    {
+                        reader.MoveToContent();
+
+                        // Loop through each element
+                        while (reader.Read())
+                        {
+                            if (reader.NodeType == XmlNodeType.Element)
+                            {
+                                switch (reader.Name)
+                                {
+                                    case "dict":
+                                        using (var subtree = reader.ReadSubtree())
+                                        {
+                                            ReadFromDictNode(subtree, info);
+                                        }
+                                        break;
+                                    default:
+                                        reader.Skip();
+                                        break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        private void ReadFromDictNode(XmlReader reader, MediaInfo info)
+        {
+            reader.MoveToContent();
+
+            string currentKey = null;
+            List<NameValuePair> pairs = new List<NameValuePair>();
+
+            // Loop through each element
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "key":
+                            if (!string.IsNullOrWhiteSpace(currentKey))
+                            {
+                                ProcessPairs(currentKey, pairs, info);
+                            }
+                            currentKey = reader.ReadElementContentAsString();
+                            pairs = new List<NameValuePair>();
+                            break;
+                        case "string":
+                            var value = reader.ReadElementContentAsString();
+                            if (!string.IsNullOrWhiteSpace(value))
+                            {
+                                pairs.Add(new NameValuePair
+                                {
+                                    Name = value,
+                                    Value = value
+                                });
+                            }
+                            break;
+                        case "array":
+                            if (!string.IsNullOrWhiteSpace(currentKey))
+                            {
+                                using (var subtree = reader.ReadSubtree())
+                                {
+                                    pairs.AddRange(ReadValueArray(subtree));
+                                }
+                            }
+                            break;
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+        }
+
+        private List<NameValuePair> ReadValueArray(XmlReader reader)
+        {
+            reader.MoveToContent();
+
+            List<NameValuePair> pairs = new List<NameValuePair>();
+
+            // Loop through each element
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "dict":
+                            using (var subtree = reader.ReadSubtree())
+                            {
+                                var dict = GetNameValuePair(subtree);
+                                if (dict != null)
+                                {
+                                    pairs.Add(dict);
+                                }
+                            }
+                            break;
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+
+            return pairs;
+        }
+
+        private void ProcessPairs(string key, List<NameValuePair> pairs, MediaInfo info)
+        {
+            if (string.Equals(key, "studio", StringComparison.OrdinalIgnoreCase))
+            {
+                foreach (var pair in pairs)
+                {
+                    info.Studios.Add(pair.Value);
+                }
+
+                info.Studios = info.Studios
+                    .Where(i => !string.IsNullOrWhiteSpace(i))
+                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                    .ToList();
+
+            }
+            else if (string.Equals(key, "screenwriters", StringComparison.OrdinalIgnoreCase))
+            {
+                foreach (var pair in pairs)
+                {
+                    info.People.Add(new BaseItemPerson
+                    {
+                        Name = pair.Value,
+                        Type = PersonType.Writer
+                    });
+                }
+            }
+            else if (string.Equals(key, "producers", StringComparison.OrdinalIgnoreCase))
+            {
+                foreach (var pair in pairs)
+                {
+                    info.People.Add(new BaseItemPerson
+                    {
+                        Name = pair.Value,
+                        Type = PersonType.Producer
+                    });
+                }
+            }
+            else if (string.Equals(key, "directors", StringComparison.OrdinalIgnoreCase))
+            {
+                foreach (var pair in pairs)
+                {
+                    info.People.Add(new BaseItemPerson
+                    {
+                        Name = pair.Value,
+                        Type = PersonType.Director
+                    });
+                }
+            }
+        }
+
+        private NameValuePair GetNameValuePair(XmlReader reader)
+        {
+            reader.MoveToContent();
+
+            string name = null;
+            string value = null;
+
+            // Loop through each element
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "key":
+                            name = reader.ReadElementContentAsString();
+                            break;
+                        case "string":
+                            value = reader.ReadElementContentAsString();
+                            break;
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+
+            if (string.IsNullOrWhiteSpace(name) ||
+                string.IsNullOrWhiteSpace(value))
+            {
+                return null;
+            }
+
+            return new NameValuePair
+            {
+                Name = name,
+                Value = value
+            };
         }
 
         /// <summary>
         /// Converts ffprobe stream info to our MediaStream class
         /// </summary>
-        /// <param name="isAudio">if set to <c>true</c> [is audio].</param>
+        /// <param name="isAudio">if set to <c>true</c> [is info].</param>
         /// <param name="streamInfo">The stream info.</param>
         /// <param name="formatInfo">The format info.</param>
         /// <returns>MediaStream.</returns>
@@ -434,11 +664,11 @@ namespace MediaBrowser.MediaEncoding.Probing
             return null;
         }
 
-        private void SetAudioRuntimeTicks(InternalMediaInfoResult result, Model.MediaInfo.MediaInfo data)
+        private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data)
         {
             if (result.streams != null)
             {
-                // Get the first audio stream
+                // Get the first info stream
                 var stream = result.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
 
                 if (stream != null)
@@ -703,10 +933,10 @@ namespace MediaBrowser.MediaEncoding.Probing
         /// <summary>
         /// Gets the studios from the tags collection
         /// </summary>
-        /// <param name="audio">The audio.</param>
+        /// <param name="info">The info.</param>
         /// <param name="tags">The tags.</param>
         /// <param name="tagName">Name of the tag.</param>
-        private void FetchStudios(Model.MediaInfo.MediaInfo audio, Dictionary<string, string> tags, string tagName)
+        private void FetchStudios(MediaInfo info, Dictionary<string, string> tags, string tagName)
         {
             var val = FFProbeHelpers.GetDictionaryValue(tags, tagName);
 
@@ -717,19 +947,19 @@ namespace MediaBrowser.MediaEncoding.Probing
                 foreach (var studio in studios)
                 {
                     // Sometimes the artist name is listed here, account for that
-                    if (audio.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase))
+                    if (info.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase))
                     {
                         continue;
                     }
-                    if (audio.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase))
+                    if (info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase))
                     {
                         continue;
                     }
 
-                    audio.Studios.Add(studio);
+                    info.Studios.Add(studio);
                 }
 
-                audio.Studios = audio.Studios
+                info.Studios = info.Studios
                     .Where(i => !string.IsNullOrWhiteSpace(i))
                     .Distinct(StringComparer.OrdinalIgnoreCase)
                     .ToList();

+ 10 - 3
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.Model.LiveTv
 
         public int PrePaddingSeconds { get; set; }
         public int PostPaddingSeconds { get; set; }
-        
+
         public LiveTvOptions()
         {
             EnableMovieProviders = true;
@@ -31,7 +31,6 @@ namespace MediaBrowser.Model.LiveTv
         public string Type { get; set; }
         public bool ImportFavoritesOnly { get; set; }
         public bool IsEnabled { get; set; }
-        public string GuideGroup { get; set; }
 
         public TunerHostInfo()
         {
@@ -49,6 +48,14 @@ namespace MediaBrowser.Model.LiveTv
         public string ZipCode { get; set; }
         public string Country { get; set; }
         public string Path { get; set; }
-        public string GuideGroup { get; set; }
+
+        public string[] EnabledTuners { get; set; }
+        public bool EnableAllTuners { get; set; }
+
+        public ListingsProviderInfo()
+        {
+            EnabledTuners = new string[] { };
+            EnableAllTuners = true;
+        }
     }
 }

+ 5 - 0
MediaBrowser.Model/MediaInfo/MediaInfo.cs

@@ -51,6 +51,11 @@ namespace MediaBrowser.Model.MediaInfo
         /// </summary>
         /// <value>The overview.</value>
         public string Overview { get; set; }
+        /// <summary>
+        /// Gets or sets the short overview.
+        /// </summary>
+        /// <value>The short overview.</value>
+        public string ShortOverview { get; set; }
 
         public MediaInfo()
         {

+ 5 - 0
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -463,6 +463,11 @@ namespace MediaBrowser.Providers.MediaInfo
                     video.Overview = data.Overview;
                 }
             }
+
+            if (string.IsNullOrWhiteSpace(video.ShortOverview) || isFullRefresh)
+            {
+                video.ShortOverview = data.ShortOverview;
+            }
         }
 
         private async Task FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)

+ 7 - 0
MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -64,6 +64,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 FileSize = new FileInfo(path).Length
             };
 
+            if (_libraryMonitor.IsPathLocked(path))
+            {
+                result.Status = FileSortingStatus.Failure;
+                result.StatusMessage = "Path is locked by other processes. Please try again later.";
+                return result;
+            }
+
             var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
             var resolver = new Naming.TV.EpisodeResolver(namingOptions, new PatternsLogger());
 

+ 6 - 0
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -78,6 +78,12 @@ namespace MediaBrowser.Server.Implementations.IO
             TemporarilyIgnore(path);
         }
 
+        public bool IsPathLocked(string path)
+        {
+            var lockedPaths = _tempIgnoredPaths.Keys.ToList();
+            return lockedPaths.Any(i => string.Equals(i, path, StringComparison.OrdinalIgnoreCase) || _fileSystem.ContainsSubPath(i, path));
+        }
+
         public async void ReportFileSystemChangeComplete(string path, bool refreshPath)
         {
             if (string.IsNullOrEmpty(path))

+ 17 - 2
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -210,9 +210,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 }
             }
 
-            if (list.Count > 0)
+            foreach (var provider in GetListingProviders())
             {
-                foreach (var provider in GetListingProviders())
+                var enabledChannels = list
+                    .Where(i => IsListingProviderEnabledForTuner(provider.Item2, i.TunerHostId))
+                    .ToList();
+
+                if (enabledChannels.Count > 0)
                 {
                     try
                     {
@@ -228,6 +232,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                     }
                 }
             }
+
             _channelCache = list;
             return list;
         }
@@ -489,6 +494,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             }
         }
 
+        private bool IsListingProviderEnabledForTuner(ListingsProviderInfo info, string tunerHostId)
+        {
+            return info.EnableAllTuners || info.EnabledTuners.Contains(tunerHostId ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+        }
+
         private async Task<IEnumerable<ProgramInfo>> GetProgramsAsyncInternal(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
         {
             var channels = await GetChannelsAsync(true, cancellationToken).ConfigureAwait(false);
@@ -496,6 +506,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
             foreach (var provider in GetListingProviders())
             {
+                if (!IsListingProviderEnabledForTuner(provider.Item2, channel.TunerHostId))
+                {
+                    continue;
+                }
+
                 var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channel.Number, channel.Name, startDateUtc, endDateUtc, cancellationToken)
                         .ConfigureAwait(false);
 

+ 18 - 11
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -14,6 +14,7 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dlna;
@@ -64,8 +65,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     {
                         Name = i.GuideName,
                         Number = i.GuideNumber.ToString(CultureInfo.InvariantCulture),
-                        Id = ChannelIdPrefix + i.GuideNumber.ToString(CultureInfo.InvariantCulture),
-                        IsFavorite = i.Favorite
+                        Id = ChannelIdPrefix + i.GuideNumber.ToString(CultureInfo.InvariantCulture) + '_' + (i.GuideName ?? string.Empty).GetMD5().ToString("N"),
+                        IsFavorite = i.Favorite,
+                        TunerHostId = info.Id
 
                     });
 
@@ -347,6 +349,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             return Config.GetConfiguration<EncodingOptions>("encoding");
         }
 
+        private string GetHdHrIdFromChannelId(string channelId)
+        {
+            return channelId.Split('_')[1];
+        }
+
         protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
         {
             var list = new List<MediaSourceInfo>();
@@ -355,9 +362,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
                 return list;
             }
-            channelId = channelId.Substring(ChannelIdPrefix.Length);
+            var hdhrId = GetHdHrIdFromChannelId(channelId);
 
-            list.Add(GetMediaSource(info, channelId, "native"));
+            list.Add(GetMediaSource(info, hdhrId, "native"));
 
             try
             {
@@ -366,12 +373,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
                 if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
                 {
-                    list.Insert(0, GetMediaSource(info, channelId, "heavy"));
+                    list.Insert(0, GetMediaSource(info, hdhrId, "heavy"));
 
-                    list.Add(GetMediaSource(info, channelId, "internet480"));
-                    list.Add(GetMediaSource(info, channelId, "internet360"));
-                    list.Add(GetMediaSource(info, channelId, "internet240"));
-                    list.Add(GetMediaSource(info, channelId, "mobile"));
+                    list.Add(GetMediaSource(info, hdhrId, "internet480"));
+                    list.Add(GetMediaSource(info, hdhrId, "internet360"));
+                    list.Add(GetMediaSource(info, hdhrId, "internet240"));
+                    list.Add(GetMediaSource(info, hdhrId, "mobile"));
                 }
             }
             catch (Exception ex)
@@ -400,9 +407,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
                 throw new ArgumentException("Channel not found");
             }
-            channelId = channelId.Substring(ChannelIdPrefix.Length);
+            var hdhrId = GetHdHrIdFromChannelId(channelId);
 
-            return GetMediaSource(info, channelId, streamId);
+            return GetMediaSource(info, hdhrId, streamId);
         }
 
         public async Task Validate(TunerHostInfo info)

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -44,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
 
         protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
         {
-            return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(info.Url, ChannelIdPrefix, cancellationToken).ConfigureAwait(false);
+            return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(info.Url, ChannelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
         }
 
         public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)

+ 8 - 7
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs

@@ -25,14 +25,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
             _httpClient = httpClient;
         }
 
-        public async Task<List<M3UChannel>> Parse(string url, string channelIdPrefix, CancellationToken cancellationToken)
+        public async Task<List<M3UChannel>> Parse(string url, string channelIdPrefix, string tunerHostId, CancellationToken cancellationToken)
         {
             var urlHash = url.GetMD5().ToString("N");
 
             // Read the file and display it line by line.
             using (var reader = new StreamReader(await GetListingsStream(url, cancellationToken).ConfigureAwait(false)))
             {
-                return GetChannels(reader, urlHash, channelIdPrefix);
+                return GetChannels(reader, urlHash, channelIdPrefix, tunerHostId);
             }
         }
 
@@ -45,7 +45,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
             return Task.FromResult(_fileSystem.OpenRead(url));
         }
 
-        private List<M3UChannel> GetChannels(StreamReader reader, string urlHash, string channelIdPrefix)
+        private List<M3UChannel> GetChannels(StreamReader reader, string urlHash, string channelIdPrefix, string tunerHostId)
         {
             var channels = new List<M3UChannel>();
             string line;
@@ -69,8 +69,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
                     _logger.Info("Found m3u channel: {0}", extInf);
                 }
                 else if (!string.IsNullOrWhiteSpace(extInf))
-                {   
-                    var channel = GetChannelnfo(extInf);
+                {
+                    var channel = GetChannelnfo(extInf, tunerHostId);
                     channel.Id = channelIdPrefix + urlHash + line.GetMD5().ToString("N");
                     channel.Path = line;
                     channels.Add(channel);
@@ -79,10 +79,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
             }
             return channels;
         }
-        public M3UChannel GetChannelnfo(string extInf)
+        private M3UChannel GetChannelnfo(string extInf, string tunerHostId)
         {
             var titleIndex = extInf.LastIndexOf(',');
             var channel = new M3UChannel();
+            channel.TunerHostId = tunerHostId;
 
             channel.Number = extInf.Trim().Split(' ')[0] ?? "0";
             channel.Name = extInf.Substring(titleIndex + 1);
@@ -108,7 +109,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
             return channel;
 
         }
-        public string FindProperty(string property, string properties, string defaultResult = "")
+        private string FindProperty(string property, string properties, string defaultResult = "")
         {
             var reg = new Regex(@"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase);
             var matches = reg.Matches(properties);

+ 1 - 1
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs

@@ -37,7 +37,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
         {
             var satInfo = (SatIpTunerHostInfo) tuner;
 
-            return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(satInfo.M3UUrl, ChannelIdPrefix, cancellationToken).ConfigureAwait(false);
+            return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(satInfo.M3UUrl, ChannelIdPrefix, tuner.Id, cancellationToken).ConfigureAwait(false);
         }
 
         public static string DeviceType

+ 111 - 441
MediaBrowser.Server.Mac/Emby.Server.Mac.csproj

@@ -476,6 +476,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\photos.html">
       <Link>Resources\dashboard-ui\photos.html</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\pin.html">
+      <Link>Resources\dashboard-ui\pin.html</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\playbackconfiguration.html">
       <Link>Resources\dashboard-ui\playbackconfiguration.html</Link>
     </BundleResource>
@@ -1316,6 +1319,18 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\visibleinviewport.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\visibleinviewport.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\actionsheet\actionsheet.css">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\actionsheet\actionsheet.css</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\actionsheet\actionsheet.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\actionsheet\actionsheet.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\confirm\confirm.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\confirm\confirm.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\confirm\nativeconfirm.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\confirm\nativeconfirm.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\images\basicimagefetcher.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\images\basicimagefetcher.js</Link>
     </BundleResource>
@@ -1877,6 +1892,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\.gitignore">
       <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\.gitignore</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\.travis.yml">
+      <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\.travis.yml</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\CONTRIBUTING.md">
       <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\CONTRIBUTING.md</Link>
     </BundleResource>
@@ -2012,6 +2030,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\index.html">
       <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\index.html</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\iron-flex-layout-classes.html">
+      <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\iron-flex-layout-classes.html</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\iron-flex-layout.html">
       <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\iron-flex-layout.html</Link>
     </BundleResource>
@@ -2021,12 +2042,18 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\classes\iron-shadow-flex-layout.html">
       <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\classes\iron-shadow-flex-layout.html</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\demo\demo-snippet.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\demo\demo-snippet.html</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\demo\index.html">
       <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\demo\index.html</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\test\index.html">
+      <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\test\index.html</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\test\iron-flex-layout-classes.html">
+      <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\test\iron-flex-layout-classes.html</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\test\iron-flex-layout.html">
+      <Link>Resources\dashboard-ui\bower_components\iron-flex-layout\test\iron-flex-layout.html</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-form-element-behavior\.bower.json">
       <Link>Resources\dashboard-ui\bower_components\iron-form-element-behavior\.bower.json</Link>
     </BundleResource>
@@ -2495,6 +2522,12 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-resizable-behavior\.gitignore">
       <Link>Resources\dashboard-ui\bower_components\iron-resizable-behavior\.gitignore</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-resizable-behavior\.travis.yml">
+      <Link>Resources\dashboard-ui\bower_components\iron-resizable-behavior\.travis.yml</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-resizable-behavior\CONTRIBUTING.md">
+      <Link>Resources\dashboard-ui\bower_components\iron-resizable-behavior\CONTRIBUTING.md</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-resizable-behavior\README.md">
       <Link>Resources\dashboard-ui\bower_components\iron-resizable-behavior\README.md</Link>
     </BundleResource>
@@ -2651,50 +2684,17 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.bower.json">
       <Link>Resources\dashboard-ui\bower_components\jquery\.bower.json</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.editorconfig">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.editorconfig</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.gitattributes">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.gitattributes</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.gitignore">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.gitignore</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.jscsrc">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.jscsrc</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.jshintignore">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.jshintignore</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.jshintrc">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.jshintrc</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.mailmap">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.mailmap</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.npmignore">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.npmignore</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\.travis.yml">
-      <Link>Resources\dashboard-ui\bower_components\jquery\.travis.yml</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\AUTHORS.txt">
       <Link>Resources\dashboard-ui\bower_components\jquery\AUTHORS.txt</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\CONTRIBUTING.md">
-      <Link>Resources\dashboard-ui\bower_components\jquery\CONTRIBUTING.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\Gruntfile.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\Gruntfile.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\LICENSE.txt">
       <Link>Resources\dashboard-ui\bower_components\jquery\LICENSE.txt</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\README.md">
       <Link>Resources\dashboard-ui\bower_components\jquery\README.md</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\package.json">
-      <Link>Resources\dashboard-ui\bower_components\jquery\package.json</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\bower.json">
+      <Link>Resources\dashboard-ui\bower_components\jquery\bower.json</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\dist\jquery.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\dist\jquery.js</Link>
@@ -2705,44 +2705,14 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\dist\jquery.min.map">
       <Link>Resources\dashboard-ui\bower_components\jquery\dist\jquery.min.map</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\npo\npo.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\npo\npo.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\qunit\LICENSE.txt">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\qunit\LICENSE.txt</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\qunit\MIT-LICENSE.txt">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\qunit\MIT-LICENSE.txt</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\qunit\qunit.css">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\qunit\qunit.css</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\qunit\qunit.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\qunit\qunit.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\qunit-assert-step\MIT-LICENSE.txt">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\qunit-assert-step\MIT-LICENSE.txt</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\qunit-assert-step\qunit-assert-step.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\qunit-assert-step\qunit-assert-step.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\requirejs\require.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\requirejs\require.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sinon\sinon-1.14.1.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\sinon\sinon-1.14.1.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\LICENSE.txt">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\LICENSE.txt</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\dist\jquery.slim.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\dist\jquery.slim.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.js</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\dist\jquery.slim.min.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\dist\jquery.slim.min.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.map">
-      <Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.map</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\dist\jquery.slim.min.map">
+      <Link>Resources\dashboard-ui\bower_components\jquery\dist\jquery.slim.min.map</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\.jshintrc">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\.jshintrc</Link>
@@ -2810,6 +2780,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\serialize.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\serialize.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\support.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\src\support.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\traversing.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\traversing.js</Link>
     </BundleResource>
@@ -2849,6 +2822,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\attributes\val.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\attributes\val.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\core\DOMEval.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\src\core\DOMEval.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\core\access.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\core\access.js</Link>
     </BundleResource>
@@ -2888,12 +2864,24 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\data\Data.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\data\Data.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\data\accepts.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\src\data\accepts.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\data\support.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\src\data\support.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\deferred\exceptionHook.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\src\deferred\exceptionHook.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\effects\Tween.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\effects\Tween.js</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\effects\animatedSelector.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\effects\animatedSelector.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\effects\support.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\src\effects\support.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\event\ajax.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\event\ajax.js</Link>
     </BundleResource>
@@ -2921,6 +2909,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\manipulation\buildFragment.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\manipulation\buildFragment.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\manipulation\createSafeFragment.js">
+      <Link>Resources\dashboard-ui\bower_components\jquery\src\manipulation\createSafeFragment.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\manipulation\getAll.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\manipulation\getAll.js</Link>
     </BundleResource>
@@ -2939,366 +2930,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\traversing\findFilter.js">
       <Link>Resources\dashboard-ui\bower_components\jquery\src\traversing\findFilter.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\.jshintrc">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\.jshintrc</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\delegatetest.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\delegatetest.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\hovertest.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\hovertest.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\index.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\jquery.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\jquery.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\localfile.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\localfile.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\networkerror.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\networkerror.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\promises_aplus_adapter.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\promises_aplus_adapter.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\readywait.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\readywait.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\xhtml.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\xhtml.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\1x1.jpg">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\1x1.jpg</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\atom+xml.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\atom+xml.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\badcall.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\badcall.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\badjson.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\badjson.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\cleanScript.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\cleanScript.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\dashboard.xml">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\dashboard.xml</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\echoData.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\echoData.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\echoQuery.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\echoQuery.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\errorWithJSON.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\errorWithJSON.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\errorWithText.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\errorWithText.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\etag.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\etag.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\headers.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\headers.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\if_modified_since.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\if_modified_since.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\iframe.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\iframe.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\jquery-1.9.1.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\jquery-1.9.1.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\json.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\json.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\json_obj.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\json_obj.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\jsonp.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\jsonp.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\name.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\name.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\name.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\name.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\nocontent.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\nocontent.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\params_html.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\params_html.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\readywaitasset.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\readywaitasset.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\readywaitloader.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\readywaitloader.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\script.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\script.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\statusText.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\statusText.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\test.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\test.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\test.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\test.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\test2.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\test2.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\test3.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\test3.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\testbar.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\testbar.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\testinit.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\testinit.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\testrunner.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\testrunner.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\testsuite.css">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\testsuite.css</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\text.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\text.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\with_fries.xml">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\with_fries.xml</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\with_fries_over_jsonp.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\with_fries_over_jsonp.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\ajax\content-type.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\ajax\content-type.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\ajax\evalScript.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\ajax\evalScript.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\ajax\method.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\ajax\method.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\ajax\onunload.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\ajax\onunload.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\ajax\unreleasedXHR.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\ajax\unreleasedXHR.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\core\aliased.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\core\aliased.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\core\cc_on.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\core\cc_on.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\core\dont_return.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\core\dont_return.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\core\dynamic_ready.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\core\dynamic_ready.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\core\onready.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\core\onready.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\css\cssWidthBeforeDocReady.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\css\cssWidthBeforeDocReady.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\data\dataAttrs.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\data\dataAttrs.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\dimensions\documentLarge.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\dimensions\documentLarge.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\event\focusElem.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\event\focusElem.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\event\focusinCrossFrame.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\event\focusinCrossFrame.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\event\interactiveReady.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\event\interactiveReady.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\event\longLoadScript.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\event\longLoadScript.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\event\onbeforeunload.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\event\onbeforeunload.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\event\promiseReady.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\event\promiseReady.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\event\syncReady.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\event\syncReady.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\event\triggerunload.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\event\triggerunload.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\manipulation\iframe-denied.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\manipulation\iframe-denied.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\offset\absolute.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\offset\absolute.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\offset\body.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\offset\body.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\offset\fixed.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\offset\fixed.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\offset\relative.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\offset\relative.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\offset\scroll.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\offset\scroll.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\offset\static.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\offset\static.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\offset\table.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\offset\table.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\selector\html5_selector.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\selector\html5_selector.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\selector\sizzle_cache.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\selector\sizzle_cache.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\support\bodyBackground.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\support\bodyBackground.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\support\csp-clean.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\support\csp-clean.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\support\csp-log.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\support\csp-log.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\support\csp.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\support\csp.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\support\csp.php">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\support\csp.php</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\data\support\getComputedSupport.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\data\support\getComputedSupport.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\integration\gh-1764-fullscreen.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\integration\gh-1764-fullscreen.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\integration\gh-2343-ie-radio-click.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\integration\gh-2343-ie-radio-click.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\integration\data\gh-1764-fullscreen-iframe.css">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\integration\data\gh-1764-fullscreen-iframe.css</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\integration\data\gh-1764-fullscreen-iframe.html">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\integration\data\gh-1764-fullscreen-iframe.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\integration\data\gh-1764-fullscreen.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\integration\data\gh-1764-fullscreen.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\.jshintrc">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\.jshintrc</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\document_missing.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\document_missing.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\document_passed.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\document_passed.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\document_present_originally.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\document_present_originally.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\iterable_with_native_symbol.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\iterable_with_native_symbol.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\iterable_with_symbol_polyfill.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\iterable_with_symbol_polyfill.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\lib\ensure_global_not_created.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\lib\ensure_global_not_created.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\lib\ensure_iterability_es6.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\lib\ensure_iterability_es6.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\node_smoke_tests\lib\ensure_jquery.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\node_smoke_tests\lib\ensure_jquery.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\ajax.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\ajax.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\attributes.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\attributes.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\basic.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\basic.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\callbacks.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\callbacks.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\core.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\core.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\css.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\css.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\data.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\data.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\deferred.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\deferred.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\deprecated.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\deprecated.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\dimensions.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\dimensions.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\effects.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\effects.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\event.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\event.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\exports.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\exports.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\manipulation.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\manipulation.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\offset.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\offset.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\queue.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\queue.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\ready.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\ready.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\selector.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\selector.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\serialize.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\serialize.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\support.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\support.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\traversing.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\traversing.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\test\unit\wrap.js">
-      <Link>Resources\dashboard-ui\bower_components\jquery\test\unit\wrap.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jstree\.bower.json">
       <Link>Resources\dashboard-ui\bower_components\jstree\.bower.json</Link>
     </BundleResource>
@@ -8774,21 +8405,21 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\ios\ios.css">
       <Link>Resources\dashboard-ui\devices\ios\ios.css</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\windowsphone\wp.css">
+      <Link>Resources\dashboard-ui\devices\windowsphone\wp.css</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\files\dummy.mp4">
       <Link>Resources\dashboard-ui\files\dummy.mp4</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\buttonenabled.js">
       <Link>Resources\dashboard-ui\legacy\buttonenabled.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\deferred.js">
-      <Link>Resources\dashboard-ui\legacy\deferred.js</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\dashboard.js">
+      <Link>Resources\dashboard-ui\legacy\dashboard.js</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\aboutpage.js">
       <Link>Resources\dashboard-ui\scripts\aboutpage.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\actionsheet.js">
-      <Link>Resources\dashboard-ui\scripts\actionsheet.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\addpluginpage.js">
       <Link>Resources\dashboard-ui\scripts\addpluginpage.js</Link>
     </BundleResource>
@@ -9116,6 +8747,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\photos.js">
       <Link>Resources\dashboard-ui\scripts\photos.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\pin.js">
+      <Link>Resources\dashboard-ui\scripts\pin.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\playbackconfiguration.js">
       <Link>Resources\dashboard-ui\scripts\playbackconfiguration.js</Link>
     </BundleResource>
@@ -9929,8 +9563,11 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.min.js">
       <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.min.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\textprocessor-en-us.js">
-      <Link>Resources\dashboard-ui\voice\textprocessor-en-us.js</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\Readme.md">
+      <Link>Resources\dashboard-ui\voice\Readme.md</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\grammarprocessor.js">
+      <Link>Resources\dashboard-ui\voice\grammarprocessor.js</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\voice.css">
       <Link>Resources\dashboard-ui\voice\voice.css</Link>
@@ -9938,5 +9575,38 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\voice.js">
       <Link>Resources\dashboard-ui\voice\voice.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\voicecommands.js">
+      <Link>Resources\dashboard-ui\voice\voicecommands.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\voicedialog.js">
+      <Link>Resources\dashboard-ui\voice\voicedialog.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\commands\controlcommands.js">
+      <Link>Resources\dashboard-ui\voice\commands\controlcommands.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\commands\disablecommands.js">
+      <Link>Resources\dashboard-ui\voice\commands\disablecommands.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\commands\enablecommands.js">
+      <Link>Resources\dashboard-ui\voice\commands\enablecommands.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\commands\playcommands.js">
+      <Link>Resources\dashboard-ui\voice\commands\playcommands.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\commands\searchcommands.js">
+      <Link>Resources\dashboard-ui\voice\commands\searchcommands.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\commands\showcommands.js">
+      <Link>Resources\dashboard-ui\voice\commands\showcommands.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\commands\togglecommands.js">
+      <Link>Resources\dashboard-ui\voice\commands\togglecommands.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\grammar\en-US.json">
+      <Link>Resources\dashboard-ui\voice\grammar\en-US.json</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\grammar\grammar.json">
+      <Link>Resources\dashboard-ui\voice\grammar\grammar.json</Link>
+    </BundleResource>
   </ItemGroup>
 </Project>

+ 0 - 6
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -278,9 +278,6 @@
     <Content Include="dashboard-ui\mysyncsettings.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\pin.html">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\robots.txt">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -320,9 +317,6 @@
     <Content Include="dashboard-ui\scripts\autoorganizesmart.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\scripts\pin.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\scripts\searchmenu.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>