aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Jellyfin.LiveTv/LiveTvManager.cs189
-rw-r--r--src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs220
2 files changed, 211 insertions, 198 deletions
diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs
index 0af40a059..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;
@@ -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 DefaultLiveTvService)
- {
- // 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);
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));
}
}