Merge pull request #1521 from Bond-009/hdhomerun

Clean up livestreaming code
This commit is contained in:
Anthony Lavado 2019-08-17 02:24:39 -04:00 committed by GitHub
commit 6766e04dd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 310 additions and 265 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 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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