Skipped "Member ... does not access instance data and can be marked as static" when future non-static use is expected. Skipped some switch statements which will be filled in.
230 lines
8.3 KiB
C#
230 lines
8.3 KiB
C#
// Copyright (C) 2022 Kevin Jilissen
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Jellyfin.Xtream.Client;
|
|
using Jellyfin.Xtream.Client.Models;
|
|
using Jellyfin.Xtream.Service;
|
|
using MediaBrowser.Controller;
|
|
using MediaBrowser.Controller.Library;
|
|
using MediaBrowser.Controller.LiveTv;
|
|
using MediaBrowser.Model.Dto;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Jellyfin.Xtream;
|
|
|
|
/// <summary>
|
|
/// Class LiveTvService.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Initializes a new instance of the <see cref="LiveTvService"/> class.
|
|
/// </remarks>
|
|
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
|
|
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
|
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
|
/// <param name="memoryCache">Instance of the <see cref="IMemoryCache"/> interface.</param>
|
|
public class LiveTvService(IServerApplicationHost appHost, IHttpClientFactory httpClientFactory, ILogger<LiveTvService> logger, IMemoryCache memoryCache) : ILiveTvService, ISupportsDirectStreamProvider
|
|
{
|
|
/// <inheritdoc />
|
|
public string Name => "Xtream Live";
|
|
|
|
/// <inheritdoc />
|
|
public string HomePageUrl => string.Empty;
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken)
|
|
{
|
|
Plugin plugin = Plugin.Instance;
|
|
List<ChannelInfo> items = [];
|
|
foreach (StreamInfo channel in await plugin.StreamService.GetLiveStreamsWithOverrides(cancellationToken).ConfigureAwait(false))
|
|
{
|
|
ParsedName parsed = StreamService.ParseName(channel.Name);
|
|
items.Add(new ChannelInfo()
|
|
{
|
|
Id = StreamService.ToGuid(StreamService.LiveTvPrefix, channel.StreamId, 0, 0).ToString(),
|
|
Number = channel.Num.ToString(CultureInfo.InvariantCulture),
|
|
ImageUrl = channel.StreamIcon,
|
|
Name = parsed.Title,
|
|
Tags = parsed.Tags,
|
|
});
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task CancelTimerAsync(string timerId, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
|
|
{
|
|
return Task.FromResult<IEnumerable<TimerInfo>>(new List<TimerInfo>());
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<IEnumerable<SeriesTimerInfo>> GetSeriesTimersAsync(CancellationToken cancellationToken)
|
|
{
|
|
return Task.FromResult<IEnumerable<SeriesTimerInfo>>(new List<SeriesTimerInfo>());
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
|
|
{
|
|
MediaSourceInfo source = await GetChannelStream(channelId, string.Empty, cancellationToken).ConfigureAwait(false);
|
|
return [source];
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task CloseLiveStream(string id, CancellationToken cancellationToken)
|
|
{
|
|
logger.LogInformation("Closing livestream {ChannelId}", id);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo? program = null)
|
|
{
|
|
return Task.FromResult(new SeriesTimerInfo
|
|
{
|
|
PostPaddingSeconds = 120,
|
|
PrePaddingSeconds = 120,
|
|
RecordAnyChannel = false,
|
|
RecordAnyTime = true,
|
|
RecordNewOnly = false
|
|
});
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
|
{
|
|
Guid guid = Guid.Parse(channelId);
|
|
StreamService.FromGuid(guid, out int prefix, out int streamId, out int _, out int _);
|
|
if (prefix != StreamService.LiveTvPrefix)
|
|
{
|
|
throw new ArgumentException("Unsupported channel");
|
|
}
|
|
|
|
string key = $"xtream-epg-{channelId}";
|
|
ICollection<ProgramInfo>? items = null;
|
|
if (memoryCache.TryGetValue(key, out ICollection<ProgramInfo>? o))
|
|
{
|
|
items = o;
|
|
}
|
|
else
|
|
{
|
|
items = new List<ProgramInfo>();
|
|
Plugin plugin = Plugin.Instance;
|
|
using (XtreamClient client = new XtreamClient())
|
|
{
|
|
EpgListings epgs = await client.GetEpgInfoAsync(plugin.Creds, streamId, cancellationToken).ConfigureAwait(false);
|
|
foreach (EpgInfo epg in epgs.Listings)
|
|
{
|
|
items.Add(new()
|
|
{
|
|
Id = StreamService.ToGuid(StreamService.EpgPrefix, streamId, epg.Id, 0).ToString(),
|
|
ChannelId = channelId,
|
|
StartDate = epg.Start,
|
|
EndDate = epg.End,
|
|
Name = epg.Title,
|
|
Overview = epg.Description,
|
|
});
|
|
}
|
|
}
|
|
|
|
memoryCache.Set(key, items, DateTimeOffset.Now.AddMinutes(10));
|
|
}
|
|
|
|
return from epg in items
|
|
where epg.EndDate >= startDateUtc && epg.StartDate < endDateUtc
|
|
select epg;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task ResetTuner(string id, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<ILiveStream> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
|
|
{
|
|
Guid guid = Guid.Parse(channelId);
|
|
StreamService.FromGuid(guid, out int prefix, out int channel, out int _, out int _);
|
|
if (prefix != StreamService.LiveTvPrefix)
|
|
{
|
|
throw new ArgumentException("Unsupported channel");
|
|
}
|
|
|
|
Plugin plugin = Plugin.Instance;
|
|
MediaSourceInfo mediaSourceInfo = plugin.StreamService.GetMediaSourceInfo(StreamType.Live, channel, restream: true);
|
|
ILiveStream? stream = currentLiveStreams.Find(stream => stream.TunerHostId == Restream.TunerHost && stream.MediaSource.Id == mediaSourceInfo.Id);
|
|
|
|
if (stream == null)
|
|
{
|
|
stream = new Restream(appHost, httpClientFactory, logger, mediaSourceInfo);
|
|
await stream.Open(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
stream.ConsumerCount++;
|
|
return stream;
|
|
}
|
|
}
|