New Upstream Release - kodi-pvr-sledovanitv-cz
Ready changes
Summary
Merged new upstream version: 20.6.0+ds1 (was: 20.3.0+ds1).
Resulting package
Built on 2023-07-25T12:02 (took 5m0s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases kodi-pvr-sledovanitv-cz-dbgsymapt install -t fresh-releases kodi-pvr-sledovanitv-cz
Lintian Result
Diff
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..986fd50
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,35 @@
+<!-- Provide a general summary of the issue in the Title above -->
+<!-- You could delete sections and/or questions irrelevant to your report -->
+
+##### Expected Behavior
+<!-- If you're describing a bug, tell us what should happen -->
+<!-- If you're suggesting a change/improvement, tell us how it should work -->
+
+##### Current Behavior
+<!-- If describing a bug, tell us what happens instead of the expected behavior -->
+<!-- If suggesting a change/improvement, explain the difference from current behavior -->
+
+##### Possible Solution
+<!-- Not obligatory, but suggest a fix/reason for the bug, -->
+<!-- or ideas how to implement the addition or change -->
+
+##### Steps to Reproduce (for bugs)
+<!-- Provide a link to a live example, or an unambiguous set of steps to -->
+<!-- reproduce this bug. Include code to reproduce, if relevant -->
+1.
+2.
+3.
+4.
+
+##### Context
+<!-- How has this issue affected you? What are you trying to accomplish? -->
+<!-- Providing context helps us come up with a solution that is most useful in the real world -->
+
+##### System Information
+<!-- Include as many relevant details about the system you experienced the bug in -->
+* pvr.sledovani.tv Version:
+* Kodi Version:
+* OS Version:
+
+##### Kodi's Log
+<!-- Include relevant parts of log, that may help -->
diff --git a/.github/build.yml b/.github/build.yml
new file mode 100644
index 0000000..03e1d49
--- /dev/null
+++ b/.github/build.yml
@@ -0,0 +1,61 @@
+name: Build and run tests
+on: [push, pull_request]
+env:
+ app_id: pvr.sledovanitv.cz
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: "Debian package test"
+ os: ubuntu-18.04
+ CC: gcc
+ CXX: g++
+ DEBIAN_BUILD: true
+ - os: ubuntu-18.04
+ CC: gcc
+ CXX: g++
+ - os: ubuntu-18.04
+ CC: clang
+ CXX: clang++
+ - os: macos-10.15
+ steps:
+ - name: Install needed ubuntu depends
+ env:
+ DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }}
+ run: |
+ if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/xbmc-nightly; fi
+ if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get update; fi
+ if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get install fakeroot; fi
+ - name: Checkout Kodi repo
+ uses: actions/checkout@v2
+ with:
+ repository: xbmc/xbmc
+ ref: master
+ path: xbmc
+ - name: Checkout pvr.sledovanitv.cz repo
+ uses: actions/checkout@v2
+ with:
+ path: ${{ env.app_id }}
+ - name: Configure
+ env:
+ CC: ${{ matrix.CC }}
+ CXX: ${{ matrix.CXX }}
+ DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }}
+ run: |
+ if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id} && mkdir -p build && cd build; fi
+ if [[ $DEBIAN_BUILD != true ]]; then cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=${{ github.workspace }} -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/xbmc/addons -DPACKAGE_ZIP=1 ${{ github.workspace }}/xbmc/cmake/addons; fi
+ if [[ $DEBIAN_BUILD == true ]]; then wget https://raw.githubusercontent.com/xbmc/xbmc/master/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; fi
+ if [[ $DEBIAN_BUILD == true ]]; then sudo apt-get build-dep ${{ github.workspace }}/${app_id}; fi
+ - name: Build
+ env:
+ CC: ${{ matrix.CC }}
+ CXX: ${{ matrix.CXX }}
+ DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }}
+ run: |
+ if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id}/build; fi
+ if [[ $DEBIAN_BUILD != true ]]; then make; fi
+ if [[ $DEBIAN_BUILD == true ]]; then ./debian-addon-package-test.sh ${{ github.workspace }}/${app_id}; fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 429eecd..09fd7ef 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,12 +14,14 @@ set(DEPLIBS ${JSONCPP_LIBRARIES})
set(SLEDOVANITV_SOURCES
src/ApiManager.cpp
- src/Data.cpp)
+ src/Data.cpp
+ src/Addon.cpp)
set(SLEDOVANITV_HEADERS
src/ApiManager.h
src/CallLimiter.hh
- src/Data.h)
+ src/Data.h
+ src/Addon.h)
if(WIN32)
add_definitions("/wd4996")
diff --git a/README.md b/README.md
index 43f400b..5d33a10 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ unofficial [sledovanitv.cz](https://sledovanitv.cz) PVR client addon for [Kodi](
### Linux
-1. `git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git`
+1. `git clone --branch Nexus --depth=1 https://github.com/xbmc/xbmc.git`
2. `git clone --depth=1 https://github.com/palinek/pvr.sledovanitv.cz.git`
3. `cd pvr.sledovanitv.cz && mkdir build && cd build`
4. `cmake -DADDONS_TO_BUILD=pvr.sledovanitv.cz -DADDON_SRC_PREFIX=../.. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=xbmc/addons -DPACKAGE_ZIP=1 -DADDONS_DEFINITION_DIR="$(pwd)/../xbmc/cmake/addons/addons" ../../xbmc/cmake/addons`
diff --git a/debian/changelog b/debian/changelog
index 3973bdb..c4338a8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+kodi-pvr-sledovanitv-cz (20.6.0+ds1-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Tue, 25 Jul 2023 11:57:29 -0000
+
kodi-pvr-sledovanitv-cz (20.3.0+ds1-1) unstable; urgency=medium
* New upstream version 20.3.0+ds1
diff --git a/pvr.sledovanitv.cz/addon.xml.in b/pvr.sledovanitv.cz/addon.xml.in
index 21b0d27..e53779b 100644
--- a/pvr.sledovanitv.cz/addon.xml.in
+++ b/pvr.sledovanitv.cz/addon.xml.in
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon
id="pvr.sledovanitv.cz"
- version="20.3.0"
+ version="20.6.0"
name="Sledovanitv.cz Client (unofficial)"
provider-name="palinek">
<requires>@ADDON_DEPENDS@
- <import addon="inputstream.adaptive" minversion="2.5.5" optional="true"/>
+ <import addon="inputstream.adaptive" minversion="20.3.9" optional="true"/>
</requires>
<extension
point="kodi.pvrclient"
diff --git a/pvr.sledovanitv.cz/resources/instance-settings.xml b/pvr.sledovanitv.cz/resources/instance-settings.xml
new file mode 100644
index 0000000..df74095
--- /dev/null
+++ b/pvr.sledovanitv.cz/resources/instance-settings.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<settings version="1">
+ <section id="pvr.sledovanitv.cz" label="30011">
+
+ <category id="basic" label="30010">
+ <group id="1" label="30010">
+ <setting id="serviceProvider" type="integer" label="30013">
+ <level>0</level>
+ <default>0</default> <!-- ServiceProvider_t::SP_DEFAULT -->
+ <constraints>
+ <options>
+ <option label="30014">0</option> <!-- ServiceProvider_t::SP_SLEDOVANITV_CZ -->
+ <option label="30015">1</option> <!-- ServiceProvider_t::SP_MODERNITV_CZ -->
+ </options>
+ </constraints>
+ <control type="list" format="string" />
+ </setting>
+ <setting id="userName" type="string" label="30000">
+ <level>0</level>
+ <default></default>
+ <constraints>
+ <allowempty>true</allowempty>
+ </constraints>
+ <control type="edit" format="string"/>
+ </setting>
+ <setting id="password" type="string" label="30001">
+ <level>0</level>
+ <default></default>
+ <constraints>
+ <allowempty>true</allowempty>
+ </constraints>
+ <control type="edit" format="string">
+ <hidden>true</hidden>
+ </control>
+ </setting>
+ <setting id="deviceId" type="string" label="30008">
+ <level>3</level>
+ <default></default>
+ <constraints>
+ <allowempty>true</allowempty>
+ </constraints>
+ <control type="edit" format="string" />
+ </setting>
+ <setting id="productId" type="string" label="30012">
+ <level>3</level>
+ <default></default>
+ <constraints>
+ <allowempty>true</allowempty>
+ </constraints>
+ <control type="edit" format="string" />
+ </setting>
+ <setting id="streamQuality" type="integer" label="30002">
+ <level>1</level>
+ <default>0</default> <!-- StreamQuality_t::SQ_DEFAULT -->
+ <constraints>
+ <options>
+ <option label="30003">20</option> <!-- StreamQuality_t::SQ_SD -->
+ <option label="30004">40</option> <!-- StreamQuality_t::SQ_HD -->
+ </options>
+ </constraints>
+ <control type="list" format="string" />
+ </setting>
+ <setting id="useH265" type="boolean" label="30005">
+ <level>1</level>
+ <default>false</default>
+ <control type="toggle" />
+ </setting>
+ <setting id="useAdaptive" type="boolean" label="30006">
+ <level>2</level>
+ <default>true</default>
+ <control type="toggle" />
+ </setting>
+ <setting id="showLockedChannels" type="boolean" label="30007">
+ <level>1</level>
+ <default>true</default>
+ <control type="toggle" />
+ </setting>
+ <setting id="showLockedOnlyPin" type="boolean" label="30009">
+ <level>1</level>
+ <default>false</default>
+ <dependencies>
+ <dependency type="enable" setting="showLockedChannels">true</dependency>
+ </dependencies>
+ <control type="toggle" />
+ </setting>
+ </group>
+ </category>
+
+ <category id="refresh" label="30100">
+ <group id="1" label="30100">
+ <setting id="fullChannelEpgRefresh" type="integer" label="30101">
+ <level>3</level>
+ <default>24</default>
+ <constraints>
+ <minimum>1</minimum>
+ <step>1</step>
+ <maximum>48</maximum>
+ </constraints>
+ <control type="spinner" format="string">
+ <formatlabel>17998</formatlabel>
+ </control>
+ </setting>
+ <setting id="loadingsRefresh" type="integer" label="30102">
+ <level>3</level>
+ <default>60</default>
+ <constraints>
+ <minimum>20</minimum>
+ <step>10</step>
+ <maximum>300</maximum>
+ </constraints>
+ <control type="spinner" format="string">
+ <formatlabel>14045</formatlabel>
+ </control>
+ </setting>
+ <setting id="keepAliveDelay" type="integer" label="30103">
+ <level>3</level>
+ <default>20</default>
+ <constraints>
+ <minimum>15</minimum>
+ <step>1</step>
+ <maximum>120</maximum>
+ </constraints>
+ <control type="spinner" format="string">
+ <formatlabel>14045</formatlabel>
+ </control>
+ </setting>
+ <setting id="epgCheckDelay" type="integer" label="30104">
+ <level>3</level>
+ <default>1</default>
+ <constraints>
+ <minimum>1</minimum>
+ <step>1</step>
+ <maximum>120</maximum>
+ </constraints>
+ <control type="spinner" format="string">
+ <formatlabel>14044</formatlabel>
+ </control>
+ </setting>
+ </group>
+ </category>
+
+ </section>
+</settings>
diff --git a/pvr.sledovanitv.cz/resources/settings.xml b/pvr.sledovanitv.cz/resources/settings.xml
index df74095..55362e3 100644
--- a/pvr.sledovanitv.cz/resources/settings.xml
+++ b/pvr.sledovanitv.cz/resources/settings.xml
@@ -1,140 +1,81 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<settings version="1">
- <section id="pvr.sledovanitv.cz" label="30011">
+ <section id="pvr.sledovanitv.cz" label="-1" help="-1">
- <category id="basic" label="30010">
- <group id="1" label="30010">
- <setting id="serviceProvider" type="integer" label="30013">
+ <category id="hidden_basic">
+ <group id="1" label="-1">
+ <setting id="serviceProvider" type="integer" label="-1">
<level>0</level>
<default>0</default> <!-- ServiceProvider_t::SP_DEFAULT -->
- <constraints>
- <options>
- <option label="30014">0</option> <!-- ServiceProvider_t::SP_SLEDOVANITV_CZ -->
- <option label="30015">1</option> <!-- ServiceProvider_t::SP_MODERNITV_CZ -->
- </options>
- </constraints>
- <control type="list" format="string" />
</setting>
- <setting id="userName" type="string" label="30000">
+ <setting id="userName" type="string" label="-1">
<level>0</level>
<default></default>
<constraints>
<allowempty>true</allowempty>
</constraints>
- <control type="edit" format="string"/>
</setting>
- <setting id="password" type="string" label="30001">
+ <setting id="password" type="string" label="-1">
<level>0</level>
<default></default>
<constraints>
<allowempty>true</allowempty>
</constraints>
- <control type="edit" format="string">
- <hidden>true</hidden>
- </control>
</setting>
- <setting id="deviceId" type="string" label="30008">
+ <setting id="deviceId" type="string" label="-1">
<level>3</level>
<default></default>
<constraints>
<allowempty>true</allowempty>
</constraints>
- <control type="edit" format="string" />
</setting>
- <setting id="productId" type="string" label="30012">
+ <setting id="productId" type="string" label="-1">
<level>3</level>
<default></default>
<constraints>
<allowempty>true</allowempty>
</constraints>
- <control type="edit" format="string" />
</setting>
- <setting id="streamQuality" type="integer" label="30002">
+ <setting id="streamQuality" type="integer" label="-1">
<level>1</level>
<default>0</default> <!-- StreamQuality_t::SQ_DEFAULT -->
- <constraints>
- <options>
- <option label="30003">20</option> <!-- StreamQuality_t::SQ_SD -->
- <option label="30004">40</option> <!-- StreamQuality_t::SQ_HD -->
- </options>
- </constraints>
- <control type="list" format="string" />
</setting>
- <setting id="useH265" type="boolean" label="30005">
+ <setting id="useH265" type="boolean" label="-1">
<level>1</level>
<default>false</default>
- <control type="toggle" />
</setting>
- <setting id="useAdaptive" type="boolean" label="30006">
+ <setting id="useAdaptive" type="boolean" label="-1">
<level>2</level>
<default>true</default>
- <control type="toggle" />
</setting>
- <setting id="showLockedChannels" type="boolean" label="30007">
+ <setting id="showLockedChannels" type="boolean" label="-1">
<level>1</level>
<default>true</default>
- <control type="toggle" />
</setting>
- <setting id="showLockedOnlyPin" type="boolean" label="30009">
+ <setting id="showLockedOnlyPin" type="boolean" label="-1">
<level>1</level>
<default>false</default>
- <dependencies>
- <dependency type="enable" setting="showLockedChannels">true</dependency>
- </dependencies>
- <control type="toggle" />
</setting>
</group>
</category>
- <category id="refresh" label="30100">
- <group id="1" label="30100">
- <setting id="fullChannelEpgRefresh" type="integer" label="30101">
+ <category id="hidden_refresh" label="-1">
+ <group id="1" label="-1">
+ <setting id="fullChannelEpgRefresh" type="integer" label="-1">
<level>3</level>
<default>24</default>
- <constraints>
- <minimum>1</minimum>
- <step>1</step>
- <maximum>48</maximum>
- </constraints>
- <control type="spinner" format="string">
- <formatlabel>17998</formatlabel>
- </control>
</setting>
- <setting id="loadingsRefresh" type="integer" label="30102">
+ <setting id="loadingsRefresh" type="integer" label="-1">
<level>3</level>
<default>60</default>
- <constraints>
- <minimum>20</minimum>
- <step>10</step>
- <maximum>300</maximum>
- </constraints>
- <control type="spinner" format="string">
- <formatlabel>14045</formatlabel>
- </control>
</setting>
- <setting id="keepAliveDelay" type="integer" label="30103">
+ <setting id="keepAliveDelay" type="integer" label="-1">
<level>3</level>
<default>20</default>
- <constraints>
- <minimum>15</minimum>
- <step>1</step>
- <maximum>120</maximum>
- </constraints>
- <control type="spinner" format="string">
- <formatlabel>14045</formatlabel>
- </control>
</setting>
- <setting id="epgCheckDelay" type="integer" label="30104">
+ <setting id="epgCheckDelay" type="integer" label="-1">
<level>3</level>
<default>1</default>
- <constraints>
- <minimum>1</minimum>
- <step>1</step>
- <maximum>120</maximum>
- </constraints>
- <control type="spinner" format="string">
- <formatlabel>14044</formatlabel>
- </control>
</setting>
</group>
</category>
diff --git a/src/Addon.cpp b/src/Addon.cpp
new file mode 100644
index 0000000..f48ecf4
--- /dev/null
+++ b/src/Addon.cpp
@@ -0,0 +1,71 @@
+#include "Addon.h"
+
+#include "Data.h"
+#include "kodi/General.h"
+#include <memory>
+
+namespace sledovanitvcz
+{
+ ADDON_STATUS Addon::Create()
+ {
+ kodi::Log(ADDON_LOG_DEBUG, "%s - Creating the PVR sledovanitv.cz (unofficial)", __FUNCTION__);
+ return ADDON_STATUS_OK;
+ }
+
+ ADDON_STATUS Addon::SetSetting(const std::string & settingName, const kodi::addon::CSettingValue & settingValue)
+ {
+ // just force our data to be re-created
+ return ADDON_STATUS_NEED_RESTART;
+ }
+
+ ADDON_STATUS Addon::CreateInstance(const kodi::addon::IInstanceInfo & instance, KODI_ADDON_INSTANCE_HDL & hdl)
+ {
+ kodi::Log(ADDON_LOG_DEBUG, "%s - Creating instance %d PVR sledovanitv.cz (unofficial)", __FUNCTION__, instance.GetNumber());
+
+ if (!instance.IsType(ADDON_INSTANCE_PVR))
+ return ADDON_STATUS_UNKNOWN;
+
+ TryMigrate(instance);
+
+ hdl = new Data{instance};
+ return ADDON_STATUS_OK;
+ }
+
+ void Addon::DestroyInstance(const kodi::addon::IInstanceInfo & instance, const KODI_ADDON_INSTANCE_HDL hdl)
+ {
+ // From parent doc:
+ /// @warning This call is only used to inform that the associated instance
+ /// is terminated. The deletion is carried out in the background.
+
+ // So we don't need to do anything here
+ //KODI_ADDON_INSTANCE_STRUCT * inst_struct = instance;
+ //delete inst_struct->pvr;
+ }
+
+ void Addon::TryMigrate(const kodi::addon::IInstanceInfo & instance)
+ {
+ auto test = std::make_unique<kodi::addon::IAddonInstance>(instance);
+ // pre-multi-instance migration
+ if (test->GetInstanceSettingString("kodi_addon_instance_name").empty())
+ {
+ kodi::QueueFormattedNotification(QUEUE_INFO, "Migrating pre-multi-instance settings to 'Migrated Add-on Config'...");
+ test->SetInstanceSettingString("kodi_addon_instance_name", "Migrated Add-on Config");
+ test->SetInstanceSettingEnum<ApiManager::ServiceProvider_t>("serviceProvider", kodi::addon::GetSettingEnum<ApiManager::ServiceProvider_t>("serviceProvider", ApiManager::SP_DEFAULT));
+ test->SetInstanceSettingString("userName", kodi::addon::GetSettingString("userName"));
+ test->SetInstanceSettingString("password", kodi::addon::GetSettingString("password"));
+ test->SetInstanceSettingString("deviceId", kodi::addon::GetSettingString("deviceId"));
+ test->SetInstanceSettingString("productId", kodi::addon::GetSettingString("productId"));
+ test->SetInstanceSettingEnum<ApiManager::StreamQuality_t>("streamQuality", kodi::addon::GetSettingEnum<ApiManager::StreamQuality_t>("streamQuality", ApiManager::SQ_DEFAULT));
+ test->SetInstanceSettingInt("fullChannelEpgRefresh", kodi::addon::GetSettingInt("fullChannelEpgRefresh", 24));
+ test->SetInstanceSettingInt("loadingsRefresh", kodi::addon::GetSettingInt("loadingsRefresh", 60));
+ test->SetInstanceSettingInt("keepAliveDelay", kodi::addon::GetSettingInt("keepAliveDelay", 20));
+ test->SetInstanceSettingInt("epgCheckDelay", kodi::addon::GetSettingInt("epgCheckDelay", 1));
+ test->SetInstanceSettingBoolean("useH265", kodi::addon::GetSettingBoolean("useH265", false));
+ test->SetInstanceSettingBoolean("useAdaptive", kodi::addon::GetSettingBoolean("useAdaptive", false));
+ test->SetInstanceSettingBoolean("showLockedChannels", kodi::addon::GetSettingBoolean("showLockedChannels", true));
+ test->SetInstanceSettingBoolean("showLockedOnlyPin", kodi::addon::GetSettingBoolean("showLockedOnlyPin", true));
+ }
+ }
+} //namespace sledovanitvcz
+
+ADDONCREATOR(sledovanitvcz::Addon)
diff --git a/src/Addon.h b/src/Addon.h
new file mode 100644
index 0000000..1ba0820
--- /dev/null
+++ b/src/Addon.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023~now Palo Kisa <palo.kisa@gmail.com>
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this addon; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#pragma once
+
+#include "kodi/AddonBase.h"
+
+namespace sledovanitvcz
+{
+ class ATTR_DLL_LOCAL Addon : public kodi::addon::CAddonBase
+ {
+ public:
+ Addon() = default;
+
+ virtual ADDON_STATUS Create() override;
+ virtual ADDON_STATUS SetSetting(const std::string & settingName, const kodi::addon::CSettingValue & settingValue) override;
+ virtual ADDON_STATUS CreateInstance(const kodi::addon::IInstanceInfo & instance, KODI_ADDON_INSTANCE_HDL & hdl) override;
+ virtual void DestroyInstance(const kodi::addon::IInstanceInfo & instance, const KODI_ADDON_INSTANCE_HDL hdl) override;
+
+ private:
+ static void TryMigrate(const kodi::addon::IInstanceInfo & instance);
+ };
+
+} //namespace sledovanitvcz
diff --git a/src/ApiManager.cpp b/src/ApiManager.cpp
index a2b3f78..8b58da6 100644
--- a/src/ApiManager.cpp
+++ b/src/ApiManager.cpp
@@ -60,6 +60,7 @@
#include <iomanip>
#include <algorithm>
#include <atomic>
+#include <chrono>
namespace sledovanitvcz
{
@@ -81,23 +82,6 @@ char to_hex(char code)
return hex[code & 15];
}
-char *url_encode(const char *str)
-{
- char *pstr = (char*) str, *buf = (char *)malloc(strlen(str) * 3 + 1), *pbuf = buf;
- while (*pstr)
-{
- if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
- *pbuf++ = *pstr;
- else if (*pstr == ' ')
- *pbuf++ = '+';
- else
- *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
- pstr++;
- }
- *pbuf = '\0';
- return buf;
-}
-
#if defined(TARGET_ANDROID) && __ANDROID_API__ < 24
// Note: declare dummy "ifaddr" functions to make this compile on pre API-24 versions
#warning "Compiling for ANDROID with API version < 24, getting MAC addres will not be available."
@@ -187,12 +171,14 @@ ApiManager::ApiManager(ServiceProvider_t serviceProvider
, const std::string & userName
, const std::string & userPassword
, const std::string & overridenMac
- , const std::string & product)
+ , const std::string & product
+ , uint64_t instanceNo)
: m_serviceProvider{serviceProvider}
, m_userName{userName}
, m_userPassword{userPassword}
, m_overridenMac{overridenMac}
, m_product{product}
+ , m_instanceNo{instanceNo}
, m_pinUnlocked{false}
, m_sessionId{std::make_shared<std::string>()}
{
@@ -209,8 +195,11 @@ std::string ApiManager::call(const std::string & urlPath, const ApiParams_t & pa
return std::string();
}
std::string url = urlPath;
- url += '?';
- url += buildQueryString(paramsMap, putSessionVar);
+ if (!paramsMap.empty())
+ {
+ url += '?';
+ url += buildQueryString(paramsMap, putSessionVar);
+ }
// add User-Agent header... TODO: make it configurable
url += "|User-Agent=okhttp%2F3.12.0";
std::string response;
@@ -277,6 +266,7 @@ bool ApiManager::deletePairing(const Json::Value & root)
Json::Value del_root;
if (isSuccess(response, del_root)
|| (del_root.get("error", "").asString() == "no device")
+ || (del_root.get("error", "").asString() == "not logged")
)
{
kodi::Log(ADDON_LOG_INFO, "Previous pairing(deviceId:%s) deleted (or no such device)", old_dev_id.c_str());
@@ -289,13 +279,15 @@ bool ApiManager::deletePairing(const Json::Value & root)
bool ApiManager::pairDevice(Json::Value & root)
{
bool new_pairing = false;
- std::string pairJson = readPairFile();
+ std::string pairJson = readPairFile(getPairFilePath());
std::string macAddr = m_overridenMac.empty() ? get_mac_address() : m_overridenMac;
if (macAddr.empty())
{
- kodi::Log(ADDON_LOG_INFO, "Unable to get MAC address, using a dummy for serial");
- macAddr = "11223344";
+ std::ostringstream os;
+ os << std::chrono::high_resolution_clock::now().time_since_epoch().count();
+ macAddr = os.str();
+ kodi::Log(ADDON_LOG_INFO, "Unable to get MAC address, using a dummy(%s) for serial", macAddr.c_str());
}
// compute SHA256 of string representation of MAC address
m_serial = picosha2::hash256_hex_string(macAddr);
@@ -420,6 +412,32 @@ bool ApiManager::login()
return success;
}
+bool ApiManager::registerDrm(std::string & licenseUrl, std::string & certificate) const
+{
+ ApiParams_t param;
+ param.emplace_back("type", "widevine");
+
+ const std::string response = apiCall("drm-registration", param, true);
+ Json::Value root;
+ if (!isSuccess(response, root))
+ return false;
+
+ const Json::Value & info = const_cast<const Json::Value &>(root)["info"];
+ if (info["type"].asString() != "widevine")
+ kodi::Log(ADDON_LOG_WARNING, "Expected DRM type widevine, got %s. DRM may not work", info["type"].asString().c_str());
+ if (info["licenseHandler"]["requestEncoding"].asString() != "binary")
+ kodi::Log(ADDON_LOG_WARNING, "Expected DRM requestEncoding binary, got %s. DRM may not work", info["licenseHandler"]["requestEncoding"].asString().c_str());
+ if (info["licenseHandler"]["responseEncoding"].asString() != "binary")
+ kodi::Log(ADDON_LOG_WARNING, "Expected DRM responseEncoding binary, got %s. DRM may not work", info["licenseHandler"]["responseEncoding"].asString().c_str());
+ licenseUrl = info["licenseUrl"].asString();
+ if (info["licenseUrl"].empty())
+ kodi::Log(ADDON_LOG_WARNING, "Got empty DRM licenseUrl. DRM may not work");
+ certificate = call(info["certificateUrl"].asString(), ApiParams_t{}, false);
+ if (certificate.empty())
+ kodi::Log(ADDON_LOG_WARNING, "Got empty DRM certificate from %s. DRM may not work", info["certificateUrl"].asString().c_str());
+ return true;
+}
+
bool ApiManager::pinUnlock(const std::string & pin)
{
ApiParams_t params;
@@ -464,7 +482,7 @@ bool ApiManager::getEpg(time_t start, bool smallDuration, const std::string & ch
params.emplace_back("time", formatTime(start));
params.emplace_back("duration", smallDuration ? "60" : "1439");
- params.emplace_back("detail", "description,poster");
+ params.emplace_back("detail", "description,score,poster,rating");
params.emplace_back("allowOrder", "1");
if (!channels.empty())
params.emplace_back("channels", std::move(channels));
@@ -477,7 +495,7 @@ bool ApiManager::getPvr(Json::Value & root)
return isSuccess(apiCall("get-pvr", ApiParams_t()), root);
}
-std::string ApiManager::getRecordingUrl(const std::string &recId, std::string & channel)
+std::string ApiManager::getRecordingUrl(const std::string &recId, std::string & channel, bool & isDrm)
{
ApiParams_t param;
param.emplace_back("recordId", recId);
@@ -488,6 +506,7 @@ std::string ApiManager::getRecordingUrl(const std::string &recId, std::string &
if (isSuccess(apiCall("record-timeshift", param), root))
{
channel = root.get("channel", "").asString();
+ isDrm = root.get("drm", 0).asInt() != 0;
return root.get("url", "").asString();
}
@@ -553,10 +572,21 @@ bool ApiManager::loggedIn() const
std::string ApiManager::urlEncode(const std::string &str)
{
- std::string strOut;
- strOut.append(url_encode(str.c_str()));
-
- return strOut;
+ std::string result;
+ for (const auto c : str)
+ {
+ if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
+ result += c;
+ else if (c == ' ')
+ result += '+';
+ else
+ {
+ result += '%';
+ result += to_hex(c >> 4);
+ result += to_hex(c & 15);
+ }
+ }
+ return result;
}
std::string ApiManager::buildQueryString(const ApiParams_t & paramMap, bool putSessionVar) const
@@ -583,15 +613,21 @@ std::string ApiManager::buildQueryString(const ApiParams_t & paramMap, bool putS
return strOut;
}
-std::string ApiManager::readPairFile()
+std::string ApiManager::getPairFilePath() const
+{
+ std::ostringstream os;
+ os << PAIR_FILE << '-' << m_instanceNo;
+ return kodi::addon::GetUserPath(os.str());
+}
+
+std::string ApiManager::readPairFile(const std::string & pairFile)
{
- std::string url = kodi::addon::GetUserPath(PAIR_FILE);
std::string strContent;
- kodi::Log(ADDON_LOG_DEBUG, "Openning file %s", url.c_str());
+ kodi::Log(ADDON_LOG_DEBUG, "Openning file %s", pairFile.c_str());
kodi::vfs::CFile fileHandle;
- if (fileHandle.OpenFile(url, 0))
+ if (fileHandle.OpenFile(pairFile, 0))
{
char buffer[1024];
while (int bytesRead = fileHandle.Read(buffer, 1024))
@@ -601,12 +637,10 @@ std::string ApiManager::readPairFile()
return strContent;
}
-void ApiManager::createPairFile(Json::Value & contentRoot)
+void ApiManager::createPairFile(Json::Value & contentRoot) const
{
- std::string url = kodi::addon::GetUserPath(PAIR_FILE);
-
kodi::vfs::CFile fileHandle;
- if (fileHandle.OpenFileForWrite(url, true))
+ if (fileHandle.OpenFileForWrite(getPairFilePath(), true))
{
std::ostringstream os;
os << contentRoot;
diff --git a/src/ApiManager.h b/src/ApiManager.h
index 3dc575a..9571bf6 100644
--- a/src/ApiManager.h
+++ b/src/ApiManager.h
@@ -59,6 +59,7 @@ public:
};
public:
static std::string formatTime(time_t t);
+ static std::string urlEncode(const std::string &str);
public:
ApiManager(ServiceProvider_t serviceProvider
@@ -66,6 +67,7 @@ public:
, const std::string & userPassword
, const std::string & overridenMac //!< device identifier (value for overriding the MAC address detection)
, const std::string & product //!< product identifier (value for overriding the hostname detection)
+ , uint64_t instanceNo
);
bool login();
@@ -74,7 +76,7 @@ public:
bool getStreamQualities(Json::Value & root);
bool getEpg(time_t start, bool smallDuration, const std::string & channels, Json::Value & root);
bool getPvr(Json::Value & root);
- std::string getRecordingUrl(const std::string &recId, std::string & channel);
+ std::string getRecordingUrl(const std::string &recId, std::string & channel, bool & isDrm);
bool getTimeShiftInfo(const std::string &eventId
, std::string & streamUrl
, std::string & channel
@@ -84,11 +86,10 @@ public:
bool keepAlive();
bool loggedIn() const;
bool pinUnlocked() const;
+ bool registerDrm(std::string & licenseUrl, std::string & certificate) const;
private:
- static std::string urlEncode(const std::string &str);
- static std::string readPairFile();
- static void createPairFile(Json::Value & contentRoot);
+ static std::string readPairFile(const std::string & pairFile);
static bool isSuccess(const std::string &response, Json::Value & root);
static bool isSuccess(const std::string &response);
@@ -97,6 +98,8 @@ private:
std::string apiCall(const std::string &function, const ApiParams_t & paramsMap, bool putSessionVar = true) const;
bool pairDevice(Json::Value & root);
bool deletePairing(const Json::Value & root);
+ std::string getPairFilePath() const;
+ void createPairFile(Json::Value & contentRoot) const;
static const std::string API_URL[SP_END];
static const std::string API_UNIT[SP_END];
@@ -106,6 +109,7 @@ private:
const std::string m_userPassword;
const std::string m_overridenMac;
const std::string m_product;
+ const uint64_t m_instanceNo;
std::string m_serial;
std::string m_deviceId;
std::string m_password;
diff --git a/src/Data.cpp b/src/Data.cpp
index a086e36..f32f9bc 100644
--- a/src/Data.cpp
+++ b/src/Data.cpp
@@ -36,6 +36,7 @@
#include "Data.h"
#include "CallLimiter.hh"
+#include "base64.hpp"
#include "kodi/General.h"
#include "kodi/Filesystem.h"
#include "kodi/gui/dialogs/Numeric.h"
@@ -79,8 +80,9 @@ static inline unsigned DiffBetweenPragueAndLocalTime(const time_t * when = nullp
return diff - (isdst > 0 ? 7200 : 3600);
}
-Data::Data()
- : m_bKeepAlive{true}
+Data::Data(const kodi::addon::IInstanceInfo& instance)
+ : kodi::addon::CInstancePVRClient{instance}
+ , m_bKeepAlive{true}
, m_bLoadRecordings{true}
, m_bChannelsLoaded{false}
, m_groups{std::make_shared<group_container_t>()}
@@ -98,16 +100,31 @@ Data::Data()
, m_iLastStart{0}
, m_iLastEnd{0}
, m_manager{
- kodi::addon::GetSettingEnum<ApiManager::ServiceProvider_t>("serviceProvider", ApiManager::SP_DEFAULT)
- , kodi::addon::GetSettingString("userName")
- , kodi::addon::GetSettingString("password")
- , kodi::addon::GetSettingString("deviceId")
- , kodi::addon::GetSettingString("productId")
+ GetInstanceSettingEnum<ApiManager::ServiceProvider_t>("serviceProvider", ApiManager::SP_DEFAULT)
+ , GetInstanceSettingString("userName")
+ , GetInstanceSettingString("password")
+ , GetInstanceSettingString("deviceId")
+ , GetInstanceSettingString("productId")
+ , instance.GetNumber()
}
{
+ if (!kodi::vfs::DirectoryExists(UserPath()))
+ {
+ kodi::vfs::CreateDirectory(UserPath());
+ }
SetEPGMaxDays(m_epgMaxFutureDays, m_epgMaxPastDays);
+ m_streamQuality = GetInstanceSettingEnum<ApiManager::StreamQuality_t>("streamQuality", ApiManager::SQ_DEFAULT);
+ m_fullChannelEpgRefresh = GetInstanceSettingInt("fullChannelEpgRefresh", 24) * 3600; // make it seconds
+ m_loadingsRefresh = GetInstanceSettingInt("loadingsRefresh", 60);
+ m_keepAliveDelay = GetInstanceSettingInt("keepAliveDelay", 20);
+ m_epgCheckDelay = GetInstanceSettingInt("epgCheckDelay", 1) * 60; // make it seconds
+ m_useH265 = GetInstanceSettingBoolean("useH265", false);
+ m_useAdaptive = GetInstanceSettingBoolean("useAdaptive", false);
+ m_showLockedChannels = GetInstanceSettingBoolean("showLockedChannels", true);
+ m_showLockedOnlyPin = GetInstanceSettingBoolean("showLockedOnlyPin", true);
+
m_thread = std::thread{[this] { Process(); }};
}
@@ -272,14 +289,15 @@ void Data::KeepAliveJob()
void Data::LoginLoop()
{
unsigned login_delay = 0;
- for (bool should_try = true; KeepAlive() && should_try; --login_delay)
+ for ( ; KeepAlive(); --login_delay)
{
if (0 >= login_delay)
{
if (m_manager.login())
{
+ registerDrm();
ConnectionStateChange("Connected", PVR_CONNECTION_STATE_CONNECTED, "");
- should_try = false;
+ break;
}
else
{
@@ -291,6 +309,27 @@ void Data::LoginLoop()
}
}
+void Data::registerDrm()
+{
+ std::string licenseUrl, certificate;
+ if (!m_manager.registerDrm(licenseUrl, certificate))
+ {
+ kodi::Log(ADDON_LOG_WARNING, "DRM registration failed. DRM may not work");
+ }
+ static constexpr char url_placeholder[] = "={streamURL|base64}";
+ auto pos = licenseUrl.rfind(url_placeholder);
+ if (pos == licenseUrl.size() - sizeof(url_placeholder) + 1)
+ licenseUrl.erase(pos + 1);
+ else
+ kodi::Log(ADDON_LOG_WARNING, "Expecting DRM licenseUrl in form '...&streamURL%s', got %s. DRM may not work", url_placeholder, licenseUrl.c_str());
+ certificate = base64::to_base64(certificate);
+ {
+ std::lock_guard<std::mutex> critical(m_mutex);
+ m_drmCertificate = std::make_shared<std::string>(std::move(certificate));
+ m_drmLicenseUrl = std::make_shared<std::string>(std::move(licenseUrl));
+ }
+}
+
bool Data::WaitForChannels() const
{
std::unique_lock<std::mutex> critical(m_mutex);
@@ -353,29 +392,7 @@ Data::~Data(void)
kodi::Log(ADDON_LOG_DEBUG, "%s destructed", __FUNCTION__);
}
-ADDON_STATUS Data::Create()
-{
- kodi::Log(ADDON_LOG_DEBUG, "%s - Creating the PVR sledovanitv.cz (unofficial)", __FUNCTION__);
-
- if (!kodi::vfs::DirectoryExists(UserPath()))
- {
- kodi::vfs::CreateDirectory(UserPath());
- }
-
- m_streamQuality = kodi::addon::GetSettingEnum<ApiManager::StreamQuality_t>("streamQuality", ApiManager::SQ_DEFAULT);
- m_fullChannelEpgRefresh = kodi::addon::GetSettingInt("fullChannelEpgRefresh", 24) * 3600; // make it seconds
- m_loadingsRefresh = kodi::addon::GetSettingInt("loadingsRefresh", 60);
- m_keepAliveDelay = kodi::addon::GetSettingInt("keepAliveDelay", 20);
- m_epgCheckDelay = kodi::addon::GetSettingInt("epgCheckDelay", 1) * 60; // make it seconds
- m_useH265 = kodi::addon::GetSettingBoolean("useH265", false);
- m_useAdaptive = kodi::addon::GetSettingBoolean("useAdaptive", false);
- m_showLockedChannels = kodi::addon::GetSettingBoolean("showLockedChannels", true);
- m_showLockedOnlyPin = kodi::addon::GetSettingBoolean("showLockedOnlyPin", true);
-
- return ADDON_STATUS_OK;
-}
-
-ADDON_STATUS Data::SetSetting(const std::string & settingName, const kodi::addon::CSettingValue & settingValue)
+ADDON_STATUS Data::SetInstanceSetting(const std::string & settingName, const kodi::addon::CSettingValue & settingValue)
{
// just force our data to be re-created
return ADDON_STATUS_NEED_RESTART;
@@ -506,6 +523,9 @@ bool Data::LoadEPG(time_t iStart, bool bSmallStep)
std::string availability = epgEntry.get("availability", "none").asString();
iptventry.availableTimeshift = availability == "timeshift" || availability == "pvr";
iptventry.strRecordId = epgEntry["recordId"].asString();
+ iptventry.starRating = round(epgEntry.get("score", 0.0).asDouble());
+ const Json::Value parent_rating{epgEntry.get("ratingAge", Json::nullValue)};
+ iptventry.parentalRating = parent_rating.isNumeric() ? parent_rating.asInt() : 0;
kodi::Log(ADDON_LOG_DEBUG, "Loading TV show: %s - %s, start=%s(epoch=%llu)", strChId.c_str(), iptventry.strTitle.c_str()
, epgEntry.get("startTime", "").asString().c_str(), static_cast<long long unsigned>(start_time));
@@ -527,6 +547,8 @@ bool Data::LoadEPG(time_t iStart, bool bSmallStep)
tag.SetGenreType(EPG_GENRE_USE_STRING); //iptventry.iGenreType;
tag.SetGenreSubType(0); //iptventry.iGenreSubType;
tag.SetGenreDescription(iptventry.strGenreString);
+ tag.SetStarRating(iptventry.starRating);
+ tag.SetParentalRating(iptventry.parentalRating);
auto result = epgChannel.epg.emplace(iptventry.startTime, iptventry);
bool value_changed = !result.second;
@@ -676,9 +698,11 @@ bool Data::LoadRecordings()
for (auto & recording : *new_recordings)
{
std::string channel_id;
- recording.strStreamUrl = m_manager.getRecordingUrl(recording.strRecordId, channel_id);
+ bool isDrm;
+ recording.strStreamUrl = m_manager.getRecordingUrl(recording.strRecordId, channel_id, isDrm);
// get the stream type based on channel
recording.strStreamType = ChannelStreamType(channel_id);
+ recording.bIsDrm = isDrm;
}
}
bool changed_t = new_timers->size() != timers->size();
@@ -753,6 +777,7 @@ bool Data::LoadPlayList(void)
iptvchan.strGroupId = channel.get("group", "").asString();
iptvchan.strStreamURL = channel.get("url", "").asString();
iptvchan.strStreamType = channel.get("streamType", "").asString();
+ iptvchan.bIsDrm = channel.get("drm", "0").asInt() != 0;
iptvchan.iUniqueId = i + 1;
iptvchan.iChannelNumber = i + 1;
kodi::Log(ADDON_LOG_DEBUG, "Channel#%d %s, URL: %s", iptvchan.iUniqueId, iptvchan.strChannelName.c_str(), iptvchan.strStreamURL.c_str());
@@ -780,8 +805,7 @@ bool Data::LoadPlayList(void)
}
kodi::Log(ADDON_LOG_INFO, "Loaded %d channels.", new_channels->size());
- kodi::QueueFormattedNotification(QUEUE_INFO, "%d channels loaded.", new_channels->size());
-
+ kodi::QueueFormattedNotification(QUEUE_INFO, "%s - %d channels loaded.", GetInstanceSettingString("kodi_addon_instance_name").c_str(), new_channels->size());
bool channels_loaded;
{
@@ -851,11 +875,12 @@ PVR_ERROR Data::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet& resul
PVR_ERROR Data::GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, std::vector<kodi::addon::PVRStreamProperty>& properties)
{
std::string streamUrl, streamType;
- PVR_ERROR ret = GetChannelStreamUrl(channel, streamUrl, streamType);
+ bool isDrm;
+ PVR_ERROR ret = GetChannelStreamUrl(channel, streamUrl, streamType, isDrm);
if (PVR_ERROR_NO_ERROR != ret)
return ret;
- properties = StreamProperties(streamUrl, streamType, true);
+ properties = StreamProperties(streamUrl, streamType, isDrm, true);
return PVR_ERROR_NO_ERROR;
}
@@ -867,7 +892,7 @@ PVR_ERROR Data::GetSignalStatus(int channelUid, kodi::addon::PVRSignalStatus& si
return PVR_ERROR_NO_ERROR;
}
-PVR_ERROR Data::GetChannelStreamUrl(const kodi::addon::PVRChannel& channel, std::string & streamUrl, std::string & streamType)
+PVR_ERROR Data::GetChannelStreamUrl(const kodi::addon::PVRChannel& channel, std::string & streamUrl, std::string & streamType, bool & isDrm)
{
decltype (m_channels) channels;
{
@@ -887,6 +912,7 @@ PVR_ERROR Data::GetChannelStreamUrl(const kodi::addon::PVRChannel& channel, std:
streamUrl = channel_i->strStreamURL;
streamType = channel_i->strStreamType;
+ isDrm = channel_i->bIsDrm;
return PVR_ERROR_NO_ERROR;
}
@@ -987,6 +1013,7 @@ static PVR_ERROR GetEPGData(const kodi::addon::PVREPGTag& tag
, const epg_container_t * epg
, epg_entry_container_t::const_iterator & epg_i
, bool * isChannelPinLocked = nullptr
+ , bool * isChannelDrm = nullptr
)
{
auto channel_i = std::find_if(channels->cbegin(), channels->cend(), [tag] (const Channel & c) { return c.iUniqueId == tag.GetUniqueChannelId(); });
@@ -997,6 +1024,8 @@ static PVR_ERROR GetEPGData(const kodi::addon::PVREPGTag& tag
}
if (isChannelPinLocked)
*isChannelPinLocked = channel_i->bIsPinLocked;
+ if (isChannelDrm)
+ *isChannelDrm = channel_i->bIsDrm;
auto ch_epg_i = epg->find(channel_i->strId);
@@ -1049,15 +1078,16 @@ PVR_ERROR Data::IsEPGTagRecordable(const kodi::addon::PVREPGTag& tag, bool& isRe
PVR_ERROR Data::GetEPGTagStreamProperties(const kodi::addon::PVREPGTag& tag, std::vector<kodi::addon::PVRStreamProperty>& properties)
{
std::string streamUrl, streamType;
- PVR_ERROR ret = GetEPGStreamUrl(tag, streamUrl, streamType);
+ bool isDrm;
+ PVR_ERROR ret = GetEPGStreamUrl(tag, streamUrl, streamType, isDrm);
if (PVR_ERROR_NO_ERROR != ret)
return ret;
- properties = StreamProperties(streamUrl, streamType, false);
+ properties = StreamProperties(streamUrl, streamType, isDrm, false);
return PVR_ERROR_NO_ERROR;
}
-PVR_ERROR Data::GetEPGStreamUrl(const kodi::addon::PVREPGTag& tag, std::string & streamUrl, std::string & streamType)
+PVR_ERROR Data::GetEPGStreamUrl(const kodi::addon::PVREPGTag& tag, std::string & streamUrl, std::string & streamType, bool & isDrm)
{
decltype (m_channels) channels;
decltype (m_epg) epg;
@@ -1069,7 +1099,7 @@ PVR_ERROR Data::GetEPGStreamUrl(const kodi::addon::PVREPGTag& tag, std::string &
bool isPinLocked;
epg_entry_container_t::const_iterator epg_i;
- PVR_ERROR ret = GetEPGData(tag, channels.get(), epg.get(), epg_i, &isPinLocked);
+ PVR_ERROR ret = GetEPGData(tag, channels.get(), epg.get(), epg_i, &isPinLocked, &isDrm);
if (PVR_ERROR_NO_ERROR != ret)
return ret;
@@ -1077,7 +1107,7 @@ PVR_ERROR Data::GetEPGStreamUrl(const kodi::addon::PVREPGTag& tag, std::string &
return PVR_ERROR_REJECTED;
if (RecordingExists(epg_i->second.strRecordId))
- return GetRecordingStreamUrl(epg_i->second.strRecordId, streamUrl, streamType);
+ return GetRecordingStreamUrl(epg_i->second.strRecordId, streamUrl, streamType, isDrm);
std::string channel_id;
int duration;
@@ -1173,15 +1203,16 @@ PVR_ERROR Data::GetRecordings(bool deleted, kodi::addon::PVRRecordingsResultSet&
PVR_ERROR Data::GetRecordingStreamProperties(const kodi::addon::PVRRecording& recording, std::vector<kodi::addon::PVRStreamProperty>& properties)
{
std::string streamUrl, streamType;
- PVR_ERROR ret = GetRecordingStreamUrl(recording.GetRecordingId(), streamUrl, streamType);
+ bool isDrm;
+ PVR_ERROR ret = GetRecordingStreamUrl(recording.GetRecordingId(), streamUrl, streamType, isDrm);
if (PVR_ERROR_NO_ERROR != ret)
return ret;
- properties = StreamProperties(streamUrl, streamType, false);
+ properties = StreamProperties(streamUrl, streamType, isDrm, false);
return PVR_ERROR_NO_ERROR;
}
-PVR_ERROR Data::GetRecordingStreamUrl(const std::string & recording, std::string & streamUrl, std::string & streamType)
+PVR_ERROR Data::GetRecordingStreamUrl(const std::string & recording, std::string & streamUrl, std::string & streamType, bool & isDrm)
{
decltype (m_recordings) recordings;
{
@@ -1197,6 +1228,7 @@ PVR_ERROR Data::GetRecordingStreamUrl(const std::string & recording, std::string
streamUrl = rec_i->strStreamUrl;
streamType = rec_i->strStreamType;
+ isDrm = rec_i->bIsDrm;
return PVR_ERROR_NO_ERROR;
}
@@ -1359,7 +1391,7 @@ bool Data::LoggedIn() const
return m_manager.loggedIn();
}
-std::vector<kodi::addon::PVRStreamProperty> Data::StreamProperties(const std::string & url, const std::string & streamType, bool isLive) const
+std::vector<kodi::addon::PVRStreamProperty> Data::StreamProperties(const std::string & url, const std::string & streamType, bool isDrm, bool isLive) const
{
static const std::set<std::string> ADAPTIVE_TYPES = {"mpd", "ism", "hls"};
@@ -1369,6 +1401,21 @@ std::vector<kodi::addon::PVRStreamProperty> Data::StreamProperties(const std::st
{
properties.emplace_back(PVR_STREAM_PROPERTY_INPUTSTREAM, "inputstream.adaptive");
properties.emplace_back("inputstream.adaptive.manifest_type", streamType);
+ if (isDrm)
+ {
+ decltype (m_drmCertificate) certificate;
+ decltype (m_drmLicenseUrl) licenseUrl;
+ {
+ std::lock_guard<std::mutex> critical(m_mutex);
+ certificate = m_drmCertificate;
+ licenseUrl = m_drmLicenseUrl;
+ }
+ properties.emplace_back("inputstream.adaptive.license_type", "com.widevine.alpha");
+ properties.emplace_back("inputstream.adaptive.server_certificate", *certificate);
+ std::string license_url{*licenseUrl};
+ license_url += ApiManager::urlEncode(base64::to_base64(url));
+ properties.emplace_back("inputstream.adaptive.license_key", license_url);
+ }
}
if (isLive)
properties.emplace_back(PVR_STREAM_PROPERTY_ISREALTIMESTREAM, "true");
@@ -1440,5 +1487,3 @@ bool Data::PinCheckUnlock(bool isPinLocked)
}
} // namespace sledovanitvcz
-
-ADDONCREATOR(sledovanitvcz::Data)
diff --git a/src/Data.h b/src/Data.h
index 15f44fb..5f1a9e7 100644
--- a/src/Data.h
+++ b/src/Data.h
@@ -55,6 +55,8 @@ struct EpgEntry
std::string strEventId;
bool availableTimeshift;
std::string strRecordId; // optionally recorded
+ int starRating;
+ int parentalRating;
};
typedef std::map<time_t, EpgEntry> epg_entry_container_t;
@@ -79,6 +81,7 @@ struct Channel
std::string strGroupId;
std::string strStreamType;
bool bIsPinLocked;
+ bool bIsDrm;
};
struct ChannelGroup
@@ -105,6 +108,7 @@ struct Recording
std::string strStreamType;
int iChannelUid;
bool bIsPinLocked;
+ bool bIsDrm;
};
struct Timer
@@ -136,15 +140,13 @@ typedef std::vector<Recording> recording_container_t;
typedef std::vector<Timer> timer_container_t;
typedef std::map<std::string, std::string> properties_t;
-class ATTR_DLL_LOCAL Data : public kodi::addon::CAddonBase,
- public kodi::addon::CInstancePVRClient
+class ATTR_DLL_LOCAL Data : public kodi::addon::CInstancePVRClient
{
public:
- Data();
+ Data(const kodi::addon::IInstanceInfo& instance);
virtual ~Data(void);
- ADDON_STATUS Create() override;
- ADDON_STATUS SetSetting(const std::string & settingName, const kodi::addon::CSettingValue & settingValue) override;
+ ADDON_STATUS SetInstanceSetting(const std::string & settingName, const kodi::addon::CSettingValue & settingValue) override;
PVR_ERROR GetCapabilities(kodi::addon::PVRCapabilities& capabilities) override;
PVR_ERROR GetBackendName(std::string& name) override;
@@ -198,11 +200,12 @@ protected:
std::string ChannelsList() const;
std::string ChannelStreamType(const std::string & channelId) const;
bool PinCheckUnlock(bool isPinLocked);
- std::vector<kodi::addon::PVRStreamProperty> StreamProperties(const std::string & url, const std::string & streamType, bool isLive) const;
- PVR_ERROR GetChannelStreamUrl(const kodi::addon::PVRChannel& channel, std::string & streamUrl, std::string & streamType);
- PVR_ERROR GetEPGStreamUrl(const kodi::addon::PVREPGTag& tag, std::string & streamUrl, std::string & streamType);
- PVR_ERROR GetRecordingStreamUrl(const std::string & recording, std::string & streamUrl, std::string & streamType);
+ std::vector<kodi::addon::PVRStreamProperty> StreamProperties(const std::string & url, const std::string & streamType, bool isDrm, bool isLive) const;
+ PVR_ERROR GetChannelStreamUrl(const kodi::addon::PVRChannel& channel, std::string & streamUrl, std::string & streamType, bool & isDrm);
+ PVR_ERROR GetEPGStreamUrl(const kodi::addon::PVREPGTag& tag, std::string & streamUrl, std::string & streamType, bool & isDrm);
+ PVR_ERROR GetRecordingStreamUrl(const std::string & recording, std::string & streamUrl, std::string & streamType, bool & isDrm);
PVR_ERROR SetEPGMaxDays(int iFutureDays, int iPastDays);
+ void registerDrm();
protected:
void Process(void);
@@ -227,6 +230,8 @@ private:
time_t m_epgMaxTime;
int m_epgMaxFutureDays;
int m_epgMaxPastDays;
+ std::shared_ptr<const std::string> m_drmCertificate;
+ std::shared_ptr<const std::string> m_drmLicenseUrl;
// data used only by "job" thread
bool m_bEGPLoaded;
diff --git a/src/base64.hpp b/src/base64.hpp
new file mode 100644
index 0000000..5625a1b
--- /dev/null
+++ b/src/base64.hpp
@@ -0,0 +1,110 @@
+/*
+MIT License
+
+Copyright (c) 2019 Tobias Locker
+
+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.
+*/
+
+/*
+ * Note:
+ * Origin of this file is git@github.com:tobiaslocker/base64.git at db7c6834bd4f733899bd93218926247a659d8924
+ */
+
+#ifndef BASE_64_HPP
+#define BASE_64_HPP
+
+#include <algorithm>
+#include <string>
+
+namespace base64 {
+
+inline const std::string & get_base64_chars() {
+ static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+ return base64_chars;
+}
+
+inline std::string to_base64(std::string const &data) {
+ int counter = 0;
+ uint32_t bit_stream = 0;
+ auto base64_chars = get_base64_chars();
+ std::string encoded;
+ int offset = 0;
+ for (unsigned char c : data) {
+ auto num_val = static_cast<unsigned int>(c);
+ offset = 16 - counter % 3 * 8;
+ bit_stream += num_val << offset;
+ if (offset == 16) {
+ encoded += base64_chars.at(bit_stream >> 18 & 0x3f);
+ }
+ if (offset == 8) {
+ encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
+ }
+ if (offset == 0 && counter != 3) {
+ encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
+ encoded += base64_chars.at(bit_stream & 0x3f);
+ bit_stream = 0;
+ }
+ ++counter;
+ }
+ if (offset == 16) {
+ encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
+ encoded += "==";
+ }
+ if (offset == 8) {
+ encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
+ encoded += '=';
+ }
+ return encoded;
+}
+
+inline std::string from_base64(std::string const &data) {
+ int counter = 0;
+ uint32_t bit_stream = 0;
+ std::string decoded;
+ int offset = 0;
+ auto base64_chars = get_base64_chars();
+ for (unsigned char c : data) {
+ auto num_val = base64_chars.find(c);
+ if (num_val != std::string::npos) {
+ offset = 18 - counter % 4 * 6;
+ bit_stream += num_val << offset;
+ if (offset == 12) {
+ decoded += static_cast<char>(bit_stream >> 16 & 0xff);
+ }
+ if (offset == 6) {
+ decoded += static_cast<char>(bit_stream >> 8 & 0xff);
+ }
+ if (offset == 0 && counter != 4) {
+ decoded += static_cast<char>(bit_stream & 0xff);
+ bit_stream = 0;
+ }
+ } else if (c != '=') {
+ return std::string();
+ }
+ counter++;
+ }
+ return decoded;
+}
+
+}
+
+#endif // BASE_64_HPP
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/debug/.build-id/68/f0bd81cc07de42aa752e27ae5df8d867e0d975.debug -rw-r--r-- root/root /usr/lib/x86_64-linux-gnu/kodi/addons/pvr.sledovanitv.cz/pvr.sledovanitv.cz.so.20.6.0 -rw-r--r-- root/root /usr/share/kodi/addons/pvr.sledovanitv.cz/resources/instance-settings.xml lrwxrwxrwx root/root /usr/lib/x86_64-linux-gnu/kodi/addons/pvr.sledovanitv.cz/pvr.sledovanitv.cz.so.20.2 -> pvr.sledovanitv.cz.so.20.6.0
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/debug/.build-id/a5/ccdcf2a9ac99be68b8993666e864d9cf326f0f.debug -rw-r--r-- root/root /usr/lib/x86_64-linux-gnu/kodi/addons/pvr.sledovanitv.cz/pvr.sledovanitv.cz.so.20.3.0 lrwxrwxrwx root/root /usr/lib/x86_64-linux-gnu/kodi/addons/pvr.sledovanitv.cz/pvr.sledovanitv.cz.so.20.2 -> pvr.sledovanitv.cz.so.20.3.0
No differences were encountered between the control files of package kodi-pvr-sledovanitv-cz
Control files of package kodi-pvr-sledovanitv-cz-dbgsym: lines which differ (wdiff format)
Build-Ids: a5ccdcf2a9ac99be68b8993666e864d9cf326f0f 68f0bd81cc07de42aa752e27ae5df8d867e0d975