mirror of
https://github.com/jellyfin/jellyfin-plugin-nextpvr.git
synced 2024-11-23 05:59:41 +00:00
Merge pull request #12 from emveepee/upgrade
Rewrite for NextPVR API v5
This commit is contained in:
commit
4f4d198280
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.3
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NextPvr", "NextPvr\NextPvr.csproj", "{83789E82-1F7F-4AAF-A427-D1BC8F01FE72}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Plugin.NextPVR", "Jellyfin.Plugin.NextPVR\Jellyfin.Plugin.NextPVR.csproj", "{83789E82-1F7F-4AAF-A427-D1BC8F01FE72}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
47
Jellyfin.Plugin.NextPVR/Configuration/PluginConfiguration.cs
Normal file
47
Jellyfin.Plugin.NextPVR/Configuration/PluginConfiguration.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Jellyfin.Plugin.NextPVR.Entities;
|
||||
|
||||
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Class PluginConfiguration
|
||||
/// </summary>
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
public string WebServiceUrl { get; set; }
|
||||
|
||||
public string Pin { get; set; }
|
||||
|
||||
public Boolean EnableDebugLogging { get; set; }
|
||||
public Boolean NewEpisodes { get; set; }
|
||||
public bool ShowRepeat { get; set; }
|
||||
public bool GetEpisodeImage { get; set; }
|
||||
public string RecordingDefault { get; set; }
|
||||
public int PrePaddingSeconds { get; set; }
|
||||
public int PostPaddingSeconds { get; set; }
|
||||
/// <summary>
|
||||
/// The genre mappings, to map localised NextPVR genres, to Emby categories.
|
||||
/// </summary>
|
||||
public SerializableDictionary<String, List<String>> GenreMappings { get; set; }
|
||||
|
||||
public PluginConfiguration()
|
||||
{
|
||||
Pin = "0000";
|
||||
WebServiceUrl = "http://localhost:8866";
|
||||
EnableDebugLogging = false;
|
||||
NewEpisodes = false;
|
||||
RecordingDefault = "2";
|
||||
// Initialise this
|
||||
GenreMappings = new SerializableDictionary<string, List<string>>();
|
||||
GenreMappings["GENRESPORT"] = new List<string>() { "Sports", "Football", "Baseball", "Basketball", "Hockey", "Soccer" };
|
||||
GenreMappings["GENRENEWS"] = new List<string>() { "News" };
|
||||
GenreMappings["GENREKIDS"] = new List<string>() { "Kids", "Children" };
|
||||
GenreMappings["GENREMOVIE"] = new List<string>() { "Movie", "Film" };
|
||||
GenreMappings["GENRELIVE"] = new List<string>() { "Awards" };
|
||||
}
|
||||
}
|
||||
}
|
162
Jellyfin.Plugin.NextPVR/Configuration/configPage.html
Normal file
162
Jellyfin.Plugin.NextPVR/Configuration/configPage.html
Normal file
@ -0,0 +1,162 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>NextPVR</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div data-role="page" class="page type-interior pluginConfigurationPage nextpvrConfigurationPage" data-require="emby-button,emby-input,emby-checkbox,emby-select,emby-collapse">
|
||||
|
||||
<div data-role="content">
|
||||
<div class="content-primary">
|
||||
<form class="nextpvrConfigurationForm">
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="txtWebServiceUrl" label="NextPVR Base URL" />
|
||||
<div class="fieldDescription">
|
||||
NextPVR backend URL (format --> http://{hostname}:{port}).
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="txtPin" label="NextPVR PIN" />
|
||||
<div class="fieldDescription">
|
||||
NextPVR PIN to access the backend.
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkboxContainer">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkDebugLogging" />
|
||||
<span>Enable NextPVR debug logging</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selRecDefault" label="Series Recording Default">
|
||||
<option value="1">New episodes on this channel</option>
|
||||
<option value="2">All episodes on this channel</option>
|
||||
<option value="3">Daily, this timeslot</option>
|
||||
<option value="4">Weekly, this timeslot</option>
|
||||
<option value="99">All Episodes, All Channels</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkNewEpisodes" />
|
||||
<span>Default to new episodes only</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div is="emby-collapse" title="Program and Recording Category Options">
|
||||
<div class="collapseContent">
|
||||
<p>Against each jellyfin category match the NextPVR genres that belong to it</p>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" id="txtMovieGenre" name="txtMovieGenre" label="Movie genres:" />
|
||||
<div class="fieldDescription">
|
||||
Example: Movie,Film,TV Movie
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" id="txtSportsGenre" name="txtSportsGenre" label="Sports genres:" />
|
||||
<div class="fieldDescription">
|
||||
Example: Sport,Football
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" id="txtNewsGenre" name="txtNewsGenre" label="News or documentary genres:" />
|
||||
<div class="fieldDescription">
|
||||
Example: News Report,Daily News
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" id="txtKidsGenre" name="txtKidsGenre" label="Kids genres:" />
|
||||
<div class="fieldDescription">
|
||||
Example: Cartoon,Animation
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" id="txtLiveGenre" name="txtLiveGenre" label="Live genres:" />
|
||||
<div class="fieldDescription">
|
||||
Example: Live Gameshow,Live Sports
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
var NextPvrConfigurationPage = {
|
||||
pluginUniqueId: "9574ac10-bf23-49bc-949f-924f23cfa48f"
|
||||
};
|
||||
|
||||
$('.nextpvrConfigurationPage').on('pageshow', function(event) {
|
||||
Dashboard.showLoadingMsg();
|
||||
var page = this;
|
||||
|
||||
ApiClient.getPluginConfiguration(NextPvrConfigurationPage.pluginUniqueId).then(function(config) {
|
||||
$('#txtWebServiceUrl', page).val(config.WebServiceUrl || "");
|
||||
$('#txtPin', page).val(config.Pin || "");
|
||||
page.querySelector('#chkDebugLogging').checked = config.EnableDebugLogging;
|
||||
page.querySelector('#chkNewEpisodes').checked = config.NewEpisodes;
|
||||
$('#selRecDefault', page).val(config.RecordingDefault);
|
||||
loadGenres(config, page);
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
$('.nextpvrConfigurationForm').on('submit', function(e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
var form = this;
|
||||
|
||||
ApiClient.getPluginConfiguration(NextPvrConfigurationPage.pluginUniqueId).then(function(config) {
|
||||
config.WebServiceUrl = $('#txtWebServiceUrl', form).val();
|
||||
config.Pin = $('#txtPin', form).val();
|
||||
config.EnableDebugLogging = document.getElementById('chkDebugLogging').checked;
|
||||
//config.EnableDebugLogging = $('#chkDebugLogging', form).checked();
|
||||
config.EnableDebugLogging = form.querySelector('#chkDebugLogging').checked;
|
||||
config.NewEpisodes = form.querySelector('#chkNewEpisodes').checked;
|
||||
config.RecordingDefault = $('#selRecDefault', form).val();
|
||||
// Copy over the genre mapping fields
|
||||
config.GenreMappings = {
|
||||
"GENREMOVIE": $('#txtMovieGenre', form).val().split(","),
|
||||
"GENRESPORT": $('#txtSportsGenre', form).val().split(","),
|
||||
"GENRENEWS": $('#txtNewsGenre', form).val().split(","),
|
||||
"GENREKIDS": $('#txtKidsGenre', form).val().split(","),
|
||||
"GENRELIVE": $('#txtLiveGenre', form).val().split(","),
|
||||
};
|
||||
|
||||
ApiClient.updatePluginConfiguration(NextPvrConfigurationPage.pluginUniqueId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
});
|
||||
|
||||
// Disable default form submission
|
||||
return false;
|
||||
});
|
||||
|
||||
function loadGenres(config, page) {
|
||||
if (config != null && config.GenreMappings) {
|
||||
if (config.GenreMappings["GENREMOVIE"] != null) {
|
||||
$('#txtMovieGenre', page).val(config.GenreMappings["GENREMOVIE"].join(','));
|
||||
}
|
||||
if (config.GenreMappings["GENRESPORT"] != null) {
|
||||
$('#txtSportsGenre', page).val(config.GenreMappings["GENRESPORT"].join(','));
|
||||
}
|
||||
if (config.GenreMappings["GENRENEWS"] != null) {
|
||||
$('#txtNewsGenre', page).val(config.GenreMappings["GENRENEWS"].join(','));
|
||||
}
|
||||
if (config.GenreMappings["GENREKIDS"] != null) {
|
||||
$('#txtKidsGenre', page).val(config.GenreMappings["GENREKIDS"].join(','));
|
||||
}
|
||||
if (config.GenreMappings["GENRELIVE"] != null) {
|
||||
$('#txtLiveGenre', page).val(config.GenreMappings["GENRELIVE"].join(','));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
72
Jellyfin.Plugin.NextPVR/Entities/SerializableDictionary.cs
Normal file
72
Jellyfin.Plugin.NextPVR/Entities/SerializableDictionary.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Xml.Schema;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Entities
|
||||
{
|
||||
[XmlRoot("dictionary")]
|
||||
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
|
||||
{
|
||||
#region IXmlSerializable Members
|
||||
|
||||
public XmlSchema GetSchema()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
var keySerializer = new XmlSerializer(typeof (TKey));
|
||||
var valueSerializer = new XmlSerializer(typeof (TValue));
|
||||
|
||||
var wasEmpty = reader.IsEmptyElement;
|
||||
reader.Read();
|
||||
|
||||
if (wasEmpty)
|
||||
return;
|
||||
|
||||
while (reader.NodeType != XmlNodeType.EndElement)
|
||||
{
|
||||
reader.ReadStartElement("item");
|
||||
|
||||
reader.ReadStartElement("key");
|
||||
var key = (TKey) keySerializer.Deserialize(reader);
|
||||
reader.ReadEndElement();
|
||||
|
||||
reader.ReadStartElement("value");
|
||||
var value = (TValue) valueSerializer.Deserialize(reader);
|
||||
reader.ReadEndElement();
|
||||
|
||||
Add(key, value);
|
||||
|
||||
reader.ReadEndElement();
|
||||
reader.MoveToContent();
|
||||
}
|
||||
reader.ReadEndElement();
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
var keySerializer = new XmlSerializer(typeof (TKey));
|
||||
var valueSerializer = new XmlSerializer(typeof (TValue));
|
||||
|
||||
foreach (TKey key in Keys)
|
||||
{
|
||||
writer.WriteStartElement("item");
|
||||
writer.WriteStartElement("key");
|
||||
keySerializer.Serialize(writer, key);
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("value");
|
||||
var value = this[key];
|
||||
valueSerializer.Serialize(writer, value);
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -2,19 +2,19 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace NextPvr.Helpers
|
||||
namespace Jellyfin.Plugin.NextPVR.Helpers
|
||||
{
|
||||
public static class ChannelHelper
|
||||
{
|
||||
public static ChannelType GetChannelType(string channelType)
|
||||
public static ChannelType GetChannelType(int channelType)
|
||||
{
|
||||
ChannelType type = new ChannelType();
|
||||
|
||||
if (channelType == "0x1")
|
||||
if (channelType == 1)
|
||||
{
|
||||
type = ChannelType.TV;
|
||||
}
|
||||
else if (channelType == "0xa")
|
||||
else if (channelType == 10)
|
||||
{
|
||||
type = ChannelType.Radio;
|
||||
}
|
137
Jellyfin.Plugin.NextPVR/Helpers/GenreMapper.cs
Normal file
137
Jellyfin.Plugin.NextPVR/Helpers/GenreMapper.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using Jellyfin.Plugin.NextPVR.Configuration;
|
||||
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to map MediaPortal genres to Emby categories
|
||||
/// </summary>
|
||||
public class GenreMapper
|
||||
{
|
||||
public const string GENRE_MOVIE = "GENREMOVIE";
|
||||
public const string GENRE_SPORT = "GENRESPORT";
|
||||
public const string GENRE_NEWS = "GENRENEWS";
|
||||
public const string GENRE_KIDS = "GENREKIDS";
|
||||
public const string GENRE_LIVE = "GENRELIVE";
|
||||
|
||||
private readonly PluginConfiguration _configuration;
|
||||
private readonly List<String> _movieGenres;
|
||||
private readonly List<String> _sportGenres;
|
||||
private readonly List<String> _newsGenres;
|
||||
private readonly List<String> _kidsGenres;
|
||||
private readonly List<String> _liveGenres;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenreMapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration.</param>
|
||||
public GenreMapper(PluginConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException("configuration");
|
||||
|
||||
_movieGenres = new List<string>();
|
||||
_sportGenres = new List<string>();
|
||||
_newsGenres = new List<string>();
|
||||
_kidsGenres = new List<string>();
|
||||
_liveGenres = new List<string>();
|
||||
LoadInternalLists(_configuration.GenreMappings);
|
||||
}
|
||||
|
||||
private void LoadInternalLists(Dictionary<string, List<string>> genreMappings)
|
||||
{
|
||||
if (genreMappings != null)
|
||||
{
|
||||
if (_configuration.GenreMappings.ContainsKey(GENRE_MOVIE) && _configuration.GenreMappings[GENRE_MOVIE] != null)
|
||||
{
|
||||
_movieGenres.AddRange(_configuration.GenreMappings[GENRE_MOVIE]);
|
||||
}
|
||||
|
||||
if (_configuration.GenreMappings.ContainsKey(GENRE_SPORT) && _configuration.GenreMappings[GENRE_SPORT] != null)
|
||||
{
|
||||
_sportGenres.AddRange(_configuration.GenreMappings[GENRE_SPORT]);
|
||||
}
|
||||
|
||||
if (_configuration.GenreMappings.ContainsKey(GENRE_NEWS) && _configuration.GenreMappings[GENRE_NEWS] != null)
|
||||
{
|
||||
_newsGenres.AddRange(_configuration.GenreMappings[GENRE_NEWS]);
|
||||
}
|
||||
|
||||
if (_configuration.GenreMappings.ContainsKey(GENRE_KIDS) && _configuration.GenreMappings[GENRE_KIDS] != null)
|
||||
{
|
||||
_kidsGenres.AddRange(_configuration.GenreMappings[GENRE_KIDS]);
|
||||
}
|
||||
|
||||
if (_configuration.GenreMappings.ContainsKey(GENRE_LIVE) && _configuration.GenreMappings[GENRE_LIVE] != null)
|
||||
{
|
||||
_liveGenres.AddRange(_configuration.GenreMappings[GENRE_LIVE]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the program genres.
|
||||
/// </summary>
|
||||
/// <param name="program">The program.</param>
|
||||
public void PopulateProgramGenres(ProgramInfo program)
|
||||
{
|
||||
// Check there is a program and genres to map
|
||||
if (program != null)
|
||||
{
|
||||
if (program.Genres != null && program.Genres.Count > 0)
|
||||
{
|
||||
program.IsMovie = _movieGenres.Any(g => program.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
program.IsSports = _sportGenres.Any(g => program.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
program.IsNews = _newsGenres.Any(g => program.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
program.IsKids = _kidsGenres.Any(g => program.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
if (program.IsLive == false)
|
||||
program.IsLive = _liveGenres.Any(g => program.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the recording genres.
|
||||
/// </summary>
|
||||
/// <param name="recording">The recording.</param>
|
||||
public void PopulateRecordingGenres(MyRecordingInfo recording)
|
||||
{
|
||||
// Check there is a recording and genres to map
|
||||
if (recording != null)
|
||||
{
|
||||
if (recording.Genres != null && recording.Genres.Count > 0)
|
||||
{
|
||||
recording.IsMovie = _movieGenres.Any(g => recording.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
recording.IsSports = _sportGenres.Any(g => recording.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
recording.IsNews = _newsGenres.Any(g => recording.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
recording.IsKids = _kidsGenres.Any(g => recording.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
recording.IsLive = _liveGenres.Any(g => recording.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the timer genres.
|
||||
/// </summary>
|
||||
/// <param name="recording">The timer.</param>
|
||||
public void PopulateTimerGenres(TimerInfo timer)
|
||||
{
|
||||
// Check there is a timer and genres to map
|
||||
if (timer != null)
|
||||
{
|
||||
if (timer.Genres != null && timer.Genres.Length > 0)
|
||||
{
|
||||
timer.IsMovie = _movieGenres.Any(g => timer.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
//timer.IsSports = _sportGenres.Any(g => timer.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
//timer.IsNews = _newsGenres.Any(g => timer.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
//timer.IsKids = _kidsGenres.Any(g => timer.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
//timer.IsProgramSeries = _seriesGenres.Any(g => timer.Genres.Contains(g, StringComparer.InvariantCultureIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<AssemblyVersion>4.0.0.0</AssemblyVersion>
|
||||
<FileVersion>4.0.0.0</FileVersion>
|
||||
<AssemblyVersion>5.0.0.0</AssemblyVersion>
|
||||
<FileVersion>5.0.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
@ -8,8 +8,8 @@ using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
using NextPvr.Responses;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
using Jellyfin.Plugin.NextPVR.Responses;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
@ -22,7 +22,7 @@ using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace NextPvr
|
||||
namespace Jellyfin.Plugin.NextPVR
|
||||
{
|
||||
/// <summary>
|
||||
/// Class LiveTvService
|
||||
@ -37,6 +37,7 @@ namespace NextPvr
|
||||
private readonly Dictionary<int, int> _heartBeat = new Dictionary<int, int>();
|
||||
|
||||
private string Sid { get; set; }
|
||||
public bool isActive { get { return Sid != null; } }
|
||||
private DateTimeOffset LastUpdatedSidDateTime { get; set; }
|
||||
private IFileSystem _fileSystem;
|
||||
|
||||
@ -62,13 +63,13 @@ namespace NextPvr
|
||||
|
||||
if (string.IsNullOrEmpty(config.WebServiceUrl))
|
||||
{
|
||||
_logger.LogError("[NextPvr] Web service url must be configured.");
|
||||
_logger.LogError("[NextPVR] Web service url must be configured.");
|
||||
throw new InvalidOperationException("NextPvr web service url must be configured.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(config.Pin))
|
||||
{
|
||||
_logger.LogError("[NextPvr] Pin must be configured.");
|
||||
_logger.LogError("[NextPVR] Pin must be configured.");
|
||||
throw new InvalidOperationException("NextPvr pin must be configured.");
|
||||
}
|
||||
|
||||
@ -83,31 +84,40 @@ namespace NextPvr
|
||||
/// </summary>
|
||||
private async Task InitiateSession(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start InitiateSession");
|
||||
_logger.LogInformation("[NextPVR] Start InitiateSession");
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/Util/NPVR/Client/Instantiate", baseUrl)
|
||||
Url = string.Format("{0}/service?method=session.initiate&ver=1.0&device=jellyfin", baseUrl)
|
||||
};
|
||||
|
||||
options.AcceptHeader = "application/json";
|
||||
options.LogErrorResponseBody = false;
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
var clientKeys = new InstantiateResponse().GetClientKeys(stream, _jsonSerializer, _logger);
|
||||
|
||||
var sid = clientKeys.sid;
|
||||
var salt = clientKeys.salt;
|
||||
_logger.LogInformation(string.Format("[NextPvr] Sid: {0}", sid));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Sid: {0}", sid));
|
||||
|
||||
var loggedIn = await Login(sid, salt, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (loggedIn)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Session initiated.");
|
||||
_logger.LogInformation("[NextPVR] Session initiated.");
|
||||
Sid = sid;
|
||||
LastUpdatedSidDateTime = DateTimeOffset.UtcNow;
|
||||
bool flag = await GetDefaultSettingsAsync(cancellationToken);
|
||||
Plugin.Instance.Configuration.GetEpisodeImage = "true" == await GetBackendSettingAsync(cancellationToken, "/Settings/General/ArtworkFromSchedulesDirect");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("[NextPVR] PIN not accepted.");
|
||||
throw new UnauthorizedAccessException("NextPVR PIN not accepted");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,20 +130,20 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
private async Task<bool> Login(string sid, string salt, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(string.Format("[NextPvr] Start Login procedure for Sid: {0} & Salt: {1}", sid, salt));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Start Login procedure for Sid: {0} & Salt: {1}", sid, salt));
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
var pin = Plugin.Instance.Configuration.Pin;
|
||||
_logger.LogInformation(string.Format("[NextPvr] Pin: {0}", pin));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Pin: {0}", pin));
|
||||
|
||||
var strb = new StringBuilder();
|
||||
var md5Result = GetMd5Hash(strb.Append(":").Append(GetMd5Hash(pin)).Append(":").Append(salt).ToString());
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
Url = string.Format("{0}/public/Util/NPVR/Client/Initialize/{1}?sid={2}", baseUrl, md5Result, sid),
|
||||
Url = string.Format("{0}/service?method=session.login&md5={1}&sid={2}", baseUrl, md5Result, sid),
|
||||
CancellationToken = cancellationToken
|
||||
};
|
||||
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
return new InitializeResponse().LoggedIn(stream, _jsonSerializer, _logger);
|
||||
@ -142,7 +152,10 @@ namespace NextPvr
|
||||
|
||||
public string GetMd5Hash(string value)
|
||||
{
|
||||
return value.GetMD5().ToString();
|
||||
byte[] hashValue;
|
||||
hashValue = System.Security.Cryptography.MD5.Create().ComputeHash(new UTF8Encoding().GetBytes(value));
|
||||
//Bit convertor return the byte to string as all caps hex values seperated by "-"
|
||||
return BitConverter.ToString(hashValue).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -152,7 +165,7 @@ namespace NextPvr
|
||||
/// <returns>Task{IEnumerable{ChannelInfo}}.</returns>
|
||||
public async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start GetChannels Async, retrieve all channels");
|
||||
_logger.LogInformation("[NextPVR] Start GetChannels Async, retrieve all channels");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
@ -160,8 +173,9 @@ namespace NextPvr
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/GuideService/Channels?sid={1}", baseUrl, Sid)
|
||||
Url = string.Format("{0}/service?method=channel.list&sid={1}", baseUrl, Sid)
|
||||
};
|
||||
options.AcceptHeader = "application/json";
|
||||
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
@ -181,7 +195,7 @@ namespace NextPvr
|
||||
/// <returns>Task{IEnumerable{RecordingInfo}}</returns>
|
||||
public async Task<IEnumerable<MyRecordingInfo>> GetAllRecordingsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start GetRecordings Async, retrieve all 'Pending', 'Inprogress' and 'Completed' recordings ");
|
||||
_logger.LogInformation("[NextPVR] Start GetRecordings Async, retrieve all 'Pending', 'Inprogress' and 'Completed' recordings ");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
@ -189,43 +203,14 @@ namespace NextPvr
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ManageService/Get/SortedFilteredList?sid={1}", baseUrl, Sid),
|
||||
DecompressionMethod = CompressionMethods.None
|
||||
Url = string.Format("{0}/service?method=recording.list&filter=ready&sid={1}", baseUrl, Sid)
|
||||
};
|
||||
|
||||
var filterOptions = new
|
||||
options.AcceptHeader = "application/json";
|
||||
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
resultLimit = -1,
|
||||
datetimeSortSeq = 0,
|
||||
channelSortSeq = 0,
|
||||
titleSortSeq = 0,
|
||||
statusSortSeq = 0,
|
||||
datetimeDecending = false,
|
||||
channelDecending = false,
|
||||
titleDecending = false,
|
||||
statusDecending = false,
|
||||
All = false,
|
||||
None = false,
|
||||
Pending = false,
|
||||
InProgress = true,
|
||||
Completed = true,
|
||||
Failed = true,
|
||||
Conflict = false,
|
||||
Recurring = false,
|
||||
Deleted = false,
|
||||
FilterByName = false,
|
||||
NameFilter = (string)null,
|
||||
NameFilterCaseSensative = false
|
||||
};
|
||||
|
||||
options.RequestContent = _jsonSerializer.SerializeToString(filterOptions).ToString();
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
var response = await _httpClient.Post(options).ConfigureAwait(false);
|
||||
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
return new RecordingResponse(baseUrl, _fileSystem).GetRecordings(stream, _jsonSerializer, _logger);
|
||||
return new RecordingResponse(baseUrl, _fileSystem, _logger).GetRecordings(stream, _jsonSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +222,7 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(string.Format("[NextPvr] Start Delete Recording Async for recordingId: {0}", recordingId));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Start Delete Recording Async for recordingId: {0}", recordingId));
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
@ -245,8 +230,9 @@ namespace NextPvr
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/Delete/{1}?sid={2}", baseUrl, recordingId, Sid)
|
||||
Url = string.Format("{0}/service?method=recording.delete&recording_id={1}&sid={2}", baseUrl, recordingId, Sid)
|
||||
};
|
||||
options.AcceptHeader = "application/json";
|
||||
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
@ -256,11 +242,11 @@ namespace NextPvr
|
||||
|
||||
if (error == null || error == true)
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPvr] Failed to delete the recording for recordingId: {0}", recordingId));
|
||||
_logger.LogError(string.Format("[NextPVR] Failed to delete the recording for recordingId: {0}", recordingId));
|
||||
throw new Exception(string.Format("Failed to delete the recording for recordingId: {0}", recordingId));
|
||||
}
|
||||
|
||||
_logger.LogInformation("[NextPvr] Deleted Recording with recordingId: {0}", recordingId);
|
||||
_logger.LogInformation("[NextPVR] Deleted Recording with recordingId: {0}", recordingId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,7 +267,7 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task CancelTimerAsync(string timerId, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(string.Format("[NextPvr] Start Cancel Recording Async for recordingId: {0}", timerId));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Start Cancel Recording Async for recordingId: {0}", timerId));
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
@ -289,9 +275,9 @@ namespace NextPvr
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/CancelRec/{1}?sid={2}", baseUrl, timerId, Sid)
|
||||
Url = string.Format("{0}/service?method=recording.delete&recording_id={1}&sid={2}", baseUrl, timerId, Sid)
|
||||
};
|
||||
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
LastRecordingChange = DateTimeOffset.UtcNow;
|
||||
@ -299,11 +285,11 @@ namespace NextPvr
|
||||
|
||||
if (error == null || error == true)
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPvr] Failed to cancel the recording for recordingId: {0}", timerId));
|
||||
_logger.LogError(string.Format("[NextPVR] Failed to cancel the recording for recordingId: {0}", timerId));
|
||||
throw new Exception(string.Format("Failed to cancel the recording for recordingId: {0}", timerId));
|
||||
}
|
||||
|
||||
_logger.LogInformation(string.Format("[NextPvr] Cancelled Recording for recordingId: {0}", timerId));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Cancelled Recording for recordingId: {0}", timerId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,7 +301,7 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(string.Format("[NextPvr] Start CreateTimer Async for ChannelId: {0} & Name: {1}", info.ChannelId, info.Name));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Start CreateTimer Async for ChannelId: {0} & Name: {1}", info.ChannelId, info.Name));
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
@ -323,37 +309,26 @@ namespace NextPvr
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/Record?sid={1}", baseUrl, Sid),
|
||||
DecompressionMethod = CompressionMethods.None
|
||||
Url = string.Format("{0}/service?method=recording.save&sid={1}&event_id={2}&pre_padding={3}&post_padding={4}", baseUrl, Sid,
|
||||
int.Parse(info.ProgramId, _usCulture),
|
||||
info.PrePaddingSeconds / 60,
|
||||
info.PostPaddingSeconds / 60,
|
||||
info.Id
|
||||
)
|
||||
};
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPVR] TimerSettings CreateTimer: {0} for ChannelId: {1} & Name: {2}", info.ProgramId, info.ChannelId, info.Name));
|
||||
|
||||
var timerSettings = await GetDefaultScheduleSettings(cancellationToken).ConfigureAwait(false);
|
||||
options.AcceptHeader = "application/json";
|
||||
|
||||
timerSettings.allChannels = false;
|
||||
timerSettings.ChannelOID = int.Parse(info.ChannelId, _usCulture);
|
||||
|
||||
if (!string.IsNullOrEmpty(info.ProgramId))
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
timerSettings.epgeventOID = int.Parse(info.ProgramId, _usCulture);
|
||||
}
|
||||
|
||||
timerSettings.post_padding_min = info.PostPaddingSeconds / 60;
|
||||
timerSettings.pre_padding_min = info.PrePaddingSeconds / 60;
|
||||
|
||||
var postContent = _jsonSerializer.SerializeToString(timerSettings);
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPvr] TimerSettings CreateTimer: {0} for ChannelId: {1} & Name: {2}", postContent, info.ChannelId, info.Name));
|
||||
|
||||
options.RequestContent = postContent;
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
try
|
||||
{
|
||||
await _httpClient.Post(options).ConfigureAwait((false));
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPvr] CreateTimer async with exception: {0}", ex.Message));
|
||||
throw new LiveTvConflictException();
|
||||
bool? error = new CancelDeleteRecordingResponse().RecordingError(stream, _jsonSerializer, _logger);
|
||||
if (error == null || error == true)
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPVR] Failed to create the timer with programId: {0}", info.ProgramId));
|
||||
throw new Exception(string.Format("Failed to create the timer with programId: {0}", info.ProgramId));
|
||||
}
|
||||
_logger.LogError("[NextPVR] CreateTimer async for programId: {0}", info.ProgramId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,50 +339,20 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start GetTimer Async, retrieve the 'Pending' recordings");
|
||||
_logger.LogInformation("[NextPVR] Start GetTimer Async, retrieve the 'Pending' recordings");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ManageService/Get/SortedFilteredList?sid={1}", baseUrl, Sid),
|
||||
DecompressionMethod = CompressionMethods.None
|
||||
Url = string.Format("{0}/service?method=recording.list&filter=pending&sid={1}", baseUrl, Sid)
|
||||
};
|
||||
|
||||
var filterOptions = new
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
resultLimit = -1,
|
||||
datetimeSortSeq = 0,
|
||||
channelSortSeq = 0,
|
||||
titleSortSeq = 0,
|
||||
statusSortSeq = 0,
|
||||
datetimeDecending = false,
|
||||
channelDecending = false,
|
||||
titleDecending = false,
|
||||
statusDecending = false,
|
||||
All = false,
|
||||
None = false,
|
||||
Pending = true,
|
||||
InProgress = false,
|
||||
Completed = false,
|
||||
Failed = false,
|
||||
Conflict = true,
|
||||
Recurring = false,
|
||||
Deleted = false,
|
||||
FilterByName = false,
|
||||
NameFilter = (string)null,
|
||||
NameFilterCaseSensative = false
|
||||
};
|
||||
|
||||
options.RequestContent = _jsonSerializer.SerializeToString(filterOptions);
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
var response = await _httpClient.Post(options).ConfigureAwait(false);
|
||||
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
return new RecordingResponse(baseUrl, _fileSystem).GetTimers(stream, _jsonSerializer, _logger);
|
||||
return new RecordingResponse(baseUrl, _fileSystem, _logger).GetTimers(stream, _jsonSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -418,39 +363,20 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SeriesTimerInfo>> GetSeriesTimersAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start GetSeriesTimer Async, retrieve the recurring recordings");
|
||||
_logger.LogInformation("[NextPVR] Start GetSeriesTimer Async, retrieve the recurring recordings");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ManageService/Get/SortedFilteredList?sid={1}", baseUrl, Sid),
|
||||
DecompressionMethod = CompressionMethods.None
|
||||
Url = string.Format("{0}/service?method=recording.recurring.list&sid={1}", baseUrl, Sid)
|
||||
};
|
||||
|
||||
var filterOptions = new
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
resultLimit = -1,
|
||||
All = false,
|
||||
None = false,
|
||||
Pending = false,
|
||||
InProgress = false,
|
||||
Completed = false,
|
||||
Failed = false,
|
||||
Conflict = false,
|
||||
Recurring = true,
|
||||
Deleted = false
|
||||
};
|
||||
|
||||
options.RequestContent = _jsonSerializer.SerializeToString(filterOptions);
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
var response = await _httpClient.Post(options).ConfigureAwait(false);
|
||||
|
||||
using (var stream = response.Content)
|
||||
{
|
||||
return new RecordingResponse(baseUrl, _fileSystem).GetSeriesTimers(stream, _jsonSerializer, _logger);
|
||||
return new RecurringResponse(baseUrl, _fileSystem, _logger).GetSeriesTimers(stream, _jsonSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -462,77 +388,62 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(string.Format("[NextPvr] Start CreateSeriesTimer Async for ChannelId: {0} & Name: {1}", info.ChannelId, info.Name));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Start CreateSeriesTimer Async for ChannelId: {0} & Name: {1}", info.ChannelId, info.Name));
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/Record?sid={1}", baseUrl, Sid),
|
||||
DecompressionMethod = CompressionMethods.None
|
||||
Url = string.Format("{0}/service?method=recording.recurring.save&sid={1}&pre_padding={2}&post_padding={3}&keep={4}", baseUrl, Sid,
|
||||
info.PrePaddingSeconds / 60,
|
||||
info.PostPaddingSeconds / 60,
|
||||
info.KeepUpTo)
|
||||
};
|
||||
|
||||
var timerSettings = await GetDefaultScheduleSettings(cancellationToken).ConfigureAwait(false);
|
||||
options.AcceptHeader = "application/json";
|
||||
|
||||
timerSettings.allChannels = info.RecordAnyChannel;
|
||||
timerSettings.onlyNew = info.RecordNewOnly;
|
||||
timerSettings.recurringName = info.Name;
|
||||
timerSettings.recordAnyTimeslot = info.RecordAnyTime;
|
||||
int recurringType = int.Parse(Plugin.Instance.Configuration.RecordingDefault);
|
||||
|
||||
if (!info.RecordAnyTime)
|
||||
if (recurringType == 99)
|
||||
{
|
||||
timerSettings.startDate = info.StartDate.ToString(_usCulture);
|
||||
timerSettings.endDate = info.EndDate.ToString(_usCulture);
|
||||
timerSettings.recordThisTimeslot = true;
|
||||
options.Url += string.Format("&name={0}&keyword=title+like+'{0}'", Uri.EscapeUriString(info.Name.Replace("'", "''")));
|
||||
}
|
||||
|
||||
if (info.Days.Count == 1)
|
||||
else
|
||||
{
|
||||
timerSettings.recordThisDay = true;
|
||||
options.Url += string.Format("&event_id={0}&recurring_type={1}", info.ProgramId, recurringType);
|
||||
}
|
||||
if (info.RecordNewOnly || Plugin.Instance.Configuration.NewEpisodes)
|
||||
options.Url += "&only_new=true";
|
||||
|
||||
if (info.Days.Count > 1 && info.Days.Count < 7)
|
||||
if (recurringType == 3 || recurringType == 4)
|
||||
options.Url += "×lot=true";
|
||||
|
||||
await CreateUpdateSeriesTimerAsync(info, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the series Timer
|
||||
/// </summary>
|
||||
/// <param name="info">The series program info</param>
|
||||
/// <param name="cancellationToken">The CancellationToken</param>
|
||||
/// <returns></returns>
|
||||
public async Task CreateUpdateSeriesTimerAsync(SeriesTimerInfo info, HttpRequestOptions options)
|
||||
{
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPVR] TimerSettings CreateSeriesTimerAsync: {0} for ChannelId: {1} & Name: {2}", info.ProgramId, info.ChannelId, info.Name));
|
||||
options.AcceptHeader = "application/json";
|
||||
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
timerSettings.recordSpecificdays = true;
|
||||
}
|
||||
|
||||
timerSettings.recordAnyDay = info.Days.Count == 7;
|
||||
timerSettings.daySunday = info.Days.Contains(DayOfWeek.Sunday);
|
||||
timerSettings.dayMonday = info.Days.Contains(DayOfWeek.Monday);
|
||||
timerSettings.dayTuesday = info.Days.Contains(DayOfWeek.Tuesday);
|
||||
timerSettings.dayWednesday = info.Days.Contains(DayOfWeek.Wednesday);
|
||||
timerSettings.dayThursday = info.Days.Contains(DayOfWeek.Thursday);
|
||||
timerSettings.dayFriday = info.Days.Contains(DayOfWeek.Friday);
|
||||
timerSettings.daySaturday = info.Days.Contains(DayOfWeek.Saturday);
|
||||
|
||||
if (!info.RecordAnyChannel)
|
||||
{
|
||||
timerSettings.ChannelOID = int.Parse(info.ChannelId, _usCulture);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(info.ProgramId))
|
||||
{
|
||||
timerSettings.epgeventOID = int.Parse(info.ProgramId, _usCulture);
|
||||
}
|
||||
|
||||
timerSettings.post_padding_min = info.PostPaddingSeconds / 60;
|
||||
timerSettings.pre_padding_min = info.PrePaddingSeconds / 60;
|
||||
|
||||
var postContent = _jsonSerializer.SerializeToString(timerSettings);
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPvr] TimerSettings CreateSeriesTimer: {0} for ChannelId: {1} & Name: {2}", postContent, info.ChannelId, info.Name));
|
||||
|
||||
options.RequestContent = postContent;
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
try
|
||||
{
|
||||
await _httpClient.Post(options).ConfigureAwait((false));
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPvr] CreateSeries async with exception: {0} ", ex.Message));
|
||||
throw new LiveTvConflictException();
|
||||
bool? error = new CancelDeleteRecordingResponse().RecordingError(stream, _jsonSerializer, _logger);
|
||||
if (error == null || error == true)
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPVR] Failed to create or update the timer with Recurring ID: {0}", info.Id));
|
||||
throw new Exception(string.Format("Failed to create or update the timer with Recurring ID: {0}", info.Id));
|
||||
}
|
||||
_logger.LogInformation("[NextPVR] CreateUpdateSeriesTimer async for Program ID: {0} Recurring ID {1}", info.ProgramId, info.Id);
|
||||
//Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
@ -544,47 +455,56 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(string.Format("[NextPvr] Start UpdateSeriesTimer Async for ChannelId: {0} & Name: {1}", info.ChannelId, info.Name));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Start UpdateSeriesTimer Async for ChannelId: {0} & Name: {1}", info.ChannelId, info.Name));
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/UpdateRecurr?sid={1}", baseUrl, Sid),
|
||||
DecompressionMethod = CompressionMethods.None
|
||||
Url = string.Format("{0}/service?method=recording.recurring.save&sid={1}&pre_padding={2}&post_padding={3}&keep={4}&recurring_id={5}", baseUrl, Sid,
|
||||
info.PrePaddingSeconds / 60,
|
||||
info.PostPaddingSeconds / 60,
|
||||
info.KeepUpTo,
|
||||
info.Id)
|
||||
};
|
||||
|
||||
var timerSettings = await GetDefaultScheduleSettings(cancellationToken).ConfigureAwait(false);
|
||||
int recurringType = 2;
|
||||
|
||||
timerSettings.recurrOID = int.Parse(info.Id);
|
||||
timerSettings.post_padding_min = info.PostPaddingSeconds / 60;
|
||||
timerSettings.pre_padding_min = info.PrePaddingSeconds / 60;
|
||||
timerSettings.allChannels = info.RecordAnyChannel;
|
||||
timerSettings.onlyNew = info.RecordNewOnly;
|
||||
timerSettings.recurringName = info.Name;
|
||||
timerSettings.recordAnyTimeslot = info.RecordAnyTime;
|
||||
timerSettings.keep_all_days = true;
|
||||
timerSettings.days_to_keep = 0;
|
||||
timerSettings.extend_end_time_min = 0;
|
||||
|
||||
var postContent = _jsonSerializer.SerializeToString(timerSettings);
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPvr] TimerSettings UpdateSeriesTimer: {0} for ChannelId: {1} & Name: {2}", postContent, info.ChannelId, info.Name));
|
||||
|
||||
options.RequestContent = postContent;
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
try
|
||||
if (info.RecordAnyChannel)
|
||||
{
|
||||
await _httpClient.Post(options).ConfigureAwait((false));
|
||||
options.Url += string.Format("&name={0}&keyword=title+like+'{0}'", Uri.EscapeUriString(info.Name.Replace("'", "''")));
|
||||
}
|
||||
catch (HttpException ex)
|
||||
else
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPvr] UpdateSeries async with exception: {0}", ex.Message));
|
||||
throw new LiveTvConflictException();
|
||||
if (info.RecordAnyTime)
|
||||
{
|
||||
if (info.RecordNewOnly)
|
||||
{
|
||||
recurringType = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (info.Days.Count == 7)
|
||||
{
|
||||
recurringType = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
recurringType = 3;
|
||||
}
|
||||
}
|
||||
options.Url += string.Format("&recurring_type={0}", recurringType);
|
||||
}
|
||||
if (info.RecordNewOnly)
|
||||
options.Url += "&only_new=true";
|
||||
|
||||
await CreateUpdateSeriesTimerAsync(info, options);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update a single Timer
|
||||
/// </summary>
|
||||
@ -593,7 +513,7 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(string.Format("[NextPvr] Start UpdateTimer Async for ChannelId: {0} & Name: {1}", info.ChannelId, info.Name));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Start UpdateTimer Async for ChannelId: {0} & Name: {1}", info.ChannelId, info.Name));
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
@ -601,32 +521,22 @@ namespace NextPvr
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/UpdateRec?sid={1}", baseUrl, Sid),
|
||||
DecompressionMethod = CompressionMethods.None
|
||||
Url = string.Format("{0}/service?method=recording.save&sid={1}&pre_padding={2}&post_padding={3}&recording_id={4}&event_id={5}", baseUrl, Sid,
|
||||
info.PrePaddingSeconds / 60,
|
||||
info.PostPaddingSeconds / 60,
|
||||
info.Id,
|
||||
info.ProgramId)
|
||||
};
|
||||
|
||||
var timerSettings = await GetDefaultScheduleSettings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
timerSettings.scheduleOID = int.Parse(info.Id);
|
||||
timerSettings.post_padding_min = info.PostPaddingSeconds / 60;
|
||||
timerSettings.pre_padding_min = info.PrePaddingSeconds / 60;
|
||||
|
||||
var postContent = _jsonSerializer.SerializeToString(timerSettings);
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPvr] TimerSettings UpdateTimer: {0} for ChannelId: {1} & Name: {2}", postContent, info.ChannelId, info.Name));
|
||||
|
||||
options.RequestContent = postContent;
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
try
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
await _httpClient.Post(options).ConfigureAwait((false));
|
||||
LastRecordingChange = DateTimeOffset.UtcNow;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
LastRecordingChange = DateTimeOffset.UtcNow;
|
||||
_logger.LogError(string.Format("[NextPvr] UpdateTimer Async with exception: {0}", ex.Message));
|
||||
throw new LiveTvConflictException();
|
||||
bool? error = new CancelDeleteRecordingResponse().RecordingError(stream, _jsonSerializer, _logger);
|
||||
if (error == null || error == true)
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPVR] Failed to update the timer with ID: {0}", info.Id));
|
||||
throw new Exception(string.Format("Failed to update the timer with ID: {0}", info.Id));
|
||||
}
|
||||
_logger.LogInformation("[NextPVR] UpdateTimer async for Program ID: {0} ID {1}", info.ProgramId, info.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -638,7 +548,7 @@ namespace NextPvr
|
||||
/// <returns></returns>
|
||||
public async Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation(string.Format("[NextPvr] Start Cancel SeriesRecording Async for recordingId: {0}", timerId));
|
||||
_logger.LogInformation(string.Format("[NextPVR] Start Cancel SeriesRecording Async for recordingId: {0}", timerId));
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
@ -646,45 +556,24 @@ namespace NextPvr
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/CancelRecurr/{1}?sid={2}", baseUrl, timerId, Sid)
|
||||
Url = string.Format("{0}/service?method=recording.recurring.delete&recurring_id={1}&sid={2}", baseUrl, timerId, Sid)
|
||||
};
|
||||
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
bool? error = new CancelDeleteRecordingResponse().RecordingError(stream, _jsonSerializer, _logger);
|
||||
|
||||
if (error == null || error == true)
|
||||
{
|
||||
_logger.LogError(string.Format("[NextPvr] Failed to cancel the recording with recordingId: {0}", timerId));
|
||||
_logger.LogError(string.Format("[NextPVR] Failed to cancel the recording with recordingId: {0}", timerId));
|
||||
throw new Exception(string.Format("Failed to cancel the recording with recordingId: {0}", timerId));
|
||||
}
|
||||
|
||||
_logger.LogInformation("[NextPvr] Cancelled Recording for recordingId: {0}", timerId);
|
||||
_logger.LogInformation("[NextPVR] Cancelled Recording for recordingId: {0}", timerId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the DefaultScheduleSettings
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The CancellationToken</param>
|
||||
/// <returns></returns>
|
||||
private async Task<ScheduleSettings> GetDefaultScheduleSettings(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start GetDefaultScheduleSettings");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/Get/SchedSettingsObj?sid={1}", baseUrl, Sid)
|
||||
};
|
||||
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
return new TimerDefaultsResponse().GetScheduleSettings(stream, _jsonSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
|
||||
{
|
||||
@ -698,13 +587,13 @@ namespace NextPvr
|
||||
|
||||
public async Task<MediaSourceInfo> GetChannelStream(string channelOid, string mediaSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start ChannelStream");
|
||||
_logger.LogInformation("[NextPVR] Start ChannelStream");
|
||||
var config = Plugin.Instance.Configuration;
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
_liveStreams++;
|
||||
|
||||
string streamUrl = string.Format("{0}/live?channeloid={1}&client=Jellyfin.{2}", baseUrl, channelOid, _liveStreams.ToString());
|
||||
_logger.LogInformation("[NextPvr] Streaming " + streamUrl);
|
||||
string streamUrl = string.Format("{0}/live?channeloid={1}&client=jellyfin.{2}", baseUrl, channelOid, _liveStreams.ToString());
|
||||
_logger.LogInformation("[NextPVR] Streaming " + streamUrl);
|
||||
return new MediaSourceInfo
|
||||
{
|
||||
Id = _liveStreams.ToString(CultureInfo.InvariantCulture),
|
||||
@ -734,13 +623,13 @@ namespace NextPvr
|
||||
|
||||
public async Task<MediaSourceInfo> GetRecordingStream(string recordingId, string mediaSourceId, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start GetRecordingStream");
|
||||
_logger.LogInformation("[NextPVR] Start GetRecordingStream");
|
||||
var recordings = await GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
|
||||
var recording = recordings.First(i => string.Equals(i.Id, recordingId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!string.IsNullOrEmpty(recording.Url))
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] RecordingUrl: {0}", recording.Url);
|
||||
_logger.LogInformation("[NextPVR] RecordingUrl: {0}", recording.Url);
|
||||
return new MediaSourceInfo
|
||||
{
|
||||
Path = recording.Url,
|
||||
@ -771,7 +660,7 @@ namespace NextPvr
|
||||
|
||||
if (!string.IsNullOrEmpty(recording.Path) && File.Exists(recording.Path))
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] RecordingPath: {0}", recording.Path);
|
||||
_logger.LogInformation("[NextPVR] RecordingPath: {0}", recording.Path);
|
||||
return new MediaSourceInfo
|
||||
{
|
||||
Path = recording.Path,
|
||||
@ -799,49 +688,56 @@ namespace NextPvr
|
||||
};
|
||||
}
|
||||
|
||||
_logger.LogError("[NextPvr] No stream exists for recording {0}", recording);
|
||||
_logger.LogError("[NextPVR] No stream exists for recording {0}", recording);
|
||||
throw new ResourceNotFoundException(string.Format("No stream exists for recording {0}", recording));
|
||||
}
|
||||
|
||||
public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Closing " + id);
|
||||
_logger.LogInformation("[NextPVR] Closing {0}", id);
|
||||
}
|
||||
|
||||
public async Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start GetNewTimerDefault Async");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions()
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/ScheduleService/Get/SchedSettingsObj?sid{1}", baseUrl, Sid)
|
||||
};
|
||||
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
return new TimerDefaultsResponse().GetDefaultTimerInfo(stream, _jsonSerializer, _logger);
|
||||
}
|
||||
SeriesTimerInfo defaultSettings = new SeriesTimerInfo();
|
||||
defaultSettings.PrePaddingSeconds = Plugin.Instance.Configuration.PrePaddingSeconds;
|
||||
defaultSettings.PostPaddingSeconds = Plugin.Instance.Configuration.PostPaddingSeconds;
|
||||
return defaultSettings;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
private async Task<bool> GetDefaultSettingsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPvr] Start GetPrograms Async, retrieve all Programs");
|
||||
_logger.LogInformation("[NextPVR] Start GetDefaultSettings Async");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions()
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/GuideService/Listing?sid={1}&stime={2}&etime={3}&channelId={4}",
|
||||
Url = string.Format("{0}/service?method=setting.list&sid={1}", baseUrl, Sid)
|
||||
};
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
return new SettingResponse().GetDefaultSettings(stream, _jsonSerializer, _logger);
|
||||
}
|
||||
}
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("[NextPVR] Start GetPrograms Async, retrieve all Programs");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions()
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/service?method=channel.listings&sid={1}&start={2}&end={3}&channel_id={4}",
|
||||
baseUrl, Sid,
|
||||
DateTimeHelper.getUnixUTCTimeFromUtcDateTime(startDateUtc).ToString(_usCulture),
|
||||
DateTimeHelper.getUnixUTCTimeFromUtcDateTime(endDateUtc).ToString(_usCulture),
|
||||
((DateTimeOffset)startDateUtc).ToUnixTimeSeconds(),
|
||||
((DateTimeOffset)endDateUtc).ToUnixTimeSeconds(),
|
||||
channelId)
|
||||
};
|
||||
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
return new ListingsResponse(baseUrl).GetPrograms(stream, _jsonSerializer, channelId, _logger).ToList();
|
||||
@ -866,9 +762,10 @@ namespace NextPvr
|
||||
var options = new HttpRequestOptions()
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/Util/NPVR/VersionCheck?sid={1}", baseUrl, Sid)
|
||||
Url = string.Format("{0}/service?method=setting.version&sid={1}", baseUrl, Sid)
|
||||
};
|
||||
|
||||
options.AcceptHeader = "application/json";
|
||||
bool upgradeAvailable;
|
||||
string serverVersion;
|
||||
|
||||
@ -885,8 +782,9 @@ namespace NextPvr
|
||||
var optionsTuner = new HttpRequestOptions()
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/public/Util/Tuner/Stat?sid={1}", baseUrl, Sid)
|
||||
Url = string.Format("{0}/service/method=system.status?sid={1}", baseUrl, Sid)
|
||||
};
|
||||
options.AcceptHeader = "application/json";
|
||||
|
||||
List<LiveTvTunerInfo> tvTunerInfos;
|
||||
using (var stream = await _httpClient.Get(optionsTuner).ConfigureAwait(false))
|
||||
@ -903,6 +801,77 @@ namespace NextPvr
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<DateTimeOffset> GetLastUpdate(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("[NextPVR] GetLastUpdateTime");
|
||||
DateTimeOffset retTime = DateTimeOffset.FromUnixTimeSeconds(0);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
// don't use sid to avoid fake login
|
||||
Url = string.Format("{0}/service?method=recording.lastupdated&ignore_resume=true&sid={1}", baseUrl, Sid)
|
||||
};
|
||||
options.AcceptHeader = "application/json";
|
||||
options.LogErrorResponseBody = false;
|
||||
try
|
||||
{
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
retTime = new LastUpdateResponse().GetUpdateTime(stream, _jsonSerializer, _logger);
|
||||
if (retTime == DateTimeOffset.FromUnixTimeSeconds(0))
|
||||
{
|
||||
LastUpdatedSidDateTime = DateTimeOffset.MinValue;
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (LastUpdatedSidDateTime != DateTimeOffset.MinValue)
|
||||
{
|
||||
LastUpdatedSidDateTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (MediaBrowser.Model.Net.HttpException httpError)
|
||||
{
|
||||
if (httpError.IsTimedOut)
|
||||
{
|
||||
_logger.LogDebug("timed out");
|
||||
LastUpdatedSidDateTime = DateTimeOffset.MinValue;
|
||||
}
|
||||
}
|
||||
catch (System.Net.Http.HttpRequestException)
|
||||
{
|
||||
_logger.LogDebug("server not running");
|
||||
LastUpdatedSidDateTime = DateTimeOffset.MinValue;
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.LogDebug(err.StackTrace);
|
||||
_logger.LogDebug(err.Message);
|
||||
throw;
|
||||
}
|
||||
_logger.LogInformation("[NextPVR] GetLastUpdateTime " + retTime.ToUnixTimeSeconds());
|
||||
return retTime;
|
||||
}
|
||||
|
||||
public async Task<string> GetBackendSettingAsync(CancellationToken cancellationToken, string key)
|
||||
{
|
||||
_logger.LogInformation("[NextPVR] GetBackendSetting");
|
||||
await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
|
||||
var baseUrl = Plugin.Instance.Configuration.WebServiceUrl;
|
||||
|
||||
var options = new HttpRequestOptions()
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = string.Format("{0}/service?method=setting.get&key={1}&sid={2}", baseUrl, key, Sid)
|
||||
};
|
||||
options.AcceptHeader = "application/json";
|
||||
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
|
||||
{
|
||||
return new SettingResponse().GetSetting(stream, _jsonSerializer, _logger);
|
||||
}
|
||||
}
|
||||
|
||||
public string HomePageUrl
|
||||
{
|
||||
get { return "http://www.nextpvr.com/"; }
|
@ -4,11 +4,11 @@ using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Configuration;
|
||||
using Jellyfin.Plugin.NextPVR.Configuration;
|
||||
using System.IO;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
|
||||
namespace NextPvr
|
||||
namespace Jellyfin.Plugin.NextPVR
|
||||
{
|
||||
/// <summary>
|
||||
/// Class Plugin
|
||||
@ -45,7 +45,7 @@ namespace NextPvr
|
||||
/// <value>The name.</value>
|
||||
public override string Name
|
||||
{
|
||||
get { return "Next Pvr"; }
|
||||
get { return "NextPVR"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
@ -15,16 +15,76 @@ using MediaBrowser.Model.Dto;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace NextPvr
|
||||
namespace Jellyfin.Plugin.NextPVR
|
||||
{
|
||||
public class RecordingsChannel : IChannel, IHasCacheKey, ISupportsDelete, ISupportsLatestMedia, ISupportsMediaProbe, IHasFolderAttributes
|
||||
public class RecordingsChannel : IChannel, IHasCacheKey, ISupportsDelete, ISupportsLatestMedia, ISupportsMediaProbe, IHasFolderAttributes, IDisposable
|
||||
{
|
||||
public ILiveTvManager _liveTvManager;
|
||||
public event EventHandler ContentChanged;
|
||||
private Timer _updateTimer;
|
||||
private DateTimeOffset _lastUpdate = DateTimeOffset.FromUnixTimeSeconds(0);
|
||||
private CancellationTokenSource _cancellationToken;
|
||||
private readonly ILogger<LiveTvService> _logger;
|
||||
|
||||
public RecordingsChannel(ILiveTvManager liveTvManager)
|
||||
IEnumerable<MyRecordingInfo> allRecordings = null;
|
||||
bool useCachedRecordings = false;
|
||||
public virtual void OnContentChanged()
|
||||
{
|
||||
if (ContentChanged != null)
|
||||
{
|
||||
ContentChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
private async void OnUpdateTimerCallbackAsync(object state)
|
||||
{
|
||||
if (GetService().isActive)
|
||||
{
|
||||
DateTimeOffset backendUpate;
|
||||
backendUpate = await GetService().GetLastUpdate(_cancellationToken.Token);
|
||||
if (backendUpate > _lastUpdate)
|
||||
{
|
||||
useCachedRecordings = false;
|
||||
OnContentChanged();
|
||||
_lastUpdate = backendUpate;
|
||||
}
|
||||
else if (backendUpate == DateTimeOffset.FromUnixTimeSeconds(0))
|
||||
{
|
||||
_logger.LogDebug("Server offline");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("No change");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Not active");
|
||||
}
|
||||
}
|
||||
|
||||
public RecordingsChannel(ILiveTvManager liveTvManager, ILogger<LiveTvService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_liveTvManager = liveTvManager;
|
||||
var interval = TimeSpan.FromSeconds(20);
|
||||
_updateTimer = new Timer(OnUpdateTimerCallbackAsync, null, interval, interval);
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
_cancellationToken = new CancellationTokenSource();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
_updateTimer.Dispose();
|
||||
_logger.LogDebug("{0}", _cancellationToken.IsCancellationRequested);
|
||||
_cancellationToken.Cancel();
|
||||
_updateTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
@ -209,7 +269,18 @@ namespace NextPvr
|
||||
public async Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, Func<MyRecordingInfo, bool> filter, CancellationToken cancellationToken)
|
||||
{
|
||||
var service = GetService();
|
||||
var allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (useCachedRecordings == false)
|
||||
{
|
||||
allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
useCachedRecordings = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("using cached recordings");
|
||||
}
|
||||
|
||||
var result = new ChannelItemResult()
|
||||
{
|
||||
@ -236,6 +307,8 @@ namespace NextPvr
|
||||
ImageUrl = item.ImageUrl,
|
||||
//HomePageUrl = item.HomePageUrl
|
||||
Id = item.Id,
|
||||
ParentIndexNumber = item.SeasonNumber,
|
||||
IndexNumber = item.EpisodeNumber,
|
||||
//IndexNumber = item.IndexNumber,
|
||||
MediaType = item.ChannelType == MediaBrowser.Model.LiveTv.ChannelType.TV ? ChannelMediaType.Video : ChannelMediaType.Audio,
|
||||
MediaSources = new List<MediaSourceInfo>
|
||||
@ -244,12 +317,14 @@ namespace NextPvr
|
||||
{
|
||||
Path = path,
|
||||
Protocol = path.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? MediaProtocol.Http : MediaProtocol.File,
|
||||
Id = item.Id
|
||||
Id = item.Id,
|
||||
IsInfiniteStream = item.Status == RecordingStatus.InProgress ? true : false,
|
||||
RunTimeTicks = (item.EndDate - item.StartDate).Ticks,
|
||||
}
|
||||
},
|
||||
//ParentIndexNumber = item.ParentIndexNumber,
|
||||
PremiereDate = item.OriginalAirDate,
|
||||
//ProductionYear = item.ProductionYear,
|
||||
ProductionYear = item.ProductionYear,
|
||||
//Studios = item.Studios,
|
||||
Type = ChannelItemType.Media,
|
||||
DateModified = item.DateLastUpdated,
|
||||
@ -265,8 +340,17 @@ namespace NextPvr
|
||||
private async Task<ChannelItemResult> GetRecordingGroups(InternalChannelItemQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var service = GetService();
|
||||
if (useCachedRecordings == false)
|
||||
{
|
||||
allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
|
||||
var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
|
||||
useCachedRecordings = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("using cached recordings@2");
|
||||
}
|
||||
|
||||
var allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
|
||||
var result = new ChannelItemResult()
|
||||
{
|
||||
Items = new List<ChannelItemInfo>()
|
||||
@ -282,8 +366,9 @@ namespace NextPvr
|
||||
FolderType = ChannelFolderType.Container,
|
||||
Id = "series_" + i.Key.GetMD5().ToString("N"),
|
||||
Type = ChannelItemType.Folder,
|
||||
ImageUrl = i.First().ImageUrl
|
||||
}));
|
||||
DateCreated = i.Last().StartDate,
|
||||
ImageUrl = i.Last().ImageUrl.Replace("=poster", "=landscape")
|
||||
})); ; ; ; ;
|
||||
|
||||
var kids = allRecordings.FirstOrDefault(i => i.IsKids);
|
||||
|
||||
@ -548,6 +633,22 @@ namespace NextPvr
|
||||
/// </summary>
|
||||
/// <value>The date last updated.</value>
|
||||
public DateTime DateLastUpdated { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the season number
|
||||
/// </summary>
|
||||
/// <value>The date last updated.</value>
|
||||
public int? SeasonNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the episode number
|
||||
/// </summary>
|
||||
/// <value>The date last updated.</value>
|
||||
public int? EpisodeNumber { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the Year
|
||||
/// </summary>
|
||||
/// <value>The date last updated.</value>
|
||||
public int? ProductionYear { get; set; }
|
||||
|
||||
public MyRecordingInfo()
|
||||
{
|
32
Jellyfin.Plugin.NextPVR/Responses/CancelRecordingResponse.cs
Normal file
32
Jellyfin.Plugin.NextPVR/Responses/CancelRecordingResponse.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class CancelDeleteRecordingResponse
|
||||
{
|
||||
public bool? RecordingError(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
if (root.stat != "ok")
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger, string.Format("[NextPVR] RecordingError Response: {0}", json.SerializeToString(root)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public class RootObject
|
||||
{
|
||||
public string stat { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
67
Jellyfin.Plugin.NextPVR/Responses/ChannelResponse.cs
Normal file
67
Jellyfin.Plugin.NextPVR/Responses/ChannelResponse.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class ChannelResponse
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public ChannelResponse(string baseUrl)
|
||||
{
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IEnumerable<ChannelInfo> GetChannels(Stream stream, IJsonSerializer json,ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
if (root == null)
|
||||
{
|
||||
logger.LogError("Failed to download channel information.");
|
||||
throw new Exception("Failed to download channel information.");
|
||||
}
|
||||
|
||||
if (root.channels != null)
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger, string.Format("[NextPVR] ChannelResponse: {0}", json.SerializeToString(root)));
|
||||
return root.channels.Select(i => new ChannelInfo
|
||||
{
|
||||
Name = i.channelName,
|
||||
Number = i.channelNumberFormated,
|
||||
Id = i.channelId.ToString(_usCulture),
|
||||
ImageUrl = string.Format("{0}/service?method=channel.icon&channel_id={1}", _baseUrl, i.channelId),
|
||||
ChannelType = ChannelHelper.GetChannelType(i.channelType),
|
||||
HasImage = i.channelIcon
|
||||
});
|
||||
}
|
||||
|
||||
return new List<ChannelInfo>();
|
||||
}
|
||||
// Classes created with http://json2csharp.com/
|
||||
public class Channel
|
||||
{
|
||||
public int channelId { get; set; }
|
||||
public int channelNumber { get; set; }
|
||||
public int channelMinor { get; set; }
|
||||
public string channelNumberFormated { get; set; }
|
||||
public int channelType { get; set; }
|
||||
public string channelName { get; set; }
|
||||
public string channelDetails { get; set; }
|
||||
public bool channelIcon { get; set; }
|
||||
}
|
||||
|
||||
public class RootObject
|
||||
{
|
||||
public List<Channel> channels { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
class DetailsServiceResponse
|
||||
{
|
30
Jellyfin.Plugin.NextPVR/Responses/InitializeResponse.cs
Normal file
30
Jellyfin.Plugin.NextPVR/Responses/InitializeResponse.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class InitializeResponse
|
||||
{
|
||||
public bool LoggedIn(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
if (root.stat != "")
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger, string.Format("[NextPVR] Connection validation: {0}", json.SerializeToString(root)));
|
||||
return root.stat == "ok";
|
||||
}
|
||||
logger.LogError("[NextPVR] Failed to validate your connection with NextPVR.");
|
||||
throw new Exception("Failed to validate your connection with NextPVR.");
|
||||
}
|
||||
|
||||
public class RootObject
|
||||
{
|
||||
public string stat { get; set; }
|
||||
public string sid { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
38
Jellyfin.Plugin.NextPVR/Responses/InstantiateResponse.cs
Normal file
38
Jellyfin.Plugin.NextPVR/Responses/InstantiateResponse.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class InstantiateResponse
|
||||
{
|
||||
public ClientKeys GetClientKeys(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
var root = json.DeserializeFromStream<ClientKeys>(stream);
|
||||
|
||||
if (root.sid != null && root.salt != null)
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger, string.Format("[NextPVR] ClientKeys: {0}", json.SerializeToString(root)));
|
||||
return root;
|
||||
}
|
||||
logger.LogError("[NextPVR] Failed to validate the ClientKeys from NextPVR.");
|
||||
throw new Exception("Failed to load the ClientKeys from NextPVR.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogError("Check NextPVR Version 5");
|
||||
throw new UnauthorizedAccessException("Check NextPVR Version");
|
||||
}
|
||||
}
|
||||
|
||||
public class ClientKeys
|
||||
{
|
||||
public string sid { get; set; }
|
||||
public string salt { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
30
Jellyfin.Plugin.NextPVR/Responses/LastUpdateResponse.cs
Normal file
30
Jellyfin.Plugin.NextPVR/Responses/LastUpdateResponse.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class LastUpdateResponse
|
||||
{
|
||||
public DateTimeOffset GetUpdateTime(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(logger, string.Format("[NextPVR] LastUpdate Response: {0}", json.SerializeToString(root)));
|
||||
return DateTimeOffset.FromUnixTimeSeconds(root.last_update);
|
||||
}
|
||||
}
|
||||
|
||||
// Classes created with http://json2csharp.com/
|
||||
|
||||
public class RootObject
|
||||
{
|
||||
public int last_update { get; set; }
|
||||
public string stat { get; set; }
|
||||
public int code { get; set; }
|
||||
public string msg { get; set; }
|
||||
}
|
||||
}
|
113
Jellyfin.Plugin.NextPVR/Responses/ListingsResponse.cs
Normal file
113
Jellyfin.Plugin.NextPVR/Responses/ListingsResponse.cs
Normal file
@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
class ListingsResponse
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly string _baseUrl;
|
||||
private string _channelId;
|
||||
|
||||
public ListingsResponse(string baseUrl)
|
||||
{
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IEnumerable<ProgramInfo> GetPrograms(Stream stream, IJsonSerializer json, string channelId, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(logger, string.Format("[NextPVR] GetPrograms Response: {0}", json.SerializeToString(root)));
|
||||
|
||||
/*
|
||||
|
||||
return listings.Where(i => string.Equals(i.Channel.channelOID.ToString(_usCulture), channelId, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.EPGEvents.Select(e => GetProgram(i.Channel, e.epgEventJSONObject.epgEvent)));
|
||||
*/
|
||||
_channelId = channelId;
|
||||
return root.listings
|
||||
.Select(i => i)
|
||||
.Select(GetProgram);
|
||||
}
|
||||
|
||||
private ProgramInfo GetProgram(Listing epg)
|
||||
{
|
||||
var genreMapper = new GenreMapper(Plugin.Instance.Configuration);
|
||||
var info = new ProgramInfo
|
||||
{
|
||||
ChannelId = _channelId,
|
||||
Id = epg.id.ToString(),
|
||||
Overview = epg.description,
|
||||
EpisodeTitle = epg.subtitle,
|
||||
SeasonNumber = epg.season,
|
||||
EpisodeNumber = epg.episode,
|
||||
StartDate = DateTimeOffset.FromUnixTimeSeconds(epg.start).DateTime,
|
||||
EndDate = DateTimeOffset.FromUnixTimeSeconds(epg.end).DateTime,
|
||||
Genres = new List<string>(), // epg.genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList(),
|
||||
OriginalAirDate = epg.original == null ? epg.original : DateTime.SpecifyKind((System.DateTime)epg.original, DateTimeKind.Local),
|
||||
ProductionYear = epg.year,
|
||||
Name = epg.name,
|
||||
OfficialRating = epg.rating,
|
||||
IsPremiere = epg.significance != null ? epg.significance.Contains("Premiere") : false,
|
||||
//CommunityRating = ParseCommunityRating(epg.StarRating),
|
||||
//Audio = ParseAudio(epg.Audio),
|
||||
//IsHD = string.Equals(epg.Quality, "hdtv", StringComparison.OrdinalIgnoreCase),
|
||||
IsLive = epg.significance != null ? epg.significance.Contains("Live") : false,
|
||||
IsRepeat = Plugin.Instance.Configuration.ShowRepeat ? !epg.firstrun : true,
|
||||
IsSeries = true, //!string.IsNullOrEmpty(epg.Subtitle), http://emby.media/community/index.php?/topic/21264-series-record-ability-missing-in-emby-epg/#entry239633
|
||||
HasImage = Plugin.Instance.Configuration.GetEpisodeImage,
|
||||
ImageUrl = Plugin.Instance.Configuration.GetEpisodeImage ? string.Format("{0}/service?method=channel.show.artwork&name={1}", _baseUrl, Uri.EscapeDataString(epg.name)) : null,
|
||||
BackdropImageUrl = Plugin.Instance.Configuration.GetEpisodeImage ? string.Format("{0}/service?method=channel.show.artwork&prefer=landscape&name={1}", _baseUrl, Uri.EscapeDataString(epg.name)) : null,
|
||||
};
|
||||
|
||||
if (epg.genres != null)
|
||||
{
|
||||
info.Genres = epg.genres;
|
||||
info.Genres[0] += " Movie";
|
||||
genreMapper.PopulateProgramGenres(info);
|
||||
if (info.IsMovie)
|
||||
{
|
||||
info.IsRepeat = false;
|
||||
info.IsSeries = false;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
// Classes created with http://json2csharp.com/
|
||||
|
||||
public class Listing
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string name { get; set; }
|
||||
public string description { get; set; }
|
||||
public string subtitle { get; set; }
|
||||
public List<string> genres { get; set; }
|
||||
public bool firstrun { get; set; }
|
||||
public int start { get; set; }
|
||||
public int end { get; set; }
|
||||
public string rating { get; set; }
|
||||
public DateTime? original { get; set; }
|
||||
public int? season { get; set; }
|
||||
public int? episode { get; set; }
|
||||
public int? year { get; set; }
|
||||
public string significance { get; set; }
|
||||
public string recording_status { get; set; }
|
||||
public int recording_id { get; set; }
|
||||
}
|
||||
|
||||
public class RootObject
|
||||
{
|
||||
public List<Listing> listings { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
269
Jellyfin.Plugin.NextPVR/Responses/RecordingResponse.cs
Normal file
269
Jellyfin.Plugin.NextPVR/Responses/RecordingResponse.cs
Normal file
@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class RecordingResponse
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly string _baseUrl;
|
||||
private IFileSystem _fileSystem;
|
||||
private readonly ILogger<LiveTvService> _logger;
|
||||
|
||||
public RecordingResponse(string baseUrl, IFileSystem fileSystem, ILogger<LiveTvService> logger)
|
||||
{
|
||||
_baseUrl = baseUrl;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IEnumerable<MyRecordingInfo> GetRecordings(Stream stream, IJsonSerializer json)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
_logger.LogError("[NextPVR] GetRecording stream == null");
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPVR] GetRecordings Response: {0}", json.SerializeToString(root)));
|
||||
|
||||
IEnumerable<MyRecordingInfo> Recordings;
|
||||
try
|
||||
{
|
||||
Recordings = root.recordings
|
||||
.Select(i => i)
|
||||
.Where(i => i.status != "failed" && i.status != "conflict")
|
||||
.Select(GetRecordingInfo);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.LogDebug(err.Message);
|
||||
throw err;
|
||||
}
|
||||
return Recordings;
|
||||
}
|
||||
|
||||
public IEnumerable<TimerInfo> GetTimers(Stream stream, IJsonSerializer json)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
_logger.LogError("[NextPVR] GetTimers stream == null");
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPVR] GetTimers Response: {0}", json.SerializeToString(root)));
|
||||
IEnumerable<TimerInfo> Timers;
|
||||
try
|
||||
{
|
||||
Timers = root.recordings
|
||||
.Select(i => i)
|
||||
.Select(GetTimerInfo);
|
||||
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.LogDebug(err.Message);
|
||||
throw err;
|
||||
}
|
||||
return Timers;
|
||||
}
|
||||
private MyRecordingInfo GetRecordingInfo(Recording i)
|
||||
{
|
||||
var genreMapper = new GenreMapper(Plugin.Instance.Configuration);
|
||||
var info = new MyRecordingInfo();
|
||||
try
|
||||
{
|
||||
info.Id = i.id.ToString(_usCulture);
|
||||
_logger.LogDebug("{0}", i.id);
|
||||
if (i.recurring)
|
||||
{
|
||||
info.SeriesTimerId = i.recurringParent.ToString(_usCulture);
|
||||
}
|
||||
|
||||
if (i.file != null)
|
||||
{
|
||||
info.Url = _baseUrl + "/live?recording=" + i.id;
|
||||
}
|
||||
|
||||
info.Status = ParseStatus(i.status);
|
||||
info.StartDate = DateTimeOffset.FromUnixTimeSeconds(i.startTime).DateTime;
|
||||
info.EndDate = DateTimeOffset.FromUnixTimeSeconds(i.startTime + i.duration).DateTime;
|
||||
|
||||
info.ProgramId = i.epgEventId.ToString(_usCulture);
|
||||
info.EpisodeTitle = i.subtitle;
|
||||
info.Name = i.name;
|
||||
info.Overview = i.desc;
|
||||
info.Genres = i.genres;
|
||||
info.IsRepeat = !i.firstrun;
|
||||
info.ChannelId = i.channelId.ToString(_usCulture);
|
||||
info.ChannelType = ChannelType.TV;
|
||||
info.ImageUrl = _baseUrl + "/service?method=channel.show.artwork&prefer=landscape&name=" + Uri.EscapeDataString(i.name);
|
||||
info.HasImage = true;
|
||||
if (i.season.HasValue)
|
||||
{
|
||||
info.SeasonNumber = i.season;
|
||||
info.EpisodeNumber = i.episode;
|
||||
info.IsSeries = true; //!string.IsNullOrEmpty(epg.Subtitle); http://emby.media/community/index.php?/topic/21264-series-record-ability-missing-in-emby-epg/#entry239633
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug(i.name);
|
||||
}
|
||||
if (i.original != null)
|
||||
{
|
||||
info.OriginalAirDate = i.original;
|
||||
}
|
||||
info.ProductionYear = i.year;
|
||||
//info.CommunityRating = ListingsResponse.ParseCommunityRating(epg.StarRating);
|
||||
//info.IsHD = true;
|
||||
//info.Audio = ProgramAudio.Stereo;
|
||||
|
||||
info.OfficialRating = i.rating;
|
||||
|
||||
if (info.Genres != null)
|
||||
{
|
||||
info.Genres = i.genres;
|
||||
genreMapper.PopulateRecordingGenres(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Genres = new List<string>();
|
||||
}
|
||||
return info;
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.LogDebug(err.Message);
|
||||
throw (err);
|
||||
}
|
||||
}
|
||||
|
||||
private TimerInfo GetTimerInfo(Recording i)
|
||||
{
|
||||
var genreMapper = new GenreMapper(Plugin.Instance.Configuration);
|
||||
var info = new TimerInfo();
|
||||
try
|
||||
{
|
||||
if (i.recurring)
|
||||
{
|
||||
info.SeriesTimerId = i.recurringParent.ToString(_usCulture);
|
||||
info.IsSeries = true; //!string.IsNullOrEmpty(epg.Subtitle); http://emby.media/community/index.php?/topic/21264-series-record-ability-missing-in-emby-epg/#entry239633
|
||||
}
|
||||
info.ChannelId = i.channelId.ToString(_usCulture);
|
||||
info.Id = i.id.ToString(_usCulture);
|
||||
info.Status = ParseStatus(i.status);
|
||||
info.StartDate = DateTimeOffset.FromUnixTimeSeconds(i.startTime).DateTime;
|
||||
info.EndDate = DateTimeOffset.FromUnixTimeSeconds(i.startTime + i.duration).DateTime;
|
||||
info.PrePaddingSeconds = i.prePadding * 60;
|
||||
info.PostPaddingSeconds = i.postPadding * 60;
|
||||
info.ProgramId = i.epgEventId.ToString(_usCulture);
|
||||
info.Name = i.name;
|
||||
info.Overview = i.desc;
|
||||
info.EpisodeTitle = i.subtitle;
|
||||
info.SeasonNumber = i.season;
|
||||
info.EpisodeNumber = i.episode;
|
||||
info.OfficialRating = i.rating;
|
||||
if (i.original != null)
|
||||
{
|
||||
info.OriginalAirDate = i.original;
|
||||
}
|
||||
info.ProductionYear = i.year;
|
||||
|
||||
if (i.genres != null)
|
||||
{
|
||||
info.Genres = i.genres.ToArray();
|
||||
genreMapper.PopulateTimerGenres(info);
|
||||
}
|
||||
|
||||
info.IsRepeat = !i.firstrun;
|
||||
|
||||
//info.CommunityRating = ListingsResponse.ParseCommunityRating(epg.StarRating);
|
||||
return info;
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.LogDebug(err.Message);
|
||||
throw (err);
|
||||
}
|
||||
}
|
||||
|
||||
private RecordingStatus ParseStatus(string value)
|
||||
{
|
||||
if (string.Equals(value, "ready", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.Completed;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "recording", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.InProgress;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "failed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.Error;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "conflict", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.ConflictedNotOk;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "deleted", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.Cancelled;
|
||||
}
|
||||
return RecordingStatus.New;
|
||||
}
|
||||
private class Recording
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string name { get; set; }
|
||||
public string desc { get; set; }
|
||||
public string subtitle { get; set; }
|
||||
public int startTime { get; set; }
|
||||
public int duration { get; set; }
|
||||
public int? season { get; set; }
|
||||
public int? episode { get; set; }
|
||||
public int epgEventId { get; set; }
|
||||
public List<string> genres { get; set; }
|
||||
public string status { get; set; }
|
||||
public string rating { get; set; }
|
||||
public string quality { get; set; }
|
||||
public string channel { get; set; }
|
||||
public int channelId { get; set; }
|
||||
public bool blue { get; set; }
|
||||
public bool green { get; set; }
|
||||
public bool yellow { get; set; }
|
||||
public bool red { get; set; }
|
||||
public int prePadding { get; set; }
|
||||
public int postPadding { get; set; }
|
||||
public string file { get; set; }
|
||||
public int playbackPosition { get; set; }
|
||||
public bool played { get; set; }
|
||||
public bool recurring { get; set; }
|
||||
public int recurringParent { get; set; }
|
||||
public bool firstrun { get; set; }
|
||||
public string reason { get; set; }
|
||||
public string significance { get; set; }
|
||||
public DateTime? original { get; set; }
|
||||
public int? year { get; set; }
|
||||
}
|
||||
|
||||
private class RootObject
|
||||
{
|
||||
public List<Recording> recordings { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
108
Jellyfin.Plugin.NextPVR/Responses/RecurringResponse.cs
Normal file
108
Jellyfin.Plugin.NextPVR/Responses/RecurringResponse.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
class RecurringResponse
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly string _baseUrl;
|
||||
private IFileSystem _fileSystem;
|
||||
private readonly ILogger<LiveTvService> _logger;
|
||||
|
||||
public RecurringResponse(string baseUrl, IFileSystem fileSystem, ILogger<LiveTvService> logger)
|
||||
{
|
||||
_baseUrl = baseUrl;
|
||||
_fileSystem = fileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
public IEnumerable<SeriesTimerInfo> GetSeriesTimers(Stream stream, IJsonSerializer json)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
_logger.LogError("[NextPVR] GetSeriesTimers stream == null");
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(_logger, string.Format("[NextPVR] GetSeriesTimers Response: {0}", json.SerializeToString(root)));
|
||||
return root.recurrings
|
||||
.Select(i => i)
|
||||
.Select(GetSeriesTimerInfo);
|
||||
}
|
||||
private SeriesTimerInfo GetSeriesTimerInfo(Recurring i)
|
||||
{
|
||||
var info = new SeriesTimerInfo();
|
||||
try
|
||||
{
|
||||
info.ChannelId = i.channelID.ToString();
|
||||
|
||||
info.Id = i.id.ToString(_usCulture);
|
||||
|
||||
info.StartDate = DateTimeOffset.FromUnixTimeSeconds(i.startTimeTicks).DateTime;
|
||||
info.EndDate = DateTimeOffset.FromUnixTimeSeconds(i.endTimeTicks).DateTime;
|
||||
|
||||
info.PrePaddingSeconds = i.prePadding * 60;
|
||||
info.PostPaddingSeconds = i.postPadding * 60;
|
||||
|
||||
info.Name = i.name ?? i.epgTitle;
|
||||
info.RecordNewOnly = i.onlyNewEpisodes;
|
||||
if (info.ChannelId == "0")
|
||||
info.RecordAnyChannel = true;
|
||||
|
||||
if (i.days == null)
|
||||
{
|
||||
info.RecordAnyTime = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Days = (i.days ?? string.Empty).Split(':')
|
||||
.Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d.Trim(), true))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
_logger.LogDebug(err.Message);
|
||||
throw (err);
|
||||
}
|
||||
}
|
||||
private class Recurring
|
||||
{
|
||||
public int id { get; set; }
|
||||
public int type { get; set; }
|
||||
public string name { get; set; }
|
||||
public int channelID { get; set; }
|
||||
public string channel { get; set; }
|
||||
public string period { get; set; }
|
||||
public int keep { get; set; }
|
||||
public int prePadding { get; set; }
|
||||
public int postPadding { get; set; }
|
||||
public string epgTitle { get; set; }
|
||||
public string directoryID { get; set; }
|
||||
public string days { get; set; }
|
||||
public bool enabled { get; set; }
|
||||
public bool onlyNewEpisodes { get; set; }
|
||||
public int startTimeTicks { get; set; }
|
||||
public int endTimeTicks { get; set; }
|
||||
public string advancedRules { get; set; }
|
||||
}
|
||||
|
||||
private class RootObject
|
||||
{
|
||||
public List<Recurring> recurrings { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
69
Jellyfin.Plugin.NextPVR/Responses/SettingResponse.cs
Normal file
69
Jellyfin.Plugin.NextPVR/Responses/SettingResponse.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class SettingResponse
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public bool GetDefaultSettings(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
ScheduleSettings root = GetScheduleSettings(stream, json);
|
||||
UtilsHelper.DebugInformation(logger, string.Format("[NextPVR] GetDefaultTimerInfo Response: {0}", json.SerializeToString(root)));
|
||||
Plugin.Instance.Configuration.PostPaddingSeconds = int.Parse(root.postPadding) * 60;
|
||||
Plugin.Instance.Configuration.PrePaddingSeconds = int.Parse(root.prePadding) * 60;
|
||||
Plugin.Instance.Configuration.ShowRepeat = root.showNewInGuide;
|
||||
return true;
|
||||
}
|
||||
|
||||
private ScheduleSettings GetScheduleSettings(Stream stream, IJsonSerializer json)
|
||||
{
|
||||
return json.DeserializeFromStream<ScheduleSettings>(stream);
|
||||
}
|
||||
|
||||
public string GetSetting(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
SettingValue root = json.DeserializeFromStream<SettingValue>(stream);
|
||||
UtilsHelper.DebugInformation(logger, string.Format("[NextPVR] GetSetting Response: {0}", json.SerializeToString(root)));
|
||||
return root.value;
|
||||
}
|
||||
|
||||
// Classes created with http://json2csharp.com/
|
||||
|
||||
private class ScheduleSettings
|
||||
{
|
||||
public string version { get; set; }
|
||||
public string nextPVRVersion { get; set; }
|
||||
public string readableVersion { get; set; }
|
||||
public bool liveTimeshift { get; set; }
|
||||
public bool liveTimeshiftBufferInfo { get; set; }
|
||||
public bool channelsUseSegmenter { get; set; }
|
||||
public bool recordingsUseSegmenter { get; set; }
|
||||
public int whatsNewDays { get; set; }
|
||||
public int skipForwardSeconds { get; set; }
|
||||
public int skipBackSeconds { get; set; }
|
||||
public int skipFFSeconds { get; set; }
|
||||
public int skipRWSeconds { get; set; }
|
||||
public string recordingView { get; set; }
|
||||
public string prePadding { get; set; }
|
||||
public string postPadding { get; set; }
|
||||
public bool confirmOnDelete { get; set; }
|
||||
public bool showNewInGuide { get; set; }
|
||||
public int slipSeconds { get; set; }
|
||||
public string recordingDirectories { get; set; }
|
||||
public bool channelDetailsLevel { get; set; }
|
||||
public string time { get; set; }
|
||||
public int timeEpoch { get; set; }
|
||||
}
|
||||
|
||||
private class SettingValue
|
||||
{
|
||||
public string value { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -3,9 +3,9 @@ using System.IO;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
using Jellyfin.Plugin.NextPVR.Helpers;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class TimerDefaultsResponse
|
||||
{
|
||||
@ -14,7 +14,7 @@ namespace NextPvr.Responses
|
||||
public SeriesTimerInfo GetDefaultTimerInfo(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = GetScheduleSettings(stream, json);
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] GetDefaultTimerInfo Response: {0}", json.SerializeToString(root)));
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPVR] GetDefaultTimerInfo Response: {0}", json.SerializeToString(root)));
|
||||
|
||||
return new SeriesTimerInfo
|
||||
{
|
@ -6,7 +6,7 @@ using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class TunerResponse
|
||||
{
|
@ -2,7 +2,7 @@
|
||||
using System.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
namespace Jellyfin.Plugin.NextPVR.Responses
|
||||
{
|
||||
public class VersionCheckResponse
|
||||
{
|
||||
@ -20,7 +20,7 @@ namespace NextPvr.Responses
|
||||
return _root.versionCheck.upgradeAvailable;
|
||||
}
|
||||
|
||||
throw new Exception("Failed to get the Update Status from NextPvr.");
|
||||
throw new Exception("Failed to get the Update Status from NextPVR.");
|
||||
}
|
||||
|
||||
public string ServerVersion()
|
||||
@ -30,7 +30,7 @@ namespace NextPvr.Responses
|
||||
return _root.versionCheck.serverVer;
|
||||
}
|
||||
|
||||
throw new Exception("Failed to get the Server Version from NextPvr.");
|
||||
throw new Exception("Failed to get the Server Version from NextPVR.");
|
||||
}
|
||||
|
||||
public class VersionCheck
|
@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace NextPvr.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Class PluginConfiguration
|
||||
/// </summary>
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
public string WebServiceUrl { get; set; }
|
||||
|
||||
public string Pin { get; set; }
|
||||
|
||||
public Boolean EnableDebugLogging { get; set; }
|
||||
|
||||
public PluginConfiguration()
|
||||
{
|
||||
Pin = "0000";
|
||||
WebServiceUrl = "http://localhost:8866";
|
||||
EnableDebugLogging = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>NextPVR</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-role="page" class="page type-interior pluginConfigurationPage nextpvrConfigurationPage" data-require="emby-button,emby-input,emby-checkbox">
|
||||
<div data-role="content">
|
||||
<div class="content-primary">
|
||||
<form class="nextpvrConfigurationForm">
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="txtWebServiceUrl" label="NextPVR Base URL" />
|
||||
<div class="fieldDescription">
|
||||
NextPVR URL
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="txtPin" label="NextPVR PIN" />
|
||||
<div class="fieldDescription">
|
||||
NextPVR PIN
|
||||
</div>
|
||||
</div>
|
||||
<label class="checkboxContainer">
|
||||
<input is="emby-checkbox" type="checkbox" id="chkDebugLogging" />
|
||||
<span>NextPVR Debug Logging</span>
|
||||
</label>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var NextPvrConfigurationPage = {
|
||||
pluginUniqueId: "9574ac10-bf23-49bc-949f-924f23cfa48f"
|
||||
};
|
||||
|
||||
$('.nextpvrConfigurationPage').on('pageshow', function (event) {
|
||||
Dashboard.showLoadingMsg();
|
||||
var page = this;
|
||||
|
||||
ApiClient.getPluginConfiguration(NextPvrConfigurationPage.pluginUniqueId).then(function (config) {
|
||||
$('#txtWebServiceUrl', page).val(config.WebServiceUrl || "");
|
||||
$('#txtPin', page).val(config.Pin || "");
|
||||
document.getElementById('chkDebugLogging').checked = config.EnableDebugLogging || false;
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
$('.nextpvrConfigurationForm').on('submit', function (e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
var form = this;
|
||||
|
||||
ApiClient.getPluginConfiguration(NextPvrConfigurationPage.pluginUniqueId).then(function (config) {
|
||||
config.WebServiceUrl = $('#txtWebServiceUrl', form).val();
|
||||
config.Pin = $('#txtPin', form).val();
|
||||
config.EnableDebugLogging = document.getElementById('chkDebugLogging').checked;
|
||||
|
||||
ApiClient.updatePluginConfiguration(NextPvrConfigurationPage.pluginUniqueId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
});
|
||||
|
||||
// Disable default form submission
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NextPvr.Helpers
|
||||
{
|
||||
public class DateTimeHelper
|
||||
{
|
||||
public static long getUnixUTCTimeFromUtcDateTime(DateTime utcTime)
|
||||
{
|
||||
//create Timespan by subtracting the value provided from the Unix Epoch
|
||||
TimeSpan span = (utcTime - new DateTime(1970, 1, 1, 0, 0, 0, 0));
|
||||
|
||||
//return the total seconds (which is a UNIX timestamp)
|
||||
return (long)span.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
{
|
||||
public class CancelDeleteRecordingResponse
|
||||
{
|
||||
public bool? RecordingError(Stream stream, IJsonSerializer json,ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
if (root.epgEventJSONObject != null && root.epgEventJSONObject.rtn != null)
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] RecordingError Response: {0}", json.SerializeToString(root)));
|
||||
return root.epgEventJSONObject.rtn.Error;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public class Rtn
|
||||
{
|
||||
public bool Error { get; set; }
|
||||
public int HTTPStatus { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class EpgEventJSONObject
|
||||
{
|
||||
public Rtn rtn { get; set; }
|
||||
}
|
||||
|
||||
public class RootObject
|
||||
{
|
||||
public EpgEventJSONObject epgEventJSONObject { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
{
|
||||
public class ChannelResponse
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public ChannelResponse(string baseUrl)
|
||||
{
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IEnumerable<ChannelInfo> GetChannels(Stream stream, IJsonSerializer json,ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
if (root.channelsJSONObject.rtn != null && root.channelsJSONObject.rtn.Error)
|
||||
{
|
||||
logger.LogError(root.channelsJSONObject.rtn.Message ?? "Failed to download channel information.");
|
||||
throw new Exception(root.channelsJSONObject.rtn.Message ?? "Failed to download channel information.");
|
||||
}
|
||||
|
||||
if (root.channelsJSONObject != null && root.channelsJSONObject.Channels != null)
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] ChannelResponse: {0}", json.SerializeToString(root)));
|
||||
return root.channelsJSONObject.Channels.Select(i => new ChannelInfo
|
||||
{
|
||||
Name = i.channel.channelName,
|
||||
Number = i.channel.channelFormattedNumber,
|
||||
Id = i.channel.channelOID.ToString(_usCulture),
|
||||
ImageUrl = string.IsNullOrEmpty(i.channel.channelIcon) ? null : (_baseUrl + "/" + i.channel.channelIcon),
|
||||
HasImage = !string.IsNullOrEmpty(i.channel.channelIcon)
|
||||
});
|
||||
}
|
||||
|
||||
return new List<ChannelInfo>();
|
||||
}
|
||||
|
||||
// Classes created with http://json2csharp.com/
|
||||
public class Channel2
|
||||
{
|
||||
public int channelNum { get; set; }
|
||||
public int channelMinor { get; set; }
|
||||
public string channelFormattedNumber { get; set; }
|
||||
public string channelName { get; set; }
|
||||
public int channelOID { get; set; }
|
||||
public string channelIcon { get; set; }
|
||||
}
|
||||
|
||||
public class Channel
|
||||
{
|
||||
public Channel2 channel { get; set; }
|
||||
}
|
||||
|
||||
public class Rtn
|
||||
{
|
||||
public bool Error { get; set; }
|
||||
public int HTTPStatus { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class ChannelsJSONObject
|
||||
{
|
||||
public List<Channel> Channels { get; set; }
|
||||
public Rtn rtn { get; set; }
|
||||
}
|
||||
|
||||
public class RootObject
|
||||
{
|
||||
public ChannelsJSONObject channelsJSONObject { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
{
|
||||
public class InitializeResponse
|
||||
{
|
||||
public bool LoggedIn(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
if (root.SIDValidation != null)
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] Connection validation: {0}", json.SerializeToString(root)));
|
||||
return root.SIDValidation.validated;
|
||||
}
|
||||
|
||||
logger.LogError("[NextPvr] Failed to validate your connection with NextPvr.");
|
||||
throw new Exception("Failed to validate your connection with NextPvr.");
|
||||
}
|
||||
|
||||
public class SIDValidation
|
||||
{
|
||||
public bool validated { get; set; }
|
||||
}
|
||||
|
||||
public class RootObject
|
||||
{
|
||||
public SIDValidation SIDValidation { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
{
|
||||
public class InstantiateResponse
|
||||
{
|
||||
public ClientKeys GetClientKeys(Stream stream, IJsonSerializer json,ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
if (root.clientKeys != null && root.clientKeys.sid != null && root.clientKeys.salt != null)
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] ClientKeys: {0}", json.SerializeToString(root)));
|
||||
return root.clientKeys;
|
||||
}
|
||||
|
||||
logger.LogError("[NextPvr] Failed to load the ClientKeys from NextPvr.");
|
||||
throw new Exception("Failed to load the ClientKeys from NextPvr.");
|
||||
}
|
||||
|
||||
public class ClientKeys
|
||||
{
|
||||
public string sid { get; set; }
|
||||
public string salt { get; set; }
|
||||
}
|
||||
|
||||
private class RootObject
|
||||
{
|
||||
public ClientKeys clientKeys { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
{
|
||||
class ListingsResponse
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public ListingsResponse(string baseUrl)
|
||||
{
|
||||
_baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public IEnumerable<ProgramInfo> GetPrograms(Stream stream, IJsonSerializer json, string channelId, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] GetPrograms Response: {0}",json.SerializeToString(root)));
|
||||
|
||||
var listings = root.Guide.Listings;
|
||||
|
||||
return listings.Where(i => string.Equals(i.Channel.channelOID.ToString(_usCulture), channelId, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.EPGEvents.Select(e => GetProgram(i.Channel, e.epgEventJSONObject.epgEvent)));
|
||||
}
|
||||
|
||||
private ProgramInfo GetProgram(Channel channel, EpgEvent2 epg)
|
||||
{
|
||||
var info = new ProgramInfo
|
||||
{
|
||||
ChannelId = channel.channelOID.ToString(_usCulture),
|
||||
Id = epg.OID.ToString(_usCulture),
|
||||
Overview = epg.Desc,
|
||||
StartDate = DateTime.Parse(epg.StartTime).ToUniversalTime(),
|
||||
EndDate = DateTime.Parse(epg.EndTime).ToUniversalTime(),
|
||||
Genres = epg.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList(),
|
||||
OriginalAirDate = DateTime.Parse(epg.OriginalAirdate).ToUniversalTime(),
|
||||
Name = epg.Title,
|
||||
OfficialRating = epg.Rating,
|
||||
CommunityRating = ParseCommunityRating(epg.StarRating),
|
||||
EpisodeTitle = epg.Subtitle,
|
||||
Audio = ParseAudio(epg.Audio),
|
||||
IsHD = string.Equals(epg.Quality, "hdtv", StringComparison.OrdinalIgnoreCase),
|
||||
IsRepeat = !epg.FirstRun,
|
||||
IsSeries = true, //!string.IsNullOrEmpty(epg.Subtitle), http://emby.media/community/index.php?/topic/21264-series-record-ability-missing-in-emby-epg/#entry239633
|
||||
ImageUrl = string.IsNullOrEmpty(epg.FanArt) ? null : (_baseUrl + "/" + epg.FanArt),
|
||||
HasImage = !string.IsNullOrEmpty(epg.FanArt),
|
||||
IsNews = epg.Genres.Contains("news", StringComparer.OrdinalIgnoreCase),
|
||||
IsMovie = epg.Genres.Contains("movie", StringComparer.OrdinalIgnoreCase),
|
||||
IsKids = epg.Genres.Contains("kids", StringComparer.OrdinalIgnoreCase),
|
||||
|
||||
IsSports = epg.Genres.Contains("sports", StringComparer.OrdinalIgnoreCase) ||
|
||||
epg.Genres.Contains("Sports non-event", StringComparer.OrdinalIgnoreCase) ||
|
||||
epg.Genres.Contains("Sports event", StringComparer.OrdinalIgnoreCase) ||
|
||||
epg.Genres.Contains("Sports talk", StringComparer.OrdinalIgnoreCase) ||
|
||||
epg.Genres.Contains("Sports news", StringComparer.OrdinalIgnoreCase)
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static float? ParseCommunityRating(string value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
var hasPlus = value.IndexOf('+') != -1;
|
||||
|
||||
var rating = value.Replace("+", string.Empty).Length + (hasPlus ? .5 : 0);
|
||||
|
||||
return (float)rating;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ProgramAudio? ParseAudio(string value)
|
||||
{
|
||||
if (string.Equals(value, "stereo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ProgramAudio.Stereo;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Classes created with http://json2csharp.com/
|
||||
|
||||
private class Channel
|
||||
{
|
||||
public int channelOID { get; set; }
|
||||
public int channelNumber { get; set; }
|
||||
public string channelName { get; set; }
|
||||
}
|
||||
|
||||
private class EpgEvent2
|
||||
{
|
||||
public int OID { get; set; }
|
||||
public string UniqueId { get; set; }
|
||||
public int ChannelOid { get; set; }
|
||||
public string StartTime { get; set; }
|
||||
public string EndTime { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Subtitle { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public string Rating { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public string StarRating { get; set; }
|
||||
public string Aspect { get; set; }
|
||||
public string Audio { get; set; }
|
||||
public string OriginalAirdate { get; set; }
|
||||
public string FanArt { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public bool FirstRun { get; set; }
|
||||
public bool HasSchedule { get; set; }
|
||||
public bool ScheduleIsRecurring { get; set; }
|
||||
}
|
||||
|
||||
private class Rtn
|
||||
{
|
||||
public bool Error { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
private class EpgEventJSONObject
|
||||
{
|
||||
public EpgEvent2 epgEvent { get; set; }
|
||||
public Rtn rtn { get; set; }
|
||||
}
|
||||
|
||||
private class EPGEvent
|
||||
{
|
||||
public EpgEventJSONObject epgEventJSONObject { get; set; }
|
||||
}
|
||||
|
||||
private class Listing
|
||||
{
|
||||
public Channel Channel { get; set; }
|
||||
public List<EPGEvent> EPGEvents { get; set; }
|
||||
}
|
||||
|
||||
private class Guide
|
||||
{
|
||||
public List<Listing> Listings { get; set; }
|
||||
}
|
||||
|
||||
private class RootObject
|
||||
{
|
||||
public Guide Guide { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,424 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
{
|
||||
public class RecordingResponse
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly string _baseUrl;
|
||||
private IFileSystem _fileSystem;
|
||||
|
||||
public RecordingResponse(string baseUrl, IFileSystem fileSystem)
|
||||
{
|
||||
_baseUrl = baseUrl;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public IEnumerable<MyRecordingInfo> GetRecordings(Stream stream, IJsonSerializer json,ILogger<LiveTvService> logger)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
logger.LogError("[NextPvr] GetRecording stream == null");
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] GetRecordings Response: {0}", json.SerializeToString(root)));
|
||||
|
||||
return root.ManageResults
|
||||
.EPGEvents
|
||||
.Select(i => i.epgEventJSONObject)
|
||||
|
||||
// Seeing epgEventJSONObject coming back null for some responses
|
||||
.Where(i => i != null)
|
||||
|
||||
// Seeing recurring parents coming back with these reponses, for some reason
|
||||
.Where(i => i.schd != null)
|
||||
.Select(GetRecordingInfo);
|
||||
}
|
||||
|
||||
public IEnumerable<TimerInfo> GetTimers(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
logger.LogError("[NextPvr] GetTimers stream == null");
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] GetTimers Response: {0}", json.SerializeToString(root)));
|
||||
|
||||
return root.ManageResults
|
||||
.EPGEvents
|
||||
.Select(i => i.epgEventJSONObject)
|
||||
|
||||
// Seeing epgEventJSONObject coming back null for some responses
|
||||
.Where(i => i != null)
|
||||
|
||||
// Seeing recurring parents coming back with these reponses, for some reason
|
||||
.Where(i => i.schd != null)
|
||||
.Select(GetTimerInfo);
|
||||
}
|
||||
|
||||
public IEnumerable<SeriesTimerInfo> GetSeriesTimers(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
logger.LogError("[NextPvr] GetSeriesTimers stream == null");
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] GetSeriesTimers Response: {0}", json.SerializeToString(root)));
|
||||
|
||||
return root.ManageResults
|
||||
.EPGEvents
|
||||
.Select(i => i.epgEventJSONObject)
|
||||
|
||||
// Seeing epgEventJSONObject coming back null for some responses
|
||||
.Where(i => i != null)
|
||||
|
||||
// Seeing recurring parents coming back with these reponses, for some reason
|
||||
.Where(i => i.recurr != null)
|
||||
.Select(GetSeriesTimerInfo);
|
||||
}
|
||||
|
||||
private MyRecordingInfo GetRecordingInfo(EpgEventJSONObject i)
|
||||
{
|
||||
var info = new MyRecordingInfo();
|
||||
|
||||
var recurr = i.recurr;
|
||||
if (recurr != null)
|
||||
{
|
||||
if (recurr.OID != 0)
|
||||
{
|
||||
info.SeriesTimerId = recurr.OID.ToString(_usCulture);
|
||||
}
|
||||
}
|
||||
|
||||
var schd = i.schd;
|
||||
|
||||
if (schd != null)
|
||||
{
|
||||
info.ChannelId = schd.ChannelOid.ToString(_usCulture);
|
||||
info.Id = schd.OID.ToString(_usCulture);
|
||||
if (File.Exists(schd.RecordingFileName))
|
||||
{
|
||||
info.Path = schd.RecordingFileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Url = _baseUrl + "/live?recording=" + schd.OID;
|
||||
}
|
||||
|
||||
info.Status = ParseStatus(schd.Status);
|
||||
info.StartDate = DateTime.Parse(schd.StartTime).ToUniversalTime();
|
||||
info.EndDate = DateTime.Parse(schd.EndTime).ToUniversalTime();
|
||||
|
||||
info.IsHD = string.Equals(schd.Quality, "hdtv", StringComparison.OrdinalIgnoreCase);
|
||||
info.ImageUrl = string.IsNullOrEmpty(schd.FanArt) ? null : (_baseUrl + "/" + schd.FanArt);
|
||||
info.HasImage = !string.IsNullOrEmpty(schd.FanArt);
|
||||
}
|
||||
|
||||
var epg = i.epgEvent;
|
||||
|
||||
if (epg != null)
|
||||
{
|
||||
info.Audio = ListingsResponse.ParseAudio(epg.Audio);
|
||||
info.ProgramId = epg.OID.ToString(_usCulture);
|
||||
info.OfficialRating = epg.Rating;
|
||||
info.EpisodeTitle = epg.Subtitle;
|
||||
info.Name = epg.Title;
|
||||
info.Overview = epg.Desc;
|
||||
info.Genres = epg.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
|
||||
info.IsRepeat = !epg.FirstRun;
|
||||
info.IsSeries = true; //!string.IsNullOrEmpty(epg.Subtitle); http://emby.media/community/index.php?/topic/21264-series-record-ability-missing-in-emby-epg/#entry239633
|
||||
info.CommunityRating = ListingsResponse.ParseCommunityRating(epg.StarRating);
|
||||
info.IsHD = string.Equals(epg.Quality, "hdtv", StringComparison.OrdinalIgnoreCase);
|
||||
info.IsNews = epg.Genres.Contains("news", StringComparer.OrdinalIgnoreCase);
|
||||
info.IsMovie = epg.Genres.Contains("movie", StringComparer.OrdinalIgnoreCase);
|
||||
info.IsKids = epg.Genres.Contains("kids", StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
info.IsSports = epg.Genres.Contains("sports", StringComparer.OrdinalIgnoreCase) ||
|
||||
epg.Genres.Contains("Sports non-event", StringComparer.OrdinalIgnoreCase) ||
|
||||
epg.Genres.Contains("Sports event", StringComparer.OrdinalIgnoreCase) ||
|
||||
epg.Genres.Contains("Sports talk", StringComparer.OrdinalIgnoreCase) ||
|
||||
epg.Genres.Contains("Sports news", StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private TimerInfo GetTimerInfo(EpgEventJSONObject i)
|
||||
{
|
||||
var info = new TimerInfo();
|
||||
|
||||
var recurr = i.recurr;
|
||||
if (recurr != null)
|
||||
{
|
||||
if (recurr.OID != 0)
|
||||
{
|
||||
info.SeriesTimerId = recurr.OID.ToString(_usCulture);
|
||||
}
|
||||
|
||||
info.Name = recurr.RecurringName;
|
||||
}
|
||||
|
||||
var schd = i.schd;
|
||||
|
||||
if (schd != null)
|
||||
{
|
||||
info.ChannelId = schd.ChannelOid.ToString(_usCulture);
|
||||
info.Id = schd.OID.ToString(_usCulture);
|
||||
info.Status = ParseStatus(schd.Status);
|
||||
info.StartDate = DateTime.Parse(schd.StartTime).ToUniversalTime();
|
||||
info.EndDate = DateTime.Parse(schd.EndTime).ToUniversalTime();
|
||||
|
||||
info.PrePaddingSeconds = int.Parse(schd.PrePadding, _usCulture) * 60;
|
||||
info.PostPaddingSeconds = int.Parse(schd.PostPadding, _usCulture) * 60;
|
||||
|
||||
info.Name = schd.Name;
|
||||
}
|
||||
|
||||
var epg = i.epgEvent;
|
||||
|
||||
if (epg != null)
|
||||
{
|
||||
//info.Audio = ListingsResponse.ParseAudio(epg.Audio);
|
||||
info.ProgramId = epg.OID.ToString(_usCulture);
|
||||
//info.OfficialRating = epg.Rating;
|
||||
//info.EpisodeTitle = epg.Subtitle;
|
||||
info.Name = epg.Title;
|
||||
info.Overview = epg.Desc;
|
||||
//info.Genres = epg.Genres;
|
||||
//info.IsRepeat = !epg.FirstRun;
|
||||
//info.CommunityRating = ListingsResponse.ParseCommunityRating(epg.StarRating);
|
||||
//info.IsHD = string.Equals(epg.Quality, "hdtv", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private SeriesTimerInfo GetSeriesTimerInfo(EpgEventJSONObject i)
|
||||
{
|
||||
var info = new SeriesTimerInfo();
|
||||
|
||||
var recurr = i.recurr;
|
||||
if (recurr != null)
|
||||
{
|
||||
info.ChannelId = GetChannelId(recurr);
|
||||
|
||||
info.Id = recurr.OID.ToString(_usCulture);
|
||||
|
||||
info.StartDate = DateTime.Parse(recurr.StartTime).ToUniversalTime();
|
||||
info.EndDate = DateTime.Parse(recurr.EndTime).ToUniversalTime();
|
||||
|
||||
info.PrePaddingSeconds = int.Parse(recurr.PrePadding, _usCulture) * 60;
|
||||
info.PostPaddingSeconds = int.Parse(recurr.PostPadding, _usCulture) * 60;
|
||||
|
||||
info.Name = recurr.RecurringName ?? recurr.EPGTitle;
|
||||
info.RecordNewOnly = recurr.OnlyNew;
|
||||
info.RecordAnyChannel = recurr.allChannels;
|
||||
|
||||
info.Days = (recurr.Day ?? string.Empty).Split(',')
|
||||
.Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d.Trim(), true))
|
||||
.ToList();
|
||||
|
||||
info.Priority = recurr.Priority;
|
||||
}
|
||||
|
||||
var epg = i.epgEvent;
|
||||
|
||||
if (epg != null)
|
||||
{
|
||||
info.ProgramId = epg.OID.ToString(_usCulture);
|
||||
info.Overview = epg.Desc;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private string GetChannelName(Recurr recurr)
|
||||
{
|
||||
if (recurr.RulesXmlDoc != null && recurr.RulesXmlDoc.Rules != null)
|
||||
{
|
||||
return string.Equals(recurr.RulesXmlDoc.Rules.ChannelName, "All Channels", StringComparison.OrdinalIgnoreCase) ? null : recurr.RulesXmlDoc.Rules.ChannelName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetChannelId(Recurr recurr)
|
||||
{
|
||||
if (recurr.RulesXmlDoc != null && recurr.RulesXmlDoc.Rules != null)
|
||||
{
|
||||
return string.Equals(recurr.RulesXmlDoc.Rules.ChannelOID, "0", StringComparison.OrdinalIgnoreCase) ? null : recurr.RulesXmlDoc.Rules.ChannelOID;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RecordingStatus ParseStatus(string value)
|
||||
{
|
||||
if (string.Equals(value, "Completed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.Completed;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "In-Progress", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.InProgress;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "Failed", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.Error;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "Conflict", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.ConflictedNotOk;
|
||||
}
|
||||
|
||||
if (string.Equals(value, "Deleted", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RecordingStatus.Cancelled;
|
||||
}
|
||||
|
||||
return RecordingStatus.New;
|
||||
}
|
||||
|
||||
private class Rules
|
||||
{
|
||||
public string ChannelOID { get; set; }
|
||||
public string ChannelName { get; set; }
|
||||
public string StartTime { get; set; }
|
||||
public string EndTime { get; set; }
|
||||
public string PrePadding { get; set; }
|
||||
public string PostPadding { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public string Keep { get; set; }
|
||||
public string Days { get; set; }
|
||||
public string EPGTitle { get; set; }
|
||||
}
|
||||
|
||||
private class RulesXmlDoc
|
||||
{
|
||||
public Rules Rules { get; set; }
|
||||
}
|
||||
|
||||
private class Recurr
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public int OID { get; set; }
|
||||
public string RecurringName { get; set; }
|
||||
public string PeriodDescription { get; set; }
|
||||
public string EPGTitle { get; set; }
|
||||
public int ChannelOid { get; set; }
|
||||
public string StartTime { get; set; }
|
||||
public string EndTime { get; set; }
|
||||
public object RecordingDirectoryID { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public string PrePadding { get; set; }
|
||||
public string PostPadding { get; set; }
|
||||
public string MaxRecordings { get; set; }
|
||||
public bool allChannels { get; set; }
|
||||
public bool OnlyNew { get; set; }
|
||||
public string Day { get; set; }
|
||||
public object AdvancedRules { get; set; }
|
||||
public RulesXmlDoc RulesXmlDoc { get; set; }
|
||||
}
|
||||
|
||||
private class Rtn
|
||||
{
|
||||
public bool Error { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
private class EpgEvent2
|
||||
{
|
||||
public int OID { get; set; }
|
||||
public string UniqueId { get; set; }
|
||||
public int ChannelOid { get; set; }
|
||||
public string StartTime { get; set; }
|
||||
public string EndTime { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Subtitle { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public string Rating { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public string StarRating { get; set; }
|
||||
public string Aspect { get; set; }
|
||||
public string Audio { get; set; }
|
||||
public string OriginalAirdate { get; set; }
|
||||
public string FanArt { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public bool FirstRun { get; set; }
|
||||
public bool HasSchedule { get; set; }
|
||||
public bool ScheduleIsRecurring { get; set; }
|
||||
}
|
||||
|
||||
private class Schd
|
||||
{
|
||||
public int OID { get; set; }
|
||||
public int ChannelOid { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Day { get; set; }
|
||||
public string StartTime { get; set; }
|
||||
public string EndTime { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string FailureReason { get; set; }
|
||||
public string PrePadding { get; set; }
|
||||
public string PostPadding { get; set; }
|
||||
public string MaxRecordings { get; set; }
|
||||
public string DownloadURL { get; set; }
|
||||
public string RecordingFileName { get; set; }
|
||||
public int PlaybackPosition { get; set; }
|
||||
public int PlaybackDuration { get; set; }
|
||||
public string LastWatched { get; set; }
|
||||
public bool OnlyNew { get; set; }
|
||||
public bool Blue { get; set; }
|
||||
public bool Green { get; set; }
|
||||
public bool Red { get; set; }
|
||||
public bool Yellow { get; set; }
|
||||
public string FanArt { get; set; }
|
||||
}
|
||||
|
||||
private class EpgEventJSONObject
|
||||
{
|
||||
public Recurr recurr { get; set; }
|
||||
public Rtn rtn { get; set; }
|
||||
public EpgEvent2 epgEvent { get; set; }
|
||||
public Schd schd { get; set; }
|
||||
}
|
||||
|
||||
private class EPGEvent
|
||||
{
|
||||
public EpgEventJSONObject epgEventJSONObject { get; set; }
|
||||
}
|
||||
|
||||
private class ManageResults
|
||||
{
|
||||
public List<EPGEvent> EPGEvents { get; set; }
|
||||
}
|
||||
|
||||
private class RootObject
|
||||
{
|
||||
public ManageResults ManageResults { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using NextPvr.Helpers;
|
||||
|
||||
namespace NextPvr.Responses
|
||||
{
|
||||
public class VLCResponse
|
||||
{
|
||||
public VLCObj GetVLCResponse(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
|
||||
if (root.JSONVlcObject.VLC_Obj != null && root.JSONVlcObject.VLC_Obj.isVlcAvailable == true)
|
||||
{
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] VLC Response: {0}", json.SerializeToString(root)));
|
||||
return root.JSONVlcObject.VLC_Obj;
|
||||
}
|
||||
logger.LogError("[NextPvr] Failed to load the VLC from NEWA");
|
||||
throw new Exception("Failed to load the VLC from NEWA.");
|
||||
}
|
||||
|
||||
public Rtn GetVLCReturn(Stream stream, IJsonSerializer json, ILogger<LiveTvService> logger)
|
||||
{
|
||||
var root = json.DeserializeFromStream<RootObject>(stream);
|
||||
UtilsHelper.DebugInformation(logger,string.Format("[NextPvr] VLC Return: {0}", json.SerializeToString(root)));
|
||||
return root.JSONVlcObject.rtn;
|
||||
}
|
||||
|
||||
public class VLCObj
|
||||
{
|
||||
public bool isVlcAvailable { get; set; }
|
||||
public string StreamLocation { get; set; }
|
||||
public int ProcessId { get; set; }
|
||||
}
|
||||
|
||||
public class Rtn
|
||||
{
|
||||
public bool Error { get; set; }
|
||||
public int HTTPStatus { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
private class JSONVlcObject
|
||||
{
|
||||
public VLCObj VLC_Obj { get; set; }
|
||||
public Rtn rtn { get; set; }
|
||||
}
|
||||
|
||||
private class RootObject
|
||||
{
|
||||
public JSONVlcObject JSONVlcObject { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
---
|
||||
name: "NextPVR"
|
||||
guid: "9574ac10-bf23-49bc-949f-924f23cfa48f"
|
||||
version: "4.0.0.0"
|
||||
targetAbi: "10.6.0.0"
|
||||
version: "5.0.0.0"
|
||||
targetAbi: "10.7.0.0"
|
||||
overview: "Live TV plugin for NextPVR"
|
||||
description: >
|
||||
Provides access to live TV, program guide, and recordings from NextPVR.
|
||||
category: "LiveTV"
|
||||
owner: "jellyfin"
|
||||
artifacts:
|
||||
- "NextPvr.dll"
|
||||
- "Jellyfin.Plugin.NextPVR.dll"
|
||||
changelog: >
|
||||
changelog
|
||||
Updated to use NextPVR API v5, no longer compatable with API v4.
|
||||
|
Loading…
Reference in New Issue
Block a user