Move media source code from LiveTvManager to LiveTvMediaSourceProvider

pull/11054/head
Patrick Barron 9 months ago
parent 3b341c06db
commit b5a3c71b3a

@ -10,7 +10,6 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
@ -105,16 +104,6 @@ namespace MediaBrowser.Controller.LiveTv
/// <returns>Task{QueryResult{SeriesTimerInfoDto}}.</returns> /// <returns>Task{QueryResult{SeriesTimerInfoDto}}.</returns>
Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken); 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> /// <summary>
/// Gets the program. /// Gets the program.
/// </summary> /// </summary>
@ -220,14 +209,6 @@ namespace MediaBrowser.Controller.LiveTv
/// <returns>Internal channels.</returns> /// <returns>Internal channels.</returns>
QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken); 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> /// <summary>
/// Adds the information to program dto. /// Adds the information to program dto.
/// </summary> /// </summary>

@ -12,7 +12,6 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using Jellyfin.LiveTv.Configuration; using Jellyfin.LiveTv.Configuration;
using Jellyfin.LiveTv.IO;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -152,73 +151,6 @@ namespace Jellyfin.LiveTv
return _libraryManager.GetItemsResult(internalQuery); 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) private ILiveTvService GetService(LiveTvChannel item)
{ {
var name = item.ServiceName; var name = item.ServiceName;
@ -240,127 +172,6 @@ namespace Jellyfin.LiveTv
"No service with the name '{0}' can be found.", "No service with the name '{0}' can be found.",
name)); 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) public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
{ {
var program = _libraryManager.GetItemById(id); var program = _libraryManager.GetItemById(id);

@ -8,11 +8,15 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.LiveTv.IO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging; 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. // 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 const char StreamIdDelimiter = '_';
private readonly ILiveTvManager _liveTvManager;
private readonly IRecordingsManager _recordingsManager;
private readonly ILogger<LiveTvMediaSourceProvider> _logger; private readonly ILogger<LiveTvMediaSourceProvider> _logger;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerApplicationHost _appHost; 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; _logger = logger;
_mediaSourceManager = mediaSourceManager;
_appHost = appHost; _appHost = appHost;
_recordingsManager = recordingsManager;
_mediaSourceManager = mediaSourceManager;
_libraryManager = libraryManager;
_services = services.ToArray();
} }
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken) public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
@ -68,7 +80,7 @@ namespace Jellyfin.LiveTv
} }
else else
{ {
sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken) sources = await GetChannelMediaSources(item, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
} }
@ -121,10 +133,200 @@ namespace Jellyfin.LiveTv
var keys = openToken.Split(StreamIdDelimiter, 3); var keys = openToken.Split(StreamIdDelimiter, 3);
var mediaSourceId = keys.Length >= 3 ? keys[2] : null; 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; var liveStream = info.Item2;
return liveStream; 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));
} }
} }

Loading…
Cancel
Save