diff -pruN 19.0.3+ds1-1/azure-pipelines.yml 20.2.3+ds1-1/azure-pipelines.yml
--- 19.0.3+ds1-1/azure-pipelines.yml	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/azure-pipelines.yml	2022-03-07 18:40:34.000000000 +0000
@@ -5,6 +5,7 @@ trigger:
   branches:
     include:
     - Matrix
+    - Nexus
     - releases/*
   paths:
     include:
@@ -46,7 +47,7 @@ jobs:
 
     - script: |
         cd ..
-        git clone --branch Matrix --depth=1 https://github.com/xbmc/xbmc.git kodi
+        git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git kodi
         cd $(Build.SourcesDirectory)
         mkdir build
         cd build
diff -pruN 19.0.3+ds1-1/CMakeLists.txt 20.2.3+ds1-1/CMakeLists.txt
--- 19.0.3+ds1-1/CMakeLists.txt	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/CMakeLists.txt	2022-03-07 18:40:34.000000000 +0000
@@ -24,10 +24,8 @@ set(NEXTPVR_SOURCES src/addon.cpp
                     src/buffers/DummyBuffer.cpp
                     src/buffers/TranscodedBuffer.cpp
                     src/buffers/ClientTimeshift.cpp
-                    src/buffers/TimeshiftBuffer.cpp
                     src/buffers/RecordingBuffer.cpp
                     src/buffers/CircularBuffer.cpp
-                    src/buffers/RollingFile.cpp
                     src/buffers/Seeker.cpp)
 
 set(NEXTPVR_HEADERS src/addon.h
@@ -46,10 +44,8 @@ set(NEXTPVR_HEADERS src/addon.h
                     src/buffers/DummyBuffer.h
                     src/buffers/TranscodedBuffer.h
                     src/buffers/ClientTimeshift.h
-                    src/buffers/TimeshiftBuffer.h
                     src/buffers/RecordingBuffer.h
                     src/buffers/CircularBuffer.h
-                    src/buffers/RollingFile.h
                     src/buffers/Seeker.h
                     src/utilities/XMLUtils.h)
 
diff -pruN 19.0.3+ds1-1/debian/changelog 20.2.3+ds1-1/debian/changelog
--- 19.0.3+ds1-1/debian/changelog	2022-03-21 17:50:11.000000000 +0000
+++ 20.2.3+ds1-1/debian/changelog	2022-08-04 09:56:05.000000000 +0000
@@ -1,3 +1,10 @@
+kodi-pvr-nextpvr (20.2.3+ds1-1) unstable; urgency=medium
+
+  * New upstream version 20.2.3+ds1
+  * Prepare for v20 in unstable
+
+ -- Vasyl Gello <vasek.gello@gmail.com>  Thu, 04 Aug 2022 09:56:05 +0000
+
 kodi-pvr-nextpvr (19.0.3+ds1-1) unstable; urgency=medium
 
   * New upstream version 19.0.3+ds1
diff -pruN 19.0.3+ds1-1/debian/control 20.2.3+ds1-1/debian/control
--- 19.0.3+ds1-1/debian/control	2022-03-21 17:50:11.000000000 +0000
+++ 20.2.3+ds1-1/debian/control	2022-08-04 09:56:05.000000000 +0000
@@ -4,11 +4,11 @@ Section: video
 Maintainer: Debian Multimedia Maintainers <debian-multimedia@lists.debian.org>
 Uploaders: Vasyl Gello <vasek.gello@gmail.com>
 Build-Depends: debhelper-compat (= 13)
-Build-Depends-Arch: dh-sequence-kodiaddon (>= 2:19~),
+Build-Depends-Arch: dh-sequence-kodiaddon (>= 2:20~),
  cmake,
  libtinyxml2-dev,
  zlib1g-dev
-Standards-Version: 4.6.0
+Standards-Version: 4.6.1
 Rules-Requires-Root: no
 Vcs-Browser: https://salsa.debian.org/multimedia-team/kodi-media-center/kodi-pvr-nextpvr
 Vcs-Git: https://salsa.debian.org/multimedia-team/kodi-media-center/kodi-pvr-nextpvr.git
diff -pruN 19.0.3+ds1-1/debian/copyright 20.2.3+ds1-1/debian/copyright
--- 19.0.3+ds1-1/debian/copyright	2021-01-20 07:28:02.000000000 +0000
+++ 20.2.3+ds1-1/debian/copyright	2022-08-04 09:56:05.000000000 +0000
@@ -12,7 +12,7 @@ License: GPL-2+
 
 Files: debian/*
 Copyright: 2015, Balint Reczey <balint@balintreczey.hu>
-           2020-2021, Vasyl Gello <vasek.gello@gmail.com>
+           2020-2022, Vasyl Gello <vasek.gello@gmail.com>
 License: GPL-2+
 
 License: GPL-2+
diff -pruN 19.0.3+ds1-1/debian/watch 20.2.3+ds1-1/debian/watch
--- 19.0.3+ds1-1/debian/watch	2021-08-26 10:31:16.000000000 +0000
+++ 20.2.3+ds1-1/debian/watch	2022-08-04 09:56:05.000000000 +0000
@@ -5,4 +5,4 @@ opts="repack, \
       repacksuffix=+ds1, \
       dversionmangle=auto" \
 https://github.com/kodi-pvr/pvr.nextpvr/releases \
-/kodi-pvr/pvr.nextpvr/archive/refs/tags/?(\d\S*)-Matrix\.tar\.gz
+/kodi-pvr/pvr.nextpvr/archive/refs/tags/?(\d\S*)-Nexus\.tar\.gz
diff -pruN 19.0.3+ds1-1/.github/workflows/build.yml 20.2.3+ds1-1/.github/workflows/build.yml
--- 19.0.3+ds1-1/.github/workflows/build.yml	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/.github/workflows/build.yml	2022-03-07 18:40:34.000000000 +0000
@@ -27,14 +27,14 @@ jobs:
       env:
         DEBIAN_BUILD: ${{ matrix.DEBIAN_BUILD }}
       run: |
-        if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/ppa; fi
+        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: Matrix
+        ref: master
         path: xbmc
     - name: Checkout pvr.nextpvr repo
       uses: actions/checkout@v2
@@ -48,7 +48,7 @@ jobs:
       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/Matrix/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; 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:
diff -pruN 19.0.3+ds1-1/.github/workflows/changelog-and-release.yml 20.2.3+ds1-1/.github/workflows/changelog-and-release.yml
--- 19.0.3+ds1-1/.github/workflows/changelog-and-release.yml	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/.github/workflows/changelog-and-release.yml	2022-03-07 18:40:34.000000000 +0000
@@ -133,7 +133,7 @@ jobs:
         shell: bash
 
       # Create a release at {steps.required-variables.outputs.branch}
-      # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 1.0.0-Matrix
+      # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 20.0.0-Nexus
       # - release body: {steps.required-variables.outputs.changes}
       - name: Create Release
         id: create-release
diff -pruN 19.0.3+ds1-1/.github/workflows/release.yml 20.2.3+ds1-1/.github/workflows/release.yml
--- 19.0.3+ds1-1/.github/workflows/release.yml	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/.github/workflows/release.yml	2022-03-07 18:40:34.000000000 +0000
@@ -50,7 +50,7 @@ jobs:
         working-directory: ${{ github.event.repository.name }}
 
       # Create a release at {steps.required-variables.outputs.branch}
-      # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 1.0.0-Matrix
+      # - tag and release name format: {steps.required-variables.outputs.version}-{steps.required-variables.outputs.branch} ie. 20.0.0-Nexus
       # - release body: {steps.required-variables.outputs.changes}
       - name: Create Release
         id: create-release
diff -pruN 19.0.3+ds1-1/Jenkinsfile 20.2.3+ds1-1/Jenkinsfile
--- 19.0.3+ds1-1/Jenkinsfile	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/Jenkinsfile	2022-03-07 18:40:34.000000000 +0000
@@ -1 +1 @@
-buildPlugin(version: "Matrix")
+buildPlugin(version: "Nexus")
diff -pruN 19.0.3+ds1-1/pvr.nextpvr/addon.xml.in 20.2.3+ds1-1/pvr.nextpvr/addon.xml.in
--- 19.0.3+ds1-1/pvr.nextpvr/addon.xml.in	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/pvr.nextpvr/addon.xml.in	2022-03-07 18:40:34.000000000 +0000
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <addon
   id="pvr.nextpvr"
-  version="19.0.3"
+  version="20.2.3"
   name="NextPVR PVR Client"
   provider-name="Graeme Blackley">
   <requires>@ADDON_DEPENDS@
diff -pruN 19.0.3+ds1-1/pvr.nextpvr/changelog.txt 20.2.3+ds1-1/pvr.nextpvr/changelog.txt
--- 19.0.3+ds1-1/pvr.nextpvr/changelog.txt	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/pvr.nextpvr/changelog.txt	2022-03-07 18:40:34.000000000 +0000
@@ -1,22 +1,44 @@
-v19.0.3
-- Translations updates from Weblate
-	- da_dk, es_mx
+v20.2.3
+- Remove backend forced season/episode support
+
+v20.2.2
+- Send channel groups in NextPVR order
+
+v20.2.1
+- Used cached group counts for GetChannelGroupsAmount()
 
-v19.0.2
+v20.2.0
+- Translation updates by Weblate
+- Kodi main API update to version 2.0.0
+
+v20.1.3
 - Fix for Kodi marking in-progress recordings as watched
 
-v19.0.1
+v20.1.2
+- Remove obsolete V4 settings from settings.xml
+- Load and save only V5 settings
+- Remove conditional settings from code
+- Remove obsolete V4 timeshifting modes
+
+v20.1.1
+- Block access for v4 backend users
 - Don't try and reconnect server after sleep announced
-- Adjust timeshift when v4 setting logic get confused
 - Handle situations where Kodi requests timer updates without a recording update
+- Document no provider support
+- Remove EDL limit check
 - Support new API XML to get recording specific art
 
-v19.0.0
+v20.1.0
+- Kodi API to 8.0.0
+  - Add supports recordings delete capability
+  - Enforce EDL limits
+
+v20.0.0
 - Translations updates from Weblate
   - be_by, ko_kr, ru_ru
   - To allow also addon.xml content update by Weblate
-- Changed test builds to 'Kodi 19 Matrix'
-- Increased version to 19.0.0
+- Changed test builds to 'Kodi 20 Nexus'
+- Increased version to 20.0.0
   - With start of Kodi 20 Nexus, takes addon as major the same version number as Kodi.
     This done to know easier to which Kodi the addon works.
 
diff -pruN 19.0.3+ds1-1/pvr.nextpvr/resources/language/resource.language.en_gb/strings.po 20.2.3+ds1-1/pvr.nextpvr/resources/language/resource.language.en_gb/strings.po
--- 19.0.3+ds1-1/pvr.nextpvr/resources/language/resource.language.en_gb/strings.po	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/pvr.nextpvr/resources/language/resource.language.en_gb/strings.po	2022-03-07 18:40:34.000000000 +0000
@@ -190,14 +190,6 @@ msgctxt "#30168"
 msgid "Recording chunk size"
 msgstr ""
 
-msgctxt "#30169"
-msgid "Kodi skin styled recordings"
-msgstr ""
-
-msgctxt "#30569"
-msgid "Separate title, season and episode for skin - season and episode numbers will not be shown in lists"
-msgstr ""
-
 msgctxt "#30170"
 msgid "Refresh cache for channel icons"
 msgstr ""
diff -pruN 19.0.3+ds1-1/pvr.nextpvr/resources/settings.xml 20.2.3+ds1-1/pvr.nextpvr/resources/settings.xml
--- 19.0.3+ds1-1/pvr.nextpvr/resources/settings.xml	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/pvr.nextpvr/resources/settings.xml	2022-03-07 18:40:34.000000000 +0000
@@ -3,11 +3,6 @@
   <section id="pvr.nextpvr">
     <category help="" id="connection" label="30040">
       <group id="0">
-        <setting help="" id="legacy" label="30177" type="boolean">
-          <level>1</level>
-          <default>false</default>
-          <control type="toggle"/>
-        </setting>
         <setting help="" id="host" label="30000" type="urlencodedstring">
           <level>0</level>
           <default>127.0.0.1</default>
@@ -81,60 +76,18 @@
       </group>
     </category>
     <category help="" id="advanced" label="30174">
-      <group id="01">
-        <setting help="Timeshift" id="livestreamingmethod" label="30172" type="integer">
-          <level>0</level>
-          <default>2</default>
-          <constraints>
-            <options>
-              <option label="Timeshift">0</option>
-              <option label="Extended Timeshift">1</option>
-              <option label="Real Time">2</option>
-            </options>
-          </constraints>
-          <control format="string" type="spinner"/>
-          <dependencies>
-            <dependency type="visible">
-              <condition operator="is" setting="legacy">true</condition>
-            </dependency>
-          </dependencies>
-        </setting>
-        <setting help="" id="prebuffer" label="30162" type="integer">
-          <level>2</level>
-          <default>8</default>
-          <constraints>
-            <minimum>0</minimum>
-            <step>1</step>
-            <maximum>16</maximum>
-          </constraints>
-          <dependencies>
-            <dependency type="visible">
-              <condition operator="is" setting="livestreamingmethod">1</condition>
-              <condition operator="is" setting="legacy">true</condition>
-            </dependency>
-          </dependencies>
-          <control format="integer" type="slider">
-            <popup>false</popup>
-          </control>
-        </setting>
-      </group>
       <group id="501">
         <setting help="" id="livestreamingmethod5" label="30172" type="integer">
           <level>0</level>
-          <default>-1</default>
+          <default>2</default>
           <constraints>
             <options>
               <option label="Real Time">2</option>
-              <option label="Timeshift">0</option>
+              <option label="Timeshift">4</option>
               <option label="Transcoded">3</option>
             </options>
           </constraints>
           <control format="string" type="spinner"/>
-          <dependencies>
-            <dependency type="visible">
-              <condition operator="is" setting="legacy">false</condition>
-            </dependency>
-          </dependencies>
         </setting>
         <setting help="" id="resolution" label="30173" type="string" parent="livestreamingmethod5">
           <level>1</level>
@@ -154,7 +107,6 @@
           <dependencies>
             <dependency type="visible">
               <condition operator="is" setting="livestreamingmethod5">3</condition>
-              <condition operator="is" setting="legacy">false</condition>
             </dependency>
           </dependencies>
           <control format="string" type="spinner"/>
@@ -172,8 +124,7 @@
           </control>
           <dependencies>
             <dependency type="visible">
-              <condition operator="is" setting="livestreamingmethod5">0</condition>
-              <condition operator="is" setting="legacy">false</condition>
+              <condition operator="is" setting="livestreamingmethod5">4</condition>
             </dependency>
           </dependencies>
         </setting>
@@ -184,7 +135,6 @@
           <dependencies>
             <dependency type="visible">
               <condition operator="is" setting="livestreamingmethod5">3</condition>
-              <condition operator="is" setting="legacy">false</condition>
             </dependency>
           </dependencies>
         </setting>
@@ -231,7 +181,6 @@
           <control type="toggle"/>
           <dependencies>
             <dependency type="visible">
-              <condition operator="is" setting="legacy">false</condition>
               <condition operator="!is" setting="pin">0000</condition>
             </dependency>
           </dependencies>
@@ -242,7 +191,6 @@
           <control type="toggle"/>
           <dependencies>
             <dependency type="visible">
-              <condition operator="is" setting="legacy">false</condition>
               <condition operator="is" setting="remoteaccess">false</condition>
             </dependency>
           </dependencies>
@@ -261,26 +209,11 @@
           <level>1</level>
           <default>false</default>
           <control type="toggle"/>
-          <dependencies>
-            <dependency type="visible">
-              <condition operator="is" setting="legacy">false</condition>
-            </dependency>
-          </dependencies>
-        </setting>
-        <setting help="30569" id="kodilook" label="30169" type="boolean">
-          <level>1</level>
-          <default>false</default>
-          <control type="toggle"/>
         </setting>
-        <setting help="30680" id="flattenrecording" label="30180" type="boolean"  parent="kodilook">
+        <setting help="30680" id="flattenrecording" label="30180" type="boolean">
           <level>2</level>
           <default>false</default>
           <control type="toggle"/>
-          <dependencies>
-            <dependency type="visible">
-              <condition operator="is" setting="kodilook">true</condition>
-            </dependency>
-          </dependencies>
         </setting>
         <setting help="30699" id="ignorepadding" label="30199" type="boolean">
           <level>2</level>
@@ -296,11 +229,6 @@
           <level>3</level>
           <default>false</default>
           <control type="toggle"/>
-          <dependencies>
-            <dependency type="visible">
-              <condition operator="is" setting="legacy">false</condition>
-            </dependency>
-          </dependencies>
         </setting>
         <setting id="diskspace" type="string" label="30198" help="30698">
           <level>1</level>
@@ -316,21 +244,11 @@
           <control type="list" format="string">
             <heading>30198</heading>
           </control>
-          <dependencies>
-            <dependency type="visible">
-              <condition operator="is" setting="legacy">false</condition>
-            </dependency>
-          </dependencies>
         </setting>
         <setting help="30694" id="genrestring" label="30194" type="boolean">
           <level>1</level>
           <default>false</default>
           <control type="toggle"/>
-          <dependencies>
-            <dependency type="visible">
-              <condition operator="is" setting="legacy">false</condition>
-            </dependency>
-          </dependencies>
         </setting>
         <setting help="30700" id="backendresume" label="30200" type="boolean">
           <level>3</level>
@@ -340,13 +258,31 @@
       </group>
     </category>
     <category help="" id="deadsettings" label="">
-      <!-- Avoid Kodi creating many log messages for obsolete user settings -->>
+      <!-- Avoid Kodi creating debug log messages for obsolete user settings -->>
       <group id="13">
+        <setting help="" id="kodilook" label="" type="boolean">
+          <level>4</level>
+          <default>false</default>
+          <control type="toggle"/>
+        </setting>
         <setting help="" id="usetimeshift" label="" type="boolean" option="hidden">
           <level>4</level>
           <default>false</default>
           <control type="toggle"/>
         </setting>
+        <setting help="" id="legacy" label="30177" type="boolean" option="hidden">
+          <level>4</level>
+          <default>false</default>
+          <control type="toggle"/>
+        </setting>
+        <setting help="" id="prebuffer" label="30162" type="integer" option="hidden">
+          <level>4</level>
+          <default>8</default>
+        </setting>
+        <setting help="Timeshift" id="livestreamingmethod" label="30172" type="integer" option="hidden">
+          <level>4</level>
+          <default>2</default>
+        </setting>
       </group>
     </category>
   </section>
diff -pruN 19.0.3+ds1-1/README.md 20.2.3+ds1-1/README.md
--- 19.0.3+ds1-1/README.md	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/README.md	2022-03-07 18:40:34.000000000 +0000
@@ -1,7 +1,7 @@
 [![License: GPL-2.0-or-later](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](LICENSE.md)
-[![Build and run tests](https://github.com/kodi-pvr/pvr.nextpvr/actions/workflows/build.yml/badge.svg?branch=Matrix)](https://github.com/kodi-pvr/pvr.nextpvr/actions/workflows/build.yml)
-[![Build Status](https://dev.azure.com/teamkodi/kodi-pvr/_apis/build/status/kodi-pvr.pvr.nextpvr?branchName=Matrix)](https://dev.azure.com/teamkodi/kodi-pvr/_build/latest?definitionId=64&branchName=Matrix)
-[![Build Status](https://jenkins.kodi.tv/view/Addons/job/kodi-pvr/job/pvr.nextpvr/job/Matrix/badge/icon)](https://jenkins.kodi.tv/blue/organizations/jenkins/kodi-pvr%2Fpvr.nextpvr/branches/)
+[![Build and run tests](https://github.com/kodi-pvr/pvr.nextpvr/actions/workflows/build.yml/badge.svg?branch=Nexus)](https://github.com/kodi-pvr/pvr.nextpvr/actions/workflows/build.yml)
+[![Build Status](https://dev.azure.com/teamkodi/kodi-pvr/_apis/build/status/kodi-pvr.pvr.nextpvr?branchName=Nexus)](https://dev.azure.com/teamkodi/kodi-pvr/_build/latest?definitionId=64&branchName=Nexus)
+[![Build Status](https://jenkins.kodi.tv/view/Addons/job/kodi-pvr/job/pvr.nextpvr/job/Nexus/badge/icon)](https://jenkins.kodi.tv/blue/organizations/jenkins/kodi-pvr%2Fpvr.nextpvr/branches/)
 [![Coverity Scan Build Status](https://scan.coverity.com/projects/5120/badge.svg)](https://scan.coverity.com/projects/5120)
 
 # NextPVR PVR
@@ -11,8 +11,8 @@ NextPVR PVR client addon for [Kodi](http
 
 ### Linux
 
-1. `git clone --branch Matrix https://github.com/xbmc/xbmc.git`
-2. `git clone --branch Matrix https://github.com/kodi-pvr/pvr.nextpvr.git`
+1. `git clone --branch master https://github.com/xbmc/xbmc.git`
+2. `git clone https://github.com/kodi-pvr/pvr.nextpvr.git`
 3. `cd pvr.nextpvr && mkdir build && cd build`
 4. `cmake -DADDONS_TO_BUILD=pvr.nextpvr -DADDON_SRC_PREFIX=../.. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../../xbmc/addons -DPACKAGE_ZIP=1 ../../xbmc/cmake/addons`
 5. `make`
diff -pruN 19.0.3+ds1-1/src/addon.cpp 20.2.3+ds1-1/src/addon.cpp
--- 19.0.3+ds1-1/src/addon.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/addon.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -32,9 +32,8 @@ ADDON_STATUS CNextPVRAddon::Create()
   return ADDON_STATUS_OK;
 }
 
-ADDON_STATUS CNextPVRAddon::CreateInstance(int instanceType,
-    const std::string& instanceID, KODI_HANDLE instance,
-    const std::string& version, KODI_HANDLE& addonInstance)
+ADDON_STATUS CNextPVRAddon::CreateInstance(const kodi::addon::IInstanceInfo& instance,
+                                           KODI_ADDON_INSTANCE_HDL& hdl)
 {
   settings.ReadFromAddon();
 
@@ -45,29 +44,28 @@ ADDON_STATUS CNextPVRAddon::CreateInstan
   }
 
   /* Create connection to NextPVR KODI TV client */
