mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 09:59:06 -07:00
Merge pull request #10858 from barronpm/livetv-tunerhostmanager
Add ITunerHostManager service and minor LiveTv cleanup
This commit is contained in:
commit
484ccf7f28
@ -695,7 +695,7 @@ namespace Emby.Server.Implementations
|
||||
GetExports<IMetadataSaver>(),
|
||||
GetExports<IExternalId>());
|
||||
|
||||
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
||||
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<IListingsProvider>());
|
||||
|
||||
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
@ -43,6 +42,7 @@ namespace Jellyfin.Api.Controllers;
|
||||
public class LiveTvController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@ -55,6 +55,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
|
||||
/// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
@ -64,6 +65,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
/// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
|
||||
public LiveTvController(
|
||||
ILiveTvManager liveTvManager,
|
||||
ITunerHostManager tunerHostManager,
|
||||
IUserManager userManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILibraryManager libraryManager,
|
||||
@ -73,6 +75,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
ITranscodeManager transcodeManager)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
_userManager = userManager;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_libraryManager = libraryManager;
|
||||
@ -951,9 +954,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
|
||||
{
|
||||
return await _liveTvManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
|
||||
}
|
||||
=> await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a tuner host.
|
||||
@ -1130,10 +1131,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[HttpGet("TunerHosts/Types")]
|
||||
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes()
|
||||
{
|
||||
return _liveTvManager.GetTunerHostTypes();
|
||||
}
|
||||
public IEnumerable<NameIdPair> GetTunerHostTypes()
|
||||
=> _tunerHostManager.GetTunerHostTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Discover tuners.
|
||||
@ -1145,10 +1144,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[HttpGet("Tuners/Discover")]
|
||||
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
|
||||
{
|
||||
return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
|
||||
=> _tunerHostManager.DiscoverTuners(newDevicesOnly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a live tv recording stream.
|
||||
|
@ -7,7 +7,6 @@ using Jellyfin.Api.WebSocketListeners;
|
||||
using Jellyfin.Drawing;
|
||||
using Jellyfin.Drawing.Skia;
|
||||
using Jellyfin.LiveTv;
|
||||
using Jellyfin.LiveTv.Channels;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Activity;
|
||||
using Jellyfin.Server.Implementations.Devices;
|
||||
@ -18,18 +17,15 @@ using Jellyfin.Server.Implementations.Users;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.BaseItemManager;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Events;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Lyrics;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Trickplay;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Providers.Lyric;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -101,11 +97,6 @@ namespace Jellyfin.Server
|
||||
|
||||
serviceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>();
|
||||
|
||||
serviceCollection.AddSingleton<LiveTvDtoService>();
|
||||
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
||||
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
||||
|
||||
foreach (var type in GetExportTypes<ILyricProvider>())
|
||||
{
|
||||
serviceCollection.AddSingleton(typeof(ILyricProvider), type);
|
||||
|
@ -5,6 +5,7 @@ using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using Jellyfin.Api.Middleware;
|
||||
using Jellyfin.LiveTv.Extensions;
|
||||
using Jellyfin.MediaEncoding.Hls.Extensions;
|
||||
using Jellyfin.Networking;
|
||||
using Jellyfin.Networking.HappyEyeballs;
|
||||
@ -121,6 +122,7 @@ namespace Jellyfin.Server
|
||||
.AddCheck<DbContextFactoryHealthCheck<JellyfinDbContext>>(nameof(JellyfinDbContext));
|
||||
|
||||
services.AddHlsPlaylistGenerator();
|
||||
services.AddLiveTvServices();
|
||||
|
||||
services.AddHostedService<AutoDiscoveryHost>();
|
||||
}
|
||||
|
@ -95,12 +95,5 @@ namespace MediaBrowser.Controller.Channels
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The item media sources.</returns>
|
||||
IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the item supports media probe.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>Whether media probe should be enabled.</returns>
|
||||
bool EnableMediaProbe(BaseItem item);
|
||||
}
|
||||
}
|
||||
|
@ -71,9 +71,8 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// Adds the parts.
|
||||
/// </summary>
|
||||
/// <param name="services">The services.</param>
|
||||
/// <param name="tunerHosts">The tuner hosts.</param>
|
||||
/// <param name="listingProviders">The listing providers.</param>
|
||||
void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders);
|
||||
void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<IListingsProvider> listingProviders);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timer.
|
||||
@ -253,14 +252,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <returns>Task.</returns>
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
/// </summary>
|
||||
/// <param name="info">Turner host to save.</param>
|
||||
/// <param name="dataSourceChanged">Option to specify that data source has changed.</param>
|
||||
/// <returns>Tuner host information wrapped in a task.</returns>
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the listing provider.
|
||||
/// </summary>
|
||||
@ -298,10 +289,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
|
||||
Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
|
||||
|
||||
List<NameIdPair> GetTunerHostTypes();
|
||||
|
||||
Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
|
||||
|
||||
string GetEmbyTvActiveRecordingPath(string id);
|
||||
|
||||
ActiveRecordingInfo GetActiveRecordingInfo(string path);
|
||||
|
@ -140,14 +140,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <returns>Task.</returns>
|
||||
Task CloseLiveStream(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Records the live stream.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task RecordLiveStream(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the tuner.
|
||||
/// </summary>
|
||||
@ -180,9 +172,4 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
Task<ILiveStream> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface ISupportsUpdatingDefaults
|
||||
{
|
||||
Task UpdateTimerDefaults(SeriesTimerInfo info, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -35,13 +35,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <returns>Task<IEnumerable<ChannelInfo>>.</returns>
|
||||
Task<List<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tuner infos.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<List<LiveTvTunerInfo>>.</returns>
|
||||
Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel stream.
|
||||
/// </summary>
|
||||
|
46
MediaBrowser.Controller/LiveTv/ITunerHostManager.cs
Normal file
46
MediaBrowser.Controller/LiveTv/ITunerHostManager.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv;
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for managing the <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
public interface ITunerHostManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the available <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
IReadOnlyList<ITunerHost> TunerHosts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="NameIdPair"/>s for the available <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="NameIdPair"/>s.</returns>
|
||||
IEnumerable<NameIdPair> GetTunerHostTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
/// </summary>
|
||||
/// <param name="info">Turner host to save.</param>
|
||||
/// <param name="dataSourceChanged">Option to specify that data source has changed.</param>
|
||||
/// <returns>Tuner host information wrapped in a task.</returns>
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
|
||||
|
||||
/// <summary>
|
||||
/// Discovers the available tuners.
|
||||
/// </summary>
|
||||
/// <param name="newDevicesOnly">A value indicating whether to only return new devices.</param>
|
||||
/// <returns>The <see cref="TunerHostInfo"/>s.</returns>
|
||||
IAsyncEnumerable<TunerHostInfo> DiscoverTuners(bool newDevicesOnly);
|
||||
|
||||
/// <summary>
|
||||
/// Scans for tuner devices that have changed URLs.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
|
||||
/// <returns>A task that represents the scanning operation.</returns>
|
||||
Task ScanForTunerDeviceChanges(CancellationToken cancellationToken);
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class LiveTvServiceStatusInfo
|
||||
{
|
||||
public LiveTvServiceStatusInfo()
|
||||
{
|
||||
Tuners = new List<LiveTvTunerInfo>();
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status.
|
||||
/// </summary>
|
||||
/// <value>The status.</value>
|
||||
public LiveTvServiceStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status message.
|
||||
/// </summary>
|
||||
/// <value>The status message.</value>
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the version.
|
||||
/// </summary>
|
||||
/// <value>The version.</value>
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has update available.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance has update available; otherwise, <c>false</c>.</value>
|
||||
public bool HasUpdateAvailable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tuners.
|
||||
/// </summary>
|
||||
/// <value>The tuners.</value>
|
||||
public List<LiveTvTunerInfo> Tuners { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is visible.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is visible; otherwise, <c>false</c>.</value>
|
||||
public bool IsVisible { get; set; }
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class LiveTvTunerInfo
|
||||
{
|
||||
public LiveTvTunerInfo()
|
||||
{
|
||||
Clients = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the source.
|
||||
/// </summary>
|
||||
/// <value>The type of the source.</value>
|
||||
public string SourceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier.
|
||||
/// </summary>
|
||||
/// <value>The identifier.</value>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL.
|
||||
/// </summary>
|
||||
/// <value>The URL.</value>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status.
|
||||
/// </summary>
|
||||
/// <value>The status.</value>
|
||||
public LiveTvTunerStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channel identifier.
|
||||
/// </summary>
|
||||
/// <value>The channel identifier.</value>
|
||||
public string ChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the recording identifier.
|
||||
/// </summary>
|
||||
/// <value>The recording identifier.</value>
|
||||
public string RecordingId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the program.
|
||||
/// </summary>
|
||||
/// <value>The name of the program.</value>
|
||||
public string ProgramName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the clients.
|
||||
/// </summary>
|
||||
/// <value>The clients.</value>
|
||||
public List<string> Clients { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance can reset.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance can reset; otherwise, <c>false</c>.</value>
|
||||
public bool CanReset { get; set; }
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class RecordingInfo
|
||||
{
|
||||
public RecordingInfo()
|
||||
{
|
||||
Genres = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the id of the recording.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the series timer identifier.
|
||||
/// </summary>
|
||||
/// <value>The series timer identifier.</value>
|
||||
public string SeriesTimerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timer identifier.
|
||||
/// </summary>
|
||||
/// <value>The timer identifier.</value>
|
||||
public string TimerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channelId of the recording.
|
||||
/// </summary>
|
||||
public string ChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the channel.
|
||||
/// </summary>
|
||||
/// <value>The type of the channel.</value>
|
||||
public ChannelType ChannelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the recording.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path.
|
||||
/// </summary>
|
||||
/// <value>The path.</value>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL.
|
||||
/// </summary>
|
||||
/// <value>The URL.</value>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the overview.
|
||||
/// </summary>
|
||||
/// <value>The overview.</value>
|
||||
public string Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start date of the recording, in UTC.
|
||||
/// </summary>
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the end date of the recording, in UTC.
|
||||
/// </summary>
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the program identifier.
|
||||
/// </summary>
|
||||
/// <value>The program identifier.</value>
|
||||
public string ProgramId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status.
|
||||
/// </summary>
|
||||
/// <value>The status.</value>
|
||||
public RecordingStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the genre of the program.
|
||||
/// </summary>
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is repeat.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value>
|
||||
public bool IsRepeat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the episode title.
|
||||
/// </summary>
|
||||
/// <value>The episode title.</value>
|
||||
public string EpisodeTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is hd.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value>
|
||||
public bool? IsHD { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio.
|
||||
/// </summary>
|
||||
/// <value>The audio.</value>
|
||||
public ProgramAudio? Audio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original air date.
|
||||
/// </summary>
|
||||
/// <value>The original air date.</value>
|
||||
public DateTime? OriginalAirDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is movie.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
|
||||
public bool IsMovie { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is sports.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
|
||||
public bool IsSports { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is series.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
|
||||
public bool IsSeries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is live.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
|
||||
public bool IsLive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is news.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
|
||||
public bool IsNews { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is kids.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
|
||||
public bool IsKids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is premiere.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
|
||||
public bool IsPremiere { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the official rating.
|
||||
/// </summary>
|
||||
/// <value>The official rating.</value>
|
||||
public string OfficialRating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the community rating.
|
||||
/// </summary>
|
||||
/// <value>The community rating.</value>
|
||||
public float? CommunityRating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image path if it can be accessed directly from the file system.
|
||||
/// </summary>
|
||||
/// <value>The image path.</value>
|
||||
public string ImagePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image url if it can be downloaded.
|
||||
/// </summary>
|
||||
/// <value>The image URL.</value>
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has image.
|
||||
/// </summary>
|
||||
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
|
||||
public bool? HasImage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the show identifier.
|
||||
/// </summary>
|
||||
/// <value>The show identifier.</value>
|
||||
public string ShowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date last updated.
|
||||
/// </summary>
|
||||
/// <value>The date last updated.</value>
|
||||
public DateTime DateLastUpdated { get; set; }
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class RecordingStatusChangedEventArgs : EventArgs
|
||||
{
|
||||
public string RecordingId { get; set; }
|
||||
|
||||
public RecordingStatus NewStatus { get; set; }
|
||||
}
|
||||
}
|
@ -13,8 +13,6 @@ namespace MediaBrowser.Model.IO
|
||||
|
||||
Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken);
|
||||
|
||||
Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken);
|
||||
|
||||
Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace MediaBrowser.Model.LiveTv
|
||||
{
|
||||
public enum LiveTvTunerStatus
|
||||
{
|
||||
Available = 0,
|
||||
Disabled = 1,
|
||||
RecordingTv = 2,
|
||||
LiveTv = 3
|
||||
}
|
||||
}
|
@ -113,15 +113,6 @@ namespace Jellyfin.LiveTv.Channels
|
||||
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool EnableMediaProbe(BaseItem item)
|
||||
{
|
||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
||||
|
||||
return channel is ISupportsMediaProbe;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteItem(BaseItem item)
|
||||
{
|
||||
@ -562,18 +553,6 @@ namespace Jellyfin.LiveTv.Channels
|
||||
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the provided Guid supports external transfer.
|
||||
/// </summary>
|
||||
/// <param name="channelId">The Guid.</param>
|
||||
/// <returns>Whether or not the provided Guid supports external transfer.</returns>
|
||||
public bool SupportsExternalTransfer(Guid channelId)
|
||||
{
|
||||
var channelProvider = GetChannelProvider(channelId);
|
||||
|
||||
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the provided channel's supported features.
|
||||
/// </summary>
|
||||
@ -1215,19 +1194,6 @@ namespace Jellyfin.LiveTv.Channels
|
||||
return result;
|
||||
}
|
||||
|
||||
internal IChannel GetChannelProvider(Guid internalChannelId)
|
||||
{
|
||||
var result = GetAllChannels()
|
||||
.FirstOrDefault(i => internalChannelId.Equals(GetInternalChannelId(i.Name)));
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -0,0 +1,18 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace Jellyfin.LiveTv.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IConfigurationManager"/> extensions for Live TV.
|
||||
/// </summary>
|
||||
public static class LiveTvConfigurationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="LiveTvOptions"/>.
|
||||
/// </summary>
|
||||
/// <param name="configurationManager">The <see cref="IConfigurationManager"/>.</param>
|
||||
/// <returns>The <see cref="LiveTvOptions"/>.</returns>
|
||||
public static LiveTvOptions GetLiveTvConfiguration(this IConfigurationManager configurationManager)
|
||||
=> configurationManager.GetConfiguration<LiveTvOptions>("livetv");
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace Jellyfin.LiveTv.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IConfigurationFactory" /> implementation for <see cref="LiveTvOptions" />.
|
||||
/// </summary>
|
||||
public class LiveTvConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
ConfigurationType = typeof(LiveTvOptions),
|
||||
Key = "livetv"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ using System.Xml;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
@ -43,8 +44,6 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
{
|
||||
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
private const int TunerDiscoveryDurationMs = 3000;
|
||||
|
||||
private readonly ILogger<EmbyTV> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
@ -53,6 +52,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
private readonly TimerManager _timerProvider;
|
||||
|
||||
private readonly LiveTvManager _liveTvManager;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly ILibraryMonitor _libraryMonitor;
|
||||
@ -79,6 +79,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IServerConfigurationManager config,
|
||||
ILiveTvManager liveTvManager,
|
||||
ITunerHostManager tunerHostManager,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager,
|
||||
ILibraryMonitor libraryMonitor,
|
||||
@ -96,6 +97,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
_providerManager = providerManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_streamHelper = streamHelper;
|
||||
|
||||
@ -126,7 +128,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
{
|
||||
get
|
||||
{
|
||||
var path = GetConfiguration().RecordingPath;
|
||||
var path = _config.GetLiveTvConfiguration().RecordingPath;
|
||||
|
||||
return string.IsNullOrWhiteSpace(path)
|
||||
? DefaultRecordingPath
|
||||
@ -189,7 +191,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
pathsAdded.AddRange(pathsToCreate);
|
||||
}
|
||||
|
||||
var config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var pathsToRemove = config.MediaLocationsCreated
|
||||
.Except(recordingFolders.SelectMany(i => i.Locations))
|
||||
@ -309,7 +311,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
{
|
||||
var list = new List<ChannelInfo>();
|
||||
|
||||
foreach (var hostInstance in _liveTvManager.TunerHosts)
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -509,7 +511,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
{
|
||||
var list = new List<ChannelInfo>();
|
||||
|
||||
foreach (var hostInstance in _liveTvManager.TunerHosts)
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -831,7 +833,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
|
||||
public Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null)
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var defaults = new SeriesTimerInfo()
|
||||
{
|
||||
@ -932,7 +934,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
|
||||
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
|
||||
{
|
||||
return GetConfiguration().ListingProviders
|
||||
return _config.GetLiveTvConfiguration().ListingProviders
|
||||
.Select(i =>
|
||||
{
|
||||
var provider = _liveTvManager.ListingProviders.FirstOrDefault(l => string.Equals(l.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
@ -965,7 +967,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var hostInstance in _liveTvManager.TunerHosts)
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -997,7 +999,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
throw new ArgumentNullException(nameof(channelId));
|
||||
}
|
||||
|
||||
foreach (var hostInstance in _liveTvManager.TunerHosts)
|
||||
foreach (var hostInstance in _tunerHostManager.TunerHosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -1021,11 +1023,6 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task RecordLiveStream(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ResetTuner(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
@ -1076,7 +1073,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
private string GetRecordingPath(TimerInfo timer, RemoteSearchResult metadata, out string seriesPath)
|
||||
{
|
||||
var recordPath = RecordingPath;
|
||||
var config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
seriesPath = null;
|
||||
|
||||
if (timer.IsProgramSeries)
|
||||
@ -1596,7 +1593,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
|
||||
private void PostProcessRecording(TimerInfo timer, string path)
|
||||
{
|
||||
var options = GetConfiguration();
|
||||
var options = _config.GetLiveTvConfiguration();
|
||||
if (string.IsNullOrWhiteSpace(options.RecordingPostProcessor))
|
||||
{
|
||||
return;
|
||||
@ -1777,7 +1774,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
program.AddGenre("News");
|
||||
}
|
||||
|
||||
var config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
if (config.SaveRecordingNFO)
|
||||
{
|
||||
@ -2128,11 +2125,6 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().FirstOrDefault();
|
||||
}
|
||||
|
||||
private LiveTvOptions GetConfiguration()
|
||||
{
|
||||
return _config.GetConfiguration<LiveTvOptions>("livetv");
|
||||
}
|
||||
|
||||
private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer)
|
||||
{
|
||||
if (timer.IsManual)
|
||||
@ -2519,7 +2511,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
};
|
||||
}
|
||||
|
||||
var customPath = GetConfiguration().MovieRecordingPath;
|
||||
var customPath = _config.GetLiveTvConfiguration().MovieRecordingPath;
|
||||
if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath))
|
||||
{
|
||||
yield return new VirtualFolderInfo
|
||||
@ -2530,7 +2522,7 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
};
|
||||
}
|
||||
|
||||
customPath = GetConfiguration().SeriesRecordingPath;
|
||||
customPath = _config.GetLiveTvConfiguration().SeriesRecordingPath;
|
||||
if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath))
|
||||
{
|
||||
yield return new VirtualFolderInfo
|
||||
@ -2541,81 +2533,5 @@ namespace Jellyfin.LiveTv.EmbyTV
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<TunerHostInfo>();
|
||||
|
||||
var configuredDeviceIds = GetConfiguration().TunerHosts
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
|
||||
.Select(i => i.DeviceId)
|
||||
.ToList();
|
||||
|
||||
foreach (var host in _liveTvManager.TunerHosts)
|
||||
{
|
||||
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (newDevicesOnly)
|
||||
{
|
||||
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
list.AddRange(discoveredDevices);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var host in _liveTvManager.TunerHosts)
|
||||
{
|
||||
await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
|
||||
{
|
||||
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var configuredDevices = GetConfiguration().TunerHosts
|
||||
.Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
foreach (var device in discoveredDevices)
|
||||
{
|
||||
var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url);
|
||||
|
||||
configuredDevice.Url = device.Url;
|
||||
await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var device in discoveredDevices)
|
||||
{
|
||||
_logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url);
|
||||
}
|
||||
|
||||
return discoveredDevices;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error discovering tuner devices");
|
||||
|
||||
return new List<TunerHostInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
using Jellyfin.LiveTv.Channels;
|
||||
using Jellyfin.LiveTv.TunerHosts;
|
||||
using Jellyfin.LiveTv.TunerHosts.HdHomerun;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Jellyfin.LiveTv.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Live TV extensions for <see cref="IServiceCollection"/>.
|
||||
/// </summary>
|
||||
public static class LiveTvServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Live TV services to the <see cref="IServiceCollection"/>.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
|
||||
public static void AddLiveTvServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<LiveTvDtoService>();
|
||||
services.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||
services.AddSingleton<IChannelManager, ChannelManager>();
|
||||
services.AddSingleton<IStreamHelper, StreamHelper>();
|
||||
services.AddSingleton<ITunerHostManager, TunerHostManager>();
|
||||
|
||||
services.AddSingleton<ITunerHost, HdHomerunHost>();
|
||||
services.AddSingleton<ITunerHost, M3UTunerHost>();
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace Jellyfin.LiveTv
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IConfigurationFactory" /> implementation for <see cref="LiveTvOptions" />.
|
||||
/// </summary>
|
||||
public class LiveTvConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new ConfigurationStore[]
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
ConfigurationType = typeof(LiveTvOptions),
|
||||
Key = "livetv"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
@ -57,9 +57,9 @@ namespace Jellyfin.LiveTv
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly LiveTvDtoService _tvDtoService;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
|
||||
private ILiveTvService[] _services = Array.Empty<ILiveTvService>();
|
||||
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
|
||||
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
|
||||
|
||||
public LiveTvManager(
|
||||
@ -74,7 +74,8 @@ namespace Jellyfin.LiveTv
|
||||
ILocalizationManager localization,
|
||||
IFileSystem fileSystem,
|
||||
IChannelManager channelManager,
|
||||
LiveTvDtoService liveTvDtoService)
|
||||
LiveTvDtoService liveTvDtoService,
|
||||
ITunerHostManager tunerHostManager)
|
||||
{
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
@ -88,6 +89,7 @@ namespace Jellyfin.LiveTv
|
||||
_userDataManager = userDataManager;
|
||||
_channelManager = channelManager;
|
||||
_tvDtoService = liveTvDtoService;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
}
|
||||
|
||||
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
|
||||
@ -104,30 +106,17 @@ namespace Jellyfin.LiveTv
|
||||
/// <value>The services.</value>
|
||||
public IReadOnlyList<ILiveTvService> Services => _services;
|
||||
|
||||
public IReadOnlyList<ITunerHost> TunerHosts => _tunerHosts;
|
||||
|
||||
public IReadOnlyList<IListingsProvider> ListingProviders => _listingProviders;
|
||||
|
||||
private LiveTvOptions GetConfiguration()
|
||||
{
|
||||
return _config.GetConfiguration<LiveTvOptions>("livetv");
|
||||
}
|
||||
|
||||
public string GetEmbyTvActiveRecordingPath(string id)
|
||||
{
|
||||
return EmbyTV.EmbyTV.Current.GetActiveRecordingPath(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the parts.
|
||||
/// </summary>
|
||||
/// <param name="services">The services.</param>
|
||||
/// <param name="tunerHosts">The tuner hosts.</param>
|
||||
/// <param name="listingProviders">The listing providers.</param>
|
||||
public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders)
|
||||
/// <inheritdoc />
|
||||
public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<IListingsProvider> listingProviders)
|
||||
{
|
||||
_services = services.ToArray();
|
||||
_tunerHosts = tunerHosts.Where(i => i.IsSupported).ToArray();
|
||||
|
||||
_listingProviders = listingProviders.ToArray();
|
||||
|
||||
@ -159,20 +148,6 @@ namespace Jellyfin.LiveTv
|
||||
}));
|
||||
}
|
||||
|
||||
public List<NameIdPair> GetTunerHostTypes()
|
||||
{
|
||||
return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Type
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
|
||||
{
|
||||
return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken);
|
||||
}
|
||||
|
||||
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = query.UserId.Equals(default)
|
||||
@ -1034,7 +1009,7 @@ namespace Jellyfin.LiveTv
|
||||
{
|
||||
await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false);
|
||||
|
||||
await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
|
||||
await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var numComplete = 0;
|
||||
double progressPerService = _services.Length == 0
|
||||
@ -1302,7 +1277,7 @@ namespace Jellyfin.LiveTv
|
||||
|
||||
private double GetGuideDays()
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
if (config.GuideDays.HasValue)
|
||||
{
|
||||
@ -2125,7 +2100,7 @@ namespace Jellyfin.LiveTv
|
||||
|
||||
private bool IsLiveTvEnabled(User user)
|
||||
{
|
||||
return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0);
|
||||
return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || _config.GetLiveTvConfiguration().TunerHosts.Length > 0);
|
||||
}
|
||||
|
||||
public IEnumerable<User> GetEnabledUsers()
|
||||
@ -2171,48 +2146,6 @@ namespace Jellyfin.LiveTv
|
||||
return _libraryManager.GetNamedView(name, CollectionType.livetv, name);
|
||||
}
|
||||
|
||||
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
|
||||
{
|
||||
info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.SerializeToUtf8Bytes(info));
|
||||
|
||||
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
if (provider is IConfigurableTunerHost configurable)
|
||||
{
|
||||
await configurable.Validate(info).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var config = GetConfiguration();
|
||||
|
||||
var list = config.TunerHosts.ToList();
|
||||
var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
||||
{
|
||||
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
list.Add(info);
|
||||
config.TunerHosts = list.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
config.TunerHosts[index] = info;
|
||||
}
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
|
||||
if (dataSourceChanged)
|
||||
{
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public async Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
|
||||
{
|
||||
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
|
||||
@ -2232,7 +2165,7 @@ namespace Jellyfin.LiveTv
|
||||
|
||||
await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
|
||||
|
||||
LiveTvOptions config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var list = config.ListingProviders.ToList();
|
||||
int index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
@ -2257,7 +2190,7 @@ namespace Jellyfin.LiveTv
|
||||
|
||||
public void DeleteListingsProvider(string id)
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
@ -2267,7 +2200,7 @@ namespace Jellyfin.LiveTv
|
||||
|
||||
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
|
||||
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
@ -2327,7 +2260,7 @@ namespace Jellyfin.LiveTv
|
||||
|
||||
public Task<List<NameIdPair>> GetLineups(string providerType, string providerId, string country, string location)
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(providerId))
|
||||
{
|
||||
@ -2357,13 +2290,13 @@ namespace Jellyfin.LiveTv
|
||||
|
||||
public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
var info = _config.GetLiveTvConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
return EmbyTV.EmbyTV.Current.GetChannelsForListingsProvider(info, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
var info = _config.GetLiveTvConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase));
|
||||
return provider.GetChannels(info, cancellationToken);
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace Jellyfin.LiveTv
|
||||
@ -38,7 +38,7 @@ namespace Jellyfin.LiveTv
|
||||
public string Category => "Live TV";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0;
|
||||
public bool IsHidden => _liveTvManager.Services.Count == 1 && _config.GetLiveTvConfiguration().TunerHosts.Length == 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
@ -66,10 +66,5 @@ namespace Jellyfin.LiveTv
|
||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
|
||||
};
|
||||
}
|
||||
|
||||
private LiveTvOptions GetConfiguration()
|
||||
{
|
||||
return _config.GetConfiguration<LiveTvOptions>("livetv");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,36 +81,6 @@ namespace Jellyfin.LiveTv
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(IODefaults.CopyToBufferSize);
|
||||
try
|
||||
{
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||
|
||||
if (bytesToWrite > 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
copyLength -= bytesToWrite;
|
||||
|
||||
if (copyLength <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
|
@ -10,7 +10,7 @@ using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
@ -69,7 +69,7 @@ namespace Jellyfin.LiveTv.TunerHosts
|
||||
|
||||
protected virtual IList<TunerHostInfo> GetTunerHosts()
|
||||
{
|
||||
return GetConfiguration().TunerHosts
|
||||
return Config.GetLiveTvConfiguration().TunerHosts
|
||||
.Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
@ -228,10 +228,5 @@ namespace Jellyfin.LiveTv.TunerHosts
|
||||
|
||||
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
protected LiveTvOptions GetConfiguration()
|
||||
{
|
||||
return Config.GetConfiguration<LiveTvOptions>("livetv");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@ -163,152 +162,6 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<LiveTvTunerInfo>> GetTunerInfosHttp(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var tuners = new List<LiveTvTunerInfo>();
|
||||
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (stream.ConfigureAwait(false))
|
||||
{
|
||||
using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
|
||||
await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false))
|
||||
{
|
||||
string stripedLine = StripXML(line);
|
||||
if (stripedLine.Contains("Channel", StringComparison.Ordinal))
|
||||
{
|
||||
LiveTvTunerStatus status;
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = LiveTvTunerStatus.Available;
|
||||
}
|
||||
|
||||
tuners.Add(new LiveTvTunerInfo
|
||||
{
|
||||
Name = name,
|
||||
SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
|
||||
ProgramName = currentChannel,
|
||||
Status = status
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tuners;
|
||||
}
|
||||
|
||||
private static string StripXML(string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
char[] buffer = new char[source.Length];
|
||||
int bufferIndex = 0;
|
||||
bool inside = false;
|
||||
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
char let = source[i];
|
||||
if (let == '<')
|
||||
{
|
||||
inside = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (let == '>')
|
||||
{
|
||||
inside = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inside)
|
||||
{
|
||||
buffer[bufferIndex++] = let;
|
||||
}
|
||||
}
|
||||
|
||||
return new string(buffer, 0, bufferIndex);
|
||||
}
|
||||
|
||||
private async Task<List<LiveTvTunerInfo>> GetTunerInfosUdp(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var tuners = new List<LiveTvTunerInfo>(model.TunerCount);
|
||||
|
||||
var uri = new Uri(GetApiUrl(info));
|
||||
|
||||
using (var manager = new HdHomerunManager())
|
||||
{
|
||||
// Legacy HdHomeruns are IPv4 only
|
||||
var ipInfo = IPAddress.Parse(uri.Host);
|
||||
|
||||
for (int i = 0; i < model.TunerCount; i++)
|
||||
{
|
||||
var name = string.Format(CultureInfo.InvariantCulture, "Tuner {0}", i + 1);
|
||||
var currentChannel = "none"; // TODO: Get current channel and map back to Station Id
|
||||
var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
|
||||
var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
|
||||
tuners.Add(new LiveTvTunerInfo
|
||||
{
|
||||
Name = name,
|
||||
SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
|
||||
ProgramName = currentChannel,
|
||||
Status = status
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tuners;
|
||||
}
|
||||
|
||||
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<LiveTvTunerInfo>();
|
||||
|
||||
foreach (var host in GetConfiguration().TunerHosts
|
||||
.Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
try
|
||||
{
|
||||
list.AddRange(await GetTunerInfos(host, cancellationToken).ConfigureAwait(false));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error getting tuner info");
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO Need faster way to determine UDP vs HTTP
|
||||
var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo;
|
||||
|
||||
if (hdHomerunChannelInfo is null || hdHomerunChannelInfo.IsLegacyTuner)
|
||||
{
|
||||
return await GetTunerInfosUdp(info, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await GetTunerInfosHttp(info, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string GetApiUrl(TunerHostInfo info)
|
||||
{
|
||||
var url = info.Url;
|
||||
@ -574,40 +427,24 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
|
||||
_streamHelper);
|
||||
}
|
||||
|
||||
var enableHttpStream = true;
|
||||
if (enableHttpStream)
|
||||
mediaSource.Protocol = MediaProtocol.Http;
|
||||
|
||||
var httpUrl = channel.Path;
|
||||
|
||||
// If raw was used, the tuner doesn't support params
|
||||
if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mediaSource.Protocol = MediaProtocol.Http;
|
||||
|
||||
var httpUrl = channel.Path;
|
||||
|
||||
// If raw was used, the tuner doesn't support params
|
||||
if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpUrl += "?transcode=" + profile;
|
||||
}
|
||||
|
||||
mediaSource.Path = httpUrl;
|
||||
|
||||
return new SharedHttpStream(
|
||||
mediaSource,
|
||||
tunerHost,
|
||||
streamId,
|
||||
FileSystem,
|
||||
_httpClientFactory,
|
||||
Logger,
|
||||
Config,
|
||||
_appHost,
|
||||
_streamHelper);
|
||||
httpUrl += "?transcode=" + profile;
|
||||
}
|
||||
|
||||
return new HdHomerunUdpStream(
|
||||
mediaSource.Path = httpUrl;
|
||||
|
||||
return new SharedHttpStream(
|
||||
mediaSource,
|
||||
tunerHost,
|
||||
streamId,
|
||||
new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
|
||||
modelInfo.TunerCount,
|
||||
FileSystem,
|
||||
_httpClientFactory,
|
||||
Logger,
|
||||
Config,
|
||||
_appHost,
|
||||
|
@ -80,22 +80,6 @@ namespace Jellyfin.LiveTv.TunerHosts
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
var list = GetTunerHosts()
|
||||
.Select(i => new LiveTvTunerInfo()
|
||||
{
|
||||
Name = Name,
|
||||
SourceType = Type,
|
||||
Status = LiveTvTunerStatus.Available,
|
||||
Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
Url = i.Url
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(list);
|
||||
}
|
||||
|
||||
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
||||
{
|
||||
var tunerCount = tunerHost.TunerCount;
|
||||
|
174
src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
Normal file
174
src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
Normal file
@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.LiveTv.TunerHosts;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class TunerHostManager : ITunerHostManager
|
||||
{
|
||||
private const int TunerDiscoveryDurationMs = 3000;
|
||||
|
||||
private readonly ILogger<TunerHostManager> _logger;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly ITunerHost[] _tunerHosts;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TunerHostManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger{T}"/>.</param>
|
||||
/// <param name="config">The <see cref="IConfigurationManager"/>.</param>
|
||||
/// <param name="taskManager">The <see cref="ITaskManager"/>.</param>
|
||||
/// <param name="tunerHosts">The <see cref="IEnumerable{T}"/>.</param>
|
||||
public TunerHostManager(
|
||||
ILogger<TunerHostManager> logger,
|
||||
IConfigurationManager config,
|
||||
ITaskManager taskManager,
|
||||
IEnumerable<ITunerHost> tunerHosts)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_taskManager = taskManager;
|
||||
_tunerHosts = tunerHosts.Where(t => t.IsSupported).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ITunerHost> TunerHosts => _tunerHosts;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<NameIdPair> GetTunerHostTypes()
|
||||
=> _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Type
|
||||
});
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
|
||||
{
|
||||
info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.SerializeToUtf8Bytes(info))!;
|
||||
|
||||
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
if (provider is IConfigurableTunerHost configurable)
|
||||
{
|
||||
await configurable.Validate(info).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var config = _config.GetLiveTvConfiguration();
|
||||
|
||||
var list = config.TunerHosts.ToList();
|
||||
var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
|
||||
{
|
||||
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
list.Add(info);
|
||||
config.TunerHosts = list.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
config.TunerHosts[index] = info;
|
||||
}
|
||||
|
||||
_config.SaveConfiguration("livetv", config);
|
||||
|
||||
if (dataSourceChanged)
|
||||
{
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<TunerHostInfo> DiscoverTuners(bool newDevicesOnly)
|
||||
{
|
||||
var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
|
||||
.Select(i => i.DeviceId)
|
||||
.ToList();
|
||||
|
||||
foreach (var host in _tunerHosts)
|
||||
{
|
||||
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, CancellationToken.None).ConfigureAwait(false);
|
||||
foreach (var tuner in discoveredDevices)
|
||||
{
|
||||
if (!newDevicesOnly || !configuredDeviceIds.Contains(tuner.DeviceId, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
yield return tuner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var host in _tunerHosts)
|
||||
{
|
||||
await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
|
||||
{
|
||||
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts
|
||||
.Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
foreach (var device in discoveredDevices)
|
||||
{
|
||||
var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url);
|
||||
|
||||
configuredDevice.Url = device.Url;
|
||||
await SaveTunerHost(configuredDevice).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IList<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var device in discoveredDevices)
|
||||
{
|
||||
_logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url);
|
||||
}
|
||||
|
||||
return discoveredDevices;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error discovering tuner devices");
|
||||
|
||||
return Array.Empty<TunerHostInfo>();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user