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

More details

Full run details