mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-26 17:40:30 +01:00
Merge branch 'master' into TVFix
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -38,6 +40,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
public virtual bool IsSupported => true;
|
||||
|
||||
protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
|
||||
|
||||
public abstract string Type { get; }
|
||||
|
||||
public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken)
|
||||
@@ -155,7 +158,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return new List<MediaSourceInfo>();
|
||||
}
|
||||
|
||||
protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tuner, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
|
||||
protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
|
||||
|
||||
public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
internal class Channels
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -10,8 +12,9 @@ using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -74,7 +77,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken)
|
||||
.ConfigureAwait(false) ?? new List<Channels>();
|
||||
@@ -92,17 +95,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
public bool IsLegacyTuner { get; set; }
|
||||
}
|
||||
|
||||
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
|
||||
{
|
||||
var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false);
|
||||
var lineup = await GetLineup(tuner, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return lineup.Select(i => new HdHomerunChannelInfo
|
||||
{
|
||||
Name = i.GuideName,
|
||||
Number = i.GuideNumber,
|
||||
Id = GetChannelId(info, i),
|
||||
Id = GetChannelId(tuner, i),
|
||||
IsFavorite = i.Favorite,
|
||||
TunerHostId = info.Id,
|
||||
TunerHostId = tuner.Id,
|
||||
IsHD = i.HD,
|
||||
AudioCodec = i.AudioCodec,
|
||||
VideoCodec = i.VideoCodec,
|
||||
@@ -182,16 +185,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
|
||||
var tuners = new List<LiveTvTunerInfo>();
|
||||
while (!sr.EndOfStream)
|
||||
await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false))
|
||||
{
|
||||
string line = StripXML(sr.ReadLine());
|
||||
if (line.Contains("Channel", StringComparison.Ordinal))
|
||||
string stripedLine = StripXML(line);
|
||||
if (stripedLine.Contains("Channel", StringComparison.Ordinal))
|
||||
{
|
||||
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")
|
||||
var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
|
||||
var name = stripedLine.Substring(0, index - 1);
|
||||
var currentChannel = stripedLine.Substring(index + 7);
|
||||
if (string.Equals(currentChannel, "none", StringComparison.Ordinal))
|
||||
{
|
||||
status = LiveTvTunerStatus.LiveTv;
|
||||
}
|
||||
@@ -421,10 +424,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
string audioCodec = channelInfo.AudioCodec;
|
||||
|
||||
if (!videoBitrate.HasValue)
|
||||
{
|
||||
videoBitrate = isHd ? 15000000 : 2000000;
|
||||
}
|
||||
videoBitrate ??= isHd ? 15000000 : 2000000;
|
||||
|
||||
int? audioBitrate = isHd ? 448000 : 192000;
|
||||
|
||||
@@ -496,57 +496,53 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
return mediaSource;
|
||||
}
|
||||
|
||||
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
|
||||
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<MediaSourceInfo>();
|
||||
|
||||
var channelId = channelInfo.Id;
|
||||
var channelId = channel.Id;
|
||||
var hdhrId = GetHdHrIdFromChannelId(channelId);
|
||||
|
||||
var hdHomerunChannelInfo = channelInfo as HdHomerunChannelInfo;
|
||||
|
||||
var isLegacyTuner = hdHomerunChannelInfo != null && hdHomerunChannelInfo.IsLegacyTuner;
|
||||
|
||||
if (isLegacyTuner)
|
||||
if (channel is HdHomerunChannelInfo hdHomerunChannelInfo && hdHomerunChannelInfo.IsLegacyTuner)
|
||||
{
|
||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
|
||||
list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
|
||||
}
|
||||
else
|
||||
{
|
||||
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||
var modelInfo = await GetModelInfo(tuner, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (modelInfo != null && modelInfo.SupportsTranscoding)
|
||||
{
|
||||
if (info.AllowHWTranscoding)
|
||||
if (tuner.AllowHWTranscoding)
|
||||
{
|
||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
|
||||
list.Add(GetMediaSource(tuner, hdhrId, channel, "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(tuner, hdhrId, channel, "internet540"));
|
||||
list.Add(GetMediaSource(tuner, hdhrId, channel, "internet480"));
|
||||
list.Add(GetMediaSource(tuner, hdhrId, channel, "internet360"));
|
||||
list.Add(GetMediaSource(tuner, hdhrId, channel, "internet240"));
|
||||
list.Add(GetMediaSource(tuner, hdhrId, channel, "mobile"));
|
||||
}
|
||||
|
||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
|
||||
list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
|
||||
}
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
|
||||
list.Add(GetMediaSource(tuner, hdhrId, channel, "native"));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
{
|
||||
var tunerCount = info.TunerCount;
|
||||
var tunerCount = tunerHost.TunerCount;
|
||||
|
||||
if (tunerCount > 0)
|
||||
{
|
||||
var tunerHostId = info.Id;
|
||||
var tunerHostId = tunerHost.Id;
|
||||
var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (liveStreams.Count() >= tunerCount)
|
||||
@@ -557,26 +553,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
var profile = streamId.Split('_')[0];
|
||||
|
||||
Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelInfo.Id, streamId, profile);
|
||||
Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channel.Id, streamId, profile);
|
||||
|
||||
var hdhrId = GetHdHrIdFromChannelId(channelInfo.Id);
|
||||
var hdhrId = GetHdHrIdFromChannelId(channel.Id);
|
||||
|
||||
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
|
||||
var hdhomerunChannel = channel as HdHomerunChannelInfo;
|
||||
|
||||
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||
var modelInfo = await GetModelInfo(tunerHost, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!modelInfo.SupportsTranscoding)
|
||||
{
|
||||
profile = "native";
|
||||
}
|
||||
|
||||
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
|
||||
var mediaSource = GetMediaSource(tunerHost, hdhrId, channel, profile);
|
||||
|
||||
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
|
||||
{
|
||||
return new HdHomerunUdpStream(
|
||||
mediaSource,
|
||||
info,
|
||||
tunerHost,
|
||||
streamId,
|
||||
new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
|
||||
modelInfo.TunerCount,
|
||||
@@ -584,7 +580,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
Logger,
|
||||
Config,
|
||||
_appHost,
|
||||
_networkManager,
|
||||
_streamHelper);
|
||||
}
|
||||
|
||||
@@ -593,7 +588,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
mediaSource.Protocol = MediaProtocol.Http;
|
||||
|
||||
var httpUrl = channelInfo.Path;
|
||||
var httpUrl = channel.Path;
|
||||
|
||||
// If raw was used, the tuner doesn't support params
|
||||
if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -605,7 +600,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
return new SharedHttpStream(
|
||||
mediaSource,
|
||||
info,
|
||||
tunerHost,
|
||||
streamId,
|
||||
FileSystem,
|
||||
_httpClientFactory,
|
||||
@@ -617,7 +612,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
return new HdHomerunUdpStream(
|
||||
mediaSource,
|
||||
info,
|
||||
tunerHost,
|
||||
streamId,
|
||||
new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
|
||||
modelInfo.TunerCount,
|
||||
@@ -625,7 +620,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
Logger,
|
||||
Config,
|
||||
_appHost,
|
||||
_networkManager,
|
||||
_streamHelper);
|
||||
}
|
||||
|
||||
@@ -661,7 +655,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
_modelCache.Clear();
|
||||
}
|
||||
|
||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
|
||||
using var timedCancellationToken = new CancellationTokenSource(discoveryDurationMs);
|
||||
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timedCancellationToken.Token, cancellationToken);
|
||||
cancellationToken = linkedCancellationTokenSource.Token;
|
||||
var list = new List<TunerHostInfo>();
|
||||
|
||||
// Create udp broadcast discovery message
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -25,6 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
private string _channel;
|
||||
private string _program;
|
||||
|
||||
public LegacyHdHomerunChannelCommands(string url)
|
||||
{
|
||||
// parse url for channel and program
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -10,7 +12,6 @@ using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
@@ -28,7 +29,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IHdHomerunChannelCommands _channelCommands;
|
||||
private readonly int _numTuners;
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
public HdHomerunUdpStream(
|
||||
MediaSourceInfo mediaSource,
|
||||
@@ -40,12 +40,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
ILogger logger,
|
||||
IConfigurationManager configurationManager,
|
||||
IServerApplicationHost appHost,
|
||||
INetworkManager networkManager,
|
||||
IStreamHelper streamHelper)
|
||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_networkManager = networkManager;
|
||||
OriginalStreamId = originalStreamId;
|
||||
_channelCommands = channelCommands;
|
||||
_numTuners = numTuners;
|
||||
@@ -126,7 +124,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
using (udpClient)
|
||||
using (hdHomerunManager)
|
||||
{
|
||||
if (!(ex is OperationCanceledException))
|
||||
if (ex is not OperationCanceledException)
|
||||
{
|
||||
Logger.LogError(ex, "Error opening live stream:");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -150,7 +152,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token;
|
||||
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token);
|
||||
cancellationToken = linkedCancellationTokenSource.Token;
|
||||
|
||||
// use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -27,6 +29,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
|
||||
{
|
||||
private static readonly string[] _disallowedSharedStreamExtensions =
|
||||
{
|
||||
".mkv",
|
||||
".mp4",
|
||||
".m3u8",
|
||||
".mpd"
|
||||
};
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly INetworkManager _networkManager;
|
||||
@@ -61,12 +71,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
|
||||
{
|
||||
var channelIdPrefix = GetFullChannelIdPrefix(info);
|
||||
var channelIdPrefix = GetFullChannelIdPrefix(tuner);
|
||||
|
||||
return await new M3uParser(Logger, _httpClientFactory, _appHost)
|
||||
.Parse(info, channelIdPrefix, cancellationToken)
|
||||
return await new M3uParser(Logger, _httpClientFactory)
|
||||
.Parse(tuner, channelIdPrefix, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -86,21 +96,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return Task.FromResult(list);
|
||||
}
|
||||
|
||||
private static readonly string[] _disallowedSharedStreamExtensions =
|
||||
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
{
|
||||
".mkv",
|
||||
".mp4",
|
||||
".m3u8",
|
||||
".mpd"
|
||||
};
|
||||
|
||||
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
{
|
||||
var tunerCount = info.TunerCount;
|
||||
var tunerCount = tunerHost.TunerCount;
|
||||
|
||||
if (tunerCount > 0)
|
||||
{
|
||||
var tunerHostId = info.Id;
|
||||
var tunerHostId = tunerHost.Id;
|
||||
var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (liveStreams.Count() >= tunerCount)
|
||||
@@ -109,7 +111,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
}
|
||||
}
|
||||
|
||||
var sources = await GetChannelStreamMediaSources(info, channelInfo, cancellationToken).ConfigureAwait(false);
|
||||
var sources = await GetChannelStreamMediaSources(tunerHost, channel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = sources[0];
|
||||
|
||||
@@ -119,23 +121,23 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
|
||||
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
|
||||
}
|
||||
}
|
||||
|
||||
return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper);
|
||||
return new LiveStream(mediaSource, tunerHost, FileSystem, Logger, Config, _streamHelper);
|
||||
}
|
||||
|
||||
public async Task Validate(TunerHostInfo info)
|
||||
{
|
||||
using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
|
||||
using (var stream = await new M3uParser(Logger, _httpClientFactory).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
|
||||
protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channelInfo) });
|
||||
return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(tuner, channel) });
|
||||
}
|
||||
|
||||
protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -8,9 +10,9 @@ using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -19,15 +21,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
public class M3uParser
|
||||
{
|
||||
private const string ExtInfPrefix = "#EXTINF:";
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory, IServerApplicationHost appHost)
|
||||
public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public async Task<List<ChannelInfo>> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken)
|
||||
@@ -35,79 +37,74 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
// Read the file and display it line by line.
|
||||
using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
|
||||
{
|
||||
return GetChannels(reader, channelIdPrefix, info.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ChannelInfo> ParseString(string text, string channelIdPrefix, string tunerHostId)
|
||||
{
|
||||
// Read the file and display it line by line.
|
||||
using (var reader = new StringReader(text))
|
||||
{
|
||||
return GetChannels(reader, channelIdPrefix, tunerHostId);
|
||||
return await GetChannelsAsync(reader, channelIdPrefix, info.Id).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
if (info == null)
|
||||
{
|
||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
|
||||
if (!string.IsNullOrEmpty(info.UserAgent))
|
||||
{
|
||||
requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
|
||||
}
|
||||
|
||||
var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.SendAsync(requestMessage, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
|
||||
return File.OpenRead(info.Url);
|
||||
if (!info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return File.OpenRead(info.Url);
|
||||
}
|
||||
|
||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, info.Url);
|
||||
if (!string.IsNullOrEmpty(info.UserAgent))
|
||||
{
|
||||
requestMessage.Headers.UserAgent.TryParseAdd(info.UserAgent);
|
||||
}
|
||||
|
||||
// Set HttpCompletionOption.ResponseHeadersRead to prevent timeouts on larger files
|
||||
var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private const string ExtInfPrefix = "#EXTINF:";
|
||||
|
||||
private List<ChannelInfo> GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId)
|
||||
private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
|
||||
{
|
||||
var channels = new List<ChannelInfo>();
|
||||
string line;
|
||||
string extInf = string.Empty;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
|
||||
{
|
||||
line = line.Trim();
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
var trimmedLine = line.Trim();
|
||||
if (string.IsNullOrWhiteSpace(trimmedLine))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
|
||||
if (trimmedLine.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
if (trimmedLine.StartsWith(ExtInfPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
extInf = line.Substring(ExtInfPrefix.Length).Trim();
|
||||
_logger.LogInformation("Found m3u channel: {0}", extInf);
|
||||
extInf = trimmedLine.Substring(ExtInfPrefix.Length).Trim();
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith('#'))
|
||||
else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
|
||||
{
|
||||
var channel = GetChannelnfo(extInf, tunerHostId, line);
|
||||
var channel = GetChannelnfo(extInf, tunerHostId, trimmedLine);
|
||||
if (string.IsNullOrWhiteSpace(channel.Id))
|
||||
{
|
||||
channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
channel.Id = channelIdPrefix + trimmedLine.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
channel.Path = line;
|
||||
channel.Path = trimmedLine;
|
||||
channels.Add(channel);
|
||||
_logger.LogInformation("Parsed channel: {ChannelName}", channel.Name);
|
||||
extInf = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -298,11 +295,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
}
|
||||
}
|
||||
|
||||
attributes.TryGetValue("tvg-name", out string name);
|
||||
string name = nameInExtInf;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = nameInExtInf;
|
||||
attributes.TryGetValue("tvg-name", out name);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
@@ -89,8 +91,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
|
||||
|
||||
// OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||
@@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
if (!taskCompletionSource.Task.Result)
|
||||
{
|
||||
Logger.LogWarning("Zero bytes copied from stream {0} to {1} but no exception raised", GetType().Name, TempFilePath);
|
||||
throw new EndOfStreamException(String.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
|
||||
throw new EndOfStreamException(string.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user