aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/LiveTv
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/LiveTv')
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs8
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs30
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs79
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs27
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs23
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs198
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs311
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs229
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs81
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs26
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs30
14 files changed, 499 insertions, 553 deletions
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index dd636e6cd..8dee7046e 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
UserAgent = "Emby/3.0",
// Shouldn't matter but may cause issues
- EnableHttpCompression = false
+ DecompressionMethod = CompressionMethod.None
};
using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 7b210d231..d7411af50 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -681,7 +681,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- timer.Id = Guid.NewGuid().ToString("N");
+ timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
LiveTvProgram programInfo = null;
@@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
{
- info.Id = Guid.NewGuid().ToString("N");
+ info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
// populate info.seriesID
var program = GetProgramInfoFromCache(info.ProgramId);
@@ -1059,7 +1059,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var json = _jsonSerializer.SerializeToString(mediaSource);
mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
- mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id;
+ mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id;
//if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing)
//{
@@ -2529,7 +2529,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var timer = new TimerInfo
{
ChannelId = channelId,
- Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N"),
+ Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N", CultureInfo.InvariantCulture),
StartDate = parent.StartDate,
EndDate = parent.EndDate.Value,
ProgramId = parent.ExternalId,
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 4137760d0..f5dffc22a 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -96,8 +96,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Url = ApiUrl + "/schedules",
UserAgent = UserAgent,
CancellationToken = cancellationToken,
- // The data can be large so give it some extra time
- TimeoutMs = 60000,
LogErrorResponseBody = true,
RequestContent = requestString
};
@@ -115,9 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Url = ApiUrl + "/programs",
UserAgent = UserAgent,
CancellationToken = cancellationToken,
- LogErrorResponseBody = true,
- // The data can be large so give it some extra time
- TimeoutMs = 60000
+ LogErrorResponseBody = true
};
httpOptions.RequestHeaders["token"] = token;
@@ -483,8 +479,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken = cancellationToken,
RequestContent = imageIdString,
LogErrorResponseBody = true,
- // The data can be large so give it some extra time
- TimeoutMs = 60000
};
try
@@ -633,15 +627,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
- options.EnableHttpCompression = true;
-
- // On windows 7 under .net core, this header is not getting added
-#if NETSTANDARD2_0
- if (Environment.OSVersion.Platform == PlatformID.Win32NT)
- {
- options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate";
- }
-#endif
+ options.DecompressionMethod = CompressionMethod.Deflate;
try
{
@@ -671,15 +657,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
- options.EnableHttpCompression = true;
-
- // On windows 7 under .net core, this header is not getting added
-#if NETSTANDARD2_0
- if (Environment.OSVersion.Platform == PlatformID.Win32NT)
- {
- options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate";
- }
-#endif
+ options.DecompressionMethod = CompressionMethod.Deflate;
try
{
@@ -871,8 +849,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
UserAgent = UserAgent,
CancellationToken = cancellationToken,
LogErrorResponseBody = true,
- // The data can be large so give it some extra time
- TimeoutMs = 60000
};
httpOptions.RequestHeaders["token"] = token;
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index 69b10e6da..88693f22a 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -2,14 +2,15 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.IO.Compression;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Emby.XmlTv.Classes;
using Emby.XmlTv.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
-using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
@@ -27,7 +28,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly IFileSystem _fileSystem;
private readonly IZipClient _zipClient;
- public XmlTvListingsProvider(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IFileSystem fileSystem, IZipClient zipClient)
+ public XmlTvListingsProvider(
+ IServerConfigurationManager config,
+ IHttpClient httpClient,
+ ILogger logger,
+ IFileSystem fileSystem,
+ IZipClient zipClient)
{
_config = config;
_httpClient = httpClient;
@@ -52,7 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private async Task<string> GetXml(string path, CancellationToken cancellationToken)
{
- _logger.LogInformation("xmltv path: {path}", path);
+ _logger.LogInformation("xmltv path: {Path}", path);
if (!path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
@@ -66,26 +72,33 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return UnzipIfNeeded(path, cacheFile);
}
- _logger.LogInformation("Downloading xmltv listings from {path}", path);
-
- string tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = path,
- Progress = new SimpleProgress<double>(),
- DecompressionMethod = CompressionMethod.Gzip,
-
- // It's going to come back gzipped regardless of this value
- // So we need to make sure the decompression method is set to gzip
- EnableHttpCompression = true,
-
- UserAgent = "Emby/3.0"
-
- }).ConfigureAwait(false);
+ _logger.LogInformation("Downloading xmltv listings from {Path}", path);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
- File.Copy(tempFile, cacheFile, true);
+ using (var res = await _httpClient.SendAsync(
+ new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = path,
+ DecompressionMethod = CompressionMethod.Gzip,
+ },
+ HttpMethod.Get).ConfigureAwait(false))
+ using (var stream = res.Content)
+ using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew))
+ {
+ if (res.ContentHeaders.ContentEncoding.Contains("gzip"))
+ {
+ using (var gzStream = new GZipStream(stream, CompressionMode.Decompress))
+ {
+ await gzStream.CopyToAsync(fileStream).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ await stream.CopyToAsync(fileStream).ConfigureAwait(false);
+ }
+ }
return UnzipIfNeeded(path, cacheFile);
}
@@ -103,7 +116,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error extracting from gz file {file}", file);
+ _logger.LogError(ex, "Error extracting from gz file {File}", file);
}
try
@@ -113,7 +126,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error extracting from zip file {file}", file);
+ _logger.LogError(ex, "Error extracting from zip file {File}", file);
}
}
@@ -161,20 +174,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
throw new ArgumentNullException(nameof(channelId));
}
- /*
- if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false))
- {
- var length = endDateUtc - startDateUtc;
- if (length.TotalDays > 1)
- {
- endDateUtc = startDateUtc.AddDays(1);
- }
- }*/
-
- _logger.LogDebug("Getting xmltv programs for channel {id}", channelId);
+ _logger.LogDebug("Getting xmltv programs for channel {Id}", channelId);
string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
- _logger.LogDebug("Opening XmlTvReader for {path}", path);
+ _logger.LogDebug("Opening XmlTvReader for {Path}", path);
var reader = new XmlTvReader(path, GetLanguage(info));
return reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken)
@@ -208,7 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null,
CommunityRating = program.StarRating,
- SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N")
+ SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N", CultureInfo.InvariantCulture)
};
if (string.IsNullOrWhiteSpace(program.ProgramId))
@@ -224,7 +227,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture);
}
- programInfo.ShowId = uniqueString.GetMD5().ToString("N");
+ programInfo.ShowId = uniqueString.GetMD5().ToString("N", CultureInfo.InvariantCulture);
// If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped
if (programInfo.IsSeries
@@ -267,7 +270,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
// In theory this should never be called because there is always only one lineup
string path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false);
- _logger.LogDebug("Opening XmlTvReader for {path}", path);
+ _logger.LogDebug("Opening XmlTvReader for {Path}", path);
var reader = new XmlTvReader(path, GetLanguage(info));
IEnumerable<XmlTvChannel> results = reader.GetChannels();
@@ -279,7 +282,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
// In theory this should never be called because there is always only one lineup
string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
- _logger.LogDebug("Opening XmlTvReader for {path}", path);
+ _logger.LogDebug("Opening XmlTvReader for {Path}", path);
var reader = new XmlTvReader(path, GetLanguage(info));
var results = reader.GetChannels();
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 1144c9ab1..e584664c9 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv
ExternalId = info.Id,
ChannelId = GetInternalChannelId(service.Name, info.ChannelId),
Status = info.Status,
- SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N"),
+ SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture),
PrePaddingSeconds = info.PrePaddingSeconds,
PostPaddingSeconds = info.PostPaddingSeconds,
IsPostPaddingRequired = info.IsPostPaddingRequired,
@@ -69,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv
if (!string.IsNullOrEmpty(info.ProgramId))
{
- dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N");
+ dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture);
}
if (program != null)
@@ -107,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv
{
var dto = new SeriesTimerInfoDto
{
- Id = GetInternalSeriesTimerId(info.Id).ToString("N"),
+ Id = GetInternalSeriesTimerId(info.Id).ToString("N", CultureInfo.InvariantCulture),
Overview = info.Overview,
EndDate = info.EndDate,
Name = info.Name,
@@ -139,7 +140,7 @@ namespace Emby.Server.Implementations.LiveTv
if (!string.IsNullOrEmpty(info.ProgramId))
{
- dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N");
+ dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture);
}
dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days.ToArray());
@@ -169,7 +170,7 @@ namespace Emby.Server.Implementations.LiveTv
try
{
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
- dto.ParentThumbItemId = librarySeries.Id.ToString("N");
+ dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
@@ -185,7 +186,7 @@ namespace Emby.Server.Implementations.LiveTv
{
_imageProcessor.GetImageCacheTag(librarySeries, image)
};
- dto.ParentBackdropItemId = librarySeries.Id.ToString("N");
+ dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
@@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.LiveTv
try
{
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
- dto.ParentPrimaryImageItemId = program.Id.ToString("N");
+ dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
@@ -232,7 +233,7 @@ namespace Emby.Server.Implementations.LiveTv
{
_imageProcessor.GetImageCacheTag(program, image)
};
- dto.ParentBackdropItemId = program.Id.ToString("N");
+ dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
@@ -263,7 +264,7 @@ namespace Emby.Server.Implementations.LiveTv
try
{
dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image);
- dto.ParentThumbItemId = librarySeries.Id.ToString("N");
+ dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
@@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.LiveTv
{
_imageProcessor.GetImageCacheTag(librarySeries, image)
};
- dto.ParentBackdropItemId = librarySeries.Id.ToString("N");
+ dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
@@ -320,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv
try
{
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
- dto.ParentPrimaryImageItemId = program.Id.ToString("N");
+ dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
@@ -339,7 +340,7 @@ namespace Emby.Server.Implementations.LiveTv
{
_imageProcessor.GetImageCacheTag(program, image)
};
- dto.ParentBackdropItemId = program.Id.ToString("N");
+ dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
@@ -407,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv
{
var name = ServiceName + externalId + InternalVersionNumber;
- return name.ToLowerInvariant().GetMD5().ToString("N");
+ return name.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
public Guid GetInternalSeriesTimerId(string externalId)
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 9093d9740..1e5198dd6 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -258,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv
}
info.RequiresClosing = true;
- var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
+ var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
info.LiveStreamId = idPrefix + info.Id;
@@ -820,7 +821,7 @@ namespace Emby.Server.Implementations.LiveTv
if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
{
var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
- var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
+ var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N", CultureInfo.InvariantCulture), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
if (seriesTimer != null)
{
internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
@@ -997,7 +998,7 @@ namespace Emby.Server.Implementations.LiveTv
if (!string.IsNullOrEmpty(timer.SeriesTimerId))
{
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId)
- .ToString("N");
+ .ToString("N", CultureInfo.InvariantCulture);
foundSeriesTimer = true;
}
@@ -1018,7 +1019,7 @@ namespace Emby.Server.Implementations.LiveTv
if (seriesTimer != null)
{
program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id)
- .ToString("N");
+ .ToString("N", CultureInfo.InvariantCulture);
}
}
}
@@ -1472,7 +1473,7 @@ namespace Emby.Server.Implementations.LiveTv
dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
? null
- : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N");
+ : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture);
dto.TimerId = string.IsNullOrEmpty(info.Id)
? null
@@ -2027,7 +2028,7 @@ namespace Emby.Server.Implementations.LiveTv
info.StartDate = program.StartDate;
info.Name = program.Name;
info.Overview = program.Overview;
- info.ProgramId = programDto.Id.ToString("N");
+ info.ProgramId = programDto.Id.ToString("N", CultureInfo.InvariantCulture);
info.ExternalProgramId = program.ExternalId;
if (program.EndDate.HasValue)
@@ -2088,7 +2089,7 @@ namespace Emby.Server.Implementations.LiveTv
if (service is ISupportsNewTimerIds supportsNewTimerIds)
{
newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
- newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N");
+ newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N", CultureInfo.InvariantCulture);
}
else
{
@@ -2192,7 +2193,7 @@ namespace Emby.Server.Implementations.LiveTv
info.EnabledUsers = _userManager.Users
.Where(IsLiveTvEnabled)
- .Select(i => i.Id.ToString("N"))
+ .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
.ToArray();
return info;
@@ -2219,7 +2220,7 @@ namespace Emby.Server.Implementations.LiveTv
{
var parts = id.Split(new[] { '_' }, 2);
- var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
+ var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), parts[0], StringComparison.OrdinalIgnoreCase));
if (service == null)
{
@@ -2269,7 +2270,7 @@ namespace Emby.Server.Implementations.LiveTv
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
{
- info.Id = Guid.NewGuid().ToString("N");
+ info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
list.Add(info);
config.TunerHosts = list.ToArray();
}
@@ -2312,7 +2313,7 @@ namespace Emby.Server.Implementations.LiveTv
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
{
- info.Id = Guid.NewGuid().ToString("N");
+ info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
list.Add(info);
config.ListingProviders = list.ToArray();
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index cd1731de5..52d60c004 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -101,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv
{
var openKeys = new List<string>();
openKeys.Add(item.GetType().Name);
- openKeys.Add(item.Id.ToString("N"));
+ openKeys.Add(item.Id.ToString("N", CultureInfo.InvariantCulture));
openKeys.Add(source.Id ?? string.Empty);
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray());
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 24b100edd..85754ca8b 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@@ -11,7 +14,6 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -20,7 +22,6 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@@ -31,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
+ private readonly IStreamHelper _streamHelper;
public HdHomerunHost(
IServerConfigurationManager config,
@@ -40,29 +42,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IHttpClient httpClient,
IServerApplicationHost appHost,
ISocketFactory socketFactory,
- INetworkManager networkManager)
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
+ _streamHelper = streamHelper;
}
public string Name => "HD Homerun";
- public override string Type => DeviceType;
-
- public static string DeviceType => "hdhomerun";
+ public override string Type => "hdhomerun";
protected override string ChannelIdPrefix => "hdhr_";
private string GetChannelId(TunerHostInfo info, Channels i)
- {
- var id = ChannelIdPrefix + i.GuideNumber;
-
- return id;
- }
+ => ChannelIdPrefix + i.GuideNumber;
private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
@@ -74,19 +72,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
CancellationToken = cancellationToken,
BufferContent = false
};
- using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
- {
- using (var stream = response.Content)
- {
- var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
- if (info.ImportFavoritesOnly)
- {
- lineup = lineup.Where(i => i.Favorite).ToList();
- }
+ using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
+ using (var stream = response.Content)
+ {
+ var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
- return lineup.Where(i => !i.DRM).ToList();
+ if (info.ImportFavoritesOnly)
+ {
+ lineup = lineup.Where(i => i.Favorite).ToList();
}
+
+ return lineup.Where(i => !i.DRM).ToList();
}
}
@@ -138,25 +135,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Url = string.Format("{0}/discover.json", GetApiUrl(info)),
CancellationToken = cancellationToken,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds),
BufferContent = false
-
- }, "GET").ConfigureAwait(false))
+ }, HttpMethod.Get).ConfigureAwait(false))
+ using (var stream = response.Content)
{
- using (var stream = response.Content)
- {
- var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
+ var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
- if (!string.IsNullOrEmpty(cacheKey))
+ if (!string.IsNullOrEmpty(cacheKey))
+ {
+ lock (_modelCache)
{
- lock (_modelCache)
- {
- _modelCache[cacheKey] = discoverResponse;
- }
+ _modelCache[cacheKey] = discoverResponse;
}
-
- return discoverResponse;
}
+
+ return discoverResponse;
}
}
catch (HttpException ex)
@@ -187,37 +180,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- using (var stream = await _httpClient.Get(new HttpRequestOptions()
+ using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
{
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
CancellationToken = cancellationToken,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
BufferContent = false
- }))
+ }, HttpMethod.Get))
+ using (var stream = response.Content)
+ using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
{
var tuners = new List<LiveTvTunerInfo>();
- using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
+ while (!sr.EndOfStream)
{
- while (!sr.EndOfStream)
+ string line = StripXML(sr.ReadLine());
+ if (line.Contains("Channel"))
{
- 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
{
- 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.ModelNumber) ? Name : model.ModelNumber,
- ProgramName = currentChannel,
- Status = status
- });
- }
+ Name = name,
+ SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
+ ProgramName = currentChannel,
+ Status = status
+ });
}
}
+
return tuners;
}
}
@@ -247,6 +239,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
bufferIndex++;
}
}
+
return new string(buffer, 0, bufferIndex);
}
@@ -258,10 +251,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var uri = new Uri(GetApiUrl(info));
- using (var manager = new HdHomerunManager(_socketFactory, Logger))
+ using (var manager = new HdHomerunManager())
{
// Legacy HdHomeruns are IPv4 only
- var ipInfo = _networkManager.ParseIpAddress(uri.Host);
+ var ipInfo = IPAddress.Parse(uri.Host);
for (int i = 0; i < model.TunerCount; ++i)
{
@@ -278,6 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
});
}
}
+
return tuners;
}
@@ -436,12 +430,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
videoCodec = channelInfo.VideoCodec;
}
+
string audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = isHd ? 15000000 : 2000000;
}
+
int? audioBitrate = isHd ? 448000 : 192000;
// normalize
@@ -463,7 +459,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
id = "native";
}
- id += "_" + channelId.GetMD5().ToString("N") + "_" + url.GetMD5().ToString("N");
+
+ id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
var mediaSource = new MediaSourceInfo
{
@@ -529,29 +526,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
else
{
- try
- {
- var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- if (modelInfo != null && modelInfo.SupportsTranscoding)
+ if (modelInfo != null && modelInfo.SupportsTranscoding)
+ {
+ if (info.AllowHWTranscoding)
{
- if (info.AllowHWTranscoding)
- {
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
-
- 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"));
- }
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+ 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
- {
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
}
if (list.Count == 0)
@@ -584,7 +574,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
- return new HdHomerunUdpStream(mediaSource, info, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+ return new HdHomerunUdpStream(
+ mediaSource,
+ info,
+ streamId,
+ new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
+ modelInfo.TunerCount,
+ FileSystem,
+ Logger,
+ Config.ApplicationPaths,
+ _appHost,
+ _socketFactory,
+ _networkManager,
+ _streamHelper);
}
var enableHttpStream = true;
@@ -601,10 +603,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
mediaSource.Path = httpUrl;
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
- }
-
- return new HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
+ }
+
+ return new HdHomerunUdpStream(
+ mediaSource,
+ info,
+ streamId,
+ new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
+ modelInfo.TunerCount,
+ FileSystem,
+ Logger,
+ Config.ApplicationPaths,
+ _appHost,
+ _socketFactory,
+ _networkManager,
+ _streamHelper);
}
public async Task Validate(TunerHostInfo info)
@@ -677,13 +691,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// Need a way to set the Receive timeout on the socket otherwise this might never timeout?
try
{
- await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
+ await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken);
var receiveBuffer = new byte[8192];
while (!cancellationToken.IsCancellationRequested)
{
var response = await udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
- var deviceIp = response.RemoteEndPoint.IpAddress.Address;
+ var deviceIp = response.RemoteEndPoint.Address.ToString();
// check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte
if (response.ReceivedBytes > 13 && response.Buffer[1] == 3)
@@ -703,9 +717,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
catch (OperationCanceledException)
{
}
- catch
+ catch (Exception ex)
{
// Socket timeout indicates all messages have been received.
+ Logger.LogError(ex, "Error while sending discovery message");
}
}
@@ -720,21 +735,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Url = url
};
- try
- {
- var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
-
- hostInfo.DeviceId = modelInfo.DeviceID;
- hostInfo.FriendlyName = modelInfo.FriendlyName;
+ var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
- return hostInfo;
- }
- catch
- {
- // logged at lower levels
- }
+ hostInfo.DeviceId = modelInfo.DeviceID;
+ hostInfo.FriendlyName = modelInfo.FriendlyName;
- return null;
+ return hostInfo;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 2205c0ecc..3699b988c 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -1,19 +1,20 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
+using System.Globalization;
using System.Net;
+using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Net;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public interface IHdHomerunChannelCommands
{
- IEnumerable<Tuple<string, string>> GetCommands();
+ IEnumerable<(string, string)> GetCommands();
}
public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
@@ -32,16 +33,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- public IEnumerable<Tuple<string, string>> GetCommands()
+ public IEnumerable<(string, string)> GetCommands()
{
- var commands = new List<Tuple<string, string>>();
-
if (!string.IsNullOrEmpty(_channel))
- commands.Add(Tuple.Create("channel", _channel));
+ {
+ yield return ("channel", _channel);
+ }
if (!string.IsNullOrEmpty(_program))
- commands.Add(Tuple.Create("program", _program));
- return commands;
+ {
+ yield return ("program", _program);
+ }
}
}
@@ -56,95 +58,87 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_profile = profile;
}
- public IEnumerable<Tuple<string, string>> GetCommands()
+ public IEnumerable<(string, string)> GetCommands()
{
- var commands = new List<Tuple<string, string>>();
-
if (!string.IsNullOrEmpty(_channel))
{
- if (!string.IsNullOrEmpty(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(_profile)
+ && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
{
- commands.Add(Tuple.Create("vchannel", string.Format("{0} transcode={1}", _channel, _profile)));
+ yield return ("vchannel", $"{_channel} transcode={_profile}");
}
else
{
- commands.Add(Tuple.Create("vchannel", _channel));
+ yield return ("vchannel", _channel);
}
}
-
- return commands;
}
}
public class HdHomerunManager : IDisposable
{
- public static int HdHomeRunPort = 65001;
+ public const 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 const byte GetSetName = 3;
+ private const byte GetSetValue = 4;
+ private const byte GetSetLockkey = 21;
+ private const ushort GetSetRequest = 4;
+ private const ushort GetSetReply = 5;
private uint? _lockkey = null;
private int _activeTuner = -1;
- private readonly ISocketFactory _socketFactory;
- private IpAddressInfo _remoteIp;
+ private IPEndPoint _remoteEndPoint;
- private ILogger _logger;
- private ISocket _currentTcpSocket;
-
- public HdHomerunManager(ISocketFactory socketFactory, ILogger logger)
- {
- _socketFactory = socketFactory;
- _logger = logger;
- }
+ private TcpClient _tcpClient;
public void Dispose()
{
- using (var socket = _currentTcpSocket)
+ using (var socket = _tcpClient)
{
if (socket != null)
{
- _currentTcpSocket = null;
+ _tcpClient = null;
- var task = StopStreaming(socket);
- Task.WaitAll(task);
+ StopStreaming(socket).GetAwaiter().GetResult();
}
}
}
- public async Task<bool> CheckTunerAvailability(IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken)
+ public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken)
{
- using (var socket = _socketFactory.CreateTcpSocket(remoteIp, HdHomeRunPort))
+ using (var client = new TcpClient(new IPEndPoint(remoteIp, HdHomeRunPort)))
+ using (var stream = client.GetStream())
{
- return await CheckTunerAvailability(socket, remoteIp, tuner, cancellationToken).ConfigureAwait(false);
+ return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false);
}
}
- private static async Task<bool> CheckTunerAvailability(ISocket socket, IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken)
+ private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken)
{
- var ipEndPoint = new IpEndPointInfo(remoteIp, HdHomeRunPort);
-
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
- await socket.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken);
+ await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
- var receiveBuffer = new byte[8192];
- var response = await socket.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
+ try
+ {
+ int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
- ParseReturnMessage(response.Buffer, response.ReceivedBytes, out string returnVal);
+ ParseReturnMessage(buffer, receivedBytes, out string returnVal);
- return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase);
+ return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase);
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
}
- public async Task StartStreaming(IpAddressInfo remoteIp, IPAddress localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken)
+ public async Task StartStreaming(IPAddress remoteIp, IPAddress localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken)
{
- _remoteIp = remoteIp;
-
- var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort);
- _currentTcpSocket = tcpClient;
+ _remoteEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort);
- var receiveBuffer = new byte[8192];
+ _tcpClient = new TcpClient(_remoteEndPoint);
if (!_lockkey.HasValue)
{
@@ -153,51 +147,64 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var lockKeyValue = _lockkey.Value;
+ var stream = _tcpClient.GetStream();
- var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
-
- for (int i = 0; i < numTuners; ++i)
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
+ try
{
- if (!await CheckTunerAvailability(tcpClient, _remoteIp, i, cancellationToken).ConfigureAwait(false))
- continue;
-
- _activeTuner = i;
- 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);
- // parse response to make sure it worked
- if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out var returnVal))
- continue;
-
- var commandList = commands.GetCommands();
- foreach (Tuple<string, string> command in commandList)
+ for (int i = 0; i < numTuners; ++i)
{
- 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);
+ if (!await CheckTunerAvailability(stream, i, cancellationToken).ConfigureAwait(false))
+ {
+ continue;
+ }
+
+ _activeTuner = i;
+ var lockKeyString = string.Format("{0:d}", lockKeyValue);
+ var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
+ await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
+ int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
// parse response to make sure it worked
- if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
- await ReleaseLockkey(tcpClient, lockKeyValue).ConfigureAwait(false);
continue;
}
- }
+ var commandList = commands.GetCommands();
+ foreach (var command in commandList)
+ {
+ var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
+ await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
+ receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
+ // parse response to make sure it worked
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
+ {
+ await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
+ continue;
+ }
+ }
- var targetValue = string.Format("rtp://{0}:{1}", localIp, localPort);
- var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue);
+ var targetValue = string.Format("rtp://{0}:{1}", localIp, localPort);
+ 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, lockKeyValue).ConfigureAwait(false);
- continue;
- }
+ await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
+ receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
- return;
+ // parse response to make sure it worked
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
+ {
+ await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
+ continue;
+ }
+
+ return;
+ }
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
}
_activeTuner = -1;
@@ -207,58 +214,74 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public async Task ChangeChannel(IHdHomerunChannelCommands commands, CancellationToken cancellationToken)
{
if (!_lockkey.HasValue)
+ {
return;
+ }
- using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
+ using (var tcpClient = new TcpClient(_remoteEndPoint))
+ using (var stream = tcpClient.GetStream())
{
var commandList = commands.GetCommands();
- var receiveBuffer = new byte[8192];
-
- foreach (Tuple<string, string> command in commandList)
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
+ try
{
- 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
- if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out string returnVal))
+ foreach (var command in commandList)
{
- return;
+ var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
+ await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
+ int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+
+ // parse response to make sure it worked
+ if (!ParseReturnMessage(buffer, receivedBytes, out _))
+ {
+ return;
+ }
}
}
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
}
}
- public Task StopStreaming(ISocket socket)
+ public Task StopStreaming(TcpClient client)
{
var lockKey = _lockkey;
if (!lockKey.HasValue)
+ {
return Task.CompletedTask;
+ }
- return ReleaseLockkey(socket, lockKey.Value);
+ return ReleaseLockkey(client, lockKey.Value);
}
- private async Task ReleaseLockkey(ISocket tcpClient, uint lockKeyValue)
+ private async Task ReleaseLockkey(TcpClient client, uint lockKeyValue)
{
- _logger.LogInformation("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue);
-
- var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
+ var stream = client.GetStream();
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
- await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false);
+ await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
- var receiveBuffer = new byte[8192];
-
- await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false);
- var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
- _lockkey = null;
- await tcpClient.SendToAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false);
- await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false);
+ var buffer = ArrayPool<byte>.Shared.Rent(8192);
+ try
+ {
+ await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+ var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
+ _lockkey = null;
+ await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false);
+ await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
}
private static byte[] CreateGetMessage(int tuner, string name)
{
- var byteName = Encoding.UTF8.GetBytes(string.Format("/tuner{0}/{1}\0", tuner, name));
+ var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/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];
@@ -270,7 +293,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// 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;
@@ -278,12 +304,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
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));
+ var byteName = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "/tuner{0}/{1}\0", tuner, name));
+ var byteValue = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\0", value));
int messageLength = byteName.Length + byteValue.Length + 12;
if (lockkey.HasValue)
+ {
messageLength += 6;
+ }
var message = new byte[messageLength];
@@ -291,21 +319,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
bool flipEndian = BitConverter.IsLittleEndian;
- message[offset] = GetSetValue;
- offset++;
- message[offset] = Convert.ToByte(byteValue.Length);
- offset++;
+ message[offset++] = GetSetValue;
+ message[offset++] = Convert.ToByte(byteValue.Length);
Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
offset += byteValue.Length;
if (lockkey.HasValue)
{
- message[offset] = GetSetLockkey;
- offset++;
- message[offset] = (byte)4;
- offset++;
+ message[offset++] = GetSetLockkey;
+ message[offset++] = 4;
var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
if (flipEndian)
+ {
Array.Reverse(lockKeyBytes);
+ }
+
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
offset += 4;
}
@@ -313,7 +340,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// 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;
@@ -342,10 +372,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
offset += 2;
// insert tag name and length
- message[offset] = GetSetName;
- offset++;
- message[offset] = Convert.ToByte(byteName.Length);
- offset++;
+ message[offset++] = GetSetName;
+ message[offset++] = Convert.ToByte(byteName.Length);
// insert name string
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
@@ -359,7 +387,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
returnVal = string.Empty;
if (numBytes < 4)
+ {
return false;
+ }
var flipEndian = BitConverter.IsLittleEndian;
int offset = 0;
@@ -367,45 +397,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
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 nameTag = buf[offset++];
- var nameLength = buf[offset];
- offset++;
+ var nameLength = buf[offset++];
// skip the name field to get to value for return
offset += nameLength;
- var valueTag = buf[offset];
- offset++;
+ var valueTag = buf[offset++];
- var valueLength = buf[offset];
- offset++;
+ var valueLength = buf[offset++];
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
return true;
}
- private class HdHomerunCrc
+ private static class HdHomerunCrc
{
private static uint[] crc_table = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
@@ -477,15 +511,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
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;
+ return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 7f426ea31..fbbab07f8 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -1,4 +1,5 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Net;
@@ -18,6 +19,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
{
+ private const int RtpHeaderBytes = 12;
+
private readonly IServerApplicationHost _appHost;
private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory;
@@ -25,8 +28,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly int _numTuners;
private readonly INetworkManager _networkManager;
- public HdHomerunUdpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, MediaBrowser.Model.Net.ISocketFactory socketFactory, INetworkManager networkManager)
- : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
+ public HdHomerunUdpStream(
+ MediaSourceInfo mediaSource,
+ TunerHostInfo tunerHostInfo,
+ string originalStreamId,
+ IHdHomerunChannelCommands channelCommands,
+ int numTuners,
+ IFileSystem fileSystem,
+ ILogger logger,
+ IServerApplicationPaths appPaths,
+ IServerApplicationHost appHost,
+ MediaBrowser.Model.Net.ISocketFactory socketFactory,
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
+ : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
{
_appHost = appHost;
_socketFactory = socketFactory;
@@ -37,13 +52,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
EnableStreamSharing = true;
}
- private static Socket CreateSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
- {
- var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
-
- return socket;
- }
-
public override async Task Open(CancellationToken openCancellationToken)
{
LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
@@ -58,15 +66,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Logger.LogInformation("Opening HDHR UDP Live stream from {host}", uri.Host);
var remoteAddress = IPAddress.Parse(uri.Host);
- var embyRemoteAddress = _networkManager.ParseIpAddress(uri.Host);
IPAddress localAddress = null;
- using (var tcpSocket = CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
+ using (var tcpClient = new TcpClient())
{
try
{
- tcpSocket.Connect(new IPEndPoint(remoteAddress, HdHomerunManager.HdHomeRunPort));
- localAddress = ((IPEndPoint)tcpSocket.LocalEndPoint).Address;
- tcpSocket.Close();
+ await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort).ConfigureAwait(false);
+ localAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address;
+ tcpClient.Close();
}
catch (Exception ex)
{
@@ -76,12 +83,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var udpClient = _socketFactory.CreateUdpSocket(localPort);
- var hdHomerunManager = new HdHomerunManager(_socketFactory, Logger);
+ var hdHomerunManager = new HdHomerunManager();
try
{
// send url to start streaming
- await hdHomerunManager.StartStreaming(embyRemoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false);
+ await hdHomerunManager.StartStreaming(
+ remoteAddress,
+ localAddress,
+ localPort,
+ _channelCommands,
+ _numTuners,
+ openCancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -92,13 +105,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Logger.LogError(ex, "Error opening live stream:");
}
+
throw;
}
}
var taskCompletionSource = new TaskCompletionSource<bool>();
- await StartStreaming(udpClient, hdHomerunManager, remoteAddress, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
+ await StartStreaming(
+ udpClient,
+ hdHomerunManager,
+ remoteAddress,
+ taskCompletionSource,
+ LiveStreamCancellationTokenSource.Token).ConfigureAwait(false);
//OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile;
@@ -143,170 +162,42 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
});
}
- private static void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
- {
- Task.Run(() =>
- {
- openTaskCompletionSource.TrySetResult(true);
- });
- }
-
- private const int RtpHeaderBytes = 12;
-
private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
- var bufferSize = 81920;
-
- byte[] buffer = new byte[bufferSize];
- int read;
- var resolved = false;
-
- using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
- using (var fileStream = FileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
+ byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamDefaults.DefaultCopyToBufferSize);
+ try
{
- var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
-
- while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
+ using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
+ using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
{
- cancellationToken.ThrowIfCancellationRequested();
-
- currentCancellationToken = cancellationToken;
-
- read -= RtpHeaderBytes;
-
- if (read > 0)
- {
- fileStream.Write(buffer, RtpHeaderBytes, read);
- }
-
- if (!resolved)
+ var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
+ int read;
+ var resolved = false;
+ while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
{
- resolved = true;
- DateOpened = DateTime.UtcNow;
- Resolve(openTaskCompletionSource);
- }
- }
- }
- }
+ cancellationToken.ThrowIfCancellationRequested();
- public class UdpClientStream : Stream
- {
- private static int RtpHeaderBytes = 12;
- private static int PacketSize = 1316;
- private readonly MediaBrowser.Model.Net.ISocket _udpClient;
- bool disposed;
+ currentCancellationToken = cancellationToken;
- public UdpClientStream(MediaBrowser.Model.Net.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(nameof(buffer));
+ read -= RtpHeaderBytes;
- if (offset + count < 0)
- throw new ArgumentOutOfRangeException(nameof(offset), "offset + count must not be negative");
+ if (read > 0)
+ {
+ await fileStream.WriteAsync(buffer, RtpHeaderBytes, read).ConfigureAwait(false);
+ }
- if (offset + count > buffer.Length)
- throw new ArgumentException("offset + count must not be greater than the length of buffer");
-
- if (disposed)
- throw new ObjectDisposedException(nameof(UdpClientStream));
-
- // 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;
- byte[] receiveBuffer = new byte[81920];
-
- for (int i = 0; i < numReads; ++i)
- {
- var data = await _udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, 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;
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- if (buffer == null)
- throw new ArgumentNullException(nameof(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");
-
- if (disposed)
- throw new ObjectDisposedException(nameof(UdpClientStream));
-
- // 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;
- byte[] receiveBuffer = new byte[81920];
-
- for (int i = 0; i < numReads; ++i)
- {
- var receivedBytes = _udpClient.Receive(receiveBuffer, 0, receiveBuffer.Length);
-
- var bytesRead = receivedBytes - RtpHeaderBytes;
-
- // remove rtp header
- Buffer.BlockCopy(receiveBuffer, RtpHeaderBytes, buffer, offset, bytesRead);
- offset += bytesRead;
- totalBytesRead += bytesRead;
+ if (!resolved)
+ {
+ resolved = true;
+ DateOpened = DateTime.UtcNow;
+ openTaskCompletionSource.TrySetResult(true);
+ }
+ }
}
- return totalBytesRead;
- }
-
- protected override void Dispose(bool disposing)
- {
- disposed = true;
}
-
- public override bool CanRead => throw new NotImplementedException();
-
- public override bool CanSeek => throw new NotImplementedException();
-
- public override bool CanWrite => throw new NotImplementedException();
-
- public override long Length => throw new NotImplementedException();
-
- public override long Position
- {
- get => throw new NotImplementedException();
-
- set => throw new NotImplementedException();
- }
-
- public override void Flush()
- {
- 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)
+ finally
{
- throw new NotImplementedException();
+ ArrayPool<byte>.Shared.Return(buffer);
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index ece2cbd54..d12c96392 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -15,34 +16,28 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class LiveStream : ILiveStream
{
- public MediaSourceInfo OriginalMediaSource { get; set; }
- public MediaSourceInfo MediaSource { get; set; }
-
- public int ConsumerCount { get; set; }
-
- public string OriginalStreamId { get; set; }
- public bool EnableStreamSharing { get; set; }
- public string UniqueId { get; }
-
protected readonly IFileSystem FileSystem;
protected readonly IServerApplicationPaths AppPaths;
+ protected readonly IStreamHelper StreamHelper;
protected string TempFilePath;
protected readonly ILogger Logger;
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
- public string TunerHostId { get; }
-
- public DateTime DateOpened { get; protected set; }
-
- public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
+ public LiveStream(
+ MediaSourceInfo mediaSource,
+ TunerHostInfo tuner,
+ IFileSystem fileSystem,
+ ILogger logger,
+ IServerApplicationPaths appPaths,
+ IStreamHelper streamHelper)
{
OriginalMediaSource = mediaSource;
FileSystem = fileSystem;
MediaSource = mediaSource;
Logger = logger;
EnableStreamSharing = true;
- UniqueId = Guid.NewGuid().ToString("N");
+ UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
if (tuner != null)
{
@@ -50,11 +45,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
AppPaths = appPaths;
+ StreamHelper = streamHelper;
ConsumerCount = 1;
SetTempFilePath("ts");
}
+ protected virtual int EmptyReadLimit => 1000;
+
+ public MediaSourceInfo OriginalMediaSource { get; set; }
+ public MediaSourceInfo MediaSource { get; set; }
+
+ public int ConsumerCount { get; set; }
+
+ public string OriginalStreamId { get; set; }
+ public bool EnableStreamSharing { get; set; }
+ public string UniqueId { get; }
+
+ public string TunerHostId { get; }
+
+ public DateTime DateOpened { get; protected set; }
+
protected void SetTempFilePath(string extension)
{
TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
@@ -70,24 +81,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
EnableStreamSharing = false;
- Logger.LogInformation("Closing " + GetType().Name);
+ Logger.LogInformation("Closing {Type}", GetType().Name);
LiveStreamCancellationTokenSource.Cancel();
return Task.CompletedTask;
}
- protected Stream GetInputStream(string path, bool allowAsyncFileRead)
- {
- var fileOpenOptions = FileOpenOptions.SequentialScan;
-
- if (allowAsyncFileRead)
- {
- fileOpenOptions |= FileOpenOptions.Asynchronous;
- }
-
- return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
- }
+ protected FileStream GetInputStream(string path, bool allowAsyncFileRead)
+ => new FileStream(
+ path,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ StreamDefaults.DefaultFileStreamBufferSize,
+ allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
public Task DeleteTempFiles()
{
@@ -143,8 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
var nextFileInfo = GetNextFile(null);
- var nextFile = nextFileInfo.Item1;
- var isLastFile = nextFileInfo.Item2;
+ var nextFile = nextFileInfo.file;
+ var isLastFile = nextFileInfo.isLastFile;
while (!string.IsNullOrEmpty(nextFile))
{
@@ -154,8 +162,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
seekFile = false;
nextFileInfo = GetNextFile(nextFile);
- nextFile = nextFileInfo.Item1;
- isLastFile = nextFileInfo.Item2;
+ nextFile = nextFileInfo.file;
+ isLastFile = nextFileInfo.isLastFile;
}
Logger.LogInformation("Live Stream ended.");
@@ -179,19 +187,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
{
- using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
+ using (var inputStream = GetInputStream(path, allowAsync))
{
if (seekFile)
{
TrySeek(inputStream, -20000);
}
- await ApplicationHost.StreamHelper.CopyToAsync(inputStream, stream, 81920, emptyReadLimit, cancellationToken).ConfigureAwait(false);
+ await StreamHelper.CopyToAsync(
+ inputStream,
+ stream,
+ StreamDefaults.DefaultCopyToBufferSize,
+ emptyReadLimit,
+ cancellationToken).ConfigureAwait(false);
}
}
- protected virtual int EmptyReadLimit => 1000;
-
private void TrySeek(FileStream stream, long offset)
{
if (!stream.CanSeek)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 2d9bec53f..a02a9ade4 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -27,14 +28,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager;
-
- public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
+ private readonly IStreamHelper _streamHelper;
+
+ public M3UTunerHost(
+ IServerConfigurationManager config,
+ IMediaSourceManager mediaSourceManager,
+ ILogger logger,
+ IJsonSerializer jsonSerializer,
+ IFileSystem fileSystem,
+ IHttpClient httpClient,
+ IServerApplicationHost appHost,
+ INetworkManager networkManager,
+ IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_networkManager = networkManager;
_mediaSourceManager = mediaSourceManager;
+ _streamHelper = streamHelper;
}
public override string Type => "m3u";
@@ -43,7 +55,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private string GetFullChannelIdPrefix(TunerHostInfo info)
{
- return ChannelIdPrefix + info.Url.GetMD5().ToString("N");
+ return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
@@ -61,7 +73,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Name = Name,
SourceType = Type,
Status = LiveTvTunerStatus.Available,
- Id = i.Url.GetMD5().ToString("N"),
+ Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture),
Url = i.Url
})
.ToList();
@@ -102,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
}
}
- return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths);
+ return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper);
}
public async Task Validate(TunerHostInfo info)
@@ -173,7 +185,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
ReadAtNativeFramerate = false,
- Id = channel.Path.GetMD5().ToString("N"),
+ Id = channel.Path.GetMD5().ToString("N", CultureInfo.InvariantCulture),
IsInfiniteStream = true,
IsRemote = isRemote,
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 814031b12..3d2267e75 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -58,6 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
UserAgent = _appHost.ApplicationUserAgent
});
}
+
return Task.FromResult((Stream)File.OpenRead(url));
}
@@ -92,11 +93,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var channel = GetChannelnfo(extInf, tunerHostId, line);
if (string.IsNullOrWhiteSpace(channel.Id))
{
- channel.Id = channelIdPrefix + line.GetMD5().ToString("N");
+ channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
else
{
- channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N");
+ channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
channel.Path = line;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index d74cf3be2..c6e894560 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -19,8 +19,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
- public SharedHttpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
- : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
+ public SharedHttpStream(
+ MediaSourceInfo mediaSource,
+ TunerHostInfo tunerHostInfo,
+ string originalStreamId,
+ IFileSystem fileSystem,
+ IHttpClient httpClient,
+ ILogger logger,
+ IServerApplicationPaths appPaths,
+ IServerApplicationHost appHost,
+ IStreamHelper streamHelper)
+ : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
{
_httpClient = httpClient;
_appHost = appHost;
@@ -46,14 +55,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Url = url,
CancellationToken = CancellationToken.None,
BufferContent = false,
-
- // Increase a little bit
- TimeoutMs = 30000,
-
- EnableHttpCompression = false,
-
- LogResponse = true,
- LogResponseHeaders = true
+ DecompressionMethod = CompressionMethod.None
};
foreach (var header in mediaSource.RequiredHttpHeaders)
@@ -125,7 +127,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
using (var stream = response.Content)
using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
{
- await ApplicationHost.StreamHelper.CopyToAsync(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false);
+ await StreamHelper.CopyToAsync(
+ stream,
+ fileStream,
+ StreamDefaults.DefaultCopyToBufferSize,
+ () => Resolve(openTaskCompletionSource),
+ cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
@@ -135,6 +142,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
Logger.LogError(ex, "Error copying live stream.");
}
+
EnableStreamSharing = false;
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
});