diff options
Diffstat (limited to 'Emby.Server.Implementations')
21 files changed, 1023 insertions, 652 deletions
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 1f72ebd54..e65ebeb04 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3388,10 +3388,10 @@ namespace Emby.Server.Implementations.Data var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); if (includeTypes.Length == 1) { - whereClauses.Add("type=@type" + paramSuffix); + whereClauses.Add("type=@type"); if (statement != null) { - statement.TryBind("@type" + paramSuffix, includeTypes[0]); + statement.TryBind("@type", includeTypes[0]); } } else if (includeTypes.Length > 1) @@ -4936,7 +4936,7 @@ namespace Emby.Server.Implementations.Data ParentId = query.ParentId, IsPlayed = query.IsPlayed }; - var whereClauses = GetWhereClauses(typeSubQuery, null, "itemTypes"); + var whereClauses = GetWhereClauses(typeSubQuery, null); whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")"); @@ -5072,7 +5072,7 @@ namespace Emby.Server.Implementations.Data if (typeSubQuery != null) { - GetWhereClauses(typeSubQuery, null, "itemTypes"); + GetWhereClauses(typeSubQuery, null); } BindSimilarParams(query, statement); GetWhereClauses(innerQuery, statement); @@ -5110,7 +5110,7 @@ namespace Emby.Server.Implementations.Data if (typeSubQuery != null) { - GetWhereClauses(typeSubQuery, null, "itemTypes"); + GetWhereClauses(typeSubQuery, null); } BindSimilarParams(query, statement); GetWhereClauses(innerQuery, statement); diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 88c0ea203..588b42a09 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -195,13 +195,12 @@ namespace Emby.Server.Implementations.Devices } var config = _config.GetUploadOptions(); - if (!string.IsNullOrWhiteSpace(config.CameraUploadPath)) + var path = config.CameraUploadPath; + if (string.IsNullOrWhiteSpace(path)) { - return config.CameraUploadPath; + path = DefaultCameraUploadsPath; } - var path = DefaultCameraUploadsPath; - if (config.EnableCameraUploadSubfolders) { path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name)); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 4ee3df7f5..147abd171 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -491,7 +491,7 @@ namespace Emby.Server.Implementations.Dto } } - //if (!(item is LiveTvProgram)) + //if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess)) { dto.PlayAccess = item.GetPlayAccess(user); } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 13efb7bf9..c704d0e4e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -104,7 +104,6 @@ <Compile Include="HttpServer\StreamWriter.cs" /> <Compile Include="HttpServer\SwaggerService.cs" /> <Compile Include="Images\BaseDynamicImageProvider.cs" /> - <Compile Include="Intros\DefaultIntroProvider.cs" /> <Compile Include="IO\FileRefresher.cs" /> <Compile Include="IO\MbLinkShortcutHandler.cs" /> <Compile Include="IO\ThrottledStream.cs" /> @@ -170,9 +169,11 @@ <Compile Include="LiveTv\RecordingImageProvider.cs" /> <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" /> <Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" /> + <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" /> <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" /> <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" /> - <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunLiveStream.cs" /> + <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" /> + <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" /> <Compile Include="LiveTv\TunerHosts\M3uParser.cs" /> <Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" /> <Compile Include="LiveTv\TunerHosts\MulticastStream.cs" /> @@ -238,7 +239,6 @@ <Compile Include="Sorting\AlbumComparer.cs" /> <Compile Include="Sorting\AlphanumComparator.cs" /> <Compile Include="Sorting\ArtistComparer.cs" /> - <Compile Include="Sorting\BudgetComparer.cs" /> <Compile Include="Sorting\CommunityRatingComparer.cs" /> <Compile Include="Sorting\CriticRatingComparer.cs" /> <Compile Include="Sorting\DateCreatedComparer.cs" /> @@ -257,7 +257,6 @@ <Compile Include="Sorting\PremiereDateComparer.cs" /> <Compile Include="Sorting\ProductionYearComparer.cs" /> <Compile Include="Sorting\RandomComparer.cs" /> - <Compile Include="Sorting\RevenueComparer.cs" /> <Compile Include="Sorting\RuntimeComparer.cs" /> <Compile Include="Sorting\SeriesSortNameComparer.cs" /> <Compile Include="Sorting\SortNameComparer.cs" /> diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 6fcdab874..b5a3c2992 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -579,7 +579,7 @@ namespace Emby.Server.Implementations.HttpServer } else { - ErrorHandler(new FileNotFoundException(), httpReq); + ErrorHandler(new FileNotFoundException(), httpReq, false); } } catch (OperationCanceledException ex) @@ -633,7 +633,6 @@ namespace Emby.Server.Implementations.HttpServer return null; } - private void Write(IResponse response, string text) { var bOutput = Encoding.UTF8.GetBytes(text); diff --git a/Emby.Server.Implementations/Intros/DefaultIntroProvider.cs b/Emby.Server.Implementations/Intros/DefaultIntroProvider.cs deleted file mode 100644 index 4d19a0e9b..000000000 --- a/Emby.Server.Implementations/Intros/DefaultIntroProvider.cs +++ /dev/null @@ -1,394 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Security; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Globalization; - -namespace Emby.Server.Implementations.Intros -{ - public class DefaultIntroProvider : IIntroProvider - { - private readonly ISecurityManager _security; - private readonly ILocalizationManager _localization; - private readonly IConfigurationManager _serverConfig; - private readonly ILibraryManager _libraryManager; - private readonly IFileSystem _fileSystem; - private readonly IMediaSourceManager _mediaSourceManager; - - public DefaultIntroProvider(ISecurityManager security, ILocalizationManager localization, IConfigurationManager serverConfig, ILibraryManager libraryManager, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager) - { - _security = security; - _localization = localization; - _serverConfig = serverConfig; - _libraryManager = libraryManager; - _fileSystem = fileSystem; - _mediaSourceManager = mediaSourceManager; - } - - public async Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, User user) - { - var config = GetOptions(); - - if (item is Movie) - { - if (!config.EnableIntrosForMovies) - { - return new List<IntroInfo>(); - } - } - else if (item is Episode) - { - if (!config.EnableIntrosForEpisodes) - { - return new List<IntroInfo>(); - } - } - else - { - return new List<IntroInfo>(); - } - - var ratingLevel = string.IsNullOrWhiteSpace(item.OfficialRating) - ? null - : _localization.GetRatingLevel(item.OfficialRating); - - var candidates = new List<ItemWithTrailer>(); - - var trailerTypes = new List<TrailerType>(); - var sourceTypes = new List<SourceType>(); - - if (config.EnableIntrosFromMoviesInLibrary) - { - trailerTypes.Add(TrailerType.LocalTrailer); - sourceTypes.Add(SourceType.Library); - } - - if (IsSupporter) - { - if (config.EnableIntrosFromUpcomingTrailers) - { - trailerTypes.Add(TrailerType.ComingSoonToTheaters); - sourceTypes.Clear(); - } - if (config.EnableIntrosFromUpcomingDvdMovies) - { - trailerTypes.Add(TrailerType.ComingSoonToDvd); - sourceTypes.Clear(); - } - if (config.EnableIntrosFromUpcomingStreamingMovies) - { - trailerTypes.Add(TrailerType.ComingSoonToStreaming); - sourceTypes.Clear(); - } - if (config.EnableIntrosFromSimilarMovies) - { - trailerTypes.Add(TrailerType.Archive); - sourceTypes.Clear(); - } - } - - if (trailerTypes.Count > 0) - { - if (trailerTypes.Count >= 5) - { - trailerTypes.Clear(); - } - - // hack - can't filter by user library because local trailers get TopParentId =null in the db. - // for now we have to use a post-query filter afterwards to solve that - var trailerResult = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Trailer).Name }, - TrailerTypes = trailerTypes.ToArray(), - SimilarTo = item, - //IsPlayed = config.EnableIntrosForWatchedContent ? (bool?)null : false, - MaxParentalRating = config.EnableIntrosParentalControl ? ratingLevel : null, - BlockUnratedItems = config.EnableIntrosParentalControl ? new[] { UnratedItem.Trailer } : new UnratedItem[] { }, - - // Account for duplicates by imdb id, since the database doesn't support this yet - Limit = config.TrailerLimit * 4, - SourceTypes = sourceTypes.ToArray() - }) - .Where(i => string.IsNullOrWhiteSpace(i.GetProviderId(MetadataProviders.Imdb)) || !string.Equals(i.GetProviderId(MetadataProviders.Imdb), item.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)) - .Where(i => i.IsVisibleStandalone(user)) - .Where(i => config.EnableIntrosForWatchedContent || !i.IsPlayed(user)) - .Take(config.TrailerLimit); - - candidates.AddRange(trailerResult.Select(i => new ItemWithTrailer - { - Item = i, - Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer, - LibraryManager = _libraryManager - })); - } - - return GetResult(item, candidates, config); - } - - private IEnumerable<IntroInfo> GetResult(BaseItem item, IEnumerable<ItemWithTrailer> candidates, CinemaModeConfiguration config) - { - var customIntros = !string.IsNullOrWhiteSpace(config.CustomIntroPath) ? - GetCustomIntros(config) : - new List<IntroInfo>(); - - var mediaInfoIntros = !string.IsNullOrWhiteSpace(config.MediaInfoIntroPath) ? - GetMediaInfoIntros(config, item) : - new List<IntroInfo>(); - - // Avoid implicitly captured closure - return candidates.Select(i => i.IntroInfo) - .Concat(customIntros.Take(1)) - .Concat(mediaInfoIntros); - } - - private CinemaModeConfiguration GetOptions() - { - return _serverConfig.GetConfiguration<CinemaModeConfiguration>("cinemamode"); - } - - private List<IntroInfo> GetCustomIntros(CinemaModeConfiguration options) - { - try - { - return GetCustomIntroFiles(options, true, false) - .OrderBy(i => Guid.NewGuid()) - .Select(i => new IntroInfo - { - Path = i - - }).ToList(); - } - catch (IOException) - { - return new List<IntroInfo>(); - } - } - - private IEnumerable<IntroInfo> GetMediaInfoIntros(CinemaModeConfiguration options, BaseItem item) - { - try - { - var hasMediaSources = item as IHasMediaSources; - - if (hasMediaSources == null) - { - return new List<IntroInfo>(); - } - - var mediaSource = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false) - .FirstOrDefault(); - - if (mediaSource == null) - { - return new List<IntroInfo>(); - } - - var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - - var allIntros = GetCustomIntroFiles(options, false, true) - .OrderBy(i => Guid.NewGuid()) - .Select(i => new IntroInfo - { - Path = i - - }).ToList(); - - var returnResult = new List<IntroInfo>(); - - if (videoStream != null) - { - returnResult.AddRange(GetMediaInfoIntrosByVideoStream(allIntros, videoStream).Take(1)); - } - - if (audioStream != null) - { - returnResult.AddRange(GetMediaInfoIntrosByAudioStream(allIntros, audioStream).Take(1)); - } - - returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1)); - - return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase); - } - catch (IOException) - { - return new List<IntroInfo>(); - } - } - - private IEnumerable<IntroInfo> GetMediaInfoIntrosByVideoStream(List<IntroInfo> allIntros, MediaStream stream) - { - var codec = stream.Codec; - - if (string.IsNullOrWhiteSpace(codec)) - { - return new List<IntroInfo>(); - } - - return allIntros - .Where(i => IsMatch(i.Path, codec)) - .OrderBy(i => Guid.NewGuid()); - } - - private IEnumerable<IntroInfo> GetMediaInfoIntrosByAudioStream(List<IntroInfo> allIntros, MediaStream stream) - { - var codec = stream.Codec; - - if (string.IsNullOrWhiteSpace(codec)) - { - return new List<IntroInfo>(); - } - - return allIntros - .Where(i => IsAudioMatch(i.Path, stream)) - .OrderBy(i => Guid.NewGuid()); - } - - private IEnumerable<IntroInfo> GetMediaInfoIntrosByTags(List<IntroInfo> allIntros, List<string> tags) - { - return allIntros - .Where(i => tags.Any(t => IsMatch(i.Path, t))) - .OrderBy(i => Guid.NewGuid()); - } - - private bool IsMatch(string file, string attribute) - { - var filename = Path.GetFileNameWithoutExtension(file) ?? string.Empty; - filename = Normalize(filename); - - if (string.IsNullOrWhiteSpace(filename)) - { - return false; - } - - attribute = Normalize(attribute); - if (string.IsNullOrWhiteSpace(attribute)) - { - return false; - } - - return string.Equals(filename, attribute, StringComparison.OrdinalIgnoreCase); - } - - private string Normalize(string value) - { - return value; - } - - private bool IsAudioMatch(string path, MediaStream stream) - { - if (!string.IsNullOrWhiteSpace(stream.Codec)) - { - if (IsMatch(path, stream.Codec)) - { - return true; - } - } - if (!string.IsNullOrWhiteSpace(stream.Profile)) - { - if (IsMatch(path, stream.Profile)) - { - return true; - } - } - - return false; - } - - private IEnumerable<string> GetCustomIntroFiles(CinemaModeConfiguration options, bool enableCustomIntros, bool enableMediaInfoIntros) - { - var list = new List<string>(); - - if (enableCustomIntros && !string.IsNullOrWhiteSpace(options.CustomIntroPath)) - { - list.AddRange(_fileSystem.GetFilePaths(options.CustomIntroPath, true) - .Where(_libraryManager.IsVideoFile)); - } - - if (enableMediaInfoIntros && !string.IsNullOrWhiteSpace(options.MediaInfoIntroPath)) - { - list.AddRange(_fileSystem.GetFilePaths(options.MediaInfoIntroPath, true) - .Where(_libraryManager.IsVideoFile)); - } - - return list.Distinct(StringComparer.OrdinalIgnoreCase); - } - - public IEnumerable<string> GetAllIntroFiles() - { - return GetCustomIntroFiles(GetOptions(), true, true); - } - - private bool IsSupporter - { - get { return _security.IsMBSupporter; } - } - - public string Name - { - get { return "Default"; } - } - - internal class ItemWithTrailer - { - internal BaseItem Item; - internal ItemWithTrailerType Type; - internal ILibraryManager LibraryManager; - - public IntroInfo IntroInfo - { - get - { - var id = Item.Id; - - if (Type == ItemWithTrailerType.ItemWithTrailer) - { - var hasTrailers = Item as IHasTrailers; - - if (hasTrailers != null) - { - id = hasTrailers.LocalTrailerIds.FirstOrDefault(); - } - } - return new IntroInfo - { - ItemId = id - }; - } - } - } - - internal enum ItemWithTrailerType - { - ChannelTrailer, - ItemWithTrailer - } - } - - public class CinemaModeConfigurationFactory : IConfigurationFactory - { - public IEnumerable<ConfigurationStore> GetConfigurations() - { - return new[] - { - new ConfigurationStore - { - ConfigurationType = typeof(CinemaModeConfiguration), - Key = "cinemamode" - } - }; - } - } - -} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 56bffc233..f7706db47 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -409,24 +409,46 @@ namespace Emby.Server.Implementations.Library if (options.DeleteFileLocation && locationType != LocationType.Remote && locationType != LocationType.Virtual) { + // Assume only the first is required + // Add this flag to GetDeletePaths if required in the future + var isRequiredForDelete = true; + foreach (var fileSystemInfo in item.GetDeletePaths().ToList()) { - if (fileSystemInfo.IsDirectory) + try { - _logger.Debug("Deleting path {0}", fileSystemInfo.FullName); - _fileSystem.DeleteDirectory(fileSystemInfo.FullName, true); + if (fileSystemInfo.IsDirectory) + { + _logger.Debug("Deleting path {0}", fileSystemInfo.FullName); + _fileSystem.DeleteDirectory(fileSystemInfo.FullName, true); + } + else + { + _logger.Debug("Deleting path {0}", fileSystemInfo.FullName); + _fileSystem.DeleteFile(fileSystemInfo.FullName); + } } - else + catch (IOException) { - _logger.Debug("Deleting path {0}", fileSystemInfo.FullName); - _fileSystem.DeleteFile(fileSystemInfo.FullName); + if (isRequiredForDelete) + { + throw; + } } + catch (UnauthorizedAccessException) + { + if (isRequiredForDelete) + { + throw; + } + } + + isRequiredForDelete = false; } if (parent != null) { - await parent.ValidateChildren(new Progress<double>(), CancellationToken.None) - .ConfigureAwait(false); + await parent.ValidateChildren(new Progress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false) .ConfigureAwait(false); } } else if (parent != null) diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index eb0d0cf9b..3b11a4767 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -434,6 +434,11 @@ namespace Emby.Server.Implementations.Library Policy = user.Policy }; + if (!hasPassword && Users.Count() == 1) + { + dto.EnableAutoLogin = true; + } + var image = user.GetImageInfo(ImageType.Primary, 0); if (image != null) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index f76735030..a36cb124d 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -282,6 +282,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings var showId = programInfo.programID ?? string.Empty; + if (!info.IsSeries) + { + // It's also a series if it starts with SH + info.IsSeries = showId.StartsWith("SH", StringComparison.OrdinalIgnoreCase) && showId.Length >= 14; + } + // According to SchedulesDirect, these are generic, unidentified episodes // SH005316560000 var hasUniqueShowId = !showId.StartsWith("SH", StringComparison.OrdinalIgnoreCase) || @@ -331,7 +337,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var gracenote = details.metadata.Find(x => x.Gracenote != null).Gracenote; info.SeasonNumber = gracenote.season; - info.EpisodeNumber = gracenote.episode; + + if (gracenote.episode > 0) + { + info.EpisodeNumber = gracenote.episode; + } } } diff --git a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs b/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs index e2f973699..9a8a930bd 100644 --- a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; namespace Emby.Server.Implementations.LiveTv @@ -16,6 +15,8 @@ namespace Emby.Server.Implementations.LiveTv private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; + const int AnalyzeDurationMs = 2000; + public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger) { _mediaEncoder = mediaEncoder; @@ -34,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv Protocol = mediaSource.Protocol, MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, ExtractChapters = false, - AnalyzeDurationSections = 2 + AnalyzeDurationMs = AnalyzeDurationMs }, cancellationToken).ConfigureAwait(false); @@ -98,6 +99,8 @@ namespace Emby.Server.Implementations.LiveTv // Try to estimate this mediaSource.InferTotalBitrate(true); + + mediaSource.AnalyzeDurationMs = AnalyzeDurationMs; } } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 654777b3c..618cd1d45 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1596,6 +1596,7 @@ namespace Emby.Server.Implementations.LiveTv IsFolder = false, IsVirtualItem = false, Limit = query.Limit, + StartIndex = query.StartIndex, SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, EnableTotalRecordCount = query.EnableTotalRecordCount, diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 2e70e1ac1..62385e172 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -28,13 +28,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; private readonly IServerApplicationHost _appHost; + private readonly ISocketFactory _socketFactory; + private readonly INetworkManager _networkManager; - public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost) + public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder) { _httpClient = httpClient; _fileSystem = fileSystem; _appHost = appHost; + _socketFactory = socketFactory; + _networkManager = networkManager; } public string Name @@ -65,9 +69,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken) { + var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); + var options = new HttpRequestOptions { - Url = string.Format("{0}/lineup.json", GetApiUrl(info, false)), + Url = model.LineupURL, CancellationToken = cancellationToken, BufferContent = false }; @@ -84,11 +90,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } + private class HdHomerunChannelInfo : ChannelInfo + { + public bool IsLegacyTuner { get; set; } + public string Url { get; set; } + } + protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken) { var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false); - return lineup.Select(i => new ChannelInfo + return lineup.Select(i => new HdHomerunChannelInfo { Name = i.GuideName, Number = i.GuideNumber, @@ -98,20 +110,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun IsHD = i.HD == 1, AudioCodec = i.AudioCodec, VideoCodec = i.VideoCodec, - ChannelType = ChannelType.TV + ChannelType = ChannelType.TV, + IsLegacyTuner = (i.URL ?? string.Empty).StartsWith("hdhomerun", StringComparison.OrdinalIgnoreCase), + Url = i.URL - }).ToList(); + }).Cast<ChannelInfo>().ToList(); } private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>(); - private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken) + private async Task<DiscoverResponse> GetModelInfo(TunerHostInfo info, bool throwAllExceptions, CancellationToken cancellationToken) { lock (_modelCache) { DiscoverResponse response; if (_modelCache.TryGetValue(info.Url, out response)) { - return response.ModelNumber; + return response; } } @@ -130,72 +144,70 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream); - lock (_modelCache) + if (!string.IsNullOrWhiteSpace(info.Id)) { - _modelCache[info.Id] = response; + lock (_modelCache) + { + _modelCache[info.Id] = response; + } } - return response.ModelNumber; + return response; } } catch (HttpException ex) { - if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound) + if (!throwAllExceptions && ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound) { var defaultValue = "HDHR"; - // HDHR4 doesn't have this api - lock (_modelCache) + var response = new DiscoverResponse + { + ModelNumber = defaultValue + }; + if (!string.IsNullOrWhiteSpace(info.Id)) { - _modelCache[info.Id] = new DiscoverResponse + // HDHR4 doesn't have this api + lock (_modelCache) { - ModelNumber = defaultValue - }; + _modelCache[info.Id] = response; + } } - return defaultValue; + return response; } throw; } } - public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken) + private async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken) { - var model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false); + var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - using (var stream = await _httpClient.Get(new HttpRequestOptions() - { - Url = string.Format("{0}/tuners.html", GetApiUrl(info, false)), - CancellationToken = cancellationToken, - TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), - BufferContent = false + var tuners = new List<LiveTvTunerInfo>(); + + var uri = new Uri(GetApiUrl(info, false)); - }).ConfigureAwait(false)) + using (var manager = new HdHomerunManager(_socketFactory)) { - var tuners = new List<LiveTvTunerInfo>(); - using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8)) + // Legacy HdHomeruns are IPv4 only + var ipInfo = _networkManager.ParseIpAddress(uri.Host); + + for (int i = 0; i < model.TunerCount; ++i) { - while (!sr.EndOfStream) + var name = String.Format("Tuner {0}", i + 1); + var currentChannel = "none"; /// @todo Get current channel and map back to Station Id + var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false); + LiveTvTunerStatus status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv; + tuners.Add(new LiveTvTunerInfo { - string line = StripXML(sr.ReadLine()); - if (line.Contains("Channel")) - { - LiveTvTunerStatus status; - var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); - var name = line.Substring(0, index - 1); - var currentChannel = line.Substring(index + 7); - if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; } - tuners.Add(new LiveTvTunerInfo - { - Name = name, - SourceType = string.IsNullOrWhiteSpace(model) ? Name : model, - ProgramName = currentChannel, - Status = status - }); - } - } + Name = name, + SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber, + ProgramName = currentChannel, + Status = status + }); } - return tuners; } + return tuners; } public async Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken) @@ -244,34 +256,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return uri.AbsoluteUri.TrimEnd('/'); } - private static string StripXML(string source) - { - char[] buffer = new char[source.Length]; - int bufferIndex = 0; - bool inside = false; - - for (int i = 0; i < source.Length; i++) - { - char let = source[i]; - if (let == '<') - { - inside = true; - continue; - } - if (let == '>') - { - inside = false; - continue; - } - if (!inside) - { - buffer[bufferIndex] = let; - bufferIndex++; - } - } - return new string(buffer, 0, bufferIndex); - } - private class Channels { public string GuideNumber { get; set; } @@ -284,13 +268,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public int HD { get; set; } } - private async Task<MediaSourceInfo> GetMediaSource(TunerHostInfo info, string channelId, string profile) + private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, ChannelInfo channelInfo, string profile) { int? width = null; int? height = null; bool isInterlaced = true; string videoCodec = null; - string audioCodec = "ac3"; + string audioCodec = null; int? videoBitrate = null; int? audioBitrate = null; @@ -344,21 +328,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun videoBitrate = 1000000; } - var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); - var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase)); - if (channel != null) + if (channelInfo != null) { if (string.IsNullOrWhiteSpace(videoCodec)) { - videoCodec = channel.VideoCodec; + videoCodec = channelInfo.VideoCodec; } - audioCodec = channel.AudioCodec; + audioCodec = channelInfo.AudioCodec; if (!videoBitrate.HasValue) { - videoBitrate = (channel.IsHD ?? true) ? 15000000 : 2000000; + videoBitrate = (channelInfo.IsHD ?? true) ? 15000000 : 2000000; } - audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000; + audioBitrate = (channelInfo.IsHD ?? true) ? 448000 : 192000; } // normalize @@ -443,6 +425,82 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return channelId.Split('_')[1]; } + private MediaSourceInfo GetLegacyMediaSource(TunerHostInfo info, string channelId, ChannelInfo channel) + { + int? width = null; + int? height = null; + bool isInterlaced = true; + string videoCodec = null; + string audioCodec = null; + + int? videoBitrate = null; + int? audioBitrate = null; + + if (channel != null) + { + if (string.IsNullOrWhiteSpace(videoCodec)) + { + videoCodec = channel.VideoCodec; + } + audioCodec = channel.AudioCodec; + } + + // normalize + if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase)) + { + videoCodec = "mpeg2video"; + } + + string nal = null; + + var url = GetApiUrl(info, false); + var id = channelId; + id += "_" + url.GetMD5().ToString("N"); + + var mediaSource = new MediaSourceInfo + { + Path = url, + Protocol = MediaProtocol.Udp, + MediaStreams = new List<MediaStream> + { + new MediaStream + { + Type = MediaStreamType.Video, + // Set the index to -1 because we don't know the exact index of the video stream within the container + Index = -1, + IsInterlaced = isInterlaced, + Codec = videoCodec, + Width = width, + Height = height, + BitRate = videoBitrate, + NalLengthSize = nal + + }, + new MediaStream + { + Type = MediaStreamType.Audio, + // Set the index to -1 because we don't know the exact index of the audio stream within the container + Index = -1, + Codec = audioCodec, + BitRate = audioBitrate + } + }, + RequiresOpening = true, + RequiresClosing = true, + BufferMs = 0, + Container = "ts", + Id = id, + SupportsDirectPlay = false, + SupportsDirectStream = true, + SupportsTranscoding = true, + IsInfiniteStream = true + }; + + mediaSource.InferTotalBitrate(); + + return mediaSource; + } + protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) { var list = new List<MediaSourceInfo>(); @@ -453,35 +511,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } var hdhrId = GetHdHrIdFromChannelId(channelId); - try - { - var model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false); - model = model ?? string.Empty; + var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); + var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)); + + var hdHomerunChannelInfo = channelInfo as HdHomerunChannelInfo; - if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)) + var isLegacyTuner = hdHomerunChannelInfo != null && hdHomerunChannelInfo.IsLegacyTuner; + + if (isLegacyTuner) + { + list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo)); + } + else + { + try { - list.Add(await GetMediaSource(info, hdhrId, "native").ConfigureAwait(false)); + var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); + var model = modelInfo == null ? string.Empty : (modelInfo.ModelNumber ?? string.Empty); - if (info.AllowHWTranscoding) + if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)) { - list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false)); + list.Add(GetMediaSource(info, hdhrId, channelInfo, "native")); + + if (info.AllowHWTranscoding) + { + list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy")); - list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false)); + list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540")); + list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480")); + list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360")); + list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240")); + list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile")); + } } } - } - catch - { + catch + { - } + } - if (list.Count == 0) - { - list.Add(await GetMediaSource(info, hdhrId, "native").ConfigureAwait(false)); + if (list.Count == 0) + { + list.Add(GetMediaSource(info, hdhrId, channelInfo, "native")); + } } return list; @@ -509,11 +581,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } var hdhrId = GetHdHrIdFromChannelId(channelId); - var mediaSource = await GetMediaSource(info, hdhrId, profile).ConfigureAwait(false); + var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); + var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)); + + var hdhomerunChannel = channelInfo as HdHomerunChannelInfo; - var liveStream = new HdHomerunLiveStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); - liveStream.EnableStreamSharing = true; - return liveStream; + if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner) + { + var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo); + var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); + + return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); + } + else + { + var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile); + //var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); + + return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + //return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); + } } public async Task Validate(TunerHostInfo info) @@ -531,18 +618,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun try { // Test it by pulling down the lineup - using (var stream = await _httpClient.Get(new HttpRequestOptions - { - Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), - CancellationToken = CancellationToken.None, - BufferContent = false - - }).ConfigureAwait(false)) - { - var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream); - - info.DeviceId = response.DeviceID; - } + var modelInfo = await GetModelInfo(info, true, CancellationToken.None).ConfigureAwait(false); + info.DeviceId = modelInfo.DeviceID; } catch (HttpException ex) { @@ -573,6 +650,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public string DeviceAuth { get; set; } public string BaseURL { get; set; } public string LineupURL { get; set; } + public int TunerCount { get; set; } } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs index 625e4457d..2798805fa 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.MediaInfo; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { - public class HdHomerunLiveStream : LiveStream, IDirectStreamProvider + public class HdHomerunHttpStream : LiveStream, IDirectStreamProvider { private readonly ILogger _logger; private readonly IHttpClient _httpClient; @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); private readonly MulticastStream _multicastStream; - public HdHomerunLiveStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost) + public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost) : base(mediaSource) { _fileSystem = fileSystem; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs new file mode 100644 index 000000000..2c678d9f8 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -0,0 +1,453 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Net; + +namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun +{ + public interface IHdHomerunChannelCommands + { + IEnumerable<Tuple<string, string>> GetCommands(); + } + + public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands + { + private string _channel; + private string _program; + public LegacyHdHomerunChannelCommands(string url) + { + // parse url for channel and program + var regExp = new Regex(@"\/ch(\d+)-?(\d*)"); + var match = regExp.Match(url); + if (match.Success) + { + _channel = match.Groups[1].Value; + _program = match.Groups[2].Value; + } + } + + public IEnumerable<Tuple<string, string>> GetCommands() + { + var commands = new List<Tuple<string, string>>(); + + if (!String.IsNullOrEmpty(_channel)) + commands.Add(Tuple.Create("channel", _channel)); + + if (!String.IsNullOrEmpty(_program)) + commands.Add(Tuple.Create("program", _program)); + return commands; + } + } + + public class HdHomerunChannelCommands : IHdHomerunChannelCommands + { + private string _channel; + + public HdHomerunChannelCommands(string channel) + { + _channel = channel; + } + + public IEnumerable<Tuple<string, string>> GetCommands() + { + var commands = new List<Tuple<string, string>>(); + + if (!String.IsNullOrEmpty(_channel)) + commands.Add(Tuple.Create("vchannel", _channel)); + + return commands; + } + } + + public class HdHomerunManager : IDisposable + { + public static int HdHomeRunPort = 65001; + // Message constants + private static byte GetSetName = 3; + private static byte GetSetValue = 4; + private static byte GetSetLockkey = 21; + private static ushort GetSetRequest = 4; + private static ushort GetSetReply = 5; + + private uint? _lockkey = null; + private int _activeTuner = 0; + private readonly ISocketFactory _socketFactory; + private IpAddressInfo _remoteIp; + + public HdHomerunManager(ISocketFactory socketFactory) + { + _socketFactory = socketFactory; + } + + public void Dispose() + { + var task = StopStreaming(); + + Task.WaitAll(task); + } + + public async Task<bool> CheckTunerAvailability(IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken) + { + using (var socket = _socketFactory.CreateTcpSocket(remoteIp, HdHomeRunPort)) + { + return await CheckTunerAvailability(socket, remoteIp, tuner, cancellationToken).ConfigureAwait(false); + } + } + + private async Task<bool> CheckTunerAvailability(ISocket socket, IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken) + { + var ipEndPoint = new IpEndPointInfo(remoteIp, HdHomeRunPort); + + var lockkeyMsg = CreateGetMessage(tuner, "lockkey"); + await socket.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken); + var response = await socket.ReceiveAsync(cancellationToken).ConfigureAwait(false); + string returnVal; + ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal); + + return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase); + } + + public async Task StartStreaming(IpAddressInfo remoteIp, IpAddressInfo localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken) + { + _remoteIp = remoteIp; + + using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort)) + { + if (!_lockkey.HasValue) + { + var rand = new Random(); + _lockkey = (uint)rand.Next(); + } + + var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort); + + for (int i = 0; i < numTuners; ++i) + { + if (!await CheckTunerAvailability(tcpClient, _remoteIp, i, cancellationToken).ConfigureAwait(false)) + continue; + + _activeTuner = i; + var lockKeyString = String.Format("{0:d}", _lockkey.Value); + var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); + await tcpClient.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); + var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + string returnVal; + // parse response to make sure it worked + if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) + continue; + + var commandList = commands.GetCommands(); + foreach(Tuple<string,string> command in commandList) + { + var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, _lockkey.Value); + await tcpClient.SendAsync(channelMsg, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); + response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + // parse response to make sure it worked + if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) + { + await ReleaseLockkey(tcpClient).ConfigureAwait(false); + continue; + } + + } + + var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort); + var targetMsg = CreateSetMessage(i, "target", targetValue, _lockkey.Value); + + await tcpClient.SendAsync(targetMsg, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); + response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + // parse response to make sure it worked + if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) + { + await ReleaseLockkey(tcpClient).ConfigureAwait(false); + continue; + } + + break; + } + } + } + + public async Task ChangeChannel(IHdHomerunChannelCommands commands, CancellationToken cancellationToken) + { + if (!_lockkey.HasValue) + return; + + using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort)) + { + var commandList = commands.GetCommands(); + foreach (Tuple<string, string> command in commandList) + { + var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey.Value); + await tcpClient.SendAsync(channelMsg, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false); + var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + // parse response to make sure it worked + string returnVal; + if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) + { + return; + } + } + } + } + + public async Task StopStreaming() + { + if (!_lockkey.HasValue) + return; + + using (var socket = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort)) + { + await ReleaseLockkey(socket).ConfigureAwait(false); + } + } + + private async Task ReleaseLockkey(ISocket tcpClient) + { + var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", _lockkey); + await tcpClient.SendAsync(releaseTarget, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false); + await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false); + var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", _lockkey); + _lockkey = null; + await tcpClient.SendAsync(releaseKeyMsg, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false); + await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false); + } + + private static byte[] CreateGetMessage(int tuner, string name) + { + var byteName = Encoding.UTF8.GetBytes(String.Format("/tuner{0}/{1}\0", tuner, name)); + int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length + + var message = new byte[messageLength]; + + int offset = InsertHeaderAndName(byteName, messageLength, message); + + bool flipEndian = BitConverter.IsLittleEndian; + + // calculate crc and insert at the end of the message + var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4)); + if (flipEndian) + Array.Reverse(crcBytes); + Buffer.BlockCopy(crcBytes, 0, message, offset, 4); + + return message; + } + + private static byte[] CreateSetMessage(int tuner, String name, String value, uint? lockkey) + { + var byteName = Encoding.UTF8.GetBytes(String.Format("/tuner{0}/{1}\0", tuner, name)); + var byteValue = Encoding.UTF8.GetBytes(String.Format("{0}\0", value)); + + int messageLength = byteName.Length + byteValue.Length + 12; + if (lockkey.HasValue) + messageLength += 6; + + var message = new byte[messageLength]; + + int offset = InsertHeaderAndName(byteName, messageLength, message); + + bool flipEndian = BitConverter.IsLittleEndian; + + message[offset] = GetSetValue; + offset++; + message[offset] = Convert.ToByte(byteValue.Length); + offset++; + Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length); + offset += byteValue.Length; + if (lockkey.HasValue) + { + message[offset] = GetSetLockkey; + offset++; + message[offset] = (byte)4; + offset++; + var lockKeyBytes = BitConverter.GetBytes(lockkey.Value); + if (flipEndian) + Array.Reverse(lockKeyBytes); + Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4); + offset += 4; + } + + // calculate crc and insert at the end of the message + var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4)); + if (flipEndian) + Array.Reverse(crcBytes); + Buffer.BlockCopy(crcBytes, 0, message, offset, 4); + + return message; + } + + private static int InsertHeaderAndName(byte[] byteName, int messageLength, byte[] message) + { + // check to see if we need to flip endiannes + bool flipEndian = BitConverter.IsLittleEndian; + int offset = 0; + + // create header bytes + var getSetBytes = BitConverter.GetBytes(GetSetRequest); + var msgLenBytes = BitConverter.GetBytes((ushort)(messageLength - 8)); // Subtrace 4 bytes for header and 4 bytes for crc + + if (flipEndian) + { + Array.Reverse(getSetBytes); + Array.Reverse(msgLenBytes); + } + + // insert header bytes into message + Buffer.BlockCopy(getSetBytes, 0, message, offset, 2); + offset += 2; + Buffer.BlockCopy(msgLenBytes, 0, message, offset, 2); + offset += 2; + + // insert tag name and length + message[offset] = GetSetName; + offset++; + message[offset] = Convert.ToByte(byteName.Length); + offset++; + + // insert name string + Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length); + offset += byteName.Length; + + return offset; + } + + private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal) + { + returnVal = String.Empty; + + if (numBytes < 4) + return false; + + var flipEndian = BitConverter.IsLittleEndian; + int offset = 0; + byte[] msgTypeBytes = new byte[2]; + Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length); + + if (flipEndian) + Array.Reverse(msgTypeBytes); + + var msgType = BitConverter.ToUInt16(msgTypeBytes, 0); + offset += 2; + + if (msgType != GetSetReply) + return false; + + byte[] msgLengthBytes = new byte[2]; + Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length); + if (flipEndian) + Array.Reverse(msgLengthBytes); + + var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0); + offset += 2; + + if (numBytes < msgLength + 8) + return false; + + var nameTag = buf[offset]; + offset++; + + var nameLength = buf[offset]; + offset++; + + // skip the name field to get to value for return + offset += nameLength; + + var valueTag = buf[offset]; + offset++; + + var valueLength = buf[offset]; + offset++; + + returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator + return true; + } + + private class HdHomerunCrc + { + private static UInt32[] crc_table = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; + + public static UInt32 GetCrc32(byte[] bytes, int numBytes) + { + var hash = 0xffffffff; + for (var i = 0; i < numBytes; i++) + hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff]; + + var tmp = ~hash & 0xffffffff; + var b0 = tmp & 0xff; + var b1 = (tmp >> 8) & 0xff; + var b2 = (tmp >> 16) & 0xff; + var b3 = (tmp >> 24) & 0xff; + hash = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + return hash; + } + } + } +} diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs new file mode 100644 index 000000000..a881d0ea1 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; + +namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun +{ + public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider + { + private readonly ILogger _logger; + private readonly IHttpClient _httpClient; + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationPaths _appPaths; + private readonly IServerApplicationHost _appHost; + private readonly ISocketFactory _socketFactory; + + private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); + private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); + private readonly MulticastStream _multicastStream; + private readonly IHdHomerunChannelCommands _channelCommands; + private readonly int _numTuners; + private readonly INetworkManager _networkManager; + + public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager) + : base(mediaSource) + { + _fileSystem = fileSystem; + _httpClient = httpClient; + _logger = logger; + _appPaths = appPaths; + _appHost = appHost; + _socketFactory = socketFactory; + _networkManager = networkManager; + OriginalStreamId = originalStreamId; + _multicastStream = new MulticastStream(_logger); + _channelCommands = channelCommands; + _numTuners = numTuners; + } + + protected override async Task OpenInternal(CancellationToken openCancellationToken) + { + _liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); + + var mediaSource = OriginalMediaSource; + + var uri = new Uri(mediaSource.Path); + var localPort = _networkManager.GetRandomUnusedUdpPort(); + + _logger.Info("Opening HDHR UDP Live stream from {0}", uri.Host); + + var taskCompletionSource = new TaskCompletionSource<bool>(); + + StartStreaming(uri.Host, localPort, taskCompletionSource, _liveStreamCancellationTokenSource.Token); + + //OpenedMediaSource.Protocol = MediaProtocol.File; + //OpenedMediaSource.Path = tempFile; + //OpenedMediaSource.ReadAtNativeFramerate = true; + + OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; + OpenedMediaSource.Protocol = MediaProtocol.Http; + OpenedMediaSource.SupportsDirectPlay = false; + OpenedMediaSource.SupportsDirectStream = true; + OpenedMediaSource.SupportsTranscoding = true; + + await taskCompletionSource.Task.ConfigureAwait(false); + + //await Task.Delay(5000).ConfigureAwait(false); + } + + public override Task Close() + { + _logger.Info("Closing HDHR UDP live stream"); + _liveStreamCancellationTokenSource.Cancel(); + + return _liveStreamTaskCompletionSource.Task; + } + + private async Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) + { + await Task.Run(async () => + { + var isFirstAttempt = true; + using (var udpClient = _socketFactory.CreateUdpSocket(localPort)) + { + using (var hdHomerunManager = new HdHomerunManager(_socketFactory)) + { + var remoteAddress = _networkManager.ParseIpAddress(remoteIp); + IpAddressInfo localAddress = null; + using (var tcpSocket = _socketFactory.CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, false)) + { + try + { + tcpSocket.Connect(new IpEndPointInfo(remoteAddress, HdHomerunManager.HdHomeRunPort)); + localAddress = tcpSocket.LocalEndPoint.IpAddress; + tcpSocket.Close(); + } + catch (Exception) + { + _logger.Error("Unable to determine local ip address for Legacy HDHomerun stream."); + return; + } + } + + while (!cancellationToken.IsCancellationRequested) + { + try + { + // send url to start streaming + await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false); + + var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + _logger.Info("Opened HDHR UDP stream from {0}", remoteAddress); + + if (!cancellationToken.IsCancellationRequested) + { + Action onStarted = null; + if (isFirstAttempt) + { + onStarted = () => openTaskCompletionSource.TrySetResult(true); + } + + var stream = new UdpClientStream(udpClient); + await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + if (isFirstAttempt) + { + _logger.ErrorException("Error opening live stream:", ex); + openTaskCompletionSource.TrySetException(ex); + break; + } + + _logger.ErrorException("Error copying live stream, will reopen", ex); + } + + isFirstAttempt = false; + } + + await hdHomerunManager.StopStreaming().ConfigureAwait(false); + udpClient.Dispose(); + _liveStreamTaskCompletionSource.TrySetResult(true); + } + } + + }).ConfigureAwait(false); + } + + public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) + { + return _multicastStream.CopyToAsync(stream); + } + } + + // This handles the ReadAsync function only of a Stream object + // This is used to wrap a UDP socket into a stream for MulticastStream which only uses ReadAsync + public class UdpClientStream : Stream + { + private static int RtpHeaderBytes = 12; + private static int PacketSize = 1316; + private readonly ISocket _udpClient; + bool disposed; + + public UdpClientStream(ISocket udpClient) : base() + { + _udpClient = udpClient; + } + + public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + + if (offset + count < 0) + throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count"); + + if (offset + count > buffer.Length) + throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count"); + + if (disposed) + throw new ObjectDisposedException(typeof(UdpClientStream).ToString()); + + // This will always receive a 1328 packet size (PacketSize + RtpHeaderSize) + // The RTP header will be stripped so see how many reads we need to make to fill the buffer. + int numReads = count / PacketSize; + int totalBytesRead = 0; + + for (int i = 0; i < numReads; ++i) + { + var data = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + + var bytesRead = data.ReceivedBytes - RtpHeaderBytes; + + // remove rtp header + Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead); + offset += bytesRead; + totalBytesRead += bytesRead; + } + return totalBytesRead; + } + + protected override void Dispose(bool disposing) + { + disposed = true; + } + + public override bool CanRead + { + get + { + throw new NotImplementedException(); + } + } + + public override bool CanSeek + { + get + { + throw new NotImplementedException(); + } + } + + public override bool CanWrite + { + get + { + throw new NotImplementedException(); + } + } + + public override long Length + { + get + { + throw new NotImplementedException(); + } + } + + public override long Position + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + public override void Flush() + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } +} diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 8366c2d57..a939cec7b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Id = channel.Path.GetMD5().ToString("N"), IsInfiniteStream = true, - SupportsDirectStream = false, IsRemote = true }; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs index df83d4341..90ff36441 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs @@ -16,7 +16,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private const int BufferSize = 81920; private CancellationToken _cancellationToken; private readonly ILogger _logger; - private readonly ConcurrentQueue<byte[]> _sharedBuffer = new ConcurrentQueue<byte[]>(); public MulticastStream(ILogger logger) { @@ -38,14 +37,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts byte[] copy = new byte[bytesRead]; Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead); - _sharedBuffer.Enqueue(copy); - - while (_sharedBuffer.Count > 3000) - { - byte[] bytes; - _sharedBuffer.TryDequeue(out bytes); - } - var allStreams = _outputStreams.ToList(); foreach (var stream in allStreams) { @@ -74,16 +65,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts OnFinished = OnFinished }; - var list = new List<byte>(); - foreach (var bytes in _sharedBuffer) - { - list.AddRange(bytes); - } - - _logger.Info("QueueStream started with {0} initial bytes", list.Count); - - result.Queue(list.ToArray()); - _outputStreams.TryAdd(result.Id, result); result.Start(_cancellationToken); diff --git a/Emby.Server.Implementations/Sorting/BudgetComparer.cs b/Emby.Server.Implementations/Sorting/BudgetComparer.cs deleted file mode 100644 index f3aef69f1..000000000 --- a/Emby.Server.Implementations/Sorting/BudgetComparer.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace Emby.Server.Implementations.Sorting -{ - public class BudgetComparer : IBaseItemComparer - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - private double GetValue(BaseItem x) - { - var hasBudget = x as IHasBudget; - if (hasBudget != null) - { - return hasBudget.Budget ?? 0; - } - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.Budget; } - } - } -} diff --git a/Emby.Server.Implementations/Sorting/RevenueComparer.cs b/Emby.Server.Implementations/Sorting/RevenueComparer.cs deleted file mode 100644 index 62e43eac1..000000000 --- a/Emby.Server.Implementations/Sorting/RevenueComparer.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace Emby.Server.Implementations.Sorting -{ - public class RevenueComparer : IBaseItemComparer - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - private double GetValue(BaseItem x) - { - var hasBudget = x as IHasBudget; - if (hasBudget != null) - { - return hasBudget.Revenue ?? 0; - } - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.Revenue; } - } - } -} diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 691ff0699..bb303d8fa 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.Udp /// <summary> /// The _udp client /// </summary> - private IUdpSocket _udpClient; + private ISocket _udpClient; private readonly ISocketFactory _socketFactory; /// <summary> @@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.Udp { try { - var result = await _udpClient.ReceiveAsync().ConfigureAwait(false); + var result = await _udpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false); OnMessageReceived(result); } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 0420900c5..a13dd0569 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -162,15 +162,15 @@ namespace Emby.Server.Implementations.Updates string packageType = null, Version applicationVersion = null) { - var data = new Dictionary<string, string> - { - { "key", _securityManager.SupporterKey }, - { "mac", _applicationHost.SystemId }, - { "systemid", _applicationHost.SystemId } - }; - if (withRegistration) { + var data = new Dictionary<string, string> + { + { "key", _securityManager.SupporterKey }, + { "mac", _applicationHost.SystemId }, + { "systemid", _applicationHost.SystemId } + }; + using (var json = await _httpClient.Post("https://www.mb3admin.com/admin/service/package/retrieveall", data, cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); @@ -353,7 +353,7 @@ namespace Emby.Server.Implementations.Updates /// <returns>Task{PackageVersionInfo}.</returns> public async Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version) { - var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); + var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false); var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase)) ?? packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); @@ -376,7 +376,7 @@ namespace Emby.Server.Implementations.Updates /// <returns>Task{PackageVersionInfo}.</returns> public async Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release) { - var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); + var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false); return GetLatestCompatibleVersion(packages, name, guid, currentServerVersion, classification); } |