-  g_pvrclient = new cPVRClientNextPVR(*this, instance, version);
-  m_curStatus = g_pvrclient->Connect();
+  g_pvrclient = new cPVRClientNextPVR(*this, instance);
+  ADDON_STATUS status = g_pvrclient->Connect();
 
-  if (m_curStatus != ADDON_STATUS_PERMANENT_FAILURE)
+  if (status != ADDON_STATUS_PERMANENT_FAILURE)
   {
-    m_curStatus = ADDON_STATUS_OK;
-    addonInstance = g_pvrclient;
-    m_usedInstances.emplace(std::make_pair(instanceID, g_pvrclient));
+    status = ADDON_STATUS_OK;
+    hdl = g_pvrclient;
+    m_usedInstances.emplace(std::make_pair(instance.GetID(), g_pvrclient));
     g_pvrclient->m_menuhook.ConfigureMenuHook();
   }
 
-  return m_curStatus;
+  return status;
 }
 
 //-- Destroy ------------------------------------------------------------------
 // Used during destruction of the client, all steps to do clean and safe Create
 // again must be done.
 //-----------------------------------------------------------------------------
-void CNextPVRAddon::DestroyInstance(int instanceType,
-                                    const std::string& instanceID,
-                                    KODI_HANDLE addonInstance)
+void CNextPVRAddon::DestroyInstance(const kodi::addon::IInstanceInfo& instance,
+                                    const KODI_ADDON_INSTANCE_HDL hdl)
 {
-  const auto& it = m_usedInstances.find(instanceID);
+  const auto& it = m_usedInstances.find(instance.GetID());
   if (it != m_usedInstances.end())
   {
     it->second->Disconnect();
@@ -80,7 +78,7 @@ void CNextPVRAddon::DestroyInstance(int
 // Called everytime a setting is changed by the user and to inform AddOn about
 // new setting and to do required stuff to apply it.
 //-----------------------------------------------------------------------------
-ADDON_STATUS CNextPVRAddon::SetSetting(const std::string& settingName, const kodi::CSettingValue& settingValue)
+ADDON_STATUS CNextPVRAddon::SetSetting(const std::string& settingName, const kodi::addon::CSettingValue& settingValue)
 {
   std::string str = settingName;
 
diff -pruN 19.0.3+ds1-1/src/addon.h 20.2.3+ds1-1/src/addon.h
--- 19.0.3+ds1-1/src/addon.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/addon.h	2022-03-07 18:40:34.000000000 +0000
@@ -21,24 +21,20 @@
 class cPVRClientNextPVR;
 extern cPVRClientNextPVR* g_pvrclient;
 
-class ATTRIBUTE_HIDDEN CNextPVRAddon : public kodi::addon::CAddonBase
+class ATTR_DLL_LOCAL CNextPVRAddon : public kodi::addon::CAddonBase
 {
 public:
   CNextPVRAddon() = default;
 
   ADDON_STATUS Create() override;
-  ADDON_STATUS GetStatus() override { return m_curStatus; }
   ADDON_STATUS SetSetting(const std::string& settingName,
-    const kodi::CSettingValue& settingValue) override;
-  ADDON_STATUS CreateInstance(int instanceType, const std::string& instanceID,
-    KODI_HANDLE instance, const std::string& version,
-    KODI_HANDLE& addonInstance) override;
-  void DestroyInstance(int instanceType,
-    const std::string& instanceID,
-    KODI_HANDLE addonInstance) override;
+    const kodi::addon::CSettingValue& settingValue) override;
+  ADDON_STATUS CreateInstance(const kodi::addon::IInstanceInfo& instance,
+    KODI_ADDON_INSTANCE_HDL& hdl) override;
+  void DestroyInstance(const kodi::addon::IInstanceInfo& instance,
+    const KODI_ADDON_INSTANCE_HDL hdl) override;
 
 private:
-  ADDON_STATUS m_curStatus = ADDON_STATUS_UNKNOWN;
   std::unordered_map<std::string, cPVRClientNextPVR*> m_usedInstances;
 };
 
diff -pruN 19.0.3+ds1-1/src/BackendRequest.cpp 20.2.3+ds1-1/src/BackendRequest.cpp
--- 19.0.3+ds1-1/src/BackendRequest.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/BackendRequest.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -213,17 +213,17 @@ namespace NextPVR
       // found multiple hosts using discovery protocol.
       if (foundAddress.size() > 1)
       {
-        offset = kodi::gui::dialogs::Select::Show(kodi::GetLocalizedString(30187), entries);
+        offset = kodi::gui::dialogs::Select::Show(kodi::addon::GetLocalizedString(30187), entries);
       }
       if (offset >= 0)
       {
         kodi::vfs::CreateDirectory("special://userdata/addon_data/pvr.nextpvr/");
         m_settings.UpdateServerPort(entries[offset], atoi(foundAddress[offset][1].c_str()));
-        kodi::QueueNotification(QUEUE_INFO, kodi::GetLocalizedString(30189),
-          kodi::tools::StringUtils::Format(kodi::GetLocalizedString(30182).c_str(), m_settings.m_hostname.c_str(), m_settings.m_port));
+        kodi::QueueNotification(QUEUE_INFO, kodi::addon::GetLocalizedString(30189),
+          kodi::tools::StringUtils::Format(kodi::addon::GetLocalizedString(30182).c_str(), m_settings.m_hostname.c_str(), m_settings.m_port));
         /* note that these run before the file is created */
-        kodi::SetSettingString("host", m_settings.m_hostname);
-        kodi::SetSettingInt("port", m_settings.m_port);
+        kodi::addon::SetSettingString("host", m_settings.m_hostname);
+        kodi::addon::SetSettingInt("port", m_settings.m_port);
         return true;
       }
     }
diff -pruN 19.0.3+ds1-1/src/BackendRequest.h 20.2.3+ds1-1/src/BackendRequest.h
--- 19.0.3+ds1-1/src/BackendRequest.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/BackendRequest.h	2022-03-07 18:40:34.000000000 +0000
@@ -27,7 +27,7 @@
 
 namespace NextPVR
 {
-  class ATTRIBUTE_HIDDEN Request
+  class ATTR_DLL_LOCAL Request
   {
   public:
     /*
diff -pruN 19.0.3+ds1-1/src/buffers/Buffer.cpp 20.2.3+ds1-1/src/buffers/Buffer.cpp
--- 19.0.3+ds1-1/src/buffers/Buffer.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/Buffer.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -81,7 +81,7 @@ void Buffer::LeaseWorker(void)
       else if (retval == LeaseClosed)
       {
         complete = true;
-        kodi::QueueNotification(QUEUE_ERROR, kodi::GetLocalizedString(30190), kodi::GetLocalizedString(30053));
+        kodi::QueueNotification(QUEUE_ERROR, kodi::addon::GetLocalizedString(30190), kodi::addon::GetLocalizedString(30053));
       }
       else
       {
diff -pruN 19.0.3+ds1-1/src/buffers/Buffer.h 20.2.3+ds1-1/src/buffers/Buffer.h
--- 19.0.3+ds1-1/src/buffers/Buffer.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/Buffer.h	2022-03-07 18:40:34.000000000 +0000
@@ -40,7 +40,7 @@ namespace timeshift {
   /**
    * Base class for all timeshift buffers
    */
-  class ATTRIBUTE_HIDDEN Buffer
+  class ATTR_DLL_LOCAL Buffer
   {
   public:
     Buffer() :
diff -pruN 19.0.3+ds1-1/src/buffers/CircularBuffer.h 20.2.3+ds1-1/src/buffers/CircularBuffer.h
--- 19.0.3+ds1-1/src/buffers/CircularBuffer.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/CircularBuffer.h	2022-03-07 18:40:34.000000000 +0000
@@ -16,7 +16,7 @@
 
 namespace timeshift {
 
-  class ATTRIBUTE_HIDDEN CircularBuffer {
+  class ATTR_DLL_LOCAL CircularBuffer {
   public:
     CircularBuffer(int size) : m_iBytes(0), m_iReadPos(0), m_iWritePos(0), m_iSize(size) { m_cBuffer = new byte[m_iSize]; }
     ~CircularBuffer() { delete[] m_cBuffer; }
diff -pruN 19.0.3+ds1-1/src/buffers/ClientTimeshift.cpp 20.2.3+ds1-1/src/buffers/ClientTimeshift.cpp
--- 19.0.3+ds1-1/src/buffers/ClientTimeshift.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/ClientTimeshift.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -90,6 +90,15 @@ bool ClientTimeShift::Open(const std::st
   return true;
 }
 
+PVR_ERROR ClientTimeShift::GetStreamTimes(kodi::addon::PVRStreamTimes& stimes)
+{
+  stimes.SetStartTime(m_streamStart);
+  stimes.SetPTSStart(0);
+  stimes.SetPTSBegin(static_cast<int64_t>(m_rollingStartSeconds - m_streamStart) * STREAM_TIME_BASE);
+  stimes.SetPTSEnd(static_cast<int64_t>(time(nullptr) - m_streamStart) * STREAM_TIME_BASE);
+  return PVR_ERROR_NO_ERROR;
+}
+
 void ClientTimeShift::Close()
 {
   if (m_active)
@@ -205,7 +214,7 @@ bool ClientTimeShift::GetStreamInfo()
           }
           else
           {
-            kodi::QueueNotification(QUEUE_ERROR, kodi::GetLocalizedString(30190), kodi::GetLocalizedString(30053));
+            kodi::QueueNotification(QUEUE_ERROR, kodi::addon::GetLocalizedString(30190), kodi::addon::GetLocalizedString(30053));
           }
         }
         kodi::Log(ADDON_LOG_DEBUG, "CT channel.stream.info %lld %lld %d %lld", m_stream_length.load(), stream_duration, m_complete, m_rollingStartSeconds.load());
diff -pruN 19.0.3+ds1-1/src/buffers/ClientTimeshift.h 20.2.3+ds1-1/src/buffers/ClientTimeshift.h
--- 19.0.3+ds1-1/src/buffers/ClientTimeshift.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/ClientTimeshift.h	2022-03-07 18:40:34.000000000 +0000
@@ -8,7 +8,7 @@
 
 #pragma once
 
-#include "RollingFile.h"
+#include "RecordingBuffer.h"
 #include <thread>
 #include <list>
 
@@ -16,7 +16,7 @@ using namespace NextPVR;
 
 namespace timeshift {
 
-  class ATTRIBUTE_HIDDEN ClientTimeShift : public RollingFile
+  class ATTR_DLL_LOCAL ClientTimeShift : public RecordingBuffer
   {
   private:
     bool m_isPaused = false;
@@ -27,8 +27,17 @@ namespace timeshift {
 	 */
 	std::string m_sourceURL;
 
+  std::atomic<int64_t> m_stream_length;
+  std::atomic<int64_t> m_stream_duration;
+  std::atomic<int> m_bytesPerSecond;
+  time_t m_lastClose;
+  int m_prebuffer;
+  std::atomic<time_t> m_rollingStartSeconds;
+  time_t m_streamStart;
+
+
   public:
-    ClientTimeShift() : RollingFile()
+    ClientTimeShift() : RecordingBuffer()
     {
       m_lastClose = 0;
       m_channel_id = 0;
@@ -55,6 +64,11 @@ namespace timeshift {
 
     virtual bool GetStreamInfo() override;
 
+    virtual int64_t Length() const override
+    {
+      return m_stream_length;
+    }
+
     virtual int64_t Position() const override
     {
       return m_inputHandle.GetPosition();
@@ -77,6 +91,7 @@ namespace timeshift {
     {
       return true;
     }
+    virtual PVR_ERROR GetStreamTimes(kodi::addon::PVRStreamTimes& times) override;
 
   };
 }
diff -pruN 19.0.3+ds1-1/src/buffers/DummyBuffer.h 20.2.3+ds1-1/src/buffers/DummyBuffer.h
--- 19.0.3+ds1-1/src/buffers/DummyBuffer.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/DummyBuffer.h	2022-03-07 18:40:34.000000000 +0000
@@ -17,7 +17,7 @@ namespace timeshift {
    * Dummy buffer that just passes all calls through to the input file
    * handle without actually buffering anything
    */
-  class ATTRIBUTE_HIDDEN DummyBuffer : public Buffer
+  class ATTR_DLL_LOCAL DummyBuffer : public Buffer
   {
   public:
     DummyBuffer() : Buffer() { kodi::Log(ADDON_LOG_INFO, "DummyBuffer created!"); }
diff -pruN 19.0.3+ds1-1/src/buffers/RecordingBuffer.h 20.2.3+ds1-1/src/buffers/RecordingBuffer.h
--- 19.0.3+ds1-1/src/buffers/RecordingBuffer.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/RecordingBuffer.h	2022-03-07 18:40:34.000000000 +0000
@@ -17,7 +17,7 @@ namespace timeshift {
    * Dummy buffer that just passes all calls through to the input file
    * handle without actually buffering anything
    */
-  class ATTRIBUTE_HIDDEN RecordingBuffer : public Buffer
+  class ATTR_DLL_LOCAL RecordingBuffer : public Buffer
   {
   private:
     int m_Duration;
diff -pruN 19.0.3+ds1-1/src/buffers/RollingFile.cpp 20.2.3+ds1-1/src/buffers/RollingFile.cpp
--- 19.0.3+ds1-1/src/buffers/RollingFile.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/RollingFile.cpp	1970-01-01 00:00:00.000000000 +0000
@@ -1,411 +0,0 @@
-/*
- *  Copyright (C) 2015-2021 Team Kodi (https://kodi.tv)
- *  Copyright (C) 2015 Sam Stenvall
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *  See LICENSE.md for more information.
- */
-
-#include "RollingFile.h"
-#include  "../BackendRequest.h"
-#include  "../pvrclient-nextpvr.h"
-#include "../utilities/XMLUtils.h"
-#include <regex>
-#include <mutex>
-
-//#define TESTURL "d:/downloads/abc.ts"
-
-using namespace NextPVR::utilities;
-using namespace timeshift;
-
-/* Rolling File mode functions */
-
-bool RollingFile::Open(const std::string inputUrl)
-{
-  m_isPaused = false;
-  m_nextLease = 0;
-  m_nextStreamInfo = 0;
-  m_nextRoll = 0;
-  m_complete = false;
-  m_isRadio = g_pvrclient->IsRadio();
-
-  m_stream_duration = 0;
-  m_bytesPerSecond = 0;
-  m_activeFilename.clear();
-  m_isLive = true;
-
-  slipFiles.clear();
-  std::stringstream ss;
-
-  ss << inputUrl ;//<< "|connection-timeout=" << 15;
-  if (ss.str().find("&epgmode=true") != std::string::npos)
-  {
-    m_isEpgBased = true;
-  }
-  else
-  {
-    m_isEpgBased = false;
-  }
-  if (!m_slipHandle.OpenFile(ss.str(), ADDON_READ_NO_CACHE))
-  {
-    kodi::Log(ADDON_LOG_ERROR, "Could not open slipHandle file");
-    return false;
-  }
-  int waitTime = 0;
-  if (m_isRadio == false)
-  {
-    waitTime = m_prebuffer;
-  }
-  do
-  {
-    // epgmode=true requires a 10 second pause changing channels
-    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
-    waitTime--;
-    if ( RollingFile::GetStreamInfo())
-    {
-      m_lastClose = 0;
-    }
-  }  while ((m_lastClose + 10) > time(nullptr));
-
-  if ( !RollingFile::GetStreamInfo())
-  {
-    kodi::Log(ADDON_LOG_ERROR, "Could not read rolling file");
-    return false;
-  }
-  m_rollingStartSeconds = m_streamStart = time(nullptr);
-  kodi::Log(ADDON_LOG_DEBUG, "RollingFile::Open in Rolling File Mode: %d", m_isEpgBased);
-  m_activeFilename = slipFiles.back().filename;
-  m_activeLength = -1;
-  m_isLeaseRunning = true;
-  m_leaseThread = std::thread([this]()
-  {
-    LeaseWorker();
-  });
-
-  while (m_stream_length < waitTime)
-  {
-    std::this_thread::sleep_for(std::chrono::milliseconds(500));
-    RollingFile::GetStreamInfo();
-  };
-  return  RollingFile::RollingFileOpen();
-}
-
-bool RollingFile::RollingFileOpen()
-{
-  kodi::addon::PVRRecording recording;
-  recording.SetRecordingTime(time(nullptr));
-  recording.SetDuration(5 * 60 * 60);
-  #if !defined(TESTURL)
-  recording.SetDirectory(m_activeFilename);
-  #endif
-
-  #if defined(TESTURL)
-  const std::string URL = TESTURL;
-  #else
-  std::string URL = kodi::tools::StringUtils::Format("%s/stream?f=%s&mode=http&sid=%s", m_settings.m_urlBase, UriEncode(m_activeFilename).c_str(), m_request.GetSID());
-  if (m_isRadio && m_activeLength == -1)
-  {
-    // reduce buffer for radio when playing in-progess slip file
-    URL += "&bufsize=32768&wait=true";
-  }
-  #endif
-  return RecordingBuffer::Open(URL.c_str(), recording);
-}
-
-bool RollingFile::GetStreamInfo()
-{
-  enum infoReturns
-  {
-    OK,
-    XML_PARSE,
-    HTTP_ERROR
-  };
-  int64_t  stream_length;
-  int64_t duration;
-  infoReturns infoReturn;
-  infoReturn = HTTP_ERROR;
-  std::string response;
-
-  if (m_nextRoll == std::numeric_limits<time_t>::max())
-  {
-    kodi::Log(ADDON_LOG_ERROR, "NextPVR not updating completed rolling file");
-    return ( m_stream_length != 0 );
-  }
-  // this call returns XML not a response
-  if (m_request.DoRequest("/service?method=channel.stream.info", response) == HTTP_OK)
-  {
-    tinyxml2::XMLDocument doc;
-    if (doc.Parse(response.c_str()) == tinyxml2::XML_SUCCESS)
-    {
-      tinyxml2::XMLNode* filesNode = doc.FirstChildElement("Files");
-      if (filesNode != nullptr)
-      {
-        stream_length = strtoll(filesNode->FirstChildElement("Length")->GetText(), nullptr, 10);
-        duration = strtoll(filesNode->FirstChildElement("Duration")->GetText(), nullptr, 10);
-        XMLUtils::GetBoolean(filesNode, "Complete", m_complete);
-        kodi::Log(ADDON_LOG_DEBUG, "channel.stream.info %lld %lld %d %d", stream_length, duration, m_complete, m_bytesPerSecond.load());
-        if (m_complete == true)
-        {
-          if ( slipFiles.empty() )
-          {
-            return false;
-          }
-          m_stream_length = stream_length-500000;
-          slipFiles.back().length = stream_length - slipFiles.back().offset;
-          m_nextStreamInfo = m_nextRoll = std::numeric_limits<time_t>::max();
-          return true;
-        }
-
-        infoReturn = OK;
-        if (duration!=0)
-        {
-          m_bytesPerSecond = stream_length/duration * 1000;
-        }
-        m_stream_length = stream_length;
-        m_stream_duration = duration/1000;
-        tinyxml2::XMLElement* pFileNode;
-        for( pFileNode = filesNode->FirstChildElement("File"); pFileNode; pFileNode=pFileNode->NextSiblingElement("File"))
-        {
-          int64_t offset = strtoll(pFileNode->Attribute("offset"), nullptr, 0);
-
-          if (!slipFiles.empty())
-          {
-            if ( slipFiles.back().offset == offset)
-            {
-              // already have this file on top
-              time_t now = time(nullptr);
-              if (now >= m_nextRoll)
-              {
-                m_nextRoll = now + 1;
-              }
-              if (slipFiles.size() == 4)
-              {
-                slipFiles.front().offset = slipFiles.front().offset + stream_length - offset;
-                if (!m_isEpgBased)
-                {
-                  duration = slipFiles.front().seconds - duration;
-                  m_rollingStartSeconds = now - m_settings.m_timeshiftBufferSeconds;
-                }
-              }
-              break;
-            }
-            slipFiles.back().length = offset - slipFiles.back().offset;
-            if (m_activeLength == -1)
-            {
-              m_activeLength = slipFiles.back().length;
-            }
-          }
-          else
-          {
-            m_activeLength = -1;
-          }
-          struct slipFile newFile;
-          newFile.filename = pFileNode->GetText();
-          newFile.offset =  offset;
-          newFile.length = -1;
-          newFile.seconds = time(nullptr);
-          slipFiles.push_back(newFile);
-          if (m_isEpgBased)
-          {
-            std::regex base_regex(".+_20.+_(\\d{4})(\\d{4})\\.ts");
-            std::smatch base_match;
-            if (std::regex_match(newFile.filename , base_match, base_regex))
-            {
-              // The first sub_match is the whole string; the next
-              // sub_match is the first parenthesized expression.
-              if (base_match.size() == 3)
-              {
-                std::ssub_match base_sub_match = base_match[1];
-                int startTime = std::stoi(base_sub_match.str());
-                base_sub_match = base_match[2];
-                int endTime = std::stoi(base_sub_match.str());
-                kodi::Log(ADDON_LOG_DEBUG, "channel.stream.info %d %d", startTime, endTime);
-                if (startTime < endTime)
-                {
-                  m_nextRoll = (time(nullptr) / 60) * 60 + (endTime - startTime) * 60 - 3 + m_settings.m_serverTimeOffset;
-                }
-                else
-                {
-                  m_nextRoll = (time(nullptr) / 60) * 60 + (2400 - startTime + endTime) * 60 - 3  + m_settings.m_serverTimeOffset;
-                }
-              }
-            }
-            if (m_nextRoll == 0)
-            {
-              m_isEpgBased = false;
-              kodi::Log(ADDON_LOG_DEBUG, "Reset to Time-based %s", newFile.filename.c_str());
-            }
-          }
-          if (!m_isEpgBased)
-          {
-            m_nextRoll = time(nullptr) + m_settings.m_timeshiftBufferSeconds/3 - 3 + m_settings.m_serverTimeOffset;
-          }
-          if (slipFiles.size() == 5)
-          {
-            time_t slipDuration = slipFiles.front().seconds;
-            slipFiles.pop_front();
-            if (m_isEpgBased)
-            {
-              slipDuration = slipFiles.front().seconds - slipDuration;
-              m_rollingStartSeconds += slipDuration;
-            }
-            else
-            {
-              m_rollingStartSeconds = time(nullptr) - m_settings.m_timeshiftBufferSeconds;
-            }
-
-          }
-          for (auto File : slipFiles )
-          {
-            kodi::Log(ADDON_LOG_DEBUG, "<Files> %s %lld %lld", File.filename.c_str(), File.offset, File.length);
-          }
-          break;
-        }
-      }
-    }
-  }
-
-  if (infoReturn != OK)
-  {
-    kodi::Log(ADDON_LOG_ERROR, "NextPVR not updating rolling file %d", infoReturn );
-    m_nextStreamInfo = time(nullptr) + 1;
-    return false;
-  }
-  m_nextStreamInfo = time(nullptr) + 10;
-  return true;
-}
-PVR_ERROR RollingFile::GetStreamTimes(kodi::addon::PVRStreamTimes& stimes)
-{
-  if (m_isLive == false)
-    return RecordingBuffer::GetStreamTimes(stimes);
-  stimes.SetStartTime(m_streamStart);
-  stimes.SetPTSStart(0);
-  stimes.SetPTSBegin(static_cast<int64_t>(m_rollingStartSeconds - m_streamStart) * STREAM_TIME_BASE);
-  stimes.SetPTSEnd(static_cast<int64_t>(time(nullptr) - m_streamStart) * STREAM_TIME_BASE);
-  return PVR_ERROR_NO_ERROR;
-}
-
-void RollingFile::Close()
-{
-  if (m_slipHandle.IsOpen())
-  {
-    RecordingBuffer::Close();
-    std::this_thread::sleep_for(std::chrono::milliseconds(500));
-    m_slipHandle.Close();
-    kodi::Log(ADDON_LOG_DEBUG, "%s:%d:", __FUNCTION__, __LINE__);
-  }
-  m_isLeaseRunning = false;
-  if (m_leaseThread.joinable())
-    m_leaseThread.join();
-
-  m_lastClose = time(nullptr);
-}
-
-ssize_t RollingFile::Read(byte *buffer, size_t length)
-{
-  std::unique_lock<std::mutex> lock(m_mutex);
-  bool foundFile = false;
-  ssize_t dataRead = m_inputHandle.Read(buffer, length);
-  if (dataRead == 0)
-  {
-    RollingFile::GetStreamInfo();
-    if (m_inputHandle.GetPosition() == m_activeLength)
-    {
-      RecordingBuffer::Close();
-      for (std::list<slipFile>::reverse_iterator File=slipFiles.rbegin(); File!=slipFiles.rend(); ++File)
-      {
-        if (File->filename == m_activeFilename)
-        {
-          foundFile = true;
-          if (File==slipFiles.rbegin())
-          {
-            // still waiting for new filename
-            kodi::Log(ADDON_LOG_ERROR, "%s:%d: waiting %s  %s", __FUNCTION__, __LINE__, File->filename.c_str(), m_activeFilename.c_str());
-          }
-          else
-          {
-            --File;
-            m_activeFilename = File->filename;
-            m_activeLength = File->length;
-          }
-          break;
-        }
-      }
-      if (foundFile == false)
-      {
-        // file removed from slip file
-        m_activeFilename = slipFiles.front().filename;
-        m_activeLength = slipFiles.front().length;
-      }
-      RollingFile::RollingFileOpen();
-      dataRead = m_inputHandle.Read(buffer, length);
-    }
-    else
-    {
-      while( m_inputHandle.GetPosition() == m_inputHandle.GetLength())
-      {
-        RollingFile::GetStreamInfo();
-        if (m_nextRoll == std::numeric_limits<time_t>::max())
-        {
-          kodi::Log(ADDON_LOG_DEBUG, "should exit %s:%d: %lld %lld %lld", __FUNCTION__, __LINE__, Length(),  m_inputHandle.GetLength() , m_inputHandle.GetPosition());
-          return 0;
-        }
-        kodi::Log(ADDON_LOG_DEBUG, "should exit %s:%d: %lld %lld %lld", __FUNCTION__, __LINE__, Length(),  m_inputHandle.GetLength() , m_inputHandle.GetPosition());
-        std::this_thread::sleep_for(std::chrono::milliseconds(200));
-      }
-    }
-    kodi::Log(ADDON_LOG_DEBUG, "%s:%d: %d %d %lld %lld", __FUNCTION__, __LINE__, length, dataRead, m_inputHandle.GetLength() , m_inputHandle.GetPosition());
-  }
-  return dataRead;
-}
-
-int64_t RollingFile::Seek(int64_t position, int whence)
-{
-  slipFile prevFile;
-  int64_t adjust;
-  RollingFile::GetStreamInfo();
-  prevFile = slipFiles.front();
-  if (slipFiles.back().offset <= position)
-  {
-    // seek on head
-    if ( m_activeFilename != slipFiles.back().filename)
-    {
-      RecordingBuffer::Close();
-      m_activeFilename = slipFiles.back().filename;
-      m_activeLength = slipFiles.back().length;
-      RollingFile::RollingFileOpen();
-    }
-    adjust = slipFiles.back().offset;
-  }
-  else
-  {
-    for (auto File : slipFiles )
-    {
-      if (position < File.offset)
-      {
-        kodi::Log(ADDON_LOG_INFO, "Found slip file %s %lld", prevFile.filename.c_str(), prevFile.offset);
-        adjust = prevFile.offset;
-        if ( m_activeFilename != prevFile.filename)
-        {
-          RecordingBuffer::Close();
-          m_activeFilename = prevFile.filename;
-          m_activeLength = prevFile.length;
-          RollingFile::RollingFileOpen();
-        }
-        break;
-      }
-      else
-      {
-        adjust = File.offset;
-      }
-      prevFile = File;
-    }
-  }
-  if (position-adjust < 0)
-  {
-    adjust = position;
-  }
-  int64_t seekval = RecordingBuffer::Seek(position - adjust, whence);
-  kodi::Log(ADDON_LOG_DEBUG, "%s:%d: %lld %d %lld", __FUNCTION__, __LINE__, position, adjust, seekval);
-  return seekval;
-}
diff -pruN 19.0.3+ds1-1/src/buffers/RollingFile.h 20.2.3+ds1-1/src/buffers/RollingFile.h
--- 19.0.3+ds1-1/src/buffers/RollingFile.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/RollingFile.h	1970-01-01 00:00:00.000000000 +0000
@@ -1,99 +0,0 @@
-/*
- *  Copyright (C) 2015-2021 Team Kodi (https://kodi.tv)
- *  Copyright (C) 2015 Sam Stenvall
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *  See LICENSE.md for more information.
- */
-
-#pragma once
-
-#include "RecordingBuffer.h"
-#include <thread>
-#include <mutex>
-#include <list>
-
-
-namespace timeshift {
-
-  /**
-   * Dummy buffer that just passes all calls through to the input file
-   * handle without actually buffering anything
-   */
-  class ATTRIBUTE_HIDDEN RollingFile : public RecordingBuffer
-  {
-  private:
-    std::string m_activeFilename;
-    int64_t m_activeLength;
-    bool m_isRadio = false;
-
-  protected:
-    kodi::vfs::CFile m_slipHandle;
-    time_t m_streamStart;
-
-    std::atomic<time_t> m_rollingStartSeconds;
-
-    std::atomic<int64_t> m_stream_length;
-    std::atomic<int64_t> m_stream_duration;
-    std::atomic<int> m_bytesPerSecond;
-
-    bool m_isEpgBased;
-    int m_prebuffer;
-    time_t m_lastClose;
-
-    bool m_isPaused;
-
-    struct slipFile{
-      std::string filename;
-      int64_t offset;
-      int64_t length;
-      int seconds;
-    };
-
-    std::list <slipFile> slipFiles;
-
-  public:
-    RollingFile() : RecordingBuffer()
-    {
-      m_lastClose = 0;
-      kodi::Log(ADDON_LOG_INFO, "EPG Based Buffer created!");
-    }
-
-    virtual ~RollingFile() {}
-
-    virtual bool Open(const std::string inputUrl) override;
-    virtual void Close() override;
-
-    virtual void PauseStream(bool bPause) override
-    {
-      if ((m_isPaused = bPause))
-        m_nextLease = 20;
-    }
-
-    virtual int64_t Length() const override
-    {
-      return m_stream_length;
-    }
-
-    virtual int64_t Position() const override
-    {
-      return m_activeLength + m_inputHandle.GetPosition();
-    }
-
-    virtual ssize_t Read(byte *buffer, size_t length) override;
-
-    int64_t Seek(int64_t position, int whence) override;
-
-    virtual PVR_ERROR GetStreamReadChunkSize(int& chunksize) override
-    {
-      chunksize = m_settings.m_liveChunkSize;
-      return PVR_ERROR_NO_ERROR;
-    }
-
-    bool RollingFileOpen();
-
-    virtual bool GetStreamInfo() override;
-
-    virtual PVR_ERROR GetStreamTimes(kodi::addon::PVRStreamTimes& times) override;
-  };
-}
diff -pruN 19.0.3+ds1-1/src/buffers/Seeker.h 20.2.3+ds1-1/src/buffers/Seeker.h
--- 19.0.3+ds1-1/src/buffers/Seeker.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/Seeker.h	2022-03-07 18:40:34.000000000 +0000
@@ -16,7 +16,7 @@
 
 namespace timeshift {
 
-  class ATTRIBUTE_HIDDEN Seeker
+  class ATTR_DLL_LOCAL Seeker
   {
   public:
     Seeker(session_data_t *sd, CircularBuffer *cirBuf) :
diff -pruN 19.0.3+ds1-1/src/buffers/TimeshiftBuffer.cpp 20.2.3+ds1-1/src/buffers/TimeshiftBuffer.cpp
--- 19.0.3+ds1-1/src/buffers/TimeshiftBuffer.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/TimeshiftBuffer.cpp	1970-01-01 00:00:00.000000000 +0000
@@ -1,569 +0,0 @@
-/*
- *  Copyright (C) 2015-2021 Team Kodi (https://kodi.tv)
- *  Copyright (C) 2015 Sam Stenvall
- *  Copyright (C) 2017 Mike Burgett [modifications to use memory-ring buffer for server-side tsb]
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *  See LICENSE.md for more information.
- */
-
-/*
- * Block request and processing logic copied from liveshift.cpp and
- * RingBuffer.cpp which are Copyright (C) Team XBMC and distributed
- * under the same license.
- */
-
-#include "TimeshiftBuffer.h"
-#include <kodi/General.h>
-
-using namespace timeshift;
-
-
-const int TimeshiftBuffer::INPUT_READ_LENGTH = 32768;
-const int TimeshiftBuffer::BUFFER_BLOCKS = 48;
-const int TimeshiftBuffer::WINDOW_SIZE = std::max(6, (BUFFER_BLOCKS/2));
-
-TimeshiftBuffer::TimeshiftBuffer()
-  : Buffer(), m_circularBuffer(INPUT_READ_LENGTH * BUFFER_BLOCKS),
-    m_seek(&m_sd, &m_circularBuffer), m_streamingclient(nullptr), m_CanPause(true)
-{
-  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer created!");
-  m_sd.lastKnownLength.store(0);
-  m_sd.ptsBegin.store(0);
-  m_sd.ptsEnd.store(0);
-  m_sd.tsbStart.store(0);
-  m_sd.streamPosition.store(0);
-  m_sd.iBytesPerSecond = 0;
-  m_sd.sessionStartTime.store(0);
-  m_sd.tsbStartTime.store(0);
-  m_sd.tsbRollOff = 0;
-  m_sd.lastBlockBuffered = 0;
-  m_sd.lastBufferTime = 0;
-  m_sd.currentWindowSize = 0;
-  m_sd.requestNumber = 0;
-  m_sd.requestBlock = 0;
-  m_sd.isPaused = false;
-  m_sd.pauseStart = 0;
-  m_sd.lastPauseAdjust = 0;
-}
-
-TimeshiftBuffer::~TimeshiftBuffer()
-{
-  TimeshiftBuffer::Close();
-}
-
-bool TimeshiftBuffer::Open(const std::string inputUrl)
-{
-  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer::Open()");
-  Buffer::Open(""); // To set the time stream starts
-  m_sd.sessionStartTime.store(m_startTime);
-  m_sd.tsbStartTime.store(m_sd.sessionStartTime.load());
-  m_streamingclient = new NextPVR::Socket(NextPVR::af_inet, NextPVR::pf_inet, NextPVR::sock_stream, NextPVR::tcp);
-  if (!m_streamingclient->create())
-  {
-    kodi::Log(ADDON_LOG_ERROR, "%s:%d: Could not connect create streaming socket", __FUNCTION__, __LINE__);
-    return false;
-  }
-
-  if (!m_streamingclient->connect(m_settings.m_hostname, m_settings.m_port))
-  {
-    kodi::Log(ADDON_LOG_ERROR, "%s:%d: Could not connect to NextPVR backend (%s:%d) for streaming", __FUNCTION__, __LINE__, m_settings.m_hostname.c_str(), m_settings.m_port);
-    return false;
-  }
-
-  m_streamingclient->send(inputUrl.c_str(), strlen(inputUrl.c_str()));
-
-  char line[256];
-
-  sprintf(line, "Connection: close\r\n");
-  m_streamingclient->send(line, strlen(line));
-
-  sprintf(line, "\r\n");
-  m_streamingclient->send(line, strlen(line));
-
-  //m_currentLivePosition = 0;
-
-
-  char buf[1024];
-  int read = m_streamingclient->receive(buf, sizeof buf, 0);
-
-  if (read < 0)
-    return false;
-
-  for (int i=0; i<read; i++)
-  {
-    if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n')
-    {
-      int remainder = read - (i+4);
-      if (remainder > 0)
-      {
-        kodi::Log(ADDON_LOG_DEBUG, "remainder: %s", &buf[i+4]);
-        WriteData((byte *)&buf[i+4], remainder, 0);
-      }
-
-      char header[256];
-      if (i < sizeof(header))
-      {
-        memset(header, 0, sizeof(header));
-        memcpy(header, buf, i);
-        kodi::Log(ADDON_LOG_DEBUG, "%s", header);
-
-        if (strstr(header, "HTTP/1.1 404") != NULL)
-        {
-          kodi::Log(ADDON_LOG_DEBUG, "Unable to start channel. 404");
-          kodi::QueueNotification(QUEUE_ERROR, kodi::GetLocalizedString(30053), "");
-          return false;
-        }
-
-      }
-
-      m_streamingclient->set_non_blocking(0);
-
-      break;
-    }
-  }
-
-  kodi::Log(ADDON_LOG_DEBUG, "TSB: Opened streaming connection!");
-  // Start the input thread
-  m_inputThread = std::thread([this]()
-  {
-    ConsumeInput();
-  });
-
-  m_tsbThread = std::thread([this]()
-  {
-    TSBTimerProc();
-  });
-
-  kodi::Log(ADDON_LOG_DEBUG, "Open grabbing lock");
-  std::unique_lock<std::mutex> lock(m_mutex);
-  kodi::Log(ADDON_LOG_DEBUG, "Open Continuing");
-  int minLength = BUFFER_BLOCKS * INPUT_READ_LENGTH;
-  kodi::Log(ADDON_LOG_DEBUG, "Open waiting for %d bytes to buffer", minLength);
-  m_reader.wait_for(lock, std::chrono::seconds(1),
-                    [this, minLength]()
-  {
-    return m_circularBuffer.BytesAvailable() >= minLength;
-  });
-  kodi::Log(ADDON_LOG_DEBUG, "Open Continuing %d / %d", m_circularBuffer.BytesAvailable(), minLength);
-  // Make sure data is flowing, before declaring success.
-  if (m_circularBuffer.BytesAvailable() != 0)
-    return true;
-  else
-    return false;
-}
-
-void TimeshiftBuffer::Close()
-{
-  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer::Close()");
-  // Wait for the input thread to terminate
-  Buffer::Close();
-
-  m_writer.notify_one();  // In case it's sleeping.
-
-  if (m_inputThread.joinable())
-    m_inputThread.join();
-
-  if (m_tsbThread.joinable())
-    m_tsbThread.join();
-
-
-  if (m_streamingclient)
-  {
-    m_streamingclient->close();
-    m_streamingclient = nullptr;
-  }
-
-  // Reset session data
-  m_sd.requestBlock = 0;
-  m_sd.requestNumber = 0;
-  m_sd.lastKnownLength.store(0);
-  m_sd.ptsBegin.store(0);
-  m_sd.ptsEnd.store(0);
-  m_sd.tsbStart.store(0);
-  m_sd.sessionStartTime.store(0);
-  m_sd.tsbStartTime.store(0);
-  m_sd.tsbRollOff = 0;
-  m_sd.iBytesPerSecond = 0;
-  m_sd.lastBlockBuffered = 0;
-  m_sd.lastBufferTime = 0;
-  m_sd.streamPosition.store(0);
-  m_sd.currentWindowSize = 0;
-  m_sd.inputBlockSize = INPUT_READ_LENGTH;
-  m_sd.isPaused = false;
-  m_sd.pauseStart = 0;
-  m_sd.lastPauseAdjust = 0;
-  m_circularBuffer.Reset();
-
-  Reset();
-}
-
-void TimeshiftBuffer::Reset()
-{
-  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer::Reset()");
-
-  // Close any open handles
-  std::unique_lock<std::mutex> lock(m_mutex);
-  m_circularBuffer.Reset();
-
-  // Reset
-  m_seek.Clear();
-
-}
-
-ssize_t TimeshiftBuffer::Read(byte *buffer, size_t length)
-{
-  int bytesRead = 0;
-  std::unique_lock<std::mutex> lock(m_mutex);
-  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer::Read() %d @ %lli", length, m_sd.streamPosition.load());
-
-  // Wait until we have enough data
-
-  if (! m_reader.wait_for(lock, std::chrono::seconds(m_readTimeout),
-    [this, length]()
-  {
-    return m_circularBuffer.BytesAvailable() >= (int )length;
-  }))
-  {
-    kodi::Log(ADDON_LOG_DEBUG, "Timeout waiting for bytes!! [buffer underflow]");
-  }
-  bytesRead = m_circularBuffer.ReadBytes(buffer, length);
-  m_sd.streamPosition.fetch_add(length);
-  if (m_circularBuffer.BytesFree() >= INPUT_READ_LENGTH)
-  {
-    // wake the filler thread if there's room in the buffer
-    m_writer.notify_one();
-  }
-
-  if (bytesRead != length)
-    kodi::Log(ADDON_LOG_DEBUG, "Read returns %d for %d request.", bytesRead, length);
-  return bytesRead;
-}
-
-//
-// When seeking, we're going to have to flush the buffers already in transit (already requested)
-// and only make data available when we get the seeked-to block in.
-//
-
-int64_t TimeshiftBuffer::Seek(int64_t position, int whence)
-{
-  bool sleep = false;
-  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer::Seek()");
-  int64_t highLimit = m_sd.lastKnownLength.load() - m_sd.iBytesPerSecond;
-  int64_t lowLimit = m_sd.tsbStart.load() + (m_sd.iBytesPerSecond << 2);  // Add Roughly 4 seconds to account for estimating the start.
-
-  if (position > highLimit)
-  {
-    kodi::Log(ADDON_LOG_ERROR, "Seek requested to %lld, limiting to %lld\n", position, highLimit);
-    position = highLimit;
-  }
-  else if (position < lowLimit)
-  {
-    kodi::Log(ADDON_LOG_ERROR, "Seek requested to %lld, limiting to %lld\n", position, lowLimit);
-    position = lowLimit;
-  }
-
-  {
-    std::unique_lock<std::mutex> lock(m_mutex);
-    // m_streamPositon is the offset in the stream that will be read next,
-    // so if that matches the seek position, don't seek.
-    kodi::Log(ADDON_LOG_DEBUG, "Seek:  %d  %d  %llu %llu", SEEK_SET, whence, m_sd.streamPosition.load(), position);
-    if ((whence == SEEK_SET) && (position == m_sd.streamPosition.load()))
-      return position;
-    m_seek.InitSeek(position, whence);
-    if (m_seek.PreprocessSeek())
-    {
-      internalRequestBlocks();
-      m_writer.notify_one(); // wake consumer.
-      sleep = true;
-    }
-  }
-  if (sleep)
-  {
-    std::unique_lock<std::mutex> sLock(m_sLock);
-    kodi::Log(ADDON_LOG_DEBUG, "Seek Waiting");
-    m_seeker.wait(sLock);
-  }
-  kodi::Log(ADDON_LOG_DEBUG, "Seek() returning %lli", position);
-  return position;
-}
-
-PVR_ERROR TimeshiftBuffer::GetStreamTimes(kodi::addon::PVRStreamTimes& stimes)
-{
-  stimes.SetStartTime(m_sd.sessionStartTime.load());
-  stimes.SetPTSStart(0);
-  stimes.SetPTSBegin(m_sd.ptsBegin.load());
-  stimes.SetPTSEnd(m_sd.ptsEnd.load());
-//  kodi::Log(ADDON_LOG_ERROR, "GetStreamTimes: %d, %lli, %lli, %lli",
-//            stimes->startTime, stimes->ptsStart, stimes->ptsBegin, stimes->ptsEnd);
-  return PVR_ERROR_NO_ERROR;
-}
-
-PVR_ERROR TimeshiftBuffer::GetStreamReadChunkSize(int& chunksize)
-{
-  // Make this a tunable parameter?
-  chunksize = TimeshiftBuffer::INPUT_READ_LENGTH;
-  return PVR_ERROR_NO_ERROR;
-}
-
-void TimeshiftBuffer::RequestBlocks()
-{
-  std::unique_lock<std::mutex> lock(m_mutex);
-  internalRequestBlocks();
-}
-
-void TimeshiftBuffer::internalRequestBlocks()
-{
-  //  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer::RequestBlocks()");
-
-  m_seek.ProcessRequests(); // Handle outstanding seek request, if there is one.
-
-  // send read request (using a basic sliding window protocol)
-  for (int i = m_sd.currentWindowSize; i < WINDOW_SIZE; i++)
-  {
-    int64_t blockOffset = m_sd.requestBlock;
-    char request[48];
-    memset(request, 0, sizeof(request));
-    snprintf(request, sizeof(request), "Range: bytes=%llu-%llu-%d", blockOffset, (blockOffset+INPUT_READ_LENGTH), m_sd.requestNumber);
-    kodi::Log(ADDON_LOG_DEBUG, "sending request: %s\n", request);
-    if (m_streamingclient->send(request, sizeof(request)) != sizeof(request))
-    {
-      kodi::Log(ADDON_LOG_DEBUG, "NOT ALL BYTES SENT!");
-    }
-
-    m_sd.requestBlock += INPUT_READ_LENGTH;
-    m_sd.requestNumber++;
-    m_sd.currentWindowSize++;
-  }
-}
-
-uint32_t TimeshiftBuffer::WatchForBlock(byte *buffer, uint64_t *block)
-{
-//  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer::WatchForBlock()");
-
-  int64_t watchFor = -1;  // Any (next) block
-  uint32_t returnBytes = 0;
-  int retries = WINDOW_SIZE + 1;
-  std::unique_lock<std::mutex> lock(m_mutex);
-  if (m_seek.Active())
-  {
-    if (m_seek.BlockRequested())
-    { // Can't watch for blocks that haven't been requested!
-      watchFor = m_seek.SeekStreamOffset();
-      kodi::Log(ADDON_LOG_DEBUG, "%s:%d: watching for bloc %llu", __FUNCTION__, __LINE__, watchFor);
-    }
-    else
-    {
-      return returnBytes;
-    }
-  }
-  //if (watchFor == -1)
-  //  kodi::Log(ADDON_LOG_DEBUG, "waiting for next block");
-  //else
-  //  kodi::Log(ADDON_LOG_DEBUG, "about to wait for block with offset: %llu\n", watchFor);
-  while (retries)
-  {
-    if (!m_streamingclient->is_valid())
-    {
-      kodi::Log(ADDON_LOG_DEBUG, "about to call receive(), socket is invalid\n");
-      return returnBytes;
-    }
-
-    if (m_streamingclient->read_ready())
-    {
-      // read response header
-      char response[128];
-      memset(response, 0, sizeof(response));
-      int responseByteCount = m_streamingclient->receive(response, sizeof(response), sizeof(response));
-      kodi::Log(ADDON_LOG_DEBUG, "%s:%d: responseByteCount: %d\n", __FUNCTION__, __LINE__, responseByteCount);
-      if (responseByteCount > 0)
-      {
-        kodi::Log(ADDON_LOG_DEBUG, "%s:%d: got: %s\n", __FUNCTION__, __LINE__, response);
-      }
-      else if (responseByteCount < 0)
-      {
-        return 0;
-      }
-  #if defined(TARGET_WINDOWS)
-      else if (responseByteCount < 0 && errno == WSAEWOULDBLOCK)
-  #else
-      else if (responseByteCount < 0 && errno == EAGAIN)
-  #endif
-      {
-        std::this_thread::sleep_for(std::chrono::milliseconds(50));
-        kodi::Log(ADDON_LOG_DEBUG, "got: %d", errno);
-        retries--;
-        continue;
-      }
-
-      // parse response header
-      long long payloadOffset;
-      int payloadSize;
-      long long fileSize;
-      int dummy;
-      sscanf(response, "%llu:%d %llu %d", &payloadOffset, &payloadSize, &fileSize, &dummy);
-      kodi::Log(ADDON_LOG_DEBUG, "PKT_IN: %llu:%d %llu %d", payloadOffset, payloadSize, fileSize, dummy);
-      if (m_sd.lastKnownLength.load() != fileSize)
-      {
-        m_sd.lastKnownLength.store(fileSize);
-      }
-
-      // read response payload
-      int bytesRead = 0;
-
-      do
-      {
-        bytesRead = m_streamingclient->receive((char *)buffer, INPUT_READ_LENGTH, payloadSize);
-#if defined(TARGET_WINDOWS)
-      } while (bytesRead < 0 && errno == WSAEWOULDBLOCK);
-#else
-      } while (bytesRead < 0 && errno == EAGAIN);
-#endif
-
-      if ((watchFor == -1) || (payloadOffset == watchFor))
-      {
-        if (m_circularBuffer.BytesAvailable() == 0) // Buffer empty!
-          m_sd.streamPosition.store(payloadOffset);
-        *block = payloadOffset;
-        returnBytes = payloadSize;
-        if (m_sd.currentWindowSize > 0)
-          m_sd.currentWindowSize--;
-        kodi::Log(ADDON_LOG_DEBUG, "Returning block %llu for buffering", payloadOffset);
-        break; // We want to buffer this payload.
-      }
-    }
-  }
-  return returnBytes;
-}
-/* Write data to ring buffer from buffer specified in 'buf'. Amount read in is
- * specified by 'size'.
- */
-bool TimeshiftBuffer::WriteData(const byte *buf, unsigned int size, uint64_t blockNum)
-{
-  std::unique_lock<std::mutex> lock(m_mutex);
-  if (m_circularBuffer.WriteBytes(buf, size))
-  {
-    m_sd.lastBlockBuffered = blockNum;
-    return true;
-  }
-  kodi::Log(ADDON_LOG_ERROR, "%s:%d: Error writing block to circularBuffer!", __FUNCTION__, __LINE__);
-  return false;
- }
-
- void TimeshiftBuffer::TSBTimerProc()
- {
-   // ONLY use atomic types/ops inR session_data, don't mess with
-   // the locks!
-   while (m_active)
-   {
-     std::this_thread::sleep_for(std::chrono::seconds(1));  // Let's try 1 per second.
-
-     // First, take a snapshot
-     time_t now = time(NULL);
-     time_t sessionStartTime = m_sd.sessionStartTime.load();
-     time_t tsbStartTime = m_sd.tsbStartTime.load();
-     int64_t lastKnownLength = m_sd.lastKnownLength.load();
-     uint64_t streamPosition = m_sd.streamPosition.load();
-     int64_t tsbStart = m_sd.tsbStart.load();
-     time_t iBytesPerSecond = m_sd.iBytesPerSecond;
-     bool isPaused = m_sd.isPaused;
-     time_t pauseStart = m_sd.pauseStart;
-     time_t lastPauseAdjust = m_sd.lastPauseAdjust;
-
-     if (tsbStartTime == 0)
-     {
-       tsbStartTime = sessionStartTime;
-     }
-
-     // Now perform the calculations
-     time_t elapsed = now - tsbStartTime;
-     //kodi::Log(ADDON_LOG_ERROR, "TSBTimerProc: time_diff: %d, tsbStartTime: %d", elapsed, tsbStartTime);
-     if (elapsed > m_settings.m_timeshiftBufferSeconds)
-     {
-       // Roll the tsb forward
-       int tsbRoll = elapsed - m_settings.m_timeshiftBufferSeconds;
-       elapsed = m_settings.m_timeshiftBufferSeconds;
-       tsbStart += (tsbRoll * iBytesPerSecond);
-       tsbStartTime += tsbRoll;
-       // kodi::Log(ADDON_LOG_ERROR, "startTime: %d, start: %lli, isPaused: %d, tsbRoll: %d", tsbStartTime, tsbStart, isPaused, tsbRoll);
-     }
-     if (m_sd.isPaused)
-     {
-       if ((now > pauseStart) && (now > lastPauseAdjust))
-       { // If we're paused, we stop requesting/receiving buffers, so lastKnownLength doesn't get updated. Fudge it here.
-         lastKnownLength += ((now - lastPauseAdjust) * iBytesPerSecond);
-         lastPauseAdjust = now;
-       }
-     }
-
-     int totalTime = now - sessionStartTime;                // total seconds we've been tuned to this channel.
-     iBytesPerSecond = totalTime ? (int )(lastKnownLength / totalTime) : 0;  // lastKnownLength (total bytes buffered) / number_of_seconds buffered.
-
-     // Write everything back
-     m_sd.tsbStartTime.store(tsbStartTime);
-     m_sd.tsbStart.store(tsbStart);
-     m_sd.lastKnownLength.store(lastKnownLength);
-     m_sd.iBytesPerSecond = iBytesPerSecond;
-     m_sd.ptsBegin.store((tsbStartTime - sessionStartTime) * STREAM_TIME_BASE);
-     m_sd.ptsEnd.store((now - sessionStartTime) * STREAM_TIME_BASE);
-     m_sd.lastPauseAdjust = lastPauseAdjust;
-
-
-//     kodi::Log(ADDON_LOG_ERROR, "tsb_start: %lli, end: %llu, B/sec: %d",
-//               tsbStart, lastKnownLength, iBytesPerSecond);
-
-   }
- }
-
-void TimeshiftBuffer::ConsumeInput()
-{
-  kodi::Log(ADDON_LOG_DEBUG, "TimeshiftBuffer::ConsumeInput()");
-  byte *buffer = new byte[INPUT_READ_LENGTH];
-
-  while (m_active)
-  {
-    memset(buffer, 0, INPUT_READ_LENGTH);
-
-    RequestBlocks();
-
-    uint32_t read;
-    uint64_t blockNo;
-    while ((read = WatchForBlock(buffer, &blockNo)))
-    {
-//      kodi::Log(ADDON_LOG_DEBUG, "Processing %d byte block", read);
-      if (WriteData(buffer, read, blockNo))
-      {
-        std::unique_lock<std::mutex> lock(m_mutex);
-        //kodi::Log(ADDON_LOG_DEBUG, "Data Buffered");
-        //kodi::Log(ADDON_LOG_DEBUG, "Notifying reader");
-        // Signal that we have data again
-        if (m_seek.Active())
-        {
-          if (m_seek.PostprocessSeek(blockNo))
-          {
-            kodi::Log(ADDON_LOG_DEBUG, "Notify Seek");
-            m_seeker.notify_one();
-          }
-        }
-        m_reader.notify_one();
-      }
-      else
-      {
-        kodi::Log(ADDON_LOG_DEBUG, "Error Buffering Data!!");
-      }
-      std::this_thread::yield();
-      std::unique_lock<std::mutex> lock(m_mutex);
-      if (m_circularBuffer.BytesFree() < INPUT_READ_LENGTH)
-      {
-        m_writer.wait(lock, [this]()
-        {
-          return (!m_active || (m_circularBuffer.BytesFree() >= INPUT_READ_LENGTH));
-        });
-      }
-      if (!m_active || ((blockNo + INPUT_READ_LENGTH) == m_sd.requestBlock))
-        break;
-    }
-  }
-  kodi::Log(ADDON_LOG_DEBUG, "CONSUMER THREAD IS EXITING!!!");
-  delete[] buffer;
-}
diff -pruN 19.0.3+ds1-1/src/buffers/TimeshiftBuffer.h 20.2.3+ds1-1/src/buffers/TimeshiftBuffer.h
--- 19.0.3+ds1-1/src/buffers/TimeshiftBuffer.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/TimeshiftBuffer.h	1970-01-01 00:00:00.000000000 +0000
@@ -1,163 +0,0 @@
-/*
- *  Copyright (C) 2015-2021 Team Kodi (https://kodi.tv)
- *  Copyright (C) 2015 Sam Stenvall
- *
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *  See LICENSE.md for more information.
- */
-
-/*
- * Block request and processing logic copied from liveshift.cpp and
- * RingBuffer.cpp which are Copyright (C) Team XBMC and distributed
- * under the same license.
- */
-
-#pragma once
-
-#include "Buffer.h"
-#include <thread>
-#include <condition_variable>
-#include <mutex>
-#include <atomic>
-#include "../Socket.h"
-#include "CircularBuffer.h"
-#include "Seeker.h"
-#include "session.h"
-
-
-namespace timeshift {
-
-  /**
-   * Timeshift buffer which buffers into a file
-   */
-  class ATTRIBUTE_HIDDEN TimeshiftBuffer : public Buffer
-  {
-  public:
-
-    TimeshiftBuffer();
-    virtual ~TimeshiftBuffer();
-
-    virtual bool Open(const std::string inputUrl) override;
-    virtual void Close() override;
-    virtual ssize_t Read(byte *buffer, size_t length) override;
-    virtual int64_t Seek(int64_t position, int whence) override;
-
-    virtual bool CanPauseStream() const override
-    {
-      return m_CanPause;
-    }
-
-    virtual void PauseStream(bool bPause) override
-    {
-      if ((m_sd.isPaused = bPause))
-        m_sd.lastPauseAdjust = m_sd.pauseStart = time(nullptr);
-      else
-        m_sd.lastPauseAdjust = m_sd.pauseStart = 0;
-    }
-
-    virtual bool CanSeekStream() const override
-    {
-      return true;
-    }
-
-    virtual int64_t Position() const override
-    {
-      return m_sd.streamPosition.load();  // very approximate
-    }
-
-    virtual int64_t Length() const override
-    {
-      return m_sd.lastKnownLength.load();
-    }
-
-    virtual bool IsTimeshifting() const override
-    {
-      if (m_active)
-        return true;
-      return false;
-    }
-
-    virtual PVR_ERROR GetStreamTimes(kodi::addon::PVRStreamTimes& stimes) override;
-    virtual PVR_ERROR GetStreamReadChunkSize(int& chunksize) override;
-
-  private:
-
-    const static int INPUT_READ_LENGTH;
-    const static int WINDOW_SIZE;
-    const static int BUFFER_BLOCKS;
-
-    NextPVR::Socket           *m_streamingclient;
-
-    /**
-     * The method that runs on m_inputThread. It reads data from the input
-     * handle and writes it to the output handle
-     */
-    void ConsumeInput();
-
-    void TSBTimerProc();
-
-
-    bool WriteData(const byte *, unsigned int, uint64_t);
-
-    /**
-     * Closes any open file handles and resets all file positions
-     */
-    void Reset();
-
-    /**
-     * Sends requests for blocks to backend.
-     */
-    void RequestBlocks(void);          // Acquires lock, calls internalRequestBlocks();
-    void internalRequestBlocks(void);  // Call when already holding lock.
-
-    /**
-     * Pull in incoming blocks.
-     */
-    uint32_t WatchForBlock(byte *, uint64_t *);
-
-    /**
-     * The thread that reads from m_inputHandle and writes to the output
-     * handles
-     */
-    std::thread m_inputThread;
-
-    /**
-     * The thread that keeps track of the size of the current tsb, and
-     * drags the starting time forward when slip seconds is exceeded
-     */
-    std::thread m_tsbThread;
-
-    /**
-     * Protects m_output*Handle
-     */
-    // mutable std::mutex m_mutex moved to base class
-
-    /**
-     * Protects seek completion
-     */
-    mutable std::mutex m_sLock;
-
-    /**
-     * Signaled whenever new packets have been added to the buffer
-     */
-    mutable std::condition_variable m_reader;
-
-    /**
-     * Signaled whenever data has read from the buffer
-     */
-    mutable std::condition_variable m_writer;
-
-    /**
-     * Signaled whenever seek processing is complete.
-     */
-    mutable std::condition_variable m_seeker;
-
-    /**
-     * The current write position in the buffer file
-     */
-    Seeker m_seek;
-    CircularBuffer m_circularBuffer;
-    session_data_t m_sd;
-    bool m_CanPause;
-  };
-}
diff -pruN 19.0.3+ds1-1/src/buffers/TranscodedBuffer.h 20.2.3+ds1-1/src/buffers/TranscodedBuffer.h
--- 19.0.3+ds1-1/src/buffers/TranscodedBuffer.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/buffers/TranscodedBuffer.h	2022-03-07 18:40:34.000000000 +0000
@@ -14,7 +14,7 @@
 
 namespace timeshift {
 
-  class ATTRIBUTE_HIDDEN TranscodedBuffer : public DummyBuffer
+  class ATTR_DLL_LOCAL TranscodedBuffer : public DummyBuffer
   {
   public:
     TranscodedBuffer() : DummyBuffer()
diff -pruN 19.0.3+ds1-1/src/Channels.cpp 20.2.3+ds1-1/src/Channels.cpp
--- 19.0.3+ds1-1/src/Channels.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/Channels.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -175,18 +175,8 @@ PVR_ERROR Channels::GetChannels(bool rad
 
 PVR_ERROR Channels::GetChannelGroupsAmount(int& amount)
 {
-  int groups = 0;
-  tinyxml2::XMLDocument doc;
-  if (m_request.DoMethodRequest("channel.groups", doc) == tinyxml2::XML_SUCCESS)
-  {
-    tinyxml2::XMLNode* groupsNode = doc.RootElement()->FirstChildElement("groups");
-    tinyxml2::XMLNode* pGroupNode;
-    for( pGroupNode = groupsNode->FirstChildElement("group"); pGroupNode; pGroupNode=pGroupNode->NextSiblingElement())
-    {
-      groups++;
-    }
-  }
-  amount = groups;
+  // this could be different from the number of backend groups if radio and TV are mixed or if groups are empty
+  amount = m_radioGroups.size() + m_tvGroups.size();
   return PVR_ERROR_NO_ERROR;
 }
 
@@ -201,42 +191,29 @@ PVR_RECORDING_CHANNEL_TYPE Channels::Get
 
 PVR_ERROR Channels::GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results)
 {
-  // radioGroups.size() will be zero before 5.2 API
-  if (radio && m_radioGroups.size() == 0)
+  const std::unordered_set selectedGroups = radio ? m_radioGroups : m_tvGroups;
+  // Many users won't have radio groups
+  if (selectedGroups.size() == 0)
     return PVR_ERROR_NO_ERROR;
 
-  if (m_settings.m_backendVersion >= 50200)
-  {
-    // empty groups will not be added
-    for (auto const& group : radio ? m_radioGroups : m_tvGroups)
-    {
-      kodi::addon::PVRChannelGroup tag;
-      tag.SetIsRadio(radio);
-      tag.SetPosition(0);
-      tag.SetGroupName(group);
-      results.Add(tag);
-    }
-    return PVR_ERROR_NO_ERROR;
-  }
-
-  // for tv, use the groups returned by nextpvr
   tinyxml2::XMLDocument doc;
   if (m_request.DoMethodRequest("channel.groups", doc) == tinyxml2::XML_SUCCESS)
   {
     tinyxml2::XMLNode* groupsNode = doc.RootElement()->FirstChildElement("groups");
     tinyxml2::XMLNode* pGroupNode;
+    std::string group;
+    int priority = 1;
     for (pGroupNode = groupsNode->FirstChildElement("group"); pGroupNode; pGroupNode = pGroupNode->NextSiblingElement())
     {
-      kodi::addon::PVRChannelGroup tag;
-      tag.SetIsRadio(radio);
-      tag.SetPosition(0); // groups default order, unused
-      std::string group;
       if (XMLUtils::GetString(pGroupNode, "name", group))
       {
-        // tell XBMC about channel, ignoring "All Channels" since xbmc has an built in group with effectively the same function
-        tag.SetGroupName(group);
-        if (group != "All Channels")
+        // "All Channels" won't match any group, skip empty NextPVR groups
+        if (selectedGroups.find(group) != selectedGroups.end())
         {
+          kodi::addon::PVRChannelGroup tag;
+          tag.SetIsRadio(radio);
+          tag.SetPosition(priority++);
+          tag.SetGroupName(group);
           results.Add(tag);
         }
       }
@@ -246,10 +223,10 @@ PVR_ERROR Channels::GetChannelGroups(boo
   {
     kodi::Log(ADDON_LOG_DEBUG, "No Channel Group");
   }
+
   return PVR_ERROR_NO_ERROR;
 }
 
-
 PVR_ERROR Channels::GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group, kodi::addon::PVRChannelGroupMembersResultSet& results)
 {
   std::string encodedGroupName = UriEncode(group.GetGroupName());
diff -pruN 19.0.3+ds1-1/src/Channels.h 20.2.3+ds1-1/src/Channels.h
--- 19.0.3+ds1-1/src/Channels.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/Channels.h	2022-03-07 18:40:34.000000000 +0000
@@ -10,12 +10,12 @@
 
 #include "BackendRequest.h"
 #include <kodi/addon-instance/PVR.h>
-#include <set>
+#include <unordered_set>
 
 namespace NextPVR
 {
 
-  class ATTRIBUTE_HIDDEN Channels
+  class ATTR_DLL_LOCAL Channels
   {
 
   public:
@@ -44,8 +44,8 @@ namespace NextPVR
     void DeleteChannelIcons();
     PVR_RECORDING_CHANNEL_TYPE GetChannelType(unsigned int uid);
     std::map<int, std::pair<bool, bool>> m_channelDetails;
-    std::set<std::string> m_tvGroups;
-    std::set<std::string> m_radioGroups;
+    std::unordered_set<std::string> m_tvGroups;
+    std::unordered_set<std::string> m_radioGroups;
 
   private:
     Channels() = default;
diff -pruN 19.0.3+ds1-1/src/EPG.cpp 20.2.3+ds1-1/src/EPG.cpp
--- 19.0.3+ds1-1/src/EPG.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/EPG.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -82,21 +82,12 @@ PVR_ERROR EPG::GetEPGForChannel(int chan
       std::string artworkPath;
       if (m_settings.m_downloadGuideArtwork)
       {
-        // artwork URL
-        if (m_settings.m_backendVersion < 50000)
-        {
-          const int epgOid = XMLUtils::GetIntValue(pListingNode, "id");
-          artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&sid=%s&event_id=%d", m_settings.m_urlBase, m_request.GetSID(), epgOid);
-        }
+        if (m_settings.m_sendSidWithMetadata)
+          artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&sid=%s&name=%s", m_settings.m_urlBase, m_request.GetSID(), UriEncode(title).c_str());
         else
-        {
-          if (m_settings.m_sendSidWithMetadata)
-            artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&sid=%s&name=%s", m_settings.m_urlBase, m_request.GetSID(), UriEncode(title).c_str());
-          else
-            artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&name=%s", m_settings.m_urlBase, UriEncode(title).c_str());
-          if (m_settings.m_guideArtPortrait)
-            artworkPath += "&prefer=poster";
-        }
+          artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&name=%s", m_settings.m_urlBase, UriEncode(title).c_str());
+        if (m_settings.m_guideArtPortrait)
+          artworkPath += "&prefer=poster";
         broadcast.SetIconPath(artworkPath);
       }
       std::string sGenre;
diff -pruN 19.0.3+ds1-1/src/EPG.h 20.2.3+ds1-1/src/EPG.h
--- 19.0.3+ds1-1/src/EPG.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/EPG.h	2022-03-07 18:40:34.000000000 +0000
@@ -15,7 +15,7 @@
 
 namespace NextPVR
 {
-  class ATTRIBUTE_HIDDEN EPG
+  class ATTR_DLL_LOCAL EPG
   {
   public:
     /**
diff -pruN 19.0.3+ds1-1/src/MenuHook.cpp 20.2.3+ds1-1/src/MenuHook.cpp
--- 19.0.3+ds1-1/src/MenuHook.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/MenuHook.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -33,7 +33,7 @@ PVR_ERROR MenuHook::CallSettingsMenuHook
   }
   else if (menuhook.GetHookId() == PVR_MENUHOOK_SETTING_OPEN_SETTINGS)
   {
-    kodi::OpenSettings();
+    kodi::addon::OpenSettings();
   }
 
   return PVR_ERROR_NO_ERROR;
@@ -95,11 +95,8 @@ void MenuHook::ConfigureMenuHook()
   menuHook.SetLocalizedStringId(30196);
   g_pvrclient->AddMenuHook(menuHook);
 
-  if (m_settings.m_backendVersion >= 50000)
-  {
-    menuHook.SetCategory(PVR_MENUHOOK_RECORDING);
-    menuHook.SetHookId(PVR_MENUHOOK_RECORDING_FORGET_RECORDING);
-    menuHook.SetLocalizedStringId(30184);
-    g_pvrclient->AddMenuHook(menuHook);
-  }
+  menuHook.SetCategory(PVR_MENUHOOK_RECORDING);
+  menuHook.SetHookId(PVR_MENUHOOK_RECORDING_FORGET_RECORDING);
+  menuHook.SetLocalizedStringId(30184);
+  g_pvrclient->AddMenuHook(menuHook);
 }
diff -pruN 19.0.3+ds1-1/src/MenuHook.h 20.2.3+ds1-1/src/MenuHook.h
--- 19.0.3+ds1-1/src/MenuHook.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/MenuHook.h	2022-03-07 18:40:34.000000000 +0000
@@ -23,7 +23,7 @@ namespace NextPVR
   constexpr int PVR_MENUHOOK_SETTING_SEND_WOL = 604;
   constexpr int PVR_MENUHOOK_SETTING_OPEN_SETTINGS = 605;
 
-  class ATTRIBUTE_HIDDEN MenuHook
+  class ATTR_DLL_LOCAL MenuHook
   {
   public:
     /**
diff -pruN 19.0.3+ds1-1/src/pvrclient-nextpvr.cpp 20.2.3+ds1-1/src/pvrclient-nextpvr.cpp
--- 19.0.3+ds1-1/src/pvrclient-nextpvr.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/pvrclient-nextpvr.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -76,8 +76,8 @@ std::string UriEncode(const std::string
 /************************************************************/
 /** Class interface */
 
-cPVRClientNextPVR::cPVRClientNextPVR(const CNextPVRAddon& base, KODI_HANDLE instance, const std::string& kodiVersion) :
-  kodi::addon::CInstancePVRClient(instance, kodiVersion), m_base(base)
+cPVRClientNextPVR::cPVRClientNextPVR(const CNextPVRAddon& base, const kodi::addon::IInstanceInfo& instance) :
+  kodi::addon::CInstancePVRClient(instance), m_base(base)
 {
   m_bConnected = false;
   m_supportsLiveTimeshift = false;
@@ -169,14 +169,14 @@ ADDON_STATUS cPVRClientNextPVR::Connect(
         else
         {
           m_request.DoActionRequest("session.logout");
-          SetConnectionState("Version failure", PVR_CONNECTION_STATE_VERSION_MISMATCH, kodi::GetLocalizedString(30050));
+          SetConnectionState("Version failure", PVR_CONNECTION_STATE_VERSION_MISMATCH, kodi::addon::GetLocalizedString(30050));
           status = ADDON_STATUS_PERMANENT_FAILURE;
         }
       }
       else
       {
         kodi::Log(ADDON_LOG_DEBUG, "session.login failed");
-        SetConnectionState("Access denied", PVR_CONNECTION_STATE_ACCESS_DENIED, kodi::GetLocalizedString(30052));
+        SetConnectionState("Access denied", PVR_CONNECTION_STATE_ACCESS_DENIED, kodi::addon::GetLocalizedString(30052));
         status = ADDON_STATUS_PERMANENT_FAILURE;
       }
     }
@@ -239,13 +239,13 @@ void cPVRClientNextPVR::ConfigurePostCon
         if (!enabled)
         {
           kodi::Log(ADDON_LOG_INFO, "%s installed but not enabled at startup", addonName.c_str());
-          kodi::QueueFormattedNotification(QueueMsg::QUEUE_ERROR, kodi::GetLocalizedString(30191).c_str(), addonName.c_str());
+          kodi::QueueFormattedNotification(QueueMsg::QUEUE_ERROR, kodi::addon::GetLocalizedString(30191).c_str(), addonName.c_str());
         }
       }
       else // Not installed
       {
         kodi::Log(ADDON_LOG_INFO, "%s not installed", addonName.c_str());
-        kodi::QueueFormattedNotification(QueueMsg::QUEUE_ERROR, kodi::GetLocalizedString(30192).c_str(), addonName.c_str());
+        kodi::QueueFormattedNotification(QueueMsg::QUEUE_ERROR, kodi::addon::GetLocalizedString(30192).c_str(), addonName.c_str());
       }
     }
 
@@ -258,21 +258,13 @@ void cPVRClientNextPVR::ConfigurePostCon
     {
       m_timeshiftBuffer = new timeshift::ClientTimeShift();
     }
-    else if (m_settings.m_liveStreamingMethod != eStreamingMethod::Timeshift)
-    {
-      m_timeshiftBuffer = new timeshift::RollingFile();
-    }
-    else
-    {
-      m_timeshiftBuffer = new timeshift::TimeshiftBuffer();
-    }
   }
 
-  const bool liveStreams = kodi::GetSettingBoolean("uselivestreams");
+  const bool liveStreams = kodi::addon::GetSettingBoolean("uselivestreams");
   if (liveStreams)
       m_channels.LoadLiveStreams();
 
-  if (m_lastEPGUpdateTime == 0 && m_settings.m_backendVersion >= 5007)
+  if (m_lastEPGUpdateTime == 0)
     m_request.GetLastUpdate("system.epg.summary", m_lastEPGUpdateTime);
 
 }
@@ -298,49 +290,46 @@ bool cPVRClientNextPVR::IsUp()
         if (update_time > m_lastRecordingUpdateTime)
         {
           m_lastRecordingUpdateTime = std::numeric_limits<time_t>::max();
-          if (m_settings.m_backendVersion >= 5007)
+          time_t lastUpdate;
+          if (m_request.GetLastUpdate("system.epg.summary", lastUpdate) == tinyxml2::XML_SUCCESS)
           {
-            time_t lastUpdate;
-            if (m_request.GetLastUpdate("system.epg.summary", lastUpdate) == tinyxml2::XML_SUCCESS)
+            if (lastUpdate > m_lastEPGUpdateTime)
             {
-              if (lastUpdate > m_lastEPGUpdateTime)
+              // trigger EPG updates for all channels with a guide source
+              kodi::Log(ADDON_LOG_DEBUG, "Trigger EPG update start");
+              int channels = 0;
+              for (const auto &updateChannel : m_channels.m_channelDetails)
               {
-                // trigger EPG updates for all channels with a guide source
-                kodi::Log(ADDON_LOG_DEBUG, "Trigger EPG update start");
-                int channels = 0;
-                for (const auto &updateChannel : m_channels.m_channelDetails)
+                if (updateChannel.second.first == false)
                 {
-                  if (updateChannel.second.first == false)
-                  {
-                    channels++;
-                    TriggerEpgUpdate(updateChannel.first);
-                  }
+                  channels++;
+                  TriggerEpgUpdate(updateChannel.first);
                 }
-                kodi::Log(ADDON_LOG_DEBUG, "Triggered %d channel updates", channels);
-
-                m_lastEPGUpdateTime = lastUpdate;
-                m_lastRecordingUpdateTime = update_time;
-                return m_bConnected;
               }
-            }
-            if (update_time <= m_timers.m_lastTimerUpdateTime + 1)
-            {
-              // we already updated this one in Kodi
-              m_lastRecordingUpdateTime = time(nullptr);
+              kodi::Log(ADDON_LOG_DEBUG, "Triggered %d channel updates", channels);
+
+              m_lastEPGUpdateTime = lastUpdate;
+              m_lastRecordingUpdateTime = update_time;
               return m_bConnected;
             }
-            if (m_request.GetLastUpdate("recording.lastupdated&ignore_resume=true", lastUpdate) == tinyxml2::XML_SUCCESS)
+          }
+          if (update_time <= m_timers.m_lastTimerUpdateTime + 1)
+          {
+            // we already updated this one in Kodi
+            m_lastRecordingUpdateTime = time(nullptr);
+            return m_bConnected;
+          }
+          if (m_request.GetLastUpdate("recording.lastupdated&ignore_resume=true", lastUpdate) == tinyxml2::XML_SUCCESS)
+          {
+            if (lastUpdate <= m_timers.m_lastTimerUpdateTime)
             {
-              if (lastUpdate <= m_timers.m_lastTimerUpdateTime)
+              if (m_settings.m_backendResume)
               {
-                if (m_settings.m_backendResume)
-                {
-                  // only resume position changed
-                  m_recordings.GetRecordingsLastPlayedPosition();
-                  m_lastRecordingUpdateTime = update_time;
-                }
-                return m_bConnected;
+                // only resume position changed
+                m_recordings.GetRecordingsLastPlayedPosition();
+                m_lastRecordingUpdateTime = update_time;
               }
+              return m_bConnected;
             }
           }
           g_pvrclient->TriggerRecordingUpdate();
@@ -484,7 +473,7 @@ PVR_ERROR cPVRClientNextPVR::GetBackendV
   if (m_bConnected)
     version = std::to_string(m_settings.m_backendVersion);
   else
-    version = kodi::GetLocalizedString(13205);
+    version = kodi::addon::GetLocalizedString(13205);
   return PVR_ERROR_NO_ERROR;
 }
 
@@ -492,7 +481,7 @@ PVR_ERROR cPVRClientNextPVR::GetConnecti
 {
   connection = m_settings.m_hostname;
   if (!m_bConnected)
-    connection += ": " + kodi::GetLocalizedString(15208);
+    connection += ": " + kodi::addon::GetLocalizedString(15208);
   return PVR_ERROR_NO_ERROR;
 }
 
@@ -579,16 +568,6 @@ bool cPVRClientNextPVR::OpenLiveStream(c
     m_livePlayer = m_realTimeBuffer;
     return m_livePlayer->Open(line, ADDON_READ_CACHED);
   }
-  else if (channel.GetIsRadio() == false && m_supportsLiveTimeshift && m_settings.m_liveStreamingMethod == Timeshift)
-  {
-    line = kodi::tools::StringUtils::Format("GET /live?channeloid=%d&mode=liveshift&client=XBMC-%s HTTP/1.0\r\n", channel.GetUniqueId(), m_request.GetSID());
-    m_livePlayer = m_timeshiftBuffer;
-  }
-  else if (m_settings.m_liveStreamingMethod == RollingFile)
-  {
-    line = kodi::tools::StringUtils::Format("%s/live?channeloid=%d&client=XBMC-%s&epgmode=true", m_settings.m_urlBase, channel.GetUniqueId(), m_request.GetSID());
-    m_livePlayer = m_timeshiftBuffer;
-  }
   else if (m_settings.m_liveStreamingMethod == ClientTimeshift)
   {
     line = kodi::tools::StringUtils::Format("%s/live?channeloid=%d&client=%s&sid=%s", m_settings.m_urlBase, channel.GetUniqueId(), m_request.GetSID(), m_request.GetSID());
@@ -965,6 +944,7 @@ PVR_ERROR cPVRClientNextPVR::GetCapabili
 
   capabilities.SetSupportsEPG(true);
   capabilities.SetSupportsRecordings(true);
+  capabilities.SetSupportsRecordingsDelete(true);
   capabilities.SetSupportsRecordingsUndelete(false);
   capabilities.SetSupportsRecordingSize(m_settings.m_showRecordingSize);
   capabilities.SetSupportsTimers(true);
@@ -980,5 +960,6 @@ PVR_ERROR cPVRClientNextPVR::GetCapabili
   capabilities.SetSupportsRecordingsLifetimeChange(false);
   capabilities.SetSupportsDescrambleInfo(false);
   capabilities.SetSupportsRecordingPlayCount(m_settings.m_backendResume);
+  capabilities.SetSupportsProviders(false);
   return PVR_ERROR_NO_ERROR;
 }
diff -pruN 19.0.3+ds1-1/src/pvrclient-nextpvr.h 20.2.3+ds1-1/src/pvrclient-nextpvr.h
--- 19.0.3+ds1-1/src/pvrclient-nextpvr.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/pvrclient-nextpvr.h	2022-03-07 18:40:34.000000000 +0000
@@ -24,8 +24,6 @@
 #include "buffers/ClientTimeshift.h"
 #include "buffers/DummyBuffer.h"
 #include "buffers/RecordingBuffer.h"
-#include "buffers/RollingFile.h"
-#include "buffers/TimeshiftBuffer.h"
 #include "buffers/TranscodedBuffer.h"
 #include <map>
 
@@ -38,11 +36,11 @@ enum eNowPlaying
   Transcoding
 };
 
-class ATTRIBUTE_HIDDEN cPVRClientNextPVR : public kodi::addon::CInstancePVRClient
+class ATTR_DLL_LOCAL cPVRClientNextPVR : public kodi::addon::CInstancePVRClient
 {
 public:
   /* Class interface */
-   cPVRClientNextPVR(const CNextPVRAddon& base, KODI_HANDLE instance, const std::string& kodiVersion);
+   cPVRClientNextPVR(const CNextPVRAddon& base, const kodi::addon::IInstanceInfo& instance);
 
   ~cPVRClientNextPVR();
 
diff -pruN 19.0.3+ds1-1/src/Recordings.cpp 20.2.3+ds1-1/src/Recordings.cpp
--- 19.0.3+ds1-1/src/Recordings.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/Recordings.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -123,7 +123,7 @@ PVR_ERROR Recordings::GetRecordings(bool
     tinyxml2::XMLNode* pRecordingNode;
     std::map<std::string, int> names;
     std::map<std::string, int> seasons;
-    if ((m_settings.m_flattenRecording && m_settings.m_kodiLook) || m_settings.m_separateSeasons)
+    if (m_settings.m_flattenRecording || m_settings.m_separateSeasons)
     {
       kodi::addon::PVRRecording mytag;
       int season;
@@ -135,7 +135,7 @@ PVR_ERROR Recordings::GetRecordings(bool
           continue;
         std::string title;
         XMLUtils::GetString(pRecordingNode, "name", title);
-        if ((m_settings.m_flattenRecording && m_settings.m_kodiLook))
+        if (m_settings.m_flattenRecording)
           names[title]++;
 
         if (ParseNextPVRSubtitle(pRecordingNode, mytag))
@@ -236,7 +236,7 @@ bool Recordings::UpdatePvrRecording(cons
   }
   else if (status == "Failed")
   {
-    buffer = kodi::tools::StringUtils::Format("/%s/%s", kodi::GetLocalizedString(30166).c_str(), title.c_str());
+    buffer = kodi::tools::StringUtils::Format("/%s/%s", kodi::addon::GetLocalizedString(30166).c_str(), title.c_str());
     tag.SetDirectory(buffer);
     if (XMLUtils::GetString(pRecordingNode, "reason", buffer))
     {
@@ -283,24 +283,11 @@ bool Recordings::UpdatePvrRecording(cons
 
   if (XMLUtils::GetString(pRecordingNode, "subtitle", buffer))
   {
-    if (!m_settings.m_kodiLook)
-    {
-      tag.SetTitle(buffer);
-    }
     if (ParseNextPVRSubtitle(pRecordingNode, tag))
     {
       if (m_settings.m_separateSeasons && multipleSeasons && tag.GetSeriesNumber() != PVR_RECORDING_INVALID_SERIES_EPISODE)
       {
-        if (!m_settings.m_kodiLook)
-        {
-          tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s %d", title.c_str(), kodi::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber()));
-          tag.SetSeriesNumber(PVR_RECORDING_INVALID_SERIES_EPISODE);
-          tag.SetEpisodeNumber(PVR_RECORDING_INVALID_SERIES_EPISODE);
-        }
-        else
-        {
-          tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s %d", tag.GetTitle().c_str(), kodi::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber()));
-        }
+        tag.SetDirectory(kodi::tools::StringUtils::Format("/%s/%s %d", tag.GetTitle().c_str(), kodi::addon::GetLocalizedString(20373).c_str(), tag.GetSeriesNumber()));
       }
     }
   }
@@ -379,30 +366,20 @@ bool Recordings::UpdatePvrRecording(cons
   if (tag.GetChannelType() != PVR_RECORDING_CHANNEL_TYPE_RADIO)
   {
     std::string artworkPath;
-    if (m_settings.m_backendVersion < 50000)
-    {
-      artworkPath = kodi::tools::StringUtils::Format("%s/service?method=recording.artwork&sid=%s&recording_id=%s", m_settings.m_urlBase, m_request.GetSID(), tag.GetRecordingId().c_str());
-      tag.SetThumbnailPath(artworkPath);
-      artworkPath = kodi::tools::StringUtils::Format("%s/service?method=recording.fanart&sid=%s&recording_id=%s", m_settings.m_urlBase, m_request.GetSID(), tag.GetRecordingId().c_str());
-      tag.SetFanartPath(artworkPath);
-    }
+    std::string name;
+    buffer.clear();
+    if (XMLUtils::GetString(pRecordingNode, "group", buffer))
+        name = UriEncode(buffer);
     else
-    {
-      std::string name;
-      buffer.clear();
-      if (XMLUtils::GetString(pRecordingNode, "group", buffer))
-          name = UriEncode(buffer);
-      else
-          name = UriEncode(title);
+        name = UriEncode(title);
 
-      if (m_settings.m_sendSidWithMetadata)
-        artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&sid=%s&name=%s", m_settings.m_urlBase, m_request.GetSID(), name.c_str());
-      else
-        artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&name=%s", m_settings.m_urlBase, name.c_str());
-      tag.SetFanartPath(artworkPath);
-      artworkPath += "&prefer=poster";
-      tag.SetThumbnailPath(artworkPath);
-    }
+    if (m_settings.m_sendSidWithMetadata)
+      artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&sid=%s&name=%s", m_settings.m_urlBase, m_request.GetSID(), name.c_str());
+    else
+      artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&name=%s", m_settings.m_urlBase, name.c_str());
+    tag.SetFanartPath(artworkPath);
+    artworkPath += "&prefer=poster";
+    tag.SetThumbnailPath(artworkPath);
   }
   if (XMLUtils::GetAdditiveString(pRecordingNode->FirstChildElement("genres"), "genre", EPG_STRING_TOKEN_SEPARATOR, buffer, true))
   {
@@ -443,18 +420,15 @@ bool Recordings::ParseNextPVRSubtitle(co
         tag.SetSeriesNumber(std::stoi(base_sub_match.str()));
         base_sub_match = base_match[2];
         tag.SetEpisodeNumber(std::stoi(base_sub_match.str()));
-        if (m_settings.m_kodiLook)
+        if (base_match.size() == 4)
         {
-          if (base_match.size() == 4)
-          {
-            base_sub_match = base_match[3];
-            tag.SetEpisodeName(base_sub_match.str());
-          }
+          base_sub_match = base_match[3];
+          tag.SetEpisodeName(base_sub_match.str());
         }
         hasSeasonEpisode = true;
       }
     }
-    else if (m_settings.m_kodiLook)
+    else
     {
       tag.SetEpisodeName(buffer);
     }
@@ -475,10 +449,6 @@ bool Recordings::ParseNextPVRSubtitle(co
           tag.SetSeriesNumber(std::stoi(base_sub_match.str()));
           base_sub_match = base_match[2];
           tag.SetEpisodeNumber(std::stoi(base_sub_match.str()));
-          if (!m_settings.m_kodiLook)
-          {
-            tag.SetTitle(kodi::tools::StringUtils::Format("S%2.2dE%2.2d - %s", tag.GetSeriesNumber(), tag.GetEpisodeNumber(), buffer.c_str()));
-          }
           hasSeasonEpisode = true;
         }
       }
@@ -585,22 +555,19 @@ PVR_ERROR Recordings::SetRecordingLastPl
       kodi::Log(ADDON_LOG_DEBUG, "SetRecordingLastPlayedPosition failed");
       return PVR_ERROR_FAILED;
     }
-    if (m_settings.m_backendVersion >= 5007)
+    time_t lastUpdate;
+    if (m_request.GetLastUpdate("recording.lastupdated&ignore_resume=true", lastUpdate) == tinyxml2::XML_SUCCESS)
     {
-      time_t lastUpdate;
-      if (m_request.GetLastUpdate("recording.lastupdated&ignore_resume=true", lastUpdate) == tinyxml2::XML_SUCCESS)
+      if (timerUpdate >= lastUpdate)
       {
-        if (timerUpdate >= lastUpdate)
+        if (m_request.GetLastUpdate("recording.lastupdated", lastUpdate) == tinyxml2::XML_SUCCESS)
         {
-          if (m_request.GetLastUpdate("recording.lastupdated", lastUpdate) == tinyxml2::XML_SUCCESS)
-          {
-            // only change is watched point so skip it
-            m_lastPlayed[std::stoi(recording.GetRecordingId())] = lastplayedposition;
-            // reload recording list so Kodi can get new duration
-            if (!isWatched)
-              g_pvrclient->TriggerRecordingUpdate();
-            g_pvrclient->m_lastRecordingUpdateTime = lastUpdate;
-          }
+          // only change is watched point so skip it
+          m_lastPlayed[std::stoi(recording.GetRecordingId())] = lastplayedposition;
+          // reload recording list so Kodi can get new duration
+          if (!isWatched)
+            g_pvrclient->TriggerRecordingUpdate();
+          g_pvrclient->m_lastRecordingUpdateTime = lastUpdate;
         }
       }
     }
@@ -624,20 +591,10 @@ PVR_ERROR Recordings::GetRecordingEdl(co
   tinyxml2::XMLDocument doc;
   if (m_request.DoMethodRequest(request, doc) == tinyxml2::XML_SUCCESS)
   {
-    int numEDL = 0;
     tinyxml2::XMLNode* commercialsNode = doc.RootElement()->FirstChildElement("commercials");
     tinyxml2::XMLNode* pCommercialNode;
     for (pCommercialNode = commercialsNode->FirstChildElement("commercial"); pCommercialNode; pCommercialNode = pCommercialNode->NextSiblingElement())
     {
-      if (numEDL++ == PVR_ADDON_EDL_LENGTH)
-      {
-        /* PVR core stores EDL breaks in a hard-coded array in PVRClient.cpp PVR_EDL_ENTRY edl_array[PVR_ADDON_EDL_LENGTH];
-        * currently https://github.com/xbmc/xbmc/blob/master/xbmc/pvr/addons/PVRClient.cpp#L1056
-        * This check avoids overflowing this arrray */
-         
-        kodi::Log(ADDON_LOG_WARNING, "Maximum EDL entries reached");
-        break;
-      }
       kodi::addon::PVREDLEntry entry;
       std::string buffer;
       XMLUtils::GetString(pCommercialNode, "start", buffer);
diff -pruN 19.0.3+ds1-1/src/Recordings.h 20.2.3+ds1-1/src/Recordings.h
--- 19.0.3+ds1-1/src/Recordings.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/Recordings.h	2022-03-07 18:40:34.000000000 +0000
@@ -17,7 +17,7 @@
 namespace NextPVR
 {
 
-  class ATTRIBUTE_HIDDEN Recordings
+  class ATTR_DLL_LOCAL Recordings
   {
 
   public:
diff -pruN 19.0.3+ds1-1/src/Settings.cpp 20.2.3+ds1-1/src/Settings.cpp
--- 19.0.3+ds1-1/src/Settings.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/Settings.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -29,19 +29,19 @@ void Settings::ReadFromAddon()
   /* Connection settings */
   /***********************/
 
-  std::string protocol = kodi::GetSettingString("hostprotocol", DEFAULT_PROTOCOL);
+  std::string protocol = kodi::addon::GetSettingString("hostprotocol", DEFAULT_PROTOCOL);
 
-  m_hostname = kodi::GetSettingString("host", DEFAULT_HOST);
+  m_hostname = kodi::addon::GetSettingString("host", DEFAULT_HOST);
   uri::decode(m_hostname);
 
-  m_port = kodi::GetSettingInt("port", DEFAULT_PORT);
+  m_port = kodi::addon::GetSettingInt("port", DEFAULT_PORT);
 
-  m_PIN = kodi::GetSettingString("pin", DEFAULT_PIN);
+  m_PIN = kodi::addon::GetSettingString("pin", DEFAULT_PIN);
 
   sprintf(m_urlBase, "%s://%.255s:%d", protocol.c_str(), m_hostname.c_str(), m_port);
 
-  m_enableWOL = kodi::GetSettingBoolean("wolenable", false);
-  m_hostMACAddress = kodi::GetSettingString("host_mac");
+  m_enableWOL = kodi::addon::GetSettingBoolean("wolenable", false);
+  m_hostMACAddress = kodi::addon::GetSettingString("host_mac");
   if (m_enableWOL)
   {
     if (m_hostMACAddress.empty())
@@ -50,35 +50,53 @@ void Settings::ReadFromAddon()
       m_enableWOL = false;
   }
 
-  m_timeoutWOL = kodi::GetSettingInt("woltimeout", 20);
+  m_timeoutWOL = kodi::addon::GetSettingInt("woltimeout", 20);
 
-  m_downloadGuideArtwork = kodi::GetSettingBoolean("guideartwork" ,DEFAULT_GUIDE_ARTWORK);
+  m_remoteAccess = kodi::addon::GetSettingBoolean("remoteaccess", false);
 
-  m_remoteAccess = kodi::GetSettingBoolean("remoteaccess", false);
+  m_liveStreamingMethod = kodi::addon::GetSettingEnum<eStreamingMethod>("livestreamingmethod5", DEFAULT_LIVE_STREAM);
 
-  m_flattenRecording = kodi::GetSettingBoolean("flattenrecording", false);
+  m_flattenRecording = kodi::addon::GetSettingBoolean("flattenrecording", false);
 
-  m_separateSeasons = kodi::GetSettingBoolean("separateseasons", false);
+  m_separateSeasons = kodi::addon::GetSettingBoolean("separateseasons", false);
 
-  m_kodiLook = kodi::GetSettingBoolean("kodilook", false);
+  m_prebuffer5 = kodi::addon::GetSettingInt("prebuffer5", 0);
 
-  m_prebuffer = kodi::GetSettingInt("prebuffer", 8);
+  m_liveChunkSize = kodi::addon::GetSettingInt("chunklivetv", 64);
 
-  m_prebuffer5 = kodi::GetSettingInt("prebuffer5", 0);
+  m_chunkRecording = kodi::addon::GetSettingInt("chunkrecording", 32);
 
-  m_liveChunkSize = kodi::GetSettingInt("chunklivetv", 64);
+  m_ignorePadding = kodi::addon::GetSettingBoolean("ignorepadding", true);
 
-  m_chunkRecording = kodi::GetSettingInt("chunkrecording", 32);
+  m_resolution = kodi::addon::GetSettingString("resolution",  "720");
 
-  m_ignorePadding = kodi::GetSettingBoolean("ignorepadding", true);
+  m_showRadio = kodi::addon::GetSettingBoolean("showradio", true);
 
-  m_resolution = kodi::GetSettingString("resolution",  "720");
+  m_backendResume = kodi::addon::GetSettingBoolean("backendresume", true);
 
-  m_showRadio = kodi::GetSettingBoolean("showradio", true);
+  m_connectionConfirmed = kodi::vfs::FileExists(connectionFlag);
 
-  m_backendResume = kodi::GetSettingBoolean("backendresume", true);
+  if (m_PIN != "0000" && m_remoteAccess)
+  {
+    m_downloadGuideArtwork = false;
+    m_sendSidWithMetadata = true;
+  }  else {
+    m_downloadGuideArtwork = kodi::addon::GetSettingBoolean("guideartwork" ,DEFAULT_GUIDE_ARTWORK);
+    m_sendSidWithMetadata = false;
+  }
+
+  m_guideArtPortrait = kodi::addon::GetSettingBoolean("guideartworkportrait", false);
+
+  m_genreString = kodi::addon::GetSettingBoolean("genrestring", false);
+
+  m_showRecordingSize = kodi::addon::GetSettingBoolean("recordingsize", false);
+
+  m_diskSpace = kodi::addon::GetSettingString("diskspace", "Default");
+
+  m_transcodedTimeshift = kodi::addon::GetSettingBoolean("ffmpegdirect", false);
+
+  m_castcrew = kodi::addon::GetSettingBoolean("castcrew", false);
 
-  m_connectionConfirmed = kodi::vfs::FileExists(connectionFlag);
 
   /* Log the current settings for debugging purposes */
   kodi::Log(ADDON_LOG_DEBUG, "settings: host='%s', port=%i, mac=%4.4s...", m_hostname.c_str(), m_port, m_hostMACAddress.c_str());
@@ -99,10 +117,10 @@ ADDON_STATUS Settings::ReadBackendSettin
       kodi::Log(ADDON_LOG_INFO, "NextPVR version: %d", m_backendVersion);
 
       // is the server new enough
-      if (m_backendVersion < 40204)
+      if (m_backendVersion < NEXTPVRC_MIN_VERSION)
       {
         kodi::Log(ADDON_LOG_ERROR, "NextPVR version '%d' is too old. Please upgrade to '%s' or higher!", m_backendVersion, NEXTPVRC_MIN_VERSION_STRING);
-        kodi::QueueNotification(QUEUE_ERROR, kodi::GetLocalizedString(30050), kodi::tools::StringUtils::Format(kodi::GetLocalizedString(30051).c_str(), NEXTPVRC_MIN_VERSION_STRING));
+        kodi::QueueNotification(QUEUE_ERROR, kodi::addon::GetLocalizedString(30050), kodi::tools::StringUtils::Format(kodi::addon::GetLocalizedString(30051).c_str(), NEXTPVRC_MIN_VERSION_STRING));
         return ADDON_STATUS_PERMANENT_FAILURE;
       }
     }
@@ -144,7 +162,7 @@ ADDON_STATUS Settings::ReadBackendSettin
       kodi::Log(ADDON_LOG_DEBUG, "Server MAC address %4.4s...", macAddress.c_str());
       if (m_hostMACAddress != macAddress)
       {
-        kodi::SetSettingString("host_mac", macAddress);
+        kodi::addon::SetSettingString("host_mac", macAddress);
       }
     }
   }
@@ -168,79 +186,12 @@ void Settings::SetConnection(bool status
 
 void Settings::SetVersionSpecificSettings()
 {
-  m_liveStreamingMethod = DEFAULT_LIVE_STREAM;
-
-  if ((m_backendVersion < 50000) != kodi::GetSettingBoolean("legacy", false))
-  {
-    kodi::SetSettingEnum<eStreamingMethod>("livestreamingmethod5", eStreamingMethod::Default);
-    kodi::SetSettingBoolean("legacy", m_backendVersion < 50000);
-  }
-
-  eStreamingMethod streamingMethod;
-  if (kodi::CheckSettingEnum<eStreamingMethod>("livestreamingmethod", streamingMethod))
-  {
-    m_liveStreamingMethod = streamingMethod;
-    // has v4 setting
-    if (m_backendVersion < 50000)
-    {
-      // previous Matrix clients had a transcoding option
-      if (m_liveStreamingMethod == eStreamingMethod::Transcoded)
-      {
-        m_liveStreamingMethod = eStreamingMethod::RealTime;
-        kodi::QueueNotification(QUEUE_ERROR, kodi::GetLocalizedString(30050), kodi::tools::StringUtils::Format(kodi::GetLocalizedString(30051).c_str(), "5"));
-      }
-    }
-    else if (m_backendVersion < 50002)
-    {
-      m_liveStreamingMethod = eStreamingMethod::RealTime;
-      kodi::QueueNotification(QUEUE_ERROR, kodi::GetLocalizedString(30050), kodi::tools::StringUtils::Format(kodi::GetLocalizedString(30051).c_str(), "5.0.3"));
-    }
-    else
-    {
-      // check for new v5 setting with no settings.xml
-      eStreamingMethod oldMethod = m_liveStreamingMethod;
-
-      if (kodi::CheckSettingEnum<eStreamingMethod>("livestreamingmethod5", streamingMethod))
-        m_liveStreamingMethod = streamingMethod;
-
-      if (m_liveStreamingMethod == eStreamingMethod::Default)
-        m_liveStreamingMethod = oldMethod;
-
-      if (m_liveStreamingMethod == RollingFile || m_liveStreamingMethod == Timeshift)
-        m_liveStreamingMethod = eStreamingMethod::ClientTimeshift;
-
-    }
-  }
-
-  if (m_backendVersion >= 50000)
-  {
-    m_sendSidWithMetadata = false;
-    if (m_PIN != "0000" && m_remoteAccess)
-    {
-      m_downloadGuideArtwork = false;
-      m_sendSidWithMetadata = true;
-    }
 
-    m_guideArtPortrait = kodi::GetSettingBoolean("guideartworkportrait", false);
+  // No version specific setting
 
-    m_genreString = kodi::GetSettingBoolean("genrestring", false);
-
-    m_showRecordingSize = kodi::GetSettingBoolean("recordingsize", false);
-
-    m_diskSpace = kodi::GetSettingString("diskspace", "Default");
-
-    m_transcodedTimeshift = kodi::GetSettingBoolean("ffmpegdirect", false);
-
-    m_castcrew = kodi::GetSettingBoolean("castcrew", false);
-  }
-  else
-  {
-    m_sendSidWithMetadata = true;
-    m_showRecordingSize = false;
-  }
 }
 
-ADDON_STATUS Settings::SetValue(const std::string& settingName, const kodi::CSettingValue& settingValue)
+ADDON_STATUS Settings::SetValue(const std::string& settingName, const kodi::addon::CSettingValue& settingValue)
 {
   //Connection
   if (g_pvrclient==nullptr)
@@ -277,29 +228,17 @@ ADDON_STATUS Settings::SetValue(const st
   else if (settingName == "diskspace")
     return SetStringSetting<ADDON_STATUS>(settingName, settingValue, m_diskSpace, ADDON_STATUS_OK, ADDON_STATUS_OK);
   else if (settingName == "flattenrecording")
-    return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_flattenRecording, ADDON_STATUS_NEED_SETTINGS, ADDON_STATUS_OK);
+    return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_flattenRecording, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
   else if (settingName == "ignorepadding")
     return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_ignorePadding, ADDON_STATUS_OK, ADDON_STATUS_OK);
   else if (settingName == "separateseasons")
-    return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_separateSeasons, ADDON_STATUS_NEED_SETTINGS, ADDON_STATUS_OK);
-  else if (settingName == "kodilook")
-    return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_kodiLook, ADDON_STATUS_NEED_SETTINGS, ADDON_STATUS_OK);
+    return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_separateSeasons, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
   else if (settingName == "genrestring")
     return SetSetting<bool, ADDON_STATUS>(settingName, settingValue, m_genreString, ADDON_STATUS_NEED_SETTINGS, ADDON_STATUS_OK);
   else if (settingName == "host_mac")
     return SetStringSetting<ADDON_STATUS>(settingName, settingValue, m_hostMACAddress, ADDON_STATUS_OK, ADDON_STATUS_OK);
-  else if (settingName == "livestreamingmethod" && m_backendVersion < 50000)
+  else if (settingName == "livestreamingmethod5")
     return SetEnumSetting<eStreamingMethod, ADDON_STATUS>(settingName, settingValue, m_liveStreamingMethod, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
-  else if (settingName == "livestreamingmethod5" && m_backendVersion >= 50000 && settingValue.GetEnum<const eStreamingMethod>() != eStreamingMethod::Default)
-  {
-    // eStreamingMethod::ClientTimeshift is internal only
-    eStreamingMethod method = m_liveStreamingMethod;
-    if (method == eStreamingMethod::ClientTimeshift)
-      method = eStreamingMethod::Timeshift;
-    return SetEnumSetting<eStreamingMethod, ADDON_STATUS>(settingName, settingValue, method, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
-  }
-  else if (settingName == "prebuffer")
-    return SetSetting<int, ADDON_STATUS>(settingName, settingValue, m_prebuffer, ADDON_STATUS_OK, ADDON_STATUS_OK);
   else if (settingName == "prebuffer5")
     return SetSetting<int, ADDON_STATUS>(settingName, settingValue, m_prebuffer5, ADDON_STATUS_OK, ADDON_STATUS_OK);
   else if (settingName == "chucksize")
diff -pruN 19.0.3+ds1-1/src/Settings.h 20.2.3+ds1-1/src/Settings.h
--- 19.0.3+ds1-1/src/Settings.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/Settings.h	2022-03-07 18:40:34.000000000 +0000
@@ -18,9 +18,6 @@ namespace NextPVR
 
   enum eStreamingMethod
   {
-    Default = -1,
-    Timeshift = 0,
-    RollingFile = 1,
     RealTime = 2,
     Transcoded = 3,
     ClientTimeshift = 4
@@ -32,8 +29,8 @@ namespace NextPVR
     Landscape = 1
   };
 
-  const static std::string PVRCLIENT_NEXTPVR_VERSION_STRING = "1.0.0.0";
-  constexpr char NEXTPVRC_MIN_VERSION_STRING[] = "4.2.4";
+  constexpr int NEXTPVRC_MIN_VERSION = 50200;
+  constexpr char NEXTPVRC_MIN_VERSION_STRING[] = "5.2.0";
   const static std::string DEFAULT_PROTOCOL = "http";
   const static std::string DEFAULT_HOST = "127.0.0.1";
   constexpr int DEFAULT_PORT = 8866;
@@ -43,7 +40,7 @@ namespace NextPVR
   constexpr bool DEFAULT_GUIDE_ARTWORK = false;
   constexpr eStreamingMethod DEFAULT_LIVE_STREAM = RealTime;
 
-  class ATTRIBUTE_HIDDEN Settings
+  class ATTR_DLL_LOCAL Settings
   {
   public:
 
@@ -68,7 +65,7 @@ namespace NextPVR
     };
 
     void ReadFromAddon();
-    ADDON_STATUS SetValue(const std::string& settingName, const kodi::CSettingValue& settingValue);
+    ADDON_STATUS SetValue(const std::string& settingName, const kodi::addon::CSettingValue& settingValue);
 
     //Connection
     std::string m_hostname = DEFAULT_HOST;
@@ -102,7 +99,6 @@ namespace NextPVR
     std::string m_diskSpace = "No";
     bool m_flattenRecording = false;
     bool m_separateSeasons = true;
-    bool m_kodiLook = false;
     int m_chunkRecording = 32;
 
     //Timers
@@ -115,8 +111,6 @@ namespace NextPVR
     int m_timeshiftBufferSeconds = 1200;
     eStreamingMethod m_liveStreamingMethod = RealTime;
     int m_liveChunkSize = 64;
-    //int m_prebuffer;
-    int m_prebuffer = 8;
     int m_prebuffer5 = 0;
     std::string m_resolution = "720";
     bool m_transcodedTimeshift = false;
@@ -129,7 +123,7 @@ namespace NextPVR
     void operator=(Settings const&) = delete;
 
     template<typename T, typename V>
-    V SetSetting(const std::string& settingName, const kodi::CSettingValue& settingValue, T& currentValue, V returnValueIfChanged, V defaultReturnValue)
+    V SetSetting(const std::string& settingName, const kodi::addon::CSettingValue& settingValue, T& currentValue, V returnValueIfChanged, V defaultReturnValue)
     {
       T newValue;
       if (std::is_same<T, float>::value)
@@ -155,7 +149,7 @@ namespace NextPVR
     };
 
     template<typename T, typename V>
-    V SetEnumSetting(const std::string& settingName, const kodi::CSettingValue& settingValue, T& currentValue, V returnValueIfChanged, V defaultReturnValue)
+    V SetEnumSetting(const std::string& settingName, const kodi::addon::CSettingValue& settingValue, T& currentValue, V returnValueIfChanged, V defaultReturnValue)
     {
       T newValue = settingValue.GetEnum<T>();
       if (newValue != currentValue)
@@ -169,7 +163,7 @@ namespace NextPVR
     };
 
     template<typename V>
-    V SetStringSetting(const std::string& settingName, const kodi::CSettingValue& settingValue, std::string& currentValue, V returnValueIfChanged, V defaultReturnValue)
+    V SetStringSetting(const std::string& settingName, const kodi::addon::CSettingValue& settingValue, std::string& currentValue, V returnValueIfChanged, V defaultReturnValue)
     {
       const std::string strSettingValue = settingValue.GetString();
 
diff -pruN 19.0.3+ds1-1/src/Timers.cpp 20.2.3+ds1-1/src/Timers.cpp
--- 19.0.3+ds1-1/src/Timers.cpp	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/Timers.cpp	2022-03-07 18:40:34.000000000 +0000
@@ -400,23 +400,23 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
   static std::vector<kodi::addon::PVRTypeIntValue> recordingLimitValues;
   if (recordingLimitValues.size() == 0)
   {
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_ASMANY, kodi::GetLocalizedString(MSG_KEEPALL)));
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_1, kodi::GetLocalizedString(MSG_KEEP1)));
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_2, kodi::GetLocalizedString(MSG_KEEP2)));
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_3, kodi::GetLocalizedString(MSG_KEEP3)));
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_4, kodi::GetLocalizedString(MSG_KEEP4)));
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_5, kodi::GetLocalizedString(MSG_KEEP5)));
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_6, kodi::GetLocalizedString(MSG_KEEP6)));
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_7, kodi::GetLocalizedString(MSG_KEEP7)));
-    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_10, kodi::GetLocalizedString(MSG_KEEP10)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_ASMANY, kodi::addon::GetLocalizedString(MSG_KEEPALL)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_1, kodi::addon::GetLocalizedString(MSG_KEEP1)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_2, kodi::addon::GetLocalizedString(MSG_KEEP2)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_3, kodi::addon::GetLocalizedString(MSG_KEEP3)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_4, kodi::addon::GetLocalizedString(MSG_KEEP4)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_5, kodi::addon::GetLocalizedString(MSG_KEEP5)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_6, kodi::addon::GetLocalizedString(MSG_KEEP6)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_7, kodi::addon::GetLocalizedString(MSG_KEEP7)));
+    recordingLimitValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_LIMIT_10, kodi::addon::GetLocalizedString(MSG_KEEP10)));
   }
 
   /* PVR_Timer.iPreventDuplicateEpisodes values and presentation.*/
   static std::vector<kodi::addon::PVRTypeIntValue> showTypeValues;
   if (showTypeValues.size() == 0)
   {
-    showTypeValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_SHOWTYPE_FIRSTRUNONLY, kodi::GetLocalizedString(MSG_SHOWTYPE_FIRSTRUNONLY)));
-    showTypeValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_SHOWTYPE_ANY, kodi::GetLocalizedString(MSG_SHOWTYPE_ANY)));
+    showTypeValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_SHOWTYPE_FIRSTRUNONLY, kodi::addon::GetLocalizedString(MSG_SHOWTYPE_FIRSTRUNONLY)));
+    showTypeValues.emplace_back(kodi::addon::PVRTypeIntValue(NEXTPVR_SHOWTYPE_ANY, kodi::addon::GetLocalizedString(MSG_SHOWTYPE_ANY)));
   }
 
   /* PVR_Timer.iRecordingGroup values and presentation */
@@ -445,7 +445,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
 
   static const unsigned int TIMER_REPEATING_MANUAL_ATTRIBS
     = PVR_TIMER_TYPE_IS_REPEATING |
-      static_cast<int>(m_settings.m_backendVersion >= ENABLE_DISABLE_VERSION ? PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE : PVR_TIMER_TYPE_ATTRIBUTE_NONE) |
+      static_cast<int>(PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE) |
       PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN |
       PVR_TIMER_TYPE_SUPPORTS_RECORDING_GROUP |
       PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS |
@@ -453,7 +453,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
 
   static const unsigned int TIMER_REPEATING_EPG_ATTRIBS
     = PVR_TIMER_TYPE_IS_REPEATING |
-      static_cast<int>(m_settings.m_backendVersion >= ENABLE_DISABLE_VERSION ? PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE : PVR_TIMER_TYPE_ATTRIBUTE_NONE) |
+      static_cast<int>(PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE) |
       PVR_TIMER_TYPE_SUPPORTS_START_END_MARGIN |
       PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS |
       PVR_TIMER_TYPE_SUPPORTS_RECORDING_GROUP |
@@ -474,7 +474,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
 
   static const unsigned int TIMER_REPEATING_KEYWORD_ATTRIBS
     = PVR_TIMER_TYPE_IS_REPEATING |
-      static_cast<int>(m_settings.m_backendVersion >= ENABLE_DISABLE_VERSION ? PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE : PVR_TIMER_TYPE_ATTRIBUTE_NONE) |
+      static_cast<int>(PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE) |
       PVR_TIMER_TYPE_SUPPORTS_RECORD_ONLY_NEW_EPISODES |
       PVR_TIMER_TYPE_SUPPORTS_ANY_CHANNEL |
       PVR_TIMER_TYPE_SUPPORTS_MAX_RECORDINGS;
@@ -492,7 +492,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
       /* Attributes. */
       TIMER_EPG_ATTRIBS,
       /* Description. */
-      kodi::GetLocalizedString(MSG_ONETIME_GUIDE), // "One time (guide)",
+      kodi::addon::GetLocalizedString(MSG_ONETIME_GUIDE), // "One time (guide)",
       /* Values definitions for attributes. */
       recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
     types.emplace_back(*t);
@@ -505,7 +505,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
         /* Attributes. */
         TIMER_MANUAL_ATTRIBS,
         /* Description. */
-        kodi::GetLocalizedString(MSG_ONETIME_MANUAL), // "One time (manual)",
+        kodi::addon::GetLocalizedString(MSG_ONETIME_MANUAL), // "One time (manual)",
         /* Values definitions for attributes. */
         recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
     types.emplace_back(*t);
@@ -518,7 +518,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
         /* Attributes. */
         TIMER_MANUAL_ATTRIBS | TIMER_REPEATING_MANUAL_ATTRIBS,
         /* Description. */
-        kodi::GetLocalizedString(MSG_REPEATING_MANUAL), // "Repeating (manual)"
+        kodi::addon::GetLocalizedString(MSG_REPEATING_MANUAL), // "Repeating (manual)"
         /* Values definitions for attributes. */
         recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
     types.emplace_back(*t);
@@ -531,7 +531,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
         /* Attributes. */
         TIMER_EPG_ATTRIBS | TIMER_REPEATING_EPG_ATTRIBS,
         /* Description. */
-        kodi::GetLocalizedString(MSG_REPEATING_GUIDE), // "Repeating (guide)"
+        kodi::addon::GetLocalizedString(MSG_REPEATING_GUIDE), // "Repeating (guide)"
         /* Values definitions for attributes. */
         recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
     types.emplace_back(*t);
@@ -544,7 +544,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
         /* Attributes. */
         TIMER_MANUAL_ATTRIBS | TIMER_CHILD_ATTRIBUTES,
         /* Description. */
-        kodi::GetLocalizedString(MSG_REPEATING_CHILD), // "Created by Repeating Timer"
+        kodi::addon::GetLocalizedString(MSG_REPEATING_CHILD), // "Created by Repeating Timer"
         /* Values definitions for attributes. */
         recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
     types.emplace_back(*t);
@@ -557,7 +557,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
         /* Attributes. */
         TIMER_EPG_ATTRIBS | TIMER_CHILD_ATTRIBUTES,
         /* Description. */
-        kodi::GetLocalizedString(MSG_REPEATING_CHILD), // "Created by Repeating Timer"
+        kodi::addon::GetLocalizedString(MSG_REPEATING_CHILD), // "Created by Repeating Timer"
         /* Values definitions for attributes. */
         recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
     types.emplace_back(*t);
@@ -570,7 +570,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
         /* Attributes. */
         TIMER_KEYWORD_ATTRIBS | TIMER_REPEATING_KEYWORD_ATTRIBS,
         /* Description. */
-        kodi::GetLocalizedString(MSG_REPEATING_KEYWORD), // "Repeating (keyword)"
+        kodi::addon::GetLocalizedString(MSG_REPEATING_KEYWORD), // "Repeating (keyword)"
         /* Values definitions for attributes. */
         recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
     types.emplace_back(*t);
@@ -582,7 +582,7 @@ PVR_ERROR Timers::GetTimerTypes(std::vec
         /* Attributes. */
         TIMER_ADVANCED_ATTRIBS | TIMER_REPEATING_KEYWORD_ATTRIBS,
         /* Description. */
-        kodi::GetLocalizedString(MSG_REPEATING_ADVANCED), // "Repeating (advanced)"
+        kodi::addon::GetLocalizedString(MSG_REPEATING_ADVANCED), // "Repeating (advanced)"
         /* Values definitions for attributes. */
         recordingLimitValues, m_defaultLimit, showTypeValues, m_defaultShowType, recordingGroupValues, 0);
     types.emplace_back(*t);
@@ -632,23 +632,20 @@ PVR_ERROR Timers::AddTimer(const kodi::a
     strcpy(preventDuplicates, "false");
 
   std::string enabled;
-  if (m_settings.m_backendVersion >= ENABLE_DISABLE_VERSION)
-  {
-    // NextPVR cannot create new disabled timers
-    if (timer.GetState() == PVR_TIMER_STATE_DISABLED)
-      if (timer.GetClientIndex() != PVR_TIMER_NO_CLIENT_INDEX)
-      {
-        enabled = "&enabled=false";
-      }
-      else
-      {
-        kodi::Log(ADDON_LOG_ERROR, "Cannot create a new disabled timer");
-        return PVR_ERROR_INVALID_PARAMETERS;
-      }
-    else if (timer.GetState() == PVR_TIMER_STATE_SCHEDULED)
+  // NextPVR cannot create new disabled timers
+  if (timer.GetState() == PVR_TIMER_STATE_DISABLED)
+    if (timer.GetClientIndex() != PVR_TIMER_NO_CLIENT_INDEX)
+    {
+      enabled = "&enabled=false";
+    }
+    else
     {
-      enabled = "&enabled=true";
+      kodi::Log(ADDON_LOG_ERROR, "Cannot create a new disabled timer");
+      return PVR_ERROR_INVALID_PARAMETERS;
     }
+  else if (timer.GetState() == PVR_TIMER_STATE_SCHEDULED)
+  {
+    enabled = "&enabled=true";
   }
 
   const std::string encodedName = UriEncode(timer.GetTitle());
@@ -704,11 +701,6 @@ PVR_ERROR Timers::AddTimer(const kodi::a
   case TIMER_ONCE_EPG_CHILD:
     kodi::Log(ADDON_LOG_DEBUG, "TIMER_ONCE_EPG_CHILD");
     // build one-off recording request
-    if (m_settings.m_backendVersion < 50102)
-    {
-      kodi::Log(ADDON_LOG_ERROR, "Feature added in NextPVR 5.1.2");
-      return PVR_ERROR_REJECTED;
-    }
     request = kodi::tools::StringUtils::Format("recording.save&recording_id=%d&recurring_id=%d&event_id=%d&pre_padding=%d&post_padding=%d&directory_id=%s",
       timer.GetClientIndex(),
       timer.GetParentClientIndex(),
diff -pruN 19.0.3+ds1-1/src/Timers.h 20.2.3+ds1-1/src/Timers.h
--- 19.0.3+ds1-1/src/Timers.h	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/src/Timers.h	2022-03-07 18:40:34.000000000 +0000
@@ -17,7 +17,6 @@ namespace NextPVR
 {
   /* Arbitrary time_t in the past well after epoch */
   constexpr time_t TIMER_DATE_MIN = 1359478800;  // Frodo PVR release date
-  constexpr unsigned int ENABLE_DISABLE_VERSION = 50101;
 
   const std::string TYPE_7_TITLE = "FIXED_TITLE_TYPE_7";
 
@@ -38,7 +37,7 @@ namespace NextPVR
   constexpr unsigned int TIMER_REPEATING_ADVANCED = TIMER_REPEATING_MIN + 3;
   constexpr unsigned int TIMER_REPEATING_MAX = TIMER_REPEATING_MIN + 3;
 
-  class ATTRIBUTE_HIDDEN Timers
+  class ATTR_DLL_LOCAL Timers
   {
     typedef enum
     {
diff -pruN 19.0.3+ds1-1/.travis.yml 20.2.3+ds1-1/.travis.yml
--- 19.0.3+ds1-1/.travis.yml	2022-01-02 11:36:09.000000000 +0000
+++ 20.2.3+ds1-1/.travis.yml	2022-03-07 18:40:34.000000000 +0000
@@ -35,7 +35,7 @@ matrix:
       osx_image: xcode10.2
 
 before_install:
-  - if [[ $DEBIAN_BUILD == true ]]; then sudo add-apt-repository -y ppa:team-xbmc/ppa; fi
+  - 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
 
@@ -45,12 +45,12 @@ before_install:
 #
 before_script:
   - if [[ $DEBIAN_BUILD != true ]]; then cd $TRAVIS_BUILD_DIR/..; fi
-  - if [[ $DEBIAN_BUILD != true ]]; then git clone --branch Matrix --depth=1 https://github.com/xbmc/xbmc.git; fi
+  - if [[ $DEBIAN_BUILD != true ]]; then git clone --branch master --depth=1 https://github.com/xbmc/xbmc.git; fi
   - if [[ $DEBIAN_BUILD != true ]]; then cd ${app_id} && mkdir build && cd build; fi
   - if [[ $DEBIAN_BUILD != true ]]; then mkdir -p definition/${app_id}; fi
   - if [[ $DEBIAN_BUILD != true ]]; then echo ${app_id} $TRAVIS_BUILD_DIR $TRAVIS_COMMIT > definition/${app_id}/${app_id}.txt; fi
   - if [[ $DEBIAN_BUILD != true ]]; then cmake -DADDONS_TO_BUILD=${app_id} -DADDON_SRC_PREFIX=$TRAVIS_BUILD_DIR/.. -DADDONS_DEFINITION_DIR=$TRAVIS_BUILD_DIR/build/definition -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$TRAVIS_BUILD_DIR/../xbmc/addons -DPACKAGE_ZIP=1 $TRAVIS_BUILD_DIR/../xbmc/cmake/addons; fi
-  - if [[ $DEBIAN_BUILD == true ]]; then wget https://raw.githubusercontent.com/xbmc/xbmc/Matrix/xbmc/addons/kodi-dev-kit/tools/debian-addon-package-test.sh && chmod +x ./debian-addon-package-test.sh; 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 $TRAVIS_BUILD_DIR; fi
 
 script:
