Merge pull request #1521 from Bond-009/hdhomerun

Clean up livestreaming code
pull/1644/head
Anthony Lavado 5 years ago committed by GitHub
commit 6766e04dd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 stream = response.Content)
{
var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
if (info.ImportFavoritesOnly)
{
lineup = lineup.Where(i => i.Favorite).ToList();
}
using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
using (var stream = response.Content)
{
var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
return lineup.Where(i => !i.DRM).ToList();
if (info.ImportFavoritesOnly)
{
lineup = lineup.Where(i => i.Favorite).ToList();
}
return lineup.Where(i => !i.DRM).ToList();
}
}
@ -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);
var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
if (!string.IsNullOrEmpty(cacheKey))
if (!string.IsNullOrEmpty(cacheKey))
{
lock (_modelCache)
{
lock (_modelCache)
{
_modelCache[cacheKey] = discoverResponse;
}
_modelCache[cacheKey] = discoverResponse;
}
return discoverResponse;
}
return discoverResponse;
}
}
catch (HttpException ex)
@ -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, "internet540"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
}
list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
}
}
catch
{
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
}
if (list.Count == 0)
@ -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 HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
}
return new HdHomerunUdpStream(
mediaSource,
info,
streamId,
new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
modelInfo.TunerCount,
FileSystem,
Logger,
Config.ApplicationPaths,
_appHost,
_socketFactory,
_networkManager,
_streamHelper);
}
public async Task Validate(TunerHostInfo info)
@ -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);
hostInfo.DeviceId = modelInfo.DeviceID;
hostInfo.FriendlyName = modelInfo.FriendlyName;
var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
return hostInfo;
}
catch
{
// logged at lower levels
}
hostInfo.DeviceId = modelInfo.DeviceID;
hostInfo.FriendlyName = modelInfo.FriendlyName;
return null;
return hostInfo;
}
}
}

@ -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();
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)
{
cancellationToken.ThrowIfCancellationRequested();
currentCancellationToken = cancellationToken;
currentCancellationToken = cancellationToken;
read -= RtpHeaderBytes;
read -= RtpHeaderBytes;
if (read > 0)
{
fileStream.Write(buffer, RtpHeaderBytes, read);
}
if (read > 0)
{
await fileStream.WriteAsync(buffer, RtpHeaderBytes, read).ConfigureAwait(false);
}
if (!resolved)
{
resolved = true;
DateOpened = DateTime.UtcNow;
Resolve(openTaskCompletionSource);
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;
public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
private readonly IStreamHelper _streamHelper;
public M3UTunerHost(
IServerConfigurationManager config,
IMediaSourceManager mediaSourceManager,
ILogger logger,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IHttpClient httpClient,
IServerApplicationHost appHost,
INetworkManager networkManager,
IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_networkManager = networkManager;
_mediaSourceManager = mediaSourceManager;
_streamHelper = streamHelper;
}
public override string Type => "m3u";
@ -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…
Cancel
Save