mirror of
https://github.com/jellyfin/jellyfin-plugin-tvheadend.git
synced 2024-11-23 05:39:39 +00:00
initial
This commit is contained in:
parent
fd28e14ba4
commit
81c413d92d
22
.gitattributes
vendored
Normal file
22
.gitattributes
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
230
.gitignore
vendored
Normal file
230
.gitignore
vendored
Normal file
@ -0,0 +1,230 @@
|
||||
#################
|
||||
## Eclipse
|
||||
#################
|
||||
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
#################
|
||||
## Media Browser
|
||||
#################
|
||||
ProgramData*/
|
||||
ProgramData-Server*/
|
||||
ProgramData-UI*/
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
.vs
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
build/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.log
|
||||
*.scc
|
||||
*.scc
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.orig
|
||||
*.rej
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.ipch
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
*.pubxml
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
packages/
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
*.publishsettings
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
#############
|
||||
## Windows detritus
|
||||
#############
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Mac crap
|
||||
.DS_Store
|
||||
|
||||
|
||||
#############
|
||||
## Python
|
||||
#############
|
||||
|
||||
*.py[co]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist/
|
||||
build/
|
||||
eggs/
|
||||
parts/
|
||||
var/
|
||||
sdist/
|
||||
develop-eggs/
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
6
.nuget/NuGet.Config
Normal file
6
.nuget/NuGet.Config
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<solution>
|
||||
<add key="disableSourceControlIntegration" value="true" />
|
||||
</solution>
|
||||
</configuration>
|
BIN
.nuget/NuGet.exe
Normal file
BIN
.nuget/NuGet.exe
Normal file
Binary file not shown.
153
.nuget/NuGet.targets
Normal file
153
.nuget/NuGet.targets
Normal file
@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
|
||||
|
||||
<!-- Enable the restore command to run before builds -->
|
||||
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages>
|
||||
|
||||
<!-- Property that enables building a package from a project -->
|
||||
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
|
||||
|
||||
<!-- Determines if package restore consent is required to restore packages -->
|
||||
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
|
||||
|
||||
<!-- Download NuGet.exe if it does not already exist -->
|
||||
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(PackageSources)' == '' ">
|
||||
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used -->
|
||||
<!-- The official NuGet package source (https://nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list -->
|
||||
<!--
|
||||
<PackageSource Include="https://nuget.org/api/v2/" />
|
||||
<PackageSource Include="https://my-nuget-source/nuget/" />
|
||||
-->
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
|
||||
<!-- Windows specific commands -->
|
||||
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
|
||||
<PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
|
||||
<PackagesDir>$([System.IO.Path]::Combine($(SolutionDir), "packages"))</PackagesDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
|
||||
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
|
||||
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
|
||||
<PackagesConfig>packages.config</PackagesConfig>
|
||||
<PackagesDir>$(SolutionDir)packages</PackagesDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- NuGet command -->
|
||||
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\nuget.exe</NuGetExePath>
|
||||
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
|
||||
|
||||
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
|
||||
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand>
|
||||
|
||||
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
|
||||
|
||||
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
|
||||
<!-- Commands -->
|
||||
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -o "$(PackagesDir)"</RestoreCommand>
|
||||
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols</BuildCommand>
|
||||
|
||||
<!-- We need to ensure packages are restored prior to assembly resolve -->
|
||||
<ResolveReferencesDependsOn Condition="$(RestorePackages) == 'true'">
|
||||
RestorePackages;
|
||||
$(ResolveReferencesDependsOn);
|
||||
</ResolveReferencesDependsOn>
|
||||
|
||||
<!-- Make the build depend on restore packages -->
|
||||
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
|
||||
$(BuildDependsOn);
|
||||
BuildPackage;
|
||||
</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CheckPrerequisites">
|
||||
<!-- Raise an error if we're unable to locate nuget.exe -->
|
||||
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
|
||||
<SetEnvironmentVariable EnvKey="VisualStudioVersion" EnvValue="$(VisualStudioVersion)" Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' " />
|
||||
<!--
|
||||
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
|
||||
This effectively acts as a lock that makes sure that the download operation will only happen once and all
|
||||
parallel builds will have to wait for it to complete.
|
||||
-->
|
||||
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_DownloadNuGet">
|
||||
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(BuildCommand)"
|
||||
Condition=" '$(OS)' != 'Windows_NT' " />
|
||||
|
||||
<Exec Command="$(BuildCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition=" '$(OS)' == 'Windows_NT' " />
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<OutputFilename ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Reference Include="System.Core" />
|
||||
<Using Namespace="System" />
|
||||
<Using Namespace="System.IO" />
|
||||
<Using Namespace="System.Net" />
|
||||
<Using Namespace="Microsoft.Build.Framework" />
|
||||
<Using Namespace="Microsoft.Build.Utilities" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try {
|
||||
OutputFilename = Path.GetFullPath(OutputFilename);
|
||||
|
||||
Log.LogMessage("Downloading latest version of NuGet.exe...");
|
||||
WebClient webClient = new WebClient();
|
||||
webClient.DownloadFile("https://nuget.org/nuget.exe", OutputFilename);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.LogErrorFromException(ex);
|
||||
return false;
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
|
||||
<UsingTask TaskName="SetEnvironmentVariable" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<EnvKey ParameterType="System.String" Required="true" />
|
||||
<EnvValue ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Using Namespace="System" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try {
|
||||
Environment.SetEnvironmentVariable(EnvKey, EnvValue, System.EnvironmentVariableTarget.Process);
|
||||
}
|
||||
catch {
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
</Project>
|
22
LICENSE.md
Normal file
22
LICENSE.md
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) Media Browser http://mediabrowser.tv
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
15
README.md
15
README.md
@ -1,2 +1,13 @@
|
||||
# Tvheadend
|
||||
A Tvheadend for Emby
|
||||
Emby.Plugins
|
||||
====================
|
||||
|
||||
This repository contains many of the plugins that are built and maintained by the Emby community.
|
||||
|
||||
Each of the projects has a build event that copies it's output to the programdata/plugins folder.
|
||||
|
||||
By default this assumes you have the server repository side by side in a folder called 'MediaBrowser'. If this is not the case, or if you've installed the server than you'll need to update the build events manually in order to test code changes.
|
||||
|
||||
|
||||
## More Information ##
|
||||
|
||||
[How to Build a Server Plugin](https://github.com/MediaBrowser/MediaBrowser/wiki/How-to-build-a-Server-Plugin)
|
||||
|
3
SharedVersion.cs
Normal file
3
SharedVersion.cs
Normal file
@ -0,0 +1,3 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("3.0.*")]
|
3
TVHeadEnd/.gitignore
vendored
Normal file
3
TVHeadEnd/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
bin/
|
||||
obj/
|
||||
packages/
|
38
TVHeadEnd/Configuration/PluginConfiguration.cs
Normal file
38
TVHeadEnd/Configuration/PluginConfiguration.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace TVHeadEnd.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Class PluginConfiguration
|
||||
/// </summary>
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
public string TVH_ServerName { get; set; }
|
||||
public int HTTP_Port { get; set; }
|
||||
public int HTSP_Port { get; set; }
|
||||
public string WebRoot { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Profile { get; set; }
|
||||
public string ChannelType { get; set; }
|
||||
public bool EnableSubsMaudios { get; set; }
|
||||
public bool ForceDeinterlace { get; set; }
|
||||
|
||||
public PluginConfiguration()
|
||||
{
|
||||
TVH_ServerName = "localhost";
|
||||
HTTP_Port = 9981;
|
||||
HTSP_Port = 9982;
|
||||
WebRoot = "/";
|
||||
Username = "";
|
||||
Password = "";
|
||||
Priority = 5;
|
||||
Profile = "";
|
||||
ChannelType = "Ignore";
|
||||
EnableSubsMaudios = false;
|
||||
ForceDeinterlace = false;
|
||||
}
|
||||
}
|
||||
}
|
156
TVHeadEnd/Configuration/configPage.html
Normal file
156
TVHeadEnd/Configuration/configPage.html
Normal file
@ -0,0 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>TVHclient</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div data-role="page" class="page type-interior pluginConfigurationPage TVHclientConfigurationPage">
|
||||
<div data-role="content">
|
||||
<div class="content-primary">
|
||||
<form class="TVHclientConfigurationForm">
|
||||
<ul class="ulForm" data-role="listview">
|
||||
<li>
|
||||
<label for="txtTVH_ServerName">
|
||||
Tvheadend-Hostname or IP-Address:
|
||||
</label>
|
||||
<input type="text" id="txtTVH_ServerName" name="txtTVH_ServerName" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtHTTP_Port">
|
||||
HTTP-Port:
|
||||
</label>
|
||||
<input type="text" id="txtHTTP_Port" name="txtHTTP_Port" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtHTSP_Port">
|
||||
HTSP-Port:
|
||||
</label>
|
||||
<input type="text" id="txtHTSP_Port" name="txtHTSP_Port" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtWebRoot">
|
||||
webroot:
|
||||
</label>
|
||||
<input type="text" id="txtWebRoot" name="txtWebRoot" />
|
||||
<div class="fieldDescription">
|
||||
This path will be added to <Tvheadend-Hostname or IP-Address> : <HTTP-Port>. It should be '/' if you have no special setup.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtUserName">
|
||||
User name:
|
||||
</label>
|
||||
<input type="text" id="txtUserName" name="txtUserName" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtPassword">
|
||||
Password:
|
||||
</label>
|
||||
<input type="password" id="txtPassword" name="txtPassword" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtPriority">
|
||||
Priority of recordings:
|
||||
</label>
|
||||
<input type="text" id="txtPriority" name="txtPriority" />
|
||||
<div class="fieldDescription">
|
||||
0 = Important, 1 = High, 2 = Normal, 3 = Low, 4 = Unimportant
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<label for="txtProfile">
|
||||
Profile for recordings:
|
||||
</label>
|
||||
<input type="text" id="txtProfile" name="txtProfile" />
|
||||
</li>
|
||||
<li>
|
||||
<label for="selChannelType">
|
||||
Use this channel type for channels taged with 'Other':
|
||||
</label>
|
||||
<select id="selChannelType">
|
||||
<option>TV</option>
|
||||
<option>Radio</option>
|
||||
<option>Ignore</option>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<label for="chkEnableSubsMaudios">
|
||||
Enable live TV subtitles and multiple audio tracks (EXPERIMENTAL)
|
||||
</label>
|
||||
<input type="checkbox" id="chkEnableSubsMaudios" />
|
||||
<div class="fieldDescription">
|
||||
Note: enabling this feature have security implications (TVH username and password will appear in Emby logs and will be transmitted in clear text on the network). Configuration change requires Emby server restart.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<label for="chkForceDeinterlace">
|
||||
Force video deinterlacing for all channels and recordings (EXPERIMENTAL)
|
||||
</label>
|
||||
<input type="checkbox" id="chkForceDeinterlace" />
|
||||
<div class="fieldDescription">
|
||||
Note: Configuration change requires Emby server restart.
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<button type="submit" data-theme="b">Save</button>
|
||||
<button type="button" onclick="history.back();">Cancel</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var TVHclientConfigurationPageVar = {
|
||||
pluginUniqueId: "95732bbe-15ed-4293-bab2-e056ccc50159"
|
||||
};
|
||||
|
||||
$('.TVHclientConfigurationPage').on('pageshow', function (event) {
|
||||
Dashboard.showLoadingMsg();
|
||||
var page = this;
|
||||
|
||||
ApiClient.getPluginConfiguration(TVHclientConfigurationPageVar.pluginUniqueId).then(function (config) {
|
||||
$('#txtTVH_ServerName', page).val(config.TVH_ServerName || "");
|
||||
$('#txtHTTP_Port', page).val(config.HTTP_Port || "9981");
|
||||
$('#txtHTSP_Port', page).val(config.HTSP_Port || "9982");
|
||||
$('#txtWebRoot', page).val(config.WebRoot || "/");
|
||||
$('#txtUserName', page).val(config.Username || "");
|
||||
$('#txtPassword', page).val(config.Password || "");
|
||||
$('#txtPriority', page).val(config.Priority || "5");
|
||||
$('#txtProfile', page).val(config.Profile || "");
|
||||
$('#selChannelType', page).val(config.ChannelType || "Ignore");
|
||||
$('#chkEnableSubsMaudios', page).checked(config.EnableSubsMaudios || false).checkboxradio("refresh");
|
||||
$('#chkForceDeinterlace', page).checked(config.ForceDeinterlace || false).checkboxradio("refresh");
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
$('.TVHclientConfigurationForm').on('submit', function (e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
var form = this;
|
||||
ApiClient.getPluginConfiguration(TVHclientConfigurationPageVar.pluginUniqueId).then(function (config) {
|
||||
config.TVH_ServerName = $('#txtTVH_ServerName', form).val();
|
||||
config.HTTP_Port = $('#txtHTTP_Port', form).val();
|
||||
config.HTSP_Port = $('#txtHTSP_Port', form).val();
|
||||
config.WebRoot = $('#txtWebRoot', form).val();
|
||||
config.Username = $('#txtUserName', form).val();
|
||||
config.Password = $('#txtPassword', form).val();
|
||||
config.Priority = $('#txtPriority', form).val();
|
||||
config.Profile = $('#txtProfile', form).val();
|
||||
config.ChannelType = $('#selChannelType', form).val();
|
||||
config.EnableSubsMaudios = $('#chkEnableSubsMaudios', form).checked();
|
||||
config.ForceDeinterlace = $('#chkForceDeinterlace', form).checked();
|
||||
ApiClient.updatePluginConfiguration(TVHclientConfigurationPageVar.pluginUniqueId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||
});
|
||||
// Disable default form submission
|
||||
return false;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
304
TVHeadEnd/DataHelper/AutorecDataHelper.cs
Normal file
304
TVHeadEnd/DataHelper/AutorecDataHelper.cs
Normal file
@ -0,0 +1,304 @@
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TVHeadEnd.HTSP;
|
||||
|
||||
|
||||
namespace TVHeadEnd.DataHelper
|
||||
{
|
||||
public class AutorecDataHelper
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Dictionary<string, HTSMessage> _data;
|
||||
|
||||
private readonly DateTime _initialDateTimeUTC = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public AutorecDataHelper(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_data = new Dictionary<string, HTSMessage>();
|
||||
}
|
||||
|
||||
public void clean()
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void autorecEntryAdd(HTSMessage message)
|
||||
{
|
||||
string id = message.getString("id");
|
||||
lock (_data)
|
||||
{
|
||||
if (_data.ContainsKey(id))
|
||||
{
|
||||
_logger.Info("[TVHclient] AutorecDataHelper.autorecEntryAdd id already in database - skip!" + message.ToString());
|
||||
return;
|
||||
}
|
||||
_data.Add(id, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void autorecEntryUpdate(HTSMessage message)
|
||||
{
|
||||
string id = message.getString("id");
|
||||
lock (_data)
|
||||
{
|
||||
HTSMessage oldMessage = _data[id];
|
||||
if (oldMessage == null)
|
||||
{
|
||||
_logger.Info("[TVHclient] AutorecDataHelper.autorecEntryAdd id not in database - skip!" + message.ToString());
|
||||
return;
|
||||
}
|
||||
foreach (KeyValuePair<string, object> entry in message)
|
||||
{
|
||||
if (oldMessage.containsField(entry.Key))
|
||||
{
|
||||
oldMessage.removeField(entry.Key);
|
||||
}
|
||||
oldMessage.putField(entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void autorecEntryDelete(HTSMessage message)
|
||||
{
|
||||
string id = message.getString("id");
|
||||
lock (_data)
|
||||
{
|
||||
_data.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IEnumerable<SeriesTimerInfo>> buildAutorecInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Factory.StartNew<IEnumerable<SeriesTimerInfo>>(() =>
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
List<SeriesTimerInfo> result = new List<SeriesTimerInfo>();
|
||||
|
||||
foreach (KeyValuePair<string, HTSMessage> entry in _data)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.Info("[TVHclient] DvrDataHelper.buildDvrInfos, call canceled - returning part list.");
|
||||
return result;
|
||||
}
|
||||
|
||||
HTSMessage m = entry.Value;
|
||||
SeriesTimerInfo sti = new SeriesTimerInfo();
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("id"))
|
||||
{
|
||||
sti.Id = m.getString("id");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("daysOfWeek"))
|
||||
{
|
||||
int daysOfWeek = m.getInt("daysOfWeek");
|
||||
sti.Days = getDayOfWeekListFromInt(daysOfWeek);
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
sti.StartDate = DateTime.Now.ToUniversalTime();
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("retention"))
|
||||
{
|
||||
int retentionInDays = m.getInt("retention");
|
||||
sti.EndDate = DateTime.Now.AddDays(retentionInDays).ToUniversalTime();
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("channel"))
|
||||
{
|
||||
sti.ChannelId = "" + m.getInt("channel");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("startExtra"))
|
||||
{
|
||||
sti.PrePaddingSeconds = (int)m.getLong("startExtra") * 60;
|
||||
sti.IsPrePaddingRequired = true;
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("stopExtra"))
|
||||
{
|
||||
sti.PostPaddingSeconds = (int)m.getLong("stopExtra") * 60;
|
||||
sti.IsPostPaddingRequired = true;
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("title"))
|
||||
{
|
||||
sti.Name = m.getString("title");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("description"))
|
||||
{
|
||||
sti.Overview = m.getString("description");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("priority"))
|
||||
{
|
||||
sti.Priority = m.getInt("priority");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("title"))
|
||||
{
|
||||
sti.SeriesId = m.getString("title");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
public string ProgramId { get; set; }
|
||||
public bool RecordAnyChannel { get; set; }
|
||||
public bool RecordAnyTime { get; set; }
|
||||
public bool RecordNewOnly { get; set; }
|
||||
*/
|
||||
|
||||
result.Add(sti);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<DayOfWeek> getDayOfWeekListFromInt(int daysOfWeek)
|
||||
{
|
||||
List<DayOfWeek> result = new List<DayOfWeek>();
|
||||
if ((daysOfWeek & 0x01) != 0)
|
||||
{
|
||||
result.Add(DayOfWeek.Monday);
|
||||
}
|
||||
if ((daysOfWeek & 0x02) != 0)
|
||||
{
|
||||
result.Add(DayOfWeek.Tuesday);
|
||||
}
|
||||
if ((daysOfWeek & 0x04) != 0)
|
||||
{
|
||||
result.Add(DayOfWeek.Wednesday);
|
||||
}
|
||||
if ((daysOfWeek & 0x08) != 0)
|
||||
{
|
||||
result.Add(DayOfWeek.Thursday);
|
||||
}
|
||||
if ((daysOfWeek & 0x10) != 0)
|
||||
{
|
||||
result.Add(DayOfWeek.Friday);
|
||||
}
|
||||
if ((daysOfWeek & 0x20) != 0)
|
||||
{
|
||||
result.Add(DayOfWeek.Saturday);
|
||||
}
|
||||
if ((daysOfWeek & 0x40) != 0)
|
||||
{
|
||||
result.Add(DayOfWeek.Sunday);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int getDaysOfWeekFromList(List<DayOfWeek> days)
|
||||
{
|
||||
int result = 0;
|
||||
foreach (DayOfWeek currDay in days)
|
||||
{
|
||||
switch (currDay)
|
||||
{
|
||||
case DayOfWeek.Monday:
|
||||
result = result | 0x1;
|
||||
break;
|
||||
case DayOfWeek.Tuesday:
|
||||
result = result | 0x2;
|
||||
break;
|
||||
case DayOfWeek.Wednesday:
|
||||
result = result | 0x4;
|
||||
break;
|
||||
case DayOfWeek.Thursday:
|
||||
result = result | 0x8;
|
||||
break;
|
||||
case DayOfWeek.Friday:
|
||||
result = result | 0x10;
|
||||
break;
|
||||
case DayOfWeek.Saturday:
|
||||
result = result | 0x20;
|
||||
break;
|
||||
case DayOfWeek.Sunday:
|
||||
result = result | 0x40;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int getMinutesFromMidnight(DateTime time)
|
||||
{
|
||||
DateTime utcTime = time.ToUniversalTime();
|
||||
int hours = utcTime.Hour;
|
||||
int minute = utcTime.Minute;
|
||||
int minutes = (hours * 60) + minute;
|
||||
return minutes;
|
||||
}
|
||||
}
|
||||
}
|
230
TVHeadEnd/DataHelper/ChannelDataHelper.cs
Normal file
230
TVHeadEnd/DataHelper/ChannelDataHelper.cs
Normal file
@ -0,0 +1,230 @@
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TVHeadEnd.HTSP;
|
||||
|
||||
namespace TVHeadEnd.DataHelper
|
||||
{
|
||||
public class ChannelDataHelper
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly TunerDataHelper _tunerDataHelper;
|
||||
private readonly Dictionary<int, HTSMessage> _data;
|
||||
private readonly Dictionary<string, string> _piconData;
|
||||
private string _channelType4Other = "Ignore";
|
||||
|
||||
public ChannelDataHelper(ILogger logger, TunerDataHelper tunerDataHelper)
|
||||
{
|
||||
_logger = logger;
|
||||
_tunerDataHelper = tunerDataHelper;
|
||||
|
||||
_data = new Dictionary<int, HTSMessage>();
|
||||
_piconData = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public ChannelDataHelper(ILogger logger) : this(logger, null) {}
|
||||
|
||||
public void SetChannelType4Other(string channelType4Other)
|
||||
{
|
||||
_channelType4Other = channelType4Other;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
_data.Clear();
|
||||
if (_tunerDataHelper != null)
|
||||
{
|
||||
_tunerDataHelper.clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(HTSMessage message)
|
||||
{
|
||||
if (_tunerDataHelper != null)
|
||||
{
|
||||
// TVHeadend don't send the information we need
|
||||
// _tunerDataHelper.addTunerInfo(message);
|
||||
}
|
||||
|
||||
lock (_data)
|
||||
{
|
||||
try
|
||||
{
|
||||
int channelID = message.getInt("channelId");
|
||||
if (_data.ContainsKey(channelID))
|
||||
{
|
||||
HTSMessage storedMessage = _data[channelID];
|
||||
if (storedMessage != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> entry in message)
|
||||
{
|
||||
if (storedMessage.containsField(entry.Key))
|
||||
{
|
||||
storedMessage.removeField(entry.Key);
|
||||
}
|
||||
storedMessage.putField(entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error("[TVHclient] ChannelDataHelper: update for channelID '" + channelID + "' but no initial data found!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (message.containsField("channelNumber") && message.getInt("channelNumber") > 0) // use only channels with number > 0
|
||||
{
|
||||
_data.Add(channelID, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("[TVHclient] ChannelDataHelper.Add caught exception: " + ex.Message + "\nHTSmessage=" + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GetChannelIcon4ChannelId(string channelId)
|
||||
{
|
||||
return _piconData[channelId];
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ChannelInfo>> BuildChannelInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Factory.StartNew<IEnumerable<ChannelInfo>>(() =>
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
List<ChannelInfo> result = new List<ChannelInfo>();
|
||||
foreach (KeyValuePair<int, HTSMessage> entry in _data)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.Info("[TVHclient] ChannelDataHelper.buildChannelInfos, call canceled - returning part list.");
|
||||
return result;
|
||||
}
|
||||
|
||||
HTSMessage m = entry.Value;
|
||||
|
||||
try
|
||||
{
|
||||
ChannelInfo ci = new ChannelInfo();
|
||||
ci.Id = "" + entry.Key;
|
||||
|
||||
ci.ImagePath = "";
|
||||
|
||||
if (m.containsField("channelIcon"))
|
||||
{
|
||||
string channelIcon = m.getString("channelIcon");
|
||||
Uri uriResult;
|
||||
bool uriCheckResult = Uri.TryCreate(channelIcon, UriKind.Absolute, out uriResult) && uriResult.Scheme == Uri.UriSchemeHttp;
|
||||
if (uriCheckResult)
|
||||
{
|
||||
ci.ImageUrl = channelIcon;
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.HasImage = true;
|
||||
if(!_piconData.ContainsKey(ci.Id))
|
||||
{
|
||||
_piconData.Add(ci.Id, channelIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m.containsField("channelName"))
|
||||
{
|
||||
string name = m.getString("channelName");
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ci.Name = m.getString("channelName");
|
||||
}
|
||||
|
||||
if (m.containsField("channelNumber"))
|
||||
{
|
||||
int channelNumber = m.getInt("channelNumber");
|
||||
ci.Number = "" + channelNumber;
|
||||
if (m.containsField("channelNumberMinor"))
|
||||
{
|
||||
int channelNumberMinor = m.getInt("channelNumberMinor");
|
||||
ci.Number = ci.Number + "." + channelNumberMinor;
|
||||
}
|
||||
}
|
||||
|
||||
Boolean serviceFound = false;
|
||||
if (m.containsField("services"))
|
||||
{
|
||||
IList tunerInfoList = m.getList("services");
|
||||
if (tunerInfoList != null && tunerInfoList.Count > 0)
|
||||
{
|
||||
HTSMessage firstServiceInList = (HTSMessage)tunerInfoList[0];
|
||||
if (firstServiceInList.containsField("type"))
|
||||
{
|
||||
string type = firstServiceInList.getString("type").ToLower();
|
||||
switch (type)
|
||||
{
|
||||
case "radio":
|
||||
ci.ChannelType = ChannelType.Radio;
|
||||
serviceFound = true;
|
||||
break;
|
||||
case "sdtv":
|
||||
case "hdtv":
|
||||
ci.ChannelType = ChannelType.TV;
|
||||
serviceFound = true;
|
||||
break;
|
||||
case "other":
|
||||
switch (_channelType4Other.ToLower())
|
||||
{
|
||||
case "tv":
|
||||
_logger.Info("[TVHclient] ChannelDataHelper: map service tag 'Other' to 'TV'.");
|
||||
ci.ChannelType = ChannelType.TV;
|
||||
serviceFound = true;
|
||||
break;
|
||||
case "radio":
|
||||
_logger.Info("[TVHclient] ChannelDataHelper: map service tag 'Other' to 'Radio'.");
|
||||
ci.ChannelType = ChannelType.Radio;
|
||||
serviceFound = true;
|
||||
break;
|
||||
default:
|
||||
_logger.Info("[TVHclient] ChannelDataHelper: don't map service tag 'Other' - will be ignored.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_logger.Info("[TVHclient] ChannelDataHelper: unkown service tag '" + type + "' - will be ignored.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!serviceFound)
|
||||
{
|
||||
_logger.Info("[TVHclient] ChannelDataHelper: unable to detect service-type (tvheadend tag!!!) from service list:" + m.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.Info("[TVHclient] ChannelDataHelper: Adding channel \n" + m.ToString());
|
||||
|
||||
result.Add(ci);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("[TVHclient] ChannelDataHelper.BuildChannelInfos caught exception: " + ex.Message + "\nHTSmessage=" + m);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
438
TVHeadEnd/DataHelper/DvrDataHelper.cs
Normal file
438
TVHeadEnd/DataHelper/DvrDataHelper.cs
Normal file
@ -0,0 +1,438 @@
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TVHeadEnd.HTSP;
|
||||
|
||||
|
||||
namespace TVHeadEnd.DataHelper
|
||||
{
|
||||
public class DvrDataHelper
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Dictionary<string, HTSMessage> _data;
|
||||
|
||||
private readonly DateTime _initialDateTimeUTC = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public DvrDataHelper(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_data = new Dictionary<string, HTSMessage>();
|
||||
}
|
||||
|
||||
public void clean()
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void dvrEntryAdd(HTSMessage message)
|
||||
{
|
||||
string id = message.getString("id");
|
||||
lock (_data)
|
||||
{
|
||||
if (_data.ContainsKey(id))
|
||||
{
|
||||
_logger.Info("[TVHclient] DvrDataHelper.dvrEntryAdd id already in database - skip!" + message.ToString());
|
||||
return;
|
||||
}
|
||||
_data.Add(id, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void dvrEntryUpdate(HTSMessage message)
|
||||
{
|
||||
string id = message.getString("id");
|
||||
lock (_data)
|
||||
{
|
||||
HTSMessage oldMessage = _data[id];
|
||||
if (oldMessage == null)
|
||||
{
|
||||
_logger.Info("[TVHclient] DvrDataHelper.dvrEntryUpdate id not in database - skip!" + message.ToString());
|
||||
return;
|
||||
}
|
||||
foreach (KeyValuePair<string, object> entry in message)
|
||||
{
|
||||
if (oldMessage.containsField(entry.Key))
|
||||
{
|
||||
oldMessage.removeField(entry.Key);
|
||||
}
|
||||
oldMessage.putField(entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dvrEntryDelete(HTSMessage message)
|
||||
{
|
||||
string id = message.getString("id");
|
||||
lock (_data)
|
||||
{
|
||||
_data.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IEnumerable<RecordingInfo>> buildDvrInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Factory.StartNew<IEnumerable<RecordingInfo>>(() =>
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
List<RecordingInfo> result = new List<RecordingInfo>();
|
||||
foreach (KeyValuePair<string, HTSMessage> entry in _data)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.Info("[TVHclient] DvrDataHelper.buildDvrInfos, call canceled - returning part list.");
|
||||
return result;
|
||||
}
|
||||
|
||||
HTSMessage m = entry.Value;
|
||||
RecordingInfo ri = new RecordingInfo();
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("id"))
|
||||
{
|
||||
ri.Id = "" + m.getInt("id");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("channel"))
|
||||
{
|
||||
ri.ChannelId = "" + m.getInt("channel");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("start"))
|
||||
{
|
||||
long unixUtc = m.getLong("start");
|
||||
ri.StartDate = _initialDateTimeUTC.AddSeconds(unixUtc).ToUniversalTime();
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("stop"))
|
||||
{
|
||||
long unixUtc = m.getLong("stop");
|
||||
ri.EndDate = _initialDateTimeUTC.AddSeconds(unixUtc).ToUniversalTime();
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("title"))
|
||||
{
|
||||
ri.Name = m.getString("title");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("description"))
|
||||
{
|
||||
ri.Overview = m.getString("description");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("summary"))
|
||||
{
|
||||
ri.EpisodeTitle = m.getString("summary");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
ri.HasImage = false;
|
||||
// public string ImagePath { get; set; }
|
||||
// public string ImageUrl { get; set; }
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("state"))
|
||||
{
|
||||
string state = m.getString("state");
|
||||
switch (state)
|
||||
{
|
||||
case "completed":
|
||||
ri.Status = RecordingStatus.Completed;
|
||||
break;
|
||||
case "scheduled":
|
||||
ri.Status = RecordingStatus.New;
|
||||
continue;
|
||||
//break;
|
||||
case "missed":
|
||||
ri.Status = RecordingStatus.Error;
|
||||
break;
|
||||
case "recording":
|
||||
ri.Status = RecordingStatus.InProgress;
|
||||
break;
|
||||
|
||||
default:
|
||||
_logger.Fatal("[TVHclient] DvrDataHelper.buildDvrInfos: state '" + state + "' not handled!");
|
||||
continue;
|
||||
//break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
// Path must not be set to force emby use of the LiveTvService methods!!!!
|
||||
//if (m.containsField("path"))
|
||||
//{
|
||||
// ri.Path = m.getString("path");
|
||||
//}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("autorecId"))
|
||||
{
|
||||
ri.SeriesTimerId = m.getString("autorecId");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("eventId"))
|
||||
{
|
||||
ri.ProgramId = "" + m.getInt("eventId");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
public ProgramAudio? Audio { get; set; }
|
||||
public ChannelType ChannelType { get; set; }
|
||||
public float? CommunityRating { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public bool? IsHD { get; set; }
|
||||
public bool IsKids { get; set; }
|
||||
public bool IsLive { get; set; }
|
||||
public bool IsMovie { get; set; }
|
||||
public bool IsNews { get; set; }
|
||||
public bool IsPremiere { get; set; }
|
||||
public bool IsRepeat { get; set; }
|
||||
public bool IsSeries { get; set; }
|
||||
public bool IsSports { get; set; }
|
||||
public string OfficialRating { get; set; }
|
||||
public DateTime? OriginalAirDate { get; set; }
|
||||
public string Url { get; set; }
|
||||
*/
|
||||
|
||||
result.Add(ri);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Task<IEnumerable<TimerInfo>> buildPendingTimersInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Factory.StartNew<IEnumerable<TimerInfo>>(() =>
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
List<TimerInfo> result = new List<TimerInfo>();
|
||||
foreach (KeyValuePair<string, HTSMessage> entry in _data)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.Info("[TVHclient] DvrDataHelper.buildDvrInfos, call canceled - returning part list.");
|
||||
return result;
|
||||
}
|
||||
|
||||
HTSMessage m = entry.Value;
|
||||
TimerInfo ti = new TimerInfo();
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("id"))
|
||||
{
|
||||
ti.Id = "" + m.getInt("id");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("channel"))
|
||||
{
|
||||
ti.ChannelId = "" + m.getInt("channel");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("start"))
|
||||
{
|
||||
long unixUtc = m.getLong("start");
|
||||
ti.StartDate = _initialDateTimeUTC.AddSeconds(unixUtc).ToUniversalTime();
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("stop"))
|
||||
{
|
||||
long unixUtc = m.getLong("stop");
|
||||
ti.EndDate = _initialDateTimeUTC.AddSeconds(unixUtc).ToUniversalTime();
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("title"))
|
||||
{
|
||||
ti.Name = m.getString("title");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("description"))
|
||||
{
|
||||
ti.Overview = m.getString("description");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("state"))
|
||||
{
|
||||
string state = m.getString("state");
|
||||
switch (state)
|
||||
{
|
||||
case "scheduled":
|
||||
ti.Status = RecordingStatus.New;
|
||||
break;
|
||||
default:
|
||||
// only scheduled timers need to be delivered
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("startExtra"))
|
||||
{
|
||||
|
||||
ti.PrePaddingSeconds = (int)m.getLong("startExtra") * 60;
|
||||
ti.IsPrePaddingRequired = true;
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("stopExtra"))
|
||||
{
|
||||
|
||||
ti.PostPaddingSeconds = (int)m.getLong("stopExtra") * 60;
|
||||
ti.IsPostPaddingRequired = true;
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("priority"))
|
||||
{
|
||||
ti.Priority = m.getInt("priority");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("autorecId"))
|
||||
{
|
||||
ti.SeriesTimerId = m.getString("autorecId");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m.containsField("eventId"))
|
||||
{
|
||||
ti.ProgramId = "" + m.getInt("eventId");
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
}
|
||||
|
||||
result.Add(ti);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
147
TVHeadEnd/DataHelper/TunerDataHelper.cs
Normal file
147
TVHeadEnd/DataHelper/TunerDataHelper.cs
Normal file
@ -0,0 +1,147 @@
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TVHeadEnd.HTSP;
|
||||
|
||||
namespace TVHeadEnd.DataHelper
|
||||
{
|
||||
public class TunerDataHelper
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Dictionary<string, HTSMessage> _data;
|
||||
|
||||
public TunerDataHelper(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_data = new Dictionary<string, HTSMessage>();
|
||||
}
|
||||
|
||||
public void clean()
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
_data.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void addTunerInfo(HTSMessage tunerMessage)
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
string channelID = "" + tunerMessage.getInt("channelId");
|
||||
if (_data.ContainsKey(channelID))
|
||||
{
|
||||
_data.Remove(channelID);
|
||||
}
|
||||
_data.Add(channelID, tunerMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
<dump>
|
||||
channelId : 240
|
||||
channelNumber : 40
|
||||
channelName : zdf.kultur
|
||||
eventId : 11708150
|
||||
nextEventId : 11708152
|
||||
services : name : CXD2837 DVB-C DVB-T/T2 (adapter 7)/KBW: 370,000 kHz/zdf.kultur
|
||||
type : SDTV
|
||||
, name : CXD2837 DVB-C DVB-T/T2 (adapter 6)/KBW: 370,000 kHz/zdf.kultur
|
||||
type : SDTV
|
||||
, name : STV0367 DVB-C DVB-T (adapter 5)/KBW: 370,000 kHz/zdf.kultur
|
||||
type : SDTV
|
||||
, name : STV0367 DVB-C DVB-T (adapter 4)/KBW: 370,000 kHz/zdf.kultur
|
||||
type : SDTV
|
||||
, name : CXD2837 DVB-C DVB-T/T2 (adapter 3)/KBW: 370,000 kHz/zdf.kultur
|
||||
type : SDTV
|
||||
, name : CXD2837 DVB-C DVB-T/T2 (adapter 2)/KBW: 370,000 kHz/zdf.kultur
|
||||
type : SDTV
|
||||
, name : STV0367 DVB-C DVB-T (adapter 1)/KBW: 370,000 kHz/zdf.kultur
|
||||
type : SDTV
|
||||
, name : STV0367 DVB-C DVB-T (adapter 0)/KBW: 370,000 kHz/zdf.kultur
|
||||
type : SDTV
|
||||
,
|
||||
tags : 1, 2,
|
||||
method : channelAdd
|
||||
</dump>
|
||||
*/
|
||||
|
||||
public Task<List<LiveTvTunerInfo>> buildTunerInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.Factory.StartNew<List<LiveTvTunerInfo>>(() =>
|
||||
{
|
||||
List<LiveTvTunerInfo> result = new List<LiveTvTunerInfo>();
|
||||
lock (_data)
|
||||
{
|
||||
foreach (HTSMessage currMessage in _data.Values)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.Info("[TVHclient] TunerDataHelper.buildTunerInfos: cancel requst received. Returning only partly results");
|
||||
return result;
|
||||
}
|
||||
|
||||
string channelId = "";
|
||||
if (currMessage.containsField("channelId"))
|
||||
{
|
||||
channelId = "" + currMessage.getInt("channelId");
|
||||
}
|
||||
|
||||
string programName = "";
|
||||
if (currMessage.containsField("channelName"))
|
||||
{
|
||||
programName = currMessage.getString("channelName");
|
||||
}
|
||||
|
||||
IList services = null;
|
||||
if (currMessage.containsField("services"))
|
||||
{
|
||||
services = currMessage.getList("services");
|
||||
}
|
||||
if (services != null)
|
||||
{
|
||||
foreach (HTSMessage currService in services)
|
||||
{
|
||||
string name = "";
|
||||
if (currService.containsField("name"))
|
||||
{
|
||||
name = currService.getString("name");
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string type = "";
|
||||
if (currService.containsField("type"))
|
||||
{
|
||||
type = currService.getString("type");
|
||||
}
|
||||
|
||||
LiveTvTunerInfo ltti = new LiveTvTunerInfo();
|
||||
ltti.Id = name;
|
||||
ltti.Name = name;
|
||||
ltti.ProgramName = programName;
|
||||
ltti.SourceType = type;
|
||||
ltti.ChannelId = channelId;
|
||||
ltti.Status = LiveTvTunerStatus.Available;
|
||||
|
||||
ltti.CanReset = false; // currently not possible
|
||||
|
||||
//ltti.Clients // not available from TVheadend
|
||||
//ltti.RecordingId // not available from TVheadend
|
||||
|
||||
result.Add(ltti);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
479
TVHeadEnd/HTSConnectionHandler.cs
Normal file
479
TVHeadEnd/HTSConnectionHandler.cs
Normal file
@ -0,0 +1,479 @@
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TVHeadEnd.DataHelper;
|
||||
using TVHeadEnd.HTSP;
|
||||
|
||||
|
||||
namespace TVHeadEnd
|
||||
{
|
||||
class HTSConnectionHandler : HTSConnectionListener
|
||||
{
|
||||
private static volatile HTSConnectionHandler _instance;
|
||||
private static object _syncRoot = new Object();
|
||||
|
||||
private readonly object _lock = new Object();
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private volatile Boolean _initialLoadFinished = false;
|
||||
private volatile Boolean _connected = false;
|
||||
|
||||
private HTSConnectionAsync _htsConnection;
|
||||
private int _priority;
|
||||
private string _profile;
|
||||
private string _httpBaseUrl;
|
||||
private string _channelType;
|
||||
private string _tvhServerName;
|
||||
private int _httpPort;
|
||||
private int _htspPort;
|
||||
private string _webRoot;
|
||||
private string _userName;
|
||||
private string _password;
|
||||
private bool _enableSubsMaudios;
|
||||
private bool _forceDeinterlace;
|
||||
|
||||
// Data helpers
|
||||
private readonly ChannelDataHelper _channelDataHelper;
|
||||
private readonly DvrDataHelper _dvrDataHelper;
|
||||
private readonly AutorecDataHelper _autorecDataHelper;
|
||||
|
||||
private LiveTvService _liveTvService;
|
||||
|
||||
private Dictionary<string, string> _headers = new Dictionary<string, string>();
|
||||
|
||||
private HTSConnectionHandler(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
//System.Diagnostics.StackTrace t = new System.Diagnostics.StackTrace();
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler()");
|
||||
|
||||
_channelDataHelper = new ChannelDataHelper(logger);
|
||||
_dvrDataHelper = new DvrDataHelper(logger);
|
||||
_autorecDataHelper = new AutorecDataHelper(logger);
|
||||
|
||||
init();
|
||||
|
||||
_channelDataHelper.SetChannelType4Other(_channelType);
|
||||
}
|
||||
|
||||
public static HTSConnectionHandler GetInstance(ILogger logger)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new HTSConnectionHandler(logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
|
||||
public void setLiveTvService(LiveTvService liveTvService)
|
||||
{
|
||||
_liveTvService = liveTvService;
|
||||
}
|
||||
|
||||
public int WaitForInitialLoad(CancellationToken cancellationToken)
|
||||
{
|
||||
ensureConnection();
|
||||
DateTime start = DateTime.Now;
|
||||
while (!_initialLoadFinished || cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
TimeSpan duration = DateTime.Now - start;
|
||||
long durationInSec = duration.Ticks / TimeSpan.TicksPerSecond;
|
||||
if (durationInSec > 60 * 15) // 15 Min timeout, should be enough to load huge data count
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
var config = Plugin.Instance.Configuration;
|
||||
|
||||
if (string.IsNullOrEmpty(config.TVH_ServerName))
|
||||
{
|
||||
string message = "[TVHclient] HTSConnectionHandler.ensureConnection: TVH-Server name must be configured.";
|
||||
_logger.Error(message);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(config.Username))
|
||||
{
|
||||
string message = "[TVHclient] HTSConnectionHandler.ensureConnection: Username must be configured.";
|
||||
_logger.Error(message);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(config.Password))
|
||||
{
|
||||
string message = "[TVHclient] HTSConnectionHandler.ensureConnection: Password must be configured.";
|
||||
_logger.Error(message);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
_priority = config.Priority;
|
||||
_profile = config.Profile.Trim();
|
||||
_channelType = config.ChannelType.Trim();
|
||||
_enableSubsMaudios = config.EnableSubsMaudios;
|
||||
_forceDeinterlace = config.ForceDeinterlace;
|
||||
|
||||
if (_priority < 0 || _priority > 4)
|
||||
{
|
||||
_priority = 2;
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.ensureConnection: Priority was out of range [0-4] - set to 2");
|
||||
}
|
||||
|
||||
_tvhServerName = config.TVH_ServerName.Trim();
|
||||
_httpPort = config.HTTP_Port;
|
||||
_htspPort = config.HTSP_Port;
|
||||
_webRoot = config.WebRoot;
|
||||
if (_webRoot.EndsWith("/"))
|
||||
{
|
||||
_webRoot = _webRoot.Substring(0, _webRoot.Length - 1);
|
||||
}
|
||||
_userName = config.Username.Trim();
|
||||
_password = config.Password.Trim();
|
||||
|
||||
if (_enableSubsMaudios)
|
||||
{
|
||||
// Use HTTP basic auth instead of TVH ticketing system for authentication to allow the users to switch subs or audio tracks at any time
|
||||
_httpBaseUrl = "http://" + _userName + ":" + _password + "@" + _tvhServerName + ":" + _httpPort + _webRoot;
|
||||
}
|
||||
else
|
||||
{
|
||||
_httpBaseUrl = "http://" + _tvhServerName + ":" + _httpPort + _webRoot;
|
||||
}
|
||||
|
||||
string authInfo = _userName + ":" + _password;
|
||||
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
|
||||
_headers["Authorization"] = "Basic " + authInfo;
|
||||
}
|
||||
|
||||
public ImageStream GetChannelImage(string channelId, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() channelId: " + channelId);
|
||||
|
||||
String channelIcon = _channelDataHelper.GetChannelIcon4ChannelId(channelId);
|
||||
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() channelIcon: " + channelIcon);
|
||||
|
||||
WebRequest request = null;
|
||||
|
||||
if (channelIcon.StartsWith("http"))
|
||||
{
|
||||
request = WebRequest.Create(channelIcon);
|
||||
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() WebRequest: " + channelIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
string requestStr = "http://" + _tvhServerName + ":" + _httpPort + _webRoot + "/" + channelIcon;
|
||||
request = WebRequest.Create(requestStr);
|
||||
request.Headers["Authorization"] = _headers["Authorization"];
|
||||
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() WebRequest: " + requestStr);
|
||||
}
|
||||
|
||||
|
||||
HttpWebResponse httpWebReponse = (HttpWebResponse)request.GetResponse();
|
||||
Stream stream = httpWebReponse.GetResponseStream();
|
||||
|
||||
ImageStream imageStream = new ImageStream();
|
||||
|
||||
int lastDot = channelIcon.LastIndexOf('.');
|
||||
if (lastDot > -1)
|
||||
{
|
||||
String suffix = channelIcon.Substring(lastDot + 1);
|
||||
suffix = suffix.ToLower();
|
||||
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() image suffix: " + suffix);
|
||||
|
||||
switch (suffix)
|
||||
{
|
||||
case "bmp":
|
||||
imageStream.Stream = stream;
|
||||
imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Bmp;
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() using fix image type BMP.");
|
||||
break;
|
||||
|
||||
case "gif":
|
||||
imageStream.Stream = stream;
|
||||
imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Gif;
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() using fix image type GIF.");
|
||||
break;
|
||||
|
||||
case "jpg":
|
||||
imageStream.Stream = stream;
|
||||
imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Jpg;
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() using fix image type JPG.");
|
||||
break;
|
||||
|
||||
case "png":
|
||||
imageStream.Stream = stream;
|
||||
imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Png;
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() using fix image type PNG.");
|
||||
break;
|
||||
|
||||
case "webp":
|
||||
imageStream.Stream = stream;
|
||||
imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Webp;
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() using fix image type WEBP.");
|
||||
break;
|
||||
|
||||
default:
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() unkown image type '" + suffix + "' - return as PNG");
|
||||
//Image image = Image.FromStream(stream);
|
||||
//imageStream.Stream = ImageToPNGStream(image);
|
||||
//imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Png;
|
||||
imageStream.Stream = stream;
|
||||
imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Png;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.GetChannelImage() no image type in suffix of channelImage name '" + channelIcon + "' found - return as PNG.");
|
||||
//Image image = Image.FromStream(stream);
|
||||
//imageStream.Stream = ImageToPNGStream(image);
|
||||
//imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Png;
|
||||
imageStream.Stream = stream;
|
||||
imageStream.Format = MediaBrowser.Model.Drawing.ImageFormat.Png;
|
||||
}
|
||||
|
||||
return imageStream;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("[TVHclient] HTSConnectionHandler.GetChannelImage() caught exception: " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetHeaders()
|
||||
{
|
||||
return new Dictionary<string, string>(_headers);
|
||||
}
|
||||
|
||||
//private static Stream ImageToPNGStream(Image image)
|
||||
//{
|
||||
// Stream stream = new System.IO.MemoryStream();
|
||||
// image.Save(stream, ImageFormat.Png);
|
||||
// stream.Position = 0;
|
||||
// return stream;
|
||||
//}
|
||||
|
||||
private void ensureConnection()
|
||||
{
|
||||
//_logger.Info("[TVHclient] HTSConnectionHandler.ensureConnection()");
|
||||
if (_htsConnection == null || _htsConnection.needsRestart())
|
||||
{
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.ensureConnection() : create new HTS-Connection");
|
||||
Version version = Assembly.GetEntryAssembly().GetName().Version;
|
||||
_htsConnection = new HTSConnectionAsync(this, "TVHclient4Emby-" + version.ToString(), "" + HTSMessage.HTSP_VERSION, _logger);
|
||||
_connected = false;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.ensureConnection: Used connection parameters: " +
|
||||
"TVH Server = '" + _tvhServerName + "'; " +
|
||||
"HTTP Port = '" + _httpPort + "'; " +
|
||||
"HTSP Port = '" + _htspPort + "'; " +
|
||||
"Web-Root = '" + _webRoot + "'; " +
|
||||
"User = '" + _userName + "'; " +
|
||||
"Password set = '" + (_password.Length > 0) + "'");
|
||||
|
||||
_htsConnection.open(_tvhServerName, _htspPort);
|
||||
_connected = _htsConnection.authenticate(_userName, _password);
|
||||
|
||||
_logger.Info("[TVHclient] HTSConnectionHandler.ensureConnection: connection established " + _connected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SendMessage(HTSMessage message, HTSResponseHandler responseHandler)
|
||||
{
|
||||
ensureConnection();
|
||||
_htsConnection.sendMessage(message, responseHandler);
|
||||
}
|
||||
|
||||
public String GetServername()
|
||||
{
|
||||
ensureConnection();
|
||||
return _htsConnection.getServername();
|
||||
}
|
||||
|
||||
public String GetServerVersion()
|
||||
{
|
||||
ensureConnection();
|
||||
return _htsConnection.getServerversion();
|
||||
}
|
||||
|
||||
public int GetServerProtocolVersion()
|
||||
{
|
||||
ensureConnection();
|
||||
return _htsConnection.getServerProtocolVersion();
|
||||
}
|
||||
|
||||
public String GetDiskSpace()
|
||||
{
|
||||
ensureConnection();
|
||||
return _htsConnection.getDiskspace();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ChannelInfo>> BuildChannelInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return _channelDataHelper.BuildChannelInfos(cancellationToken);
|
||||
}
|
||||
|
||||
public int GetPriority()
|
||||
{
|
||||
return _priority;
|
||||
}
|
||||
|
||||
public String GetProfile()
|
||||
{
|
||||
return _profile;
|
||||
}
|
||||
|
||||
public String GetHttpBaseUrl()
|
||||
{
|
||||
return _httpBaseUrl;
|
||||
}
|
||||
|
||||
public bool GetEnableSubsMaudios()
|
||||
{
|
||||
return _enableSubsMaudios;
|
||||
}
|
||||
|
||||
public bool GetForceDeinterlace()
|
||||
{
|
||||
return _forceDeinterlace;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<RecordingInfo>> BuildDvrInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return _dvrDataHelper.buildDvrInfos(cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<SeriesTimerInfo>> BuildAutorecInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return _autorecDataHelper.buildAutorecInfos(cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<TimerInfo>> BuildPendingTimersInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
return _dvrDataHelper.buildPendingTimersInfos(cancellationToken);
|
||||
}
|
||||
|
||||
public void onError(Exception ex)
|
||||
{
|
||||
_logger.ErrorException("[TVHclient] HTSConnectionHandler recorded a HTSP error: " + ex.Message, ex);
|
||||
_htsConnection.stop();
|
||||
_htsConnection = null;
|
||||
_connected = false;
|
||||
//_liveTvService.sendDataSourceChanged();
|
||||
ensureConnection();
|
||||
}
|
||||
|
||||
public void onMessage(HTSMessage response)
|
||||
{
|
||||
if (response != null)
|
||||
{
|
||||
switch (response.Method)
|
||||
{
|
||||
case "tagAdd":
|
||||
case "tagUpdate":
|
||||
case "tagDelete":
|
||||
//_logger.Fatal("[TVHclient] tad add/update/delete" + response.ToString());
|
||||
break;
|
||||
|
||||
case "channelAdd":
|
||||
case "channelUpdate":
|
||||
_channelDataHelper.Add(response);
|
||||
break;
|
||||
|
||||
case "dvrEntryAdd":
|
||||
_dvrDataHelper.dvrEntryAdd(response);
|
||||
break;
|
||||
case "dvrEntryUpdate":
|
||||
_dvrDataHelper.dvrEntryUpdate(response);
|
||||
break;
|
||||
case "dvrEntryDelete":
|
||||
_dvrDataHelper.dvrEntryDelete(response);
|
||||
break;
|
||||
|
||||
case "autorecEntryAdd":
|
||||
_autorecDataHelper.autorecEntryAdd(response);
|
||||
break;
|
||||
case "autorecEntryUpdate":
|
||||
_autorecDataHelper.autorecEntryUpdate(response);
|
||||
break;
|
||||
case "autorecEntryDelete":
|
||||
_autorecDataHelper.autorecEntryDelete(response);
|
||||
break;
|
||||
|
||||
case "eventAdd":
|
||||
case "eventUpdate":
|
||||
case "eventDelete":
|
||||
// should not happen as we don't subscribe for this events.
|
||||
break;
|
||||
|
||||
//case "subscriptionStart":
|
||||
//case "subscriptionGrace":
|
||||
//case "subscriptionStop":
|
||||
//case "subscriptionSkip":
|
||||
//case "subscriptionSpeed":
|
||||
//case "subscriptionStatus":
|
||||
// _logger.Fatal("[TVHclient] subscription events " + response.ToString());
|
||||
// break;
|
||||
|
||||
//case "queueStatus":
|
||||
// _logger.Fatal("[TVHclient] queueStatus event " + response.ToString());
|
||||
// break;
|
||||
|
||||
//case "signalStatus":
|
||||
// _logger.Fatal("[TVHclient] signalStatus event " + response.ToString());
|
||||
// break;
|
||||
|
||||
//case "timeshiftStatus":
|
||||
// _logger.Fatal("[TVHclient] timeshiftStatus event " + response.ToString());
|
||||
// break;
|
||||
|
||||
//case "muxpkt": // streaming data
|
||||
// _logger.Fatal("[TVHclient] muxpkt event " + response.ToString());
|
||||
// break;
|
||||
|
||||
case "initialSyncCompleted":
|
||||
_initialLoadFinished = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
//_logger.Fatal("[TVHclient] Method '" + response.Method + "' not handled in LiveTvService.cs");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
466
TVHeadEnd/HTSP/HTSConnectionAsync.cs
Normal file
466
TVHeadEnd/HTSP/HTSConnectionAsync.cs
Normal file
@ -0,0 +1,466 @@
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TVHeadEnd.Helper;
|
||||
using TVHeadEnd.HTSP_Responses;
|
||||
|
||||
namespace TVHeadEnd.HTSP
|
||||
{
|
||||
public class HTSConnectionAsync
|
||||
{
|
||||
private const long BytesPerGiga = 1024 * 1024 * 1024;
|
||||
|
||||
private volatile Boolean _needsRestart = false;
|
||||
private volatile Boolean _connected;
|
||||
private volatile int _seq = 0;
|
||||
|
||||
private readonly object _lock;
|
||||
private readonly HTSConnectionListener _listener;
|
||||
private readonly String _clientName;
|
||||
private readonly String _clientVersion;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private int _serverProtocolVersion;
|
||||
private string _servername;
|
||||
private string _serverversion;
|
||||
private string _diskSpace;
|
||||
|
||||
private readonly ByteList _buffer;
|
||||
private readonly SizeQueue<HTSMessage> _receivedMessagesQueue;
|
||||
private readonly SizeQueue<HTSMessage> _messagesForSendQueue;
|
||||
private readonly Dictionary<int, HTSResponseHandler> _responseHandlers;
|
||||
|
||||
private Thread _receiveHandlerThread;
|
||||
private Thread _messageBuilderThread;
|
||||
private Thread _sendingHandlerThread;
|
||||
private Thread _messageDistributorThread;
|
||||
|
||||
private Socket _socket = null;
|
||||
|
||||
public HTSConnectionAsync(HTSConnectionListener listener, String clientName, String clientVersion, ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
_connected = false;
|
||||
_lock = new object();
|
||||
|
||||
_listener = listener;
|
||||
_clientName = clientName;
|
||||
_clientVersion = clientVersion;
|
||||
|
||||
_buffer = new ByteList();
|
||||
_receivedMessagesQueue = new SizeQueue<HTSMessage>(int.MaxValue);
|
||||
_messagesForSendQueue = new SizeQueue<HTSMessage>(int.MaxValue);
|
||||
_responseHandlers = new Dictionary<int, HTSResponseHandler>();
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
if (_receiveHandlerThread != null && _receiveHandlerThread.IsAlive)
|
||||
{
|
||||
_receiveHandlerThread.Abort();
|
||||
}
|
||||
if (_messageBuilderThread != null && _messageBuilderThread.IsAlive)
|
||||
{
|
||||
_messageBuilderThread.Abort();
|
||||
}
|
||||
if (_sendingHandlerThread != null && _sendingHandlerThread.IsAlive)
|
||||
{
|
||||
_sendingHandlerThread.Abort();
|
||||
}
|
||||
if (_messageDistributorThread != null && _messageDistributorThread.IsAlive)
|
||||
{
|
||||
_messageDistributorThread.Abort();
|
||||
}
|
||||
if (_socket != null && _socket.Connected)
|
||||
{
|
||||
_socket.Close();
|
||||
}
|
||||
_needsRestart = true;
|
||||
_connected = false;
|
||||
}
|
||||
|
||||
public Boolean needsRestart()
|
||||
{
|
||||
return _needsRestart;
|
||||
}
|
||||
|
||||
public void open(String hostname, int port)
|
||||
{
|
||||
if (_connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Monitor.Enter(_lock);
|
||||
while (!_connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Establish the remote endpoint for the socket.
|
||||
|
||||
IPAddress ipAddress;
|
||||
if (!IPAddress.TryParse(hostname, out ipAddress))
|
||||
{
|
||||
// no IP --> ask DNS
|
||||
IPHostEntry ipHostInfo = Dns.GetHostEntry(hostname);
|
||||
ipAddress = ipHostInfo.AddressList[0];
|
||||
}
|
||||
|
||||
IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);
|
||||
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.open: " +
|
||||
"IPEndPoint = '" + remoteEP.ToString() + "'; " +
|
||||
"AddressFamily = '" + ipAddress.AddressFamily + "'");
|
||||
|
||||
// Create a TCP/IP socket.
|
||||
_socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
||||
|
||||
// connect to server
|
||||
_socket.Connect(remoteEP);
|
||||
|
||||
_connected = true;
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.open: socket connected.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("[TVHclient] HTSConnectionAsync.open: caught exception : {0}", ex.Message);
|
||||
|
||||
Thread.Sleep(2000);
|
||||
}
|
||||
}
|
||||
|
||||
ThreadStart ReceiveHandlerRef = new ThreadStart(ReceiveHandler);
|
||||
_receiveHandlerThread = new Thread(ReceiveHandlerRef);
|
||||
_receiveHandlerThread.IsBackground = true;
|
||||
_receiveHandlerThread.Start();
|
||||
|
||||
ThreadStart MessageBuilderRef = new ThreadStart(MessageBuilder);
|
||||
_messageBuilderThread = new Thread(MessageBuilderRef);
|
||||
_messageBuilderThread.IsBackground = true;
|
||||
_messageBuilderThread.Start();
|
||||
|
||||
ThreadStart SendingHandlerRef = new ThreadStart(SendingHandler);
|
||||
_sendingHandlerThread = new Thread(SendingHandlerRef);
|
||||
_sendingHandlerThread.IsBackground = true;
|
||||
_sendingHandlerThread.Start();
|
||||
|
||||
ThreadStart MessageDistributorRef = new ThreadStart(MessageDistributor);
|
||||
_messageDistributorThread = new Thread(MessageDistributorRef);
|
||||
_messageDistributorThread.IsBackground = true;
|
||||
_messageDistributorThread.Start();
|
||||
|
||||
Monitor.Exit(_lock);
|
||||
}
|
||||
|
||||
public Boolean authenticate(String username, String password)
|
||||
{
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.authenticate: start");
|
||||
|
||||
HTSMessage helloMessage = new HTSMessage();
|
||||
helloMessage.Method = "hello";
|
||||
helloMessage.putField("clientname", _clientName);
|
||||
helloMessage.putField("clientversion", _clientVersion);
|
||||
helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION);
|
||||
helloMessage.putField("username", username);
|
||||
|
||||
LoopBackResponseHandler loopBackResponseHandler = new LoopBackResponseHandler();
|
||||
sendMessage(helloMessage, loopBackResponseHandler);
|
||||
HTSMessage helloResponse = loopBackResponseHandler.getResponse();
|
||||
if (helloResponse != null)
|
||||
{
|
||||
if (helloResponse.containsField("htspversion"))
|
||||
{
|
||||
_serverProtocolVersion = helloResponse.getInt("htspversion");
|
||||
}
|
||||
else
|
||||
{
|
||||
_serverProtocolVersion = -1;
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'htspversion' - htsp wrong implemented on tvheadend side.");
|
||||
}
|
||||
|
||||
if (helloResponse.containsField("servername"))
|
||||
{
|
||||
_servername = helloResponse.getString("servername");
|
||||
}
|
||||
else
|
||||
{
|
||||
_servername = "n/a";
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'servername' - htsp wrong implemented on tvheadend side.");
|
||||
}
|
||||
|
||||
if (helloResponse.containsField("serverversion"))
|
||||
{
|
||||
_serverversion = helloResponse.getString("serverversion");
|
||||
}
|
||||
else
|
||||
{
|
||||
_serverversion = "n/a";
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'serverversion' - htsp wrong implemented on tvheadend side.");
|
||||
}
|
||||
|
||||
byte[] salt = null;
|
||||
if (helloResponse.containsField("challenge"))
|
||||
{
|
||||
salt = helloResponse.getByteArray("challenge");
|
||||
}
|
||||
else
|
||||
{
|
||||
salt = new byte[0];
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.authenticate: hello don't deliver required field 'challenge' - htsp wrong implemented on tvheadend side.");
|
||||
}
|
||||
|
||||
byte[] digest = SHA1helper.GenerateSaltedSHA1(password, salt);
|
||||
HTSMessage authMessage = new HTSMessage();
|
||||
authMessage.Method = "authenticate";
|
||||
authMessage.putField("username", username);
|
||||
authMessage.putField("digest", digest);
|
||||
sendMessage(authMessage, loopBackResponseHandler);
|
||||
HTSMessage authResponse = loopBackResponseHandler.getResponse();
|
||||
if (authResponse != null)
|
||||
{
|
||||
Boolean auth = authResponse.getInt("noaccess", 0) != 1;
|
||||
if (auth)
|
||||
{
|
||||
HTSMessage getDiskSpaceMessage = new HTSMessage();
|
||||
getDiskSpaceMessage.Method = "getDiskSpace";
|
||||
sendMessage(getDiskSpaceMessage, loopBackResponseHandler);
|
||||
HTSMessage diskSpaceResponse = loopBackResponseHandler.getResponse();
|
||||
if (diskSpaceResponse != null)
|
||||
{
|
||||
long freeDiskSpace = -1;
|
||||
long totalDiskSpace = -1;
|
||||
if (diskSpaceResponse.containsField("freediskspace"))
|
||||
{
|
||||
freeDiskSpace = diskSpaceResponse.getLong("freediskspace") / BytesPerGiga;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.authenticate: getDiskSpace don't deliver required field 'freediskspace' - htsp wrong implemented on tvheadend side.");
|
||||
}
|
||||
if (diskSpaceResponse.containsField("totaldiskspace"))
|
||||
{
|
||||
totalDiskSpace = diskSpaceResponse.getLong("totaldiskspace") / BytesPerGiga;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.authenticate: getDiskSpace don't deliver required field 'totaldiskspace' - htsp wrong implemented on tvheadend side.");
|
||||
}
|
||||
|
||||
_diskSpace = freeDiskSpace + "GB / " + totalDiskSpace + "GB";
|
||||
}
|
||||
|
||||
HTSMessage enableAsyncMetadataMessage = new HTSMessage();
|
||||
enableAsyncMetadataMessage.Method = "enableAsyncMetadata";
|
||||
sendMessage(enableAsyncMetadataMessage, null);
|
||||
}
|
||||
|
||||
_logger.Info("[TVHclient] HTSConnectionAsync.authenticate: authenticated = " + auth);
|
||||
return auth;
|
||||
}
|
||||
}
|
||||
_logger.Error("[TVHclient] HTSConnectionAsync.authenticate: no hello response");
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getServerProtocolVersion()
|
||||
{
|
||||
return _serverProtocolVersion;
|
||||
}
|
||||
|
||||
public string getServername()
|
||||
{
|
||||
return _servername;
|
||||
}
|
||||
|
||||
public string getServerversion()
|
||||
{
|
||||
return _serverversion;
|
||||
}
|
||||
|
||||
public string getDiskspace()
|
||||
{
|
||||
return _diskSpace;
|
||||
}
|
||||
|
||||
public void sendMessage(HTSMessage message, HTSResponseHandler responseHandler)
|
||||
{
|
||||
// loop the sequence number
|
||||
if (_seq == int.MaxValue)
|
||||
{
|
||||
_seq = int.MinValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
_seq++;
|
||||
}
|
||||
|
||||
// housekeeping verry old response handlers
|
||||
if (_responseHandlers.ContainsKey(_seq))
|
||||
{
|
||||
_responseHandlers.Remove(_seq);
|
||||
}
|
||||
|
||||
message.putField("seq", _seq);
|
||||
_messagesForSendQueue.Enqueue(message);
|
||||
_responseHandlers.Add(_seq, responseHandler);
|
||||
}
|
||||
|
||||
private void SendingHandler()
|
||||
{
|
||||
Boolean threadOk = true;
|
||||
while (_connected && threadOk)
|
||||
{
|
||||
try
|
||||
{
|
||||
HTSMessage message = _messagesForSendQueue.Dequeue();
|
||||
byte[] data2send = message.BuildBytes();
|
||||
int bytesSent = _socket.Send(data2send);
|
||||
if (bytesSent != data2send.Length)
|
||||
{
|
||||
_logger.Error("[TVHclient] SendingHandler: Sending not complete! \nBytes sent: " + bytesSent + "\nMessage bytes: " +
|
||||
data2send.Length + "\nMessage: " + message.ToString());
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
threadOk = false;
|
||||
Thread.ResetAbort();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error("[TVHclient] SendingHandler caught exception : {0}", ex.ToString());
|
||||
if (_listener != null)
|
||||
{
|
||||
_listener.onError(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.ErrorException("[TVHclient] SendingHandler caught exception : {0} but no error listener is configured!!!", ex, ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveHandler()
|
||||
{
|
||||
Boolean threadOk = true;
|
||||
byte[] readBuffer = new byte[1024];
|
||||
while (_connected && threadOk)
|
||||
{
|
||||
try
|
||||
{
|
||||
int bytesReveived = _socket.Receive(readBuffer);
|
||||
_buffer.appendCount(readBuffer, bytesReveived);
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
threadOk = false;
|
||||
Thread.ResetAbort();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
threadOk = false;
|
||||
if (_listener != null)
|
||||
{
|
||||
Task.Factory.StartNew(() => _listener.onError(ex));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.ErrorException("[TVHclient] ReceiveHandler caught exception : {0} but no error listener is configured!!!", ex, ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MessageBuilder()
|
||||
{
|
||||
Boolean threadOk = true;
|
||||
while (_connected && threadOk)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] lengthInformation = _buffer.getFromStart(4);
|
||||
long messageDataLength = HTSMessage.uIntToLong(lengthInformation[0], lengthInformation[1], lengthInformation[2], lengthInformation[3]);
|
||||
byte[] messageData = _buffer.extractFromStart((int)messageDataLength + 4); // should be long !!!
|
||||
HTSMessage response = HTSMessage.parse(messageData, _logger);
|
||||
_receivedMessagesQueue.Enqueue(response);
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
threadOk = false;
|
||||
Thread.ResetAbort();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_listener != null)
|
||||
{
|
||||
_listener.onError(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.ErrorException("[TVHclient] MessageBuilder caught exception : {0} but no error listener is configured!!!", ex, ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MessageDistributor()
|
||||
{
|
||||
Boolean threadOk = true;
|
||||
while (_connected && threadOk)
|
||||
{
|
||||
try
|
||||
{
|
||||
HTSMessage response = _receivedMessagesQueue.Dequeue();
|
||||
if (response.containsField("seq"))
|
||||
{
|
||||
int seqNo = response.getInt("seq");
|
||||
if (_responseHandlers.ContainsKey(seqNo))
|
||||
{
|
||||
HTSResponseHandler currHTSResponseHandler = _responseHandlers[seqNo];
|
||||
if (currHTSResponseHandler != null)
|
||||
{
|
||||
_responseHandlers.Remove(seqNo);
|
||||
currHTSResponseHandler.handleResponse(response);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Fatal("[TVHclient] MessageDistributor: HTSResponseHandler for seq = '" + seqNo + "' not found!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// auto update messages
|
||||
if (_listener != null)
|
||||
{
|
||||
_listener.onMessage(response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
threadOk = false;
|
||||
Thread.ResetAbort();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_listener != null)
|
||||
{
|
||||
_listener.onError(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.ErrorException("[TVHclient] MessageBuilder caught exception : {0} but no error listener is configured!!!", ex, ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
TVHeadEnd/HTSP/HTSConnectionListener.cs
Normal file
10
TVHeadEnd/HTSP/HTSConnectionListener.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace TVHeadEnd.HTSP
|
||||
{
|
||||
public interface HTSConnectionListener
|
||||
{
|
||||
void onMessage(HTSMessage response);
|
||||
void onError(Exception ex);
|
||||
}
|
||||
}
|
531
TVHeadEnd/HTSP/HTSMessage.cs
Normal file
531
TVHeadEnd/HTSP/HTSMessage.cs
Normal file
@ -0,0 +1,531 @@
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TVHeadEnd.Helper;
|
||||
|
||||
namespace TVHeadEnd.HTSP
|
||||
{
|
||||
public class HTSMessage
|
||||
{
|
||||
public const long HTSP_VERSION = 20;
|
||||
private const byte HMF_MAP = 1;
|
||||
private const byte HMF_S64 = 2;
|
||||
private const byte HMF_STR = 3;
|
||||
private const byte HMF_BIN = 4;
|
||||
private const byte HMF_LIST = 5;
|
||||
|
||||
private readonly Dictionary<string, object> _dict;
|
||||
private ILogger _logger = null;
|
||||
private byte[] _data = null;
|
||||
|
||||
public HTSMessage()
|
||||
{
|
||||
_dict = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
public void putField(string name, object value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
_dict[name] = value;
|
||||
_data = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeField(string name)
|
||||
{
|
||||
_dict.Remove(name);
|
||||
_data = null;
|
||||
}
|
||||
|
||||
public Dictionary<string, object>.Enumerator GetEnumerator()
|
||||
{
|
||||
return _dict.GetEnumerator();
|
||||
}
|
||||
|
||||
public string Method
|
||||
{
|
||||
set
|
||||
{
|
||||
_dict["method"] = value;
|
||||
_data = null;
|
||||
}
|
||||
get
|
||||
{
|
||||
return getString("method", "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool containsField(string name)
|
||||
{
|
||||
return _dict.ContainsKey(name);
|
||||
}
|
||||
|
||||
public System.Numerics.BigInteger getBigInteger(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (System.Numerics.BigInteger)_dict[name];
|
||||
}
|
||||
catch(InvalidCastException ice)
|
||||
{
|
||||
_logger.Fatal("[TVHclient] Caught InvalidCastException for field name '" + name + "'. Expected 'System.Numerics.BigInteger' but got '" +
|
||||
_dict[name].GetType() + "'");
|
||||
throw ice;
|
||||
}
|
||||
}
|
||||
|
||||
public long getLong(string name)
|
||||
{
|
||||
return (long)getBigInteger(name);
|
||||
}
|
||||
|
||||
public long getLong(string name, long std)
|
||||
{
|
||||
if (!containsField(name))
|
||||
{
|
||||
return std;
|
||||
}
|
||||
return getLong(name);
|
||||
}
|
||||
|
||||
public int getInt(string name)
|
||||
{
|
||||
return (int)getBigInteger(name);
|
||||
}
|
||||
|
||||
public int getInt(string name, int std)
|
||||
{
|
||||
if (!containsField(name))
|
||||
{
|
||||
return std;
|
||||
}
|
||||
return getInt(name);
|
||||
}
|
||||
|
||||
public string getString(string name, string std)
|
||||
{
|
||||
if (!containsField(name))
|
||||
{
|
||||
return std;
|
||||
}
|
||||
return getString(name);
|
||||
}
|
||||
|
||||
public string getString(string name)
|
||||
{
|
||||
object obj = _dict[name];
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return obj.ToString();
|
||||
}
|
||||
|
||||
public IList<long?> getLongList(string name)
|
||||
{
|
||||
List<long?> list = new List<long?>();
|
||||
|
||||
if (!containsField(name))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
foreach (object obj in (IList)_dict[name])
|
||||
{
|
||||
if (obj is System.Numerics.BigInteger)
|
||||
{
|
||||
list.Add((long)((System.Numerics.BigInteger)obj));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
internal IList<long?> getLongList(string name, IList<long?> std)
|
||||
{
|
||||
if (!containsField(name))
|
||||
{
|
||||
return std;
|
||||
}
|
||||
|
||||
return getLongList(name);
|
||||
}
|
||||
|
||||
public IList<int?> getIntList(string name)
|
||||
{
|
||||
List<int?> list = new List<int?>();
|
||||
|
||||
if (!containsField(name))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
foreach (object obj in (IList)_dict[name])
|
||||
{
|
||||
if (obj is System.Numerics.BigInteger)
|
||||
{
|
||||
list.Add((int)((System.Numerics.BigInteger)obj));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
internal IList<int?> getIntList(string name, IList<int?> std)
|
||||
{
|
||||
if (!containsField(name))
|
||||
{
|
||||
return std;
|
||||
}
|
||||
|
||||
return getIntList(name);
|
||||
}
|
||||
|
||||
public IList getList(string name)
|
||||
{
|
||||
return (IList)_dict[name];
|
||||
}
|
||||
|
||||
public byte[] getByteArray(string name)
|
||||
{
|
||||
return (byte[])_dict[name];
|
||||
}
|
||||
|
||||
public DateTime getDate(string name)
|
||||
{
|
||||
return new DateTime(getLong(name) * 1000);
|
||||
}
|
||||
|
||||
public byte[] BuildBytes()
|
||||
{
|
||||
if(_data != null)
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
byte[] buf = new byte[0];
|
||||
|
||||
// calc data
|
||||
byte[] data = serializeBinary(_dict);
|
||||
|
||||
// calc length
|
||||
int len = data.Length;
|
||||
byte[] tmpByte = new byte[1];
|
||||
tmpByte[0] = unchecked((byte)((len >> 24) & 0xFF));
|
||||
buf = buf.Concat(tmpByte).ToArray();
|
||||
tmpByte[0] = unchecked((byte)((len >> 16) & 0xFF));
|
||||
buf = buf.Concat(tmpByte).ToArray();
|
||||
tmpByte[0] = unchecked((byte)((len >> 8) & 0xFF));
|
||||
buf = buf.Concat(tmpByte).ToArray();
|
||||
tmpByte[0] = unchecked((byte)((len) & 0xFF));
|
||||
buf = buf.Concat(tmpByte).ToArray();
|
||||
|
||||
// append data
|
||||
buf = buf.Concat(data).ToArray();
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("\nHTSMessage:\n");
|
||||
sb.Append(" <dump>\n");
|
||||
sb.Append(getValueString(_dict, " "));
|
||||
sb.Append(" </dump>\n\n");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string getValueString(object value, string pad)
|
||||
{
|
||||
if (value is byte[])
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
byte[] bVal = (byte[])value;
|
||||
for (int ii = 0; ii < bVal.Length; ii++)
|
||||
{
|
||||
sb.Append(bVal[ii]);
|
||||
//sb.Append(" (" + Convert.ToString(bVal[ii], 2).PadLeft(8, '0') + ")");
|
||||
sb.Append(", ");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
else if (value is IDictionary)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
IDictionary dictVal = (IDictionary)value;
|
||||
foreach (object key in dictVal.Keys)
|
||||
{
|
||||
object currValue = dictVal[key];
|
||||
sb.Append(pad + key + " : " + getValueString(currValue, pad + " ") + "\n");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
else if (value is ICollection)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
ICollection colVal = (ICollection)value;
|
||||
foreach (object tmpObj in colVal)
|
||||
{
|
||||
sb.Append(getValueString(tmpObj, pad) + ", ");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
return "" + value;
|
||||
}
|
||||
|
||||
private byte[] serializeBinary(IDictionary map)
|
||||
{
|
||||
byte[] buf = new byte[0];
|
||||
foreach (object key in map.Keys)
|
||||
{
|
||||
object value = map[key];
|
||||
byte[] sub = serializeBinary(key.ToString(), value);
|
||||
buf = buf.Concat(sub).ToArray();
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private byte[] serializeBinary(ICollection list)
|
||||
{
|
||||
byte[] buf = new byte[0];
|
||||
foreach (object value in list)
|
||||
{
|
||||
byte[] sub = serializeBinary("", value);
|
||||
buf = buf.Concat(sub).ToArray();
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
private byte[] serializeBinary(string name, object value)
|
||||
{
|
||||
byte[] bName = GetBytes(name);
|
||||
byte[] bData = new byte[0];
|
||||
byte type;
|
||||
|
||||
if (value is string)
|
||||
{
|
||||
type = HTSMessage.HMF_STR;
|
||||
bData = GetBytes(((string)value));
|
||||
}
|
||||
else if (value is System.Numerics.BigInteger)
|
||||
{
|
||||
type = HTSMessage.HMF_S64;
|
||||
bData = toByteArray((System.Numerics.BigInteger)value);
|
||||
}
|
||||
else if (value is int?)
|
||||
{
|
||||
type = HTSMessage.HMF_S64;
|
||||
bData = toByteArray((int)value);
|
||||
}
|
||||
else if (value is long?)
|
||||
{
|
||||
type = HTSMessage.HMF_S64;
|
||||
bData = toByteArray((long)value);
|
||||
}
|
||||
else if (value is byte[])
|
||||
{
|
||||
type = HTSMessage.HMF_BIN;
|
||||
bData = (byte[])value;
|
||||
}
|
||||
else if (value is IDictionary)
|
||||
{
|
||||
type = HTSMessage.HMF_MAP;
|
||||
bData = serializeBinary((IDictionary)value);
|
||||
}
|
||||
else if (value is ICollection)
|
||||
{
|
||||
type = HTSMessage.HMF_LIST;
|
||||
bData = serializeBinary((ICollection)value);
|
||||
}
|
||||
else if (value == null)
|
||||
{
|
||||
throw new IOException("HTSP doesn't support null values");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Unhandled class for " + name + ": " + value + " (" + value.GetType().Name + ")");
|
||||
}
|
||||
|
||||
byte[] buf = new byte[1 + 1 + 4 + bName.Length + bData.Length];
|
||||
buf[0] = type;
|
||||
buf[1] = unchecked((byte)(bName.Length & 0xFF));
|
||||
buf[2] = unchecked((byte)((bData.Length >> 24) & 0xFF));
|
||||
buf[3] = unchecked((byte)((bData.Length >> 16) & 0xFF));
|
||||
buf[4] = unchecked((byte)((bData.Length >> 8) & 0xFF));
|
||||
buf[5] = unchecked((byte)((bData.Length) & 0xFF));
|
||||
|
||||
Array.Copy(bName, 0, buf, 6, bName.Length);
|
||||
Array.Copy(bData, 0, buf, 6 + bName.Length, bData.Length);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
private byte[] toByteArray(System.Numerics.BigInteger big)
|
||||
{
|
||||
byte[] b = BitConverter.GetBytes((long)big);
|
||||
byte[] b1 = new byte[0];
|
||||
Boolean tail = false;
|
||||
for (int ii = 0; ii < b.Length; ii++)
|
||||
{
|
||||
if (b[ii] != 0 || !tail)
|
||||
{
|
||||
tail = true;
|
||||
b1 = b1.Concat(new byte[] { b[ii] }).ToArray();
|
||||
}
|
||||
}
|
||||
if (b1.Length == 0)
|
||||
{
|
||||
b1 = new byte[1];
|
||||
}
|
||||
return b1;
|
||||
}
|
||||
|
||||
public static HTSMessage parse(byte[] data, ILogger logger)
|
||||
{
|
||||
if (data.Length < 4)
|
||||
{
|
||||
logger.Error("[HTSMessage.parse(byte[])] Really to short");
|
||||
return null;
|
||||
}
|
||||
|
||||
long len = uIntToLong(data[0], data[1], data[2], data[3]);
|
||||
//Message not fully read
|
||||
if (data.Length < len + 4)
|
||||
{
|
||||
logger.Error("[HTSMessage.parse(byte[])] not enough data for len: " + len);
|
||||
return null;
|
||||
}
|
||||
|
||||
//drops 4 bytes (length information)
|
||||
byte[] messageData = new byte[len];
|
||||
Array.Copy(data, 4, messageData, 0, len);
|
||||
|
||||
HTSMessage msg = deserializeBinary(messageData);
|
||||
|
||||
msg._logger = logger;
|
||||
msg._data = data;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static long uIntToLong(byte b1, byte b2, byte b3, byte b4)
|
||||
{
|
||||
long i = 0;
|
||||
i <<= 8;
|
||||
i ^= b1 & 0xFF;
|
||||
i <<= 8;
|
||||
i ^= b2 & 0xFF;
|
||||
i <<= 8;
|
||||
i ^= b3 & 0xFF;
|
||||
i <<= 8;
|
||||
i ^= b4 & 0xFF;
|
||||
return i;
|
||||
}
|
||||
|
||||
private static System.Numerics.BigInteger toBigInteger(byte[] b)
|
||||
{
|
||||
byte[] b1 = new byte[8];
|
||||
for (int ii = 0; ii < b.Length; ii++)
|
||||
{
|
||||
b1[ii] = b[ii];
|
||||
}
|
||||
long lValue = BitConverter.ToInt64(b1, 0);
|
||||
return new System.Numerics.BigInteger(lValue);
|
||||
}
|
||||
|
||||
private static HTSMessage deserializeBinary(byte[] messageData)
|
||||
{
|
||||
byte type, namelen;
|
||||
long datalen;
|
||||
|
||||
HTSMessage msg = new HTSMessage();
|
||||
int cnt = 0;
|
||||
|
||||
ByteBuffer buf = new ByteBuffer(messageData);
|
||||
while (buf.hasRemaining())
|
||||
{
|
||||
type = buf.get();
|
||||
namelen = buf.get();
|
||||
datalen = uIntToLong(buf.get(), buf.get(), buf.get(), buf.get());
|
||||
|
||||
if (buf.Length() < namelen + datalen)
|
||||
{
|
||||
throw new IOException("Buffer limit exceeded");
|
||||
}
|
||||
|
||||
//Get the key for the map (the name)
|
||||
string name = null;
|
||||
if (namelen == 0)
|
||||
{
|
||||
name = Convert.ToString(cnt++);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] bName = new byte[namelen];
|
||||
buf.get(bName);
|
||||
name = NewString(bName);
|
||||
}
|
||||
|
||||
//Get the actual content
|
||||
object obj = null;
|
||||
byte[] bData = new byte[datalen];
|
||||
buf.get(bData);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case HTSMessage.HMF_STR:
|
||||
{
|
||||
obj = NewString(bData);
|
||||
break;
|
||||
}
|
||||
case HMF_BIN:
|
||||
{
|
||||
obj = bData;
|
||||
break;
|
||||
}
|
||||
case HMF_S64:
|
||||
{
|
||||
obj = toBigInteger(bData);
|
||||
break;
|
||||
}
|
||||
case HMF_MAP:
|
||||
{
|
||||
obj = deserializeBinary(bData);
|
||||
break;
|
||||
}
|
||||
case HMF_LIST:
|
||||
{
|
||||
obj = new List<object>(deserializeBinary(bData)._dict.Values);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IOException("Unknown data type");
|
||||
}
|
||||
msg.putField(name, obj);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
private static string NewString(byte[] bytes)
|
||||
{
|
||||
return System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
private byte[] GetBytes(string s)
|
||||
{
|
||||
System.Text.Encoding encoding = System.Text.Encoding.UTF8;
|
||||
byte[] bytes = new byte[encoding.GetByteCount(s)];
|
||||
encoding.GetBytes(s, 0, s.Length, bytes, 0);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
8
TVHeadEnd/HTSP/HTSResponseHandler.cs
Normal file
8
TVHeadEnd/HTSP/HTSResponseHandler.cs
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
namespace TVHeadEnd.HTSP
|
||||
{
|
||||
public interface HTSResponseHandler
|
||||
{
|
||||
void handleResponse(HTSMessage response);
|
||||
}
|
||||
}
|
776
TVHeadEnd/HTSP_Responses/GetEventsResponseHandler.cs
Normal file
776
TVHeadEnd/HTSP_Responses/GetEventsResponseHandler.cs
Normal file
@ -0,0 +1,776 @@
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TVHeadEnd.HTSP;
|
||||
|
||||
namespace TVHeadEnd.HTSP_Responses
|
||||
{
|
||||
public class GetEventsResponseHandler : HTSResponseHandler
|
||||
{
|
||||
private volatile Boolean _dataReady = false;
|
||||
|
||||
private readonly DateTime _initialDateTimeUTC = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private readonly DateTime _startDateTimeUtc, _endDateTimeUtc;
|
||||
private readonly ILogger _logger;
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
|
||||
private readonly List<ProgramInfo> _result;
|
||||
|
||||
public GetEventsResponseHandler(DateTime startDateTimeUtc, DateTime endDateTimeUtc, ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
_startDateTimeUtc = startDateTimeUtc;
|
||||
_endDateTimeUtc = endDateTimeUtc;
|
||||
|
||||
_logger = logger;
|
||||
_cancellationToken = cancellationToken;
|
||||
|
||||
_result = new List<ProgramInfo>();
|
||||
}
|
||||
|
||||
public void handleResponse(HTSMessage response)
|
||||
{
|
||||
_logger.Info("[TVHclient] GetEventsResponseHandler.handleResponse: received answer from TVH server\n" + response.ToString());
|
||||
|
||||
if (response.containsField("events"))
|
||||
{
|
||||
IList events = response.getList("events");
|
||||
foreach (HTSMessage currEventMessage in events)
|
||||
{
|
||||
ProgramInfo pi = new ProgramInfo();
|
||||
|
||||
if (currEventMessage.containsField("start"))
|
||||
{
|
||||
long currStartTimeUnix = currEventMessage.getLong("start");
|
||||
DateTime currentStartDateTimeUTC = _initialDateTimeUTC.AddSeconds(currStartTimeUnix).ToUniversalTime();
|
||||
int compResult = DateTime.Compare(currentStartDateTimeUTC, _endDateTimeUtc);
|
||||
if (compResult > 0)
|
||||
{
|
||||
_logger.Info("[TVHclient] GetEventsResponseHandler.handleResponse: start value of event larger query stop value - skipping! \n"
|
||||
+ "Query start UTC dateTime: " + _startDateTimeUtc + "\n"
|
||||
+ "Query end UTC dateTime: " + _endDateTimeUtc + "\n"
|
||||
+ "Event start UTC dateTime: " + currentStartDateTimeUTC + "\n"
|
||||
+ currEventMessage.ToString());
|
||||
continue;
|
||||
}
|
||||
pi.StartDate = currentStartDateTimeUTC;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("[TVHclient] GetEventsResponseHandler.handleResponse: no start value for event - skipping! \n" + currEventMessage.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("stop"))
|
||||
{
|
||||
long currEndTimeUnix = currEventMessage.getLong("stop");
|
||||
DateTime currentEndDateTimeUTC = _initialDateTimeUTC.AddSeconds(currEndTimeUnix).ToUniversalTime();
|
||||
int compResult = DateTime.Compare(currentEndDateTimeUTC, _startDateTimeUtc);
|
||||
if (compResult < 0)
|
||||
{
|
||||
_logger.Info("[TVHclient] GetEventsResponseHandler.handleResponse: stop value of event smaller query start value - skipping! \n"
|
||||
+ "Query start UTC dateTime: " + _startDateTimeUtc + "\n"
|
||||
+ "Query end UTC dateTime: " + _endDateTimeUtc + "\n"
|
||||
+ "Event start UTC dateTime: " + currentEndDateTimeUTC + "\n"
|
||||
+ currEventMessage.ToString());
|
||||
continue;
|
||||
}
|
||||
pi.EndDate = currentEndDateTimeUTC;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("[TVHclient] GetEventsResponseHandler.handleResponse: no stop value for event - skipping! \n" + currEventMessage.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("channelId"))
|
||||
{
|
||||
pi.ChannelId = "" + currEventMessage.getInt("channelId");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("eventId"))
|
||||
{
|
||||
pi.Id = "" + currEventMessage.getInt("eventId");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("serieslinkId"))
|
||||
{
|
||||
pi.SeriesId = "" + currEventMessage.getInt("serieslinkId");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("episodeNumber"))
|
||||
{
|
||||
pi.EpisodeNumber = currEventMessage.getInt("episodeNumber");
|
||||
}
|
||||
else if (currEventMessage.containsField("episodeId"))
|
||||
{
|
||||
pi.EpisodeNumber = currEventMessage.getInt("episodeId");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("seasonNumber"))
|
||||
{
|
||||
pi.SeasonNumber = currEventMessage.getInt("seasonNumber");
|
||||
}
|
||||
else if (currEventMessage.containsField("seasonId"))
|
||||
{
|
||||
pi.SeasonNumber = currEventMessage.getInt("seasonId");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("title"))
|
||||
{
|
||||
pi.Name = currEventMessage.getString("title");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("description"))
|
||||
{
|
||||
pi.Overview = currEventMessage.getString("description");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("summary"))
|
||||
{
|
||||
pi.EpisodeTitle = currEventMessage.getString("summary");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("firstAired"))
|
||||
{
|
||||
long firstAiredUtcLong = currEventMessage.getLong("firstAired");
|
||||
pi.OriginalAirDate = _initialDateTimeUTC.AddSeconds(firstAiredUtcLong).ToUniversalTime();
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("starRating"))
|
||||
{
|
||||
pi.OfficialRating = "" + currEventMessage.getInt("starRating");
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("image"))
|
||||
{
|
||||
pi.HasImage = true;
|
||||
pi.ImageUrl = "" + currEventMessage.getString("image");
|
||||
}
|
||||
else
|
||||
{
|
||||
pi.HasImage = false;
|
||||
}
|
||||
|
||||
if (currEventMessage.containsField("contentType"))
|
||||
{
|
||||
List<string> genres = new List<string>();
|
||||
|
||||
int contentType = currEventMessage.getInt("contentType");
|
||||
//byte major = (byte)((contentTypeRaw & 0xF0) >> 4);
|
||||
//byte minor = (byte) (contentTypeRaw & 0xF);
|
||||
|
||||
switch (contentType)
|
||||
{
|
||||
// movie/drama
|
||||
case 0x10:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0x11:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
genres.Add("Detective");
|
||||
genres.Add("Thriller");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0x12:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
genres.Add("Adventure");
|
||||
genres.Add("Western");
|
||||
genres.Add("War");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0x13:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
genres.Add("Science Fiction");
|
||||
genres.Add("Fantasy");
|
||||
genres.Add("Horror");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0x14:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
genres.Add("Comedy");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0x15:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
genres.Add("Soap");
|
||||
genres.Add("Melodrama");
|
||||
genres.Add("Folkloric");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0x16:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
genres.Add("Romance");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0x17:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
genres.Add("Serious");
|
||||
genres.Add("ClassicalReligion");
|
||||
genres.Add("Historical");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0x18:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Movie");
|
||||
genres.Add("Adult Movie");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
|
||||
// news/current affairs
|
||||
case 0x20:
|
||||
genres.Add("News");
|
||||
genres.Add("Current Affairs");
|
||||
pi.IsNews = true;
|
||||
break;
|
||||
case 0x21:
|
||||
genres.Add("News");
|
||||
genres.Add("Current Affairs");
|
||||
genres.Add("Weather Report");
|
||||
pi.IsNews = true;
|
||||
break;
|
||||
case 0x22:
|
||||
genres.Add("News");
|
||||
genres.Add("Current Affairs");
|
||||
genres.Add("Magazine");
|
||||
pi.IsNews = true;
|
||||
break;
|
||||
case 0x23:
|
||||
genres.Add("News");
|
||||
genres.Add("Current Affairs");
|
||||
genres.Add("Documentary");
|
||||
pi.IsNews = true;
|
||||
break;
|
||||
case 0x24:
|
||||
genres.Add("News");
|
||||
genres.Add("Current Affairs");
|
||||
genres.Add("Discussion");
|
||||
genres.Add("Interview");
|
||||
genres.Add("Debate");
|
||||
pi.IsNews = true;
|
||||
break;
|
||||
|
||||
// show/game show
|
||||
case 0x30:
|
||||
genres.Add("Show");
|
||||
genres.Add("Game Show");
|
||||
break;
|
||||
case 0x31:
|
||||
genres.Add("Show");
|
||||
genres.Add("Game Show");
|
||||
genres.Add("Quiz");
|
||||
genres.Add("Contest");
|
||||
break;
|
||||
case 0x32:
|
||||
genres.Add("Show");
|
||||
genres.Add("Game Show");
|
||||
genres.Add("Variety");
|
||||
break;
|
||||
case 0x33:
|
||||
genres.Add("Show");
|
||||
genres.Add("Game Show");
|
||||
genres.Add("Talk");
|
||||
break;
|
||||
|
||||
// sports
|
||||
case 0x40:
|
||||
genres.Add("Sports");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x41:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Special Event");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x42:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Magazine");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x43:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Football");
|
||||
genres.Add("Soccer");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x44:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Tennis");
|
||||
genres.Add("Squash");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x45:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Team Sports");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x46:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Athletics");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x47:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Motor Sport");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x48:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Water Sport");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x49:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Winter Sport");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x4a:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Equestrian");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
case 0x4b:
|
||||
genres.Add("Sports");
|
||||
genres.Add("Martial Sports");
|
||||
pi.IsSports = true;
|
||||
break;
|
||||
|
||||
// childrens/youth
|
||||
case 0x50:
|
||||
genres.Add("Childrens");
|
||||
genres.Add("Youth");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x51:
|
||||
genres.Add("Childrens");
|
||||
genres.Add("Youth");
|
||||
genres.Add("Pre-school");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x52:
|
||||
genres.Add("Childrens");
|
||||
genres.Add("Youth");
|
||||
genres.Add("Entertainment (6 to 14 year-olds)");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x53:
|
||||
genres.Add("Childrens");
|
||||
genres.Add("Youth");
|
||||
genres.Add("Entertainment (10 to 16 year-olds)");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x54:
|
||||
genres.Add("Childrens");
|
||||
genres.Add("Youth");
|
||||
genres.Add("Informational");
|
||||
genres.Add("Educational");
|
||||
genres.Add("Schools");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x55:
|
||||
genres.Add("Childrens");
|
||||
genres.Add("Youth");
|
||||
genres.Add("Cartoons");
|
||||
genres.Add("Puppets");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
|
||||
// music/ballet/dance
|
||||
case 0x60:
|
||||
genres.Add("Music");
|
||||
genres.Add("Ballet");
|
||||
genres.Add("Dance");
|
||||
break;
|
||||
case 0x61:
|
||||
genres.Add("Music");
|
||||
genres.Add("Ballet");
|
||||
genres.Add("Dance");
|
||||
genres.Add("Pop");
|
||||
genres.Add("Rock");
|
||||
break;
|
||||
case 0x62:
|
||||
genres.Add("Music");
|
||||
genres.Add("Ballet");
|
||||
genres.Add("Dance");
|
||||
genres.Add("Serious Music");
|
||||
genres.Add("Classical Music");
|
||||
break;
|
||||
case 0x63:
|
||||
genres.Add("Music");
|
||||
genres.Add("Ballet");
|
||||
genres.Add("Dance");
|
||||
genres.Add("Folk");
|
||||
genres.Add("Traditional Music");
|
||||
break;
|
||||
case 0x64:
|
||||
genres.Add("Music");
|
||||
genres.Add("Ballet");
|
||||
genres.Add("Dance");
|
||||
genres.Add("Jazz");
|
||||
break;
|
||||
case 0x65:
|
||||
genres.Add("Music");
|
||||
genres.Add("Ballet");
|
||||
genres.Add("Dance");
|
||||
genres.Add("Musical");
|
||||
genres.Add("Opera");
|
||||
break;
|
||||
case 0x66:
|
||||
genres.Add("Music");
|
||||
genres.Add("Ballet");
|
||||
genres.Add("Dance");
|
||||
break;
|
||||
|
||||
// arts/culture
|
||||
case 0x70:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
break;
|
||||
case 0x71:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Performing Arts");
|
||||
break;
|
||||
case 0x72:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Fine Arts");
|
||||
break;
|
||||
case 0x73:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Religion");
|
||||
break;
|
||||
case 0x74:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Popular Culture");
|
||||
genres.Add("Tradital Arts");
|
||||
break;
|
||||
case 0x75:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Literature");
|
||||
break;
|
||||
case 0x76:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Film");
|
||||
genres.Add("Cinema");
|
||||
break;
|
||||
case 0x77:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Experimantal Film");
|
||||
genres.Add("Video");
|
||||
break;
|
||||
case 0x78:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Broadcasting");
|
||||
genres.Add("Press");
|
||||
break;
|
||||
case 0x79:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("New Media");
|
||||
break;
|
||||
case 0x7a:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Magazine");
|
||||
break;
|
||||
case 0x7b:
|
||||
genres.Add("Arts");
|
||||
genres.Add("Culture");
|
||||
genres.Add("Fashion");
|
||||
break;
|
||||
|
||||
// social/political/economic
|
||||
case 0x80:
|
||||
genres.Add("Social");
|
||||
genres.Add("Political");
|
||||
genres.Add("Economic");
|
||||
break;
|
||||
case 0x81:
|
||||
genres.Add("Social");
|
||||
genres.Add("Political");
|
||||
genres.Add("Economic");
|
||||
genres.Add("Magazin");
|
||||
genres.Add("Report");
|
||||
genres.Add("Documentary");
|
||||
break;
|
||||
case 0x82:
|
||||
genres.Add("Social");
|
||||
genres.Add("Political");
|
||||
genres.Add("Economic");
|
||||
genres.Add("Economics");
|
||||
genres.Add("Social Advisory");
|
||||
break;
|
||||
case 0x83:
|
||||
genres.Add("Social");
|
||||
genres.Add("Political");
|
||||
genres.Add("Economic");
|
||||
genres.Add("Remarkable People");
|
||||
break;
|
||||
|
||||
// children's youth: educational/science/factual
|
||||
case 0x90:
|
||||
genres.Add("Educational");
|
||||
genres.Add("Science");
|
||||
genres.Add("Factual");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x91:
|
||||
genres.Add("Educational");
|
||||
genres.Add("Science");
|
||||
genres.Add("Factual");
|
||||
genres.Add("Nature");
|
||||
genres.Add("Animals");
|
||||
genres.Add("Environment");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x92:
|
||||
genres.Add("Educational");
|
||||
genres.Add("Science");
|
||||
genres.Add("Factual");
|
||||
genres.Add("Technology");
|
||||
genres.Add("Natural Sciences");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x93:
|
||||
genres.Add("Educational");
|
||||
genres.Add("Science");
|
||||
genres.Add("Factual");
|
||||
genres.Add("Medicine");
|
||||
genres.Add("Physiology");
|
||||
genres.Add("Psychology");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x94:
|
||||
genres.Add("Educational");
|
||||
genres.Add("Science");
|
||||
genres.Add("Factual");
|
||||
genres.Add("Foreign Countries");
|
||||
genres.Add("Expeditions");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x95:
|
||||
genres.Add("Educational");
|
||||
genres.Add("Science");
|
||||
genres.Add("Factual");
|
||||
genres.Add("Social");
|
||||
genres.Add("Spiritual Sciences");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x96:
|
||||
genres.Add("Educational");
|
||||
genres.Add("Science");
|
||||
genres.Add("Factual");
|
||||
genres.Add("Further Education");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
case 0x97:
|
||||
genres.Add("Educational");
|
||||
genres.Add("Science");
|
||||
genres.Add("Factual");
|
||||
genres.Add("Languages");
|
||||
pi.IsKids = true;
|
||||
break;
|
||||
|
||||
// leisure hobbies
|
||||
case 0xa0:
|
||||
genres.Add("Leisure");
|
||||
genres.Add("Hobbies");
|
||||
break;
|
||||
case 0xa1:
|
||||
genres.Add("Leisure");
|
||||
genres.Add("Hobbies");
|
||||
genres.Add("Tourism");
|
||||
genres.Add("Travel");
|
||||
break;
|
||||
case 0xa2:
|
||||
genres.Add("Leisure");
|
||||
genres.Add("Hobbies");
|
||||
genres.Add("Handicraft");
|
||||
break;
|
||||
case 0xa3:
|
||||
genres.Add("Leisure");
|
||||
genres.Add("Hobbies");
|
||||
genres.Add("Motoring");
|
||||
break;
|
||||
case 0xa4:
|
||||
genres.Add("Leisure");
|
||||
genres.Add("Hobbies");
|
||||
genres.Add("Fitness");
|
||||
genres.Add("Health");
|
||||
break;
|
||||
case 0xa5:
|
||||
genres.Add("Leisure");
|
||||
genres.Add("Hobbies");
|
||||
genres.Add("Cooking");
|
||||
break;
|
||||
case 0xa6:
|
||||
genres.Add("Leisure");
|
||||
genres.Add("Hobbies");
|
||||
genres.Add("Advertisement");
|
||||
genres.Add("Shopping");
|
||||
break;
|
||||
case 0xa7:
|
||||
genres.Add("Leisure");
|
||||
genres.Add("Hobbies");
|
||||
genres.Add("Gardening");
|
||||
break;
|
||||
|
||||
// misc
|
||||
case 0xb0:
|
||||
genres.Add("Original Language");
|
||||
break;
|
||||
case 0xb1:
|
||||
genres.Add("Black and White");
|
||||
break;
|
||||
case 0xb2:
|
||||
genres.Add("Unpublished");
|
||||
break;
|
||||
case 0xb3:
|
||||
genres.Add("Live Broadcast");
|
||||
pi.IsLive = true;
|
||||
break;
|
||||
|
||||
// drama (user defined, specced in the UK "D-Book")
|
||||
case 0xf0:
|
||||
genres.Add("Drama");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0xf1:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Detective");
|
||||
genres.Add("Thriller");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0xf2:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Adventure");
|
||||
genres.Add("Western");
|
||||
genres.Add("War");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0xf3:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Science Fiction");
|
||||
genres.Add("Fantasy");
|
||||
genres.Add("Horror");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0xf4:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Commedy");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0xf5:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Soap");
|
||||
genres.Add("Melodrama");
|
||||
genres.Add("Folkloric");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0xf6:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Romance");
|
||||
break;
|
||||
case 0xf7:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Serious");
|
||||
genres.Add("ClassicalReligion");
|
||||
genres.Add("Historical");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
case 0xf8:
|
||||
genres.Add("Drama");
|
||||
genres.Add("Adult");
|
||||
pi.IsMovie = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
// unused values
|
||||
break;
|
||||
}
|
||||
pi.Genres = genres;
|
||||
}
|
||||
|
||||
//pi.IsSeries - bool
|
||||
//pi.CommunityRating - float
|
||||
//pi.IsHD - bool
|
||||
//pi.IsPremiere - bool
|
||||
//pi.IsRepeat - bool
|
||||
//pi.ImagePath - string
|
||||
//pi.Audio - MediaBrowser.Model.LiveTv.ProgramAudio
|
||||
//pi.ProductionYear - int
|
||||
|
||||
_logger.Info("[TVHclient] GetEventsResponseHandler.handleResponse: add event\n" + currEventMessage.ToString() + "\n" + createPiInfo(pi));
|
||||
|
||||
_result.Add(pi);
|
||||
}
|
||||
}
|
||||
_dataReady = true;
|
||||
}
|
||||
|
||||
private String createPiInfo(ProgramInfo pi)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("\n<ProgramInfo>\n");
|
||||
sb.Append(" Id: " + pi.Id + "\n");
|
||||
sb.Append(" StartDate: " + pi.StartDate + "\n");
|
||||
sb.Append(" EndDate: " + pi.EndDate + "\n");
|
||||
sb.Append(" ChannelId: " + pi.ChannelId + "\n");
|
||||
sb.Append(" Name: " + pi.Name + "\n");
|
||||
sb.Append(" Overview: " + pi.Overview + "\n");
|
||||
sb.Append(" EpisodeTitle: " + pi.EpisodeTitle + "\n");
|
||||
sb.Append(" OriginalAirDate: " + pi.OriginalAirDate + "\n");
|
||||
sb.Append(" OfficialRating: " + pi.OfficialRating + "\n");
|
||||
sb.Append(" HasImage: " + pi.HasImage + "\n");
|
||||
sb.Append(" ImageUrl: " + pi.ImageUrl + "\n");
|
||||
sb.Append(" IsMovie: " + pi.IsMovie + "\n");
|
||||
sb.Append(" IsKids: " + pi.IsKids + "\n");
|
||||
sb.Append(" IsLive: " + pi.IsLive + "\n");
|
||||
sb.Append(" IsNews: " + pi.IsNews + "\n");
|
||||
sb.Append(" IsSports: " + pi.IsSports + "\n");
|
||||
sb.Append(" Genres:\n");
|
||||
List<string> genres = pi.Genres;
|
||||
foreach(string currGenres in genres)
|
||||
{
|
||||
sb.Append(" --> " + currGenres + "\n");
|
||||
}
|
||||
sb.Append("\n");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<ProgramInfo>> GetEvents(CancellationToken cancellationToken, string channelId)
|
||||
{
|
||||
return Task.Factory.StartNew<IEnumerable<ProgramInfo>>(() =>
|
||||
{
|
||||
while (!_dataReady || cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
//_logger.Info("[TVHclient] GetEventsResponseHandler.GetEvents: channelId=" + channelId + " / dataReady=" + _dataReady + " / cancellationToken.IsCancellationRequested=" + cancellationToken.IsCancellationRequested);
|
||||
return _result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
25
TVHeadEnd/HTSP_Responses/LoopBackResponseHandler.cs
Normal file
25
TVHeadEnd/HTSP_Responses/LoopBackResponseHandler.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using TVHeadEnd.Helper;
|
||||
using TVHeadEnd.HTSP;
|
||||
|
||||
namespace TVHeadEnd.HTSP_Responses
|
||||
{
|
||||
public class LoopBackResponseHandler : HTSResponseHandler
|
||||
{
|
||||
private readonly SizeQueue<HTSMessage> _responseDataQueue;
|
||||
|
||||
public LoopBackResponseHandler()
|
||||
{
|
||||
_responseDataQueue = new SizeQueue<HTSMessage>(1);
|
||||
}
|
||||
|
||||
public void handleResponse(HTSMessage response)
|
||||
{
|
||||
_responseDataQueue.Enqueue(response);
|
||||
}
|
||||
|
||||
public HTSMessage getResponse()
|
||||
{
|
||||
return _responseDataQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
47
TVHeadEnd/Helper/ByteBuffer.cs
Normal file
47
TVHeadEnd/Helper/ByteBuffer.cs
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
namespace TVHeadEnd.Helper
|
||||
{
|
||||
public class ByteBuffer
|
||||
{
|
||||
private System.IO.MemoryStream stream;
|
||||
private System.IO.BinaryReader reader;
|
||||
private System.IO.BinaryWriter writer;
|
||||
|
||||
public ByteBuffer(byte[] data)
|
||||
{
|
||||
stream = new System.IO.MemoryStream();
|
||||
reader = new System.IO.BinaryReader(stream);
|
||||
writer = new System.IO.BinaryWriter(stream);
|
||||
writer.Write(data);
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
~ByteBuffer()
|
||||
{
|
||||
reader.Close();
|
||||
writer.Close();
|
||||
stream.Close();
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
public long Length()
|
||||
{
|
||||
return stream.Length;
|
||||
}
|
||||
|
||||
public bool hasRemaining()
|
||||
{
|
||||
return (stream.Length - stream.Position) > 0;
|
||||
}
|
||||
|
||||
public byte get()
|
||||
{
|
||||
return (byte)stream.ReadByte();
|
||||
}
|
||||
|
||||
public void get(byte[] dst)
|
||||
{
|
||||
stream.Read(dst, 0, dst.Length);
|
||||
}
|
||||
}
|
||||
}
|
87
TVHeadEnd/Helper/ByteList.cs
Normal file
87
TVHeadEnd/Helper/ByteList.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace TVHeadEnd.Helper
|
||||
{
|
||||
public class ByteList
|
||||
{
|
||||
private readonly List<byte> _data;
|
||||
|
||||
public ByteList()
|
||||
{
|
||||
_data = new List<byte>();
|
||||
}
|
||||
|
||||
public byte[] getFromStart(int count)
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
while (_data.Count < count)
|
||||
{
|
||||
Monitor.Wait(_data);
|
||||
}
|
||||
byte[] result = new byte[count];
|
||||
for (int ii = 0; ii < count; ii++)
|
||||
{
|
||||
result[ii] = _data[ii];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] extractFromStart(int count)
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
while (_data.Count < count)
|
||||
{
|
||||
Monitor.Wait(_data);
|
||||
}
|
||||
byte[] result = new byte[count];
|
||||
for (int ii = 0; ii < count; ii++)
|
||||
{
|
||||
result[ii] = _data[0];
|
||||
_data.RemoveAt(0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void appendAll(byte[] data)
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
_data.AddRange(data);
|
||||
if (_data.Count >= 1)
|
||||
{
|
||||
// wake up any blocked dequeue
|
||||
Monitor.PulseAll(_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void appendCount(byte[] data, long count)
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
for (long ii = 0; ii < count; ii++)
|
||||
{
|
||||
_data.Add(data[ii]);
|
||||
}
|
||||
if (_data.Count >= 1)
|
||||
{
|
||||
// wake up any blocked dequeue
|
||||
Monitor.PulseAll(_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count()
|
||||
{
|
||||
lock (_data)
|
||||
{
|
||||
return _data.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
TVHeadEnd/Helper/DateTimeHelper.cs
Normal file
20
TVHeadEnd/Helper/DateTimeHelper.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TVHeadEnd.Helper
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
28
TVHeadEnd/Helper/SHA1helper.cs
Normal file
28
TVHeadEnd/Helper/SHA1helper.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace TVHeadEnd.Helper
|
||||
{
|
||||
public class SHA1helper
|
||||
{
|
||||
public static byte[] GenerateSaltedSHA1(string plainTextString, byte[] saltBytes)
|
||||
{
|
||||
HashAlgorithm algorithm = new SHA1Managed();
|
||||
|
||||
byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainTextString);
|
||||
|
||||
byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + saltBytes.Length];
|
||||
for (int i = 0; i < plainTextBytes.Length; i++)
|
||||
{
|
||||
plainTextWithSaltBytes[i] = plainTextBytes[i];
|
||||
}
|
||||
for (int i = 0; i < saltBytes.Length; i++)
|
||||
{
|
||||
plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];
|
||||
}
|
||||
|
||||
byte[] digest = algorithm.ComputeHash(plainTextWithSaltBytes);
|
||||
|
||||
return digest;
|
||||
}
|
||||
}
|
||||
}
|
49
TVHeadEnd/Helper/SizeQueue.cs
Normal file
49
TVHeadEnd/Helper/SizeQueue.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System;
|
||||
|
||||
namespace TVHeadEnd.Helper
|
||||
{
|
||||
public class SizeQueue<T>
|
||||
{
|
||||
private readonly TimeSpan _timeOut = new TimeSpan(0, 0, 30);
|
||||
private readonly Queue<T> _queue = new Queue<T>();
|
||||
private readonly int _maxSize;
|
||||
public SizeQueue(int maxSize) { _maxSize = maxSize; }
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
lock (_queue)
|
||||
{
|
||||
while (_queue.Count >= _maxSize)
|
||||
{
|
||||
Monitor.Wait(_queue, _timeOut);
|
||||
}
|
||||
_queue.Enqueue(item);
|
||||
if (_queue.Count == 1)
|
||||
{
|
||||
// wake up any blocked dequeue
|
||||
Monitor.PulseAll(_queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
lock (_queue)
|
||||
{
|
||||
while (_queue.Count == 0)
|
||||
{
|
||||
Monitor.Wait(_queue, _timeOut);
|
||||
}
|
||||
T item = _queue.Dequeue();
|
||||
if (_queue.Count == _maxSize - 1)
|
||||
{
|
||||
// wake up any blocked enqueue
|
||||
Monitor.PulseAll(_queue);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
TVHeadEnd/Images/TVHeadEnd.png
Normal file
BIN
TVHeadEnd/Images/TVHeadEnd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
BIN
TVHeadEnd/Images/TVHeadEnd.xcf
Normal file
BIN
TVHeadEnd/Images/TVHeadEnd.xcf
Normal file
Binary file not shown.
1016
TVHeadEnd/LiveTvService.cs
Normal file
1016
TVHeadEnd/LiveTvService.cs
Normal file
File diff suppressed because it is too large
Load Diff
62
TVHeadEnd/Plugin.cs
Normal file
62
TVHeadEnd/Plugin.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using TVHeadEnd.Configuration;
|
||||
|
||||
|
||||
namespace TVHeadEnd
|
||||
{
|
||||
/// <summary>
|
||||
/// Class Plugin
|
||||
/// </summary>
|
||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
{
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
||||
: base(applicationPaths, xmlSerializer)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public IEnumerable<PluginPageInfo> GetPages()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new PluginPageInfo
|
||||
{
|
||||
Name = Name,
|
||||
EmbeddedResourcePath = GetType().Namespace + ".Configuration.configPage.html"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the plugin
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public override string Name
|
||||
{
|
||||
get { return "TVHclient"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description.
|
||||
/// </summary>
|
||||
/// <value>The description.</value>
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Provides live TV using Tvheadend as a back-end.";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance.
|
||||
/// </summary>
|
||||
/// <value>The instance.</value>
|
||||
public static Plugin Instance { get; private set; }
|
||||
}
|
||||
|
||||
}
|
35
TVHeadEnd/Properties/AssemblyInfo.cs
Normal file
35
TVHeadEnd/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("MediaBrowser.Plugins.TVHclient")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("MediaBrowser.Plugins.TVHclient")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("95732bbe-15ed-4293-bab2-e056ccc50159")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
106
TVHeadEnd/TVHeadEnd.csproj
Normal file
106
TVHeadEnd/TVHeadEnd.csproj
Normal file
@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{255072D7-49B0-4E0A-93D4-C283144499B7}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TVHeadEnd</RootNamespace>
|
||||
<AssemblyName>TVHeadEnd</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="MediaBrowser.Common, Version=3.1.6143.3133, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MediaBrowser.Common.3.0.680\lib\net45\MediaBrowser.Common.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MediaBrowser.Controller, Version=3.1.6143.3132, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MediaBrowser.Server.Core.3.0.680\lib\net45\MediaBrowser.Controller.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MediaBrowser.Model, Version=3.1.6143.3133, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\MediaBrowser.Common.3.0.680\lib\net45\MediaBrowser.Model.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DataHelper\ChannelDataHelper.cs" />
|
||||
<Compile Include="DataHelper\AutorecDataHelper.cs" />
|
||||
<Compile Include="DataHelper\DvrDataHelper.cs" />
|
||||
<Compile Include="DataHelper\TunerDataHelper.cs" />
|
||||
<Compile Include="Helper\ByteBuffer.cs" />
|
||||
<Compile Include="Helper\ByteList.cs" />
|
||||
<Compile Include="Helper\DateTimeHelper.cs" />
|
||||
<Compile Include="Helper\SHA1helper.cs" />
|
||||
<Compile Include="Helper\SizeQueue.cs" />
|
||||
<Compile Include="HTSConnectionHandler.cs" />
|
||||
<Compile Include="HTSP\HTSConnectionAsync.cs" />
|
||||
<Compile Include="HTSP\HTSConnectionListener.cs" />
|
||||
<Compile Include="HTSP\HTSMessage.cs" />
|
||||
<Compile Include="HTSP\HTSResponseHandler.cs" />
|
||||
<Compile Include="HTSP_Responses\GetEventsResponseHandler.cs" />
|
||||
<Compile Include="HTSP_Responses\LoopBackResponseHandler.cs" />
|
||||
<Compile Include="Configuration\PluginConfiguration.cs" />
|
||||
<Compile Include="LiveTvService.cs" />
|
||||
<Compile Include="Plugin.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TimeoutHelper\TaskWithTimeoutResult.cs" />
|
||||
<Compile Include="TimeoutHelper\TaskWithTimeoutRunner.cs" />
|
||||
<Compile Include="TunerHost.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Configuration\configPage.html" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Images\TVHeadEnd.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>xcopy "$(TargetPath)" "%25AppData%25\MediaBrowser-Server\Plugins\" /y</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
14
TVHeadEnd/TimeoutHelper/TaskWithTimeoutResult.cs
Normal file
14
TVHeadEnd/TimeoutHelper/TaskWithTimeoutResult.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TVHeadEnd.TimeoutHelper
|
||||
{
|
||||
public class TaskWithTimeoutResult<T>
|
||||
{
|
||||
public T Result { get; set; }
|
||||
public Boolean HasTimeout { get; set; }
|
||||
}
|
||||
}
|
49
TVHeadEnd/TimeoutHelper/TaskWithTimeoutRunner.cs
Normal file
49
TVHeadEnd/TimeoutHelper/TaskWithTimeoutRunner.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TVHeadEnd.TimeoutHelper
|
||||
{
|
||||
public class TaskWithTimeoutRunner<T>
|
||||
{
|
||||
private readonly TimeSpan TIMEOUT;
|
||||
|
||||
public TaskWithTimeoutRunner(TimeSpan timeout)
|
||||
{
|
||||
TIMEOUT = timeout;
|
||||
}
|
||||
|
||||
public Task<TaskWithTimeoutResult<T>> RunWithTimeout(Task<T> task)
|
||||
{
|
||||
return Task.Factory.StartNew<TaskWithTimeoutResult<T>>(() =>
|
||||
{
|
||||
Task<TaskWithTimeoutResult<T>> outherTask = new Task<TaskWithTimeoutResult<T>>(() =>
|
||||
{
|
||||
Task<TaskWithTimeoutResult<T>> longRunningTask = new Task<TaskWithTimeoutResult<T>>(() =>
|
||||
{
|
||||
TaskWithTimeoutResult<T> myTaskResult = new TaskWithTimeoutResult<T>();
|
||||
myTaskResult.Result = task.Result;
|
||||
myTaskResult.HasTimeout = false;
|
||||
return myTaskResult;
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
|
||||
longRunningTask.Start();
|
||||
|
||||
if (longRunningTask.Wait(TIMEOUT))
|
||||
{
|
||||
return longRunningTask.Result;
|
||||
}
|
||||
|
||||
// If we reach here we had an timeout
|
||||
TaskWithTimeoutResult<T> timeoutResult = new TaskWithTimeoutResult<T>();
|
||||
timeoutResult.Result = default(T);
|
||||
timeoutResult.HasTimeout = true;
|
||||
return timeoutResult;
|
||||
});
|
||||
|
||||
outherTask.Start();
|
||||
outherTask.Wait();
|
||||
return outherTask.Result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
117
TVHeadEnd/TunerHost.cs
Normal file
117
TVHeadEnd/TunerHost.cs
Normal file
@ -0,0 +1,117 @@
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TVHeadEnd.HTSP;
|
||||
using TVHeadEnd.HTSP_Responses;
|
||||
using TVHeadEnd.TimeoutHelper;
|
||||
|
||||
|
||||
namespace TVHeadEnd
|
||||
{
|
||||
/*
|
||||
class TunerHost : ITunerHost
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private volatile int _subscriptionId = 0;
|
||||
private HTSConnectionHandler _htsConnectionHandler;
|
||||
|
||||
public TunerHost(ILogger logger)
|
||||
{
|
||||
logger.Info("[TVHclient] TunerHost()");
|
||||
_logger = logger;
|
||||
_htsConnectionHandler = HTSConnectionHandler.GetInstance(_logger);
|
||||
}
|
||||
|
||||
public string Name { get { return "TVHclient-TunerHost"; } }
|
||||
|
||||
public string Type { get { return "Live-TV"; } }
|
||||
|
||||
public Task<IEnumerable<ChannelInfo>> GetChannels(CancellationToken cancellationToken)
|
||||
{
|
||||
int timeOut = _htsConnectionHandler.WaitForInitialLoad(cancellationToken);
|
||||
if (timeOut == -1 || cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.Info("[TVHclient] TunerHost.GetChannels, call canceled or timed out - returning empty list.");
|
||||
return Task.Factory.StartNew<IEnumerable<ChannelInfo>>(() =>
|
||||
{
|
||||
return new List<ChannelInfo>();
|
||||
});
|
||||
}
|
||||
|
||||
return _htsConnectionHandler.BuildChannelInfos(cancellationToken);
|
||||
}
|
||||
|
||||
public Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
|
||||
{
|
||||
HTSMessage getTicketMessage = new HTSMessage();
|
||||
getTicketMessage.Method = "getTicket";
|
||||
getTicketMessage.putField("channelId", channelId);
|
||||
|
||||
LoopBackResponseHandler lbrh = new LoopBackResponseHandler();
|
||||
_htsConnectionHandler.SendMessage(getTicketMessage, lbrh);
|
||||
HTSMessage getTicketResponse = lbrh.getResponse();
|
||||
|
||||
|
||||
if (_subscriptionId == int.MaxValue)
|
||||
{
|
||||
_subscriptionId = 0;
|
||||
}
|
||||
int currSubscriptionId = _subscriptionId++;
|
||||
|
||||
return Task.Factory.StartNew<MediaSourceInfo>(() =>
|
||||
{
|
||||
return new MediaSourceInfo
|
||||
{
|
||||
Id = "" + currSubscriptionId,
|
||||
Path = _htsConnectionHandler.GetHttpBaseUrl() + getTicketResponse.getString("path") + "?ticket=" + getTicketResponse.getString("ticket"),
|
||||
Protocol = MediaProtocol.Http,
|
||||
MediaStreams = new List<MediaStream>
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Video,
|
||||
// Set the index to -1 because we don't know the exact index of the video stream within the container
|
||||
Index = -1,
|
||||
// Set to true if unknown to enable deinterlacing
|
||||
IsInterlaced = true
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Audio,
|
||||
// Set the index to -1 because we don't know the exact index of the audio stream within the container
|
||||
Index = -1
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
throw new TimeoutException("");
|
||||
}
|
||||
|
||||
public Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Fatal("[TVHclient] TunerHost.GetChannelStreamMediaSources called for channelID '" + channelId + "'");
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
||||
{
|
||||
// TVHeadend can't deliver TunerInfo data
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Validate(TunerHostInfo info)
|
||||
{
|
||||
// TVHeadend can't deliver TunerInfo data
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
5
TVHeadEnd/packages.config
Normal file
5
TVHeadEnd/packages.config
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="MediaBrowser.Common" version="3.0.680" targetFramework="net45" />
|
||||
<package id="MediaBrowser.Server.Core" version="3.0.680" targetFramework="net45" />
|
||||
</packages>
|
31
Tvheadend.sln
Normal file
31
Tvheadend.sln
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F17571BB-66D4-4DB5-8EC0-33A76DD5B018}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
SharedVersion.cs = SharedVersion.cs
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TVHeadEnd", "TVHeadEnd\TVHeadEnd.csproj", "{255072D7-49B0-4E0A-93D4-C283144499B7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{255072D7-49B0-4E0A-93D4-C283144499B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{255072D7-49B0-4E0A-93D4-C283144499B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{255072D7-49B0-4E0A-93D4-C283144499B7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{255072D7-49B0-4E0A-93D4-C283144499B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{255072D7-49B0-4E0A-93D4-C283144499B7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{255072D7-49B0-4E0A-93D4-C283144499B7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
Loading…
Reference in New Issue
Block a user