mirror of https://github.com/jellyfin/jellyfin.git
Clean up livestreaming code
This commit is contained in:
parent
7a27dd8a1b
commit
237db8ae92
|
@ -16,6 +16,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
|
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
|
||||||
private const string FriendlyName = "Jellyfin";
|
private const string FriendlyName = "Jellyfin";
|
||||||
|
|
||||||
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
|
@ -25,7 +27,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<XDocument> SendCommandAsync(string baseUrl,
|
public async Task<XDocument> SendCommandAsync(
|
||||||
|
string baseUrl,
|
||||||
DeviceService service,
|
DeviceService service,
|
||||||
string command,
|
string command,
|
||||||
string postData,
|
string postData,
|
||||||
|
@ -35,12 +38,20 @@ namespace Emby.Dlna.PlayTo
|
||||||
var cancellationToken = CancellationToken.None;
|
var cancellationToken = CancellationToken.None;
|
||||||
|
|
||||||
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
||||||
using (var response = await PostSoapDataAsync(url, '\"' + service.ServiceType + '#' + command + '\"', postData, header, logRequest, cancellationToken)
|
using (var response = await PostSoapDataAsync(
|
||||||
|
url,
|
||||||
|
$"\"{service.ServiceType}#{command}\"",
|
||||||
|
postData,
|
||||||
|
header,
|
||||||
|
logRequest,
|
||||||
|
cancellationToken)
|
||||||
.ConfigureAwait(false))
|
.ConfigureAwait(false))
|
||||||
using (var stream = response.Content)
|
using (var stream = response.Content)
|
||||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||||
{
|
{
|
||||||
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
|
return XDocument.Parse(
|
||||||
|
await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||||
|
LoadOptions.PreserveWhitespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,9 +69,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
return baseUrl + serviceUrl;
|
return baseUrl + serviceUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
public async Task SubscribeAsync(
|
||||||
|
string url,
|
||||||
public async Task SubscribeAsync(string url,
|
|
||||||
string ip,
|
string ip,
|
||||||
int port,
|
int port,
|
||||||
string localIp,
|
string localIp,
|
||||||
|
@ -101,14 +111,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
|
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
|
||||||
|
using (var stream = response.Content)
|
||||||
|
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||||
{
|
{
|
||||||
using (var stream = response.Content)
|
return XDocument.Parse(
|
||||||
{
|
await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
LoadOptions.PreserveWhitespace);
|
||||||
{
|
|
||||||
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +130,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
if (soapAction[0] != '\"')
|
if (soapAction[0] != '\"')
|
||||||
{
|
{
|
||||||
soapAction = '\"' + soapAction + '\"';
|
soapAction = $"\"{soapAction}\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
var options = new HttpRequestOptions
|
||||||
|
|
|
@ -315,8 +315,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
private IMediaSourceManager MediaSourceManager { get; set; }
|
private IMediaSourceManager MediaSourceManager { get; set; }
|
||||||
|
|
||||||
private IPlaylistManager PlaylistManager { get; set; }
|
|
||||||
|
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -325,14 +323,6 @@ namespace Emby.Server.Implementations
|
||||||
/// <value>The installation manager.</value>
|
/// <value>The installation manager.</value>
|
||||||
protected IInstallationManager InstallationManager { get; private set; }
|
protected IInstallationManager InstallationManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the zip client.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The zip client.</value>
|
|
||||||
protected IZipClient ZipClient { get; private set; }
|
|
||||||
|
|
||||||
protected IHttpResultFactory HttpResultFactory { get; private set; }
|
|
||||||
|
|
||||||
protected IAuthService AuthService { get; private set; }
|
protected IAuthService AuthService { get; private set; }
|
||||||
|
|
||||||
public IStartupOptions StartupOptions { get; }
|
public IStartupOptions StartupOptions { get; }
|
||||||
|
@ -680,8 +670,6 @@ namespace Emby.Server.Implementations
|
||||||
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
|
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IStreamHelper StreamHelper { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers resources that classes will depend on
|
/// Registers resources that classes will depend on
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -725,8 +713,7 @@ namespace Emby.Server.Implementations
|
||||||
ProcessFactory = new ProcessFactory();
|
ProcessFactory = new ProcessFactory();
|
||||||
serviceCollection.AddSingleton(ProcessFactory);
|
serviceCollection.AddSingleton(ProcessFactory);
|
||||||
|
|
||||||
ApplicationHost.StreamHelper = new StreamHelper();
|
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
||||||
serviceCollection.AddSingleton(StreamHelper);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
|
serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
|
||||||
|
|
||||||
|
@ -735,11 +722,9 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
|
serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
|
||||||
|
|
||||||
ZipClient = new ZipClient();
|
serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient));
|
||||||
serviceCollection.AddSingleton(ZipClient);
|
|
||||||
|
|
||||||
HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper);
|
serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory));
|
||||||
serviceCollection.AddSingleton(HttpResultFactory);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||||
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
||||||
|
@ -836,8 +821,7 @@ namespace Emby.Server.Implementations
|
||||||
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
|
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
|
||||||
serviceCollection.AddSingleton(CollectionManager);
|
serviceCollection.AddSingleton(CollectionManager);
|
||||||
|
|
||||||
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
|
serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager));
|
||||||
serviceCollection.AddSingleton(PlaylistManager);
|
|
||||||
|
|
||||||
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
|
LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
|
||||||
serviceCollection.AddSingleton(LiveTvManager);
|
serviceCollection.AddSingleton(LiveTvManager);
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
@ -31,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
|
private readonly IStreamHelper _streamHelper;
|
||||||
|
|
||||||
public HdHomerunHost(
|
public HdHomerunHost(
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
|
@ -40,29 +42,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ISocketFactory socketFactory,
|
ISocketFactory socketFactory,
|
||||||
INetworkManager networkManager)
|
INetworkManager networkManager,
|
||||||
|
IStreamHelper streamHelper)
|
||||||
: base(config, logger, jsonSerializer, fileSystem)
|
: base(config, logger, jsonSerializer, fileSystem)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_socketFactory = socketFactory;
|
_socketFactory = socketFactory;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
|
_streamHelper = streamHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "HD Homerun";
|
public string Name => "HD Homerun";
|
||||||
|
|
||||||
public override string Type => DeviceType;
|
public override string Type => "hdhomerun";
|
||||||
|
|
||||||
public static string DeviceType => "hdhomerun";
|
|
||||||
|
|
||||||
protected override string ChannelIdPrefix => "hdhr_";
|
protected override string ChannelIdPrefix => "hdhr_";
|
||||||
|
|
||||||
private string GetChannelId(TunerHostInfo info, Channels i)
|
private string GetChannelId(TunerHostInfo info, Channels i)
|
||||||
{
|
=> ChannelIdPrefix + i.GuideNumber;
|
||||||
var id = ChannelIdPrefix + i.GuideNumber;
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
|
private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
@ -74,19 +72,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
BufferContent = false
|
BufferContent = false
|
||||||
};
|
};
|
||||||
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
|
|
||||||
|
using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
|
||||||
|
using (var stream = response.Content)
|
||||||
{
|
{
|
||||||
using (var stream = response.Content)
|
var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
|
||||||
|
|
||||||
|
if (info.ImportFavoritesOnly)
|
||||||
{
|
{
|
||||||
var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
|
lineup = lineup.Where(i => i.Favorite).ToList();
|
||||||
|
|
||||||
if (info.ImportFavoritesOnly)
|
|
||||||
{
|
|
||||||
lineup = lineup.Where(i => i.Favorite).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return lineup.Where(i => !i.DRM).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return lineup.Where(i => !i.DRM).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,23 +136,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
Url = string.Format("{0}/discover.json", GetApiUrl(info)),
|
Url = string.Format("{0}/discover.json", GetApiUrl(info)),
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
BufferContent = false
|
BufferContent = false
|
||||||
|
}, HttpMethod.Get).ConfigureAwait(false))
|
||||||
}, "GET").ConfigureAwait(false))
|
using (var stream = response.Content)
|
||||||
{
|
{
|
||||||
using (var stream = response.Content)
|
var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(cacheKey))
|
||||||
{
|
{
|
||||||
var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
|
lock (_modelCache)
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(cacheKey))
|
|
||||||
{
|
{
|
||||||
lock (_modelCache)
|
_modelCache[cacheKey] = discoverResponse;
|
||||||
{
|
|
||||||
_modelCache[cacheKey] = discoverResponse;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return discoverResponse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return discoverResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
|
@ -186,36 +180,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
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)),
|
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
BufferContent = false
|
BufferContent = false
|
||||||
}))
|
}, HttpMethod.Get))
|
||||||
|
using (var stream = response.Content)
|
||||||
|
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
|
||||||
{
|
{
|
||||||
var tuners = new List<LiveTvTunerInfo>();
|
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());
|
LiveTvTunerStatus status;
|
||||||
if (line.Contains("Channel"))
|
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;
|
Name = name,
|
||||||
var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
|
SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
|
||||||
var name = line.Substring(0, index - 1);
|
ProgramName = currentChannel,
|
||||||
var currentChannel = line.Substring(index + 7);
|
Status = status
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tuners;
|
return tuners;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,6 +239,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
bufferIndex++;
|
bufferIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new string(buffer, 0, bufferIndex);
|
return new string(buffer, 0, bufferIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +251,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
var uri = new Uri(GetApiUrl(info));
|
var uri = new Uri(GetApiUrl(info));
|
||||||
|
|
||||||
using (var manager = new HdHomerunManager(Logger))
|
using (var manager = new HdHomerunManager())
|
||||||
{
|
{
|
||||||
// Legacy HdHomeruns are IPv4 only
|
// Legacy HdHomeruns are IPv4 only
|
||||||
var ipInfo = IPAddress.Parse(uri.Host);
|
var ipInfo = IPAddress.Parse(uri.Host);
|
||||||
|
@ -276,6 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tuners;
|
return tuners;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,12 +430,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
videoCodec = channelInfo.VideoCodec;
|
videoCodec = channelInfo.VideoCodec;
|
||||||
}
|
}
|
||||||
|
|
||||||
string audioCodec = channelInfo.AudioCodec;
|
string audioCodec = channelInfo.AudioCodec;
|
||||||
|
|
||||||
if (!videoBitrate.HasValue)
|
if (!videoBitrate.HasValue)
|
||||||
{
|
{
|
||||||
videoBitrate = isHd ? 15000000 : 2000000;
|
videoBitrate = isHd ? 15000000 : 2000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
int? audioBitrate = isHd ? 448000 : 192000;
|
int? audioBitrate = isHd ? 448000 : 192000;
|
||||||
|
|
||||||
// normalize
|
// normalize
|
||||||
|
@ -461,6 +459,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
id = "native";
|
id = "native";
|
||||||
}
|
}
|
||||||
|
|
||||||
id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
var mediaSource = new MediaSourceInfo
|
var mediaSource = new MediaSourceInfo
|
||||||
|
@ -527,29 +526,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
}
|
}
|
||||||
else
|
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, "heavy"));
|
|
||||||
|
|
||||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
|
||||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
|
||||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
|
||||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
|
||||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
|
|
||||||
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.Count == 0)
|
if (list.Count == 0)
|
||||||
|
@ -582,7 +574,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
|
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;
|
var enableHttpStream = true;
|
||||||
|
@ -599,10 +603,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
}
|
}
|
||||||
mediaSource.Path = httpUrl;
|
mediaSource.Path = httpUrl;
|
||||||
|
|
||||||
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 HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
|
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)
|
public async Task Validate(TunerHostInfo info)
|
||||||
|
@ -701,9 +717,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Socket timeout indicates all messages have been received.
|
// Socket timeout indicates all messages have been received.
|
||||||
|
Logger.LogError(ex, "Error while sending discovery message");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -718,21 +735,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
Url = url
|
Url = url
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
hostInfo.DeviceId = modelInfo.DeviceID;
|
hostInfo.DeviceId = modelInfo.DeviceID;
|
||||||
hostInfo.FriendlyName = modelInfo.FriendlyName;
|
hostInfo.FriendlyName = modelInfo.FriendlyName;
|
||||||
|
|
||||||
return hostInfo;
|
return hostInfo;
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// logged at lower levels
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
public interface IHdHomerunChannelCommands
|
public interface IHdHomerunChannelCommands
|
||||||
{
|
{
|
||||||
IEnumerable<Tuple<string, string>> GetCommands();
|
IEnumerable<(string, string)> GetCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
|
public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
|
||||||
|
@ -33,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))
|
if (!string.IsNullOrEmpty(_channel))
|
||||||
commands.Add(Tuple.Create("channel", _channel));
|
{
|
||||||
|
yield return ("channel", _channel);
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_program))
|
if (!string.IsNullOrEmpty(_program))
|
||||||
commands.Add(Tuple.Create("program", _program));
|
{
|
||||||
return commands;
|
yield return ("program", _program);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,23 +58,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
_profile = profile;
|
_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(_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
|
else
|
||||||
{
|
{
|
||||||
commands.Add(Tuple.Create("vchannel", _channel));
|
yield return ("vchannel", _channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return commands;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,19 +85,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
private const ushort GetSetRequest = 4;
|
private const ushort GetSetRequest = 4;
|
||||||
private const ushort GetSetReply = 5;
|
private const ushort GetSetReply = 5;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
private uint? _lockkey = null;
|
private uint? _lockkey = null;
|
||||||
private int _activeTuner = -1;
|
private int _activeTuner = -1;
|
||||||
private IPEndPoint _remoteEndPoint;
|
private IPEndPoint _remoteEndPoint;
|
||||||
|
|
||||||
private TcpClient _tcpClient;
|
private TcpClient _tcpClient;
|
||||||
|
|
||||||
public HdHomerunManager(ILogger logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
using (var socket = _tcpClient)
|
using (var socket = _tcpClient)
|
||||||
|
@ -108,8 +99,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
_tcpClient = null;
|
_tcpClient = null;
|
||||||
|
|
||||||
var task = StopStreaming(socket);
|
StopStreaming(socket).GetAwaiter().GetResult();
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,19 +164,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
|
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
|
||||||
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||||
// parse response to make sure it worked
|
// parse response to make sure it worked
|
||||||
if (!ParseReturnMessage(buffer, receivedBytes, out var returnVal))
|
if (!ParseReturnMessage(buffer, receivedBytes, out _))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandList = commands.GetCommands();
|
var commandList = commands.GetCommands();
|
||||||
foreach (Tuple<string, string> command in commandList)
|
foreach (var command in commandList)
|
||||||
{
|
{
|
||||||
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
|
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
|
||||||
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
|
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
|
||||||
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||||
// parse response to make sure it worked
|
// parse response to make sure it worked
|
||||||
if (!ParseReturnMessage(buffer, receivedBytes, out returnVal))
|
if (!ParseReturnMessage(buffer, receivedBytes, out _))
|
||||||
{
|
{
|
||||||
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
|
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
|
||||||
continue;
|
continue;
|
||||||
|
@ -199,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
|
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
|
||||||
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||||
// parse response to make sure it worked
|
// parse response to make sure it worked
|
||||||
if (!ParseReturnMessage(buffer, receivedBytes, out returnVal))
|
if (!ParseReturnMessage(buffer, receivedBytes, out _))
|
||||||
{
|
{
|
||||||
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
|
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
|
||||||
continue;
|
continue;
|
||||||
|
@ -231,13 +221,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (Tuple<string, string> command in commandList)
|
foreach (var command in commandList)
|
||||||
{
|
{
|
||||||
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
|
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
|
||||||
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
|
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
|
||||||
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||||
// parse response to make sure it worked
|
// parse response to make sure it worked
|
||||||
if (!ParseReturnMessage(buffer, receivedBytes, out string returnVal))
|
if (!ParseReturnMessage(buffer, receivedBytes, out _))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -264,21 +254,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
private async Task ReleaseLockkey(TcpClient client, uint lockKeyValue)
|
private async Task ReleaseLockkey(TcpClient client, uint lockKeyValue)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue);
|
|
||||||
|
|
||||||
var stream = client.GetStream();
|
var stream = client.GetStream();
|
||||||
|
|
||||||
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
|
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
|
||||||
await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length, CancellationToken.None).ConfigureAwait(false);
|
await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
|
||||||
|
|
||||||
var buffer = ArrayPool<byte>.Shared.Rent(8192);
|
var buffer = ArrayPool<byte>.Shared.Rent(8192);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
|
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||||
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
|
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
|
||||||
_lockkey = null;
|
_lockkey = null;
|
||||||
await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, CancellationToken.None).ConfigureAwait(false);
|
await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false);
|
||||||
await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
|
await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -316,7 +304,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
int messageLength = byteName.Length + byteValue.Length + 12;
|
int messageLength = byteName.Length + byteValue.Length + 12;
|
||||||
if (lockkey.HasValue)
|
if (lockkey.HasValue)
|
||||||
|
{
|
||||||
messageLength += 6;
|
messageLength += 6;
|
||||||
|
}
|
||||||
|
|
||||||
var message = new byte[messageLength];
|
var message = new byte[messageLength];
|
||||||
|
|
||||||
|
@ -324,21 +314,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
bool flipEndian = BitConverter.IsLittleEndian;
|
bool flipEndian = BitConverter.IsLittleEndian;
|
||||||
|
|
||||||
message[offset] = GetSetValue;
|
message[offset++] = GetSetValue;
|
||||||
offset++;
|
message[offset++] = Convert.ToByte(byteValue.Length);
|
||||||
message[offset] = Convert.ToByte(byteValue.Length);
|
|
||||||
offset++;
|
|
||||||
Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
|
Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
|
||||||
offset += byteValue.Length;
|
offset += byteValue.Length;
|
||||||
if (lockkey.HasValue)
|
if (lockkey.HasValue)
|
||||||
{
|
{
|
||||||
message[offset] = GetSetLockkey;
|
message[offset++] = GetSetLockkey;
|
||||||
offset++;
|
message[offset++] = (byte)4;
|
||||||
message[offset] = (byte)4;
|
|
||||||
offset++;
|
|
||||||
var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
|
var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
|
||||||
if (flipEndian)
|
if (flipEndian)
|
||||||
|
{
|
||||||
Array.Reverse(lockKeyBytes);
|
Array.Reverse(lockKeyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
|
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
|
||||||
offset += 4;
|
offset += 4;
|
||||||
}
|
}
|
||||||
|
@ -346,7 +335,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
// calculate crc and insert at the end of the message
|
// calculate crc and insert at the end of the message
|
||||||
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
|
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
|
||||||
if (flipEndian)
|
if (flipEndian)
|
||||||
|
{
|
||||||
Array.Reverse(crcBytes);
|
Array.Reverse(crcBytes);
|
||||||
|
}
|
||||||
|
|
||||||
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
|
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
@ -375,10 +367,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
offset += 2;
|
offset += 2;
|
||||||
|
|
||||||
// insert tag name and length
|
// insert tag name and length
|
||||||
message[offset] = GetSetName;
|
message[offset++] = GetSetName;
|
||||||
offset++;
|
message[offset++] = Convert.ToByte(byteName.Length);
|
||||||
message[offset] = Convert.ToByte(byteName.Length);
|
|
||||||
offset++;
|
|
||||||
|
|
||||||
// insert name string
|
// insert name string
|
||||||
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
|
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
|
||||||
|
@ -392,7 +382,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
returnVal = string.Empty;
|
returnVal = string.Empty;
|
||||||
|
|
||||||
if (numBytes < 4)
|
if (numBytes < 4)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var flipEndian = BitConverter.IsLittleEndian;
|
var flipEndian = BitConverter.IsLittleEndian;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
@ -400,45 +392,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
|
Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
|
||||||
|
|
||||||
if (flipEndian)
|
if (flipEndian)
|
||||||
|
{
|
||||||
Array.Reverse(msgTypeBytes);
|
Array.Reverse(msgTypeBytes);
|
||||||
|
}
|
||||||
|
|
||||||
var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
|
var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
|
||||||
offset += 2;
|
offset += 2;
|
||||||
|
|
||||||
if (msgType != GetSetReply)
|
if (msgType != GetSetReply)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
byte[] msgLengthBytes = new byte[2];
|
byte[] msgLengthBytes = new byte[2];
|
||||||
Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
|
Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
|
||||||
if (flipEndian)
|
if (flipEndian)
|
||||||
|
{
|
||||||
Array.Reverse(msgLengthBytes);
|
Array.Reverse(msgLengthBytes);
|
||||||
|
}
|
||||||
|
|
||||||
var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
|
var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
|
||||||
offset += 2;
|
offset += 2;
|
||||||
|
|
||||||
if (numBytes < msgLength + 8)
|
if (numBytes < msgLength + 8)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var nameTag = buf[offset];
|
var nameTag = buf[offset++];
|
||||||
offset++;
|
|
||||||
|
|
||||||
var nameLength = buf[offset];
|
var nameLength = buf[offset++];
|
||||||
offset++;
|
|
||||||
|
|
||||||
// skip the name field to get to value for return
|
// skip the name field to get to value for return
|
||||||
offset += nameLength;
|
offset += nameLength;
|
||||||
|
|
||||||
var valueTag = buf[offset];
|
var valueTag = buf[offset++];
|
||||||
offset++;
|
|
||||||
|
|
||||||
var valueLength = buf[offset];
|
var valueLength = buf[offset++];
|
||||||
offset++;
|
|
||||||
|
|
||||||
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
|
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HdHomerunCrc
|
private static class HdHomerunCrc
|
||||||
{
|
{
|
||||||
private static uint[] crc_table = {
|
private static uint[] crc_table = {
|
||||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
||||||
|
@ -510,15 +506,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
var hash = 0xffffffff;
|
var hash = 0xffffffff;
|
||||||
for (var i = 0; i < numBytes; i++)
|
for (var i = 0; i < numBytes; i++)
|
||||||
|
{
|
||||||
hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
|
hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
|
||||||
|
}
|
||||||
|
|
||||||
var tmp = ~hash & 0xffffffff;
|
var tmp = ~hash & 0xffffffff;
|
||||||
var b0 = tmp & 0xff;
|
var b0 = tmp & 0xff;
|
||||||
var b1 = (tmp >> 8) & 0xff;
|
var b1 = (tmp >> 8) & 0xff;
|
||||||
var b2 = (tmp >> 16) & 0xff;
|
var b2 = (tmp >> 16) & 0xff;
|
||||||
var b3 = (tmp >> 24) & 0xff;
|
var b3 = (tmp >> 24) & 0xff;
|
||||||
hash = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
|
return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
@ -18,6 +19,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
|
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
|
||||||
{
|
{
|
||||||
|
private const int RtpHeaderBytes = 12;
|
||||||
|
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory;
|
private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory;
|
||||||
|
|
||||||
|
@ -32,13 +35,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
IHdHomerunChannelCommands channelCommands,
|
IHdHomerunChannelCommands channelCommands,
|
||||||
int numTuners,
|
int numTuners,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IHttpClient httpClient,
|
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
MediaBrowser.Model.Net.ISocketFactory socketFactory,
|
MediaBrowser.Model.Net.ISocketFactory socketFactory,
|
||||||
INetworkManager networkManager)
|
INetworkManager networkManager,
|
||||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
|
IStreamHelper streamHelper)
|
||||||
|
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
|
||||||
{
|
{
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_socketFactory = socketFactory;
|
_socketFactory = socketFactory;
|
||||||
|
@ -80,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
}
|
}
|
||||||
|
|
||||||
var udpClient = _socketFactory.CreateUdpSocket(localPort);
|
var udpClient = _socketFactory.CreateUdpSocket(localPort);
|
||||||
var hdHomerunManager = new HdHomerunManager(Logger);
|
var hdHomerunManager = new HdHomerunManager();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -103,7 +106,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
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.Protocol = MediaProtocol.File;
|
||||||
//OpenedMediaSource.Path = tempFile;
|
//OpenedMediaSource.Path = tempFile;
|
||||||
|
@ -148,50 +156,43 @@ 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)
|
private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var bufferSize = 81920;
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamDefaults.DefaultCopyToBufferSize);
|
||||||
|
try
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
|
using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
|
||||||
|
using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
|
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
|
||||||
|
int read;
|
||||||
currentCancellationToken = cancellationToken;
|
var resolved = false;
|
||||||
|
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
|
||||||
read -= RtpHeaderBytes;
|
|
||||||
|
|
||||||
if (read > 0)
|
|
||||||
{
|
{
|
||||||
fileStream.Write(buffer, RtpHeaderBytes, read);
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
}
|
|
||||||
|
|
||||||
if (!resolved)
|
currentCancellationToken = cancellationToken;
|
||||||
{
|
|
||||||
resolved = true;
|
read -= RtpHeaderBytes;
|
||||||
DateOpened = DateTime.UtcNow;
|
|
||||||
Resolve(openTaskCompletionSource);
|
if (read > 0)
|
||||||
|
{
|
||||||
|
await fileStream.WriteAsync(buffer, RtpHeaderBytes, read).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolved)
|
||||||
|
{
|
||||||
|
resolved = true;
|
||||||
|
DateOpened = DateTime.UtcNow;
|
||||||
|
openTaskCompletionSource.TrySetResult(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,27 +16,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
{
|
{
|
||||||
public class LiveStream : ILiveStream
|
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 IFileSystem FileSystem;
|
||||||
protected readonly IServerApplicationPaths AppPaths;
|
protected readonly IServerApplicationPaths AppPaths;
|
||||||
|
protected readonly IStreamHelper StreamHelper;
|
||||||
|
|
||||||
protected string TempFilePath;
|
protected string TempFilePath;
|
||||||
protected readonly ILogger Logger;
|
protected readonly ILogger Logger;
|
||||||
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
|
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
public string TunerHostId { get; }
|
public LiveStream(
|
||||||
|
MediaSourceInfo mediaSource,
|
||||||
public DateTime DateOpened { get; protected set; }
|
TunerHostInfo tuner,
|
||||||
|
IFileSystem fileSystem,
|
||||||
public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
|
ILogger logger,
|
||||||
|
IServerApplicationPaths appPaths,
|
||||||
|
IStreamHelper streamHelper)
|
||||||
{
|
{
|
||||||
OriginalMediaSource = mediaSource;
|
OriginalMediaSource = mediaSource;
|
||||||
FileSystem = fileSystem;
|
FileSystem = fileSystem;
|
||||||
|
@ -51,11 +45,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
}
|
}
|
||||||
|
|
||||||
AppPaths = appPaths;
|
AppPaths = appPaths;
|
||||||
|
StreamHelper = streamHelper;
|
||||||
|
|
||||||
ConsumerCount = 1;
|
ConsumerCount = 1;
|
||||||
SetTempFilePath("ts");
|
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)
|
protected void SetTempFilePath(string extension)
|
||||||
{
|
{
|
||||||
TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
|
TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
|
||||||
|
@ -71,24 +81,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
{
|
{
|
||||||
EnableStreamSharing = false;
|
EnableStreamSharing = false;
|
||||||
|
|
||||||
Logger.LogInformation("Closing " + GetType().Name);
|
Logger.LogInformation("Closing {Type}", GetType().Name);
|
||||||
|
|
||||||
LiveStreamCancellationTokenSource.Cancel();
|
LiveStreamCancellationTokenSource.Cancel();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Stream GetInputStream(string path, bool allowAsyncFileRead)
|
protected FileStream GetInputStream(string path, bool allowAsyncFileRead)
|
||||||
{
|
=> new FileStream(
|
||||||
var fileOpenOptions = FileOpenOptions.SequentialScan;
|
path,
|
||||||
|
FileMode.Open,
|
||||||
if (allowAsyncFileRead)
|
FileAccess.Read,
|
||||||
{
|
FileShare.ReadWrite,
|
||||||
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
StreamDefaults.DefaultFileStreamBufferSize,
|
||||||
}
|
allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
|
||||||
|
|
||||||
return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task DeleteTempFiles()
|
public Task DeleteTempFiles()
|
||||||
{
|
{
|
||||||
|
@ -144,8 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
|
bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
|
||||||
|
|
||||||
var nextFileInfo = GetNextFile(null);
|
var nextFileInfo = GetNextFile(null);
|
||||||
var nextFile = nextFileInfo.Item1;
|
var nextFile = nextFileInfo.file;
|
||||||
var isLastFile = nextFileInfo.Item2;
|
var isLastFile = nextFileInfo.isLastFile;
|
||||||
|
|
||||||
while (!string.IsNullOrEmpty(nextFile))
|
while (!string.IsNullOrEmpty(nextFile))
|
||||||
{
|
{
|
||||||
|
@ -155,8 +162,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
seekFile = false;
|
seekFile = false;
|
||||||
nextFileInfo = GetNextFile(nextFile);
|
nextFileInfo = GetNextFile(nextFile);
|
||||||
nextFile = nextFileInfo.Item1;
|
nextFile = nextFileInfo.file;
|
||||||
isLastFile = nextFileInfo.Item2;
|
isLastFile = nextFileInfo.isLastFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInformation("Live Stream ended.");
|
Logger.LogInformation("Live Stream ended.");
|
||||||
|
@ -180,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)
|
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)
|
if (seekFile)
|
||||||
{
|
{
|
||||||
TrySeek(inputStream, -20000);
|
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)
|
private void TrySeek(FileStream stream, long offset)
|
||||||
{
|
{
|
||||||
if (!stream.CanSeek)
|
if (!stream.CanSeek)
|
||||||
|
|
|
@ -28,14 +28,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
private readonly IStreamHelper _streamHelper;
|
||||||
|
|
||||||
public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
|
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)
|
: base(config, logger, jsonSerializer, fileSystem)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
_streamHelper = streamHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Type => "m3u";
|
public override string Type => "m3u";
|
||||||
|
@ -103,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
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)
|
public async Task Validate(TunerHostInfo info)
|
||||||
|
|
|
@ -19,8 +19,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
|
||||||
public SharedHttpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
|
public SharedHttpStream(
|
||||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
|
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;
|
_httpClient = httpClient;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
@ -118,7 +127,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
using (var stream = response.Content)
|
using (var stream = response.Content)
|
||||||
using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
|
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.DefaultFileStreamBufferSize,
|
||||||
|
() => Resolve(openTaskCompletionSource),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
|
@ -128,6 +142,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error copying live stream.");
|
Logger.LogError(ex, "Error copying live stream.");
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableStreamSharing = false;
|
EnableStreamSharing = false;
|
||||||
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
|
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,6 @@ namespace MediaBrowser.Model.IO
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default file stream buffer size
|
/// The default file stream buffer size
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int DefaultFileStreamBufferSize = 81920;
|
public const int DefaultFileStreamBufferSize = 4096;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue