Merge pull request #12 from emveepee/upgrade

Rewrite for NextPVR API v5
This commit is contained in:
Odd Stråbø 2020-11-10 19:52:36 +01:00 committed by GitHub
commit 4f4d198280
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1585 additions and 1290 deletions

View File

@ -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

View 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" };
}
}
}

View 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>

View 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
}
}

View File

@ -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;
}

View 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));
}
}
}
}
}

View File

@ -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>

View File

@ -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 += "&timeslot=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/"; }

View File

@ -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>

View File

@ -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()
{

View 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; }
}
}
}

View 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; }
}
}
}

View File

@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NextPvr.Responses
namespace Jellyfin.Plugin.NextPVR.Responses
{
class DetailsServiceResponse
{

View 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; }
}
}
}

View 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; }
}
}
}

View 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; }
}
}

View 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; }
}
}
}

View 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; }
}
}
}

View 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; }
}
}
}

View 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; }
}
}
}

View File

@ -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
{

View File

@ -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
{

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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.