Feature: All bussiness units get a tile
This commit is contained in:
parent
5667c2e12f
commit
7a719ee4ac
@ -20,6 +20,13 @@ public class RecordingEntry
|
|||||||
[JsonPropertyName("urn")]
|
[JsonPropertyName("urn")]
|
||||||
public string Urn { get; set; } = string.Empty;
|
public string Urn { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the business unit this recording belongs to (e.g. "srf", "rts").
|
||||||
|
/// Used to show recordings under the matching unit's channel.
|
||||||
|
/// </summary>
|
||||||
|
[JsonPropertyName("businessUnit")]
|
||||||
|
public string BusinessUnit { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the title.
|
/// Gets or sets the title.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
43
Jellyfin.Plugin.SRFPlay/Channels/RsiChannel.cs
Normal file
43
Jellyfin.Plugin.SRFPlay/Channels/RsiChannel.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Jellyfin.Plugin.SRFPlay.Api;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Configuration;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SRFPlay.Channels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Radiotelevisione svizzera — Swiss Italian channel.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RsiChannel : SrgChannelBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RsiChannel"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
|
/// <param name="contentRefreshService">The content refresh service.</param>
|
||||||
|
/// <param name="streamResolver">The stream resolver.</param>
|
||||||
|
/// <param name="mediaSourceFactory">The media source factory.</param>
|
||||||
|
/// <param name="categoryService">The category service.</param>
|
||||||
|
/// <param name="apiClientFactory">The API client factory.</param>
|
||||||
|
/// <param name="recordingService">The recording service.</param>
|
||||||
|
public RsiChannel(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IContentRefreshService contentRefreshService,
|
||||||
|
IStreamUrlResolver streamResolver,
|
||||||
|
IMediaSourceFactory mediaSourceFactory,
|
||||||
|
ICategoryService? categoryService,
|
||||||
|
ISRFApiClientFactory apiClientFactory,
|
||||||
|
IRecordingService recordingService)
|
||||||
|
: base(loggerFactory, contentRefreshService, streamResolver, mediaSourceFactory, categoryService, apiClientFactory, recordingService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override BusinessUnit Unit => BusinessUnit.RSI;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LogoResourceName => "Jellyfin.Plugin.SRFPlay.Images.rsi-logo.png";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Description => "Radiotelevisione svizzera — Swiss Italian video-on-demand content";
|
||||||
|
}
|
||||||
43
Jellyfin.Plugin.SRFPlay/Channels/RtrChannel.cs
Normal file
43
Jellyfin.Plugin.SRFPlay/Channels/RtrChannel.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Jellyfin.Plugin.SRFPlay.Api;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Configuration;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SRFPlay.Channels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Radiotelevisiun Svizra Rumantscha — Swiss Romansh channel.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RtrChannel : SrgChannelBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RtrChannel"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
|
/// <param name="contentRefreshService">The content refresh service.</param>
|
||||||
|
/// <param name="streamResolver">The stream resolver.</param>
|
||||||
|
/// <param name="mediaSourceFactory">The media source factory.</param>
|
||||||
|
/// <param name="categoryService">The category service.</param>
|
||||||
|
/// <param name="apiClientFactory">The API client factory.</param>
|
||||||
|
/// <param name="recordingService">The recording service.</param>
|
||||||
|
public RtrChannel(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IContentRefreshService contentRefreshService,
|
||||||
|
IStreamUrlResolver streamResolver,
|
||||||
|
IMediaSourceFactory mediaSourceFactory,
|
||||||
|
ICategoryService? categoryService,
|
||||||
|
ISRFApiClientFactory apiClientFactory,
|
||||||
|
IRecordingService recordingService)
|
||||||
|
: base(loggerFactory, contentRefreshService, streamResolver, mediaSourceFactory, categoryService, apiClientFactory, recordingService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override BusinessUnit Unit => BusinessUnit.RTR;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LogoResourceName => "Jellyfin.Plugin.SRFPlay.Images.rtr-logo.png";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Description => "Radiotelevisiun Svizra Rumantscha — Swiss Romansh video-on-demand content";
|
||||||
|
}
|
||||||
43
Jellyfin.Plugin.SRFPlay/Channels/RtsChannel.cs
Normal file
43
Jellyfin.Plugin.SRFPlay/Channels/RtsChannel.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Jellyfin.Plugin.SRFPlay.Api;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Configuration;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SRFPlay.Channels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Radio Télévision Suisse — Swiss French channel.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RtsChannel : SrgChannelBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RtsChannel"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
|
/// <param name="contentRefreshService">The content refresh service.</param>
|
||||||
|
/// <param name="streamResolver">The stream resolver.</param>
|
||||||
|
/// <param name="mediaSourceFactory">The media source factory.</param>
|
||||||
|
/// <param name="categoryService">The category service.</param>
|
||||||
|
/// <param name="apiClientFactory">The API client factory.</param>
|
||||||
|
/// <param name="recordingService">The recording service.</param>
|
||||||
|
public RtsChannel(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IContentRefreshService contentRefreshService,
|
||||||
|
IStreamUrlResolver streamResolver,
|
||||||
|
IMediaSourceFactory mediaSourceFactory,
|
||||||
|
ICategoryService? categoryService,
|
||||||
|
ISRFApiClientFactory apiClientFactory,
|
||||||
|
IRecordingService recordingService)
|
||||||
|
: base(loggerFactory, contentRefreshService, streamResolver, mediaSourceFactory, categoryService, apiClientFactory, recordingService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override BusinessUnit Unit => BusinessUnit.RTS;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LogoResourceName => "Jellyfin.Plugin.SRFPlay.Images.rts-logo.png";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Description => "Radio Télévision Suisse — Swiss French video-on-demand content";
|
||||||
|
}
|
||||||
43
Jellyfin.Plugin.SRFPlay/Channels/SrfChannel.cs
Normal file
43
Jellyfin.Plugin.SRFPlay/Channels/SrfChannel.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Jellyfin.Plugin.SRFPlay.Api;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Configuration;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SRFPlay.Channels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schweizer Radio und Fernsehen — Swiss German channel.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SrfChannel : SrgChannelBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SrfChannel"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
|
/// <param name="contentRefreshService">The content refresh service.</param>
|
||||||
|
/// <param name="streamResolver">The stream resolver.</param>
|
||||||
|
/// <param name="mediaSourceFactory">The media source factory.</param>
|
||||||
|
/// <param name="categoryService">The category service.</param>
|
||||||
|
/// <param name="apiClientFactory">The API client factory.</param>
|
||||||
|
/// <param name="recordingService">The recording service.</param>
|
||||||
|
public SrfChannel(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IContentRefreshService contentRefreshService,
|
||||||
|
IStreamUrlResolver streamResolver,
|
||||||
|
IMediaSourceFactory mediaSourceFactory,
|
||||||
|
ICategoryService? categoryService,
|
||||||
|
ISRFApiClientFactory apiClientFactory,
|
||||||
|
IRecordingService recordingService)
|
||||||
|
: base(loggerFactory, contentRefreshService, streamResolver, mediaSourceFactory, categoryService, apiClientFactory, recordingService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override BusinessUnit Unit => BusinessUnit.SRF;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LogoResourceName => "Jellyfin.Plugin.SRFPlay.Images.srf-logo.png";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Description => "Schweizer Radio und Fernsehen — Swiss German video-on-demand content";
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Plugin.SRFPlay.Api;
|
using Jellyfin.Plugin.SRFPlay.Api;
|
||||||
using Jellyfin.Plugin.SRFPlay.Api.Models;
|
using Jellyfin.Plugin.SRFPlay.Api.Models;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Configuration;
|
||||||
using Jellyfin.Plugin.SRFPlay.Constants;
|
using Jellyfin.Plugin.SRFPlay.Constants;
|
||||||
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
using Jellyfin.Plugin.SRFPlay.Utilities;
|
using Jellyfin.Plugin.SRFPlay.Utilities;
|
||||||
@ -22,11 +23,12 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace Jellyfin.Plugin.SRFPlay.Channels;
|
namespace Jellyfin.Plugin.SRFPlay.Channels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SRF Play channel for browsing and playing content.
|
/// Base channel for browsing and playing content from a single SRG SSR business unit.
|
||||||
|
/// One concrete subclass exists per unit (SRF, RTS, RSI, RTR, SWI), each shown as its own tile.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SRFPlayChannel : IChannel, IHasCacheKey
|
public abstract class SrgChannelBase : IChannel, IHasCacheKey
|
||||||
{
|
{
|
||||||
private readonly ILogger<SRFPlayChannel> _logger;
|
private readonly ILogger<SrgChannelBase> _logger;
|
||||||
private readonly IContentRefreshService _contentRefreshService;
|
private readonly IContentRefreshService _contentRefreshService;
|
||||||
private readonly IStreamUrlResolver _streamResolver;
|
private readonly IStreamUrlResolver _streamResolver;
|
||||||
private readonly IMediaSourceFactory _mediaSourceFactory;
|
private readonly IMediaSourceFactory _mediaSourceFactory;
|
||||||
@ -35,7 +37,7 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
private readonly IRecordingService _recordingService;
|
private readonly IRecordingService _recordingService;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SRFPlayChannel"/> class.
|
/// Initializes a new instance of the <see cref="SrgChannelBase"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="loggerFactory">The logger factory.</param>
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
/// <param name="contentRefreshService">The content refresh service.</param>
|
/// <param name="contentRefreshService">The content refresh service.</param>
|
||||||
@ -44,7 +46,7 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
/// <param name="categoryService">The category service (optional).</param>
|
/// <param name="categoryService">The category service (optional).</param>
|
||||||
/// <param name="apiClientFactory">The API client factory.</param>
|
/// <param name="apiClientFactory">The API client factory.</param>
|
||||||
/// <param name="recordingService">The recording service.</param>
|
/// <param name="recordingService">The recording service.</param>
|
||||||
public SRFPlayChannel(
|
protected SrgChannelBase(
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IContentRefreshService contentRefreshService,
|
IContentRefreshService contentRefreshService,
|
||||||
IStreamUrlResolver streamResolver,
|
IStreamUrlResolver streamResolver,
|
||||||
@ -53,7 +55,7 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
ISRFApiClientFactory apiClientFactory,
|
ISRFApiClientFactory apiClientFactory,
|
||||||
IRecordingService recordingService)
|
IRecordingService recordingService)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger<SRFPlayChannel>();
|
_logger = loggerFactory.CreateLogger<SrgChannelBase>();
|
||||||
_contentRefreshService = contentRefreshService;
|
_contentRefreshService = contentRefreshService;
|
||||||
_streamResolver = streamResolver;
|
_streamResolver = streamResolver;
|
||||||
_mediaSourceFactory = mediaSourceFactory;
|
_mediaSourceFactory = mediaSourceFactory;
|
||||||
@ -66,14 +68,24 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
_logger.LogWarning("CategoryService not available - category folders will be disabled");
|
_logger.LogWarning("CategoryService not available - category folders will be disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("SRFPlayChannel initialized");
|
_logger.LogDebug("SrgChannelBase initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public string Name => "SRF Play";
|
/// Gets the business unit this channel serves.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract BusinessUnit Unit { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the embedded resource name of this unit's logo PNG.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract string LogoResourceName { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Description => "Swiss Radio and Television video-on-demand content";
|
public string Name => $"{Unit} Play";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract string Description { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string DataVersion => "2.0"; // Back to authenticating at channel refresh with auto-refresh for fresh tokens
|
public string DataVersion => "2.0"; // Back to authenticating at channel refresh with auto-refresh for fresh tokens
|
||||||
@ -114,7 +126,8 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
public Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken)
|
public Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var assembly = GetType().Assembly;
|
var assembly = GetType().Assembly;
|
||||||
var resourceStream = assembly.GetManifestResourceStream("Jellyfin.Plugin.SRFPlay.Images.logo.png");
|
var resourceStream = assembly.GetManifestResourceStream(LogoResourceName)
|
||||||
|
?? assembly.GetManifestResourceStream("Jellyfin.Plugin.SRFPlay.Images.logo.png");
|
||||||
|
|
||||||
if (resourceStream == null)
|
if (resourceStream == null)
|
||||||
{
|
{
|
||||||
@ -162,8 +175,21 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsUnitEnabled()
|
||||||
|
{
|
||||||
|
var config = Plugin.Instance?.Configuration;
|
||||||
|
return config == null || config.ResolveEnabledUnits().Contains(Unit);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<List<ChannelItemInfo>> GetFolderItemsAsync(string? folderId, CancellationToken cancellationToken)
|
private async Task<List<ChannelItemInfo>> GetFolderItemsAsync(string? folderId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// If this unit is not enabled in configuration, show nothing (empty tile).
|
||||||
|
if (!IsUnitEnabled())
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Business unit {Unit} is not enabled - returning no items", Unit);
|
||||||
|
return new List<ChannelItemInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
// Root level - show folder list
|
// Root level - show folder list
|
||||||
if (string.IsNullOrEmpty(folderId))
|
if (string.IsNullOrEmpty(folderId))
|
||||||
{
|
{
|
||||||
@ -198,7 +224,7 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var businessUnit = config.BusinessUnit.ToLowerString();
|
var businessUnit = Unit.ToLowerString();
|
||||||
var topics = await _categoryService.GetTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
|
var topics = await _categoryService.GetTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var topic in topics.Where(t => !string.IsNullOrEmpty(t.Id)))
|
foreach (var topic in topics.Where(t => !string.IsNullOrEmpty(t.Id)))
|
||||||
@ -239,24 +265,23 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
|
|
||||||
private async Task<List<ChannelItemInfo>> GetLatestVideosAsync(CancellationToken cancellationToken)
|
private async Task<List<ChannelItemInfo>> GetLatestVideosAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var urns = await _contentRefreshService.RefreshLatestContentAsync(cancellationToken).ConfigureAwait(false);
|
var urns = await _contentRefreshService.RefreshLatestContentAsync(Unit.ToLowerString(), cancellationToken).ConfigureAwait(false);
|
||||||
return await ConvertUrnsToChannelItems(urns, cancellationToken).ConfigureAwait(false);
|
return await ConvertUrnsToChannelItems(urns, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ChannelItemInfo>> GetTrendingVideosAsync(CancellationToken cancellationToken)
|
private async Task<List<ChannelItemInfo>> GetTrendingVideosAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var urns = await _contentRefreshService.RefreshTrendingContentAsync(cancellationToken).ConfigureAwait(false);
|
var urns = await _contentRefreshService.RefreshTrendingContentAsync(Unit.ToLowerString(), cancellationToken).ConfigureAwait(false);
|
||||||
return await ConvertUrnsToChannelItems(urns, cancellationToken).ConfigureAwait(false);
|
return await ConvertUrnsToChannelItems(urns, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ChannelItemInfo>> GetLiveSportsAsync(CancellationToken cancellationToken)
|
private async Task<List<ChannelItemInfo>> GetLiveSportsAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var items = new List<ChannelItemInfo>();
|
var items = new List<ChannelItemInfo>();
|
||||||
var config = Plugin.Instance?.Configuration;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var businessUnit = config?.BusinessUnit.ToLowerString() ?? "srf";
|
var businessUnit = Unit.ToLowerString();
|
||||||
|
|
||||||
using var apiClient = _apiClientFactory.CreateClient();
|
using var apiClient = _apiClientFactory.CreateClient();
|
||||||
var scheduledLivestreams = await apiClient.GetScheduledLivestreamsAsync(businessUnit, "SPORT", cancellationToken).ConfigureAwait(false);
|
var scheduledLivestreams = await apiClient.GetScheduledLivestreamsAsync(businessUnit, "SPORT", cancellationToken).ConfigureAwait(false);
|
||||||
@ -307,6 +332,7 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
private List<ChannelItemInfo> GetRecordingItems()
|
private List<ChannelItemInfo> GetRecordingItems()
|
||||||
{
|
{
|
||||||
var items = new List<ChannelItemInfo>();
|
var items = new List<ChannelItemInfo>();
|
||||||
|
var unit = Unit.ToLowerString();
|
||||||
var recordings = _recordingService.GetRecordings(RecordingState.Completed);
|
var recordings = _recordingService.GetRecordings(RecordingState.Completed);
|
||||||
|
|
||||||
foreach (var recording in recordings)
|
foreach (var recording in recordings)
|
||||||
@ -316,6 +342,15 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only show recordings belonging to this unit. Recordings created before the
|
||||||
|
// BusinessUnit tag existed have an empty value and are shown everywhere so they
|
||||||
|
// are not lost.
|
||||||
|
if (!string.IsNullOrEmpty(recording.BusinessUnit) &&
|
||||||
|
!string.Equals(recording.BusinessUnit, unit, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var fileInfo = new System.IO.FileInfo(recording.OutputPath);
|
var fileInfo = new System.IO.FileInfo(recording.OutputPath);
|
||||||
var itemId = $"recording_{recording.Id}";
|
var itemId = $"recording_{recording.Id}";
|
||||||
|
|
||||||
@ -368,9 +403,8 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var config = Plugin.Instance?.Configuration;
|
|
||||||
var topicId = folderId.Substring("category_".Length);
|
var topicId = folderId.Substring("category_".Length);
|
||||||
var businessUnit = config?.BusinessUnit.ToLowerString() ?? "srf";
|
var businessUnit = Unit.ToLowerString();
|
||||||
|
|
||||||
var shows = await _categoryService.GetShowsByTopicAsync(topicId, businessUnit, 20, cancellationToken).ConfigureAwait(false);
|
var shows = await _categoryService.GetShowsByTopicAsync(topicId, businessUnit, 20, cancellationToken).ConfigureAwait(false);
|
||||||
var urns = new List<string>();
|
var urns = new List<string>();
|
||||||
@ -472,7 +506,7 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
|
|||||||
var timeKey = timeBucket.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture);
|
var timeKey = timeBucket.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
var recordingCount = _recordingService.GetRecordings(RecordingState.Completed).Count;
|
var recordingCount = _recordingService.GetRecordings(RecordingState.Completed).Count;
|
||||||
return $"{config?.BusinessUnit}_{config?.EnableLatestContent}_{config?.EnableTrendingContent}_{config?.EnableCategoryFolders}_{enabledTopics}_{timeKey}_rec{recordingCount}";
|
return $"{Unit}_{config?.EnableLatestContent}_{config?.EnableTrendingContent}_{config?.EnableCategoryFolders}_{enabledTopics}_{timeKey}_rec{recordingCount}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ChannelItemInfo>> ConvertUrnsToChannelItems(List<string> urns, CancellationToken cancellationToken)
|
private async Task<List<ChannelItemInfo>> ConvertUrnsToChannelItems(List<string> urns, CancellationToken cancellationToken)
|
||||||
43
Jellyfin.Plugin.SRFPlay/Channels/SwiChannel.cs
Normal file
43
Jellyfin.Plugin.SRFPlay/Channels/SwiChannel.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Jellyfin.Plugin.SRFPlay.Api;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Configuration;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SRFPlay.Channels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SWI swissinfo.ch — Swiss international channel.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SwiChannel : SrgChannelBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SwiChannel"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
|
/// <param name="contentRefreshService">The content refresh service.</param>
|
||||||
|
/// <param name="streamResolver">The stream resolver.</param>
|
||||||
|
/// <param name="mediaSourceFactory">The media source factory.</param>
|
||||||
|
/// <param name="categoryService">The category service.</param>
|
||||||
|
/// <param name="apiClientFactory">The API client factory.</param>
|
||||||
|
/// <param name="recordingService">The recording service.</param>
|
||||||
|
public SwiChannel(
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IContentRefreshService contentRefreshService,
|
||||||
|
IStreamUrlResolver streamResolver,
|
||||||
|
IMediaSourceFactory mediaSourceFactory,
|
||||||
|
ICategoryService? categoryService,
|
||||||
|
ISRFApiClientFactory apiClientFactory,
|
||||||
|
IRecordingService recordingService)
|
||||||
|
: base(loggerFactory, contentRefreshService, streamResolver, mediaSourceFactory, categoryService, apiClientFactory, recordingService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override BusinessUnit Unit => BusinessUnit.SWI;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LogoResourceName => "Jellyfin.Plugin.SRFPlay.Images.swi-logo.png";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Description => "SWI swissinfo.ch — Swiss international video-on-demand content";
|
||||||
|
}
|
||||||
@ -66,6 +66,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
{
|
{
|
||||||
// Set default options
|
// Set default options
|
||||||
BusinessUnit = BusinessUnit.SRF;
|
BusinessUnit = BusinessUnit.SRF;
|
||||||
|
EnabledBusinessUnits = new System.Collections.Generic.List<BusinessUnit> { BusinessUnit.SRF };
|
||||||
QualityPreference = QualityPreference.Auto;
|
QualityPreference = QualityPreference.Auto;
|
||||||
ContentRefreshIntervalHours = 6;
|
ContentRefreshIntervalHours = 6;
|
||||||
ExpirationCheckIntervalHours = 24;
|
ExpirationCheckIntervalHours = 24;
|
||||||
@ -78,10 +79,20 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the business unit to fetch content from.
|
/// Gets or sets the legacy single business unit. Retained for backwards compatibility and
|
||||||
|
/// migration into <see cref="EnabledBusinessUnits"/>; new code should use the list instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BusinessUnit BusinessUnit { get; set; }
|
public BusinessUnit BusinessUnit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of business units to expose as channels. One channel tile is shown
|
||||||
|
/// per enabled unit, so polylingual households can browse e.g. SRF (German) and RTS (French)
|
||||||
|
/// at the same time.
|
||||||
|
/// </summary>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Required for configuration serialization")]
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Configuration DTO")]
|
||||||
|
public System.Collections.Generic.List<BusinessUnit> EnabledBusinessUnits { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the preferred video quality.
|
/// Gets or sets the preferred video quality.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -161,4 +172,20 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// Gets or sets the output directory for sport livestream recordings.
|
/// Gets or sets the output directory for sport livestream recordings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RecordingOutputPath { get; set; } = string.Empty;
|
public string RecordingOutputPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the effective set of enabled business units, migrating from the legacy single
|
||||||
|
/// <see cref="BusinessUnit"/> value when the list has not been populated yet.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The business units that should have channels.</returns>
|
||||||
|
public System.Collections.Generic.IReadOnlyList<BusinessUnit> ResolveEnabledUnits()
|
||||||
|
{
|
||||||
|
if (EnabledBusinessUnits != null && EnabledBusinessUnits.Count > 0)
|
||||||
|
{
|
||||||
|
return EnabledBusinessUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy installs only had a single BusinessUnit; honour it so they keep working.
|
||||||
|
return new System.Collections.Generic.List<BusinessUnit> { BusinessUnit };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,16 +2,29 @@
|
|||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
<form id="SRFPlayConfigForm">
|
<form id="SRFPlayConfigForm">
|
||||||
<div class="selectContainer">
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
<label class="selectLabel" for="BusinessUnit">Business Unit</label>
|
<h3>Channels (Business Units)</h3>
|
||||||
<select is="emby-select" id="BusinessUnit" name="BusinessUnit" class="emby-select-withcolor emby-select">
|
<div class="fieldDescription" style="margin-bottom: 0.5em;">Enable a channel tile for each Swiss broadcaster you want. A separate tile appears per enabled unit, so polylingual households can browse e.g. SRF (German) and RTS (French) at the same time.</div>
|
||||||
<option id="optSRF" value="SRF">SRF (German)</option>
|
<label class="emby-checkbox-label">
|
||||||
<option id="optRTS" value="RTS">RTS (French)</option>
|
<input id="buSRF" type="checkbox" is="emby-checkbox" data-bu="SRF" />
|
||||||
<option id="optRSI" value="RSI">RSI (Italian)</option>
|
<span>SRF — Schweizer Radio und Fernsehen (German)</span>
|
||||||
<option id="optRTR" value="RTR">RTR (Romansh)</option>
|
</label>
|
||||||
<option id="optSWI" value="SWI">SWI (International)</option>
|
<label class="emby-checkbox-label">
|
||||||
</select>
|
<input id="buRTS" type="checkbox" is="emby-checkbox" data-bu="RTS" />
|
||||||
<div class="fieldDescription">Select the Swiss broadcasting unit to fetch content from</div>
|
<span>RTS — Radio Télévision Suisse (French)</span>
|
||||||
|
</label>
|
||||||
|
<label class="emby-checkbox-label">
|
||||||
|
<input id="buRSI" type="checkbox" is="emby-checkbox" data-bu="RSI" />
|
||||||
|
<span>RSI — Radiotelevisione svizzera (Italian)</span>
|
||||||
|
</label>
|
||||||
|
<label class="emby-checkbox-label">
|
||||||
|
<input id="buRTR" type="checkbox" is="emby-checkbox" data-bu="RTR" />
|
||||||
|
<span>RTR — Radiotelevisiun Svizra Rumantscha (Romansh)</span>
|
||||||
|
</label>
|
||||||
|
<label class="emby-checkbox-label">
|
||||||
|
<input id="buSWI" type="checkbox" is="emby-checkbox" data-bu="SWI" />
|
||||||
|
<span>SWI — swissinfo.ch (International)</span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="selectContainer">
|
<div class="selectContainer">
|
||||||
<label class="selectLabel" for="QualityPreference">Quality Preference</label>
|
<label class="selectLabel" for="QualityPreference">Quality Preference</label>
|
||||||
@ -142,7 +155,12 @@
|
|||||||
.addEventListener('pageshow', function() {
|
.addEventListener('pageshow', function() {
|
||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
ApiClient.getPluginConfiguration(SRFPlayConfig.pluginUniqueId).then(function (config) {
|
ApiClient.getPluginConfiguration(SRFPlayConfig.pluginUniqueId).then(function (config) {
|
||||||
document.querySelector('#BusinessUnit').value = config.BusinessUnit;
|
var enabled = (config.EnabledBusinessUnits && config.EnabledBusinessUnits.length)
|
||||||
|
? config.EnabledBusinessUnits
|
||||||
|
: [config.BusinessUnit]; // migrate legacy single value
|
||||||
|
['SRF','RTS','RSI','RTR','SWI'].forEach(function(bu) {
|
||||||
|
document.querySelector('#bu' + bu).checked = enabled.indexOf(bu) !== -1;
|
||||||
|
});
|
||||||
document.querySelector('#QualityPreference').value = config.QualityPreference;
|
document.querySelector('#QualityPreference').value = config.QualityPreference;
|
||||||
document.querySelector('#ContentRefreshIntervalHours').value = config.ContentRefreshIntervalHours;
|
document.querySelector('#ContentRefreshIntervalHours').value = config.ContentRefreshIntervalHours;
|
||||||
document.querySelector('#ExpirationCheckIntervalHours').value = config.ExpirationCheckIntervalHours;
|
document.querySelector('#ExpirationCheckIntervalHours').value = config.ExpirationCheckIntervalHours;
|
||||||
@ -168,7 +186,11 @@
|
|||||||
.addEventListener('submit', function(e) {
|
.addEventListener('submit', function(e) {
|
||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
ApiClient.getPluginConfiguration(SRFPlayConfig.pluginUniqueId).then(function (config) {
|
ApiClient.getPluginConfiguration(SRFPlayConfig.pluginUniqueId).then(function (config) {
|
||||||
config.BusinessUnit = document.querySelector('#BusinessUnit').value;
|
config.EnabledBusinessUnits = ['SRF','RTS','RSI','RTR','SWI'].filter(function(bu) {
|
||||||
|
return document.querySelector('#bu' + bu).checked;
|
||||||
|
});
|
||||||
|
// Keep legacy field in sync with the first enabled unit for backwards compat.
|
||||||
|
config.BusinessUnit = config.EnabledBusinessUnits[0] || 'SRF';
|
||||||
config.QualityPreference = document.querySelector('#QualityPreference').value;
|
config.QualityPreference = document.querySelector('#QualityPreference').value;
|
||||||
config.ContentRefreshIntervalHours = parseInt(document.querySelector('#ContentRefreshIntervalHours').value);
|
config.ContentRefreshIntervalHours = parseInt(document.querySelector('#ContentRefreshIntervalHours').value);
|
||||||
config.ExpirationCheckIntervalHours = parseInt(document.querySelector('#ExpirationCheckIntervalHours').value);
|
config.ExpirationCheckIntervalHours = parseInt(document.querySelector('#ExpirationCheckIntervalHours').value);
|
||||||
|
|||||||
@ -33,6 +33,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="..\assests\main logo.png" />
|
<None Remove="..\assests\main logo.png" />
|
||||||
<EmbeddedResource Include="..\assests\main logo.png" LogicalName="Jellyfin.Plugin.SRFPlay.Images.logo.png" />
|
<EmbeddedResource Include="..\assests\main logo.png" LogicalName="Jellyfin.Plugin.SRFPlay.Images.logo.png" />
|
||||||
|
<EmbeddedResource Include="..\assests\units\srf-logo.png" LogicalName="Jellyfin.Plugin.SRFPlay.Images.srf-logo.png" />
|
||||||
|
<EmbeddedResource Include="..\assests\units\rts-logo.png" LogicalName="Jellyfin.Plugin.SRFPlay.Images.rts-logo.png" />
|
||||||
|
<EmbeddedResource Include="..\assests\units\rsi-logo.png" LogicalName="Jellyfin.Plugin.SRFPlay.Images.rsi-logo.png" />
|
||||||
|
<EmbeddedResource Include="..\assests\units\rtr-logo.png" LogicalName="Jellyfin.Plugin.SRFPlay.Images.rtr-logo.png" />
|
||||||
|
<EmbeddedResource Include="..\assests\units\swi-logo.png" LogicalName="Jellyfin.Plugin.SRFPlay.Images.swi-logo.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Utilities;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@ -63,20 +64,25 @@ public class ContentRefreshTask : IScheduledTask
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh latest content
|
// Refresh content for every enabled business unit (one channel per unit).
|
||||||
if (config.EnableLatestContent)
|
var units = config.ResolveEnabledUnits();
|
||||||
|
foreach (var unit in units)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Refreshing latest content");
|
var businessUnit = unit.ToLowerString();
|
||||||
progress?.Report(25);
|
|
||||||
await _contentRefreshService.RefreshLatestContentAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh trending content
|
if (config.EnableLatestContent)
|
||||||
if (config.EnableTrendingContent)
|
{
|
||||||
{
|
_logger.LogInformation("Refreshing latest content for {BusinessUnit}", businessUnit);
|
||||||
_logger.LogInformation("Refreshing trending content");
|
progress?.Report(25);
|
||||||
progress?.Report(75);
|
await _contentRefreshService.RefreshLatestContentAsync(businessUnit, cancellationToken).ConfigureAwait(false);
|
||||||
await _contentRefreshService.RefreshTrendingContentAsync(cancellationToken).ConfigureAwait(false);
|
}
|
||||||
|
|
||||||
|
if (config.EnableTrendingContent)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Refreshing trending content for {BusinessUnit}", businessUnit);
|
||||||
|
progress?.Report(75);
|
||||||
|
await _contentRefreshService.RefreshTrendingContentAsync(businessUnit, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
progress?.Report(100);
|
progress?.Report(100);
|
||||||
|
|||||||
@ -49,7 +49,13 @@ public class ServiceRegistrator : IPluginServiceRegistrator
|
|||||||
serviceCollection.AddSingleton<IScheduledTask, ExpirationCheckTask>();
|
serviceCollection.AddSingleton<IScheduledTask, ExpirationCheckTask>();
|
||||||
serviceCollection.AddSingleton<IScheduledTask, RecordingSchedulerTask>();
|
serviceCollection.AddSingleton<IScheduledTask, RecordingSchedulerTask>();
|
||||||
|
|
||||||
// Register channel - must register as IChannel interface for Jellyfin to discover it
|
// Register one channel (tile) per SRG business unit. Each must be registered as IChannel
|
||||||
serviceCollection.AddSingleton<IChannel, SRFPlayChannel>();
|
// for Jellyfin to discover it. A unit that is not in EnabledBusinessUnits returns no
|
||||||
|
// content (its tile appears empty) - see SrgChannelBase.
|
||||||
|
serviceCollection.AddSingleton<IChannel, SrfChannel>();
|
||||||
|
serviceCollection.AddSingleton<IChannel, RtsChannel>();
|
||||||
|
serviceCollection.AddSingleton<IChannel, RsiChannel>();
|
||||||
|
serviceCollection.AddSingleton<IChannel, RtrChannel>();
|
||||||
|
serviceCollection.AddSingleton<IChannel, SwiChannel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,9 +34,10 @@ public class ContentRefreshService : IContentRefreshService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes latest content from SRF API using Play v3.
|
/// Refreshes latest content from SRF API using Play v3.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="businessUnit">The business unit to fetch content for (e.g. "srf", "rts").</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>List of URNs for new content.</returns>
|
/// <returns>List of URNs for new content.</returns>
|
||||||
public async Task<List<string>> RefreshLatestContentAsync(CancellationToken cancellationToken)
|
public async Task<List<string>> RefreshLatestContentAsync(string businessUnit, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var config = Plugin.Instance?.Configuration;
|
var config = Plugin.Instance?.Configuration;
|
||||||
if (config == null || !config.EnableLatestContent)
|
if (config == null || !config.EnableLatestContent)
|
||||||
@ -46,7 +47,7 @@ public class ContentRefreshService : IContentRefreshService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await FetchVideosFromShowsAsync(
|
return await FetchVideosFromShowsAsync(
|
||||||
config.BusinessUnit.ToLowerString(),
|
businessUnit,
|
||||||
minEpisodeCount: 0,
|
minEpisodeCount: 0,
|
||||||
maxShows: 20,
|
maxShows: 20,
|
||||||
videosPerShow: 1,
|
videosPerShow: 1,
|
||||||
@ -58,9 +59,10 @@ public class ContentRefreshService : IContentRefreshService
|
|||||||
/// Refreshes trending content from SRF API using Play v3.
|
/// Refreshes trending content from SRF API using Play v3.
|
||||||
/// Gets videos from shows with the most episodes.
|
/// Gets videos from shows with the most episodes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="businessUnit">The business unit to fetch content for (e.g. "srf", "rts").</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>List of URNs for trending content.</returns>
|
/// <returns>List of URNs for trending content.</returns>
|
||||||
public async Task<List<string>> RefreshTrendingContentAsync(CancellationToken cancellationToken)
|
public async Task<List<string>> RefreshTrendingContentAsync(string businessUnit, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var config = Plugin.Instance?.Configuration;
|
var config = Plugin.Instance?.Configuration;
|
||||||
if (config == null || !config.EnableTrendingContent)
|
if (config == null || !config.EnableTrendingContent)
|
||||||
@ -70,7 +72,7 @@ public class ContentRefreshService : IContentRefreshService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return await FetchVideosFromShowsAsync(
|
return await FetchVideosFromShowsAsync(
|
||||||
config.BusinessUnit.ToLowerString(),
|
businessUnit,
|
||||||
minEpisodeCount: 10,
|
minEpisodeCount: 10,
|
||||||
maxShows: 15,
|
maxShows: 15,
|
||||||
videosPerShow: 2,
|
videosPerShow: 2,
|
||||||
|
|||||||
@ -12,14 +12,16 @@ public interface IContentRefreshService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes latest content from SRF API using Play v3.
|
/// Refreshes latest content from SRF API using Play v3.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="businessUnit">The business unit to fetch content for (e.g. "srf", "rts").</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>List of URNs for new content.</returns>
|
/// <returns>List of URNs for new content.</returns>
|
||||||
Task<List<string>> RefreshLatestContentAsync(CancellationToken cancellationToken);
|
Task<List<string>> RefreshLatestContentAsync(string businessUnit, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes trending content from SRF API using Play v3.
|
/// Refreshes trending content from SRF API using Play v3.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="businessUnit">The business unit to fetch content for (e.g. "srf", "rts").</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>List of URNs for trending content.</returns>
|
/// <returns>List of URNs for trending content.</returns>
|
||||||
Task<List<string>> RefreshTrendingContentAsync(CancellationToken cancellationToken);
|
Task<List<string>> RefreshTrendingContentAsync(string businessUnit, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,18 +148,25 @@ public class RecordingService : IRecordingService, IDisposable
|
|||||||
public async Task<IReadOnlyList<PlayV3TvProgram>> GetUpcomingScheduleAsync(CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<PlayV3TvProgram>> GetUpcomingScheduleAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var config = Plugin.Instance?.Configuration;
|
var config = Plugin.Instance?.Configuration;
|
||||||
var businessUnit = (config?.BusinessUnit ?? Configuration.BusinessUnit.SRF).ToString().ToLowerInvariant();
|
var units = config?.ResolveEnabledUnits() ?? new[] { Configuration.BusinessUnit.SRF };
|
||||||
|
|
||||||
using var apiClient = _apiClientFactory.CreateClient();
|
using var apiClient = _apiClientFactory.CreateClient();
|
||||||
var livestreams = await apiClient.GetScheduledLivestreamsAsync(businessUnit, "SPORT", cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (livestreams == null)
|
// Aggregate sport livestreams across every enabled business unit so the recordings
|
||||||
|
// page shows events from all enabled languages.
|
||||||
|
var all = new List<PlayV3TvProgram>();
|
||||||
|
foreach (var unit in units)
|
||||||
{
|
{
|
||||||
return Array.Empty<PlayV3TvProgram>();
|
var businessUnit = unit.ToString().ToLowerInvariant();
|
||||||
|
var livestreams = await apiClient.GetScheduledLivestreamsAsync(businessUnit, "SPORT", cancellationToken).ConfigureAwait(false);
|
||||||
|
if (livestreams != null)
|
||||||
|
{
|
||||||
|
all.AddRange(livestreams);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter to only future/current livestreams that aren't blocked
|
// Filter to only future/current livestreams that aren't blocked
|
||||||
return livestreams
|
return all
|
||||||
.Where(ls => ls.Blocked != true && (ls.ValidTo == null || ls.ValidTo.Value.ToUniversalTime() > DateTime.UtcNow))
|
.Where(ls => ls.Blocked != true && (ls.ValidTo == null || ls.ValidTo.Value.ToUniversalTime() > DateTime.UtcNow))
|
||||||
.OrderBy(ls => ls.ValidFrom)
|
.OrderBy(ls => ls.ValidFrom)
|
||||||
.ToList();
|
.ToList();
|
||||||
@ -178,9 +185,11 @@ public class RecordingService : IRecordingService, IDisposable
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch metadata for the URN
|
// Fetch metadata for the URN. The unit is encoded in the URN itself, so use that to
|
||||||
|
// query the right schedule regardless of which units are enabled.
|
||||||
var config = Plugin.Instance?.Configuration;
|
var config = Plugin.Instance?.Configuration;
|
||||||
var businessUnit = (config?.BusinessUnit ?? Configuration.BusinessUnit.SRF).ToString().ToLowerInvariant();
|
var businessUnit = ParseBusinessUnitFromUrn(urn)
|
||||||
|
?? (config?.BusinessUnit ?? Configuration.BusinessUnit.SRF).ToString().ToLowerInvariant();
|
||||||
|
|
||||||
using var apiClient = _apiClientFactory.CreateClient();
|
using var apiClient = _apiClientFactory.CreateClient();
|
||||||
var livestreams = await apiClient.GetScheduledLivestreamsAsync(businessUnit, "SPORT", cancellationToken).ConfigureAwait(false);
|
var livestreams = await apiClient.GetScheduledLivestreamsAsync(businessUnit, "SPORT", cancellationToken).ConfigureAwait(false);
|
||||||
@ -190,6 +199,7 @@ public class RecordingService : IRecordingService, IDisposable
|
|||||||
{
|
{
|
||||||
Id = Guid.NewGuid().ToString("N"),
|
Id = Guid.NewGuid().ToString("N"),
|
||||||
Urn = urn,
|
Urn = urn,
|
||||||
|
BusinessUnit = ParseBusinessUnitFromUrn(urn) ?? businessUnit,
|
||||||
Title = program?.Title ?? urn,
|
Title = program?.Title ?? urn,
|
||||||
Description = program?.Lead ?? program?.Description,
|
Description = program?.Lead ?? program?.Description,
|
||||||
ImageUrl = program?.ImageUrl,
|
ImageUrl = program?.ImageUrl,
|
||||||
@ -206,6 +216,24 @@ public class RecordingService : IRecordingService, IDisposable
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the business unit from an SRF URN of the form "urn:<bu>:<type>:...".
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="urn">The URN.</param>
|
||||||
|
/// <returns>The lowercase business unit, or null if it cannot be determined.</returns>
|
||||||
|
private static string? ParseBusinessUnitFromUrn(string urn)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(urn))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = urn.Split(':');
|
||||||
|
return parts.Length >= 2 && !string.IsNullOrEmpty(parts[1])
|
||||||
|
? parts[1].ToLowerInvariant()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool CancelRecording(string recordingId)
|
public bool CancelRecording(string recordingId)
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
assests/units/rsi-logo.png
Normal file
BIN
assests/units/rsi-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
assests/units/rtr-logo.png
Normal file
BIN
assests/units/rtr-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
assests/units/rts-logo.png
Normal file
BIN
assests/units/rts-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
assests/units/srf-logo.png
Normal file
BIN
assests/units/srf-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
assests/units/swi-logo.png
Normal file
BIN
assests/units/swi-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Loading…
x
Reference in New Issue
Block a user