Clean up livestreaming code

This commit is contained in:
Bond_009 2019-07-07 16:39:35 +02:00
parent 7a27dd8a1b
commit 237db8ae92
9 changed files with 294 additions and 260 deletions

View File

@ -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

View File

@ -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);

View File

@ -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;
} }
} }
} }

View File

@ -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;
} }
} }
} }

View File

@ -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);
}
} }
} }
} }

View File

@ -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)

View File

@ -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)

View File

@ -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);
}); });

View File

@ -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;
} }
} }