diff options
Diffstat (limited to 'Emby.Server.Implementations/LiveTv')
12 files changed, 356 insertions, 334 deletions
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 4a2836d59..be5e57539 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1222,7 +1222,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logger.Info("Closing live stream {0}", id); - await stream.Close().ConfigureAwait(false); + stream.Close(); _logger.Info("Live stream {0} closed successfully", id); } } @@ -1286,9 +1286,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Id = timer.Id }; - if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo)) + if (!_activeRecordings.ContainsKey(timer.Id)) { - await RecordStream(timer, recordingEndDate, activeRecordingInfo, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false); + await RecordStream(timer, recordingEndDate, activeRecordingInfo).ConfigureAwait(false); } else { @@ -1397,8 +1397,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return Path.Combine(recordPath, recordingFileName); } - private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, - ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) + private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo) { if (timer == null) { @@ -1420,19 +1419,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (programInfo != null) { CopyProgramInfoToTimerInfo(programInfo, timer); - //activeRecordingInfo.Program = programInfo; } string seriesPath = null; var recordPath = GetRecordingPath(timer, out seriesPath); var recordingStatus = RecordingStatus.New; + var recorder = await GetRecorder().ConfigureAwait(false); + string liveStreamId = null; try { - var recorder = await GetRecorder().ConfigureAwait(false); - var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); _logger.Info("Opening recording stream from tuner provider"); @@ -1442,14 +1440,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var mediaStreamInfo = liveStreamInfo.Item2; liveStreamId = mediaStreamInfo.Id; - // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg - //await Task.Delay(3000, cancellationToken).ConfigureAwait(false); - recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = EnsureFileUnique(recordPath, timer.Id); _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); - activeRecordingInfo.Path = recordPath; var duration = recordingEndDate - DateTime.UtcNow; @@ -1459,15 +1453,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Action onStarted = async () => { + activeRecordingInfo.Path = recordPath; + + _activeRecordings.TryAdd(timer.Id, activeRecordingInfo); + timer.Status = RecordingStatus.InProgress; _timerProvider.AddOrUpdate(timer, false); await SaveRecordingMetadata(timer, recordPath, seriesPath).ConfigureAwait(false); + + CreateRecordingFolders(); + TriggerRefresh(recordPath); EnforceKeepUpTo(timer, seriesPath); }; - await recorder.Record(liveStreamInfo.Item1 as IDirectStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false); + await recorder.Record(liveStreamInfo.Item1 as IDirectStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false); recordingStatus = RecordingStatus.Completed; _logger.Info("Recording completed: {0}", recordPath); @@ -1507,6 +1508,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds); timer.Status = RecordingStatus.New; + timer.PrePaddingSeconds = 0; timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds); timer.RetryCount++; _timerProvider.AddOrUpdate(timer); @@ -1526,13 +1528,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private void TriggerRefresh(string path) { - _logger.Debug("Triggering refresh on {0}", path); + _logger.Info("Triggering refresh on {0}", path); var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path)); if (item != null) { - _logger.Debug("Refreshing recording parent {0}", item.Path); + _logger.Info("Refreshing recording parent {0}", item.Path); _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 149f69e5b..d6f5e0d9f 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -272,11 +272,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private bool EncodeVideo(MediaSourceInfo mediaSource) { - if (string.Equals(_liveTvOptions.RecordedVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>(); return !mediaStreams.Any(i => i.Type == MediaStreamType.Video && string.Equals(i.Codec, "h264", StringComparison.OrdinalIgnoreCase) && !i.IsInterlaced); } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index b7cfdea1b..b3c7ecc9f 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -541,27 +541,30 @@ namespace Emby.Server.Implementations.LiveTv.Listings try { - using (Stream responce = await Get(options, false, info).ConfigureAwait(false)) + using (var httpResponse = await Get(options, false, info).ConfigureAwait(false)) { - var root = _jsonSerializer.DeserializeFromStream<List<ScheduleDirect.Headends>>(responce); - - if (root != null) + using (Stream responce = httpResponse.Content) { - foreach (ScheduleDirect.Headends headend in root) + var root = _jsonSerializer.DeserializeFromStream<List<ScheduleDirect.Headends>>(responce); + + if (root != null) { - foreach (ScheduleDirect.Lineup lineup in headend.lineups) + foreach (ScheduleDirect.Headends headend in root) { - lineups.Add(new NameIdPair + foreach (ScheduleDirect.Lineup lineup in headend.lineups) { - Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name, - Id = lineup.uri.Substring(18) - }); + lineups.Add(new NameIdPair + { + Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name, + Id = lineup.uri.Substring(18) + }); + } } } - } - else - { - _logger.Info("No lineups available"); + else + { + _logger.Info("No lineups available"); + } } } } @@ -647,6 +650,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings bool enableRetry, ListingsProviderInfo providerInfo) { + // Schedules direct requires that the client support compression and will return a 400 response without it + options.EnableHttpCompression = true; + try { return await _httpClient.Post(options).ConfigureAwait(false); @@ -671,13 +677,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings return await Post(options, false, providerInfo).ConfigureAwait(false); } - private async Task<Stream> Get(HttpRequestOptions options, + private async Task<HttpResponseInfo> Get(HttpRequestOptions options, bool enableRetry, ListingsProviderInfo providerInfo) { + // Schedules direct requires that the client support compression and will return a 400 response without it + options.EnableHttpCompression = true; + try { - return await _httpClient.Get(options).ConfigureAwait(false); + return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); } catch (HttpException ex) { @@ -797,11 +806,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings try { - using (var response = await Get(options, false, null).ConfigureAwait(false)) + using (var httpResponse = await Get(options, false, null).ConfigureAwait(false)) { - var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Lineups>(response); + using (var response = httpResponse.Content) + { + var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Lineups>(response); - return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase)); + return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase)); + } } } catch (HttpException ex) @@ -879,53 +891,56 @@ namespace Emby.Server.Implementations.LiveTv.Listings var list = new List<ChannelInfo>(); - using (var response = await Get(httpOptions, true, info).ConfigureAwait(false)) + using (var httpResponse = await Get(httpOptions, true, info).ConfigureAwait(false)) { - var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Channel>(response); - _logger.Info("Found " + root.map.Count + " channels on the lineup on ScheduleDirect"); - _logger.Info("Mapping Stations to Channel"); - - var allStations = root.stations ?? new List<ScheduleDirect.Station>(); - - foreach (ScheduleDirect.Map map in root.map) + using (var response = httpResponse.Content) { - var channelNumber = GetChannelNumber(map); - - var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); - if (station == null) - { - station = new ScheduleDirect.Station - { - stationID = map.stationID - }; - } + var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Channel>(response); + _logger.Info("Found " + root.map.Count + " channels on the lineup on ScheduleDirect"); + _logger.Info("Mapping Stations to Channel"); - var name = channelNumber; + var allStations = root.stations ?? new List<ScheduleDirect.Station>(); - var channelInfo = new ChannelInfo + foreach (ScheduleDirect.Map map in root.map) { - Number = channelNumber, - Name = name - }; + var channelNumber = GetChannelNumber(map); - if (station != null) - { - if (!string.IsNullOrWhiteSpace(station.name)) + var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); + if (station == null) { - channelInfo.Name = station.name; + station = new ScheduleDirect.Station + { + stationID = map.stationID + }; } - channelInfo.Id = station.stationID; - channelInfo.CallSign = station.callsign; + var name = channelNumber; + + var channelInfo = new ChannelInfo + { + Number = channelNumber, + Name = name + }; - if (station.logo != null) + if (station != null) { - channelInfo.ImageUrl = station.logo.URL; - channelInfo.HasImage = true; + if (!string.IsNullOrWhiteSpace(station.name)) + { + channelInfo.Name = station.name; + } + + channelInfo.Id = station.stationID; + channelInfo.CallSign = station.callsign; + + if (station.logo != null) + { + channelInfo.ImageUrl = station.logo.URL; + channelInfo.HasImage = true; + } } - } - list.Add(channelInfo); + list.Add(channelInfo); + } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 8ea98879a..95ec1dee0 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -110,7 +110,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings var tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); _fileSystem.CreateDirectory(tempFolder); - _zipClient.ExtractAllFromGz(stream, tempFolder, true); + try + { + _zipClient.ExtractAllFromGz(stream, tempFolder, true); + } + catch + { + // If the extraction fails just return the original file, it could be a gz + return file; + } return _fileSystem.GetFiles(tempFolder, true) .Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index e6479feaa..15bbca136 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -89,6 +89,11 @@ namespace Emby.Server.Implementations.LiveTv if (channel != null) { dto.ChannelName = channel.Name; + + if (channel.HasImage(ImageType.Primary)) + { + dto.ChannelPrimaryImageTag = GetImageTag(channel); + } } return dto; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 857afa378..718620ab5 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -173,11 +173,11 @@ namespace Emby.Server.Implementations.LiveTv } } - public async Task<QueryResult<BaseItem>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) + public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); - var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); + var topFolder = GetInternalLiveTvFolder(cancellationToken); var internalQuery = new InternalItemsQuery(user) { @@ -527,18 +527,18 @@ namespace Emby.Server.Implementations.LiveTv item.ChannelType = channelInfo.ChannelType; item.ServiceName = serviceName; + + if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal)) + { + forceUpdate = true; + } item.Number = channelInfo.Number; - //if (!string.Equals(item.ProviderImageUrl, channelInfo.ImageUrl, StringComparison.OrdinalIgnoreCase)) - //{ - // isNew = true; - // replaceImages.Add(ImageType.Primary); - //} - //if (!string.Equals(item.ProviderImagePath, channelInfo.ImagePath, StringComparison.OrdinalIgnoreCase)) - //{ - // isNew = true; - // replaceImages.Add(ImageType.Primary); - //} + if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal)) + { + forceUpdate = true; + } + item.Name = channelInfo.Name; if (!item.HasImage(ImageType.Primary)) { @@ -554,18 +554,13 @@ namespace Emby.Server.Implementations.LiveTv } } - if (string.IsNullOrEmpty(item.Name)) - { - item.Name = channelInfo.Name; - } - if (isNew) { _libraryManager.CreateItem(item, cancellationToken); } else if (forceUpdate) { - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken); } await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem) @@ -760,7 +755,7 @@ namespace Emby.Server.Implementations.LiveTv return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated); } - private async Task<Guid> CreateRecordingRecord(RecordingInfo info, string serviceName, Guid parentFolderId, CancellationToken cancellationToken) + private Guid CreateRecordingRecord(RecordingInfo info, string serviceName, Guid parentFolderId, CancellationToken cancellationToken) { var isNew = false; @@ -892,7 +887,7 @@ namespace Emby.Server.Implementations.LiveTv else if (dataChanged || info.DateLastUpdated > recording.DateLastSaved || statusChanged) { metadataRefreshMode = MetadataRefreshMode.FullRefresh; - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken); } if (info.Status != RecordingStatus.InProgress) @@ -928,7 +923,7 @@ namespace Emby.Server.Implementations.LiveTv { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); - var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); + var topFolder = GetInternalLiveTvFolder(cancellationToken); if (query.OrderBy.Length == 0) { @@ -1007,11 +1002,11 @@ namespace Emby.Server.Implementations.LiveTv return result; } - public async Task<QueryResult<BaseItem>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken) + public QueryResult<BaseItem> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken) { var user = _userManager.GetUserById(query.UserId); - var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); + var topFolder = GetInternalLiveTvFolder(cancellationToken); var internalQuery = new InternalItemsQuery(user) { @@ -1072,11 +1067,11 @@ namespace Emby.Server.Implementations.LiveTv return result; } - public async Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken) + public QueryResult<BaseItemDto> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken) { RemoveFields(options); - var internalResult = await GetRecommendedProgramsInternal(query, options, cancellationToken).ConfigureAwait(false); + var internalResult = GetRecommendedProgramsInternal(query, options, cancellationToken); var user = _userManager.GetUserById(query.UserId); @@ -1302,7 +1297,7 @@ namespace Emby.Server.Implementations.LiveTv var list = new List<LiveTvChannel>(); var numComplete = 0; - var parentFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); + var parentFolder = GetInternalLiveTvFolder(cancellationToken); var parentFolderId = parentFolder.Id; foreach (var channelInfo in allChannelsList) @@ -1425,7 +1420,7 @@ namespace Emby.Server.Implementations.LiveTv // TODO: Do this in bulk foreach (var program in updatedPrograms) { - await _libraryManager.UpdateItem(program, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + _libraryManager.UpdateItem(program, ItemUpdateType.MetadataImport, cancellationToken); } currentChannel.IsMovie = isMovie; @@ -1434,7 +1429,7 @@ namespace Emby.Server.Implementations.LiveTv currentChannel.IsKids = isKids; currentChannel.IsSeries = iSSeries; - await currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken); } catch (OperationCanceledException) { @@ -1549,9 +1544,8 @@ namespace Emby.Server.Implementations.LiveTv var results = await Task.WhenAll(tasks).ConfigureAwait(false); - var recordingTasks = results.SelectMany(i => i.ToList()).Select(i => CreateRecordingRecord(i.Item1, i.Item2.Name, internalLiveTvFolderId, cancellationToken)); - - var idList = await Task.WhenAll(recordingTasks).ConfigureAwait(false); + var idList = results.SelectMany(i => i.ToList()).Select(i => CreateRecordingRecord(i.Item1, i.Item2.Name, internalLiveTvFolderId, cancellationToken)) + .ToArray(); await CleanDatabaseInternal(idList, new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false); @@ -1726,7 +1720,7 @@ namespace Emby.Server.Implementations.LiveTv return new QueryResult<BaseItem>(); } - var folder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); + var folder = GetInternalLiveTvFolder(cancellationToken); // TODO: Figure out how to merge emby recordings + service recordings if (_services.Length == 1) @@ -2143,18 +2137,6 @@ namespace Emby.Server.Implementations.LiveTv }; } - public Task OnRecordingFileDeleted(BaseItem recording) - { - var service = GetService(recording); - - if (service is EmbyTV.EmbyTV) - { - return service.DeleteRecordingAsync(GetItemExternalId(recording), CancellationToken.None); - } - - return Task.FromResult(true); - } - public async Task DeleteRecording(string recordingId) { var recording = await GetInternalRecording(recordingId, CancellationToken.None).ConfigureAwait(false); @@ -2171,13 +2153,17 @@ namespace Emby.Server.Implementations.LiveTv { var service = GetService(recording.ServiceName); - try - { - await service.DeleteRecordingAsync(GetItemExternalId(recording), CancellationToken.None).ConfigureAwait(false); - } - catch (ResourceNotFoundException) + if (service != null) { + // handle the service being uninstalled and the item hanging around in the database + try + { + await service.DeleteRecordingAsync(GetItemExternalId(recording), CancellationToken.None).ConfigureAwait(false); + } + catch (ResourceNotFoundException) + { + } } _lastRecordingRefreshTime = DateTime.MinValue; @@ -2387,7 +2373,7 @@ namespace Emby.Server.Implementations.LiveTv MinEndDate = now, Limit = channelIds.Length, OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }, - TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }, + TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id.ToString("N") }, DtoOptions = options }) : new List<BaseItem>(); @@ -2910,11 +2896,11 @@ namespace Emby.Server.Implementations.LiveTv return service.ResetTuner(parts[1], cancellationToken); } - public async Task<BaseItemDto> GetLiveTvFolder(string userId, CancellationToken cancellationToken) + public BaseItemDto GetLiveTvFolder(string userId, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(userId); - var folder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); + var folder = GetInternalLiveTvFolder(cancellationToken); return _dtoService.GetBaseItemDto(folder, new DtoOptions(), user); } @@ -2930,10 +2916,10 @@ namespace Emby.Server.Implementations.LiveTv options.Fields = fields.ToArray(fields.Count); } - public async Task<Folder> GetInternalLiveTvFolder(CancellationToken cancellationToken) + public Folder GetInternalLiveTvFolder(CancellationToken cancellationToken) { - var name = _localization.GetLocalizedString("ViewTypeLiveTV"); - return await _libraryManager.GetNamedView(name, CollectionType.LiveTv, name, cancellationToken).ConfigureAwait(false); + var name = _localization.GetLocalizedString("HeaderLiveTV"); + return _libraryManager.GetNamedView(name, CollectionType.LiveTv, name, cancellationToken); } public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index bb11dac5f..59346cdec 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -86,16 +86,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun CancellationToken = cancellationToken, BufferContent = false }; - using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) + using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)) { - var lineup = JsonSerializer.DeserializeFromStream<List<Channels>>(stream) ?? new List<Channels>(); - - if (info.ImportFavoritesOnly) + using (var stream = response.Content) { - lineup = lineup.Where(i => i.Favorite).ToList(); - } + var lineup = JsonSerializer.DeserializeFromStream<List<Channels>>(stream) ?? new List<Channels>(); + + if (info.ImportFavoritesOnly) + { + lineup = lineup.Where(i => i.Favorite).ToList(); + } - return lineup.Where(i => !i.DRM).ToList(); + return lineup.Where(i => !i.DRM).ToList(); + } } } @@ -143,26 +146,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun try { - using (var stream = await _httpClient.Get(new HttpRequestOptions() + using (var response = await _httpClient.SendAsync(new HttpRequestOptions() { Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), CancellationToken = cancellationToken, TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), BufferContent = false - }).ConfigureAwait(false)) + }, "GET").ConfigureAwait(false)) { - var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream); - - if (!string.IsNullOrWhiteSpace(info.Id)) + using (var stream = response.Content) { - lock (_modelCache) + var discoverResponse = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream); + + if (!string.IsNullOrWhiteSpace(info.Id)) { - _modelCache[info.Id] = response; + lock (_modelCache) + { + _modelCache[info.Id] = discoverResponse; + } } - } - return response; + return discoverResponse; + } } } catch (HttpException ex) @@ -197,7 +203,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var uri = new Uri(GetApiUrl(info, false)); - using (var manager = new HdHomerunManager(_socketFactory)) + using (var manager = new HdHomerunManager(_socketFactory, Logger)) { // Legacy HdHomeruns are IPv4 only var ipInfo = _networkManager.ParseIpAddress(uri.Host); @@ -299,6 +305,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int? videoBitrate = null; int? audioBitrate = null; + var isHd = channelInfo.IsHD ?? true; + if (string.Equals(profile, "mobile", StringComparison.OrdinalIgnoreCase)) { width = 1280; @@ -350,7 +358,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun else { // This is for android tv's 1200 condition. Remove once not needed anymore so that we can avoid possible side effects of dummying up this data - if ((channelInfo.IsHD ?? true)) + if (isHd) { width = 1920; height = 1080; @@ -367,9 +375,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (!videoBitrate.HasValue) { - videoBitrate = (channelInfo.IsHD ?? true) ? 15000000 : 2000000; + videoBitrate = isHd ? 15000000 : 2000000; } - audioBitrate = (channelInfo.IsHD ?? true) ? 448000 : 192000; + audioBitrate = isHd ? 448000 : 192000; } // normalize diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs index 604aa74f0..7e0e5fc5c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs @@ -22,9 +22,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IHttpClient _httpClient; private readonly IServerApplicationHost _appHost; - private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); - private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); - public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment) : base(mediaSource, environment, fileSystem, logger, appPaths) { @@ -33,19 +30,34 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun OriginalStreamId = originalStreamId; } - protected override Task OpenInternal(CancellationToken openCancellationToken) + public override async Task Open(CancellationToken openCancellationToken) { - _liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); + LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); var mediaSource = OriginalMediaSource; var url = mediaSource.Path; + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath)); + Logger.Info("Opening HDHR Live stream from {0}", url); - var taskCompletionSource = new TaskCompletionSource<bool>(); + var response = await _httpClient.SendAsync(new HttpRequestOptions + { + Url = url, + CancellationToken = CancellationToken.None, + BufferContent = false, + + // Increase a little bit + TimeoutMs = 30000, + + EnableHttpCompression = false + + }, "GET").ConfigureAwait(false); + + Logger.Info("Opened HDHR stream from {0}", url); - StartStreaming(url, taskCompletionSource, _liveStreamCancellationTokenSource.Token); + StartStreaming(response, LiveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Path = tempFile; @@ -59,77 +71,42 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun //OpenedMediaSource.SupportsDirectPlay = false; //OpenedMediaSource.SupportsDirectStream = true; //OpenedMediaSource.SupportsTranscoding = true; - - return taskCompletionSource.Task; - - //await Task.Delay(5000).ConfigureAwait(false); } - public override Task Close() + public override void Close() { Logger.Info("Closing HDHR live stream"); - _liveStreamCancellationTokenSource.Cancel(); - - return _liveStreamTaskCompletionSource.Task; + LiveStreamCancellationTokenSource.Cancel(); } - private Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) + private Task StartStreaming(HttpResponseInfo response, CancellationToken cancellationToken) { return Task.Run(async () => { - var isFirstAttempt = true; - - while (!cancellationToken.IsCancellationRequested) + try { - try + using (response) { - using (var response = await _httpClient.SendAsync(new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - BufferContent = false, - - // Increase a little bit - TimeoutMs = 30000, - - EnableHttpCompression = false - - }, "GET").ConfigureAwait(false)) + using (var stream = response.Content) { - Logger.Info("Opened HDHR stream from {0}", url); + Logger.Info("Beginning HdHomerunHttpStream stream to file"); - if (!cancellationToken.IsCancellationRequested) + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath)); + using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) { - Logger.Info("Beginning multicastStream.CopyUntilCancelled"); - - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath)); - using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) - { - StreamHelper.CopyTo(response.Content, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken); - } + StreamHelper.CopyTo(stream, fileStream, 81920, null, cancellationToken); } } } - 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; + } + catch (OperationCanceledException) + { + } + catch (Exception ex) + { + Logger.ErrorException("Error copying live stream.", ex); } - _liveStreamTaskCompletionSource.TrySetResult(true); await DeleteTempFile(TempFilePath).ConfigureAwait(false); }); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index c737c4cba..5156f1744 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Logging; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -89,9 +90,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly ISocketFactory _socketFactory; private IpAddressInfo _remoteIp; - public HdHomerunManager(ISocketFactory socketFactory) + private ILogger _logger; + + public HdHomerunManager(ISocketFactory socketFactory, ILogger logger) { _socketFactory = socketFactory; + _logger = logger; } public void Dispose() @@ -140,6 +144,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _lockkey = (uint)rand.Next(); } + var lockKeyValue = _lockkey.Value; + var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort); for (int i = 0; i < numTuners; ++i) @@ -148,7 +154,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun continue; _activeTuner = i; - var lockKeyString = String.Format("{0:d}", _lockkey.Value); + var lockKeyString = String.Format("{0:d}", lockKeyValue); var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); await tcpClient.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); @@ -160,27 +166,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var commandList = commands.GetCommands(); foreach(Tuple<string,string> command in commandList) { - var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, _lockkey.Value); + var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue); await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) { - await ReleaseLockkey(tcpClient).ConfigureAwait(false); + await ReleaseLockkey(tcpClient, lockKeyValue).ConfigureAwait(false); continue; } } var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort); - var targetMsg = CreateSetMessage(i, "target", targetValue, _lockkey.Value); + var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); await tcpClient.SendToAsync(targetMsg, 0, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false); response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal)) { - await ReleaseLockkey(tcpClient).ConfigureAwait(false); + await ReleaseLockkey(tcpClient, lockKeyValue).ConfigureAwait(false); continue; } @@ -201,7 +207,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun foreach (Tuple<string, string> command in commandList) { - var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey.Value); + var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey); await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false); var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false); // parse response to make sure it worked @@ -216,24 +222,28 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public async Task StopStreaming() { - if (!_lockkey.HasValue) + var lockKey = _lockkey; + + if (!lockKey.HasValue) return; using (var socket = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort)) { - await ReleaseLockkey(socket).ConfigureAwait(false); + await ReleaseLockkey(socket, lockKey.Value).ConfigureAwait(false); } } - private async Task ReleaseLockkey(ISocket tcpClient) + private async Task ReleaseLockkey(ISocket tcpClient, uint lockKeyValue) { - var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", _lockkey); + _logger.Info("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue); + + var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue); await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false); var receiveBuffer = new byte[8192]; await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false); - var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", _lockkey); + var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue); _lockkey = null; await tcpClient.SendToAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false); await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index ff8fd1bc4..06326d26c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -1,23 +1,16 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.IO; 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; using MediaBrowser.Model.System; -using System.Globalization; -using MediaBrowser.Controller.IO; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -26,8 +19,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IServerApplicationHost _appHost; private readonly ISocketFactory _socketFactory; - private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); - private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); private readonly IHdHomerunChannelCommands _channelCommands; private readonly int _numTuners; private readonly INetworkManager _networkManager; @@ -43,20 +34,62 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _numTuners = numTuners; } - protected override Task OpenInternal(CancellationToken openCancellationToken) + public override async Task Open(CancellationToken openCancellationToken) { - _liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); + LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); var mediaSource = OriginalMediaSource; var uri = new Uri(mediaSource.Path); var localPort = _networkManager.GetRandomUnusedUdpPort(); + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath)); + Logger.Info("Opening HDHR UDP Live stream from {0}", uri.Host); + var remoteAddress = _networkManager.ParseIpAddress(uri.Host); + 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; + } + } + + var udpClient = _socketFactory.CreateUdpSocket(localPort); + var hdHomerunManager = new HdHomerunManager(_socketFactory, Logger); + + try + { + // send url to start streaming + await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + using (udpClient) + { + using (hdHomerunManager) + { + if (!(ex is OperationCanceledException)) + { + Logger.ErrorException("Error opening live stream:", ex); + } + throw; + } + } + } + var taskCompletionSource = new TaskCompletionSource<bool>(); - StartStreaming(uri.Host, localPort, taskCompletionSource, _liveStreamCancellationTokenSource.Token); + StartStreaming(udpClient, hdHomerunManager, remoteAddress, localAddress, localPort, taskCompletionSource, LiveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Path = tempFile; @@ -68,86 +101,47 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun //OpenedMediaSource.SupportsDirectStream = true; //OpenedMediaSource.SupportsTranscoding = true; - return taskCompletionSource.Task; - //await Task.Delay(5000).ConfigureAwait(false); + await taskCompletionSource.Task.ConfigureAwait(false); } - public override Task Close() + public override void Close() { Logger.Info("Closing HDHR UDP live stream"); - _liveStreamCancellationTokenSource.Cancel(); - - return _liveStreamTaskCompletionSource.Task; + LiveStreamCancellationTokenSource.Cancel(); } - private Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) + private Task StartStreaming(ISocket udpClient, HdHomerunManager hdHomerunManager, IpAddressInfo remoteAddress, IpAddressInfo localAddress, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) { return Task.Run(async () => { - var isFirstAttempt = true; - using (var udpClient = _socketFactory.CreateUdpSocket(localPort)) + using (udpClient) { - using (var hdHomerunManager = new HdHomerunManager(_socketFactory)) + using (hdHomerunManager) { - var remoteAddress = _networkManager.ParseIpAddress(remoteIp); - IpAddressInfo localAddress = null; - using (var tcpSocket = _socketFactory.CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, false)) + try + { + await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException ex) { - 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; - } + Logger.Info("HDHR UDP stream cancelled or timed out from {0}", remoteAddress); + openTaskCompletionSource.TrySetException(ex); + } + catch (Exception ex) + { + Logger.ErrorException("Error opening live stream:", ex); + openTaskCompletionSource.TrySetException(ex); } - while (!cancellationToken.IsCancellationRequested) + try { - try - { - // send url to start streaming - await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false); - - Logger.Info("Opened HDHR UDP stream from {0}", remoteAddress); - - if (!cancellationToken.IsCancellationRequested) - { - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath)); - using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) - { - CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken); - } - } - } - catch (OperationCanceledException ex) - { - Logger.Info("HDHR UDP stream cancelled or timed out from {0}", remoteAddress); - openTaskCompletionSource.TrySetException(ex); - 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); } + catch + { - await hdHomerunManager.StopStreaming().ConfigureAwait(false); - _liveStreamTaskCompletionSource.TrySetResult(true); + } } } @@ -158,36 +152,45 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource) { Task.Run(() => - { - openTaskCompletionSource.TrySetResult(true); - }); + { + openTaskCompletionSource.TrySetResult(true); + }); } private static int RtpHeaderBytes = 12; - private void CopyTo(ISocket udpClient, Stream target, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) + private async Task CopyTo(ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) { - var source = _socketFactory.CreateNetworkStream(udpClient, false); var bufferSize = 81920; byte[] buffer = new byte[bufferSize]; int read; var resolved = false; - while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + using (var source = _socketFactory.CreateNetworkStream(udpClient, false)) { - cancellationToken.ThrowIfCancellationRequested(); + using (var fileStream = FileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) + { + var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token; - read -= RtpHeaderBytes; + while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); - if (read > 0) - { - target.Write(buffer, RtpHeaderBytes, read); - } + currentCancellationToken = cancellationToken; - if (!resolved) - { - resolved = true; - Resolve(openTaskCompletionSource); + read -= RtpHeaderBytes; + + if (read > 0) + { + fileStream.Write(buffer, RtpHeaderBytes, read); + } + + if (!resolved) + { + resolved = true; + Resolve(openTaskCompletionSource); + } + } } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 4641a1c91..12695cd8e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -32,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected readonly string TempFilePath; protected readonly ILogger Logger; + protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource(); public LiveStream(MediaSourceInfo mediaSource, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths) { @@ -46,19 +47,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts TempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts"); } - public Task Open(CancellationToken cancellationToken) - { - return OpenInternal(cancellationToken); - } - - protected virtual Task OpenInternal(CancellationToken cancellationToken) + public virtual Task Open(CancellationToken openCancellationToken) { return Task.FromResult(true); } - public virtual Task Close() + public virtual void Close() { - return Task.FromResult(true); } protected Stream GetInputStream(string path, bool allowAsyncFileRead) @@ -75,11 +70,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected async Task DeleteTempFile(string path, int retryCount = 0) { + if (retryCount == 0) + { + Logger.Info("Deleting temp file {0}", path); + } + try { FileSystem.DeleteFile(path); return; } + catch (DirectoryNotFoundException) + { + return; + } + catch (FileNotFoundException) + { + return; + } catch { @@ -96,6 +104,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token; + var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 @@ -110,16 +120,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private static async Task CopyTo(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) { byte[] buffer = new byte[bufferSize]; - while (true) + + var eofCount = 0; + var emptyReadLimit = 1000; + + while (eofCount < emptyReadLimit) { cancellationToken.ThrowIfCancellationRequested(); - var read = source.Read(buffer, 0, buffer.Length); + var bytesRead = source.Read(buffer, 0, buffer.Length); - if (read > 0) + if (bytesRead == 0) { + eofCount++; + await Task.Delay(10, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; + //await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); - destination.Write(buffer, 0, read); + destination.Write(buffer, 0, bytesRead); if (onStarted != null) { @@ -127,10 +148,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts onStarted = null; } } - else - { - await Task.Delay(10).ConfigureAwait(false); - } } } @@ -140,6 +157,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { stream.Seek(offset, SeekOrigin.End); } + catch (IOException) + { + + } catch (ArgumentException) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 8d1854f4b..9fc6687d1 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -93,13 +93,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) { - var channelIdPrefix = GetFullChannelIdPrefix(info); - - if (!channelId.StartsWith(channelIdPrefix, StringComparison.OrdinalIgnoreCase)) - { - return null; - } - var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false); var channel = channels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase)); if (channel != null) @@ -165,7 +158,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts RequiresOpening = true, RequiresClosing = true, RequiresLooping = info.EnableStreamLooping, - EnableMpDecimate = info.EnableMpDecimate, ReadAtNativeFramerate = false, |
