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