aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2024-02-25 22:01:28 +0100
committerGitHub <noreply@github.com>2024-02-25 22:01:28 +0100
commitc72bd8a09292e8ee4a42abbdcc1df7bf283a6d55 (patch)
treee738a4f1b45693fe71dafb03558afc84c6914734
parent43b1e12166c18561685824de0b699cc25d1ef0e9 (diff)
parentb5a3c71b3aba0d8a1e1e65f7d07e1caae43856e2 (diff)
Merge pull request #11054 from barronpm/livetv-mediasourceprovider
LiveTV MediaSourceProvider refactor
-rw-r--r--Jellyfin.Server/Startup.cs5
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs19
-rw-r--r--src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs9
-rw-r--r--src/Jellyfin.LiveTv/DefaultLiveTvService.cs (renamed from src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs)10
-rw-r--r--src/Jellyfin.LiveTv/EmbyTV/NfoConfigurationExtensions.cs19
-rw-r--r--src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs2
-rw-r--r--src/Jellyfin.LiveTv/Guide/GuideManager.cs2
-rw-r--r--src/Jellyfin.LiveTv/LiveTvManager.cs197
-rw-r--r--src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs220
-rw-r--r--src/Jellyfin.LiveTv/Recordings/RecordingHelper.cs (renamed from src/Jellyfin.LiveTv/EmbyTV/RecordingHelper.cs)9
-rw-r--r--src/Jellyfin.LiveTv/Recordings/RecordingNotifier.cs (renamed from src/Jellyfin.LiveTv/RecordingNotifier.cs)2
-rw-r--r--src/Jellyfin.LiveTv/Recordings/RecordingsHost.cs (renamed from src/Jellyfin.LiveTv/EmbyTV/LiveTvHost.cs)10
-rw-r--r--src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs1
-rw-r--r--src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs1
-rw-r--r--src/Jellyfin.LiveTv/Timers/TimerManager.cs4
-rw-r--r--tests/Jellyfin.LiveTv.Tests/RecordingHelperTests.cs2
16 files changed, 243 insertions, 269 deletions
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 558ad5b7b..e9fb3e4c2 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -6,9 +6,8 @@ using System.Net.Mime;
using System.Text;
using Emby.Server.Implementations.EntryPoints;
using Jellyfin.Api.Middleware;
-using Jellyfin.LiveTv;
-using Jellyfin.LiveTv.EmbyTV;
using Jellyfin.LiveTv.Extensions;
+using Jellyfin.LiveTv.Recordings;
using Jellyfin.MediaEncoding.Hls.Extensions;
using Jellyfin.Networking;
using Jellyfin.Networking.HappyEyeballs;
@@ -128,7 +127,7 @@ namespace Jellyfin.Server
services.AddHlsPlaylistGenerator();
services.AddLiveTvServices();
- services.AddHostedService<LiveTvHost>();
+ services.AddHostedService<RecordingsHost>();
services.AddHostedService<AutoDiscoveryHost>();
services.AddHostedService<PortForwardingHost>();
services.AddHostedService<NfoUserDataSaver>();
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index ed08cdc47..c0e46ba24 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -10,7 +10,6 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
@@ -106,16 +105,6 @@ namespace MediaBrowser.Controller.LiveTv
Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken);
/// <summary>
- /// Gets the channel stream.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <param name="mediaSourceId">The media source identifier.</param>
- /// <param name="currentLiveStreams">The current live streams.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{StreamResponseInfo}.</returns>
- Task<Tuple<MediaSourceInfo, ILiveStream>> GetChannelStream(string id, string mediaSourceId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
-
- /// <summary>
/// Gets the program.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -221,14 +210,6 @@ namespace MediaBrowser.Controller.LiveTv
QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken);
/// <summary>
- /// Gets the channel media sources.
- /// </summary>
- /// <param name="item">Item to search for.</param>
- /// <param name="cancellationToken">CancellationToken to use for operation.</param>
- /// <returns>Channel media sources wrapped in a task.</returns>
- Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(BaseItem item, CancellationToken cancellationToken);
-
- /// <summary>
/// Adds the information to program dto.
/// </summary>
/// <param name="programs">The programs.</param>
diff --git a/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs
index 67d0e5295..f7888496f 100644
--- a/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs
+++ b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.LiveTv;
namespace Jellyfin.LiveTv.Configuration;
@@ -15,4 +16,12 @@ public static class LiveTvConfigurationExtensions
/// <returns>The <see cref="LiveTvOptions"/>.</returns>
public static LiveTvOptions GetLiveTvConfiguration(this IConfigurationManager configurationManager)
=> configurationManager.GetConfiguration<LiveTvOptions>("livetv");
+
+ /// <summary>
+ /// Gets the <see cref="XbmcMetadataOptions"/>.
+ /// </summary>
+ /// <param name="configurationManager">The <see cref="IConfigurationManager"/>.</param>
+ /// <returns>The <see cref="XbmcMetadataOptions"/>.</returns>
+ public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager configurationManager)
+ => configurationManager.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
}
diff --git a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/DefaultLiveTvService.cs
index 06a0ea4e9..318cc7acd 100644
--- a/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs
+++ b/src/Jellyfin.LiveTv/DefaultLiveTvService.cs
@@ -24,13 +24,13 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Jellyfin.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv
{
- public sealed class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds
+ public sealed class DefaultLiveTvService : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds
{
public const string ServiceName = "Emby";
- private readonly ILogger<EmbyTV> _logger;
+ private readonly ILogger<DefaultLiveTvService> _logger;
private readonly IServerConfigurationManager _config;
private readonly ITunerHostManager _tunerHostManager;
private readonly IListingsManager _listingsManager;
@@ -40,8 +40,8 @@ namespace Jellyfin.LiveTv.EmbyTV
private readonly TimerManager _timerManager;
private readonly SeriesTimerManager _seriesTimerManager;
- public EmbyTV(
- ILogger<EmbyTV> logger,
+ public DefaultLiveTvService(
+ ILogger<DefaultLiveTvService> logger,
IServerConfigurationManager config,
ITunerHostManager tunerHostManager,
IListingsManager listingsManager,
diff --git a/src/Jellyfin.LiveTv/EmbyTV/NfoConfigurationExtensions.cs b/src/Jellyfin.LiveTv/EmbyTV/NfoConfigurationExtensions.cs
deleted file mode 100644
index e8570f0e0..000000000
--- a/src/Jellyfin.LiveTv/EmbyTV/NfoConfigurationExtensions.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Configuration;
-
-namespace Jellyfin.LiveTv.EmbyTV
-{
- /// <summary>
- /// Class containing extension methods for working with the nfo configuration.
- /// </summary>
- public static class NfoConfigurationExtensions
- {
- /// <summary>
- /// Gets the nfo configuration.
- /// </summary>
- /// <param name="configurationManager">The configuration manager.</param>
- /// <returns>The nfo configuration.</returns>
- public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager configurationManager)
- => configurationManager.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
- }
-}
diff --git a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs
index e247ecb44..73729c950 100644
--- a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs
+++ b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs
@@ -37,7 +37,7 @@ public static class LiveTvServiceCollectionExtensions
services.AddSingleton<IGuideManager, GuideManager>();
services.AddSingleton<IRecordingsManager, RecordingsManager>();
- services.AddSingleton<ILiveTvService, EmbyTV.EmbyTV>();
+ services.AddSingleton<ILiveTvService, DefaultLiveTvService>();
services.AddSingleton<ITunerHost, HdHomerunHost>();
services.AddSingleton<ITunerHost, M3UTunerHost>();
services.AddSingleton<IListingsProvider, SchedulesDirect>();
diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs
index 056bb6e6d..39f174cc2 100644
--- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs
+++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs
@@ -141,7 +141,7 @@ public class GuideManager : IGuideManager
CleanDatabase(newProgramIdList.ToArray(), [BaseItemKind.LiveTvProgram], progress, cancellationToken);
}
- var coreService = _liveTvManager.Services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
+ var coreService = _liveTvManager.Services.OfType<DefaultLiveTvService>().FirstOrDefault();
if (coreService is not null)
{
await coreService.RefreshSeriesTimers(cancellationToken).ConfigureAwait(false);
diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs
index f7b9604af..c19d8195c 100644
--- a/src/Jellyfin.LiveTv/LiveTvManager.cs
+++ b/src/Jellyfin.LiveTv/LiveTvManager.cs
@@ -12,7 +12,6 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using Jellyfin.LiveTv.Configuration;
-using Jellyfin.LiveTv.IO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@@ -72,7 +71,7 @@ namespace Jellyfin.LiveTv
_recordingsManager = recordingsManager;
_services = services.ToArray();
- var defaultService = _services.OfType<EmbyTV.EmbyTV>().First();
+ var defaultService = _services.OfType<DefaultLiveTvService>().First();
defaultService.TimerCreated += OnEmbyTvTimerCreated;
defaultService.TimerCancelled += OnEmbyTvTimerCancelled;
}
@@ -152,73 +151,6 @@ namespace Jellyfin.LiveTv
return _libraryManager.GetItemsResult(internalQuery);
}
- public async Task<Tuple<MediaSourceInfo, ILiveStream>> GetChannelStream(string id, string mediaSourceId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
- {
- if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
- {
- mediaSourceId = null;
- }
-
- var channel = (LiveTvChannel)_libraryManager.GetItemById(id);
-
- bool isVideo = channel.ChannelType == ChannelType.TV;
- var service = GetService(channel);
- _logger.LogInformation("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
-
- MediaSourceInfo info;
-#pragma warning disable CA1859 // TODO: Analyzer bug?
- ILiveStream liveStream;
-#pragma warning restore CA1859
- if (service is ISupportsDirectStreamProvider supportsManagedStream)
- {
- liveStream = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
- info = liveStream.MediaSource;
- }
- else
- {
- info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
- var openedId = info.Id;
- Func<Task> closeFn = () => service.CloseLiveStream(openedId, CancellationToken.None);
-
- liveStream = new ExclusiveLiveStream(info, closeFn);
-
- var startTime = DateTime.UtcNow;
- await liveStream.Open(cancellationToken).ConfigureAwait(false);
- var endTime = DateTime.UtcNow;
- _logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
- }
-
- info.RequiresClosing = true;
-
- var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
-
- info.LiveStreamId = idPrefix + info.Id;
-
- Normalize(info, service, isVideo);
-
- return new Tuple<MediaSourceInfo, ILiveStream>(info, liveStream);
- }
-
- public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(BaseItem item, CancellationToken cancellationToken)
- {
- var baseItem = (LiveTvChannel)item;
- var service = GetService(baseItem);
-
- var sources = await service.GetChannelStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false);
-
- if (sources.Count == 0)
- {
- throw new NotImplementedException();
- }
-
- foreach (var source in sources)
- {
- Normalize(source, service, baseItem.ChannelType == ChannelType.TV);
- }
-
- return sources;
- }
-
private ILiveTvService GetService(LiveTvChannel item)
{
var name = item.ServiceName;
@@ -240,127 +172,6 @@ namespace Jellyfin.LiveTv
"No service with the name '{0}' can be found.",
name));
- private static void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
- {
- // Not all of the plugins are setting this
- mediaSource.IsInfiniteStream = true;
-
- if (mediaSource.MediaStreams.Count == 0)
- {
- if (isVideo)
- {
- mediaSource.MediaStreams = new MediaStream[]
- {
- new MediaStream
- {
- Type = MediaStreamType.Video,
- // Set the index to -1 because we don't know the exact index of the video stream within the container
- Index = -1,
-
- // Set to true if unknown to enable deinterlacing
- IsInterlaced = true
- },
- new MediaStream
- {
- Type = MediaStreamType.Audio,
- // Set the index to -1 because we don't know the exact index of the audio stream within the container
- Index = -1
- }
- };
- }
- else
- {
- mediaSource.MediaStreams = new MediaStream[]
- {
- new MediaStream
- {
- Type = MediaStreamType.Audio,
- // Set the index to -1 because we don't know the exact index of the audio stream within the container
- Index = -1
- }
- };
- }
- }
-
- // Clean some bad data coming from providers
- foreach (var stream in mediaSource.MediaStreams)
- {
- if (stream.BitRate.HasValue && stream.BitRate <= 0)
- {
- stream.BitRate = null;
- }
-
- if (stream.Channels.HasValue && stream.Channels <= 0)
- {
- stream.Channels = null;
- }
-
- if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
- {
- stream.AverageFrameRate = null;
- }
-
- if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
- {
- stream.RealFrameRate = null;
- }
-
- if (stream.Width.HasValue && stream.Width <= 0)
- {
- stream.Width = null;
- }
-
- if (stream.Height.HasValue && stream.Height <= 0)
- {
- stream.Height = null;
- }
-
- if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
- {
- stream.SampleRate = null;
- }
-
- if (stream.Level.HasValue && stream.Level <= 0)
- {
- stream.Level = null;
- }
- }
-
- var indexes = mediaSource.MediaStreams.Select(i => i.Index).Distinct().ToList();
-
- // If there are duplicate stream indexes, set them all to unknown
- if (indexes.Count != mediaSource.MediaStreams.Count)
- {
- foreach (var stream in mediaSource.MediaStreams)
- {
- stream.Index = -1;
- }
- }
-
- // Set the total bitrate if not already supplied
- mediaSource.InferTotalBitrate();
-
- if (service is not EmbyTV.EmbyTV)
- {
- // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
- // mediaSource.SupportsDirectPlay = false;
- // mediaSource.SupportsDirectStream = false;
- mediaSource.SupportsTranscoding = true;
- foreach (var stream in mediaSource.MediaStreams)
- {
- if (stream.Type == MediaStreamType.Video && string.IsNullOrWhiteSpace(stream.NalLengthSize))
- {
- stream.NalLengthSize = "0";
- }
-
- if (stream.Type == MediaStreamType.Video)
- {
- stream.IsInterlaced = true;
- }
- }
- }
- }
-
public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
{
var program = _libraryManager.GetItemById(id);
@@ -769,7 +580,7 @@ namespace Jellyfin.LiveTv
var channel = string.IsNullOrWhiteSpace(info.ChannelId)
? null
- : _libraryManager.GetItemById(_tvDtoService.GetInternalChannelId(EmbyTV.EmbyTV.ServiceName, info.ChannelId));
+ : _libraryManager.GetItemById(_tvDtoService.GetInternalChannelId(DefaultLiveTvService.ServiceName, info.ChannelId));
dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
? null
@@ -1005,7 +816,7 @@ namespace Jellyfin.LiveTv
await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
- if (service is not EmbyTV.EmbyTV)
+ if (service is not DefaultLiveTvService)
{
TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
}
@@ -1314,7 +1125,7 @@ namespace Jellyfin.LiveTv
_logger.LogInformation("New recording scheduled");
- if (service is not EmbyTV.EmbyTV)
+ if (service is not DefaultLiveTvService)
{
TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
new TimerEventInfo(newTimerId)
diff --git a/src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs b/src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs
index c6874e4db..40ac5ce0f 100644
--- a/src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs
+++ b/src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs
@@ -8,11 +8,15 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.LiveTv.IO;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
@@ -23,19 +27,27 @@ namespace Jellyfin.LiveTv
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char StreamIdDelimiter = '_';
- private readonly ILiveTvManager _liveTvManager;
- private readonly IRecordingsManager _recordingsManager;
private readonly ILogger<LiveTvMediaSourceProvider> _logger;
- private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerApplicationHost _appHost;
+ private readonly IRecordingsManager _recordingsManager;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILiveTvService[] _services;
- public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IRecordingsManager recordingsManager, ILogger<LiveTvMediaSourceProvider> logger, IMediaSourceManager mediaSourceManager, IServerApplicationHost appHost)
+ public LiveTvMediaSourceProvider(
+ ILogger<LiveTvMediaSourceProvider> logger,
+ IServerApplicationHost appHost,
+ IRecordingsManager recordingsManager,
+ IMediaSourceManager mediaSourceManager,
+ ILibraryManager libraryManager,
+ IEnumerable<ILiveTvService> services)
{
- _liveTvManager = liveTvManager;
- _recordingsManager = recordingsManager;
_logger = logger;
- _mediaSourceManager = mediaSourceManager;
_appHost = appHost;
+ _recordingsManager = recordingsManager;
+ _mediaSourceManager = mediaSourceManager;
+ _libraryManager = libraryManager;
+ _services = services.ToArray();
}
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
@@ -68,7 +80,7 @@ namespace Jellyfin.LiveTv
}
else
{
- sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
+ sources = await GetChannelMediaSources(item, cancellationToken)
.ConfigureAwait(false);
}
}
@@ -121,10 +133,200 @@ namespace Jellyfin.LiveTv
var keys = openToken.Split(StreamIdDelimiter, 3);
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
- var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
+ var info = await GetChannelStream(keys[1], mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
var liveStream = info.Item2;
return liveStream;
}
+
+ private static void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
+ {
+ // Not all of the plugins are setting this
+ mediaSource.IsInfiniteStream = true;
+
+ if (mediaSource.MediaStreams.Count == 0)
+ {
+ if (isVideo)
+ {
+ mediaSource.MediaStreams = new[]
+ {
+ new MediaStream
+ {
+ Type = MediaStreamType.Video,
+ // Set the index to -1 because we don't know the exact index of the video stream within the container
+ Index = -1,
+ // Set to true if unknown to enable deinterlacing
+ IsInterlaced = true
+ },
+ new MediaStream
+ {
+ Type = MediaStreamType.Audio,
+ // Set the index to -1 because we don't know the exact index of the audio stream within the container
+ Index = -1
+ }
+ };
+ }
+ else
+ {
+ mediaSource.MediaStreams = new[]
+ {
+ new MediaStream
+ {
+ Type = MediaStreamType.Audio,
+ // Set the index to -1 because we don't know the exact index of the audio stream within the container
+ Index = -1
+ }
+ };
+ }
+ }
+
+ // Clean some bad data coming from providers
+ foreach (var stream in mediaSource.MediaStreams)
+ {
+ if (stream.BitRate is <= 0)
+ {
+ stream.BitRate = null;
+ }
+
+ if (stream.Channels is <= 0)
+ {
+ stream.Channels = null;
+ }
+
+ if (stream.AverageFrameRate is <= 0)
+ {
+ stream.AverageFrameRate = null;
+ }
+
+ if (stream.RealFrameRate is <= 0)
+ {
+ stream.RealFrameRate = null;
+ }
+
+ if (stream.Width is <= 0)
+ {
+ stream.Width = null;
+ }
+
+ if (stream.Height is <= 0)
+ {
+ stream.Height = null;
+ }
+
+ if (stream.SampleRate is <= 0)
+ {
+ stream.SampleRate = null;
+ }
+
+ if (stream.Level is <= 0)
+ {
+ stream.Level = null;
+ }
+ }
+
+ var indexCount = mediaSource.MediaStreams.Select(i => i.Index).Distinct().Count();
+
+ // If there are duplicate stream indexes, set them all to unknown
+ if (indexCount != mediaSource.MediaStreams.Count)
+ {
+ foreach (var stream in mediaSource.MediaStreams)
+ {
+ stream.Index = -1;
+ }
+ }
+
+ // Set the total bitrate if not already supplied
+ mediaSource.InferTotalBitrate();
+
+ if (service is not DefaultLiveTvService)
+ {
+ mediaSource.SupportsTranscoding = true;
+ foreach (var stream in mediaSource.MediaStreams)
+ {
+ if (stream.Type == MediaStreamType.Video && string.IsNullOrWhiteSpace(stream.NalLengthSize))
+ {
+ stream.NalLengthSize = "0";
+ }
+
+ if (stream.Type == MediaStreamType.Video)
+ {
+ stream.IsInterlaced = true;
+ }
+ }
+ }
+ }
+
+ private async Task<Tuple<MediaSourceInfo, ILiveStream>> GetChannelStream(
+ string id,
+ string mediaSourceId,
+ List<ILiveStream> currentLiveStreams,
+ CancellationToken cancellationToken)
+ {
+ if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
+ {
+ mediaSourceId = null;
+ }
+
+ var channel = (LiveTvChannel)_libraryManager.GetItemById(id);
+
+ bool isVideo = channel.ChannelType == ChannelType.TV;
+ var service = GetService(channel.ServiceName);
+ _logger.LogInformation("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
+
+ MediaSourceInfo info;
+#pragma warning disable CA1859 // TODO: Analyzer bug?
+ ILiveStream liveStream;
+#pragma warning restore CA1859
+ if (service is ISupportsDirectStreamProvider supportsManagedStream)
+ {
+ liveStream = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
+ info = liveStream.MediaSource;
+ }
+ else
+ {
+ info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
+ var openedId = info.Id;
+ Func<Task> closeFn = () => service.CloseLiveStream(openedId, CancellationToken.None);
+
+ liveStream = new ExclusiveLiveStream(info, closeFn);
+
+ var startTime = DateTime.UtcNow;
+ await liveStream.Open(cancellationToken).ConfigureAwait(false);
+ var endTime = DateTime.UtcNow;
+ _logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
+ }
+
+ info.RequiresClosing = true;
+
+ var idPrefix = service.GetType().FullName!.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
+
+ info.LiveStreamId = idPrefix + info.Id;
+
+ Normalize(info, service, isVideo);
+
+ return new Tuple<MediaSourceInfo, ILiveStream>(info, liveStream);
+ }
+
+ private async Task<List<MediaSourceInfo>> GetChannelMediaSources(BaseItem item, CancellationToken cancellationToken)
+ {
+ var baseItem = (LiveTvChannel)item;
+ var service = GetService(baseItem.ServiceName);
+
+ var sources = await service.GetChannelStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false);
+ if (sources.Count == 0)
+ {
+ throw new NotImplementedException();
+ }
+
+ foreach (var source in sources)
+ {
+ Normalize(source, service, baseItem.ChannelType == ChannelType.TV);
+ }
+
+ return sources;
+ }
+
+ private ILiveTvService GetService(string name)
+ => _services.First(service => string.Equals(service.Name, name, StringComparison.OrdinalIgnoreCase));
}
}
diff --git a/src/Jellyfin.LiveTv/EmbyTV/RecordingHelper.cs b/src/Jellyfin.LiveTv/Recordings/RecordingHelper.cs
index 6bda231b2..2b7564045 100644
--- a/src/Jellyfin.LiveTv/EmbyTV/RecordingHelper.cs
+++ b/src/Jellyfin.LiveTv/Recordings/RecordingHelper.cs
@@ -1,19 +1,12 @@
-#pragma warning disable CS1591
-
using System;
using System.Globalization;
using System.Text;
using MediaBrowser.Controller.LiveTv;
-namespace Jellyfin.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.Recordings
{
internal static class RecordingHelper
{
- public static DateTime GetStartTime(TimerInfo timer)
- {
- return timer.StartDate.AddSeconds(-timer.PrePaddingSeconds);
- }
-
public static string GetRecordingName(TimerInfo info)
{
var name = info.Name;
diff --git a/src/Jellyfin.LiveTv/RecordingNotifier.cs b/src/Jellyfin.LiveTv/Recordings/RecordingNotifier.cs
index 226d525e7..e63afa626 100644
--- a/src/Jellyfin.LiveTv/RecordingNotifier.cs
+++ b/src/Jellyfin.LiveTv/Recordings/RecordingNotifier.cs
@@ -11,7 +11,7 @@ using MediaBrowser.Model.Session;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-namespace Jellyfin.LiveTv
+namespace Jellyfin.LiveTv.Recordings
{
/// <summary>
/// <see cref="IHostedService"/> responsible for notifying users when a LiveTV recording is completed.
diff --git a/src/Jellyfin.LiveTv/EmbyTV/LiveTvHost.cs b/src/Jellyfin.LiveTv/Recordings/RecordingsHost.cs
index 18ff6a949..f4daa0975 100644
--- a/src/Jellyfin.LiveTv/EmbyTV/LiveTvHost.cs
+++ b/src/Jellyfin.LiveTv/Recordings/RecordingsHost.cs
@@ -4,22 +4,22 @@ using Jellyfin.LiveTv.Timers;
using MediaBrowser.Controller.LiveTv;
using Microsoft.Extensions.Hosting;
-namespace Jellyfin.LiveTv.EmbyTV;
+namespace Jellyfin.LiveTv.Recordings;
/// <summary>
-/// <see cref="IHostedService"/> responsible for initializing Live TV.
+/// <see cref="IHostedService"/> responsible for Live TV recordings.
/// </summary>
-public sealed class LiveTvHost : IHostedService
+public sealed class RecordingsHost : IHostedService
{
private readonly IRecordingsManager _recordingsManager;
private readonly TimerManager _timerManager;
/// <summary>
- /// Initializes a new instance of the <see cref="LiveTvHost"/> class.
+ /// Initializes a new instance of the <see cref="RecordingsHost"/> class.
/// </summary>
/// <param name="recordingsManager">The <see cref="IRecordingsManager"/>.</param>
/// <param name="timerManager">The <see cref="TimerManager"/>.</param>
- public LiveTvHost(IRecordingsManager recordingsManager, TimerManager timerManager)
+ public RecordingsHost(IRecordingsManager recordingsManager, TimerManager timerManager)
{
_recordingsManager = recordingsManager;
_timerManager = timerManager;
diff --git a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
index 20f89ec8f..92605a1eb 100644
--- a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
+++ b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using AsyncKeyedLock;
using Jellyfin.Data.Enums;
using Jellyfin.LiveTv.Configuration;
-using Jellyfin.LiveTv.EmbyTV;
using Jellyfin.LiveTv.IO;
using Jellyfin.LiveTv.Timers;
using MediaBrowser.Common.Configuration;
diff --git a/src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs b/src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs
index 0a71a4d46..b2b82332d 100644
--- a/src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs
+++ b/src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs
@@ -9,7 +9,6 @@ using System.Xml;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.LiveTv.Configuration;
-using Jellyfin.LiveTv.EmbyTV;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
diff --git a/src/Jellyfin.LiveTv/Timers/TimerManager.cs b/src/Jellyfin.LiveTv/Timers/TimerManager.cs
index 6bcbd3324..da5deea36 100644
--- a/src/Jellyfin.LiveTv/Timers/TimerManager.cs
+++ b/src/Jellyfin.LiveTv/Timers/TimerManager.cs
@@ -7,7 +7,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using Jellyfin.Data.Events;
-using Jellyfin.LiveTv.EmbyTV;
+using Jellyfin.LiveTv.Recordings;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
@@ -95,7 +95,7 @@ namespace Jellyfin.LiveTv.Timers
return;
}
- var startDate = RecordingHelper.GetStartTime(item);
+ var startDate = item.StartDate.AddSeconds(-item.PrePaddingSeconds);
var now = DateTime.UtcNow;
if (startDate < now)
diff --git a/tests/Jellyfin.LiveTv.Tests/RecordingHelperTests.cs b/tests/Jellyfin.LiveTv.Tests/RecordingHelperTests.cs
index b4960dc0b..6a33a6699 100644
--- a/tests/Jellyfin.LiveTv.Tests/RecordingHelperTests.cs
+++ b/tests/Jellyfin.LiveTv.Tests/RecordingHelperTests.cs
@@ -1,5 +1,5 @@
using System;
-using Jellyfin.LiveTv.EmbyTV;
+using Jellyfin.LiveTv.Recordings;
using MediaBrowser.Controller.LiveTv;
using Xunit;